diff --git a/venv/lib/python3.10/site-packages/evaluate/__init__.py b/venv/lib/python3.10/site-packages/evaluate/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d3d7fc99ea1325805857b10f5e800e07c0691043 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/__init__.py @@ -0,0 +1,51 @@ +# flake8: noqa +# Copyright 2020 The HuggingFace Evaluate Authors and the TensorFlow Datasets Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +# pylint: enable=line-too-long +# pylint: disable=g-import-not-at-top,g-bad-import-order,wrong-import-position + +__version__ = "0.4.6" + +from packaging import version + + +SCRIPTS_VERSION = "main" if version.parse(__version__).is_devrelease else __version__ + +del version + +from .evaluation_suite import EvaluationSuite +from .evaluator import ( + AudioClassificationEvaluator, + AutomaticSpeechRecognitionEvaluator, + Evaluator, + ImageClassificationEvaluator, + QuestionAnsweringEvaluator, + SummarizationEvaluator, + Text2TextGenerationEvaluator, + TextClassificationEvaluator, + TextGenerationEvaluator, + TokenClassificationEvaluator, + TranslationEvaluator, + evaluator, +) +from .hub import push_to_hub +from .info import ComparisonInfo, EvaluationModuleInfo, MeasurementInfo, MetricInfo +from .inspect import inspect_evaluation_module, list_evaluation_modules +from .loading import load +from .module import CombinedEvaluations, Comparison, EvaluationModule, Measurement, Metric, combine +from .saving import save +from .utils import * +from .utils import gradio, logging diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1fd7260c39a2dbd03f6f4bdf74cbf2d04b0f12de Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/config.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..217584647d4fff0fa9be1abae61869c1ab8f310b Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/config.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/hub.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/hub.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0019c0fe8813dfc03f0f9a4ed06ad3580c0659d Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/hub.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/info.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/info.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f95ae3d2a7a5f68010152a1bd2848cf41667937 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/info.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/inspect.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/inspect.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da697bb1e5733dccf985502b8ebc38cbcf9b3697 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/inspect.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/loading.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/loading.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..890641d4059af5436ee24aad3ddeab2d9fb0be81 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/loading.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/module.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/module.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0702d5e8f30e8d75d61141d57dd8fcd975f1d97b Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/module.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/naming.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/naming.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51b4f4cae1fea9b82047aa9a8981dbac2692219e Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/naming.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/saving.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/saving.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bfde45abfda75fae45c26872191d31ebc7430a0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/saving.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/__pycache__/visualization.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/__pycache__/visualization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8339078d64550556ea604aee3eb8c33fb3ddbd8e Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/__pycache__/visualization.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/commands/__init__.py b/venv/lib/python3.10/site-packages/evaluate/commands/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/evaluate/commands/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/commands/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92dedf626479326a712a80acab300de780b8244f Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/commands/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/commands/__pycache__/evaluate_cli.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/commands/__pycache__/evaluate_cli.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ec712b9d411abaf64a5442ea68e3eb3f7a90e28 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/commands/__pycache__/evaluate_cli.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/commands/evaluate_cli.py b/venv/lib/python3.10/site-packages/evaluate/commands/evaluate_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..80593c4dfa0f96c8d3ea5ff6131c13c0a94181eb --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/commands/evaluate_cli.py @@ -0,0 +1,137 @@ +import argparse +import os +import subprocess +from pathlib import Path + +from cookiecutter.main import cookiecutter +from huggingface_hub import HfApi, Repository, create_repo + +from evaluate.utils.logging import get_logger + + +logger = get_logger(__name__) + +INSTRUCTIONS = """\ +A new repository for your module "{module_name}" of type "{module_type}" has been created at {output_dir} and pushed to the Hugging Face Hub: {repo_url}. + +Here are the next steps: +- implement the module logic in {module_slug}/{module_slug}.py +- document your module in {module_slug}/README.md +- add test cases for your module in {module_slug}/tests.py +- if your module has any dependencies update them in {module_slug}/requirements.txt + +You can test your module's widget locally by running: + +``` +python {output_dir}/{module_slug}/app.py +``` + +When you are happy with your changes you can push your changes with the following commands to the Hugging Face Hub: + +``` +cd {output_dir}/{module_slug} +git add . +git commit -m "Updating module" +git push +``` + +You should then see the update widget on the Hugging Face Hub: {repo_url} +And you can load your module in Python with the following code: + +``` +from evaluate import load +module = load("{namespace}/{module_slug}") +``` +""" + + +def main(): + parser = argparse.ArgumentParser("HuggingFace Evaluate CLI tool", usage="evaluate-cli []") + subparsers = parser.add_subparsers() + parser_create = subparsers.add_parser("create", help="Create new evaluation module.") + parser_create.add_argument( + "module_name", type=str, help='Pretty name of new evaluation module, e.g. "Recall" or "Exact Match".' + ) + parser_create.add_argument( + "--module_type", + default="metric", + type=str, + help="Type of module, has to be one of [metric|comparison|measurement].", + ) + parser_create.add_argument( + "--dataset_name", default="", type=str, help="Name of dataset if evaluation module is dataset specific." + ) + parser_create.add_argument("--module_description", type=str, help="Short description of evaluation module.") + parser_create.add_argument("--output_dir", default=Path.cwd(), type=str, help="Path to output directory.") + parser_create.add_argument( + "--organization", default=None, type=str, help="Organization on the Hub to push evaluation module to." + ) + parser_create.add_argument("--private", action="store_true", help="Sets evaluation module repository to private.") + args = vars(parser.parse_args()) + + if args["module_type"] not in ["metric", "comparison", "measurement"]: + raise ValueError("The module_type needs to be one of metric, comparison, or measurement") + + if "-" in args["module_name"]: + raise ValueError("Hyphens ('-') are not allowed in module names.") + + output_dir = Path(args["output_dir"]) + organization = args["organization"] + module_slug = args["module_name"].lower().replace(" ", "_") + + if organization is None: + hfapi = HfApi() + namespace = hfapi.whoami()["name"] + else: + namespace = organization + args["namespace"] = namespace + repo_url = f"https://huggingface.co/spaces/{namespace}/{module_slug}" + + try: + create_repo(namespace + "/" + module_slug, repo_type="space", space_sdk="gradio", private=args["private"]) + except Exception as exception: + logger.error( + f"Could not create Space for module at hf.co/spaces/{namespace}/{module_slug}. Make sure this space does not exist already." + ) + raise exception + subprocess.run( + f"git clone {repo_url}".split(), + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + check=True, + encoding="utf-8", + cwd=output_dir, + env=os.environ.copy(), + ) + + repo = Repository( + local_dir=output_dir / module_slug, + ) + + cookiecutter( + "https://github.com/huggingface/evaluate/", + directory="templates", + no_input=True, + extra_context=args, + output_dir=output_dir, + overwrite_if_exists=True, + ) + + repo.git_add() + repo.git_commit("add module default template") + repo.git_push() + + print( + INSTRUCTIONS.format( + module_name=args["module_name"], + module_type=args["module_type"], + module_slug=module_slug, + namespace=namespace, + repo_url=repo_url, + output_dir=output_dir, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.10/site-packages/evaluate/config.py b/venv/lib/python3.10/site-packages/evaluate/config.py new file mode 100644 index 0000000000000000000000000000000000000000..4909fa251ff82893d7c3c536bb111ae947735a8a --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/config.py @@ -0,0 +1,192 @@ +import importlib +import os +import platform +from pathlib import Path + +from packaging import version + +from .utils.logging import get_logger + + +logger = get_logger(__name__) + + +# Metrics +S3_METRICS_BUCKET_PREFIX = "https://s3.amazonaws.com/datasets.huggingface.co/datasets/metrics" +CLOUDFRONT_METRICS_DISTRIB_PREFIX = "https://cdn-datasets.huggingface.co/datasets/metric" +REPO_METRICS_URL = "https://raw.githubusercontent.com/huggingface/evaluate/{revision}/metrics/{path}/{name}" +REPO_MEASUREMENTS_URL = "https://raw.githubusercontent.com/huggingface/evaluate/{revision}/measurements/{path}/{name}" +REPO_COMPARISONS_URL = "https://raw.githubusercontent.com/huggingface/evaluate/{revision}/comparisons/{path}/{name}" + +# Evaluation module types +EVALUATION_MODULE_TYPES = ["metric", "comparison", "measurement"] + +# Hub +HF_ENDPOINT = os.environ.get("HF_ENDPOINT", "https://huggingface.co") +HF_LIST_ENDPOINT = HF_ENDPOINT + "/api/spaces?filter={type}" +HUB_EVALUATE_URL = HF_ENDPOINT + "/spaces/{path}/resolve/{revision}/{name}" +HUB_DEFAULT_VERSION = "main" + +PY_VERSION = version.parse(platform.python_version()) + +if PY_VERSION < version.parse("3.8"): + import importlib_metadata +else: + import importlib.metadata as importlib_metadata + +# General environment variables accepted values for booleans +ENV_VARS_TRUE_VALUES = {"1", "ON", "YES", "TRUE"} +ENV_VARS_TRUE_AND_AUTO_VALUES = ENV_VARS_TRUE_VALUES.union({"AUTO"}) + + +# Imports +PANDAS_VERSION = version.parse(importlib_metadata.version("pandas")) +PYARROW_VERSION = version.parse(importlib_metadata.version("pyarrow")) + +USE_TF = os.environ.get("USE_TF", "AUTO").upper() +USE_TORCH = os.environ.get("USE_TORCH", "AUTO").upper() +USE_JAX = os.environ.get("USE_JAX", "AUTO").upper() + +TORCH_VERSION = "N/A" +TORCH_AVAILABLE = False + +if USE_TORCH in ENV_VARS_TRUE_AND_AUTO_VALUES and USE_TF not in ENV_VARS_TRUE_VALUES: + TORCH_AVAILABLE = importlib.util.find_spec("torch") is not None + if TORCH_AVAILABLE: + try: + TORCH_VERSION = version.parse(importlib_metadata.version("torch")) + logger.info(f"PyTorch version {TORCH_VERSION} available.") + except importlib_metadata.PackageNotFoundError: + pass +else: + logger.info("Disabling PyTorch because USE_TF is set") + +TF_VERSION = "N/A" +TF_AVAILABLE = False + +if USE_TF in ENV_VARS_TRUE_AND_AUTO_VALUES and USE_TORCH not in ENV_VARS_TRUE_VALUES: + TF_AVAILABLE = importlib.util.find_spec("tensorflow") is not None + if TF_AVAILABLE: + # For the metadata, we have to look for both tensorflow and tensorflow-cpu + for package in [ + "tensorflow", + "tensorflow-cpu", + "tensorflow-gpu", + "tf-nightly", + "tf-nightly-cpu", + "tf-nightly-gpu", + "intel-tensorflow", + "tensorflow-rocm", + "tensorflow-macos", + ]: + try: + TF_VERSION = version.parse(importlib_metadata.version(package)) + except importlib_metadata.PackageNotFoundError: + continue + else: + break + else: + TF_AVAILABLE = False + if TF_AVAILABLE: + if TF_VERSION.major < 2: + logger.info(f"TensorFlow found but with version {TF_VERSION}. `datasets` requires version 2 minimum.") + TF_AVAILABLE = False + else: + logger.info(f"TensorFlow version {TF_VERSION} available.") +else: + logger.info("Disabling Tensorflow because USE_TORCH is set") + + +JAX_VERSION = "N/A" +JAX_AVAILABLE = False + +if USE_JAX in ENV_VARS_TRUE_AND_AUTO_VALUES: + JAX_AVAILABLE = importlib.util.find_spec("jax") is not None + if JAX_AVAILABLE: + try: + JAX_VERSION = version.parse(importlib_metadata.version("jax")) + logger.info(f"JAX version {JAX_VERSION} available.") + except importlib_metadata.PackageNotFoundError: + pass +else: + logger.info("Disabling JAX because USE_JAX is set to False") + + +# Cache location +DEFAULT_XDG_CACHE_HOME = "~/.cache" +XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME", DEFAULT_XDG_CACHE_HOME) +DEFAULT_HF_CACHE_HOME = os.path.join(XDG_CACHE_HOME, "huggingface") +HF_CACHE_HOME = os.path.expanduser(os.getenv("HF_HOME", DEFAULT_HF_CACHE_HOME)) + +DEFAULT_HF_EVALUATE_CACHE = os.path.join(HF_CACHE_HOME, "evaluate") +HF_EVALUATE_CACHE = Path(os.getenv("HF_EVALUATE_CACHE", DEFAULT_HF_EVALUATE_CACHE)) + +DEFAULT_HF_METRICS_CACHE = os.path.join(HF_CACHE_HOME, "metrics") +HF_METRICS_CACHE = Path(os.getenv("HF_METRICS_CACHE", DEFAULT_HF_METRICS_CACHE)) + +DEFAULT_HF_MODULES_CACHE = os.path.join(HF_CACHE_HOME, "modules") +HF_MODULES_CACHE = Path(os.getenv("HF_MODULES_CACHE", DEFAULT_HF_MODULES_CACHE)) + +DOWNLOADED_DATASETS_DIR = "downloads" +DEFAULT_DOWNLOADED_EVALUATE_PATH = os.path.join(HF_EVALUATE_CACHE, DOWNLOADED_DATASETS_DIR) +DOWNLOADED_EVALUATE_PATH = Path(os.getenv("HF_DATASETS_DOWNLOADED_EVALUATE_PATH", DEFAULT_DOWNLOADED_EVALUATE_PATH)) + +EXTRACTED_EVALUATE_DIR = "extracted" +DEFAULT_EXTRACTED_EVALUATE_PATH = os.path.join(DEFAULT_DOWNLOADED_EVALUATE_PATH, EXTRACTED_EVALUATE_DIR) +EXTRACTED_EVALUATE_PATH = Path(os.getenv("HF_DATASETS_EXTRACTED_EVALUATE_PATH", DEFAULT_EXTRACTED_EVALUATE_PATH)) + +# Download count for the website +HF_UPDATE_DOWNLOAD_COUNTS = ( + os.environ.get("HF_UPDATE_DOWNLOAD_COUNTS", "AUTO").upper() in ENV_VARS_TRUE_AND_AUTO_VALUES +) + +# Offline mode +HF_EVALUATE_OFFLINE = os.environ.get("HF_EVALUATE_OFFLINE", "AUTO").upper() in ENV_VARS_TRUE_VALUES + + +# File names +LICENSE_FILENAME = "LICENSE" +METRIC_INFO_FILENAME = "metric_info.json" +DATASETDICT_JSON_FILENAME = "dataset_dict.json" + +MODULE_NAME_FOR_DYNAMIC_MODULES = "evaluate_modules" + +HF_HUB_ALLOWED_TASKS = [ + "image-classification", + "translation", + "image-segmentation", + "fill-mask", + "automatic-speech-recognition", + "token-classification", + "sentence-similarity", + "audio-classification", + "question-answering", + "summarization", + "zero-shot-classification", + "table-to-text", + "feature-extraction", + "other", + "multiple-choice", + "text-classification", + "text-to-image", + "text2text-generation", + "zero-shot-image-classification", + "tabular-classification", + "tabular-regression", + "image-to-image", + "tabular-to-text", + "unconditional-image-generation", + "text-retrieval", + "text-to-speech", + "object-detection", + "audio-to-audio", + "text-generation", + "conversational", + "table-question-answering", + "visual-question-answering", + "image-to-text", + "reinforcement-learning", + "voice-activity-detection", + "time-series-forecasting", + "document-question-answering", +] diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluation_suite/__init__.py b/venv/lib/python3.10/site-packages/evaluate/evaluation_suite/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a306d8068a6d66b60fbcc5420bf0cbb334c36305 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluation_suite/__init__.py @@ -0,0 +1,128 @@ +import importlib +import inspect +from dataclasses import dataclass +from pathlib import Path +from typing import Callable, Dict, Optional, Union + +from datasets import Dataset, DownloadConfig, DownloadMode, load_dataset +from datasets.utils.version import Version + +from ..evaluator import evaluator +from ..loading import evaluation_module_factory +from ..utils.logging import get_logger + + +logger = get_logger(__name__) + + +@dataclass +class SubTask: + task_type: str + data: Optional[Union[str, Dataset]] = None + subset: Optional[str] = None + split: Optional[str] = None + data_preprocessor: Optional[Callable] = None + args_for_task: Optional[dict] = None + + def __post_init__(self): + if type(self.task_type) is not str: + raise ValueError(f"'task_type' must be type 'str', got {type(self.task_type)}") + if type(self.data) not in [Dataset, str]: + raise ValueError( + f"'data' must be an already-instantiated Dataset object or type 'str', got {type(self.data)}" + ) + if self.subset and type(self.subset) is not str: + raise ValueError(f"'subset' must be type 'str', got {type(self.subset)}") + if self.split and type(self.split) is not str: + raise ValueError(f"'split' must be type 'str', got {type(self.split)}") + if self.data_preprocessor and not callable(self.data_preprocessor): + raise ValueError(f"'data_preprocessor' must be a Callable', got {self.data_preprocessor}") + if self.args_for_task and type(self.args_for_task) is not dict: + raise ValueError(f"'args_for_task' must be type 'dict', got {type(self.args_for_task)}") + + +def import_main_class(module_path): + """Import a module at module_path and return the EvaluationSuite class""" + module = importlib.import_module(module_path) + + module_main_cls = None + for name, obj in module.__dict__.items(): + if isinstance(obj, type) and obj.__name__ == "Suite": + if inspect.isabstract(obj): + continue + module_main_cls = obj + break + + return module_main_cls + + +class EvaluationSuite: + """ + This class instantiates an evaluation suite made up of multiple tasks, where each task consists of a dataset and + an associated metric, and runs evaluation on a model or pipeline. Evaluation suites can be a Python script found + either locally or uploaded as a Space on the Hugging Face Hub. + Usage: + ```python + from evaluate import EvaluationSuite + suite = EvaluationSuite.load("evaluate/evaluation-suite-ci") + results = suite.run("lvwerra/distilbert-imdb") + ``` + """ + + def __init__(self, name): + self.name = name + + @staticmethod + def load( + path: str, + download_mode: Optional[DownloadMode] = None, + revision: Optional[Union[str, Version]] = None, + download_config: Optional[DownloadConfig] = None, + ): + download_mode = DownloadMode(download_mode or DownloadMode.REUSE_DATASET_IF_EXISTS) + evaluation_module = evaluation_module_factory( + path, module_type=None, revision=revision, download_config=download_config, download_mode=download_mode + ) + name = Path(path).stem + evaluation_cls = import_main_class(evaluation_module.module_path) + evaluation_instance = evaluation_cls(name) + + return evaluation_instance + + def __repr__(self): + self.tasks = [str(task) for task in self.suite] + return f'EvaluationSuite name: "{self.name}", ' f"Tasks: {self.tasks})" + + def assert_suite_nonempty(self): + if not self.suite: + raise ValueError( + "No evaluation tasks found. The EvaluationSuite must include at least one SubTask definition." + ) + + def run( + self, model_or_pipeline: Union[str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel"] # noqa: F821 + ) -> Dict[str, float]: + + self.assert_suite_nonempty() + + results_all = [] + for task in self.suite: + + task_name = task.data + + if task.data_preprocessor: # task requires extra preprocessing + ds = load_dataset(task.data, name=task.subset, split=task.split) + task.data = ds.map(task.data_preprocessor) + + task_evaluator = evaluator(task.task_type) + args_for_task = task.args_for_task + args_for_task["model_or_pipeline"] = model_or_pipeline + args_for_task["data"] = task.data + args_for_task["subset"] = task.subset + args_for_task["split"] = task.split + results = task_evaluator.compute(**args_for_task) + + results["task_name"] = task_name + "/" + task.subset if task.subset else task_name + results["data_preprocessor"] = str(task.data_preprocessor) if task.data_preprocessor is not None else None + results_all.append(results) + return results_all diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluation_suite/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluation_suite/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94f8647268bf8c28bd250202df63c19ed787e832 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluation_suite/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__init__.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a2fe4be8a1332417fb8515f019c1b7e8c41a58bf --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/__init__.py @@ -0,0 +1,140 @@ +# Copyright 2022 The HuggingFace Datasets Authors and the TensorFlow Datasets Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +try: + from transformers.pipelines import SUPPORTED_TASKS as SUPPORTED_PIPELINE_TASKS + from transformers.pipelines import TASK_ALIASES + from transformers.pipelines import check_task as check_pipeline_task + + TRANSFORMERS_AVAILABLE = True +except ImportError: + TRANSFORMERS_AVAILABLE = False + +from typing import Dict, List + +from .audio_classification import AudioClassificationEvaluator +from .automatic_speech_recognition import AutomaticSpeechRecognitionEvaluator +from .base import Evaluator +from .image_classification import ImageClassificationEvaluator +from .question_answering import QuestionAnsweringEvaluator +from .text2text_generation import SummarizationEvaluator, Text2TextGenerationEvaluator, TranslationEvaluator +from .text_classification import TextClassificationEvaluator +from .text_generation import TextGenerationEvaluator +from .token_classification import TokenClassificationEvaluator + + +SUPPORTED_EVALUATOR_TASKS = { + "text-classification": { + "implementation": TextClassificationEvaluator, + "default_metric_name": "accuracy", + }, + "image-classification": { + "implementation": ImageClassificationEvaluator, + "default_metric_name": "accuracy", + }, + "question-answering": { + "implementation": QuestionAnsweringEvaluator, + "default_metric_name": "squad", + }, + "token-classification": { + "implementation": TokenClassificationEvaluator, + "default_metric_name": "seqeval", + }, + "text-generation": { + "implementation": TextGenerationEvaluator, + "default_metric_name": "word_count", + }, + "text2text-generation": { + "implementation": Text2TextGenerationEvaluator, + "default_metric_name": "bleu", + }, + "summarization": { + "implementation": SummarizationEvaluator, + "default_metric_name": "rouge", + }, + "translation": { + "implementation": TranslationEvaluator, + "default_metric_name": "bleu", + }, + "automatic-speech-recognition": { + "implementation": AutomaticSpeechRecognitionEvaluator, + "default_metric_name": "wer", + }, + "audio-classification": { + "implementation": AudioClassificationEvaluator, + "default_metric_name": "accuracy", + }, +} + + +def get_supported_tasks() -> List[str]: + """ + Returns a list of supported task strings. + """ + return list(SUPPORTED_EVALUATOR_TASKS.keys()) + + +def check_task(task: str) -> Dict: + """ + Checks an incoming task string, to validate it's correct and returns the default Evaluator class and default metric + name. It first performs a check to validata that the string is a valid `Pipeline` task, then it checks if it's a + valid `Evaluator` task. `Evaluator` tasks are a substet of `Pipeline` tasks. + Args: + task (`str`): + The task defining which evaluator will be returned. Currently accepted tasks are: + - `"image-classification"` + - `"question-answering"` + - `"text-classification"` (alias `"sentiment-analysis"` available) + - `"token-classification"` + Returns: + task_defaults: `dict`, contains the implementasion class of a give Evaluator and the default metric name. + """ + if task in TASK_ALIASES: + task = TASK_ALIASES[task] + if not check_pipeline_task(task): + raise KeyError(f"Unknown task {task}, available tasks are: {get_supported_tasks()}.") + if task in SUPPORTED_EVALUATOR_TASKS.keys() and task in SUPPORTED_PIPELINE_TASKS.keys(): + return SUPPORTED_EVALUATOR_TASKS[task] + raise KeyError(f"Unknown task {task}, available tasks are: {get_supported_tasks()}.") + + +def evaluator(task: str = None) -> Evaluator: + """ + Utility factory method to build an [`Evaluator`]. + Evaluators encapsulate a task and a default metric name. They leverage `pipeline` functionality from `transformers` + to simplify the evaluation of multiple combinations of models, datasets and metrics for a given task. + Args: + task (`str`): + The task defining which evaluator will be returned. Currently accepted tasks are: + - `"image-classification"`: will return a [`ImageClassificationEvaluator`]. + - `"question-answering"`: will return a [`QuestionAnsweringEvaluator`]. + - `"text-classification"` (alias `"sentiment-analysis"` available): will return a [`TextClassificationEvaluator`]. + - `"token-classification"`: will return a [`TokenClassificationEvaluator`]. + Returns: + [`Evaluator`]: An evaluator suitable for the task. + Examples: + ```python + >>> from evaluate import evaluator + >>> # Sentiment analysis evaluator + >>> evaluator("sentiment-analysis") + ```""" + if not TRANSFORMERS_AVAILABLE: + raise ImportError( + "If you want to use the `Evaluator` you need `transformers`. Run `pip install evaluate[transformers]`." + ) + targeted_task = check_task(task) + evaluator_class = targeted_task["implementation"] + default_metric_name = targeted_task["default_metric_name"] + return evaluator_class(task=task, default_metric_name=default_metric_name) diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c5e89966e79e643281b15f71789739ecc89ee76 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/audio_classification.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/audio_classification.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6d2f929a7bb782a8024f95b39db4fde4498b7d0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/audio_classification.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/automatic_speech_recognition.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/automatic_speech_recognition.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4944d669dab996871c407e5cefda762cf3e87f2b Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/automatic_speech_recognition.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/base.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/base.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e15361525ea013b4796466ba776cd2ce80a8024 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/base.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/image_classification.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/image_classification.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19c1589669bec878801504d18862645c0b77569e Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/image_classification.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/question_answering.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/question_answering.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23a88b27f92a7f0f1926932a5e4c9e2a41adb59d Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/question_answering.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text2text_generation.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text2text_generation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6321d72c8ca98e903023444141a75681b228bc3f Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text2text_generation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text_classification.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text_classification.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5231135a32bb1c137886d5517eac7fbe2c172fe6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text_classification.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text_generation.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text_generation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06f764c125be1d462510b888f919c306a94b2da8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/text_generation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/token_classification.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/token_classification.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..747d00d4cca882b94a36057497690543f743a9ea Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/token_classification.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/utils.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7729a1562f6e666d8f761565371c2d91f9eabf34 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/evaluator/__pycache__/utils.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/audio_classification.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/audio_classification.py new file mode 100644 index 0000000000000000000000000000000000000000..685fb9fd8515f8506b89e9375948fea181f79a8f --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/audio_classification.py @@ -0,0 +1,151 @@ +# Copyright 2022 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from numbers import Number +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Union + +from datasets import Dataset +from typing_extensions import Literal + +from ..module import EvaluationModule +from ..utils.file_utils import add_end_docstrings, add_start_docstrings +from .base import EVALUATOR_COMPUTE_RETURN_DOCSTRING, EVALUTOR_COMPUTE_START_DOCSTRING, Evaluator + + +if TYPE_CHECKING: + from transformers import FeatureExtractionMixin, Pipeline, PreTrainedModel, PreTrainedTokenizer, TFPreTrainedModel + + +TASK_DOCUMENTATION = r""" + Examples: + + + + Remember that, in order to process audio files, you need ffmpeg installed (https://ffmpeg.org/download.html) + + + + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + + >>> task_evaluator = evaluator("audio-classification") + >>> data = load_dataset("superb", 'ks', split="test[:40]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline=""superb/wav2vec2-base-superb-ks"", + >>> data=data, + >>> label_column="label", + >>> input_column="file", + >>> metric="accuracy", + >>> label_mapping={0: "yes", 1: "no", 2: "up", 3: "down"} + >>> ) + ``` + + + + The evaluator supports raw audio data as well, in the form of a numpy array. However, be aware that calling + the audio column automatically decodes and resamples the audio files, which can be slow for large datasets. + + + + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + + >>> task_evaluator = evaluator("audio-classification") + >>> data = load_dataset("superb", 'ks', split="test[:40]") + >>> data = data.map(lambda example: {"audio": example["audio"]["array"]}) + >>> results = task_evaluator.compute( + >>> model_or_pipeline=""superb/wav2vec2-base-superb-ks"", + >>> data=data, + >>> label_column="label", + >>> input_column="audio", + >>> metric="accuracy", + >>> label_mapping={0: "yes", 1: "no", 2: "up", 3: "down"} + >>> ) + ``` +""" + + +class AudioClassificationEvaluator(Evaluator): + """ + Audio classification evaluator. + This audio classification evaluator can currently be loaded from [`evaluator`] using the default task name + `audio-classification`. + Methods in this class assume a data format compatible with the [`transformers.AudioClassificationPipeline`]. + """ + + PIPELINE_KWARGS = {} + + def __init__(self, task="audio-classification", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + def predictions_processor(self, predictions, label_mapping): + pred_label = [max(pred, key=lambda x: x["score"])["label"] for pred in predictions] + pred_label = [label_mapping[pred] if label_mapping is not None else pred for pred in pred_label] + + return {"predictions": pred_label} + + @add_start_docstrings(EVALUTOR_COMPUTE_START_DOCSTRING) + @add_end_docstrings(EVALUATOR_COMPUTE_RETURN_DOCSTRING, TASK_DOCUMENTATION) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + feature_extractor: Optional[Union[str, "FeatureExtractionMixin"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + input_column: str = "file", + label_column: str = "label", + label_mapping: Optional[Dict[str, Number]] = None, + ) -> Tuple[Dict[str, float], Any]: + + """ + input_column (`str`, defaults to `"file"`): + The name of the column containing either the audio files or a raw waveform, represented as a numpy array, in the dataset specified by `data`. + label_column (`str`, defaults to `"label"`): + The name of the column containing the labels in the dataset specified by `data`. + label_mapping (`Dict[str, Number]`, *optional*, defaults to `None`): + We want to map class labels defined by the model in the pipeline to values consistent with those + defined in the `label_column` of the `data` dataset. + """ + + result = super().compute( + model_or_pipeline=model_or_pipeline, + data=data, + subset=subset, + split=split, + metric=metric, + tokenizer=tokenizer, + feature_extractor=feature_extractor, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + device=device, + random_state=random_state, + input_column=input_column, + label_column=label_column, + label_mapping=label_mapping, + ) + + return result diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/automatic_speech_recognition.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/automatic_speech_recognition.py new file mode 100644 index 0000000000000000000000000000000000000000..ee423826cdd7bac384080b3db8a369cc59a53283 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/automatic_speech_recognition.py @@ -0,0 +1,112 @@ +# Copyright 2022 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Union + +from datasets import Dataset +from typing_extensions import Literal + +from ..module import EvaluationModule +from ..utils.file_utils import add_end_docstrings, add_start_docstrings +from .base import EVALUATOR_COMPUTE_RETURN_DOCSTRING, EVALUTOR_COMPUTE_START_DOCSTRING, Evaluator + + +if TYPE_CHECKING: + from transformers import Pipeline, PreTrainedModel, PreTrainedTokenizer, TFPreTrainedModel + + +TASK_DOCUMENTATION = r""" + Examples: + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("automatic-speech-recognition") + >>> data = load_dataset("mozilla-foundation/common_voice_11_0", "en", split="validation[:40]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline="https://huggingface.co/openai/whisper-tiny.en", + >>> data=data, + >>> input_column="path", + >>> label_column="sentence", + >>> metric="wer", + >>> ) + ``` +""" + + +class AutomaticSpeechRecognitionEvaluator(Evaluator): + """ + Automatic speech recognition evaluator. + This automatic speech recognition evaluator can currently be loaded from [`evaluator`] using the default task name + `automatic-speech-recognition`. + Methods in this class assume a data format compatible with the [`AutomaticSpeechRecognitionPipeline`]. + """ + + PIPELINE_KWARGS = {"truncation": True} + + def __init__(self, task="automatic-speech-recognition", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + def predictions_processor(self, predictions, label_mapping): + return {"predictions": [pred["text"] for pred in predictions]} + + @add_start_docstrings(EVALUTOR_COMPUTE_START_DOCSTRING) + @add_end_docstrings(EVALUATOR_COMPUTE_RETURN_DOCSTRING, TASK_DOCUMENTATION) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + input_column: str = "path", + label_column: str = "sentence", + generation_kwargs: dict = None, + ) -> Tuple[Dict[str, float], Any]: + """ + input_column (`str`, defaults to `"path"`): + the name of the column containing the input audio path in the dataset specified by `data`. + label_column (`str`, defaults to `"sentence"`): + the name of the column containing the labels in the dataset specified by `data`. + generation_kwargs (`Dict`, *optional*, defaults to `None`): + The generation kwargs are passed to the pipeline and set the text generation strategy. + """ + + if generation_kwargs is not None: + self.PIPELINE_KWARGS.update(generation_kwargs) + + result = super().compute( + model_or_pipeline=model_or_pipeline, + data=data, + subset=subset, + split=split, + metric=metric, + tokenizer=tokenizer, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + device=device, + random_state=random_state, + input_column=input_column, + label_column=label_column, + ) + + return result diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/base.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/base.py new file mode 100644 index 0000000000000000000000000000000000000000..09de31f19dedda5dbe38ce0295d8e5b0d962f389 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/base.py @@ -0,0 +1,544 @@ +# Copyright 2022 The HuggingFace Datasets Authors and the TensorFlow Datasets Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from numbers import Number +from typing import Any, Callable, Dict, List, Optional, Union + +# Lint as: python3 +from datasets import Dataset, load_dataset + +from evaluate.evaluator.utils import choose_split + + +try: + from scipy.stats import bootstrap + + SCIPY_AVAILABLE = True +except ImportError: + SCIPY_AVAILABLE = False + +try: + import transformers + from transformers import Pipeline, pipeline + + TRANSFORMERS_AVAILABLE = True +except ImportError: + TRANSFORMERS_AVAILABLE = False + +from time import perf_counter + +from typing_extensions import Literal + +from ..loading import load +from ..module import EvaluationModule +from ..utils.logging import get_logger +from .utils import DatasetColumn + + +logger = get_logger(__name__) + + +EVALUTOR_COMPUTE_START_DOCSTRING = r""" + Compute the metric for a given pipeline and dataset combination. + Args: + model_or_pipeline (`str` or `Pipeline` or `Callable` or `PreTrainedModel` or `TFPreTrainedModel`, defaults to `None`): + If the argument in not specified, we initialize the default pipeline for the task (in this case + `text-classification` or its alias - `sentiment-analysis`). If the argument is of the type `str` or + is a model instance, we use it to initialize a new `Pipeline` with the given model. Otherwise we assume the + argument specifies a pre-initialized pipeline. + data (`str` or `Dataset`, defaults to `None`): + Specifies the dataset we will run evaluation on. If it is of type `str`, we treat it as the dataset + name, and load it. Otherwise we assume it represents a pre-loaded dataset. + subset (`str`, defaults to `None`): + Defines which dataset subset to load. If `None` is passed the default subset is loaded. + split (`str`, defaults to `None`): + Defines which dataset split to load. If `None` is passed, infers based on the `choose_split` function. + metric (`str` or `EvaluationModule`, defaults to `None`): + Specifies the metric we use in evaluator. If it is of type `str`, we treat it as the metric name, and + load it. Otherwise we assume it represents a pre-loaded metric. + tokenizer (`str` or `PreTrainedTokenizer`, *optional*, defaults to `None`): + Argument can be used to overwrite a default tokenizer if `model_or_pipeline` represents a model for + which we build a pipeline. If `model_or_pipeline` is `None` or a pre-initialized pipeline, we ignore + this argument. + strategy (`Literal["simple", "bootstrap"]`, defaults to "simple"): + specifies the evaluation strategy. Possible values are: + - `"simple"` - we evaluate the metric and return the scores. + - `"bootstrap"` - on top of computing the metric scores, we calculate the confidence interval for each + of the returned metric keys, using `scipy`'s `bootstrap` method + https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bootstrap.html. + confidence_level (`float`, defaults to `0.95`): + The `confidence_level` value passed to `bootstrap` if `"bootstrap"` strategy is chosen. + n_resamples (`int`, defaults to `9999`): + The `n_resamples` value passed to `bootstrap` if `"bootstrap"` strategy is chosen. + device (`int`, defaults to `None`): + Device ordinal for CPU/GPU support of the pipeline. Setting this to -1 will leverage CPU, a positive + integer will run the model on the associated CUDA device ID. If `None` is provided it will be inferred and + CUDA:0 used if available, CPU otherwise. + random_state (`int`, *optional*, defaults to `None`): + The `random_state` value passed to `bootstrap` if `"bootstrap"` strategy is chosen. Useful for + debugging. +""" + +EVALUATOR_COMPUTE_RETURN_DOCSTRING = r""" + Return: + A `Dict`. The keys represent metric keys calculated for the `metric` spefied in function arguments. For the + `"simple"` strategy, the value is the metric score. For the `"bootstrap"` strategy, the value is a `Dict` + containing the score, the confidence interval and the standard error calculated for each metric key. +""" + + +class Evaluator(ABC): + """ + The [`Evaluator`] class is the class from which all evaluators inherit. Refer to this class for methods shared across + different evaluators. + Base class implementing evaluator operations. + """ + + PIPELINE_KWARGS = {} + METRIC_KWARGS = {} + + def __init__(self, task: str, default_metric_name: str = None): + if not TRANSFORMERS_AVAILABLE: + raise ImportError( + "If you want to use the `Evaluator` you need `transformers`. Run `pip install evaluate[evaluator]`." + ) + if not SCIPY_AVAILABLE: + raise ImportError( + "If you want to use the `Evaluator` you need `scipy>=1.7.1`. Run `pip install evaluate[evaluator]`." + ) + self.task = task + self.default_metric_name = default_metric_name + + @staticmethod + def _compute_confidence_interval( + metric, + metric_inputs, + metric_keys: List[str], + confidence_level: float = 0.95, + n_resamples: int = 9999, + random_state: Optional[int] = None, + ) -> Dict[str, Any]: + """ + A utility function enabling the confidence interval calculation for metrics computed + by the evaluator based on `scipy`'s `bootstrap` method. + """ + + # bootstrap only works with functions that use args and no kwargs + def build_args_metric(metric, key, **kwargs): + def args_metric(*args): + return metric.compute(**{k: v for k, v in zip(kwargs.keys(), args)})[key] + + return args_metric + + bootstrap_dict = {} + for key in metric_keys: + bs = bootstrap( + data=list(metric_inputs.values()), + statistic=build_args_metric(metric, key, **metric_inputs), + paired=True, + vectorized=False, + confidence_level=confidence_level, + n_resamples=n_resamples, + random_state=random_state, + ) + bootstrap_dict[key] = { + "confidence_interval": (bs.confidence_interval.low, bs.confidence_interval.high), + "standard_error": bs.standard_error, + } + return bootstrap_dict + + @staticmethod + def _compute_time_perf(start_time: float, end_time: float, num_samples: int) -> Dict[str, Any]: + """ + A utility function computing time performance metrics: + - `total_time_in_seconds` - pipeline inference runtime for the evaluation data in seconds, + - `samples_per_second` - pipeline throughput in the number of samples per second. + - `latency_in_seconds` - pipeline inference runtime for the evaluation data in seconds per sample, + + """ + latency = end_time - start_time + throughput = num_samples / latency + latency_sample = 1.0 / throughput + + return { + "total_time_in_seconds": latency, + "samples_per_second": throughput, + "latency_in_seconds": latency_sample, + } + + @staticmethod + def _infer_device() -> int: + """Helper function to check if GPU or CPU is available for inference.""" + # try infer with torch first + try: + import torch + + if torch.cuda.is_available(): + device = 0 # first GPU + else: + device = -1 # CPU + except ImportError: + # if not available try TF + try: + import tensorflow as tf + + if len(tf.config.list_physical_devices("GPU")) > 0: + device = 0 # first GPU + else: + device = -1 # CPU + except ImportError: + device = -1 + + if device == -1: + logger.info("No GPU found. The default device for pipeline inference is set to CPU.") + else: + logger.info("GPU found. The default device for pipeline inference is set to GPU (CUDA:0).") + + return device + + @abstractmethod + def predictions_processor(self, *args, **kwargs): + """ + A core method of the `Evaluator` class, which processes the pipeline outputs for compatibility with the metric. + """ + raise NotImplementedError() + + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + feature_extractor: Optional[Union[str, "FeatureExtractionMixin"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + input_column: str = "text", + label_column: str = "label", + label_mapping: Optional[Dict[str, Number]] = None, + ) -> Dict[str, float]: + + result = {} + + self.check_for_mismatch_in_device_setup(device, model_or_pipeline) + + # Prepare inputs + data = self.load_data(data=data, subset=subset, split=split) + metric_inputs, pipe_inputs = self.prepare_data(data=data, input_column=input_column, label_column=label_column) + pipe = self.prepare_pipeline( + model_or_pipeline=model_or_pipeline, + tokenizer=tokenizer, + feature_extractor=feature_extractor, + device=device, + ) + metric = self.prepare_metric(metric) + + # Compute predictions + predictions, perf_results = self.call_pipeline(pipe, pipe_inputs) + predictions = self.predictions_processor(predictions, label_mapping) + + metric_inputs.update(predictions) + + # Compute metrics from references and predictions + metric_results = self.compute_metric( + metric=metric, + metric_inputs=metric_inputs, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + random_state=random_state, + ) + + # TODO: To clarify why `wer` and `cer` return float + # even though metric.compute contract says that it + # returns Optional[dict]. + if type(metric_results) is float: + metric_results = {metric.name: metric_results} + + result.update(metric_results) + result.update(perf_results) + + return result + + @staticmethod + def check_for_mismatch_in_device_setup(device, model_or_pipeline): + if device is not None and device != -1 and isinstance(model_or_pipeline, Pipeline): + if model_or_pipeline.device.type == "cpu": + raise ValueError( + "The value of the `device` kwarg passed to `compute` suggests that this pipe should be run on an " + "accelerator, but the pipe was instantiated on CPU. Pass `device` to the pipeline during " + "initialization to use an accelerator, or pass `device=None` to `compute`. " + ) + elif device != model_or_pipeline.device.index: + raise ValueError( + f"This pipeline was instantiated on device {model_or_pipeline.device.index} but device={device} was passed to `compute`." + ) + + def check_required_columns(self, data: Union[str, Dataset], columns_names: Dict[str, str]): + """ + Ensure the columns required for the evaluation are present in the dataset. + + Args: + data (`str` or [`Dataset`]): + Specifies the dataset we will run evaluation on. + columns_names (`List[str]`): + List of column names to check in the dataset. The keys are the arguments to the [`evaluate.EvaluationModule.compute`] method, + while the values are the column names to check. + + Example: + + ```py + >>> from datasets import load_dataset + >>> from evaluate import evaluator + >>> data = load_dataset("rotten_tomatoes', split="train") + >>> evaluator.check_required_columns(data, {"input_column": "text", "label_column": "label"}) + ``` + """ + for input_name, column_name in columns_names.items(): + if column_name not in data.column_names: + raise ValueError( + f"Invalid `{input_name}` {column_name} specified. The dataset contains the following columns: {data.column_names}." + ) + + @staticmethod + def get_dataset_split(data, subset=None, split=None): + """ + Infers which split to use if `None` is given. + + Args: + data (`str`): + Name of dataset. + subset (`str`): + Name of config for datasets with multiple configurations (e.g. 'glue/cola'). + split (`str`, defaults to `None`): + Split to use. + Returns: + `split`: `str` containing which split to use + + Example: + + ```py + >>> from evaluate import evaluator + >>> evaluator("text-classification").get_dataset_split(data="rotten_tomatoes") + WARNING:evaluate.evaluator.base:Dataset split not defined! Automatically evaluating with split: TEST + 'test' + ``` + """ + if split is None: + split = choose_split(data, subset) + logger.warning(f"Dataset split not defined! Automatically evaluating with split: {split.upper()}") + return split + + def load_data(self, data: Union[str, Dataset], subset: str = None, split: str = None): + """ + Load dataset with given subset and split. + Args: + data ([`Dataset`] or `str`, defaults to `None`): + Specifies the dataset we will run evaluation on. If it is of + type `str`, we treat it as the dataset name, and load it. Otherwise we assume it represents a pre-loaded dataset. + subset (`str`, defaults to `None`): + Specifies dataset subset to be passed to `name` in `load_dataset`. To be + used with datasets with several configurations (e.g. glue/sst2). + split (`str`, defaults to `None`): + User-defined dataset split by name (e.g. train, validation, test). Supports slice-split (`test[:n]`). + If not defined and data is a `str` type, will automatically select the best one via `choose_split()`. + Returns: + data ([`Dataset`]): Loaded dataset which will be used for evaluation. + + Example: + + ```py + >>> from evaluate import evaluator + >>> evaluator("text-classification").load_data(data="rotten_tomatoes", split="train") + Dataset({ + features: ['text', 'label'], + num_rows: 8530 + }) + ``` + """ + if isinstance(data, str): + split = self.get_dataset_split(data, subset, split) + data = load_dataset(data, name=subset, split=split) + return data + elif isinstance(data, Dataset): + if split is not None or subset is not None: + logger.warning("`data` is a preloaded Dataset! Ignoring `subset` and `split`.") + return data + else: + raise ValueError( + "Please specify a valid `data` object - either a `str` with a name or a `Dataset` object." + ) + + def prepare_data(self, data: Dataset, input_column: str, label_column: str, *args, **kwargs): + """ + Prepare data. + + Args: + data ([`Dataset`]): + Specifies the dataset we will run evaluation on. + input_column (`str`, defaults to `"text"`): + The name of the column containing the text feature in the dataset specified by `data`. + second_input_column(`str`, *optional*): + The name of the column containing the second text feature if there is one. Otherwise, set to `None`. + label_column (`str`, defaults to `"label"`): + The name of the column containing the labels in the dataset specified by `data`. + Returns: + `dict`: metric inputs. + `list`: pipeline inputs. + + Example: + + ```py + >>> from evaluate import evaluator + >>> from datasets import load_dataset + + >>> ds = load_dataset("rotten_tomatoes", split="train") + >>> evaluator("text-classification").prepare_data(ds, input_column="text", second_input_column=None, label_column="label") + ``` + """ + + self.check_required_columns(data, {"input_column": input_column, "label_column": label_column}) + + return {"references": data[label_column]}, DatasetColumn(data, input_column) + + def prepare_pipeline( + self, + model_or_pipeline: Union[str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel"], # noqa: F821 + tokenizer: Union["PreTrainedTokenizerBase", "FeatureExtractionMixin"] = None, # noqa: F821 + feature_extractor: Union["PreTrainedTokenizerBase", "FeatureExtractionMixin"] = None, # noqa: F821 + device: int = None, + ): + """ + Prepare pipeline. + + Args: + model_or_pipeline (`str` or [`~transformers.Pipeline`] or `Callable` or [`~transformers.PreTrainedModel`] or [`~transformers.TFPreTrainedModel`], defaults to `None`): + If the argument in not specified, we initialize the default pipeline for the task. If the argument is of the type `str` or + is a model instance, we use it to initialize a new [`~transformers.Pipeline`] with the given model. Otherwise we assume the + argument specifies a pre-initialized pipeline. + preprocessor ([`~transformers.PreTrainedTokenizerBase`] or [`~transformers.FeatureExtractionMixin`], *optional*, defaults to `None`): + Argument can be used to overwrite a default preprocessor if `model_or_pipeline` represents a model for + which we build a pipeline. If `model_or_pipeline` is `None` or a pre-initialized pipeline, we ignore + this argument. + Returns: + The initialized pipeline. + + Example: + + ```py + >>> from evaluate import evaluator + >>> evaluator("text-classification").prepare_pipeline(model_or_pipeline="distilbert-base-uncased") + ``` + """ + + if device is None: + device = self._infer_device() + + if ( + isinstance(model_or_pipeline, str) + or isinstance(model_or_pipeline, transformers.PreTrainedModel) + or isinstance(model_or_pipeline, transformers.TFPreTrainedModel) + ): + pipe = pipeline( + self.task, + model=model_or_pipeline, + tokenizer=tokenizer, + feature_extractor=feature_extractor, + device=device, + ) + else: + if model_or_pipeline is None: + pipe = pipeline(self.task, device=device) + else: + pipe = model_or_pipeline + if tokenizer is not None and feature_extractor is not None: + logger.warning("Ignoring the value of the preprocessor argument (`tokenizer` or `feature_extractor`).") + if (pipe.task != self.task) and not (self.task == "translation" and pipe.task.startswith("translation")): + raise ValueError( + f"Incompatible `model_or_pipeline`. Please specify `model_or_pipeline` compatible with the `{self.task}` task." + ) + return pipe + + def prepare_metric(self, metric: Union[str, EvaluationModule]): + """ + Prepare metric. + + Args: + metric (`str` or [`EvaluationModule`], defaults to `None`): + Specifies the metric we use in evaluator. If it is of type `str`, we treat it as the metric name, and + load it. Otherwise we assume it represents a pre-loaded metric. + + Returns: + The loaded metric. + + Example: + + ```py + >>> from evaluate import evaluator + >>> evaluator("text-classification").prepare_metric("accuracy") + ``` + """ + # Prepare metric. + if metric is None: + if self.default_metric_name is None: + raise ValueError( + "`Evaluator` doesn't specify a default metric. Please specify a valid `metric` argument." + ) + metric = load(self.default_metric_name) + elif isinstance(metric, str): + metric = load(metric) + + return metric + + def call_pipeline(self, pipe, *args, **kwargs): + start_time = perf_counter() + pipe_output = pipe(*args, **kwargs, **self.PIPELINE_KWARGS) + end_time = perf_counter() + return pipe_output, self._compute_time_perf(start_time, end_time, len(pipe_output)) + + def compute_metric( + self, + metric: EvaluationModule, + metric_inputs: Dict, + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + random_state: Optional[int] = None, + ): + """Compute and return metrics.""" + result = metric.compute(**metric_inputs, **self.METRIC_KWARGS) + + if strategy == "bootstrap": + metric_keys = result.keys() + bootstrap_dict = self._compute_confidence_interval( + metric, + metric_inputs, + metric_keys, + confidence_level, + n_resamples, + random_state, + ) + for key in metric_keys: + bootstrap_dict[key]["score"] = result[key] + + return bootstrap_dict + + return result diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/image_classification.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/image_classification.py new file mode 100644 index 0000000000000000000000000000000000000000..82831458bb8789ce9c9418d6c19d4af4ba5b35a2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/image_classification.py @@ -0,0 +1,119 @@ +# Copyright 2022 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from numbers import Number +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Union + +from datasets import Dataset +from typing_extensions import Literal + +from ..module import EvaluationModule +from ..utils.file_utils import add_end_docstrings, add_start_docstrings +from .base import EVALUATOR_COMPUTE_RETURN_DOCSTRING, EVALUTOR_COMPUTE_START_DOCSTRING, Evaluator + + +if TYPE_CHECKING: + from transformers import FeatureExtractionMixin, Pipeline, PreTrainedModel, PreTrainedTokenizer, TFPreTrainedModel + + +TASK_DOCUMENTATION = r""" + Examples: + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("image-classification") + >>> data = load_dataset("beans", split="test[:40]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline="nateraw/vit-base-beans", + >>> data=data, + >>> label_column="labels", + >>> metric="accuracy", + >>> label_mapping={'angular_leaf_spot': 0, 'bean_rust': 1, 'healthy': 2}, + >>> strategy="bootstrap" + >>> ) + ``` +""" + + +class ImageClassificationEvaluator(Evaluator): + """ + Image classification evaluator. + This image classification evaluator can currently be loaded from [`evaluator`] using the default task name + `image-classification`. + Methods in this class assume a data format compatible with the [`ImageClassificationPipeline`]. + """ + + PIPELINE_KWARGS = {} + + def __init__(self, task="image-classification", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + def predictions_processor(self, predictions, label_mapping): + pred_label = [max(pred, key=lambda x: x["score"])["label"] for pred in predictions] + pred_label = [label_mapping[pred] if label_mapping is not None else pred for pred in pred_label] + + return {"predictions": pred_label} + + @add_start_docstrings(EVALUTOR_COMPUTE_START_DOCSTRING) + @add_end_docstrings(EVALUATOR_COMPUTE_RETURN_DOCSTRING, TASK_DOCUMENTATION) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + feature_extractor: Optional[Union[str, "FeatureExtractionMixin"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + input_column: str = "image", + label_column: str = "label", + label_mapping: Optional[Dict[str, Number]] = None, + ) -> Tuple[Dict[str, float], Any]: + + """ + input_column (`str`, defaults to `"image"`): + The name of the column containing the images as PIL ImageFile in the dataset specified by `data`. + label_column (`str`, defaults to `"label"`): + The name of the column containing the labels in the dataset specified by `data`. + label_mapping (`Dict[str, Number]`, *optional*, defaults to `None`): + We want to map class labels defined by the model in the pipeline to values consistent with those + defined in the `label_column` of the `data` dataset. + """ + + result = super().compute( + model_or_pipeline=model_or_pipeline, + data=data, + subset=subset, + split=split, + metric=metric, + tokenizer=tokenizer, + feature_extractor=feature_extractor, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + device=device, + random_state=random_state, + input_column=input_column, + label_column=label_column, + label_mapping=label_mapping, + ) + + return result diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/question_answering.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/question_answering.py new file mode 100644 index 0000000000000000000000000000000000000000..99b4190eebdda4e90617d0979fe23af2965d3204 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/question_answering.py @@ -0,0 +1,239 @@ +# Copyright 2022 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union + +# Lint as: python3 +from datasets import Dataset + + +try: + TRANSFORMERS_AVAILABLE = True +except ImportError: + TRANSFORMERS_AVAILABLE = False + +from typing_extensions import Literal + +from ..module import EvaluationModule +from ..utils.file_utils import add_end_docstrings, add_start_docstrings +from ..utils.logging import get_logger +from .base import EVALUATOR_COMPUTE_RETURN_DOCSTRING, EVALUTOR_COMPUTE_START_DOCSTRING, Evaluator +from .utils import DatasetColumn + + +if TYPE_CHECKING: + from transformers import Pipeline, PreTrainedModel, PreTrainedTokenizer, TFPreTrainedModel + + +logger = get_logger(__name__) + + +TASK_DOCUMENTATION = r""" + Examples: + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("question-answering") + >>> data = load_dataset("squad", split="validation[:2]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline="sshleifer/tiny-distilbert-base-cased-distilled-squad", + >>> data=data, + >>> metric="squad", + >>> ) + ``` + + + + Datasets where the answer may be missing in the context are supported, for example SQuAD v2 dataset. In this case, it is safer to pass `squad_v2_format=True` to + the compute() call. + + + + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("question-answering") + >>> data = load_dataset("squad_v2", split="validation[:2]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline="mrm8488/bert-tiny-finetuned-squadv2", + >>> data=data, + >>> metric="squad_v2", + >>> squad_v2_format=True, + >>> ) + ``` +""" + + +class QuestionAnsweringEvaluator(Evaluator): + """ + Question answering evaluator. This evaluator handles + [**extractive** question answering](https://huggingface.co/docs/transformers/task_summary#extractive-question-answering), + where the answer to the question is extracted from a context. + + This question answering evaluator can currently be loaded from [`evaluator`] using the default task name + `question-answering`. + + Methods in this class assume a data format compatible with the + [`~transformers.QuestionAnsweringPipeline`]. + """ + + PIPELINE_KWARGS = {} + + def __init__(self, task="question-answering", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + def prepare_data( + self, data: Dataset, question_column: str, context_column: str, id_column: str, label_column: str + ): + """Prepare data.""" + if data is None: + raise ValueError( + "Please specify a valid `data` object - either a `str` with a name or a `Dataset` object." + ) + self.check_required_columns( + data, + { + "question_column": question_column, + "context_column": context_column, + "id_column": id_column, + "label_column": label_column, + }, + ) + + metric_inputs = dict() + metric_inputs["references"] = [ + {"id": element[id_column], "answers": element[label_column]} for element in data + ] + + return metric_inputs, { + "question": DatasetColumn(data, question_column), + "context": DatasetColumn(data, context_column), + } + + def is_squad_v2_format(self, data: Dataset, label_column: str = "answers"): + """ + Check if the provided dataset follows the squad v2 data schema, namely possible samples where the answer is not in the context. + In this case, the answer text list should be `[]`. + """ + original_num_rows = data.num_rows + nonempty_num_rows = data.filter( + lambda x: len(x[label_column]["text"]) > 0, load_from_cache_file=False + ).num_rows + if original_num_rows > nonempty_num_rows: + return True + else: + return False + + def predictions_processor(self, predictions: List, squad_v2_format: bool, ids: List): + result = [] + for i in range(len(predictions)): + pred = {"prediction_text": predictions[i]["answer"], "id": ids[i]} + if squad_v2_format: + pred["no_answer_probability"] = predictions[i]["score"] + result.append(pred) + return {"predictions": result} + + @add_start_docstrings(EVALUTOR_COMPUTE_START_DOCSTRING) + @add_end_docstrings(EVALUATOR_COMPUTE_RETURN_DOCSTRING, TASK_DOCUMENTATION) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + question_column: str = "question", + context_column: str = "context", + id_column: str = "id", + label_column: str = "answers", + squad_v2_format: Optional[bool] = None, + ) -> Tuple[Dict[str, float], Any]: + """ + question_column (`str`, defaults to `"question"`): + The name of the column containing the question in the dataset specified by `data`. + context_column (`str`, defaults to `"context"`): + The name of the column containing the context in the dataset specified by `data`. + id_column (`str`, defaults to `"id"`): + The name of the column containing the identification field of the question and answer pair in the + dataset specified by `data`. + label_column (`str`, defaults to `"answers"`): + The name of the column containing the answers in the dataset specified by `data`. + squad_v2_format (`bool`, *optional*, defaults to `None`): + Whether the dataset follows the format of squad_v2 dataset. This is the case when the provided dataset + has questions where the answer is not in the context, more specifically when are answers as + `{"text": [], "answer_start": []}` in the answer column. If all questions have at least one answer, this parameter + should be set to `False`. If this parameter is not provided, the format will be automatically inferred. + """ + result = {} + self.check_for_mismatch_in_device_setup(device, model_or_pipeline) + + data = self.load_data(data=data, subset=subset, split=split) + metric_inputs, pipe_inputs = self.prepare_data( + data=data, + question_column=question_column, + context_column=context_column, + id_column=id_column, + label_column=label_column, + ) + + if squad_v2_format is None: + squad_v2_format = self.is_squad_v2_format(data=data, label_column=label_column) + logger.warning( + f"`squad_v2_format` parameter not provided to QuestionAnsweringEvaluator.compute(). Automatically inferred `squad_v2_format` as {squad_v2_format}." + ) + pipe = self.prepare_pipeline(model_or_pipeline=model_or_pipeline, tokenizer=tokenizer, device=device) + + metric = self.prepare_metric(metric) + + if squad_v2_format and metric.name == "squad": + logger.warning( + "The dataset has SQuAD v2 format but you are using the SQuAD metric. Consider passing the 'squad_v2' metric." + ) + if not squad_v2_format and metric.name == "squad_v2": + logger.warning( + "The dataset has SQuAD v1 format but you are using the SQuAD v2 metric. Consider passing the 'squad' metric." + ) + + if squad_v2_format: + self.PIPELINE_KWARGS["handle_impossible_answer"] = True + else: + self.PIPELINE_KWARGS["handle_impossible_answer"] = False + + # Compute predictions + predictions, perf_results = self.call_pipeline(pipe, **pipe_inputs) + predictions = self.predictions_processor(predictions, squad_v2_format=squad_v2_format, ids=data[id_column]) + metric_inputs.update(predictions) + + # Compute metrics from references and predictions + metric_results = self.compute_metric( + metric=metric, + metric_inputs=metric_inputs, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + random_state=random_state, + ) + + result.update(metric_results) + result.update(perf_results) + + return result diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/text2text_generation.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/text2text_generation.py new file mode 100644 index 0000000000000000000000000000000000000000..6dfd2c035695b38c1e4f0d9d4929b12c6be30920 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/text2text_generation.py @@ -0,0 +1,267 @@ +# Copyright 2022 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Union + +from datasets import Dataset +from typing_extensions import Literal + +from ..module import EvaluationModule +from ..utils.file_utils import add_start_docstrings +from .base import EVALUATOR_COMPUTE_RETURN_DOCSTRING, EVALUTOR_COMPUTE_START_DOCSTRING, Evaluator + + +if TYPE_CHECKING: + from transformers import Pipeline, PreTrainedModel, PreTrainedTokenizer, TFPreTrainedModel + + +TASK_DOCUMENTATION_KWARGS = r""" + input_column (`str`, defaults to `"text"`): + the name of the column containing the input text in the dataset specified by `data`. + label_column (`str`, defaults to `"label"`): + the name of the column containing the labels in the dataset specified by `data`. + generation_kwargs (`Dict`, *optional*, defaults to `None`): + The generation kwargs are passed to the pipeline and set the text generation strategy. +""" + +TEXT2TEXT_TASK_DOCSTRING_EXAMPLE = r""" + Examples: + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("text2text-generation") + >>> data = load_dataset("cnn_dailymail", "3.0.0", split="validation[:40]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline="facebook/bart-large-cnn", + >>> data=data, + >>> input_column="article", + >>> label_column="highlights", + >>> metric="rouge", + >>> ) + ``` +""" + +SUMMARIZATION_TASK_DOCSTRING_EXAMPLE = r""" + Examples: + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("summarization") + >>> data = load_dataset("cnn_dailymail", "3.0.0", split="validation[:40]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline="facebook/bart-large-cnn", + >>> data=data, + >>> input_column="article", + >>> label_column="highlights", + >>> ) + ``` +""" + + +TRANSLATION_TASK_DOCSTRING_EXAMPLE = r""" + Examples: + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("translation") + >>> data = load_dataset("wmt19", "fr-de", split="validation[:40]") + >>> data = data.map(lambda x: {"text": x["translation"]["de"], "label": x["translation"]["fr"]}) + >>> results = task_evaluator.compute( + >>> model_or_pipeline="Helsinki-NLP/opus-mt-de-fr", + >>> data=data, + >>> ) + ``` +""" + + +class Text2TextGenerationEvaluator(Evaluator): + """ + Text2Text generation evaluator. + This Text2Text generation evaluator can currently be loaded from [`evaluator`] using the default task name + `text2text-generation`. + Methods in this class assume a data format compatible with the [`~transformers.Text2TextGenerationPipeline`]. + """ + + PREDICTION_PREFIX = "generated" + PIPELINE_KWARGS = {"truncation": True} + + def __init__(self, task="text2text-generation", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + def predictions_processor(self, predictions, label_mapping): + return {"predictions": [pred[f"{self.PREDICTION_PREFIX}_text"] for pred in predictions]} + + @add_start_docstrings( + EVALUTOR_COMPUTE_START_DOCSTRING, + TASK_DOCUMENTATION_KWARGS, + EVALUATOR_COMPUTE_RETURN_DOCSTRING, + TEXT2TEXT_TASK_DOCSTRING_EXAMPLE, + ) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + input_column: str = "text", + label_column: str = "label", + generation_kwargs: dict = None, + ) -> Tuple[Dict[str, float], Any]: + if generation_kwargs is not None: + self.PIPELINE_KWARGS.update(generation_kwargs) + + result = super().compute( + model_or_pipeline=model_or_pipeline, + data=data, + subset=subset, + split=split, + metric=metric, + tokenizer=tokenizer, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + device=device, + random_state=random_state, + input_column=input_column, + label_column=label_column, + ) + + return result + + +class SummarizationEvaluator(Text2TextGenerationEvaluator): + """ + Text summarization evaluator. + This text summarization evaluator can currently be loaded from [`evaluator`] using the default task name + `summarization`. + Methods in this class assume a data format compatible with the [`SummarizationEvaluator`]. + """ + + PREDICTION_PREFIX = "summary" + PIPELINE_KWARGS = {"truncation": True} + + def __init__(self, task="summarization", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + @add_start_docstrings( + EVALUTOR_COMPUTE_START_DOCSTRING, + TASK_DOCUMENTATION_KWARGS, + EVALUATOR_COMPUTE_RETURN_DOCSTRING, + SUMMARIZATION_TASK_DOCSTRING_EXAMPLE, + ) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + input_column: str = "text", + label_column: str = "label", + generation_kwargs: dict = None, + ) -> Tuple[Dict[str, float], Any]: + result = super().compute( + model_or_pipeline=model_or_pipeline, + data=data, + subset=subset, + split=split, + metric=metric, + tokenizer=tokenizer, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + device=device, + random_state=random_state, + input_column=input_column, + label_column=label_column, + generation_kwargs=generation_kwargs, + ) + + return result + + +class TranslationEvaluator(Text2TextGenerationEvaluator): + """ + Translation evaluator. + This translation generation evaluator can currently be loaded from [`evaluator`] using the default task name + `translation`. + Methods in this class assume a data format compatible with the [`~transformers.TranslationPipeline`]. + """ + + PREDICTION_PREFIX = "translation" + PIPELINE_KWARGS = {"truncation": True} + + def __init__(self, task="translation", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + @add_start_docstrings( + EVALUTOR_COMPUTE_START_DOCSTRING, + TASK_DOCUMENTATION_KWARGS, + EVALUATOR_COMPUTE_RETURN_DOCSTRING, + TRANSLATION_TASK_DOCSTRING_EXAMPLE, + ) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + input_column: str = "text", + label_column: str = "label", + generation_kwargs: dict = None, + ) -> Tuple[Dict[str, float], Any]: + result = super().compute( + model_or_pipeline=model_or_pipeline, + data=data, + subset=subset, + split=split, + metric=metric, + tokenizer=tokenizer, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + device=device, + random_state=random_state, + input_column=input_column, + label_column=label_column, + generation_kwargs=generation_kwargs, + ) + + return result diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/text_classification.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/text_classification.py new file mode 100644 index 0000000000000000000000000000000000000000..200eb01d70336148db473edebebc96e3137c5799 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/text_classification.py @@ -0,0 +1,160 @@ +# Copyright 2022 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from numbers import Number +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Union + +from datasets import Dataset, load_dataset +from typing_extensions import Literal + +from ..module import EvaluationModule +from ..utils.file_utils import add_end_docstrings, add_start_docstrings +from .base import EVALUATOR_COMPUTE_RETURN_DOCSTRING, EVALUTOR_COMPUTE_START_DOCSTRING, Evaluator +from .utils import DatasetColumnPair + + +if TYPE_CHECKING: + from transformers import FeatureExtractionMixin, Pipeline, PreTrainedModel, PreTrainedTokenizer, TFPreTrainedModel + + +TASK_DOCUMENTATION = r""" + Examples: + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("text-classification") + >>> data = load_dataset("imdb", split="test[:2]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline="huggingface/prunebert-base-uncased-6-finepruned-w-distil-mnli", + >>> data=data, + >>> metric="accuracy", + >>> label_mapping={"LABEL_0": 0.0, "LABEL_1": 1.0}, + >>> strategy="bootstrap", + >>> n_resamples=10, + >>> random_state=0 + >>> ) + ``` +""" + + +class TextClassificationEvaluator(Evaluator): + """ + Text classification evaluator. + This text classification evaluator can currently be loaded from [`evaluator`] using the default task name + `text-classification` or with a `"sentiment-analysis"` alias. + Methods in this class assume a data format compatible with the [`~transformers.TextClassificationPipeline`] - a single textual + feature as input and a categorical label as output. + """ + + PIPELINE_KWARGS = {"truncation": True} + + def __init__(self, task="text-classification", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + def prepare_data(self, data: Union[str, Dataset], input_column: str, second_input_column: str, label_column: str): + if data is None: + raise ValueError( + "Please specify a valid `data` object - either a `str` with a name or a `Dataset` object." + ) + + self.check_required_columns(data, {"input_column": input_column, "label_column": label_column}) + + if second_input_column is not None: + self.check_required_columns(data, {"second_input_column": second_input_column}) + + data = load_dataset(data) if isinstance(data, str) else data + + return {"references": data[label_column]}, DatasetColumnPair( + data, input_column, second_input_column, "text", "text_pair" + ) + + def predictions_processor(self, predictions, label_mapping): + predictions = [ + label_mapping[element["label"]] if label_mapping is not None else element["label"] + for element in predictions + ] + return {"predictions": predictions} + + @add_start_docstrings(EVALUTOR_COMPUTE_START_DOCSTRING) + @add_end_docstrings(EVALUATOR_COMPUTE_RETURN_DOCSTRING, TASK_DOCUMENTATION) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: Optional[str] = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + feature_extractor: Optional[Union[str, "FeatureExtractionMixin"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: int = None, + random_state: Optional[int] = None, + input_column: str = "text", + second_input_column: Optional[str] = None, + label_column: str = "label", + label_mapping: Optional[Dict[str, Number]] = None, + ) -> Tuple[Dict[str, float], Any]: + """ + input_column (`str`, *optional*, defaults to `"text"`): + The name of the column containing the text feature in the dataset specified by `data`. + second_input_column (`str`, *optional*, defaults to `None`): + The name of the second column containing the text features. This may be useful for classification tasks + as MNLI, where two columns are used. + label_column (`str`, defaults to `"label"`): + The name of the column containing the labels in the dataset specified by `data`. + label_mapping (`Dict[str, Number]`, *optional*, defaults to `None`): + We want to map class labels defined by the model in the pipeline to values consistent with those + defined in the `label_column` of the `data` dataset. + """ + + result = {} + + self.check_for_mismatch_in_device_setup(device, model_or_pipeline) + + # Prepare inputs + data = self.load_data(data=data, subset=subset, split=split) + metric_inputs, pipe_inputs = self.prepare_data( + data=data, input_column=input_column, second_input_column=second_input_column, label_column=label_column + ) + pipe = self.prepare_pipeline( + model_or_pipeline=model_or_pipeline, + tokenizer=tokenizer, + feature_extractor=feature_extractor, + device=device, + ) + metric = self.prepare_metric(metric) + + # Compute predictions + predictions, perf_results = self.call_pipeline(pipe, pipe_inputs) + predictions = self.predictions_processor(predictions, label_mapping) + metric_inputs.update(predictions) + + # Compute metrics from references and predictions + metric_results = self.compute_metric( + metric=metric, + metric_inputs=metric_inputs, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + random_state=random_state, + ) + + result.update(metric_results) + result.update(perf_results) + + return result diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/text_generation.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/text_generation.py new file mode 100644 index 0000000000000000000000000000000000000000..15522e860f7eb6fc693780f637337c0fdb22a21c --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/text_generation.py @@ -0,0 +1,69 @@ +# Copyright 2022 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Tuple + +from datasets import Dataset + +from .base import Evaluator +from .utils import DatasetColumn + + +TASK_DOCUMENTATION_KWARGS = r""" + input_column (`str`, defaults to `"text"`): + the name of the column containing the input text in the dataset specified by `data`. + generation_kwargs (`Dict`, *optional*, defaults to `None`): + The generation kwargs are passed to the pipeline and set the text generation strategy. +""" + + +class TextGenerationEvaluator(Evaluator): + """ + Text generation evaluator. + This Text generation evaluator can currently be loaded from [`evaluator`] using the default task name + `text-generation`. + Methods in this class assume a data format compatible with the [`~transformers.TextGenerationPipeline`]. + """ + + def predictions_processor(self, predictions, *args, **kwargs): + """ + Args: + predictions: A list of lists of dicts + + Returns: + `dict`: All the generated texts are flattened and stored under the "data" key. + """ + return {"data": [pred[f"{self.predictions_prefix}_text"] for pred_list in predictions for pred in pred_list]} + + def __init__(self, task="text-generation", default_metric_name=None, predictions_prefix: str = "generated"): + super().__init__(task=task, default_metric_name=default_metric_name) + self.predictions_prefix = predictions_prefix + + def prepare_data(self, data: Dataset, input_column: str, *args, **kwargs) -> Tuple[Dict, DatasetColumn]: + """ + Prepare data. + + Args: + data ([`Dataset`]): + Specifies the dataset we will run evaluation on. + input_column (`str`, defaults to `"text"`): + The name of the column containing the text feature in the dataset specified by `data`. + Returns: + `dict`: metric inputs. + `list`: pipeline inputs. + """ + + self.check_required_columns(data, {"input_column": input_column}) + + return {}, DatasetColumn(data, input_column) diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/token_classification.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/token_classification.py new file mode 100644 index 0000000000000000000000000000000000000000..ba08ebd58d72417eed4e20c93a46c53adaa49811 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/token_classification.py @@ -0,0 +1,278 @@ +# Copyright 2022 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union + +from datasets import ClassLabel, Dataset, Sequence +from typing_extensions import Literal + +from ..module import EvaluationModule +from ..utils.file_utils import add_end_docstrings, add_start_docstrings +from .base import EVALUATOR_COMPUTE_RETURN_DOCSTRING, EVALUTOR_COMPUTE_START_DOCSTRING, Evaluator +from .utils import DatasetColumn + + +if TYPE_CHECKING: + from transformers import Pipeline, PreTrainedModel, PreTrainedTokenizer, TFPreTrainedModel + + +TASK_DOCUMENTATION = r""" + The dataset input and label columns are expected to be formatted as a list of words and a list of labels respectively, following [conll2003 dataset](https://huggingface.co/datasets/conll2003). Datasets whose inputs are single strings, and labels are a list of offset are not supported. + + Examples: + ```python + >>> from evaluate import evaluator + >>> from datasets import load_dataset + >>> task_evaluator = evaluator("token-classification") + >>> data = load_dataset("conll2003", split="validation[:2]") + >>> results = task_evaluator.compute( + >>> model_or_pipeline="elastic/distilbert-base-uncased-finetuned-conll03-english", + >>> data=data, + >>> metric="seqeval", + >>> ) + ``` + + + + For example, the following dataset format is accepted by the evaluator: + + ```python + dataset = Dataset.from_dict( + mapping={ + "tokens": [["New", "York", "is", "a", "city", "and", "Felix", "a", "person", "."]], + "ner_tags": [[1, 2, 0, 0, 0, 0, 3, 0, 0, 0]], + }, + features=Features({ + "tokens": Sequence(feature=Value(dtype="string")), + "ner_tags": Sequence(feature=ClassLabel(names=["O", "B-LOC", "I-LOC", "B-PER", "I-PER"])), + }), + ) + ``` + + + + + + For example, the following dataset format is **not** accepted by the evaluator: + + ```python + dataset = Dataset.from_dict( + mapping={ + "tokens": [["New York is a city and Felix a person."]], + "starts": [[0, 23]], + "ends": [[7, 27]], + "ner_tags": [["LOC", "PER"]], + }, + features=Features({ + "tokens": Value(dtype="string"), + "starts": Sequence(feature=Value(dtype="int32")), + "ends": Sequence(feature=Value(dtype="int32")), + "ner_tags": Sequence(feature=Value(dtype="string")), + }), + ) + ``` + + +""" + + +class TokenClassificationEvaluator(Evaluator): + """ + Token classification evaluator. + + This token classification evaluator can currently be loaded from [`evaluator`] using the default task name + `token-classification`. + + Methods in this class assume a data format compatible with the [`~transformers.TokenClassificationPipeline`]. + """ + + PIPELINE_KWARGS = {"ignore_labels": []} + + def __init__(self, task="token-classification", default_metric_name=None): + super().__init__(task, default_metric_name=default_metric_name) + + def predictions_processor(self, predictions: List[List[Dict]], words: List[List[str]], join_by: str): + """ + Transform the pipeline predictions into a list of predicted labels of the same length as the true labels. + + Args: + predictions (`List[List[Dict]]`): + List of pipeline predictions, where each token has been labeled. + words (`List[List[str]]`): + Original input data to the pipeline, used to build predicted labels of the same length. + join_by (`str`): + String to use to join two words. In English, it will typically be " ". + + Returns: + `dict`: a dictionary holding the predictions + """ + preds = [] + + # iterate over the data rows + for i, prediction in enumerate(predictions): + pred_processed = [] + + # get a list of tuples giving the indexes of the start and end character of each word + words_offsets = self.words_to_offsets(words[i], join_by) + + token_index = 0 + for word_offset in words_offsets: + # for each word, we may keep only the predicted label for the first token, discard the others + while prediction[token_index]["start"] < word_offset[0]: + token_index += 1 + + if prediction[token_index]["start"] > word_offset[0]: # bad indexing + pred_processed.append("O") + elif prediction[token_index]["start"] == word_offset[0]: + pred_processed.append(prediction[token_index]["entity"]) + + preds.append(pred_processed) + + return {"predictions": preds} + + def words_to_offsets(self, words: List[str], join_by: str): + """ + Convert a list of words to a list of offsets, where word are joined by `join_by`. + + Args: + words (`List[str]`): + List of words to get offsets from. + join_by (`str`): + String to insert between words. + + Returns: + `List[Tuple[int, int]]`: List of the characters (start index, end index) for each of the words. + """ + offsets = [] + + start = 0 + for word in words: + end = start + len(word) - 1 + offsets.append((start, end)) + start = end + len(join_by) + 1 + + return offsets + + def prepare_data(self, data: Union[str, Dataset], input_column: str, label_column: str, join_by: str): + super().prepare_data(data, input_column, label_column) + + if not isinstance(data.features[input_column], Sequence) or not isinstance( + data.features[label_column], Sequence + ): + raise ValueError( + "TokenClassificationEvaluator expects the input and label columns to be provided as lists." + ) + + # If the labels are of type ClassLabel, they are already integers and we have the map stored somewhere. + # Otherwise, we have to get the list of labels manually. + labels_are_int = isinstance(data.features[label_column].feature, ClassLabel) + if labels_are_int: + label_list = data.features[label_column].feature.names # list of string labels + id_to_label = {i: label for i, label in enumerate(label_list)} + references = [[id_to_label[label_id] for label_id in label_ids] for label_ids in data[label_column]] + elif data.features[label_column].feature.dtype.startswith("int"): + raise NotImplementedError( + "References provided as integers, but the reference column is not a Sequence of ClassLabels." + ) + else: + # In the event the labels are not a `Sequence[ClassLabel]`, we have already labels as strings + # An example is labels as ["PER", "PER", "O", "LOC", "O", "LOC", "O"], e.g. in polyglot_ner dataset + references = data[label_column] + + metric_inputs = {"references": references} + data = data.map(lambda x: {input_column: join_by.join(x[input_column])}) + pipeline_inputs = DatasetColumn(data, input_column) + + return metric_inputs, pipeline_inputs + + def prepare_pipeline( + self, + model_or_pipeline: Union[str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel"], # noqa: F821 + tokenizer: Union["PreTrainedTokenizerBase", "FeatureExtractionMixin"] = None, # noqa: F821 + feature_extractor: Union["PreTrainedTokenizerBase", "FeatureExtractionMixin"] = None, # noqa: F821 + device: int = None, + ): + pipe = super().prepare_pipeline(model_or_pipeline, tokenizer, feature_extractor, device) + + # check the pipeline outputs start characters in its predictions + dummy_output = pipe(["2003 New York Gregory"], **self.PIPELINE_KWARGS) + if dummy_output[0][0]["start"] is None: + raise ValueError( + "TokenClassificationEvaluator supports only pipelines giving 'start' index as a pipeline output (got None). " + "Transformers pipelines with a slow tokenizer will raise this error." + ) + + return pipe + + @add_start_docstrings(EVALUTOR_COMPUTE_START_DOCSTRING) + @add_end_docstrings(EVALUATOR_COMPUTE_RETURN_DOCSTRING, TASK_DOCUMENTATION) + def compute( + self, + model_or_pipeline: Union[ + str, "Pipeline", Callable, "PreTrainedModel", "TFPreTrainedModel" # noqa: F821 + ] = None, + data: Union[str, Dataset] = None, + subset: Optional[str] = None, + split: str = None, + metric: Union[str, EvaluationModule] = None, + tokenizer: Optional[Union[str, "PreTrainedTokenizer"]] = None, # noqa: F821 + strategy: Literal["simple", "bootstrap"] = "simple", + confidence_level: float = 0.95, + n_resamples: int = 9999, + device: Optional[int] = None, + random_state: Optional[int] = None, + input_column: str = "tokens", + label_column: str = "ner_tags", + join_by: Optional[str] = " ", + ) -> Tuple[Dict[str, float], Any]: + """ + input_column (`str`, defaults to `"tokens"`): + The name of the column containing the tokens feature in the dataset specified by `data`. + label_column (`str`, defaults to `"label"`): + The name of the column containing the labels in the dataset specified by `data`. + join_by (`str`, *optional*, defaults to `" "`): + This evaluator supports dataset whose input column is a list of words. This parameter specifies how to join + words to generate a string input. This is especially useful for languages that do not separate words by a space. + """ + result = {} + + self.check_for_mismatch_in_device_setup(device, model_or_pipeline) + + # Prepare inputs + data = self.load_data(data=data, subset=subset, split=split) + metric_inputs, pipe_inputs = self.prepare_data( + data=data, input_column=input_column, label_column=label_column, join_by=join_by + ) + pipe = self.prepare_pipeline(model_or_pipeline=model_or_pipeline, tokenizer=tokenizer, device=device) + metric = self.prepare_metric(metric) + + # Compute predictions + predictions, perf_results = self.call_pipeline(pipe, pipe_inputs) + predictions = self.predictions_processor(predictions, data[input_column], join_by) + metric_inputs.update(predictions) + + # Compute metrics from references and predictions + metric_results = self.compute_metric( + metric=metric, + metric_inputs=metric_inputs, + strategy=strategy, + confidence_level=confidence_level, + n_resamples=n_resamples, + random_state=random_state, + ) + + result.update(metric_results) + result.update(perf_results) + + return result diff --git a/venv/lib/python3.10/site-packages/evaluate/evaluator/utils.py b/venv/lib/python3.10/site-packages/evaluate/evaluator/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e364276d008b689d726b8dbbea1402fa93886d9b --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/evaluator/utils.py @@ -0,0 +1,84 @@ +from datasets import Dataset, get_dataset_split_names + + +class DatasetColumn(list): + """Helper class to avoid loading a dataset column into memory when accessing it.""" + + def __init__(self, dataset: Dataset, key: str): + self.dataset = dataset + self.key = key + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, i): + return self.dataset[i][self.key] + + def __iter__(self): + return (self.dataset[i][self.key] for i in range(len(self))) + + +def choose_split(data, subset=None): + available_splits = get_dataset_split_names(data, subset) + preferred_split_order = [ + "test", + "testing", + "eval", + "evaluation", + "validation", + "val", + "valid", + "dev", + "train", + "training", + ] + for split in preferred_split_order: + if split in available_splits: + return split + raise ValueError("No dataset split defined! Pass an explicit value to the `split` kwarg.") + + +class DatasetColumnPair(list): + """Helper class to avoid loading two dataset columns into memory when accessing it.""" + + def __init__( + self, + dataset: Dataset, + first_col: str, + second_col: str, + first_key: str, + second_key: str, + ): + """ + Args: + dataset (Dataset): dataset to build an iterator on + first_col (str): first column name to use in the dataset + second_col (str): second column name to use in the dataset + first_key (str): key name used for the first column in the returned dictionary + second_key (str): key name used for the second column in the returned dictionary + """ + self.dataset = dataset + + self.first_col = first_col + self.second_col = second_col + + self.first_key = first_key + self.second_key = second_key + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, i): + return { + self.first_key: self.dataset[i][self.first_col], + self.second_key: self.dataset[i][self.second_col] if self.second_col else None, + } + + def __iter__(self): + return ( + { + self.first_key: self.dataset[i][self.first_col], + self.second_key: self.dataset[i][self.second_col] if self.second_col else None, + } + for i in range(len(self)) + ) diff --git a/venv/lib/python3.10/site-packages/evaluate/hub.py b/venv/lib/python3.10/site-packages/evaluate/hub.py new file mode 100644 index 0000000000000000000000000000000000000000..86118332c6d2293f475e84e80726364dcc63e292 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/hub.py @@ -0,0 +1,133 @@ +from typing import Dict + +import requests +from huggingface_hub import dataset_info, model_info +from huggingface_hub.repocard import metadata_update + +from .config import HF_HUB_ALLOWED_TASKS +from .utils.logging import get_logger + + +logger = get_logger(__name__) + + +def push_to_hub( + model_id: str, + task_type: str, + dataset_type: str, + dataset_name: str, + metric_type: str, + metric_name: str, + metric_value: float, + task_name: str = None, + dataset_config: str = None, + dataset_split: str = None, + dataset_revision: str = None, + dataset_args: Dict[str, int] = None, + metric_config: str = None, + metric_args: Dict[str, int] = None, + overwrite: bool = False, +): + r""" + Pushes the result of a metric to the metadata of a model repository in the Hub. + + Args: + model_id (`str`): + Model id from https://hf.co/models. + task_type (`str`): + Task id, refer to the [Hub allowed tasks](https://github.com/huggingface/evaluate/blob/main/src/evaluate/config.py#L154) for allowed values. + dataset_type (`str`): + Dataset id from https://hf.co/datasets. + dataset_name (`str`): + Pretty name for the dataset. + metric_type (`str`): + Metric id from https://hf.co/metrics. + metric_name (`str`): + Pretty name for the metric. + metric_value (`float`): + Computed metric value. + task_name (`str`, *optional*): + Pretty name for the task. + dataset_config (`str`, *optional*): + Dataset configuration used in [`~datasets.load_dataset`]. + See [`~datasets.load_dataset`] for more info. + dataset_split (`str`, *optional*): + Name of split used for metric computation. + dataset_revision (`str`, *optional*): + Git hash for the specific version of the dataset. + dataset_args (`dict[str, int]`, *optional*): + Additional arguments passed to [`~datasets.load_dataset`]. + metric_config (`str`, *optional*): + Configuration for the metric (e.g. the GLUE metric has a configuration for each subset). + metric_args (`dict[str, int]`, *optional*): + Arguments passed during [`~evaluate.EvaluationModule.compute`]. + overwrite (`bool`, *optional*, defaults to `False`): + If set to `True` an existing metric field can be overwritten, otherwise + attempting to overwrite any existing fields will cause an error. + + Example: + + ```python + >>> push_to_hub( + ... model_id="huggingface/gpt2-wikitext2", + ... metric_value=0.5 + ... metric_type="bleu", + ... metric_name="BLEU", + ... dataset_name="WikiText", + ... dataset_type="wikitext", + ... dataset_split="test", + ... task_type="text-generation", + ... task_name="Text Generation" + ... ) + ```""" + if task_type not in HF_HUB_ALLOWED_TASKS: + raise ValueError(f"Task type not supported. Task has to be one of {HF_HUB_ALLOWED_TASKS}") + + try: + dataset_info(dataset_type) + except requests.exceptions.HTTPError: + logger.warning(f"Dataset {dataset_type} not found on the Hub at hf.co/datasets/{dataset_type}") + + try: + model_info(model_id) + except requests.exceptions.HTTPError: + raise ValueError(f"Model {model_id} not found on the Hub at hf.co/{model_id}") + + result = { + "task": { + "type": task_type, + }, + "dataset": { + "type": dataset_type, + "name": dataset_name, + }, + "metrics": [ + { + "type": metric_type, + "value": metric_value, + }, + ], + } + + if dataset_config is not None: + result["dataset"]["config"] = dataset_config + if dataset_split is not None: + result["dataset"]["split"] = dataset_split + if dataset_revision is not None: + result["dataset"]["revision"] = dataset_revision + if dataset_args is not None: + result["dataset"]["args"] = dataset_args + + if task_name is not None: + result["task"]["name"] = task_name + + if metric_name is not None: + result["metrics"][0]["name"] = metric_name + if metric_config is not None: + result["metrics"][0]["config"] = metric_config + if metric_args is not None: + result["metrics"][0]["args"] = metric_args + + metadata = {"model-index": [{"results": [result]}]} + + return metadata_update(repo_id=model_id, metadata=metadata, overwrite=overwrite) diff --git a/venv/lib/python3.10/site-packages/evaluate/info.py b/venv/lib/python3.10/site-packages/evaluate/info.py new file mode 100644 index 0000000000000000000000000000000000000000..cc095784e4f1c1f473dd85955447d97d5fdc4e65 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/info.py @@ -0,0 +1,157 @@ +# Copyright 2020 The HuggingFace Datasets Authors and the TensorFlow Datasets Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +""" EvaluationModuleInfo records information we know about a dataset and a metric. +""" + +import dataclasses +import json +import os +from dataclasses import asdict, dataclass, field +from typing import List, Optional, Union + +from datasets.features import Features, Value + +from . import config +from .utils.logging import get_logger + + +logger = get_logger(__name__) + + +@dataclass +class EvaluationModuleInfo: + """Base class to store information about an evaluation used for `MetricInfo`, `ComparisonInfo`, + and `MeasurementInfo`. + + `EvaluationModuleInfo` documents an evaluation, including its name, version, and features. + See the constructor arguments and properties for a full list. + + Note: Not all fields are known on construction and may be updated later. + """ + + # Set in the dataset scripts + description: str + citation: str + features: Union[Features, List[Features]] + inputs_description: str = field(default_factory=str) + homepage: str = field(default_factory=str) + license: str = field(default_factory=str) + codebase_urls: List[str] = field(default_factory=list) + reference_urls: List[str] = field(default_factory=list) + streamable: bool = False + format: Optional[str] = None + module_type: str = "metric" # deprecate this in the future + + # Set later by the builder + module_name: Optional[str] = None + config_name: Optional[str] = None + experiment_id: Optional[str] = None + + def __post_init__(self): + if self.format is not None: + for key, value in self.features.items(): + if not isinstance(value, Value): + raise ValueError( + f"When using 'numpy' format, all features should be a `datasets.Value` feature. " + f"Here {key} is an instance of {value.__class__.__name__}" + ) + + def write_to_directory(self, metric_info_dir): + """Write `EvaluationModuleInfo` as JSON to `metric_info_dir`. + Also save the license separately in LICENSE. + + Args: + metric_info_dir (`str`): + The directory to save `metric_info_dir` to. + + Example: + + ```py + >>> my_metric.info.write_to_directory("/path/to/directory/") + ``` + """ + with open(os.path.join(metric_info_dir, config.METRIC_INFO_FILENAME), "w", encoding="utf-8") as f: + json.dump(asdict(self), f) + + with open(os.path.join(metric_info_dir, config.LICENSE_FILENAME), "w", encoding="utf-8") as f: + f.write(self.license) + + @classmethod + def from_directory(cls, metric_info_dir) -> "EvaluationModuleInfo": + """Create `EvaluationModuleInfo` from the JSON file in `metric_info_dir`. + + Args: + metric_info_dir (`str`): + The directory containing the `metric_info` JSON file. This + should be the root directory of a specific metric version. + + Example: + + ```py + >>> my_metric = EvaluationModuleInfo.from_directory("/path/to/directory/") + ``` + """ + logger.info(f"Loading Metric info from {metric_info_dir}") + if not metric_info_dir: + raise ValueError("Calling EvaluationModuleInfo.from_directory() with undefined metric_info_dir.") + + with open(os.path.join(metric_info_dir, config.METRIC_INFO_FILENAME), encoding="utf-8") as f: + metric_info_dict = json.load(f) + return cls.from_dict(metric_info_dict) + + @classmethod + def from_dict(cls, metric_info_dict: dict) -> "EvaluationModuleInfo": + field_names = {f.name for f in dataclasses.fields(cls)} + return cls(**{k: v for k, v in metric_info_dict.items() if k in field_names}) + + +@dataclass +class MetricInfo(EvaluationModuleInfo): + """Information about a metric. + + `EvaluationModuleInfo` documents a metric, including its name, version, and features. + See the constructor arguments and properties for a full list. + + Note: Not all fields are known on construction and may be updated later. + """ + + module_type: str = "metric" + + +@dataclass +class ComparisonInfo(EvaluationModuleInfo): + """Information about a comparison. + + `EvaluationModuleInfo` documents a comparison, including its name, version, and features. + See the constructor arguments and properties for a full list. + + Note: Not all fields are known on construction and may be updated later. + """ + + module_type: str = "comparison" + + +@dataclass +class MeasurementInfo(EvaluationModuleInfo): + """Information about a measurement. + + `EvaluationModuleInfo` documents a measurement, including its name, version, and features. + See the constructor arguments and properties for a full list. + + Note: Not all fields are known on construction and may be updated later. + """ + + module_type: str = "measurement" diff --git a/venv/lib/python3.10/site-packages/evaluate/inspect.py b/venv/lib/python3.10/site-packages/evaluate/inspect.py new file mode 100644 index 0000000000000000000000000000000000000000..20e2af28ed4df4e99c6d67cccdd24dda1c8cecf9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/inspect.py @@ -0,0 +1,129 @@ +# Copyright 2020 The HuggingFace Evaluate Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +""" List and inspect metrics.""" + +from typing import Optional + +import requests +from datasets import DownloadConfig + +from .config import EVALUATION_MODULE_TYPES, HF_LIST_ENDPOINT +from .loading import evaluation_module_factory +from .utils.logging import get_logger + + +logger = get_logger(__name__) + + +class SplitsNotFoundError(ValueError): + pass + + +def list_evaluation_modules(module_type=None, include_community=True, with_details=False): + """List all evaluation modules available on the Hugging Face Hub. + + Args: + module_type (`str`, *optional*, defaults to `None`): + Type of evaluation modules to list. Has to be one of `'metric'`, `'comparison'`, or `'measurement'`. If `None`, all types are listed. + include_community (`bool`, *optional*, defaults to `True`): + Include community modules in the list. + with_details (`bool`, *optional*, defaults to `False`): + Return the full details on the metrics instead of only the ID. + + Returns: + `List[Union[str, dict]]` + + Example: + + ```py + >>> from evaluate import list_evaluation_modules + >>> list_evaluation_modules(module_type="metric") + ``` + """ + + if module_type is None: + evaluations_list = [] + for module_type in EVALUATION_MODULE_TYPES: + evaluations_list.extend( + _list_evaluation_modules_type( + module_type, include_community=include_community, with_details=with_details + ) + ) + else: + if module_type not in EVALUATION_MODULE_TYPES: + raise ValueError(f"Invalid module type '{module_type}'. Has to be one of {EVALUATION_MODULE_TYPES}.") + evaluations_list = _list_evaluation_modules_type( + module_type, include_community=include_community, with_details=with_details + ) + return evaluations_list + + +def _list_evaluation_modules_type(module_type, include_community=True, with_details=False): + + r = requests.get(HF_LIST_ENDPOINT.format(type=module_type)) + r.raise_for_status() + d = r.json() + + if not include_community: + d = [element for element in d if element["id"].split("/")[0] == f"evaluate-{module_type}"] + + # remove namespace for canonical modules and add community tag + for element in d: + if element["id"].split("/")[0] == f"evaluate-{module_type}": + element["id"] = element["id"].split("/")[1] + element["community"] = False + else: + element["community"] = True + + if with_details: + return [ + { + "name": element["id"], + "type": module_type, + "community": element["community"], + "likes": element.get("likes", 0), + } + for element in d + ] + else: + return [element["id"] for element in d] + + +def inspect_evaluation_module( + path: str, local_path: str, download_config: Optional[DownloadConfig] = None, **download_kwargs +): + r""" + Allow inspection/modification of a evaluation script by copying it on local drive at local_path. + + Args: + path (``str``): path to the evaluation script. Can be either: + + - a local path to script or the directory containing the script (if the script has the same name as the directory), + e.g. ``'./metrics/accuracy'`` or ``'./metrics/accuracy/accuracy.py'`` + - a dataset identifier on the Hugging Face Hub (list all available datasets and ids with ``evaluate.list_evaluation_modules()``) + e.g. ``'accuracy'``, ``'bleu'`` or ``'word_length'`` + local_path (``str``): path to the local folder to copy the datset script to. + download_config (Optional ``datasets.DownloadConfig``: specific download configuration parameters. + **download_kwargs: optional attributes for DownloadConfig() which will override the attributes in download_config if supplied. + """ + evaluation_module = evaluation_module_factory( + path, download_config=download_config, force_local_path=local_path, **download_kwargs + ) + print( + f"The processing scripts for metric {path} can be inspected at {local_path}. " + f"The main class is in {evaluation_module.module_path}. " + f"You can modify this processing scripts and use it with `evaluate.load({local_path})`." + ) diff --git a/venv/lib/python3.10/site-packages/evaluate/loading.py b/venv/lib/python3.10/site-packages/evaluate/loading.py new file mode 100644 index 0000000000000000000000000000000000000000..a9cbbe60de07fed296cd27fd1954ef14cf0de032 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/loading.py @@ -0,0 +1,771 @@ +# Copyright 2020 The HuggingFace Datasets Authors and the TensorFlow Datasets Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +"""Access datasets.""" +import filecmp +import importlib +import inspect +import json +import os +import re +import shutil +import time +from dataclasses import dataclass +from pathlib import Path +from typing import List, Optional, Tuple, Type, Union +from urllib.parse import urlparse + +from datasets import DownloadConfig, DownloadMode +from datasets.builder import DatasetBuilder +from datasets.packaged_modules import _EXTENSION_TO_MODULE, _hash_python_lines +from datasets.utils.filelock import FileLock +from datasets.utils.version import Version + +from . import SCRIPTS_VERSION, config +from .module import EvaluationModule +from .utils.file_utils import ( + cached_path, + head_hf_s3, + hf_hub_url, + init_hf_modules, + is_relative_path, + relative_to_absolute_path, + url_or_path_join, +) +from .utils.logging import get_logger + + +logger = get_logger(__name__) + + +ALL_ALLOWED_EXTENSIONS = list(_EXTENSION_TO_MODULE.keys()) + ["zip"] + + +def init_dynamic_modules( + name: str = config.MODULE_NAME_FOR_DYNAMIC_MODULES, hf_modules_cache: Optional[Union[Path, str]] = None +): + """ + Create a module with name `name` in which you can add dynamic modules + such as metrics or datasets. The module can be imported using its name. + The module is created in the HF_MODULE_CACHE directory by default (~/.cache/huggingface/modules) but it can + be overriden by specifying a path to another directory in `hf_modules_cache`. + """ + hf_modules_cache = init_hf_modules(hf_modules_cache) + dynamic_modules_path = os.path.join(hf_modules_cache, name) + os.makedirs(dynamic_modules_path, exist_ok=True) + if not os.path.exists(os.path.join(dynamic_modules_path, "__init__.py")): + with open(os.path.join(dynamic_modules_path, "__init__.py"), "w"): + pass + return dynamic_modules_path + + +def import_main_class(module_path) -> Optional[Union[Type[DatasetBuilder], Type[EvaluationModule]]]: + """Import a module at module_path and return its main class, a Metric by default""" + module = importlib.import_module(module_path) + main_cls_type = EvaluationModule + + # Find the main class in our imported module + module_main_cls = None + for name, obj in module.__dict__.items(): + if isinstance(obj, type) and issubclass(obj, main_cls_type): + if inspect.isabstract(obj): + continue + module_main_cls = obj + break + + return module_main_cls + + +def files_to_hash(file_paths: List[str]) -> str: + """ + Convert a list of scripts or text files provided in file_paths into a hashed filename in a repeatable way. + """ + # List all python files in directories if directories are supplied as part of external imports + to_use_files: List[Union[Path, str]] = [] + for file_path in file_paths: + if os.path.isdir(file_path): + to_use_files.extend(list(Path(file_path).rglob("*.[pP][yY]"))) + else: + to_use_files.append(file_path) + + # Get the code from all these files + lines = [] + for file_path in to_use_files: + with open(file_path, encoding="utf-8") as f: + lines.extend(f.readlines()) + return _hash_python_lines(lines) + + +def convert_github_url(url_path: str) -> Tuple[str, Optional[str]]: + """Convert a link to a file on a github repo in a link to the raw github object.""" + parsed = urlparse(url_path) + sub_directory = None + if parsed.scheme in ("http", "https", "s3") and parsed.netloc == "github.com": + if "blob" in url_path: + if not url_path.endswith(".py"): + raise ValueError(f"External import from github at {url_path} should point to a file ending with '.py'") + url_path = url_path.replace("blob", "raw") # Point to the raw file + else: + # Parse github url to point to zip + github_path = parsed.path[1:] + repo_info, branch = github_path.split("/tree/") if "/tree/" in github_path else (github_path, "master") + repo_owner, repo_name = repo_info.split("/") + url_path = f"https://github.com/{repo_owner}/{repo_name}/archive/{branch}.zip" + sub_directory = f"{repo_name}-{branch}" + return url_path, sub_directory + + +def increase_load_count(name: str, resource_type: str): + """Update the download count of a dataset or metric.""" + if not config.HF_EVALUATE_OFFLINE and config.HF_UPDATE_DOWNLOAD_COUNTS: + try: + head_hf_s3(name, filename=name + ".py", dataset=(resource_type == "dataset")) + except Exception: + pass + + +def get_imports(file_path: str) -> Tuple[str, str, str, str]: + """Find whether we should import or clone additional files for a given processing script. + And list the import. + + We allow: + - library dependencies, + - local dependencies and + - external dependencies whose url is specified with a comment starting from "# From:' followed by the raw url to a file, an archive or a github repository. + external dependencies will be downloaded (and extracted if needed in the dataset folder). + We also add an `__init__.py` to each sub-folder of a downloaded folder so the user can import from them in the script. + + Note that only direct import in the dataset processing script will be handled + We don't recursively explore the additional import to download further files. + + Example:: + + import tensorflow + import .c4_utils + import .clicr.dataset-code.build_json_dataset # From: https://raw.githubusercontent.com/clips/clicr/master/dataset-code/build_json_dataset + """ + lines = [] + with open(file_path, encoding="utf-8") as f: + lines.extend(f.readlines()) + + logger.debug(f"Checking {file_path} for additional imports.") + imports: List[Tuple[str, str, str, Optional[str]]] = [] + is_in_docstring = False + for line in lines: + docstr_start_match = re.findall(r'[\s\S]*?"""[\s\S]*?', line) + + if len(docstr_start_match) == 1: + # flip True <=> False only if doctstring + # starts at line without finishing + is_in_docstring = not is_in_docstring + + if is_in_docstring: + # import statements in doctstrings should + # not be added as required dependencies + continue + + match = re.match(r"^import\s+(\.?)([^\s\.]+)[^#\r\n]*(?:#\s+From:\s+)?([^\r\n]*)", line, flags=re.MULTILINE) + if match is None: + match = re.match( + r"^from\s+(\.?)([^\s\.]+)(?:[^\s]*)\s+import\s+[^#\r\n]*(?:#\s+From:\s+)?([^\r\n]*)", + line, + flags=re.MULTILINE, + ) + if match is None: + continue + if match.group(1): + # The import starts with a '.', we will download the relevant file + if any(imp[1] == match.group(2) for imp in imports): + # We already have this import + continue + if match.group(3): + # The import has a comment with 'From:', we'll retrieve it from the given url + url_path = match.group(3) + url_path, sub_directory = convert_github_url(url_path) + imports.append(("external", match.group(2), url_path, sub_directory)) + elif match.group(2): + # The import should be at the same place as the file + imports.append(("internal", match.group(2), match.group(2), None)) + else: + if match.group(3): + # The import has a comment with `From: git+https:...`, asks user to pip install from git. + url_path = match.group(3) + imports.append(("library", match.group(2), url_path, None)) + else: + imports.append(("library", match.group(2), match.group(2), None)) + + return imports + + +def _download_additional_modules( + name: str, base_path: str, imports: Tuple[str, str, str, str], download_config: Optional[DownloadConfig] +) -> List[Tuple[str, str]]: + """ + Download additional module for a module .py at URL (or local path) /.py + The imports must have been parsed first using ``get_imports``. + + If some modules need to be installed with pip, an error is raised showing how to install them. + This function return the list of downloaded modules as tuples (import_name, module_file_path). + + The downloaded modules can then be moved into an importable directory with ``_copy_script_and_other_resources_in_importable_dir``. + """ + local_imports = [] + library_imports = [] + download_config = download_config.copy() + if download_config.download_desc is None: + download_config.download_desc = "Downloading extra modules" + for import_type, import_name, import_path, sub_directory in imports: + if import_type == "library": + library_imports.append((import_name, import_path)) # Import from a library + continue + + if import_name == name: + raise ValueError( + f"Error in the {name} script, importing relative {import_name} module " + f"but {import_name} is the name of the script. " + f"Please change relative import {import_name} to another name and add a '# From: URL_OR_PATH' " + f"comment pointing to the original relative import file path." + ) + if import_type == "internal": + url_or_filename = url_or_path_join(base_path, import_path + ".py") + elif import_type == "external": + url_or_filename = import_path + else: + raise ValueError("Wrong import_type") + + local_import_path = cached_path( + url_or_filename, + download_config=download_config, + ) + if sub_directory is not None: + local_import_path = os.path.join(local_import_path, sub_directory) + local_imports.append((import_name, local_import_path)) + + # Check library imports + needs_to_be_installed = set() + for library_import_name, library_import_path in library_imports: + try: + lib = importlib.import_module(library_import_name) # noqa F841 + except ImportError: + library_import_name = "scikit-learn" if library_import_name == "sklearn" else library_import_name + needs_to_be_installed.add((library_import_name, library_import_path)) + if needs_to_be_installed: + raise ImportError( + f"To be able to use {name}, you need to install the following dependencies" + f"{[lib_name for lib_name, lib_path in needs_to_be_installed]} using 'pip install " + f"{' '.join([lib_path for lib_name, lib_path in needs_to_be_installed])}' for instance'" + ) + return local_imports + + +def _copy_script_and_other_resources_in_importable_dir( + name: str, + importable_directory_path: str, + subdirectory_name: str, + original_local_path: str, + local_imports: List[Tuple[str, str]], + additional_files: List[Tuple[str, str]], + download_mode: Optional[DownloadMode], +) -> str: + """Copy a script and its required imports to an importable directory + + Args: + name (str): name of the resource to load + importable_directory_path (str): path to the loadable folder in the dynamic modules directory + subdirectory_name (str): name of the subdirectory in importable_directory_path in which to place the script + original_local_path (str): local path to the resource script + local_imports (List[Tuple[str, str]]): list of (destination_filename, import_file_to_copy) + additional_files (List[Tuple[str, str]]): list of (destination_filename, additional_file_to_copy) + download_mode (Optional[DownloadMode]): download mode + + Return: + importable_local_file: path to an importable module with importlib.import_module + """ + + # Define a directory with a unique name in our dataset or metric folder + # path is: ./datasets|metrics/dataset|metric_name/hash_from_code/script.py + # we use a hash as subdirectory_name to be able to have multiple versions of a dataset/metric processing file together + importable_subdirectory = os.path.join(importable_directory_path, subdirectory_name) + importable_local_file = os.path.join(importable_subdirectory, name + ".py") + + # Prevent parallel disk operations + lock_path = importable_directory_path + ".lock" + with FileLock(lock_path): + # Create main dataset/metrics folder if needed + if download_mode == DownloadMode.FORCE_REDOWNLOAD and os.path.exists(importable_directory_path): + shutil.rmtree(importable_directory_path) + os.makedirs(importable_directory_path, exist_ok=True) + + # add an __init__ file to the main dataset folder if needed + init_file_path = os.path.join(importable_directory_path, "__init__.py") + if not os.path.exists(init_file_path): + with open(init_file_path, "w"): + pass + + # Create hash dataset folder if needed + os.makedirs(importable_subdirectory, exist_ok=True) + # add an __init__ file to the hash dataset folder if needed + init_file_path = os.path.join(importable_subdirectory, "__init__.py") + if not os.path.exists(init_file_path): + with open(init_file_path, "w"): + pass + + # Copy dataset.py file in hash folder if needed + if not os.path.exists(importable_local_file): + shutil.copyfile(original_local_path, importable_local_file) + + # Record metadata associating original dataset path with local unique folder + meta_path = importable_local_file.split(".py")[0] + ".json" + if not os.path.exists(meta_path): + meta = {"original file path": original_local_path, "local file path": importable_local_file} + # the filename is *.py in our case, so better rename to filenam.json instead of filename.py.json + with open(meta_path, "w", encoding="utf-8") as meta_file: + json.dump(meta, meta_file) + + # Copy all the additional imports + for import_name, import_path in local_imports: + if os.path.isfile(import_path): + full_path_local_import = os.path.join(importable_subdirectory, import_name + ".py") + if not os.path.exists(full_path_local_import): + shutil.copyfile(import_path, full_path_local_import) + elif os.path.isdir(import_path): + full_path_local_import = os.path.join(importable_subdirectory, import_name) + if not os.path.exists(full_path_local_import): + shutil.copytree(import_path, full_path_local_import) + else: + raise OSError(f"Error with local import at {import_path}") + + # Copy aditional files like dataset infos file if needed + for file_name, original_path in additional_files: + destination_additional_path = os.path.join(importable_subdirectory, file_name) + if not os.path.exists(destination_additional_path) or not filecmp.cmp( + original_path, destination_additional_path + ): + shutil.copyfile(original_path, destination_additional_path) + return importable_local_file + + +def _create_importable_file( + local_path: str, + local_imports: List[Tuple[str, str]], + additional_files: List[Tuple[str, str]], + dynamic_modules_path: str, + module_namespace: str, + name: str, + download_mode: DownloadMode, +) -> Tuple[str, str]: + importable_directory_path = os.path.join(dynamic_modules_path, module_namespace, name.replace("/", "--")) + Path(importable_directory_path).mkdir(parents=True, exist_ok=True) + (Path(importable_directory_path).parent / "__init__.py").touch(exist_ok=True) + hash = files_to_hash([local_path] + [loc[1] for loc in local_imports]) + importable_local_file = _copy_script_and_other_resources_in_importable_dir( + name=name.split("/")[-1], + importable_directory_path=importable_directory_path, + subdirectory_name=hash, + original_local_path=local_path, + local_imports=local_imports, + additional_files=additional_files, + download_mode=download_mode, + ) + logger.debug(f"Created importable dataset file at {importable_local_file}") + module_path = ".".join( + [os.path.basename(dynamic_modules_path), module_namespace, name.replace("/", "--"), hash, name.split("/")[-1]] + ) + return module_path, hash + + +@dataclass +class ImportableModule: + module_path: str + hash: str + + +class _EvaluationModuleFactory: + def get_module(self) -> ImportableModule: + raise NotImplementedError + + +class LocalEvaluationModuleFactory(_EvaluationModuleFactory): + """Get the module of a local metric. The metric script is loaded from a local script.""" + + def __init__( + self, + path: str, + module_type: str = "metrics", + download_config: Optional[DownloadConfig] = None, + download_mode: Optional[DownloadMode] = None, + dynamic_modules_path: Optional[str] = None, + ): + self.path = path + self.module_type = module_type + self.name = Path(path).stem + self.download_config = download_config or DownloadConfig() + self.download_mode = download_mode + self.dynamic_modules_path = dynamic_modules_path + + def get_module(self) -> ImportableModule: + # get script and other files + imports = get_imports(self.path) + local_imports = _download_additional_modules( + name=self.name, + base_path=str(Path(self.path).parent), + imports=imports, + download_config=self.download_config, + ) + # copy the script and the files in an importable directory + dynamic_modules_path = self.dynamic_modules_path if self.dynamic_modules_path else init_dynamic_modules() + module_path, hash = _create_importable_file( + local_path=self.path, + local_imports=local_imports, + additional_files=[], + dynamic_modules_path=dynamic_modules_path, + module_namespace=self.module_type, + name=self.name, + download_mode=self.download_mode, + ) + # make the new module to be noticed by the import system + importlib.invalidate_caches() + return ImportableModule(module_path, hash) + + +class HubEvaluationModuleFactory(_EvaluationModuleFactory): + """Get the module of a metric from a metric repository on the Hub.""" + + def __init__( + self, + name: str, + module_type: str = "metrics", + revision: Optional[Union[str, Version]] = None, + download_config: Optional[DownloadConfig] = None, + download_mode: Optional[DownloadMode] = None, + dynamic_modules_path: Optional[str] = None, + ): + self.name = name + self.module_type = module_type + self.revision = revision + self.download_config = download_config or DownloadConfig() + self.download_mode = download_mode + self.dynamic_modules_path = dynamic_modules_path + assert self.name.count("/") == 1 + increase_load_count(name, resource_type="metric") + + def download_loading_script(self, revision) -> str: + file_path = hf_hub_url(path=self.name, name=self.name.split("/")[1] + ".py", revision=revision) + download_config = self.download_config.copy() + if download_config.download_desc is None: + download_config.download_desc = "Downloading builder script" + return cached_path(file_path, download_config=download_config) + + def get_module(self) -> ImportableModule: + revision = self.revision or os.getenv("HF_SCRIPTS_VERSION", SCRIPTS_VERSION) + + if re.match(r"\d*\.\d*\.\d*", revision): # revision is version number (three digits separated by full stops) + revision = "v" + revision # tagging convention on evaluate repository starts with v + + # get script and other files + try: + local_path = self.download_loading_script(revision) + except FileNotFoundError as err: + # if there is no file found with current revision tag try to load main + if self.revision is None and os.getenv("HF_SCRIPTS_VERSION", SCRIPTS_VERSION) != "main": + revision = "main" + local_path = self.download_loading_script(revision) + else: + raise err + + imports = get_imports(local_path) + local_imports = _download_additional_modules( + name=self.name, + base_path=hf_hub_url(path=self.name, name="", revision=revision), + imports=imports, + download_config=self.download_config, + ) + # copy the script and the files in an importable directory + dynamic_modules_path = self.dynamic_modules_path if self.dynamic_modules_path else init_dynamic_modules() + module_path, hash = _create_importable_file( + local_path=local_path, + local_imports=local_imports, + additional_files=[], + dynamic_modules_path=dynamic_modules_path, + module_namespace=self.module_type, + name=self.name, + download_mode=self.download_mode, + ) + # make the new module to be noticed by the import system + importlib.invalidate_caches() + return ImportableModule(module_path, hash) + + +class CachedEvaluationModuleFactory(_EvaluationModuleFactory): + """ + Get the module of a metric that has been loaded once already and cached. + The script that is loaded from the cache is the most recent one with a matching name. + """ + + def __init__( + self, + name: str, + module_type: str = "metrics", + dynamic_modules_path: Optional[str] = None, + ): + self.name = name + self.module_type = module_type + self.dynamic_modules_path = dynamic_modules_path + assert self.name.count("/") == 0 + + def get_module(self) -> ImportableModule: + dynamic_modules_path = self.dynamic_modules_path if self.dynamic_modules_path else init_dynamic_modules() + importable_directory_path = os.path.join(dynamic_modules_path, self.module_type, self.name) + hashes = ( + [h for h in os.listdir(importable_directory_path) if len(h) == 64] + if os.path.isdir(importable_directory_path) + else None + ) + if not hashes: + raise FileNotFoundError(f"Metric {self.name} is not cached in {dynamic_modules_path}") + # get most recent + + def _get_modification_time(module_hash): + return ( + (Path(importable_directory_path) / module_hash / (self.name.split("--")[-1] + ".py")).stat().st_mtime + ) + + hash = sorted(hashes, key=_get_modification_time)[-1] + logger.warning( + f"Using the latest cached version of the module from {os.path.join(importable_directory_path, hash)} " + f"(last modified on {time.ctime(_get_modification_time(hash))}) since it " + f"couldn't be found locally at {self.name}, or remotely on the Hugging Face Hub." + ) + # make the new module to be noticed by the import system + module_path = ".".join( + [os.path.basename(dynamic_modules_path), self.module_type, self.name, hash, self.name.split("--")[-1]] + ) + importlib.invalidate_caches() + return ImportableModule(module_path, hash) + + +def evaluation_module_factory( + path: str, + module_type: Optional[str] = None, + revision: Optional[Union[str, Version]] = None, + download_config: Optional[DownloadConfig] = None, + download_mode: Optional[DownloadMode] = None, + force_local_path: Optional[str] = None, + dynamic_modules_path: Optional[str] = None, + **download_kwargs, +) -> ImportableModule: + """ + Download/extract/cache a metric module. + + Metrics codes are cached inside the the dynamic modules cache to allow easy import (avoid ugly sys.path tweaks). + + Args: + + path (str): Path or name of the metric script. + + - if ``path`` is a local metric script or a directory containing a local metric script (if the script has the same name as the directory): + -> load the module from the metric script + e.g. ``'./metrics/accuracy'`` or ``'./metrics/accuracy/accuracy.py'``. + - if ``path`` is a metric on the Hugging Face Hub (ex: `glue`, `squad`) + -> load the module from the metric script in the github repository at huggingface/datasets + e.g. ``'accuracy'`` or ``'rouge'``. + + revision (Optional ``Union[str, datasets.Version]``): + If specified, the module will be loaded from the datasets repository at this version. + By default: + - it is set to the local version of the lib. + - it will also try to load it from the master branch if it's not available at the local version of the lib. + Specifying a version that is different from your local version of the lib might cause compatibility issues. + download_config (:class:`DownloadConfig`, optional): Specific download configuration parameters. + download_mode (:class:`DownloadMode`, default ``REUSE_DATASET_IF_EXISTS``): Download/generate mode. + force_local_path (Optional str): Optional path to a local path to download and prepare the script to. + Used to inspect or modify the script folder. + dynamic_modules_path (Optional str, defaults to HF_MODULES_CACHE / "datasets_modules", i.e. ~/.cache/huggingface/modules/datasets_modules): + Optional path to the directory in which the dynamic modules are saved. It must have been initialized with :obj:`init_dynamic_modules`. + By default the datasets and metrics are stored inside the `datasets_modules` module. + download_kwargs: optional attributes for DownloadConfig() which will override the attributes in download_config if supplied. + + Returns: + ImportableModule + """ + if download_config is None: + download_config = DownloadConfig(**download_kwargs) + download_mode = DownloadMode(download_mode or DownloadMode.REUSE_DATASET_IF_EXISTS) + download_config.extract_compressed_file = True + download_config.force_extract = True + + filename = list(filter(lambda x: x, path.replace(os.sep, "/").split("/")))[-1] + if not filename.endswith(".py"): + filename = filename + ".py" + combined_path = os.path.join(path, filename) + # Try locally + if path.endswith(filename): + if os.path.isfile(path): + return LocalEvaluationModuleFactory( + path, download_mode=download_mode, dynamic_modules_path=dynamic_modules_path + ).get_module() + else: + raise FileNotFoundError(f"Couldn't find a metric script at {relative_to_absolute_path(path)}") + elif os.path.isfile(combined_path): + return LocalEvaluationModuleFactory( + combined_path, download_mode=download_mode, dynamic_modules_path=dynamic_modules_path + ).get_module() + elif is_relative_path(path) and path.count("/") <= 1 and not force_local_path: + try: + # load a canonical evaluation module from hub + if path.count("/") == 0: + # if no type provided look through all possible modules + if module_type is None: + for current_type in ["metric", "comparison", "measurement"]: + try: + return HubEvaluationModuleFactory( + f"evaluate-{current_type}/{path}", + revision=revision, + download_config=download_config, + download_mode=download_mode, + dynamic_modules_path=dynamic_modules_path, + ).get_module() + except ConnectionError: + pass + raise FileNotFoundError + # if module_type provided load specific module_type + else: + return HubEvaluationModuleFactory( + f"evaluate-{module_type}/{path}", + revision=revision, + download_config=download_config, + download_mode=download_mode, + dynamic_modules_path=dynamic_modules_path, + ).get_module() + # load community evaluation module from hub + elif path.count("/") == 1: + return HubEvaluationModuleFactory( + path, + revision=revision, + download_config=download_config, + download_mode=download_mode, + dynamic_modules_path=dynamic_modules_path, + ).get_module() + except Exception as e1: # noqa: all the attempts failed, before raising the error we should check if the module is already cached. + # if it's a canonical module we need to check if it's any of the types + if path.count("/") == 0: + for current_type in ["metric", "comparison", "measurement"]: + try: + return CachedEvaluationModuleFactory( + f"evaluate-{current_type}--{path}", dynamic_modules_path=dynamic_modules_path + ).get_module() + except Exception as e2: # noqa: if it's not in the cache, then it doesn't exist. + pass + # if it's a community module we just need to check on path + elif path.count("/") == 1: + try: + return CachedEvaluationModuleFactory( + path.replace("/", "--"), dynamic_modules_path=dynamic_modules_path + ).get_module() + except Exception as e2: # noqa: if it's not in the cache, then it doesn't exist. + pass + if not isinstance(e1, (ConnectionError, FileNotFoundError)): + raise e1 from None + raise FileNotFoundError( + f"Couldn't find a module script at {relative_to_absolute_path(combined_path)}. " + f"Module '{path}' doesn't exist on the Hugging Face Hub either." + ) from None + else: + raise FileNotFoundError(f"Couldn't find a module script at {relative_to_absolute_path(combined_path)}.") + + +def load( + path: str, + config_name: Optional[str] = None, + module_type: Optional[str] = None, + process_id: int = 0, + num_process: int = 1, + cache_dir: Optional[str] = None, + experiment_id: Optional[str] = None, + keep_in_memory: bool = False, + download_config: Optional[DownloadConfig] = None, + download_mode: Optional[DownloadMode] = None, + revision: Optional[Union[str, Version]] = None, + **init_kwargs, +) -> EvaluationModule: + """Load a [`~evaluate.EvaluationModule`]. + + Args: + + path (`str`): + Path to the evaluation processing script with the evaluation builder. Can be either: + - a local path to processing script or the directory containing the script (if the script has the same name as the directory), + e.g. `'./metrics/rouge'` or `'./metrics/rouge/rouge.py'` + - a evaluation module identifier on the HuggingFace evaluate repo e.g. `'rouge'` or `'bleu'` that are in either `'metrics/'`, + `'comparisons/'`, or `'measurements/'` depending on the provided `module_type` + config_name (`str`, *optional*): + Selecting a configuration for the metric (e.g. the GLUE metric has a configuration for each subset). + module_type (`str`, default `'metric'`): + Type of evaluation module, can be one of `'metric'`, `'comparison'`, or `'measurement'`. + process_id (`int`, *optional*): + For distributed evaluation: id of the process. + num_process (`int`, *optional*): + For distributed evaluation: total number of processes. + cache_dir (`str`, *optional*): + Path to store the temporary predictions and references (default to `~/.cache/huggingface/evaluate/`). + experiment_id (`str`): + A specific experiment id. This is used if several distributed evaluations share the same file system. + This is useful to compute metrics in distributed setups (in particular non-additive metrics like F1). + keep_in_memory (`bool`): + Whether to store the temporary results in memory (defaults to `False`). + download_config ([`~evaluate.DownloadConfig`], *optional*): + Specific download configuration parameters. + download_mode ([`DownloadMode`], defaults to `REUSE_DATASET_IF_EXISTS`): + Download/generate mode. + revision (`Union[str, evaluate.Version]`, *optional*): + If specified, the module will be loaded from the datasets repository + at this version. By default it is set to the local version of the lib. Specifying a version that is different from + your local version of the lib might cause compatibility issues. + + Returns: + [`evaluate.EvaluationModule`] + + Example: + + ```py + >>> from evaluate import load + >>> accuracy = load("accuracy") + ``` + """ + download_mode = DownloadMode(download_mode or DownloadMode.REUSE_DATASET_IF_EXISTS) + evaluation_module = evaluation_module_factory( + path, module_type=module_type, revision=revision, download_config=download_config, download_mode=download_mode + ) + evaluation_cls = import_main_class(evaluation_module.module_path) + evaluation_instance = evaluation_cls( + config_name=config_name, + process_id=process_id, + num_process=num_process, + cache_dir=cache_dir, + keep_in_memory=keep_in_memory, + experiment_id=experiment_id, + hash=evaluation_module.hash, + **init_kwargs, + ) + + if module_type and module_type != evaluation_instance.module_type: + raise TypeError( + f"No module of module type '{module_type}' not found for '{path}' locally, or on the Hugging Face Hub. Found module of module type '{evaluation_instance.module_type}' instead." + ) + + # Download and prepare resources for the metric + evaluation_instance.download_and_prepare(download_config=download_config) + + return evaluation_instance diff --git a/venv/lib/python3.10/site-packages/evaluate/module.py b/venv/lib/python3.10/site-packages/evaluate/module.py new file mode 100644 index 0000000000000000000000000000000000000000..ca38b9b1584690bfed2658a717eeb972576d0846 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/module.py @@ -0,0 +1,1034 @@ +# Copyright 2020 The HuggingFace Datasets Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +""" EvaluationModule base class.""" +import collections +import itertools +import os +import types +import uuid +from typing import Any, Dict, List, Optional, Tuple, Union + +import numpy as np +import pyarrow as pa +from datasets import DatasetInfo, DownloadConfig, DownloadManager +from datasets.arrow_dataset import Dataset +from datasets.arrow_reader import ArrowReader +from datasets.arrow_writer import ArrowWriter +from datasets.features import Features, Sequence, Value +from datasets.features.features import _check_non_null_non_empty_recursive +from datasets.utils.filelock import BaseFileLock, FileLock, Timeout +from datasets.utils.py_utils import copyfunc, temp_seed, zip_dict + +from . import config +from .info import EvaluationModuleInfo +from .naming import camelcase_to_snakecase +from .utils.logging import get_logger + + +logger = get_logger(__name__) + + +class FileFreeLock(BaseFileLock): + """Thread lock until a file **cannot** be locked""" + + def __init__(self, lock_file, *args, **kwargs): + self.filelock = FileLock(lock_file) + super().__init__(lock_file, *args, **kwargs) + self._lock_file_fd = None + + def _acquire(self): + try: + self.filelock.acquire(timeout=0.01, poll_interval=0.02) # Try to lock once + except Timeout: + # We couldn't acquire the lock, the file is locked! + self._lock_file_fd = self.filelock.lock_file + else: + # We were able to acquire the lock, the file is not yet locked! + self.filelock.release() + self._lock_file_fd = None + + def _release(self): + self._lock_file_fd = None + + @property + def is_locked(self) -> bool: + return self._lock_file_fd is not None + + +# lists - summarize long lists similarly to NumPy +# arrays/tensors - let the frameworks control formatting +def summarize_if_long_list(obj): + if type(obj) is not list or len(obj) <= 6: + return f"{obj}" + + def format_chunk(chunk): + return ", ".join(repr(x) for x in chunk) + + return f"[{format_chunk(obj[:3])}, ..., {format_chunk(obj[-3:])}]" + + +class EvaluationModuleInfoMixin: + """This base class exposes some attributes of EvaluationModuleInfo + at the base level of the EvaluationModule for easy access. + """ + + def __init__(self, info: EvaluationModuleInfo): + self._module_info = info + + @property + def info(self): + """:class:`evaluate.EvaluationModuleInfo` object containing all the metadata in the evaluation module.""" + return self._module_info + + @property + def name(self) -> str: + return self._module_info.module_name + + @property + def experiment_id(self) -> Optional[str]: + return self._module_info.experiment_id + + @property + def description(self) -> str: + return self._module_info.description + + @property + def citation(self) -> str: + return self._module_info.citation + + @property + def features(self) -> Features: + return self._module_info.features + + @property + def inputs_description(self) -> str: + return self._module_info.inputs_description + + @property + def homepage(self) -> Optional[str]: + return self._module_info.homepage + + @property + def license(self) -> str: + return self._module_info.license + + @property + def codebase_urls(self) -> Optional[List[str]]: + return self._module_info.codebase_urls + + @property + def reference_urls(self) -> Optional[List[str]]: + return self._module_info.reference_urls + + @property + def streamable(self) -> bool: + return self._module_info.streamable + + @property + def format(self) -> Optional[str]: + return self._module_info.format + + @property + def module_type(self) -> str: + return self._module_info.module_type + + +class EvaluationModule(EvaluationModuleInfoMixin): + """A `EvaluationModule` is the base class and common API for metrics, comparisons, and measurements. + + Args: + config_name (`str`): + This is used to define a hash specific to a module computation script and prevents the module's data + to be overridden when the module loading script is modified. + keep_in_memory (`bool`): + Keep all predictions and references in memory. Not possible in distributed settings. + cache_dir (`str`): + Path to a directory in which temporary prediction/references data will be stored. + The data directory should be located on a shared file-system in distributed setups. + num_process (`int`): + Specify the total number of nodes in a distributed settings. + This is useful to compute module in distributed setups (in particular non-additive modules like F1). + process_id (`int`): + Specify the id of the current process in a distributed setup (between 0 and num_process-1) + This is useful to compute module in distributed setups (in particular non-additive metrics like F1). + seed (`int`, optional): + If specified, this will temporarily set numpy's random seed when [`~evaluate.EvaluationModule.compute`] is run. + experiment_id (`str`): + A specific experiment id. This is used if several distributed evaluations share the same file system. + This is useful to compute module in distributed setups (in particular non-additive metrics like F1). + hash (`str`): + Used to identify the evaluation module according to the hashed file contents. + max_concurrent_cache_files (`int`): + Max number of concurrent module cache files (default `10000`). + timeout (`Union[int, float]`): + Timeout in second for distributed setting synchronization. + """ + + def __init__( + self, + config_name: Optional[str] = None, + keep_in_memory: bool = False, + cache_dir: Optional[str] = None, + num_process: int = 1, + process_id: int = 0, + seed: Optional[int] = None, + experiment_id: Optional[str] = None, + hash: str = None, + max_concurrent_cache_files: int = 10000, + timeout: Union[int, float] = 100, + **kwargs, + ): + # prepare info + self.config_name = config_name or "default" + info = self._info() + info.module_name = camelcase_to_snakecase(self.__class__.__name__) + info.config_name = self.config_name + info.experiment_id = experiment_id or "default_experiment" + EvaluationModuleInfoMixin.__init__(self, info) # For easy access on low level + + # Safety checks on num_process and process_id + if not isinstance(process_id, int) or process_id < 0: + raise ValueError("'process_id' should be a number greater than 0") + if not isinstance(num_process, int) or num_process <= process_id: + raise ValueError("'num_process' should be a number greater than process_id") + if keep_in_memory and num_process != 1: + raise ValueError("Using 'keep_in_memory' is not possible in distributed setting (num_process > 1).") + + self.num_process = num_process + self.process_id = process_id + self.max_concurrent_cache_files = max_concurrent_cache_files + + self.keep_in_memory = keep_in_memory + self._data_dir_root = os.path.expanduser(cache_dir or config.HF_METRICS_CACHE) + self.data_dir = self._build_data_dir() + if seed is None: + _, seed, pos, *_ = np.random.get_state() + self.seed: int = seed[pos] if pos < 624 else seed[0] + else: + self.seed: int = seed + self.timeout: Union[int, float] = timeout + + # Update 'compute' and 'add' docstring + # methods need to be copied otherwise it changes the docstrings of every instance + self.compute = types.MethodType(copyfunc(self.compute), self) + self.add_batch = types.MethodType(copyfunc(self.add_batch), self) + self.add = types.MethodType(copyfunc(self.add), self) + self.compute.__func__.__doc__ += self.info.inputs_description + self.add_batch.__func__.__doc__ += self.info.inputs_description + self.add.__func__.__doc__ += self.info.inputs_description + + # self.arrow_schema = pa.schema(field for field in self.info.features.type) + self.selected_feature_format = None + self.buf_writer = None + self.writer = None + self.writer_batch_size = None + self.data = None + + # This is the cache file we store our predictions/references in + # Keep it None for now so we can (cloud)pickle the object + self.cache_file_name = None + self.filelock = None + self.rendez_vous_lock = None + + # This is all the cache files on which we have a lock when we are in a distributed setting + self.file_paths = None + self.filelocks = None + + # This fingerprints the evaluation module according to the hashed contents of the module code + self._hash = hash + + def __len__(self): + """Return the number of examples (predictions or predictions/references pair) + currently stored in the evaluation module's cache. + """ + return 0 if self.writer is None else len(self.writer) + + def __repr__(self): + return ( + f'EvaluationModule(name: "{self.name}", module_type: "{self.module_type}", ' + f'features: {self.features}, usage: """{self.inputs_description}""", ' + f"stored examples: {len(self)})" + ) + + def _build_data_dir(self): + """Path of this evaluation module in cache_dir: + Will be: + self._data_dir_root/self.name/self.config_name/self.hash (if not none)/ + If any of these element is missing or if ``with_version=False`` the corresponding subfolders are dropped. + """ + builder_data_dir = self._data_dir_root + builder_data_dir = os.path.join(builder_data_dir, self.name, self.config_name) + os.makedirs(builder_data_dir, exist_ok=True) + return builder_data_dir + + def _create_cache_file(self, timeout=1) -> Tuple[str, FileLock]: + """Create a new cache file. If the default cache file is used, we generated a new hash.""" + file_path = os.path.join(self.data_dir, f"{self.experiment_id}-{self.num_process}-{self.process_id}.arrow") + filelock = None + for i in range(self.max_concurrent_cache_files): + filelock = FileLock(file_path + ".lock") + try: + filelock.acquire(timeout=timeout) + except Timeout: + # If we have reached the max number of attempts or we are not allow to find a free name (distributed setup) + # We raise an error + if self.num_process != 1: + raise ValueError( + f"Error in _create_cache_file: another evaluation module instance is already using the local cache file at {file_path}. " + f"Please specify an experiment_id (currently: {self.experiment_id}) to avoid collision " + f"between distributed evaluation module instances." + ) from None + if i == self.max_concurrent_cache_files - 1: + raise ValueError( + f"Cannot acquire lock, too many evaluation module instance are operating concurrently on this file system." + f"You should set a larger value of max_concurrent_cache_files when creating the evaluation module " + f"(current value is {self.max_concurrent_cache_files})." + ) from None + # In other cases (allow to find new file name + not yet at max num of attempts) we can try to sample a new hashing name. + file_uuid = str(uuid.uuid4()) + file_path = os.path.join( + self.data_dir, f"{self.experiment_id}-{file_uuid}-{self.num_process}-{self.process_id}.arrow" + ) + else: + break + + return file_path, filelock + + def _get_all_cache_files(self) -> Tuple[List[str], List[FileLock]]: + """Get a lock on all the cache files in a distributed setup. + We wait for timeout second to let all the distributed node finish their tasks (default is 100 seconds). + """ + if self.num_process == 1: + if self.cache_file_name is None: + raise ValueError( + "Evaluation module cache file doesn't exist. Please make sure that you call `add` or `add_batch` " + "at least once before calling `compute`." + ) + file_paths = [self.cache_file_name] + else: + file_paths = [ + os.path.join(self.data_dir, f"{self.experiment_id}-{self.num_process}-{process_id}.arrow") + for process_id in range(self.num_process) + ] + + # Let's acquire a lock on each process files to be sure they are finished writing + filelocks = [] + for process_id, file_path in enumerate(file_paths): + if process_id == 0: # process 0 already has its lock file + filelocks.append(self.filelock) + else: + filelock = FileLock(file_path + ".lock") + try: + filelock.acquire(timeout=self.timeout) + except Timeout: + raise ValueError( + f"Cannot acquire lock on cached file {file_path} for process {process_id}." + ) from None + else: + filelocks.append(filelock) + + return file_paths, filelocks + + def _check_all_processes_locks(self): + expected_lock_file_names = [ + os.path.join(self.data_dir, f"{self.experiment_id}-{self.num_process}-{process_id}.arrow.lock") + for process_id in range(self.num_process) + ] + for expected_lock_file_name in expected_lock_file_names: + nofilelock = FileFreeLock(expected_lock_file_name) + try: + nofilelock.acquire(timeout=self.timeout) + except Timeout: + raise ValueError( + f"Expected to find locked file {expected_lock_file_name} from process {self.process_id} but it doesn't exist." + ) from None + else: + nofilelock.release() + + def _check_rendez_vous(self): + expected_lock_file_name = os.path.join(self.data_dir, f"{self.experiment_id}-{self.num_process}-0.arrow.lock") + nofilelock = FileFreeLock(expected_lock_file_name) + try: + nofilelock.acquire(timeout=self.timeout) + except Timeout: + raise ValueError( + f"Expected to find locked file {expected_lock_file_name} from process {self.process_id} but it doesn't exist." + ) from None + else: + nofilelock.release() + lock_file_name = os.path.join(self.data_dir, f"{self.experiment_id}-{self.num_process}-rdv.lock") + rendez_vous_lock = FileLock(lock_file_name) + try: + rendez_vous_lock.acquire(timeout=self.timeout) + except Timeout: + raise ValueError(f"Couldn't acquire lock on {lock_file_name} from process {self.process_id}.") from None + else: + rendez_vous_lock.release() + + def _finalize(self): + """Close all the writing process and load/gather the data + from all the nodes if main node or all_process is True. + """ + if self.writer is not None: + self.writer.finalize() + self.writer = None + # release the locks of the processes > 0 so that process 0 can lock them to read + delete the data + if self.filelock is not None and self.process_id > 0: + self.filelock.release() + + if self.keep_in_memory: + # Read the predictions and references + reader = ArrowReader(path=self.data_dir, info=DatasetInfo(features=self.selected_feature_format)) + self.data = Dataset.from_buffer(self.buf_writer.getvalue()) + + elif self.process_id == 0: + # Let's acquire a lock on each node files to be sure they are finished writing + file_paths, filelocks = self._get_all_cache_files() + + # Read the predictions and references + try: + reader = ArrowReader(path="", info=DatasetInfo(features=self.selected_feature_format)) + self.data = Dataset(**reader.read_files([{"filename": f} for f in file_paths])) + except FileNotFoundError: + raise ValueError( + "Error in finalize: another evaluation module instance is already using the local cache file. " + "Please specify an experiment_id to avoid collision between distributed evaluation module instances." + ) from None + + # Store file paths and locks and we will release/delete them after the computation. + self.file_paths = file_paths + self.filelocks = filelocks + + def compute(self, *, predictions=None, references=None, **kwargs) -> Optional[dict]: + """Compute the evaluation module. + + Usage of positional arguments is not allowed to prevent mistakes. + + Args: + predictions (`list/array/tensor`, *optional*): + Predictions. + references (`list/array/tensor`, *optional*): + References. + **kwargs (optional): + Keyword arguments that will be forwarded to the evaluation module [`~evaluate.EvaluationModule.compute`] + method (see details in the docstring). + + Return: + `dict` or `None` + + - Dictionary with the results if this evaluation module is run on the main process (`process_id == 0`). + - `None` if the evaluation module is not run on the main process (`process_id != 0`). + + ```py + >>> import evaluate + >>> accuracy = evaluate.load("accuracy") + >>> accuracy.compute(predictions=[0, 1, 1, 0], references=[0, 1, 0, 1]) + ``` + """ + all_kwargs = {"predictions": predictions, "references": references, **kwargs} + if predictions is None and references is None: + missing_kwargs = {k: None for k in self._feature_names() if k not in all_kwargs} + all_kwargs.update(missing_kwargs) + else: + missing_inputs = [k for k in self._feature_names() if k not in all_kwargs] + if missing_inputs: + raise ValueError( + f"Evaluation module inputs are missing: {missing_inputs}. All required inputs are {list(self._feature_names())}" + ) + inputs = {input_name: all_kwargs[input_name] for input_name in self._feature_names()} + compute_kwargs = {k: kwargs[k] for k in kwargs if k not in self._feature_names()} + + if any(v is not None for v in inputs.values()): + self.add_batch(**inputs) + self._finalize() + + self.cache_file_name = None + self.filelock = None + self.selected_feature_format = None + + if self.process_id == 0: + self.data.set_format(type=self.info.format) + + inputs = {input_name: self.data[input_name][:] for input_name in self._feature_names()} + with temp_seed(self.seed): + output = self._compute(**inputs, **compute_kwargs) + + if self.buf_writer is not None: + self.buf_writer = None + del self.data + self.data = None + else: + # Release locks and delete all the cache files. Process 0 is released last. + for filelock, file_path in reversed(list(zip(self.filelocks, self.file_paths))): + logger.info(f"Removing {file_path}") + del self.data + self.data = None + del self.writer + self.writer = None + os.remove(file_path) + filelock.release() + + return output + else: + return None + + def add_batch(self, *, predictions=None, references=None, **kwargs): + """Add a batch of predictions and references for the evaluation module's stack. + + Args: + predictions (`list/array/tensor`, *optional*): + Predictions. + references (`list/array/tensor`, *optional*): + References. + + Example: + + ```py + >>> import evaluate + >>> accuracy = evaluate.load("accuracy") + >>> for refs, preds in zip([[0,1],[0,1]], [[1,0],[0,1]]): + ... accuracy.add_batch(references=refs, predictions=preds) + ``` + """ + bad_inputs = [input_name for input_name in kwargs if input_name not in self._feature_names()] + if bad_inputs: + raise ValueError( + f"Bad inputs for evaluation module: {bad_inputs}. All required inputs are {list(self._feature_names())}" + ) + batch = {"predictions": predictions, "references": references, **kwargs} + batch = {input_name: batch[input_name] for input_name in self._feature_names()} + if self.writer is None: + self.selected_feature_format = self._infer_feature_from_batch(batch) + self._init_writer() + try: + for key, column in batch.items(): + if len(column) > 0: + self._enforce_nested_string_type(self.selected_feature_format[key], column[0]) + batch = self.selected_feature_format.encode_batch(batch) + self.writer.write_batch(batch) + except (pa.ArrowInvalid, TypeError): + if any(len(batch[c]) != len(next(iter(batch.values()))) for c in batch): + col0 = next(iter(batch)) + bad_col = [c for c in batch if len(batch[c]) != len(batch[col0])][0] + error_msg = ( + f"Mismatch in the number of {col0} ({len(batch[col0])}) and {bad_col} ({len(batch[bad_col])})" + ) + elif set(self.selected_feature_format) != {"references", "predictions"}: + error_msg = ( + f"Module inputs don't match the expected format.\n" + f"Expected format: {self.selected_feature_format },\n" + ) + error_msg_inputs = ",\n".join( + f"Input {input_name}: {summarize_if_long_list(batch[input_name])}" + for input_name in self.selected_feature_format + ) + error_msg += error_msg_inputs + else: + error_msg = ( + f"Predictions and/or references don't match the expected format.\n" + f"Expected format: {self.selected_feature_format },\n" + f"Input predictions: {summarize_if_long_list(predictions)},\n" + f"Input references: {summarize_if_long_list(references)}" + ) + raise ValueError(error_msg) from None + + def add(self, *, prediction=None, reference=None, **kwargs): + """Add one prediction and reference for the evaluation module's stack. + + Args: + prediction (`list/array/tensor`, *optional*): + Predictions. + reference (`list/array/tensor`, *optional*): + References. + + Example: + + ```py + >>> import evaluate + >>> accuracy = evaluate.load("accuracy") + >>> accuracy.add(references=[0,1], predictions=[1,0]) + ``` + """ + bad_inputs = [input_name for input_name in kwargs if input_name not in self._feature_names()] + if bad_inputs: + raise ValueError( + f"Bad inputs for evaluation module: {bad_inputs}. All required inputs are {list(self._feature_names())}" + ) + example = {"predictions": prediction, "references": reference, **kwargs} + example = {input_name: example[input_name] for input_name in self._feature_names()} + if self.writer is None: + self.selected_feature_format = self._infer_feature_from_example(example) + self._init_writer() + try: + self._enforce_nested_string_type(self.selected_feature_format, example) + example = self.selected_feature_format.encode_example(example) + self.writer.write(example) + except (pa.ArrowInvalid, TypeError): + error_msg = ( + f"Evaluation module inputs don't match the expected format.\n" + f"Expected format: {self.selected_feature_format},\n" + ) + error_msg_inputs = ",\n".join( + f"Input {input_name}: {summarize_if_long_list(example[input_name])}" + for input_name in self.selected_feature_format + ) + error_msg += error_msg_inputs + raise ValueError(error_msg) from None + + def _infer_feature_from_batch(self, batch): + if isinstance(self.features, Features): + return self.features + else: + example = dict([(k, v[0]) for k, v in batch.items()]) + return self._infer_feature_from_example(example) + + def _infer_feature_from_example(self, example): + if isinstance(self.features, Features): + return self.features + else: + for features in self.features: + try: + self._enforce_nested_string_type(features, example) + features.encode_example(example) + return features + except (ValueError, TypeError): + continue + feature_strings = "\n".join([f"Feature option {i}: {feature}" for i, feature in enumerate(self.features)]) + error_msg = ( + f"Predictions and/or references don't match the expected format.\n" + f"Expected format:\n{feature_strings},\n" + f"Input predictions: {summarize_if_long_list(example['predictions'])},\n" + f"Input references: {summarize_if_long_list(example['references'])}" + ) + raise ValueError(error_msg) from None + + def _feature_names(self): + if isinstance(self.features, list): + feature_names = list(self.features[0].keys()) + else: + feature_names = list(self.features.keys()) + return feature_names + + def _init_writer(self, timeout=1): + if self.num_process > 1: + if self.process_id == 0: + file_path = os.path.join(self.data_dir, f"{self.experiment_id}-{self.num_process}-rdv.lock") + self.rendez_vous_lock = FileLock(file_path) + try: + self.rendez_vous_lock.acquire(timeout=timeout) + except TimeoutError: + raise ValueError( + f"Error in _init_writer: another evalution module instance is already using the local cache file at {file_path}. " + f"Please specify an experiment_id (currently: {self.experiment_id}) to avoid collision " + f"between distributed evaluation module instances." + ) from None + + if self.keep_in_memory: + self.buf_writer = pa.BufferOutputStream() + self.writer = ArrowWriter( + features=self.selected_feature_format, stream=self.buf_writer, writer_batch_size=self.writer_batch_size + ) + else: + self.buf_writer = None + + # Get cache file name and lock it + if self.cache_file_name is None or self.filelock is None: + cache_file_name, filelock = self._create_cache_file() # get ready + self.cache_file_name = cache_file_name + self.filelock = filelock + + self.writer = ArrowWriter( + features=self.selected_feature_format, + path=self.cache_file_name, + writer_batch_size=self.writer_batch_size, + ) + # Setup rendez-vous here if + if self.num_process > 1: + if self.process_id == 0: + self._check_all_processes_locks() # wait for everyone to be ready + self.rendez_vous_lock.release() # let everyone go + else: + self._check_rendez_vous() # wait for master to be ready and to let everyone go + + def _info(self) -> EvaluationModuleInfo: + """Construct the EvaluationModuleInfo object. See `EvaluationModuleInfo` for details. + + Warning: This function is only called once and the result is cached for all + following .info() calls. + + Returns: + info: (EvaluationModuleInfo) The EvaluationModule information + """ + raise NotImplementedError + + def download_and_prepare( + self, + download_config: Optional[DownloadConfig] = None, + dl_manager: Optional[DownloadManager] = None, + ): + """Downloads and prepares evaluation module for reading. + + Args: + download_config ([`DownloadConfig`], *optional*): + Specific download configuration parameters. + dl_manager ([`DownloadManager`], *optional*): + Specific download manager to use. + + Example: + + ```py + >>> import evaluate + ``` + """ + if dl_manager is None: + if download_config is None: + download_config = DownloadConfig() + download_config.cache_dir = os.path.join(self.data_dir, "downloads") + download_config.force_download = False + + dl_manager = DownloadManager( + dataset_name=self.name, download_config=download_config, data_dir=self.data_dir + ) + + self._download_and_prepare(dl_manager) + + def _download_and_prepare(self, dl_manager): + """Downloads and prepares resources for the evaluation module. + + This is the internal implementation to overwrite called when user calls + `download_and_prepare`. It should download all required resources for the evaluation module. + + Args: + dl_manager (:class:`DownloadManager`): `DownloadManager` used to download and cache data. + """ + return None + + def _compute(self, *, predictions=None, references=None, **kwargs) -> Dict[str, Any]: + """This method defines the common API for all the evaluation module in the library""" + raise NotImplementedError + + def __del__(self): + if hasattr(self, "filelock") and self.filelock is not None: + self.filelock.release() + if hasattr(self, "rendez_vous_lock") and self.rendez_vous_lock is not None: + self.rendez_vous_lock.release() + if hasattr(self, "writer"): # in case it was already deleted + del self.writer + if hasattr(self, "data"): # in case it was already deleted + del self.data + + def _enforce_nested_string_type(self, schema, obj): + """ + Recursively checks if there is any Value feature of type string and throws TypeError if corresponding object is not a string. + Since any Python object can be cast to string this avoids implicitly casting wrong input types (e.g. lists) to string without error. + """ + # Nested structures: we allow dict, list, tuples, sequences + if isinstance(schema, dict): + return [self._enforce_nested_string_type(sub_schema, o) for k, (sub_schema, o) in zip_dict(schema, obj)] + + elif isinstance(schema, (list, tuple)): + sub_schema = schema[0] + return [self._enforce_nested_string_type(sub_schema, o) for o in obj] + elif isinstance(schema, Sequence): + # We allow to reverse list of dict => dict of list for compatiblity with tfds + if isinstance(schema.feature, dict): + if isinstance(obj, (list, tuple)): + # obj is a list of dict + for k, dict_tuples in zip_dict(schema.feature, *obj): + for sub_obj in dict_tuples[1:]: + if _check_non_null_non_empty_recursive(sub_obj, dict_tuples[0]): + self._enforce_nested_string_type(dict_tuples[0], sub_obj) + break + return None + else: + # obj is a single dict + for k, (sub_schema, sub_objs) in zip_dict(schema.feature, obj): + for sub_obj in sub_objs: + if _check_non_null_non_empty_recursive(sub_obj, sub_schema): + self._enforce_nested_string_type(sub_schema, sub_obj) + break + return None + # schema.feature is not a dict + if isinstance(obj, str): # don't interpret a string as a list + raise ValueError(f"Got a string but expected a list instead: '{obj}'") + if obj is None: + return None + else: + if len(obj) > 0: + for first_elmt in obj: + if _check_non_null_non_empty_recursive(first_elmt, schema.feature): + break + if not isinstance(first_elmt, list): + return self._enforce_nested_string_type(schema.feature, first_elmt) + + elif isinstance(schema, Value): + if pa.types.is_string(schema.pa_type) and not isinstance(obj, str): + raise TypeError(f"Expected type str but got {type(obj)}.") + + +class Metric(EvaluationModule): + """A Metric is the base class and common API for all metrics. + + Args: + config_name (`str`): + This is used to define a hash specific to a metric computation script and prevents the metric's data + to be overridden when the metric loading script is modified. + keep_in_memory (`bool`): + Keep all predictions and references in memory. Not possible in distributed settings. + cache_dir (`str`): + Path to a directory in which temporary prediction/references data will be stored. + The data directory should be located on a shared file-system in distributed setups. + num_process (`int`): + Specify the total number of nodes in a distributed settings. + This is useful to compute metrics in distributed setups (in particular non-additive metrics like F1). + process_id (`int`): + Specify the id of the current process in a distributed setup (between 0 and num_process-1) + This is useful to compute metrics in distributed setups (in particular non-additive metrics like F1). + seed (`int`, *optional*): + If specified, this will temporarily set numpy's random seed when [`~evaluate.Metric.compute`] is run. + experiment_id (`str`): + A specific experiment id. This is used if several distributed evaluations share the same file system. + This is useful to compute metrics in distributed setups (in particular non-additive metrics like F1). + max_concurrent_cache_files (`int`): + Max number of concurrent metric cache files (default `10000`). + timeout (`Union[int, float]`): + Timeout in second for distributed setting synchronization. + """ + + +class Comparison(EvaluationModule): + """A Comparison is the base class and common API for all comparisons. + + Args: + config_name (`str`): + This is used to define a hash specific to a comparison computation script and prevents the comparison's data + to be overridden when the comparison loading script is modified. + keep_in_memory (`bool`): + Keep all predictions and references in memory. Not possible in distributed settings. + cache_dir (`str`): + Path to a directory in which temporary prediction/references data will be stored. + The data directory should be located on a shared file-system in distributed setups. + num_process (`int`): + Specify the total number of nodes in a distributed settings. + This is useful to compute comparisons in distributed setups (in particular non-additive comparisons). + process_id (`int`): + Specify the id of the current process in a distributed setup (between 0 and num_process-1) + This is useful to compute comparisons in distributed setups (in particular non-additive comparisons). + seed (`int`, *optional*): + If specified, this will temporarily set numpy's random seed when [`~evaluate.Comparison.compute`] is run. + experiment_id (`str`): + A specific experiment id. This is used if several distributed evaluations share the same file system. + This is useful to compute comparisons in distributed setups (in particular non-additive comparisons). + max_concurrent_cache_files (`int`): + Max number of concurrent comparison cache files (default `10000`). + timeout (`Union[int, float]`): + Timeout in second for distributed setting synchronization. + """ + + +class Measurement(EvaluationModule): + """A Measurement is the base class and common API for all measurements. + + Args: + config_name (`str`): + This is used to define a hash specific to a measurement computation script and prevents the measurement's data + to be overridden when the measurement loading script is modified. + keep_in_memory (`bool`): + Keep all predictions and references in memory. Not possible in distributed settings. + cache_dir (`str`): + Path to a directory in which temporary prediction/references data will be stored. + The data directory should be located on a shared file-system in distributed setups. + num_process (`int`): + Specify the total number of nodes in a distributed settings. + This is useful to compute measurements in distributed setups (in particular non-additive measurements). + process_id (`int`): + Specify the id of the current process in a distributed setup (between 0 and num_process-1) + This is useful to compute measurements in distributed setups (in particular non-additive measurements). + seed (`int`, *optional*): + If specified, this will temporarily set numpy's random seed when [`~evaluate.Measurement.compute`] is run. + experiment_id (`str`): + A specific experiment id. This is used if several distributed evaluations share the same file system. + This is useful to compute measurements in distributed setups (in particular non-additive measurements). + max_concurrent_cache_files (`int`): + Max number of concurrent measurement cache files (default `10000`). + timeout (`Union[int, float]`): + Timeout in second for distributed setting synchronization. + """ + + +class CombinedEvaluations: + def __init__(self, evaluation_modules, force_prefix=False): + from .loading import load # avoid circular imports + + self.evaluation_module_names = None + if isinstance(evaluation_modules, list): + self.evaluation_modules = evaluation_modules + elif isinstance(evaluation_modules, dict): + self.evaluation_modules = list(evaluation_modules.values()) + self.evaluation_module_names = list(evaluation_modules.keys()) + loaded_modules = [] + + for module in self.evaluation_modules: + if isinstance(module, str): + module = load(module) + loaded_modules.append(module) + self.evaluation_modules = loaded_modules + + if self.evaluation_module_names is None: + self.evaluation_module_names = [module.name for module in self.evaluation_modules] + + self.force_prefix = force_prefix + + def add(self, prediction=None, reference=None, **kwargs): + """Add one prediction and reference for each evaluation module's stack. + + Args: + predictions (`list/array/tensor`, *optional*): + Predictions. + references (`list/array/tensor`, *optional*): + References. + + Example: + + ```py + >>> import evaluate + >>> accuracy = evaluate.load("accuracy") + >>> f1 = evaluate.load("f1") + >>> clf_metrics = combine(["accuracy", "f1"]) + >>> for ref, pred in zip([0,1,0,1], [1,0,0,1]): + ... clf_metrics.add(references=ref, predictions=pred) + ``` + """ + for evaluation_module in self.evaluation_modules: + batch = {"predictions": prediction, "references": reference, **kwargs} + batch = {input_name: batch[input_name] for input_name in evaluation_module._feature_names()} + evaluation_module.add(**batch) + + def add_batch(self, predictions=None, references=None, **kwargs): + """Add a batch of predictions and references for each evaluation module's stack. + + Args: + predictions (`list/array/tensor`, *optional*): + Predictions. + references (`list/array/tensor`, *optional*): + References. + + Example: + ```py + >>> import evaluate + >>> accuracy = evaluate.load("accuracy") + >>> f1 = evaluate.load("f1") + >>> clf_metrics = combine(["accuracy", "f1"]) + >>> for refs, preds in zip([[0,1],[0,1]], [[1,0],[0,1]]): + ... clf_metrics.add(references=refs, predictions=preds) + ``` + """ + for evaluation_module in self.evaluation_modules: + batch = {"predictions": predictions, "references": references, **kwargs} + batch = {input_name: batch[input_name] for input_name in evaluation_module._feature_names()} + evaluation_module.add_batch(**batch) + + def compute(self, predictions=None, references=None, **kwargs): + """Compute each evaluation module. + + Usage of positional arguments is not allowed to prevent mistakes. + + Args: + predictions (`list/array/tensor`, *optional*): + Predictions. + references (`list/array/tensor`, *optional*): + References. + **kwargs (*optional*): + Keyword arguments that will be forwarded to the evaluation module [`~evaluate.EvaluationModule.compute`] + method (see details in the docstring). + + Return: + `dict` or `None` + + - Dictionary with the results if this evaluation module is run on the main process (`process_id == 0`). + - `None` if the evaluation module is not run on the main process (`process_id != 0`). + + Example: + + ```py + >>> import evaluate + >>> accuracy = evaluate.load("accuracy") + >>> f1 = evaluate.load("f1") + >>> clf_metrics = combine(["accuracy", "f1"]) + >>> clf_metrics.compute(predictions=[0,1], references=[1,1]) + {'accuracy': 0.5, 'f1': 0.6666666666666666} + ``` + """ + results = [] + + for evaluation_module in self.evaluation_modules: + batch = {"predictions": predictions, "references": references, **kwargs} + results.append(evaluation_module.compute(**batch)) + + return self._merge_results(results) + + def _merge_results(self, results): + merged_results = {} + results_keys = list(itertools.chain.from_iterable([r.keys() for r in results])) + duplicate_keys = {item for item, count in collections.Counter(results_keys).items() if count > 1} + + duplicate_names = [ + item for item, count in collections.Counter(self.evaluation_module_names).items() if count > 1 + ] + duplicate_counter = {name: 0 for name in duplicate_names} + + for module_name, result in zip(self.evaluation_module_names, results): + for k, v in result.items(): + if k not in duplicate_keys and not self.force_prefix: + merged_results[f"{k}"] = v + elif module_name in duplicate_counter: + merged_results[f"{module_name}_{duplicate_counter[module_name]}_{k}"] = v + else: + merged_results[f"{module_name}_{k}"] = v + + if module_name in duplicate_counter: + duplicate_counter[module_name] += 1 + + return merged_results + + +def combine(evaluations, force_prefix=False): + """Combines several metrics, comparisons, or measurements into a single `CombinedEvaluations` object that + can be used like a single evaluation module. + + If two scores have the same name, then they are prefixed with their module names. + And if two modules have the same name, please use a dictionary to give them different names, otherwise an integer id is appended to the prefix. + + Args: + evaluations (`Union[list, dict]`): + A list or dictionary of evaluation modules. The modules can either be passed + as strings or loaded `EvaluationModule`s. If a dictionary is passed its keys are the names used and the values the modules. + The names are used as prefix in case there are name overlaps in the returned results of each module or if `force_prefix=True`. + force_prefix (`bool`, *optional*, defaults to `False`): + If `True` all scores from the modules are prefixed with their name. If + a dictionary is passed the keys are used as name otherwise the module's name. + + Examples: + + ```py + >>> import evaluate + >>> accuracy = evaluate.load("accuracy") + >>> f1 = evaluate.load("f1") + >>> clf_metrics = combine(["accuracy", "f1"]) + ``` + """ + + return CombinedEvaluations(evaluations, force_prefix=force_prefix) diff --git a/venv/lib/python3.10/site-packages/evaluate/naming.py b/venv/lib/python3.10/site-packages/evaluate/naming.py new file mode 100644 index 0000000000000000000000000000000000000000..6335cf1b0ff47f0f2a409d6641fd5c528a31e949 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/naming.py @@ -0,0 +1,82 @@ +# Copyright 2020 The HuggingFace Datasets Authors and the TensorFlow Datasets Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +"""Utilities for file names.""" + +import itertools +import os +import re + + +_uppercase_uppercase_re = re.compile(r"([A-Z]+)([A-Z][a-z])") +_lowercase_uppercase_re = re.compile(r"([a-z\d])([A-Z])") + +_single_underscore_re = re.compile(r"(?>> import evaluate + >>> result = {"bleu": 0.7} + >>> params = {"model": "gpt-2"} + >>> evaluate.save("./results/", **result, **params) + ``` + """ + current_time = datetime.now() + + file_path = _setup_path(path_or_file, current_time) + + data["_timestamp"] = current_time.isoformat() + data["_git_commit_hash"] = _git_commit_hash() + data["_evaluate_version"] = __version__ + data["_python_version"] = sys.version + data["_interpreter_path"] = sys.executable + + with FileLock(str(file_path) + ".lock"): + with open(file_path, "w") as f: + json.dump(data, f) + + # cleanup lock file + try: + os.remove(str(file_path) + ".lock") + except FileNotFoundError: + pass + + return file_path + + +def _setup_path(path_or_file, current_time): + path_or_file = Path(path_or_file) + is_file = len(path_or_file.suffix) > 0 + if is_file: + folder = path_or_file.parent + file_name = path_or_file.name + else: + folder = path_or_file + file_name = "result-" + current_time.strftime("%Y_%m_%d-%H_%M_%S") + ".json" + folder.mkdir(parents=True, exist_ok=True) + return folder / file_name + + +def _git_commit_hash(): + res = subprocess.run("git rev-parse --is-inside-work-tree".split(), cwd="./", stdout=subprocess.PIPE) + if res.stdout.decode().strip() == "true": + res = subprocess.run("git rev-parse HEAD".split(), cwd=os.getcwd(), stdout=subprocess.PIPE) + return res.stdout.decode().strip() + else: + return None diff --git a/venv/lib/python3.10/site-packages/evaluate/utils/__init__.py b/venv/lib/python3.10/site-packages/evaluate/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fac692eeb80def645bce9901324902ac9e1899f5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/utils/__init__.py @@ -0,0 +1,39 @@ +# Copyright 2020 The HuggingFace Datasets Authors and the TensorFlow Datasets Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# flake8: noqa +# Lint as: python3 +"""Util import.""" + +__all__ = [ + "disable_progress_bar", + "enable_progress_bar", + "is_progress_bar_enabled", + "infer_gradio_input_types", + "json_to_string_type", + "parse_readme", + "parse_gradio_data", + "parse_test_cases", + "launch_gradio_widget", +] + +from .gradio import ( + infer_gradio_input_types, + json_to_string_type, + launch_gradio_widget, + parse_gradio_data, + parse_readme, + parse_test_cases, +) +from .logging import disable_progress_bar, enable_progress_bar, is_progress_bar_enabled diff --git a/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5878cb48d8caf8f1df25fa562da8a500d8d647dd Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/file_utils.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/file_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4f6acf727e6ccc74a05488f38660d2962521cfc Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/file_utils.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/gradio.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/gradio.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..876a28ec0056ab11a66237ab2897728ff7d5b2c4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/gradio.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/logging.cpython-310.pyc b/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/logging.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77ed320226e170a9507772cbc24cacd8772aa0ab Binary files /dev/null and b/venv/lib/python3.10/site-packages/evaluate/utils/__pycache__/logging.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/evaluate/utils/file_utils.py b/venv/lib/python3.10/site-packages/evaluate/utils/file_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..4c278905f9bf9a61810b6ba256dae331ce6d8a8c --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/utils/file_utils.py @@ -0,0 +1,605 @@ +""" +Utilities for working with the local dataset cache. +This file is adapted from the AllenNLP library at https://github.com/allenai/allennlp +Copyright by the AllenNLP authors. +""" + +import copy +import io +import json +import os +import posixpath +import re +import shutil +import sys +import tempfile +import time +import urllib +from contextlib import closing, contextmanager +from functools import partial +from hashlib import sha256 +from pathlib import Path +from typing import List, Optional, Type, TypeVar, Union +from urllib.parse import urlparse + +import requests +from datasets import DownloadConfig +from datasets.utils.extract import ExtractManager +from datasets.utils.filelock import FileLock + +from .. import __version__, config +from . import logging + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + +INCOMPLETE_SUFFIX = ".incomplete" + +T = TypeVar("T", str, Path) + + +def init_hf_modules(hf_modules_cache: Optional[Union[Path, str]] = None) -> str: + """ + Add hf_modules_cache to the python path. + By default hf_modules_cache='~/.cache/huggingface/modules'. + It can also be set with the environment variable HF_MODULES_CACHE. + This is used to add modules such as `datasets_modules` + """ + hf_modules_cache = hf_modules_cache if hf_modules_cache is not None else config.HF_MODULES_CACHE + hf_modules_cache = str(hf_modules_cache) + if hf_modules_cache not in sys.path: + sys.path.append(hf_modules_cache) + + os.makedirs(hf_modules_cache, exist_ok=True) + if not os.path.exists(os.path.join(hf_modules_cache, "__init__.py")): + with open(os.path.join(hf_modules_cache, "__init__.py"), "w"): + pass + return hf_modules_cache + + +def is_remote_url(url_or_filename: str) -> bool: + parsed = urlparse(url_or_filename) + return parsed.scheme in ("http", "https", "s3", "gs", "hdfs", "ftp") + + +def is_local_path(url_or_filename: str) -> bool: + # On unix the scheme of a local path is empty (for both absolute and relative), + # while on windows the scheme is the drive name (ex: "c") for absolute paths. + # for details on the windows behavior, see https://bugs.python.org/issue42215 + return urlparse(url_or_filename).scheme == "" or os.path.ismount(urlparse(url_or_filename).scheme + ":/") + + +def is_relative_path(url_or_filename: str) -> bool: + return urlparse(url_or_filename).scheme == "" and not os.path.isabs(url_or_filename) + + +def relative_to_absolute_path(path: T) -> T: + """Convert relative path to absolute path.""" + abs_path_str = os.path.abspath(os.path.expanduser(os.path.expandvars(str(path)))) + return Path(abs_path_str) if isinstance(path, Path) else abs_path_str + + +def hf_bucket_url(identifier: str, filename: str, use_cdn=False, dataset=True) -> str: + if dataset: + endpoint = config.CLOUDFRONT_DATASETS_DISTRIB_PREFIX if use_cdn else config.S3_DATASETS_BUCKET_PREFIX + else: + endpoint = config.CLOUDFRONT_METRICS_DISTRIB_PREFIX if use_cdn else config.S3_METRICS_BUCKET_PREFIX + return "/".join((endpoint, identifier, filename)) + + +def head_hf_s3( + identifier: str, filename: str, use_cdn=False, dataset=True, max_retries=0 +) -> Union[requests.Response, Exception]: + return http_head( + hf_bucket_url(identifier=identifier, filename=filename, use_cdn=use_cdn, dataset=dataset), + max_retries=max_retries, + ) + + +def hf_hub_url(path: str, name: str, revision: Optional[str] = None) -> str: + revision = revision or config.HUB_DEFAULT_VERSION + return config.HUB_EVALUATE_URL.format(path=path, name=name, revision=revision) + + +def url_or_path_join(base_name: str, *pathnames: str) -> str: + if is_remote_url(base_name): + return posixpath.join(base_name, *(str(pathname).replace(os.sep, "/").lstrip("/") for pathname in pathnames)) + else: + return Path(base_name, *pathnames).as_posix() + + +def url_or_path_parent(url_or_path: str) -> str: + if is_remote_url(url_or_path): + return url_or_path[: url_or_path.rindex("/")] + else: + return os.path.dirname(url_or_path) + + +def hash_url_to_filename(url, etag=None): + """ + Convert `url` into a hashed filename in a repeatable way. + If `etag` is specified, append its hash to the url's, delimited + by a period. + If the url ends with .h5 (Keras HDF5 weights) adds '.h5' to the name + so that TF 2.0 can identify it as a HDF5 file + (see https://github.com/tensorflow/tensorflow/blob/00fad90125b18b80fe054de1055770cfb8fe4ba3/tensorflow/python/keras/engine/network.py#L1380) + """ + url_bytes = url.encode("utf-8") + url_hash = sha256(url_bytes) + filename = url_hash.hexdigest() + + if etag: + etag_bytes = etag.encode("utf-8") + etag_hash = sha256(etag_bytes) + filename += "." + etag_hash.hexdigest() + + if url.endswith(".py"): + filename += ".py" + + return filename + + +def cached_path( + url_or_filename, + download_config=None, + **download_kwargs, +) -> str: + """ + Given something that might be a URL (or might be a local path), + determine which. If it's a URL, download the file and cache it, and + return the path to the cached file. If it's already a local path, + make sure the file exists and then return the path. + + Return: + Local path (string) + + Raises: + FileNotFoundError: in case of non-recoverable file + (non-existent or no cache on disk) + ConnectionError: in case of unreachable url + and no cache on disk + ValueError: if it couldn't parse the url or filename correctly + requests.exceptions.ConnectionError: in case of internet connection issue + """ + if download_config is None: + download_config = DownloadConfig(**download_kwargs) + + cache_dir = download_config.cache_dir or config.DOWNLOADED_EVALUATE_PATH + if isinstance(cache_dir, Path): + cache_dir = str(cache_dir) + if isinstance(url_or_filename, Path): + url_or_filename = str(url_or_filename) + + if is_remote_url(url_or_filename): + # URL, so get it from the cache (downloading if necessary) + output_path = get_from_cache( + url_or_filename, + cache_dir=cache_dir, + force_download=download_config.force_download, + proxies=download_config.proxies, + resume_download=download_config.resume_download, + user_agent=download_config.user_agent, + local_files_only=download_config.local_files_only, + use_etag=download_config.use_etag, + max_retries=download_config.max_retries, + token=download_config.token, + download_desc=download_config.download_desc, + ) + elif os.path.exists(url_or_filename): + # File, and it exists. + output_path = url_or_filename + elif is_local_path(url_or_filename): + # File, but it doesn't exist. + raise FileNotFoundError(f"Local file {url_or_filename} doesn't exist") + else: + # Something unknown + raise ValueError(f"unable to parse {url_or_filename} as a URL or as a local path") + + if output_path is None: + return output_path + + if download_config.extract_compressed_file: + output_path = ExtractManager(cache_dir=download_config.cache_dir).extract( + output_path, force_extract=download_config.force_extract + ) + + return output_path + + +def get_datasets_user_agent(user_agent: Optional[Union[str, dict]] = None) -> str: + ua = f"datasets/{__version__}; python/{config.PY_VERSION}" + ua += f"; pyarrow/{config.PYARROW_VERSION}" + if config.TORCH_AVAILABLE: + ua += f"; torch/{config.TORCH_VERSION}" + if config.TF_AVAILABLE: + ua += f"; tensorflow/{config.TF_VERSION}" + if config.JAX_AVAILABLE: + ua += f"; jax/{config.JAX_VERSION}" + if isinstance(user_agent, dict): + ua += f"; {'; '.join(f'{k}/{v}' for k, v in user_agent.items())}" + elif isinstance(user_agent, str): + ua += "; " + user_agent + return ua + + +def get_authentication_headers_for_url(url: str, token: Optional[Union[str, bool]] = None) -> dict: + """Handle the HF authentication""" + headers = {} + if url.startswith(config.HF_ENDPOINT): + from huggingface_hub.utils import build_hf_headers + + headers = build_hf_headers(token=token, library_name="evaluate", library_version=__version__) + + return headers + + +class OfflineModeIsEnabled(ConnectionError): + pass + + +def _raise_if_offline_mode_is_enabled(msg: Optional[str] = None): + """Raise an OfflineModeIsEnabled error (subclass of ConnectionError) if HF_EVALUATE_OFFLINE is True.""" + if config.HF_EVALUATE_OFFLINE: + raise OfflineModeIsEnabled( + "Offline mode is enabled." if msg is None else "Offline mode is enabled. " + str(msg) + ) + + +def _retry( + func, + func_args: Optional[tuple] = None, + func_kwargs: Optional[dict] = None, + exceptions: Type[requests.exceptions.RequestException] = requests.exceptions.RequestException, + status_codes: Optional[List[int]] = None, + max_retries: int = 0, + base_wait_time: float = 0.5, + max_wait_time: float = 2, +): + func_args = func_args or () + func_kwargs = func_kwargs or {} + retry = 0 + while True: + try: + return func(*func_args, **func_kwargs) + except exceptions as err: + if retry >= max_retries or (status_codes and err.response.status_code not in status_codes): + raise err + else: + sleep_time = min(max_wait_time, base_wait_time * 2**retry) # Exponential backoff + logger.info(f"{func} timed out, retrying in {sleep_time}s... [{retry/max_retries}]") + time.sleep(sleep_time) + retry += 1 + + +def _request_with_retry( + method: str, + url: str, + max_retries: int = 0, + base_wait_time: float = 0.5, + max_wait_time: float = 2, + timeout: float = 10.0, + **params, +) -> requests.Response: + """Wrapper around requests to retry in case it fails with a ConnectTimeout, with exponential backoff. + + Note that if the environment variable HF_EVALUATE_OFFLINE is set to 1, then a OfflineModeIsEnabled error is raised. + + Args: + method (str): HTTP method, such as 'GET' or 'HEAD'. + url (str): The URL of the resource to fetch. + max_retries (int): Maximum number of retries, defaults to 0 (no retries). + base_wait_time (float): Duration (in seconds) to wait before retrying the first time. Wait time between + retries then grows exponentially, capped by max_wait_time. + max_wait_time (float): Maximum amount of time between two retries, in seconds. + **params: Params to pass to :obj:`requests.request`. + """ + _raise_if_offline_mode_is_enabled(f"Tried to reach {url}") + tries, success = 0, False + while not success: + tries += 1 + try: + response = requests.request(method=method.upper(), url=url, timeout=timeout, **params) + success = True + except (requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError) as err: + if tries > max_retries: + raise err + else: + logger.info(f"{method} request to {url} timed out, retrying... [{tries/max_retries}]") + sleep_time = min(max_wait_time, base_wait_time * 2 ** (tries - 1)) # Exponential backoff + time.sleep(sleep_time) + return response + + +def ftp_head(url, timeout=10.0): + _raise_if_offline_mode_is_enabled(f"Tried to reach {url}") + try: + with closing(urllib.request.urlopen(url, timeout=timeout)) as r: + r.read(1) + except Exception: + return False + return True + + +def ftp_get(url, temp_file, timeout=10.0): + _raise_if_offline_mode_is_enabled(f"Tried to reach {url}") + try: + logger.info(f"Getting through FTP {url} into {temp_file.name}") + with closing(urllib.request.urlopen(url, timeout=timeout)) as r: + shutil.copyfileobj(r, temp_file) + except urllib.error.URLError as e: + raise ConnectionError(e) from None + + +def http_get( + url, temp_file, proxies=None, resume_size=0, headers=None, cookies=None, timeout=100.0, max_retries=0, desc=None +): + headers = copy.deepcopy(headers) or {} + headers["user-agent"] = get_datasets_user_agent(user_agent=headers.get("user-agent")) + if resume_size > 0: + headers["Range"] = f"bytes={resume_size:d}-" + response = _request_with_retry( + method="GET", + url=url, + stream=True, + proxies=proxies, + headers=headers, + cookies=cookies, + max_retries=max_retries, + timeout=timeout, + ) + if response.status_code == 416: # Range not satisfiable + return + content_length = response.headers.get("Content-Length") + total = resume_size + int(content_length) if content_length is not None else None + with logging.tqdm( + unit="B", + unit_scale=True, + total=total, + initial=resume_size, + desc=desc or "Downloading", + disable=not logging.is_progress_bar_enabled(), + ) as progress: + for chunk in response.iter_content(chunk_size=1024): + progress.update(len(chunk)) + temp_file.write(chunk) + + +def http_head( + url, proxies=None, headers=None, cookies=None, allow_redirects=True, timeout=10.0, max_retries=0 +) -> requests.Response: + headers = copy.deepcopy(headers) or {} + headers["user-agent"] = get_datasets_user_agent(user_agent=headers.get("user-agent")) + response = _request_with_retry( + method="HEAD", + url=url, + proxies=proxies, + headers=headers, + cookies=cookies, + allow_redirects=allow_redirects, + timeout=timeout, + max_retries=max_retries, + ) + return response + + +def request_etag(url: str, token: Optional[Union[str, bool]] = None) -> Optional[str]: + headers = get_authentication_headers_for_url(url, token=token) + response = http_head(url, headers=headers, max_retries=3) + response.raise_for_status() + etag = response.headers.get("ETag") if response.ok else None + return etag + + +def get_from_cache( + url, + cache_dir=None, + force_download=False, + proxies=None, + etag_timeout=100, + resume_download=False, + user_agent=None, + local_files_only=False, + use_etag=True, + max_retries=0, + token=None, + download_desc=None, +) -> str: + """ + Given a URL, look for the corresponding file in the local cache. + If it's not there, download it. Then return the path to the cached file. + + Return: + Local path (string) + + Raises: + FileNotFoundError: in case of non-recoverable file + (non-existent or no cache on disk) + ConnectionError: in case of unreachable url + and no cache on disk + """ + if cache_dir is None: + cache_dir = config.HF_EVALUATE_CACHE + if isinstance(cache_dir, Path): + cache_dir = str(cache_dir) + + os.makedirs(cache_dir, exist_ok=True) + + connected = False + response = None + cookies = None + etag = None + head_error = None + + # Try a first time to file the file on the local file system without eTag (None) + # if we don't ask for 'force_download' then we spare a request + filename = hash_url_to_filename(url, etag=None) + cache_path = os.path.join(cache_dir, filename) + + if os.path.exists(cache_path) and not force_download and not use_etag: + return cache_path + + # Prepare headers for authentication + headers = get_authentication_headers_for_url(url, token=token) + if user_agent is not None: + headers["user-agent"] = user_agent + + # We don't have the file locally or we need an eTag + if not local_files_only: + if url.startswith("ftp://"): + connected = ftp_head(url) + try: + response = http_head( + url, + allow_redirects=True, + proxies=proxies, + timeout=etag_timeout, + max_retries=max_retries, + headers=headers, + ) + if response.status_code == 200: # ok + etag = response.headers.get("ETag") if use_etag else None + for k, v in response.cookies.items(): + # In some edge cases, we need to get a confirmation token + if k.startswith("download_warning") and "drive.google.com" in url: + url += "&confirm=" + v + cookies = response.cookies + connected = True + # Fix Google Drive URL to avoid Virus scan warning + if "drive.google.com" in url and "confirm=" not in url: + url += "&confirm=t" + # In some edge cases, head request returns 400 but the connection is actually ok + elif ( + (response.status_code == 400 and "firebasestorage.googleapis.com" in url) + or (response.status_code == 405 and "drive.google.com" in url) + or ( + response.status_code == 403 + and ( + re.match(r"^https?://github.com/.*?/.*?/releases/download/.*?/.*?$", url) + or re.match(r"^https://.*?s3.*?amazonaws.com/.*?$", response.url) + ) + ) + or (response.status_code == 403 and "ndownloader.figstatic.com" in url) + ): + connected = True + logger.info(f"Couldn't get ETag version for url {url}") + elif response.status_code == 401 and config.HF_ENDPOINT in url and token is None: + raise ConnectionError( + f"Unauthorized for URL {url}. Please use the parameter ``token=True`` after logging in with ``huggingface-cli login``" + ) + except (OSError, requests.exceptions.Timeout) as e: + # not connected + head_error = e + pass + + # connected == False = we don't have a connection, or url doesn't exist, or is otherwise inaccessible. + # try to get the last downloaded one + if not connected: + if os.path.exists(cache_path) and not force_download: + return cache_path + if local_files_only: + raise FileNotFoundError( + f"Cannot find the requested files in the cached path at {cache_path} and outgoing traffic has been" + " disabled. To enable file online look-ups, set 'local_files_only' to False." + ) + elif response is not None and response.status_code == 404: + raise FileNotFoundError(f"Couldn't find file at {url}") + _raise_if_offline_mode_is_enabled(f"Tried to reach {url}") + if head_error is not None: + raise ConnectionError(f"Couldn't reach {url} ({repr(head_error)})") + elif response is not None: + raise ConnectionError(f"Couldn't reach {url} (error {response.status_code})") + else: + raise ConnectionError(f"Couldn't reach {url}") + + # Try a second time + filename = hash_url_to_filename(url, etag) + cache_path = os.path.join(cache_dir, filename) + + if os.path.exists(cache_path) and not force_download: + return cache_path + + # From now on, connected is True. + # Prevent parallel downloads of the same file with a lock. + lock_path = cache_path + ".lock" + with FileLock(lock_path): + + if resume_download: + incomplete_path = cache_path + ".incomplete" + + @contextmanager + def _resumable_file_manager(): + with open(incomplete_path, "a+b") as f: + yield f + + temp_file_manager = _resumable_file_manager + if os.path.exists(incomplete_path): + resume_size = os.stat(incomplete_path).st_size + else: + resume_size = 0 + else: + temp_file_manager = partial(tempfile.NamedTemporaryFile, dir=cache_dir, delete=False) + resume_size = 0 + + # Download to temporary file, then copy to cache dir once finished. + # Otherwise you get corrupt cache entries if the download gets interrupted. + with temp_file_manager() as temp_file: + logger.info(f"{url} not found in cache or force_download set to True, downloading to {temp_file.name}") + + # GET file object + if url.startswith("ftp://"): + ftp_get(url, temp_file) + else: + http_get( + url, + temp_file, + proxies=proxies, + resume_size=resume_size, + headers=headers, + cookies=cookies, + max_retries=max_retries, + desc=download_desc, + ) + + logger.info(f"storing {url} in cache at {cache_path}") + shutil.move(temp_file.name, cache_path) + + logger.info(f"creating metadata file for {cache_path}") + meta = {"url": url, "etag": etag} + meta_path = cache_path + ".json" + with open(meta_path, "w", encoding="utf-8") as meta_file: + json.dump(meta, meta_file) + + return cache_path + + +def add_start_docstrings(*docstr): + def docstring_decorator(fn): + fn.__doc__ = "".join(docstr) + "\n\n" + (fn.__doc__ if fn.__doc__ is not None else "") + return fn + + return docstring_decorator + + +def add_end_docstrings(*docstr): + def docstring_decorator(fn): + fn.__doc__ = (fn.__doc__ if fn.__doc__ is not None else "") + "\n\n" + "".join(docstr) + return fn + + return docstring_decorator + + +def estimate_dataset_size(paths): + return sum(path.stat().st_size for path in paths) + + +def readline(f: io.RawIOBase): + # From: https://github.com/python/cpython/blob/d27e2f4d118e7a9909b6a3e5da06c5ff95806a85/Lib/_pyio.py#L525 + res = bytearray() + while True: + b = f.read(1) + if not b: + break + res += b + if res.endswith(b"\n"): + break + return bytes(res) diff --git a/venv/lib/python3.10/site-packages/evaluate/utils/gradio.py b/venv/lib/python3.10/site-packages/evaluate/utils/gradio.py new file mode 100644 index 0000000000000000000000000000000000000000..3b73d9c67e711caad66edaf0f66808fff296aabd --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/utils/gradio.py @@ -0,0 +1,131 @@ +import json +import os +import re +import sys +from pathlib import Path + +import numpy as np +from datasets import Value + +from .logging import get_logger + + +logger = get_logger(__name__) + +REGEX_YAML_BLOCK = re.compile(r"---[\n\r]+([\S\s]*?)[\n\r]+---[\n\r]") + + +def infer_gradio_input_types(feature_types): + """ + Maps metric feature types to input types for gradio Dataframes: + - float/int -> numbers + - string -> strings + - any other -> json + Note that json is not a native gradio type but will be treated as string that + is then parsed as a json. + """ + input_types = [] + for feature_type in feature_types: + input_type = "json" + if isinstance(feature_type, Value): + if feature_type.dtype.startswith("int") or feature_type.dtype.startswith("float"): + input_type = "number" + elif feature_type.dtype == "string": + input_type = "str" + input_types.append(input_type) + return input_types + + +def json_to_string_type(input_types): + """Maps json input type to str.""" + return ["str" if i == "json" else i for i in input_types] + + +def parse_readme(filepath): + """Parses a repositories README and removes""" + if not os.path.exists(filepath): + return "No README.md found." + with open(filepath, "r") as f: + text = f.read() + match = REGEX_YAML_BLOCK.search(text) + if match: + text = text[match.end() :] + return text + + +def parse_gradio_data(data, input_types): + """Parses data from gradio Dataframe for use in metric.""" + metric_inputs = {} + data.replace("", np.nan, inplace=True) + data.dropna(inplace=True) + for feature_name, input_type in zip(data, input_types): + if input_type == "json": + metric_inputs[feature_name] = [json.loads(d) for d in data[feature_name].to_list()] + elif input_type == "str": + metric_inputs[feature_name] = [d.strip('"') for d in data[feature_name].to_list()] + else: + metric_inputs[feature_name] = data[feature_name] + return metric_inputs + + +def parse_test_cases(test_cases, feature_names, input_types): + """ + Parses test cases to be used in gradio Dataframe. Note that an apostrophe is added + to strings to follow the format in json. + """ + if len(test_cases) == 0: + return None + examples = [] + for test_case in test_cases: + parsed_cases = [] + for feat, input_type in zip(feature_names, input_types): + if input_type == "json": + parsed_cases.append([str(element) for element in test_case[feat]]) + elif input_type == "str": + parsed_cases.append(['"' + element + '"' for element in test_case[feat]]) + else: + parsed_cases.append(test_case[feat]) + examples.append([list(i) for i in zip(*parsed_cases)]) + return examples + + +def launch_gradio_widget(metric): + """Launches `metric` widget with Gradio.""" + + try: + import gradio as gr + except ImportError as error: + logger.error("To create a metric widget with Gradio make sure gradio is installed.") + raise error + + local_path = Path(sys.path[0]) + # if there are several input types, use first as default. + if isinstance(metric.features, list): + (feature_names, feature_types) = zip(*metric.features[0].items()) + else: + (feature_names, feature_types) = zip(*metric.features.items()) + gradio_input_types = infer_gradio_input_types(feature_types) + + def compute(data): + return metric.compute(**parse_gradio_data(data, gradio_input_types)) + + iface = gr.Interface( + fn=compute, + inputs=gr.inputs.Dataframe( + headers=feature_names, + col_count=len(feature_names), + row_count=1, + datatype=json_to_string_type(gradio_input_types), + ), + outputs=gr.outputs.Textbox(label=metric.name), + description=( + metric.info.description + "\nIf this is a text-based metric, make sure to wrap you input in double quotes." + " Alternatively you can use a JSON-formatted list as input." + ), + title=f"Metric: {metric.name}", + article=parse_readme(local_path / "README.md"), + # TODO: load test cases and use them to populate examples + # examples=[parse_test_cases(test_cases, feature_names, gradio_input_types)] + ) + + iface.launch() diff --git a/venv/lib/python3.10/site-packages/evaluate/utils/logging.py b/venv/lib/python3.10/site-packages/evaluate/utils/logging.py new file mode 100644 index 0000000000000000000000000000000000000000..d29b7f4845f292f13dc40a4b3df123c704b8d807 --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/utils/logging.py @@ -0,0 +1,233 @@ +# Copyright 2020 Optuna, Hugging Face +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" Logging utilities. """ + +import logging +import os +from logging import CRITICAL # NOQA +from logging import DEBUG # NOQA +from logging import ERROR # NOQA +from logging import FATAL # NOQA +from logging import INFO # NOQA +from logging import NOTSET # NOQA +from logging import WARN # NOQA +from logging import WARNING # NOQA +from typing import Optional + +from tqdm import auto as tqdm_lib + + +log_levels = { + "debug": logging.DEBUG, + "info": logging.INFO, + "warning": logging.WARNING, + "error": logging.ERROR, + "critical": logging.CRITICAL, +} + +_default_log_level = logging.WARNING + + +def _get_default_logging_level(): + """ + If EVALUATE_VERBOSITY env var is set to one of the valid choices return that as the new default level. + If it is not - fall back to ``_default_log_level`` + """ + env_level_str = os.getenv("EVALUATE_VERBOSITY", None) + if env_level_str: + if env_level_str in log_levels: + return log_levels[env_level_str] + else: + logging.getLogger().warning( + f"Unknown option EVALUATE_VERBOSITY={env_level_str}, " + f"has to be one of: { ', '.join(log_levels.keys()) }" + ) + return _default_log_level + + +def _get_library_name() -> str: + return __name__.split(".")[0] + + +def _get_library_root_logger() -> logging.Logger: + return logging.getLogger(_get_library_name()) + + +def _configure_library_root_logger() -> None: + # Apply our default configuration to the library root logger. + library_root_logger = _get_library_root_logger() + library_root_logger.setLevel(_get_default_logging_level()) + + +def _reset_library_root_logger() -> None: + library_root_logger = _get_library_root_logger() + library_root_logger.setLevel(logging.NOTSET) + + +def get_logger(name: Optional[str] = None) -> logging.Logger: + """Return a logger with the specified name.""" + if name is None: + name = _get_library_name() + return logging.getLogger(name) + + +def get_verbosity() -> int: + """Return the current level for the Hugging Face Evaluate library's root logger. + Returns: + Logging level, e.g., `evaluate.logging.DEBUG` and `evaluate.logging.INFO`. + + + + Hugging Face Evaluate library has following logging levels: + - `evaluate.logging.CRITICAL`, `evaluate.logging.FATAL` + - `evaluate.logging.ERROR` + - `evaluate.logging.WARNING`, `evaluate.logging.WARN` + - `evaluate.logging.INFO` + - `evaluate.logging.DEBUG` + + + """ + return _get_library_root_logger().getEffectiveLevel() + + +def set_verbosity(verbosity: int) -> None: + """Set the level for the Hugging Face Evaluate library's root logger. + Args: + verbosity: + Logging level, e.g., `evaluate.logging.DEBUG` and `evaluate.logging.INFO`. + """ + _get_library_root_logger().setLevel(verbosity) + + +def set_verbosity_info(): + """Set the level for the Hugging Face Evaluate library's root logger to `INFO`. + + This will display most of the logging information and tqdm bars. + + Shortcut to `evaluate.logging.set_verbosity(evaluate.logging.INFO)`. + """ + return set_verbosity(INFO) + + +def set_verbosity_warning(): + """Set the level for the Hugging Face Evaluate library's root logger to `WARNING`. + + This will display only the warning and errors logging information and tqdm bars. + + Shortcut to `evaluate.logging.set_verbosity(evaluate.logging.WARNING)`. + """ + return set_verbosity(WARNING) + + +def set_verbosity_debug(): + """Set the level for the Hugging Face Evaluate library's root logger to `DEBUG`. + + This will display all the logging information and tqdm bars. + + Shortcut to `evaluate.logging.set_verbosity(evaluate.logging.DEBUG)`. + """ + return set_verbosity(DEBUG) + + +def set_verbosity_error(): + """Set the level for the Hugging Face Evaluate library's root logger to `ERROR`. + + This will display only the errors logging information and tqdm bars. + + Shortcut to `evaluate.logging.set_verbosity(evaluate.logging.ERROR)`. + """ + return set_verbosity(ERROR) + + +def disable_propagation() -> None: + """Disable propagation of the library log outputs. + Note that log propagation is disabled by default. + """ + _get_library_root_logger().propagate = False + + +def enable_propagation() -> None: + """Enable propagation of the library log outputs. + Please disable the Hugging Face Evaluate library's default handler to prevent double logging if the root logger has + been configured. + """ + _get_library_root_logger().propagate = True + + +# Configure the library root logger at the module level (singleton-like) +_configure_library_root_logger() + + +class EmptyTqdm: + """Dummy tqdm which doesn't do anything.""" + + def __init__(self, *args, **kwargs): # pylint: disable=unused-argument + self._iterator = args[0] if args else None + + def __iter__(self): + return iter(self._iterator) + + def __getattr__(self, _): + """Return empty function.""" + + def empty_fn(*args, **kwargs): # pylint: disable=unused-argument + return + + return empty_fn + + def __enter__(self): + return self + + def __exit__(self, type_, value, traceback): + return + + +_tqdm_active = True + + +class _tqdm_cls: + def __call__(self, *args, **kwargs): + if _tqdm_active: + return tqdm_lib.tqdm(*args, **kwargs) + else: + return EmptyTqdm(*args, **kwargs) + + def set_lock(self, *args, **kwargs): + self._lock = None + if _tqdm_active: + return tqdm_lib.tqdm.set_lock(*args, **kwargs) + + def get_lock(self): + if _tqdm_active: + return tqdm_lib.tqdm.get_lock() + + +tqdm = _tqdm_cls() + + +def is_progress_bar_enabled() -> bool: + """Return a boolean indicating whether tqdm progress bars are enabled.""" + return bool(_tqdm_active) + + +def enable_progress_bar(): + """Enable tqdm progress bar.""" + global _tqdm_active + _tqdm_active = True + + +def disable_progress_bar(): + """Enable tqdm progress bar.""" + global _tqdm_active + _tqdm_active = False diff --git a/venv/lib/python3.10/site-packages/evaluate/visualization.py b/venv/lib/python3.10/site-packages/evaluate/visualization.py new file mode 100644 index 0000000000000000000000000000000000000000..b8be8605805e4d11ef93a2911ab32afe934a78bb --- /dev/null +++ b/venv/lib/python3.10/site-packages/evaluate/visualization.py @@ -0,0 +1,230 @@ +import textwrap + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + + +class ComplexRadar: + """Create a complex radar chart with different scales for each variable + Args: + fig (`matplotlib.figure`) : A matplotlib figure object to add the axes on. + variables (`list`) : a list of variables to. plot + ranges (`list` of `tuples`): A list of ranges (min, max) for each variable + n_ring_levels (`int): Number of ordinate or ring levels to draw. + Default: 5. + show_scales (`bool`): Indicates if we the ranges for each variable are plotted. + Default: True. + format_cfg (`dict`): A dictionary with formatting configurations. + Default: None. + Returns: + `matplotlib.figure.Figure`: a radar plot. + """ + + def __init__(self, fig, variables, ranges, n_ring_levels=5, show_scales=True, format_cfg=None): + + self.format_cfg = format_cfg + + # Calculate angles and create for each variable an axes + # Consider here the trick with having the first axes element twice (len+1) + angles = np.arange(0, 360, 360.0 / len(variables)) + axes = [ + fig.add_axes([0.1, 0.1, 0.9, 0.9], polar=True, label="axes{}".format(i), **self.format_cfg["axes_args"]) + for i in range(len(variables) + 1) + ] + + # Ensure clockwise rotation (first variable at the top N) + for ax in axes: + ax.set_theta_zero_location("N") + ax.set_theta_direction(-1) + ax.set_axisbelow(True) + + # Writing the ranges on each axes + for i, ax in enumerate(axes): + + # Here we do the trick by repeating the first iteration + j = 0 if (i == 0 or i == 1) else i - 1 + ax.set_ylim(*ranges[j]) + # Set endpoint to True if you like to have values right before the last circle + grid = np.linspace(*ranges[j], num=n_ring_levels, endpoint=self.format_cfg["incl_endpoint"]) + gridlabel = ["{}".format(round(x, 2)) for x in grid] + gridlabel[0] = "" # remove values from the center + lines, labels = ax.set_rgrids( + grid, labels=gridlabel, angle=angles[j], **self.format_cfg["rgrid_tick_lbls_args"] + ) + + ax.set_ylim(*ranges[j]) + ax.spines["polar"].set_visible(False) + ax.grid(visible=False) + + if show_scales is False: + ax.set_yticklabels([]) + + # Set all axes except the first one unvisible + for ax in axes[1:]: + ax.patch.set_visible(False) + ax.xaxis.set_visible(False) + + # Setting the attributes + self.angle = np.deg2rad(np.r_[angles, angles[0]]) + self.ranges = ranges + self.ax = axes[0] + self.ax1 = axes[1] + self.plot_counter = 0 + + # Draw (inner) circles and lines + self.ax.yaxis.grid(**self.format_cfg["rad_ln_args"]) + # Draw outer circle + self.ax.spines["polar"].set(**self.format_cfg["outer_ring"]) + # Draw angle lines + self.ax.xaxis.grid(**self.format_cfg["angle_ln_args"]) + + # ax1 is the duplicate of axes[0] (self.ax) + # Remove everything from ax1 except the plot itself + self.ax1.axis("off") + self.ax1.set_zorder(9) + + # Create the outer labels for each variable + l, text = self.ax.set_thetagrids(angles, labels=variables) + + # Beautify them + labels = [t.get_text() for t in self.ax.get_xticklabels()] + labels = [ + "\n".join( + textwrap.wrap( + label, + self.format_cfg["theta_tick_lbls_txt_wrap"], + break_long_words=self.format_cfg["theta_tick_lbls_brk_lng_wrds"], + ) + ) + for label in labels + ] + self.ax.set_xticklabels(labels, **self.format_cfg["theta_tick_lbls"]) + + for t, a in zip(self.ax.get_xticklabels(), angles): + if a == 0: + t.set_ha("center") + elif a > 0 and a < 180: + t.set_ha("left") + elif a == 180: + t.set_ha("center") + else: + t.set_ha("right") + + self.ax.tick_params(axis="both", pad=self.format_cfg["theta_tick_lbls_pad"]) + + def _scale_data(self, data, ranges): + """Scales data[1:] to ranges[0]""" + for d, (y1, y2) in zip(data[1:], ranges[1:]): + assert (y1 <= d <= y2) or (y2 <= d <= y1) + x1, x2 = ranges[0] + d = data[0] + sdata = [d] + for d, (y1, y2) in zip(data[1:], ranges[1:]): + sdata.append((d - y1) / (y2 - y1) * (x2 - x1) + x1) + return sdata + + def plot(self, data, *args, **kwargs): + """Plots a line""" + sdata = self._scale_data(data, self.ranges) + self.ax1.plot(self.angle, np.r_[sdata, sdata[0]], *args, **kwargs) + self.plot_counter = self.plot_counter + 1 + + def use_legend(self, *args, **kwargs): + """Shows a legend""" + self.ax1.legend(*args, **kwargs) + + +def radar_plot(data, model_names, invert_range=[], config=None, fig=None): + """Create a complex radar chart with different scales for each variable + Source: https://towardsdatascience.com/how-to-create-and-visualize-complex-radar-charts-f7764d0f3652 + + Args: + data (`List[dict]`): the results (list of metric + value pairs). + E.g. data = [{"accuracy": 0.9, "precision":0.8},{"accuracy": 0.7, "precision":0.6}] + names (`List[dict]`): model names. + E.g. names = ["model1", "model 2", ...] + invert_range (`List[dict]`, optional): the metrics to invert (in cases when smaller is better, e.g. speed) + E.g. invert_range=["latency_in_seconds"] + config (`dict`, optional) : a specification of the formatting configurations, namely: + + - rad_ln_args (`dict`, default `{"visible": True}`): The visibility of the radial (circle) lines. + + - outer_ring (`dict`, default `{"visible": True}`): The visibility of the outer ring. + + - angle_ln_args (`dict`, default `{"visible": True}`): The visibility of the angle lines. + + - rgrid_tick_lbls_args (`dict`, default `{"fontsize": 12}`): The font size of the tick labels on the scales. + + - theta_tick_lbls (`dict`, default `{"fontsize": 12}`): The font size of the variable labels on the plot. + + - theta_tick_lbls_pad (`int`, default `3`): The padding of the variable labels on the plot. + + - theta_tick_lbls_brk_lng_wrds (`bool`, default `True` ): Whether long words in the label are broken up or not. + + - theta_tick_lbls_txt_wrap (`int`, default `15`): Text wrap for tick labels + + - incl_endpoint (`bool`, default `False`): Include value endpoints on calse + + - marker (`str`, default `"o"`): the shape of the marker used in the radar plot. + + - markersize (`int`, default `3`): the shape of the marker used in the radar plot. + + - legend_loc (`str`, default `"upper right"`): the location of the legend in the radar plot. Must be one of: 'upper left', 'upper right', 'lower left', 'lower right'. + + - bbox_to_anchor (`tuple`, default `(2, 1)`: anchor for the legend. + fig (`matplotlib.figure.Figure`, optional): figure used to plot the radar plot. + + Returns: + `matplotlib.figure.Figure` + """ + data = pd.DataFrame(data) + data.index = model_names + variables = data.keys() + if all(x in variables for x in invert_range) is False: + raise ValueError("All of the metrics in `invert_range` should be in the data provided.") + min_max_per_variable = data.describe().T[["min", "max"]] + min_max_per_variable["min"] = min_max_per_variable["min"] - 0.1 * ( + min_max_per_variable["max"] - min_max_per_variable["min"] + ) + min_max_per_variable["max"] = min_max_per_variable["max"] + 0.1 * ( + min_max_per_variable["max"] - min_max_per_variable["min"] + ) + + ranges = list(min_max_per_variable.itertuples(index=False, name=None)) + ranges = [ + (max_value, min_value) if var in invert_range else (min_value, max_value) + for var, (min_value, max_value) in zip(variables, ranges) + ] + format_cfg = { + "axes_args": {}, + "rad_ln_args": {"visible": True}, + "outer_ring": {"visible": True}, + "angle_ln_args": {"visible": True}, + "rgrid_tick_lbls_args": {"fontsize": 12}, + "theta_tick_lbls": {"fontsize": 12}, + "theta_tick_lbls_pad": 3, + "theta_tick_lbls_brk_lng_wrds": True, + "theta_tick_lbls_txt_wrap": 15, + "incl_endpoint": False, + "marker": "o", + "markersize": 3, + "legend_loc": "upper right", + "bbox_to_anchor": (2, 1), + } + if config is not None: + format_cfg.update(config) + if fig is None: + fig = plt.figure() + radar = ComplexRadar( + fig, + variables, + ranges, + n_ring_levels=3, + show_scales=True, + format_cfg=format_cfg, + ) + for g in zip(data.index): + radar.plot(data.loc[g].values, label=g, marker=format_cfg["marker"], markersize=format_cfg["markersize"]) + radar.use_legend(**{"loc": format_cfg["legend_loc"], "bbox_to_anchor": format_cfg["bbox_to_anchor"]}) + return fig diff --git a/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/METADATA b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..30d66530bc93801fc3385006ce179e72b42644df --- /dev/null +++ b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/METADATA @@ -0,0 +1,672 @@ +Metadata-Version: 2.4 +Name: frozenlist +Version: 1.8.0 +Summary: A list-like structure which implements collections.abc.MutableSequence +Home-page: https://github.com/aio-libs/frozenlist +Maintainer: aiohttp team +Maintainer-email: team@aiohttp.org +License: Apache-2.0 +Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org +Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org +Project-URL: CI: Github Actions, https://github.com/aio-libs/frozenlist/actions +Project-URL: Code of Conduct, https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/frozenlist +Project-URL: Docs: Changelog, https://github.com/aio-libs/frozenlist/blob/master/CHANGES.rst#changelog +Project-URL: Docs: RTD, https://frozenlist.aio-libs.org +Project-URL: GitHub: issues, https://github.com/aio-libs/frozenlist/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/frozenlist +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=3.9 +Description-Content-Type: text/x-rst +License-File: LICENSE +Dynamic: license-file + +frozenlist +========== + +.. image:: https://github.com/aio-libs/frozenlist/workflows/CI/badge.svg + :target: https://github.com/aio-libs/frozenlist/actions + :alt: GitHub status for master branch + +.. image:: https://codecov.io/gh/aio-libs/frozenlist/branch/master/graph/badge.svg?flag=pytest + :target: https://codecov.io/gh/aio-libs/frozenlist?flags[]=pytest + :alt: codecov.io status for master branch + +.. image:: https://img.shields.io/pypi/v/frozenlist.svg?logo=Python&logoColor=white + :target: https://pypi.org/project/frozenlist + :alt: frozenlist @ PyPI + +.. image:: https://readthedocs.org/projects/frozenlist/badge/?version=latest + :target: https://frozenlist.aio-libs.org + :alt: Read The Docs build status badge + +.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs:matrix.org + :alt: Matrix Room — #aio-libs:matrix.org + +.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs-space:matrix.org + :alt: Matrix Space — #aio-libs-space:matrix.org + +Introduction +------------ + +``frozenlist.FrozenList`` is a list-like structure which implements +``collections.abc.MutableSequence``. The list is *mutable* until ``FrozenList.freeze`` +is called, after which list modifications raise ``RuntimeError``: + + +>>> from frozenlist import FrozenList +>>> fl = FrozenList([17, 42]) +>>> fl.append('spam') +>>> fl.append('Vikings') +>>> fl + +>>> fl.freeze() +>>> fl + +>>> fl.frozen +True +>>> fl.append("Monty") +Traceback (most recent call last): + File "", line 1, in + File "frozenlist/_frozenlist.pyx", line 97, in frozenlist._frozenlist.FrozenList.append + self._check_frozen() + File "frozenlist/_frozenlist.pyx", line 19, in frozenlist._frozenlist.FrozenList._check_frozen + raise RuntimeError("Cannot modify frozen list.") +RuntimeError: Cannot modify frozen list. + + +FrozenList is also hashable, but only when frozen. Otherwise it also throws a RuntimeError: + + +>>> fl = FrozenList([17, 42, 'spam']) +>>> hash(fl) +Traceback (most recent call last): + File "", line 1, in + File "frozenlist/_frozenlist.pyx", line 111, in frozenlist._frozenlist.FrozenList.__hash__ + raise RuntimeError("Cannot hash unfrozen list.") +RuntimeError: Cannot hash unfrozen list. +>>> fl.freeze() +>>> hash(fl) +3713081631934410656 +>>> dictionary = {fl: 'Vikings'} # frozen fl can be a dict key +>>> dictionary +{: 'Vikings'} + + +Installation +------------ + +:: + + $ pip install frozenlist + + +Documentation +------------- + +https://frozenlist.aio-libs.org + +Communication channels +---------------------- + +We have a *Matrix Space* `#aio-libs-space:matrix.org +`_ which is +also accessible via Gitter. + +License +------- + +``frozenlist`` is offered under the Apache 2 license. + +Source code +----------- + +The project is hosted on GitHub_ + +Please file an issue in the `bug tracker +`_ if you have found a bug +or have some suggestions to improve the library. + +.. _GitHub: https://github.com/aio-libs/frozenlist + +========= +Changelog +========= + +.. + You should *NOT* be adding new change log entries to this file, this + file is managed by towncrier. You *may* edit previous change logs to + fix problems like typo corrections or such. + To add a new change log entry, please see + https://pip.pypa.io/en/latest/development/contributing/#news-entries + we named the news folder "changes". + + WARNING: Don't drop the next directive! + +.. towncrier release notes start + +v1.8.0 +====== + +*(2025-10-05)* + + +Contributor-facing changes +-------------------------- + +- The ``reusable-cibuildwheel.yml`` workflow has been refactored to + be more generic and ``ci-cd.yml`` now holds all the configuration + toggles -- by `@webknjaz `__. + + *Related issues and pull requests on GitHub:* + `#668 `__. + +- When building wheels, the source distribution is now passed directly + to the ``cibuildwheel`` invocation -- by `@webknjaz `__. + + *Related issues and pull requests on GitHub:* + `#669 `__. + +- Builds and tests have been added to + ``ci-cd.yml`` for arm64 Windows wheels -- by `@finnagin `__. + + *Related issues and pull requests on GitHub:* + `#677 `__. + +- Started building wheels for CPython 3.14 -- by `@kumaraditya303 `__. + + *Related issues and pull requests on GitHub:* + `#681 `__, `#682 `__. + +- Removed ``--config-settings=pure-python=false`` from ``requirements/dev.txt``. + Developers on CPython still get accelerated builds by default. To explicitly build + a pure Python wheel, use ``pip install -e . --config-settings=pure-python=true`` + -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#687 `__. + + +---- + + +v1.7.0 +====== + +*(2025-06-09)* + + +Features +-------- + +- Added deepcopy support to FrozenList -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#659 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Fixed an issue where ``frozenlist`` binary wheels would be built with debugging symbols and line tracing enabled, which significantly impacted performance. Line tracing is now disabled by default and can only be enabled explicitly -- by `@bdraco `__. + + This change ensures that production builds are optimized for performance. Developers who need line tracing for debugging purposes can still enable it by: + + 1. Setting the ``FROZENLIST_CYTHON_TRACING`` environment variable + 2. Using the ``--config-setting=with-cython-tracing=true`` option with pip + + *Related issues and pull requests on GitHub:* + `#660 `__. + +- Enabled ``PIP_CONSTRAINT`` environment variable in the build configuration to ensure the pinned Cython version from ``requirements/cython.txt`` is used during wheel builds. + + *Related issues and pull requests on GitHub:* + `#661 `__. + + +---- + + +v1.6.2 +====== + +*(2025-06-03)* + + +No significant changes. + + +---- + + +v1.6.1 +====== + +*(2025-06-02)* + + +Bug fixes +--------- + +- Correctly use ``cimport`` for including ``PyBool_FromLong`` -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#653 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Exclude ``_frozenlist.cpp`` from bdists/wheels -- by `@musicinmybrain `__. + + *Related issues and pull requests on GitHub:* + `#649 `__. + +- Updated to use Cython 3.1 universally across the build path -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#654 `__. + + +---- + + +v1.6.0 +====== + +*(2025-04-17)* + + +Bug fixes +--------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by `@ajsanchezsanz `__ and + `@markgreene74 `__. + + *Related commits on GitHub:* + `41591f2 `__. + + +Features +-------- + +- Implemented support for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#618 `__. + +- Started building armv7l wheels -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#642 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by `@ajsanchezsanz `__ and + `@markgreene74 `__. + + *Related commits on GitHub:* + `41591f2 `__. + +- Started building wheels for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#618 `__. + +- The packaging metadata switched to including an SPDX license identifier introduced in `PEP 639 `__ -- by `@cdce8p `__. + + *Related issues and pull requests on GitHub:* + `#639 `__. + + +Contributor-facing changes +-------------------------- + +- GitHub Actions CI/CD is now configured to manage caching pip-ecosystem + dependencies using `re-actors/cache-python-deps`_ -- an action by + `@webknjaz `__ that takes into account ABI stability and the exact + version of Python runtime. + + .. _`re-actors/cache-python-deps`: + https://github.com/marketplace/actions/cache-python-deps + + *Related issues and pull requests on GitHub:* + `#633 `__. + +- Organized dependencies into test and lint dependencies so that no + unnecessary ones are installed during CI runs -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#636 `__. + + +---- + + +1.5.0 (2024-10-22) +================== + +Bug fixes +--------- + +- An incorrect signature of the ``__class_getitem__`` class method + has been fixed, adding a missing ``class_item`` argument under + Python 3.8 and older. + + This change also improves the code coverage of this method that + was previously missing -- by `@webknjaz `__. + + + *Related issues and pull requests on GitHub:* + `#567 `__, `#571 `__. + + +Improved documentation +---------------------- + +- Rendered issue, PR, and commit links now lead to + ``frozenlist``'s repo instead of ``yarl``'s repo. + + + *Related issues and pull requests on GitHub:* + `#573 `__. + +- On the ``Contributing docs`` page, + a link to the ``Towncrier philosophy`` has been fixed. + + + *Related issues and pull requests on GitHub:* + `#574 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- A name of a temporary building directory now reflects + that it's related to ``frozenlist``, not ``yarl``. + + + *Related issues and pull requests on GitHub:* + `#573 `__. + +- Declared Python 3.13 supported officially in the distribution package metadata. + + + *Related issues and pull requests on GitHub:* + `#595 `__. + + +---- + + +1.4.1 (2023-12-15) +================== + +Packaging updates and notes for downstreams +------------------------------------------- + +- Declared Python 3.12 and PyPy 3.8-3.10 supported officially + in the distribution package metadata. + + + *Related issues and pull requests on GitHub:* + `#553 `__. + +- Replaced the packaging is replaced from an old-fashioned ``setup.py`` to an + in-tree `PEP 517 `__ build backend -- by `@webknjaz `__. + + Whenever the end-users or downstream packagers need to build ``frozenlist`` + from source (a Git checkout or an sdist), they may pass a ``config_settings`` + flag ``pure-python``. If this flag is not set, a C-extension will be built + and included into the distribution. + + Here is how this can be done with ``pip``: + + .. code-block:: console + + $ python3 -m pip install . --config-settings=pure-python= + + This will also work with ``-e | --editable``. + + The same can be achieved via ``pypa/build``: + + .. code-block:: console + + $ python3 -m build --config-setting=pure-python= + + Adding ``-w | --wheel`` can force ``pypa/build`` produce a wheel from source + directly, as opposed to building an ``sdist`` and then building from it. + + + *Related issues and pull requests on GitHub:* + `#560 `__. + + +Contributor-facing changes +-------------------------- + +- It is now possible to request line tracing in Cython builds using the + ``with-cython-tracing`` `PEP 517 `__ config setting + -- `@webknjaz `__. + + This can be used in CI and development environment to measure coverage + on Cython modules, but is not normally useful to the end-users or + downstream packagers. + + Here's a usage example: + + .. code-block:: console + + $ python3 -Im pip install . --config-settings=with-cython-tracing=true + + For editable installs, this setting is on by default. Otherwise, it's + off unless requested explicitly. + + The following produces C-files required for the Cython coverage + plugin to map the measurements back to the PYX-files: + + .. code-block:: console + + $ python -Im pip install -e . + + Alternatively, the ``FROZENLIST_CYTHON_TRACING=1`` environment variable + can be set to do the same as the `PEP 517 `__ config setting. + + + *Related issues and pull requests on GitHub:* + `#560 `__. + +- Coverage collection has been implemented for the Cython modules + -- by `@webknjaz `__. + + It will also be reported to Codecov from any non-release CI jobs. + + + *Related issues and pull requests on GitHub:* + `#561 `__. + +- A step-by-step ``Release Guide`` guide has + been added, describing how to release *frozenlist* -- by `@webknjaz `__. + + This is primarily targeting the maintainers. + + + *Related issues and pull requests on GitHub:* + `#563 `__. + +- Detailed ``Contributing Guidelines`` on + authoring the changelog fragments have been published in the + documentation -- by `@webknjaz `__. + + + *Related issues and pull requests on GitHub:* + `#564 `__. + + +---- + + +1.4.0 (2023-07-12) +================== + +The published source distribution package became buildable +under Python 3.12. + + +---- + + +Bugfixes +-------- + +- Removed an unused ``typing.Tuple`` import + `#411 `_ + + +Deprecations and Removals +------------------------- + +- Dropped Python 3.7 support. + `#413 `_ + + +Misc +---- + +- `#410 `_, `#433 `_ + + +---- + + +1.3.3 (2022-11-08) +================== + +- Fixed CI runs when creating a new release, where new towncrier versions + fail when the current version section is already present. + + +---- + + +1.3.2 (2022-11-08) +================== + +Misc +---- + +- Updated the CI runs to better check for test results and to avoid deprecated syntax. `#327 `_ + + +---- + + +1.3.1 (2022-08-02) +================== + +The published source distribution package became buildable +under Python 3.11. + + +---- + + +1.3.0 (2022-01-18) +================== + +Bugfixes +-------- + +- Do not install C sources with binary distributions. + `#250 `_ + + +Deprecations and Removals +------------------------- + +- Dropped Python 3.6 support + `#274 `_ + + +---- + + +1.2.0 (2021-10-16) +================== + +Features +-------- + +- ``FrozenList`` now supports being used as a generic type as per PEP 585, e.g. ``frozen_int_list: FrozenList[int]`` (requires Python 3.9 or newer). + `#172 `_ +- Added support for Python 3.10. + `#227 `_ +- Started shipping platform-specific wheels with the ``musl`` tag targeting typical Alpine Linux runtimes. + `#227 `_ +- Started shipping platform-specific arm64 wheels for Apple Silicon. + `#227 `_ + + +---- + + +1.1.1 (2020-11-14) +================== + +Bugfixes +-------- + +- Provide x86 Windows wheels. + `#169 `_ + + +---- + + +1.1.0 (2020-10-13) +================== + +Features +-------- + +- Add support for hashing of a frozen list. + `#136 `_ + +- Support Python 3.8 and 3.9. + +- Provide wheels for ``aarch64``, ``i686``, ``ppc64le``, ``s390x`` architectures on + Linux as well as ``x86_64``. + + +---- + + +1.0.0 (2019-11-09) +================== + +Deprecations and Removals +------------------------- + +- Dropped support for Python 3.5; only 3.6, 3.7 and 3.8 are supported going forward. + `#24 `_ diff --git a/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/RECORD b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..a94ae9c8033b61b8dd2095ce3cde8e300042a88a --- /dev/null +++ b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/RECORD @@ -0,0 +1,13 @@ +frozenlist-1.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +frozenlist-1.8.0.dist-info/METADATA,sha256=lGwi3J9-LHby1pjwCkMG2-Sagg4rPxoOIv5ShsvsNfE,20333 +frozenlist-1.8.0.dist-info/RECORD,, +frozenlist-1.8.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +frozenlist-1.8.0.dist-info/WHEEL,sha256=YEyko93MSH4KMaRG6eOab49RUU6QVcyYoGlmjzyr8Qc,186 +frozenlist-1.8.0.dist-info/licenses/LICENSE,sha256=b9UkPpLdf5jsacesN3co50kFcJ_1J6W_mNbQJjwE9bY,11332 +frozenlist-1.8.0.dist-info/top_level.txt,sha256=jivtxsPXA3nK3WBWW2LW5Mtu_GHt8UZA13NeCs2cKuA,11 +frozenlist/__init__.py,sha256=xAIE2u9ncAbjATGIPfno_OJfe8AQ-1h7z_uc73dYsEA,2108 +frozenlist/__init__.pyi,sha256=vMEoES1xGegPtVXoCi9XydEeHsyuIq-KdeXwP5PdsaA,1470 +frozenlist/__pycache__/__init__.cpython-310.pyc,, +frozenlist/_frozenlist.cpython-310-x86_64-linux-gnu.so,sha256=E3qmkXpP0M4_XLfrnwLG7tHsDVqeBvXdDCU4SdR_1rU,699088 +frozenlist/_frozenlist.pyx,sha256=t-aGjuEiVt_MZPBJ0RnraavVmPBK6arz3i48ZvXuYsU,3708 +frozenlist/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7 diff --git a/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/WHEEL b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..4b66e2eab6ce2489fc050e63c817e1edd30aa310 --- /dev/null +++ b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/WHEEL @@ -0,0 +1,7 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: false +Tag: cp310-cp310-manylinux_2_5_x86_64 +Tag: cp310-cp310-manylinux1_x86_64 +Tag: cp310-cp310-manylinux_2_28_x86_64 + diff --git a/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/licenses/LICENSE b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..7082a2d5b9047bfc09589f387053e24ea490bc54 --- /dev/null +++ b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/licenses/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013-2019 Nikolay Kim and Andrew Svetlov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/top_level.txt b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..52f13fc459edf8f3def6f792c432f0b64f313176 --- /dev/null +++ b/venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/top_level.txt @@ -0,0 +1 @@ +frozenlist diff --git a/venv/lib/python3.10/site-packages/networkx/__init__.py b/venv/lib/python3.10/site-packages/networkx/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5710827b61d5f5fa25dde778489c2d56677b9b37 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/__init__.py @@ -0,0 +1,53 @@ +""" +NetworkX +======== + +NetworkX is a Python package for the creation, manipulation, and study of the +structure, dynamics, and functions of complex networks. + +See https://networkx.org for complete documentation. +""" + +__version__ = "3.4.2" + + +# These are imported in order as listed +from networkx.lazy_imports import _lazy_import + +from networkx.exception import * + +from networkx import utils +from networkx.utils import _clear_cache, _dispatchable + +# load_and_call entry_points, set configs +config = utils.backends._set_configs_from_environment() +utils.config = utils.configs.config = config # type: ignore[attr-defined] + +from networkx import classes +from networkx.classes import filters +from networkx.classes import * + +from networkx import convert +from networkx.convert import * + +from networkx import convert_matrix +from networkx.convert_matrix import * + +from networkx import relabel +from networkx.relabel import * + +from networkx import generators +from networkx.generators import * + +from networkx import readwrite +from networkx.readwrite import * + +# Need to test with SciPy, when available +from networkx import algorithms +from networkx.algorithms import * + +from networkx import linalg +from networkx.linalg import * + +from networkx import drawing +from networkx.drawing import * diff --git a/venv/lib/python3.10/site-packages/networkx/conftest.py b/venv/lib/python3.10/site-packages/networkx/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..4a261c6d0d6f1a31c55349c2cee6776f9b2ba6c4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/conftest.py @@ -0,0 +1,284 @@ +""" +Testing +======= + +General guidelines for writing good tests: + +- doctests always assume ``import networkx as nx`` so don't add that +- prefer pytest fixtures over classes with setup methods. +- use the ``@pytest.mark.parametrize`` decorator +- use ``pytest.importorskip`` for numpy, scipy, pandas, and matplotlib b/c of PyPy. + and add the module to the relevant entries below. + +""" + +import os +import sys +import warnings +from importlib.metadata import entry_points + +import pytest + +import networkx + + +def pytest_addoption(parser): + parser.addoption( + "--runslow", action="store_true", default=False, help="run slow tests" + ) + parser.addoption( + "--backend", + action="store", + default=None, + help="Run tests with a backend by auto-converting nx graphs to backend graphs", + ) + parser.addoption( + "--fallback-to-nx", + action="store_true", + default=False, + help="Run nx function if a backend doesn't implement a dispatchable function" + " (use with --backend)", + ) + + +def pytest_configure(config): + config.addinivalue_line("markers", "slow: mark test as slow to run") + backend = config.getoption("--backend") + if backend is None: + backend = os.environ.get("NETWORKX_TEST_BACKEND") + # nx_loopback backend is only available when testing with a backend + loopback_ep = entry_points(name="nx_loopback", group="networkx.backends") + if not loopback_ep: + warnings.warn( + "\n\n WARNING: Mixed NetworkX configuration! \n\n" + " This environment has mixed configuration for networkx.\n" + " The test object nx_loopback is not configured correctly.\n" + " You should not be seeing this message.\n" + " Try `pip install -e .`, or change your PYTHONPATH\n" + " Make sure python finds the networkx repo you are testing\n\n" + ) + config.backend = backend + if backend: + # We will update `networkx.config.backend_priority` below in `*_modify_items` + # to allow tests to get set up with normal networkx graphs. + networkx.utils.backends.backends["nx_loopback"] = loopback_ep["nx_loopback"] + networkx.utils.backends.backend_info["nx_loopback"] = {} + networkx.config.backends = networkx.utils.Config( + nx_loopback=networkx.utils.Config(), + **networkx.config.backends, + ) + fallback_to_nx = config.getoption("--fallback-to-nx") + if not fallback_to_nx: + fallback_to_nx = os.environ.get("NETWORKX_FALLBACK_TO_NX") + networkx.config.fallback_to_nx = bool(fallback_to_nx) + + +def pytest_collection_modifyitems(config, items): + # Setting this to True here allows tests to be set up before dispatching + # any function call to a backend. + if config.backend: + # Allow pluggable backends to add markers to tests (such as skip or xfail) + # when running in auto-conversion test mode + backend_name = config.backend + if backend_name != "networkx": + networkx.utils.backends._dispatchable._is_testing = True + networkx.config.backend_priority.algos = [backend_name] + networkx.config.backend_priority.generators = [backend_name] + backend = networkx.utils.backends.backends[backend_name].load() + if hasattr(backend, "on_start_tests"): + getattr(backend, "on_start_tests")(items) + + if config.getoption("--runslow"): + # --runslow given in cli: do not skip slow tests + return + skip_slow = pytest.mark.skip(reason="need --runslow option to run") + for item in items: + if "slow" in item.keywords: + item.add_marker(skip_slow) + + +# TODO: The warnings below need to be dealt with, but for now we silence them. +@pytest.fixture(autouse=True) +def set_warnings(): + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message="\n\nsingle_target_shortest_path_length", + ) + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message="\n\nshortest_path", + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nThe `normalized`" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nall_triplets" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nrandom_triad" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="minimal_d_separator" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="d_separated" + ) + warnings.filterwarnings("ignore", category=DeprecationWarning, message="\n\nk_core") + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nk_shell" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nk_crust" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nk_corona" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\ntotal_spanning_tree_weight" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message=r"\n\nThe 'create=matrix'" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\n`compute_v_structures" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="Keyword argument 'link'" + ) + + +@pytest.fixture(autouse=True) +def add_nx(doctest_namespace): + doctest_namespace["nx"] = networkx + + +# What dependencies are installed? + +try: + import numpy + + has_numpy = True +except ImportError: + has_numpy = False + +try: + import scipy + + has_scipy = True +except ImportError: + has_scipy = False + +try: + import matplotlib + + has_matplotlib = True +except ImportError: + has_matplotlib = False + +try: + import pandas + + has_pandas = True +except ImportError: + has_pandas = False + +try: + import pygraphviz + + has_pygraphviz = True +except ImportError: + has_pygraphviz = False + +try: + import pydot + + has_pydot = True +except ImportError: + has_pydot = False + +try: + import sympy + + has_sympy = True +except ImportError: + has_sympy = False + + +# List of files that pytest should ignore + +collect_ignore = [] + +needs_numpy = [ + "algorithms/approximation/traveling_salesman.py", + "algorithms/centrality/current_flow_closeness.py", + "algorithms/centrality/laplacian.py", + "algorithms/node_classification.py", + "algorithms/non_randomness.py", + "algorithms/polynomials.py", + "algorithms/shortest_paths/dense.py", + "algorithms/tree/mst.py", + "drawing/nx_latex.py", + "generators/expanders.py", + "linalg/bethehessianmatrix.py", + "linalg/laplacianmatrix.py", + "utils/misc.py", +] +needs_scipy = [ + "algorithms/approximation/traveling_salesman.py", + "algorithms/assortativity/correlation.py", + "algorithms/assortativity/mixing.py", + "algorithms/assortativity/pairs.py", + "algorithms/bipartite/matrix.py", + "algorithms/bipartite/spectral.py", + "algorithms/centrality/current_flow_betweenness.py", + "algorithms/centrality/current_flow_betweenness_subset.py", + "algorithms/centrality/eigenvector.py", + "algorithms/centrality/katz.py", + "algorithms/centrality/laplacian.py", + "algorithms/centrality/second_order.py", + "algorithms/centrality/subgraph_alg.py", + "algorithms/communicability_alg.py", + "algorithms/community/divisive.py", + "algorithms/distance_measures.py", + "algorithms/link_analysis/hits_alg.py", + "algorithms/link_analysis/pagerank_alg.py", + "algorithms/node_classification.py", + "algorithms/similarity.py", + "algorithms/tree/mst.py", + "algorithms/walks.py", + "convert_matrix.py", + "drawing/layout.py", + "drawing/nx_pylab.py", + "generators/spectral_graph_forge.py", + "generators/expanders.py", + "linalg/algebraicconnectivity.py", + "linalg/attrmatrix.py", + "linalg/bethehessianmatrix.py", + "linalg/graphmatrix.py", + "linalg/laplacianmatrix.py", + "linalg/modularitymatrix.py", + "linalg/spectrum.py", + "utils/rcm.py", +] +needs_matplotlib = ["drawing/nx_pylab.py", "generators/classic.py"] +needs_pandas = ["convert_matrix.py"] +needs_pygraphviz = ["drawing/nx_agraph.py"] +needs_pydot = ["drawing/nx_pydot.py"] +needs_sympy = ["algorithms/polynomials.py"] + +if not has_numpy: + collect_ignore += needs_numpy +if not has_scipy: + collect_ignore += needs_scipy +if not has_matplotlib: + collect_ignore += needs_matplotlib +if not has_pandas: + collect_ignore += needs_pandas +if not has_pygraphviz: + collect_ignore += needs_pygraphviz +if not has_pydot: + collect_ignore += needs_pydot +if not has_sympy: + collect_ignore += needs_sympy diff --git a/venv/lib/python3.10/site-packages/networkx/convert.py b/venv/lib/python3.10/site-packages/networkx/convert.py new file mode 100644 index 0000000000000000000000000000000000000000..3f89c35d8067bf06768109f73caa3436d3719662 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/convert.py @@ -0,0 +1,502 @@ +"""Functions to convert NetworkX graphs to and from other formats. + +The preferred way of converting data to a NetworkX graph is through the +graph constructor. The constructor calls the to_networkx_graph() function +which attempts to guess the input type and convert it automatically. + +Examples +-------- +Create a graph with a single edge from a dictionary of dictionaries + +>>> d = {0: {1: 1}} # dict-of-dicts single edge (0,1) +>>> G = nx.Graph(d) + +See Also +-------- +nx_agraph, nx_pydot +""" + +import warnings +from collections.abc import Collection, Generator, Iterator + +import networkx as nx + +__all__ = [ + "to_networkx_graph", + "from_dict_of_dicts", + "to_dict_of_dicts", + "from_dict_of_lists", + "to_dict_of_lists", + "from_edgelist", + "to_edgelist", +] + + +def to_networkx_graph(data, create_using=None, multigraph_input=False): + """Make a NetworkX graph from a known data structure. + + The preferred way to call this is automatically + from the class constructor + + >>> d = {0: {1: {"weight": 1}}} # dict-of-dicts single edge (0,1) + >>> G = nx.Graph(d) + + instead of the equivalent + + >>> G = nx.from_dict_of_dicts(d) + + Parameters + ---------- + data : object to be converted + + Current known types are: + any NetworkX graph + dict-of-dicts + dict-of-lists + container (e.g. set, list, tuple) of edges + iterator (e.g. itertools.chain) that produces edges + generator of edges + Pandas DataFrame (row per edge) + 2D numpy array + scipy sparse array + pygraphviz agraph + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + multigraph_input : bool (default False) + If True and data is a dict_of_dicts, + try to create a multigraph assuming dict_of_dict_of_lists. + If data and create_using are both multigraphs then create + a multigraph from a multigraph. + + """ + # NX graph + if hasattr(data, "adj"): + try: + result = from_dict_of_dicts( + data.adj, + create_using=create_using, + multigraph_input=data.is_multigraph(), + ) + # data.graph should be dict-like + result.graph.update(data.graph) + # data.nodes should be dict-like + # result.add_node_from(data.nodes.items()) possible but + # for custom node_attr_dict_factory which may be hashable + # will be unexpected behavior + for n, dd in data.nodes.items(): + result._node[n].update(dd) + return result + except Exception as err: + raise nx.NetworkXError("Input is not a correct NetworkX graph.") from err + + # dict of dicts/lists + if isinstance(data, dict): + try: + return from_dict_of_dicts( + data, create_using=create_using, multigraph_input=multigraph_input + ) + except Exception as err1: + if multigraph_input is True: + raise nx.NetworkXError( + f"converting multigraph_input raised:\n{type(err1)}: {err1}" + ) + try: + return from_dict_of_lists(data, create_using=create_using) + except Exception as err2: + raise TypeError("Input is not known type.") from err2 + + # edgelists + if isinstance(data, list | tuple | nx.reportviews.EdgeViewABC | Iterator): + try: + return from_edgelist(data, create_using=create_using) + except: + pass + + # pygraphviz agraph + if hasattr(data, "is_strict"): + try: + return nx.nx_agraph.from_agraph(data, create_using=create_using) + except Exception as err: + raise nx.NetworkXError("Input is not a correct pygraphviz graph.") from err + + # Pandas DataFrame + try: + import pandas as pd + + if isinstance(data, pd.DataFrame): + if data.shape[0] == data.shape[1]: + try: + return nx.from_pandas_adjacency(data, create_using=create_using) + except Exception as err: + msg = "Input is not a correct Pandas DataFrame adjacency matrix." + raise nx.NetworkXError(msg) from err + else: + try: + return nx.from_pandas_edgelist( + data, edge_attr=True, create_using=create_using + ) + except Exception as err: + msg = "Input is not a correct Pandas DataFrame edge-list." + raise nx.NetworkXError(msg) from err + except ImportError: + pass + + # numpy array + try: + import numpy as np + + if isinstance(data, np.ndarray): + try: + return nx.from_numpy_array(data, create_using=create_using) + except Exception as err: + raise nx.NetworkXError( + f"Failed to interpret array as an adjacency matrix." + ) from err + except ImportError: + pass + + # scipy sparse array - any format + try: + import scipy + + if hasattr(data, "format"): + try: + return nx.from_scipy_sparse_array(data, create_using=create_using) + except Exception as err: + raise nx.NetworkXError( + "Input is not a correct scipy sparse array type." + ) from err + except ImportError: + pass + + # Note: most general check - should remain last in order of execution + # Includes containers (e.g. list, set, dict, etc.), generators, and + # iterators (e.g. itertools.chain) of edges + + if isinstance(data, Collection | Generator | Iterator): + try: + return from_edgelist(data, create_using=create_using) + except Exception as err: + raise nx.NetworkXError("Input is not a valid edge list") from err + + raise nx.NetworkXError("Input is not a known data type for conversion.") + + +@nx._dispatchable +def to_dict_of_lists(G, nodelist=None): + """Returns adjacency representation of graph as a dictionary of lists. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list + Use only nodes specified in nodelist + + Notes + ----- + Completely ignores edge data for MultiGraph and MultiDiGraph. + + """ + if nodelist is None: + nodelist = G + + d = {} + for n in nodelist: + d[n] = [nbr for nbr in G.neighbors(n) if nbr in nodelist] + return d + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_dict_of_lists(d, create_using=None): + """Returns a graph from a dictionary of lists. + + Parameters + ---------- + d : dictionary of lists + A dictionary of lists adjacency representation. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Examples + -------- + >>> dol = {0: [1]} # single edge (0,1) + >>> G = nx.from_dict_of_lists(dol) + + or + + >>> G = nx.Graph(dol) # use Graph constructor + + """ + G = nx.empty_graph(0, create_using) + G.add_nodes_from(d) + if G.is_multigraph() and not G.is_directed(): + # a dict_of_lists can't show multiedges. BUT for undirected graphs, + # each edge shows up twice in the dict_of_lists. + # So we need to treat this case separately. + seen = {} + for node, nbrlist in d.items(): + for nbr in nbrlist: + if nbr not in seen: + G.add_edge(node, nbr) + seen[node] = 1 # don't allow reverse edge to show up + else: + G.add_edges_from( + ((node, nbr) for node, nbrlist in d.items() for nbr in nbrlist) + ) + return G + + +def to_dict_of_dicts(G, nodelist=None, edge_data=None): + """Returns adjacency representation of graph as a dictionary of dictionaries. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list + Use only nodes specified in nodelist + + edge_data : scalar, optional + If provided, the value of the dictionary will be set to `edge_data` for + all edges. Usual values could be `1` or `True`. If `edge_data` is + `None` (the default), the edgedata in `G` is used, resulting in a + dict-of-dict-of-dicts. If `G` is a MultiGraph, the result will be a + dict-of-dict-of-dict-of-dicts. See Notes for an approach to customize + handling edge data. `edge_data` should *not* be a container. + + Returns + ------- + dod : dict + A nested dictionary representation of `G`. Note that the level of + nesting depends on the type of `G` and the value of `edge_data` + (see Examples). + + See Also + -------- + from_dict_of_dicts, to_dict_of_lists + + Notes + ----- + For a more custom approach to handling edge data, try:: + + dod = { + n: {nbr: custom(n, nbr, dd) for nbr, dd in nbrdict.items()} + for n, nbrdict in G.adj.items() + } + + where `custom` returns the desired edge data for each edge between `n` and + `nbr`, given existing edge data `dd`. + + Examples + -------- + >>> G = nx.path_graph(3) + >>> nx.to_dict_of_dicts(G) + {0: {1: {}}, 1: {0: {}, 2: {}}, 2: {1: {}}} + + Edge data is preserved by default (``edge_data=None``), resulting + in dict-of-dict-of-dicts where the innermost dictionary contains the + edge data: + + >>> G = nx.Graph() + >>> G.add_edges_from( + ... [ + ... (0, 1, {"weight": 1.0}), + ... (1, 2, {"weight": 2.0}), + ... (2, 0, {"weight": 1.0}), + ... ] + ... ) + >>> d = nx.to_dict_of_dicts(G) + >>> d # doctest: +SKIP + {0: {1: {'weight': 1.0}, 2: {'weight': 1.0}}, + 1: {0: {'weight': 1.0}, 2: {'weight': 2.0}}, + 2: {1: {'weight': 2.0}, 0: {'weight': 1.0}}} + >>> d[1][2]["weight"] + 2.0 + + If `edge_data` is not `None`, edge data in the original graph (if any) is + replaced: + + >>> d = nx.to_dict_of_dicts(G, edge_data=1) + >>> d + {0: {1: 1, 2: 1}, 1: {0: 1, 2: 1}, 2: {1: 1, 0: 1}} + >>> d[1][2] + 1 + + This also applies to MultiGraphs: edge data is preserved by default: + + >>> G = nx.MultiGraph() + >>> G.add_edge(0, 1, key="a", weight=1.0) + 'a' + >>> G.add_edge(0, 1, key="b", weight=5.0) + 'b' + >>> d = nx.to_dict_of_dicts(G) + >>> d # doctest: +SKIP + {0: {1: {'a': {'weight': 1.0}, 'b': {'weight': 5.0}}}, + 1: {0: {'a': {'weight': 1.0}, 'b': {'weight': 5.0}}}} + >>> d[0][1]["b"]["weight"] + 5.0 + + But multi edge data is lost if `edge_data` is not `None`: + + >>> d = nx.to_dict_of_dicts(G, edge_data=10) + >>> d + {0: {1: 10}, 1: {0: 10}} + """ + dod = {} + if nodelist is None: + if edge_data is None: + for u, nbrdict in G.adjacency(): + dod[u] = nbrdict.copy() + else: # edge_data is not None + for u, nbrdict in G.adjacency(): + dod[u] = dod.fromkeys(nbrdict, edge_data) + else: # nodelist is not None + if edge_data is None: + for u in nodelist: + dod[u] = {} + for v, data in ((v, data) for v, data in G[u].items() if v in nodelist): + dod[u][v] = data + else: # nodelist and edge_data are not None + for u in nodelist: + dod[u] = {} + for v in (v for v in G[u] if v in nodelist): + dod[u][v] = edge_data + return dod + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_dict_of_dicts(d, create_using=None, multigraph_input=False): + """Returns a graph from a dictionary of dictionaries. + + Parameters + ---------- + d : dictionary of dictionaries + A dictionary of dictionaries adjacency representation. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + multigraph_input : bool (default False) + When True, the dict `d` is assumed + to be a dict-of-dict-of-dict-of-dict structure keyed by + node to neighbor to edge keys to edge data for multi-edges. + Otherwise this routine assumes dict-of-dict-of-dict keyed by + node to neighbor to edge data. + + Examples + -------- + >>> dod = {0: {1: {"weight": 1}}} # single edge (0,1) + >>> G = nx.from_dict_of_dicts(dod) + + or + + >>> G = nx.Graph(dod) # use Graph constructor + + """ + G = nx.empty_graph(0, create_using) + G.add_nodes_from(d) + # does dict d represent a MultiGraph or MultiDiGraph? + if multigraph_input: + if G.is_directed(): + if G.is_multigraph(): + G.add_edges_from( + (u, v, key, data) + for u, nbrs in d.items() + for v, datadict in nbrs.items() + for key, data in datadict.items() + ) + else: + G.add_edges_from( + (u, v, data) + for u, nbrs in d.items() + for v, datadict in nbrs.items() + for key, data in datadict.items() + ) + else: # Undirected + if G.is_multigraph(): + seen = set() # don't add both directions of undirected graph + for u, nbrs in d.items(): + for v, datadict in nbrs.items(): + if (u, v) not in seen: + G.add_edges_from( + (u, v, key, data) for key, data in datadict.items() + ) + seen.add((v, u)) + else: + seen = set() # don't add both directions of undirected graph + for u, nbrs in d.items(): + for v, datadict in nbrs.items(): + if (u, v) not in seen: + G.add_edges_from( + (u, v, data) for key, data in datadict.items() + ) + seen.add((v, u)) + + else: # not a multigraph to multigraph transfer + if G.is_multigraph() and not G.is_directed(): + # d can have both representations u-v, v-u in dict. Only add one. + # We don't need this check for digraphs since we add both directions, + # or for Graph() since it is done implicitly (parallel edges not allowed) + seen = set() + for u, nbrs in d.items(): + for v, data in nbrs.items(): + if (u, v) not in seen: + G.add_edge(u, v, key=0) + G[u][v][0].update(data) + seen.add((v, u)) + else: + G.add_edges_from( + ((u, v, data) for u, nbrs in d.items() for v, data in nbrs.items()) + ) + return G + + +@nx._dispatchable(preserve_edge_attrs=True) +def to_edgelist(G, nodelist=None): + """Returns a list of edges in the graph. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list + Use only nodes specified in nodelist + + """ + if nodelist is None: + return G.edges(data=True) + return G.edges(nodelist, data=True) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_edgelist(edgelist, create_using=None): + """Returns a graph from a list of edges. + + Parameters + ---------- + edgelist : list or iterator + Edge tuples + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Examples + -------- + >>> edgelist = [(0, 1)] # single edge (0,1) + >>> G = nx.from_edgelist(edgelist) + + or + + >>> G = nx.Graph(edgelist) # use Graph constructor + + """ + G = nx.empty_graph(0, create_using) + G.add_edges_from(edgelist) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/convert_matrix.py b/venv/lib/python3.10/site-packages/networkx/convert_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..8992627cbac970e46ca7dce0557611a51cea2c26 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/convert_matrix.py @@ -0,0 +1,1317 @@ +"""Functions to convert NetworkX graphs to and from common data containers +like numpy arrays, scipy sparse arrays, and pandas DataFrames. + +The preferred way of converting data to a NetworkX graph is through the +graph constructor. The constructor calls the `~networkx.convert.to_networkx_graph` +function which attempts to guess the input type and convert it automatically. + +Examples +-------- +Create a 10 node random graph from a numpy array + +>>> import numpy as np +>>> rng = np.random.default_rng() +>>> a = rng.integers(low=0, high=2, size=(10, 10)) +>>> DG = nx.from_numpy_array(a, create_using=nx.DiGraph) + +or equivalently: + +>>> DG = nx.DiGraph(a) + +which calls `from_numpy_array` internally based on the type of ``a``. + +See Also +-------- +nx_agraph, nx_pydot +""" + +import itertools +from collections import defaultdict + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = [ + "from_pandas_adjacency", + "to_pandas_adjacency", + "from_pandas_edgelist", + "to_pandas_edgelist", + "from_scipy_sparse_array", + "to_scipy_sparse_array", + "from_numpy_array", + "to_numpy_array", +] + + +@nx._dispatchable(edge_attrs="weight") +def to_pandas_adjacency( + G, + nodelist=None, + dtype=None, + order=None, + multigraph_weight=sum, + weight="weight", + nonedge=0.0, +): + """Returns the graph adjacency matrix as a Pandas DataFrame. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the Pandas DataFrame. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is None, then the ordering is produced by G.nodes(). + + multigraph_weight : {sum, min, max}, optional + An operator that determines how weights in multigraphs are handled. + The default is to sum the weights of the multiple edges. + + weight : string or None, optional + The edge attribute that holds the numerical value used for + the edge weight. If an edge does not have that attribute, then the + value 1 is used instead. + + nonedge : float, optional + The matrix values corresponding to nonedges are typically set to zero. + However, this could be undesirable if there are matrix values + corresponding to actual edges that also have the value zero. If so, + one might prefer nonedges to have some other value, such as nan. + + Returns + ------- + df : Pandas DataFrame + Graph adjacency matrix + + Notes + ----- + For directed graphs, entry i,j corresponds to an edge from i to j. + + The DataFrame entries are assigned to the weight edge attribute. When + an edge does not have a weight attribute, the value of the entry is set to + the number 1. For multiple (parallel) edges, the values of the entries + are determined by the 'multigraph_weight' parameter. The default is to + sum the weight attributes for each of the parallel edges. + + When `nodelist` does not contain every node in `G`, the matrix is built + from the subgraph of `G` that is induced by the nodes in `nodelist`. + + The convention used for self-loop edges in graphs is to assign the + diagonal matrix entry value to the weight attribute of the edge + (or the number 1 if the edge has no weight attribute). If the + alternate convention of doubling the edge weight is desired the + resulting Pandas DataFrame can be modified as follows:: + + >>> import pandas as pd + >>> G = nx.Graph([(1, 1), (2, 2)]) + >>> df = nx.to_pandas_adjacency(G) + >>> df + 1 2 + 1 1.0 0.0 + 2 0.0 1.0 + >>> diag_idx = list(range(len(df))) + >>> df.iloc[diag_idx, diag_idx] *= 2 + >>> df + 1 2 + 1 2.0 0.0 + 2 0.0 2.0 + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_edge(0, 1, weight=2) + 0 + >>> G.add_edge(1, 0) + 0 + >>> G.add_edge(2, 2, weight=3) + 0 + >>> G.add_edge(2, 2) + 1 + >>> nx.to_pandas_adjacency(G, nodelist=[0, 1, 2], dtype=int) + 0 1 2 + 0 0 2 0 + 1 1 0 0 + 2 0 0 4 + + """ + import pandas as pd + + M = to_numpy_array( + G, + nodelist=nodelist, + dtype=dtype, + order=order, + multigraph_weight=multigraph_weight, + weight=weight, + nonedge=nonedge, + ) + if nodelist is None: + nodelist = list(G) + return pd.DataFrame(data=M, index=nodelist, columns=nodelist) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_pandas_adjacency(df, create_using=None): + r"""Returns a graph from Pandas DataFrame. + + The Pandas DataFrame is interpreted as an adjacency matrix for the graph. + + Parameters + ---------- + df : Pandas DataFrame + An adjacency matrix representation of a graph + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Notes + ----- + For directed graphs, explicitly mention create_using=nx.DiGraph, + and entry i,j of df corresponds to an edge from i to j. + + If `df` has a single data type for each entry it will be converted to an + appropriate Python data type. + + If you have node attributes stored in a separate dataframe `df_nodes`, + you can load those attributes to the graph `G` using the following code: + + ``` + df_nodes = pd.DataFrame({"node_id": [1, 2, 3], "attribute1": ["A", "B", "C"]}) + G.add_nodes_from((n, dict(d)) for n, d in df_nodes.iterrows()) + ``` + + If `df` has a user-specified compound data type the names + of the data fields will be used as attribute keys in the resulting + NetworkX graph. + + See Also + -------- + to_pandas_adjacency + + Examples + -------- + Simple integer weights on edges: + + >>> import pandas as pd + >>> pd.options.display.max_columns = 20 + >>> df = pd.DataFrame([[1, 1], [2, 1]]) + >>> df + 0 1 + 0 1 1 + 1 2 1 + >>> G = nx.from_pandas_adjacency(df) + >>> G.name = "Graph from pandas adjacency matrix" + >>> print(G) + Graph named 'Graph from pandas adjacency matrix' with 2 nodes and 3 edges + """ + + try: + df = df[df.index] + except Exception as err: + missing = list(set(df.index).difference(set(df.columns))) + msg = f"{missing} not in columns" + raise nx.NetworkXError("Columns must match Indices.", msg) from err + + A = df.values + G = from_numpy_array(A, create_using=create_using, nodelist=df.columns) + + return G + + +@nx._dispatchable(preserve_edge_attrs=True) +def to_pandas_edgelist( + G, + source="source", + target="target", + nodelist=None, + dtype=None, + edge_key=None, +): + """Returns the graph edge list as a Pandas DataFrame. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the Pandas DataFrame. + + source : str or int, optional + A valid column name (string or integer) for the source nodes (for the + directed case). + + target : str or int, optional + A valid column name (string or integer) for the target nodes (for the + directed case). + + nodelist : list, optional + Use only nodes specified in nodelist + + dtype : dtype, default None + Use to create the DataFrame. Data type to force. + Only a single dtype is allowed. If None, infer. + + edge_key : str or int or None, optional (default=None) + A valid column name (string or integer) for the edge keys (for the + multigraph case). If None, edge keys are not stored in the DataFrame. + + Returns + ------- + df : Pandas DataFrame + Graph edge list + + Examples + -------- + >>> G = nx.Graph( + ... [ + ... ("A", "B", {"cost": 1, "weight": 7}), + ... ("C", "E", {"cost": 9, "weight": 10}), + ... ] + ... ) + >>> df = nx.to_pandas_edgelist(G, nodelist=["A", "C"]) + >>> df[["source", "target", "cost", "weight"]] + source target cost weight + 0 A B 1 7 + 1 C E 9 10 + + >>> G = nx.MultiGraph([("A", "B", {"cost": 1}), ("A", "B", {"cost": 9})]) + >>> df = nx.to_pandas_edgelist(G, nodelist=["A", "C"], edge_key="ekey") + >>> df[["source", "target", "cost", "ekey"]] + source target cost ekey + 0 A B 1 0 + 1 A B 9 1 + + """ + import pandas as pd + + if nodelist is None: + edgelist = G.edges(data=True) + else: + edgelist = G.edges(nodelist, data=True) + source_nodes = [s for s, _, _ in edgelist] + target_nodes = [t for _, t, _ in edgelist] + + all_attrs = set().union(*(d.keys() for _, _, d in edgelist)) + if source in all_attrs: + raise nx.NetworkXError(f"Source name {source!r} is an edge attr name") + if target in all_attrs: + raise nx.NetworkXError(f"Target name {target!r} is an edge attr name") + + nan = float("nan") + edge_attr = {k: [d.get(k, nan) for _, _, d in edgelist] for k in all_attrs} + + if G.is_multigraph() and edge_key is not None: + if edge_key in all_attrs: + raise nx.NetworkXError(f"Edge key name {edge_key!r} is an edge attr name") + edge_keys = [k for _, _, k in G.edges(keys=True)] + edgelistdict = {source: source_nodes, target: target_nodes, edge_key: edge_keys} + else: + edgelistdict = {source: source_nodes, target: target_nodes} + + edgelistdict.update(edge_attr) + return pd.DataFrame(edgelistdict, dtype=dtype) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_pandas_edgelist( + df, + source="source", + target="target", + edge_attr=None, + create_using=None, + edge_key=None, +): + """Returns a graph from Pandas DataFrame containing an edge list. + + The Pandas DataFrame should contain at least two columns of node names and + zero or more columns of edge attributes. Each row will be processed as one + edge instance. + + Note: This function iterates over DataFrame.values, which is not + guaranteed to retain the data type across columns in the row. This is only + a problem if your row is entirely numeric and a mix of ints and floats. In + that case, all values will be returned as floats. See the + DataFrame.iterrows documentation for an example. + + Parameters + ---------- + df : Pandas DataFrame + An edge list representation of a graph + + source : str or int + A valid column name (string or integer) for the source nodes (for the + directed case). + + target : str or int + A valid column name (string or integer) for the target nodes (for the + directed case). + + edge_attr : str or int, iterable, True, or None + A valid column name (str or int) or iterable of column names that are + used to retrieve items and add them to the graph as edge attributes. + If `True`, all columns will be added except `source`, `target` and `edge_key`. + If `None`, no edge attributes are added to the graph. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + edge_key : str or None, optional (default=None) + A valid column name for the edge keys (for a MultiGraph). The values in + this column are used for the edge keys when adding edges if create_using + is a multigraph. + + If you have node attributes stored in a separate dataframe `df_nodes`, + you can load those attributes to the graph `G` using the following code: + + ``` + df_nodes = pd.DataFrame({"node_id": [1, 2, 3], "attribute1": ["A", "B", "C"]}) + G.add_nodes_from((n, dict(d)) for n, d in df_nodes.iterrows()) + ``` + + See Also + -------- + to_pandas_edgelist + + Examples + -------- + Simple integer weights on edges: + + >>> import pandas as pd + >>> pd.options.display.max_columns = 20 + >>> import numpy as np + >>> rng = np.random.RandomState(seed=5) + >>> ints = rng.randint(1, 11, size=(3, 2)) + >>> a = ["A", "B", "C"] + >>> b = ["D", "A", "E"] + >>> df = pd.DataFrame(ints, columns=["weight", "cost"]) + >>> df[0] = a + >>> df["b"] = b + >>> df[["weight", "cost", 0, "b"]] + weight cost 0 b + 0 4 7 A D + 1 7 1 B A + 2 10 9 C E + >>> G = nx.from_pandas_edgelist(df, 0, "b", ["weight", "cost"]) + >>> G["E"]["C"]["weight"] + 10 + >>> G["E"]["C"]["cost"] + 9 + >>> edges = pd.DataFrame( + ... { + ... "source": [0, 1, 2], + ... "target": [2, 2, 3], + ... "weight": [3, 4, 5], + ... "color": ["red", "blue", "blue"], + ... } + ... ) + >>> G = nx.from_pandas_edgelist(edges, edge_attr=True) + >>> G[0][2]["color"] + 'red' + + Build multigraph with custom keys: + + >>> edges = pd.DataFrame( + ... { + ... "source": [0, 1, 2, 0], + ... "target": [2, 2, 3, 2], + ... "my_edge_key": ["A", "B", "C", "D"], + ... "weight": [3, 4, 5, 6], + ... "color": ["red", "blue", "blue", "blue"], + ... } + ... ) + >>> G = nx.from_pandas_edgelist( + ... edges, + ... edge_key="my_edge_key", + ... edge_attr=["weight", "color"], + ... create_using=nx.MultiGraph(), + ... ) + >>> G[0][2] + AtlasView({'A': {'weight': 3, 'color': 'red'}, 'D': {'weight': 6, 'color': 'blue'}}) + + + """ + g = nx.empty_graph(0, create_using) + + if edge_attr is None: + if g.is_multigraph() and edge_key is not None: + for u, v, k in zip(df[source], df[target], df[edge_key]): + g.add_edge(u, v, k) + else: + g.add_edges_from(zip(df[source], df[target])) + return g + + reserved_columns = [source, target] + if g.is_multigraph() and edge_key is not None: + reserved_columns.append(edge_key) + + # Additional columns requested + attr_col_headings = [] + attribute_data = [] + if edge_attr is True: + attr_col_headings = [c for c in df.columns if c not in reserved_columns] + elif isinstance(edge_attr, list | tuple): + attr_col_headings = edge_attr + else: + attr_col_headings = [edge_attr] + if len(attr_col_headings) == 0: + raise nx.NetworkXError( + f"Invalid edge_attr argument: No columns found with name: {attr_col_headings}" + ) + + try: + attribute_data = zip(*[df[col] for col in attr_col_headings]) + except (KeyError, TypeError) as err: + msg = f"Invalid edge_attr argument: {edge_attr}" + raise nx.NetworkXError(msg) from err + + if g.is_multigraph(): + # => append the edge keys from the df to the bundled data + if edge_key is not None: + try: + multigraph_edge_keys = df[edge_key] + attribute_data = zip(attribute_data, multigraph_edge_keys) + except (KeyError, TypeError) as err: + msg = f"Invalid edge_key argument: {edge_key}" + raise nx.NetworkXError(msg) from err + + for s, t, attrs in zip(df[source], df[target], attribute_data): + if edge_key is not None: + attrs, multigraph_edge_key = attrs + key = g.add_edge(s, t, key=multigraph_edge_key) + else: + key = g.add_edge(s, t) + + g[s][t][key].update(zip(attr_col_headings, attrs)) + else: + for s, t, attrs in zip(df[source], df[target], attribute_data): + g.add_edge(s, t) + g[s][t].update(zip(attr_col_headings, attrs)) + + return g + + +@nx._dispatchable(edge_attrs="weight") +def to_scipy_sparse_array(G, nodelist=None, dtype=None, weight="weight", format="csr"): + """Returns the graph adjacency matrix as a SciPy sparse array. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the sparse array. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is None, then the ordering is produced by ``G.nodes()``. + + 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 attribute that holds the numerical value used for + the edge weight. If None then all edge weights are 1. + + format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'} + The format of the sparse array to be returned (default 'csr'). For + some algorithms different implementations of sparse arrays + can perform better. See [1]_ for details. + + Returns + ------- + A : SciPy sparse array + Graph adjacency matrix. + + Notes + ----- + For directed graphs, matrix entry ``i, j`` corresponds to an edge from + ``i`` to ``j``. + + The values of the adjacency matrix are populated using the edge attribute held in + parameter `weight`. When an edge does not have that attribute, the + value of the entry is 1. + + For multiple edges the matrix values are the sums of the edge weights. + + When `nodelist` does not contain every node in `G`, the adjacency matrix + is built from the subgraph of `G` that is induced by the nodes in + `nodelist`. + + The convention used for self-loop edges in graphs is to assign the + diagonal matrix entry value to the weight attribute of the edge + (or the number 1 if the edge has no weight attribute). If the + alternate convention of doubling the edge weight is desired the + resulting array can be modified as follows:: + + >>> G = nx.Graph([(1, 1)]) + >>> A = nx.to_scipy_sparse_array(G) + >>> A.toarray() + array([[1]]) + >>> A.setdiag(A.diagonal() * 2) + >>> A.toarray() + array([[2]]) + + Examples + -------- + + Basic usage: + + >>> G = nx.path_graph(4) + >>> A = nx.to_scipy_sparse_array(G) + >>> A # doctest: +SKIP + + + >>> A.toarray() + array([[0, 1, 0, 0], + [1, 0, 1, 0], + [0, 1, 0, 1], + [0, 0, 1, 0]]) + + .. note:: The `toarray` method is used in these examples to better visualize + the adjacancy matrix. For a dense representation of the adjaceny matrix, + use `to_numpy_array` instead. + + Directed graphs: + + >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3)]) + >>> nx.to_scipy_sparse_array(G).toarray() + array([[0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [0, 0, 0, 0]]) + + >>> H = G.reverse() + >>> H.edges + OutEdgeView([(1, 0), (2, 1), (3, 2)]) + >>> nx.to_scipy_sparse_array(H).toarray() + array([[0, 0, 0, 0], + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0]]) + + By default, the order of the rows/columns of the adjacency matrix is determined + by the ordering of the nodes in `G`: + + >>> G = nx.Graph() + >>> G.add_nodes_from([3, 5, 0, 1]) + >>> G.add_edges_from([(1, 3), (1, 5)]) + >>> nx.to_scipy_sparse_array(G).toarray() + array([[0, 0, 0, 1], + [0, 0, 0, 1], + [0, 0, 0, 0], + [1, 1, 0, 0]]) + + The ordering of the rows can be changed with `nodelist`: + + >>> ordered = [0, 1, 3, 5] + >>> nx.to_scipy_sparse_array(G, nodelist=ordered).toarray() + array([[0, 0, 0, 0], + [0, 0, 1, 1], + [0, 1, 0, 0], + [0, 1, 0, 0]]) + + If `nodelist` contains a subset of the nodes in `G`, the adjacency matrix + for the node-induced subgraph is produced: + + >>> nx.to_scipy_sparse_array(G, nodelist=[1, 3, 5]).toarray() + array([[0, 1, 1], + [1, 0, 0], + [1, 0, 0]]) + + The values of the adjacency matrix are drawn from the edge attribute + specified by the `weight` parameter: + + >>> G = nx.path_graph(4) + >>> nx.set_edge_attributes( + ... G, values={(0, 1): 1, (1, 2): 10, (2, 3): 2}, name="weight" + ... ) + >>> nx.set_edge_attributes( + ... G, values={(0, 1): 50, (1, 2): 35, (2, 3): 10}, name="capacity" + ... ) + >>> nx.to_scipy_sparse_array(G).toarray() # Default weight="weight" + array([[ 0, 1, 0, 0], + [ 1, 0, 10, 0], + [ 0, 10, 0, 2], + [ 0, 0, 2, 0]]) + >>> nx.to_scipy_sparse_array(G, weight="capacity").toarray() + array([[ 0, 50, 0, 0], + [50, 0, 35, 0], + [ 0, 35, 0, 10], + [ 0, 0, 10, 0]]) + + Any edges that don't have a `weight` attribute default to 1: + + >>> G[1][2].pop("capacity") + 35 + >>> nx.to_scipy_sparse_array(G, weight="capacity").toarray() + array([[ 0, 50, 0, 0], + [50, 0, 1, 0], + [ 0, 1, 0, 10], + [ 0, 0, 10, 0]]) + + When `G` is a multigraph, the values in the adjacency matrix are given by + the sum of the `weight` edge attribute over each edge key: + + >>> G = nx.MultiDiGraph([(0, 1), (0, 1), (0, 1), (2, 0)]) + >>> nx.to_scipy_sparse_array(G).toarray() + array([[0, 3, 0], + [0, 0, 0], + [1, 0, 0]]) + + References + ---------- + .. [1] Scipy Dev. References, "Sparse Arrays", + https://docs.scipy.org/doc/scipy/reference/sparse.html + """ + import scipy as sp + + if len(G) == 0: + raise nx.NetworkXError("Graph has no nodes or edges") + + if nodelist is None: + nodelist = list(G) + nlen = len(G) + else: + nlen = len(nodelist) + if nlen == 0: + raise nx.NetworkXError("nodelist has no nodes") + nodeset = set(G.nbunch_iter(nodelist)) + if nlen != len(nodeset): + for n in nodelist: + if n not in G: + raise nx.NetworkXError(f"Node {n} in nodelist is not in G") + raise nx.NetworkXError("nodelist contains duplicates.") + if nlen < len(G): + G = G.subgraph(nodelist) + + index = dict(zip(nodelist, range(nlen))) + coefficients = zip( + *((index[u], index[v], wt) for u, v, wt in G.edges(data=weight, default=1)) + ) + try: + row, col, data = coefficients + except ValueError: + # there is no edge in the subgraph + row, col, data = [], [], [] + + if G.is_directed(): + A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, nlen), dtype=dtype) + else: + # symmetrize matrix + d = data + data + r = row + col + c = col + row + # selfloop entries get double counted when symmetrizing + # so we subtract the data on the diagonal + selfloops = list(nx.selfloop_edges(G, data=weight, default=1)) + if selfloops: + diag_index, diag_data = zip(*((index[u], -wt) for u, v, wt in selfloops)) + d += diag_data + r += diag_index + c += diag_index + A = sp.sparse.coo_array((d, (r, c)), shape=(nlen, nlen), dtype=dtype) + try: + return A.asformat(format) + except ValueError as err: + raise nx.NetworkXError(f"Unknown sparse matrix format: {format}") from err + + +def _csr_gen_triples(A): + """Converts a SciPy sparse array in **Compressed Sparse Row** format to + an iterable of weighted edge triples. + + """ + nrows = A.shape[0] + indptr, dst_indices, data = A.indptr, A.indices, A.data + import numpy as np + + src_indices = np.repeat(np.arange(nrows), np.diff(indptr)) + return zip(src_indices.tolist(), dst_indices.tolist(), A.data.tolist()) + + +def _csc_gen_triples(A): + """Converts a SciPy sparse array in **Compressed Sparse Column** format to + an iterable of weighted edge triples. + + """ + ncols = A.shape[1] + indptr, src_indices, data = A.indptr, A.indices, A.data + import numpy as np + + dst_indices = np.repeat(np.arange(ncols), np.diff(indptr)) + return zip(src_indices.tolist(), dst_indices.tolist(), A.data.tolist()) + + +def _coo_gen_triples(A): + """Converts a SciPy sparse array in **Coordinate** format to an iterable + of weighted edge triples. + + """ + return zip(A.row.tolist(), A.col.tolist(), A.data.tolist()) + + +def _dok_gen_triples(A): + """Converts a SciPy sparse array in **Dictionary of Keys** format to an + iterable of weighted edge triples. + + """ + for (r, c), v in A.items(): + # Use `v.item()` to convert a NumPy scalar to the appropriate Python scalar + yield int(r), int(c), v.item() + + +def _generate_weighted_edges(A): + """Returns an iterable over (u, v, w) triples, where u and v are adjacent + vertices and w is the weight of the edge joining u and v. + + `A` is a SciPy sparse array (in any format). + + """ + if A.format == "csr": + return _csr_gen_triples(A) + if A.format == "csc": + return _csc_gen_triples(A) + if A.format == "dok": + return _dok_gen_triples(A) + # If A is in any other format (including COO), convert it to COO format. + return _coo_gen_triples(A.tocoo()) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_scipy_sparse_array( + A, parallel_edges=False, create_using=None, edge_attribute="weight" +): + """Creates a new graph from an adjacency matrix given as a SciPy sparse + array. + + Parameters + ---------- + A: scipy.sparse array + An adjacency matrix representation of a graph + + parallel_edges : Boolean + If this is True, `create_using` is a multigraph, and `A` is an + integer matrix, then entry *(i, j)* in the matrix is interpreted as the + number of parallel edges joining vertices *i* and *j* in the graph. + If it is False, then the entries in the matrix are interpreted as + the weight of a single edge joining the vertices. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + 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)). + + Notes + ----- + For directed graphs, explicitly mention create_using=nx.DiGraph, + and entry i,j of A corresponds to an edge from i to j. + + If `create_using` is :class:`networkx.MultiGraph` or + :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the + entries of `A` are of type :class:`int`, then this function returns a + multigraph (constructed from `create_using`) with parallel edges. + In this case, `edge_attribute` will be ignored. + + If `create_using` indicates an undirected multigraph, then only the edges + indicated by the upper triangle of the matrix `A` will be added to the + graph. + + Examples + -------- + >>> import scipy as sp + >>> A = sp.sparse.eye(2, 2, 1) + >>> G = nx.from_scipy_sparse_array(A) + + If `create_using` indicates a multigraph and the matrix has only integer + entries and `parallel_edges` is False, then the entries will be treated + as weights for edges joining the nodes (without creating parallel edges): + + >>> A = sp.sparse.csr_array([[1, 1], [1, 2]]) + >>> G = nx.from_scipy_sparse_array(A, create_using=nx.MultiGraph) + >>> G[1][1] + AtlasView({0: {'weight': 2}}) + + If `create_using` indicates a multigraph and the matrix has only integer + entries and `parallel_edges` is True, then the entries will be treated + as the number of parallel edges joining those two vertices: + + >>> A = sp.sparse.csr_array([[1, 1], [1, 2]]) + >>> G = nx.from_scipy_sparse_array( + ... A, parallel_edges=True, create_using=nx.MultiGraph + ... ) + >>> G[1][1] + AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) + + """ + G = nx.empty_graph(0, create_using) + n, m = A.shape + if n != m: + raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}") + # Make sure we get even the isolated nodes of the graph. + G.add_nodes_from(range(n)) + # Create an iterable over (u, v, w) triples and for each triple, add an + # edge from u to v with weight w. + triples = _generate_weighted_edges(A) + # If the entries in the adjacency matrix are integers, the graph is a + # multigraph, and parallel_edges is True, 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() and parallel_edges: + chain = itertools.chain.from_iterable + # The following line is equivalent to: + # + # for (u, v) in edges: + # for d in range(A[u, v]): + # G.add_edge(u, v, weight=1) + # + triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples) + # If we are creating an undirected multigraph, only add the edges from the + # upper triangle of the matrix. Otherwise, add all the edges. This relies + # on the fact that the vertices created in the + # `_generated_weighted_edges()` function are actually the row/column + # indices for the matrix `A`. + # + # Without this check, we run into a problem where each edge is added twice + # when `G.add_weighted_edges_from()` is invoked below. + if G.is_multigraph() and not G.is_directed(): + triples = ((u, v, d) for u, v, d in triples if u <= v) + G.add_weighted_edges_from(triples, weight=edge_attribute) + return G + + +@nx._dispatchable(edge_attrs="weight") # edge attrs may also be obtained from `dtype` +def to_numpy_array( + G, + nodelist=None, + dtype=None, + order=None, + multigraph_weight=sum, + weight="weight", + nonedge=0.0, +): + """Returns the graph adjacency matrix as a NumPy array. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the NumPy array. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is ``None``, then the ordering is produced by ``G.nodes()``. + + dtype : NumPy data type, optional + A NumPy data type used to initialize the array. If None, then the NumPy + default is used. The dtype can be structured if `weight=None`, in which + case the dtype field names are used to look up edge attributes. The + result is a structured array where each named field in the dtype + corresponds to the adjacency for that edge attribute. See examples for + details. + + order : {'C', 'F'}, optional + Whether to store multidimensional data in C- or Fortran-contiguous + (row- or column-wise) order in memory. If None, then the NumPy default + is used. + + multigraph_weight : callable, optional + An function that determines how weights in multigraphs are handled. + The function should accept a sequence of weights and return a single + value. The default is to sum the weights of the multiple edges. + + weight : string or None optional (default = 'weight') + The edge attribute that holds the numerical value used for + the edge weight. If an edge does not have that attribute, then the + value 1 is used instead. `weight` must be ``None`` if a structured + dtype is used. + + nonedge : array_like (default = 0.0) + The value used to represent non-edges in the adjacency matrix. + The array values corresponding to nonedges are typically set to zero. + However, this could be undesirable if there are array values + corresponding to actual edges that also have the value zero. If so, + one might prefer nonedges to have some other value, such as ``nan``. + + Returns + ------- + A : NumPy ndarray + Graph adjacency matrix + + Raises + ------ + NetworkXError + If `dtype` is a structured dtype and `G` is a multigraph + ValueError + If `dtype` is a structured dtype and `weight` is not `None` + + See Also + -------- + from_numpy_array + + Notes + ----- + For directed graphs, entry ``i, j`` corresponds to an edge from ``i`` to ``j``. + + Entries in the adjacency matrix are given by the `weight` edge attribute. + When an edge does not have a weight attribute, the value of the entry is + set to the number 1. For multiple (parallel) edges, the values of the + entries are determined by the `multigraph_weight` parameter. The default is + to sum the weight attributes for each of the parallel edges. + + When `nodelist` does not contain every node in `G`, the adjacency matrix is + built from the subgraph of `G` that is induced by the nodes in `nodelist`. + + The convention used for self-loop edges in graphs is to assign the + diagonal array entry value to the weight attribute of the edge + (or the number 1 if the edge has no weight attribute). If the + alternate convention of doubling the edge weight is desired the + resulting NumPy array can be modified as follows: + + >>> import numpy as np + >>> G = nx.Graph([(1, 1)]) + >>> A = nx.to_numpy_array(G) + >>> A + array([[1.]]) + >>> A[np.diag_indices_from(A)] *= 2 + >>> A + array([[2.]]) + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_edge(0, 1, weight=2) + 0 + >>> G.add_edge(1, 0) + 0 + >>> G.add_edge(2, 2, weight=3) + 0 + >>> G.add_edge(2, 2) + 1 + >>> nx.to_numpy_array(G, nodelist=[0, 1, 2]) + array([[0., 2., 0.], + [1., 0., 0.], + [0., 0., 4.]]) + + When `nodelist` argument is used, nodes of `G` which do not appear in the `nodelist` + and their edges are not included in the adjacency matrix. Here is an example: + + >>> G = nx.Graph() + >>> G.add_edge(3, 1) + >>> G.add_edge(2, 0) + >>> G.add_edge(2, 1) + >>> G.add_edge(3, 0) + >>> nx.to_numpy_array(G, nodelist=[1, 2, 3]) + array([[0., 1., 1.], + [1., 0., 0.], + [1., 0., 0.]]) + + This function can also be used to create adjacency matrices for multiple + edge attributes with structured dtypes: + + >>> G = nx.Graph() + >>> G.add_edge(0, 1, weight=10) + >>> G.add_edge(1, 2, cost=5) + >>> G.add_edge(2, 3, weight=3, cost=-4.0) + >>> dtype = np.dtype([("weight", int), ("cost", float)]) + >>> A = nx.to_numpy_array(G, dtype=dtype, weight=None) + >>> A["weight"] + array([[ 0, 10, 0, 0], + [10, 0, 1, 0], + [ 0, 1, 0, 3], + [ 0, 0, 3, 0]]) + >>> A["cost"] + array([[ 0., 1., 0., 0.], + [ 1., 0., 5., 0.], + [ 0., 5., 0., -4.], + [ 0., 0., -4., 0.]]) + + As stated above, the argument "nonedge" is useful especially when there are + actually edges with weight 0 in the graph. Setting a nonedge value different than 0, + makes it much clearer to differentiate such 0-weighted edges and actual nonedge values. + + >>> G = nx.Graph() + >>> G.add_edge(3, 1, weight=2) + >>> G.add_edge(2, 0, weight=0) + >>> G.add_edge(2, 1, weight=0) + >>> G.add_edge(3, 0, weight=1) + >>> nx.to_numpy_array(G, nonedge=-1.0) + array([[-1., 2., -1., 1.], + [ 2., -1., 0., -1.], + [-1., 0., -1., 0.], + [ 1., -1., 0., -1.]]) + """ + import numpy as np + + if nodelist is None: + nodelist = list(G) + nlen = len(nodelist) + + # Input validation + nodeset = set(nodelist) + if nodeset - set(G): + raise nx.NetworkXError(f"Nodes {nodeset - set(G)} in nodelist is not in G") + if len(nodeset) < nlen: + raise nx.NetworkXError("nodelist contains duplicates.") + + A = np.full((nlen, nlen), fill_value=nonedge, dtype=dtype, order=order) + + # Corner cases: empty nodelist or graph without any edges + if nlen == 0 or G.number_of_edges() == 0: + return A + + # If dtype is structured and weight is None, use dtype field names as + # edge attributes + edge_attrs = None # Only single edge attribute by default + if A.dtype.names: + if weight is None: + edge_attrs = dtype.names + else: + raise ValueError( + "Specifying `weight` not supported for structured dtypes\n." + "To create adjacency matrices from structured dtypes, use `weight=None`." + ) + + # Map nodes to row/col in matrix + idx = dict(zip(nodelist, range(nlen))) + if len(nodelist) < len(G): + G = G.subgraph(nodelist).copy() + + # Collect all edge weights and reduce with `multigraph_weights` + if G.is_multigraph(): + if edge_attrs: + raise nx.NetworkXError( + "Structured arrays are not supported for MultiGraphs" + ) + d = defaultdict(list) + for u, v, wt in G.edges(data=weight, default=1.0): + d[(idx[u], idx[v])].append(wt) + i, j = np.array(list(d.keys())).T # indices + wts = [multigraph_weight(ws) for ws in d.values()] # reduced weights + else: + i, j, wts = [], [], [] + + # Special branch: multi-attr adjacency from structured dtypes + if edge_attrs: + # Extract edges with all data + for u, v, data in G.edges(data=True): + i.append(idx[u]) + j.append(idx[v]) + wts.append(data) + # Map each attribute to the appropriate named field in the + # structured dtype + for attr in edge_attrs: + attr_data = [wt.get(attr, 1.0) for wt in wts] + A[attr][i, j] = attr_data + if not G.is_directed(): + A[attr][j, i] = attr_data + return A + + for u, v, wt in G.edges(data=weight, default=1.0): + i.append(idx[u]) + j.append(idx[v]) + wts.append(wt) + + # Set array values with advanced indexing + A[i, j] = wts + if not G.is_directed(): + A[j, i] = wts + + return A + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_numpy_array( + A, parallel_edges=False, create_using=None, edge_attr="weight", *, nodelist=None +): + """Returns a graph from a 2D NumPy array. + + The 2D NumPy array is interpreted as an adjacency matrix for the graph. + + Parameters + ---------- + A : a 2D numpy.ndarray + An adjacency matrix representation of a graph + + parallel_edges : Boolean + If this is True, `create_using` is a multigraph, and `A` is an + integer array, then entry *(i, j)* in the array is interpreted as the + number of parallel edges joining vertices *i* and *j* in the graph. + If it is False, then the entries in the array are interpreted as + the weight of a single edge joining the vertices. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + edge_attr : String, optional (default="weight") + The attribute to which the array values are assigned on each edge. If + it is None, edge attributes will not be assigned. + + nodelist : sequence of nodes, optional + A sequence of objects to use as the nodes in the graph. If provided, the + list of nodes must be the same length as the dimensions of `A`. The + default is `None`, in which case the nodes are drawn from ``range(n)``. + + Notes + ----- + For directed graphs, explicitly mention create_using=nx.DiGraph, + and entry i,j of A corresponds to an edge from i to j. + + If `create_using` is :class:`networkx.MultiGraph` or + :class:`networkx.MultiDiGraph`, `parallel_edges` is True, 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. + + If `create_using` indicates an undirected multigraph, then only the edges + indicated by the upper triangle of the array `A` will be added to the + graph. + + If `edge_attr` is Falsy (False or None), edge attributes will not be + assigned, and the array data will be treated like a binary mask of + edge presence or absence. Otherwise, the attributes will be assigned + as follows: + + If the NumPy array has a single data type for each array entry it + will be converted to an appropriate Python data type. + + If the NumPy array has a user-specified compound data type the names + of the data fields will be used as attribute keys in the resulting + NetworkX graph. + + See Also + -------- + to_numpy_array + + Examples + -------- + Simple integer weights on edges: + + >>> import numpy as np + >>> A = np.array([[1, 1], [2, 1]]) + >>> G = nx.from_numpy_array(A) + >>> G.edges(data=True) + EdgeDataView([(0, 0, {'weight': 1}), (0, 1, {'weight': 2}), (1, 1, {'weight': 1})]) + + If `create_using` indicates a multigraph and the array has only integer + entries and `parallel_edges` is False, then the entries will be treated + as weights for edges joining the nodes (without creating parallel edges): + + >>> A = np.array([[1, 1], [1, 2]]) + >>> G = nx.from_numpy_array(A, create_using=nx.MultiGraph) + >>> G[1][1] + AtlasView({0: {'weight': 2}}) + + If `create_using` indicates a multigraph and the array has only integer + entries and `parallel_edges` is True, then the entries will be treated + as the number of parallel edges joining those two vertices: + + >>> A = np.array([[1, 1], [1, 2]]) + >>> temp = nx.MultiGraph() + >>> G = nx.from_numpy_array(A, parallel_edges=True, create_using=temp) + >>> G[1][1] + AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) + + User defined compound data type on edges: + + >>> dt = [("weight", float), ("cost", int)] + >>> A = np.array([[(1.0, 2)]], dtype=dt) + >>> G = nx.from_numpy_array(A) + >>> G.edges() + EdgeView([(0, 0)]) + >>> G[0][0]["cost"] + 2 + >>> G[0][0]["weight"] + 1.0 + + """ + kind_to_python_type = { + "f": float, + "i": int, + "u": int, + "b": bool, + "c": complex, + "S": str, + "U": str, + "V": "void", + } + G = nx.empty_graph(0, create_using) + if A.ndim != 2: + raise nx.NetworkXError(f"Input array must be 2D, not {A.ndim}") + n, m = A.shape + if n != m: + raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}") + dt = A.dtype + try: + python_type = kind_to_python_type[dt.kind] + except Exception as err: + raise TypeError(f"Unknown numpy data type: {dt}") from err + if _default_nodes := (nodelist is None): + nodelist = range(n) + else: + if len(nodelist) != n: + raise ValueError("nodelist must have the same length as A.shape[0]") + + # Make sure we get even the isolated nodes of the graph. + G.add_nodes_from(nodelist) + # Get a list of all the entries in the array with nonzero entries. These + # coordinates become edges in the graph. (convert to int from np.int64) + edges = ((int(e[0]), int(e[1])) for e in zip(*A.nonzero())) + # handle numpy constructed data type + if python_type == "void": + # Sort the fields by their offset, then by dtype, then by name. + fields = sorted( + (offset, dtype, name) for name, (dtype, offset) in A.dtype.fields.items() + ) + triples = ( + ( + u, + v, + {} + if edge_attr in [False, None] + else { + name: kind_to_python_type[dtype.kind](val) + for (_, dtype, name), val in zip(fields, A[u, v]) + }, + ) + for u, v in edges + ) + # If the entries in the adjacency matrix are integers, the graph is a + # multigraph, and parallel_edges is True, 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. + elif python_type is int and G.is_multigraph() and parallel_edges: + chain = itertools.chain.from_iterable + # The following line is equivalent to: + # + # for (u, v) in edges: + # for d in range(A[u, v]): + # G.add_edge(u, v, weight=1) + # + if edge_attr in [False, None]: + triples = chain(((u, v, {}) for d in range(A[u, v])) for (u, v) in edges) + else: + triples = chain( + ((u, v, {edge_attr: 1}) for d in range(A[u, v])) for (u, v) in edges + ) + else: # basic data type + if edge_attr in [False, None]: + triples = ((u, v, {}) for u, v in edges) + else: + triples = ((u, v, {edge_attr: python_type(A[u, v])}) for u, v in edges) + # If we are creating an undirected multigraph, only add the edges from the + # upper triangle of the matrix. Otherwise, add all the edges. This relies + # on the fact that the vertices created in the + # `_generated_weighted_edges()` function are actually the row/column + # indices for the matrix `A`. + # + # Without this check, we run into a problem where each edge is added twice + # when `G.add_edges_from()` is invoked below. + if G.is_multigraph() and not G.is_directed(): + triples = ((u, v, d) for u, v, d in triples if u <= v) + # Remap nodes if user provided custom `nodelist` + if not _default_nodes: + idx_to_node = dict(enumerate(nodelist)) + triples = ((idx_to_node[u], idx_to_node[v], d) for u, v, d in triples) + G.add_edges_from(triples) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/exception.py b/venv/lib/python3.10/site-packages/networkx/exception.py new file mode 100644 index 0000000000000000000000000000000000000000..c960cf13fd5a8e4da0ca68c66350b8baa1728c34 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/exception.py @@ -0,0 +1,131 @@ +""" +********** +Exceptions +********** + +Base exceptions and errors for NetworkX. +""" + +__all__ = [ + "HasACycle", + "NodeNotFound", + "PowerIterationFailedConvergence", + "ExceededMaxIterations", + "AmbiguousSolution", + "NetworkXAlgorithmError", + "NetworkXException", + "NetworkXError", + "NetworkXNoCycle", + "NetworkXNoPath", + "NetworkXNotImplemented", + "NetworkXPointlessConcept", + "NetworkXUnbounded", + "NetworkXUnfeasible", +] + + +class NetworkXException(Exception): + """Base class for exceptions in NetworkX.""" + + +class NetworkXError(NetworkXException): + """Exception for a serious error in NetworkX""" + + +class NetworkXPointlessConcept(NetworkXException): + """Raised when a null graph is provided as input to an algorithm + that cannot use it. + + The null graph is sometimes considered a pointless concept [1]_, + thus the name of the exception. + + Notes + ----- + Null graphs and empty graphs are often used interchangeably but they + are well defined in NetworkX. An ``empty_graph`` is a graph with ``n`` nodes + and 0 edges, and a ``null_graph`` is a graph with 0 nodes and 0 edges. + + References + ---------- + .. [1] Harary, F. and Read, R. "Is the Null Graph a Pointless + Concept?" In Graphs and Combinatorics Conference, George + Washington University. New York: Springer-Verlag, 1973. + + """ + + +class NetworkXAlgorithmError(NetworkXException): + """Exception for unexpected termination of algorithms.""" + + +class NetworkXUnfeasible(NetworkXAlgorithmError): + """Exception raised by algorithms trying to solve a problem + instance that has no feasible solution.""" + + +class NetworkXNoPath(NetworkXUnfeasible): + """Exception for algorithms that should return a path when running + on graphs where such a path does not exist.""" + + +class NetworkXNoCycle(NetworkXUnfeasible): + """Exception for algorithms that should return a cycle when running + on graphs where such a cycle does not exist.""" + + +class HasACycle(NetworkXException): + """Raised if a graph has a cycle when an algorithm expects that it + will have no cycles. + + """ + + +class NetworkXUnbounded(NetworkXAlgorithmError): + """Exception raised by algorithms trying to solve a maximization + or a minimization problem instance that is unbounded.""" + + +class NetworkXNotImplemented(NetworkXException): + """Exception raised by algorithms not implemented for a type of graph.""" + + +class NodeNotFound(NetworkXException): + """Exception raised if requested node is not present in the graph""" + + +class AmbiguousSolution(NetworkXException): + """Raised if more than one valid solution exists for an intermediary step + of an algorithm. + + In the face of ambiguity, refuse the temptation to guess. + This may occur, for example, when trying to determine the + bipartite node sets in a disconnected bipartite graph when + computing bipartite matchings. + + """ + + +class ExceededMaxIterations(NetworkXException): + """Raised if a loop iterates too many times without breaking. + + This may occur, for example, in an algorithm that computes + progressively better approximations to a value but exceeds an + iteration bound specified by the user. + + """ + + +class PowerIterationFailedConvergence(ExceededMaxIterations): + """Raised when the power iteration method fails to converge within a + specified iteration limit. + + `num_iterations` is the number of iterations that have been + completed when this exception was raised. + + """ + + def __init__(self, num_iterations, *args, **kw): + msg = f"power iteration failed to converge within {num_iterations} iterations" + exception_message = msg + superinit = super().__init__ + superinit(self, exception_message, *args, **kw) diff --git a/venv/lib/python3.10/site-packages/networkx/generators/__init__.py b/venv/lib/python3.10/site-packages/networkx/generators/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6ec027c2405b6f9de2e7b6a0f7c18d782ac8761c --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/__init__.py @@ -0,0 +1,34 @@ +""" +A package for generating various graphs in networkx. + +""" + +from networkx.generators.atlas import * +from networkx.generators.classic import * +from networkx.generators.cographs import * +from networkx.generators.community import * +from networkx.generators.degree_seq import * +from networkx.generators.directed import * +from networkx.generators.duplication import * +from networkx.generators.ego import * +from networkx.generators.expanders import * +from networkx.generators.geometric import * +from networkx.generators.harary_graph import * +from networkx.generators.internet_as_graphs import * +from networkx.generators.intersection import * +from networkx.generators.interval_graph import * +from networkx.generators.joint_degree_seq import * +from networkx.generators.lattice import * +from networkx.generators.line import * +from networkx.generators.mycielski import * +from networkx.generators.nonisomorphic_trees import * +from networkx.generators.random_clustered import * +from networkx.generators.random_graphs import * +from networkx.generators.small import * +from networkx.generators.social import * +from networkx.generators.spectral_graph_forge import * +from networkx.generators.stochastic import * +from networkx.generators.sudoku import * +from networkx.generators.time_series import * +from networkx.generators.trees import * +from networkx.generators.triads import * diff --git a/venv/lib/python3.10/site-packages/networkx/generators/atlas.py b/venv/lib/python3.10/site-packages/networkx/generators/atlas.py new file mode 100644 index 0000000000000000000000000000000000000000..c5dd8d2d8fee8cf7b49525fb05bbda14ab75305e --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/atlas.py @@ -0,0 +1,180 @@ +""" +Generators for the small graph atlas. +""" + +import gzip +import importlib.resources +import os +import os.path +from itertools import islice + +import networkx as nx + +__all__ = ["graph_atlas", "graph_atlas_g"] + +#: The total number of graphs in the atlas. +#: +#: The graphs are labeled starting from 0 and extending to (but not +#: including) this number. +NUM_GRAPHS = 1253 + +#: The path to the data file containing the graph edge lists. +#: +#: This is the absolute path of the gzipped text file containing the +#: edge list for each graph in the atlas. The file contains one entry +#: per graph in the atlas, in sequential order, starting from graph +#: number 0 and extending through graph number 1252 (see +#: :data:`NUM_GRAPHS`). Each entry looks like +#: +#: .. sourcecode:: text +#: +#: GRAPH 6 +#: NODES 3 +#: 0 1 +#: 0 2 +#: +#: where the first two lines are the graph's index in the atlas and the +#: number of nodes in the graph, and the remaining lines are the edge +#: list. +#: +#: This file was generated from a Python list of graphs via code like +#: the following:: +#: +#: import gzip +#: from networkx.generators.atlas import graph_atlas_g +#: from networkx.readwrite.edgelist import write_edgelist +#: +#: with gzip.open('atlas.dat.gz', 'wb') as f: +#: for i, G in enumerate(graph_atlas_g()): +#: f.write(bytes(f'GRAPH {i}\n', encoding='utf-8')) +#: f.write(bytes(f'NODES {len(G)}\n', encoding='utf-8')) +#: write_edgelist(G, f, data=False) +#: + +# Path to the atlas file +ATLAS_FILE = importlib.resources.files("networkx.generators") / "atlas.dat.gz" + + +def _generate_graphs(): + """Sequentially read the file containing the edge list data for the + graphs in the atlas and generate the graphs one at a time. + + This function reads the file given in :data:`.ATLAS_FILE`. + + """ + with gzip.open(ATLAS_FILE, "rb") as f: + line = f.readline() + while line and line.startswith(b"GRAPH"): + # The first two lines of each entry tell us the index of the + # graph in the list and the number of nodes in the graph. + # They look like this: + # + # GRAPH 3 + # NODES 2 + # + graph_index = int(line[6:].rstrip()) + line = f.readline() + num_nodes = int(line[6:].rstrip()) + # The remaining lines contain the edge list, until the next + # GRAPH line (or until the end of the file). + edgelist = [] + line = f.readline() + while line and not line.startswith(b"GRAPH"): + edgelist.append(line.rstrip()) + line = f.readline() + G = nx.Graph() + G.name = f"G{graph_index}" + G.add_nodes_from(range(num_nodes)) + G.add_edges_from(tuple(map(int, e.split())) for e in edgelist) + yield G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def graph_atlas(i): + """Returns graph number `i` from the Graph Atlas. + + For more information, see :func:`.graph_atlas_g`. + + Parameters + ---------- + i : int + The index of the graph from the atlas to get. The graph at index + 0 is assumed to be the null graph. + + Returns + ------- + list + A list of :class:`~networkx.Graph` objects, the one at index *i* + corresponding to the graph *i* in the Graph Atlas. + + See also + -------- + graph_atlas_g + + Notes + ----- + The time required by this function increases linearly with the + argument `i`, since it reads a large file sequentially in order to + generate the graph [1]_. + + References + ---------- + .. [1] Ronald C. Read and Robin J. Wilson, *An Atlas of Graphs*. + Oxford University Press, 1998. + + """ + if not (0 <= i < NUM_GRAPHS): + raise ValueError(f"index must be between 0 and {NUM_GRAPHS}") + return next(islice(_generate_graphs(), i, None)) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def graph_atlas_g(): + """Returns the list of all graphs with up to seven nodes named in the + Graph Atlas. + + The graphs are listed in increasing order by + + 1. number of nodes, + 2. number of edges, + 3. degree sequence (for example 111223 < 112222), + 4. number of automorphisms, + + in that order, with three exceptions as described in the *Notes* + section below. This causes the list to correspond with the index of + the graphs in the Graph Atlas [atlas]_, with the first graph, + ``G[0]``, being the null graph. + + Returns + ------- + list + A list of :class:`~networkx.Graph` objects, the one at index *i* + corresponding to the graph *i* in the Graph Atlas. + + See also + -------- + graph_atlas + + Notes + ----- + This function may be expensive in both time and space, since it + reads a large file sequentially in order to populate the list. + + Although the NetworkX atlas functions match the order of graphs + given in the "Atlas of Graphs" book, there are (at least) three + errors in the ordering described in the book. The following three + pairs of nodes violate the lexicographically nondecreasing sorted + degree sequence rule: + + - graphs 55 and 56 with degree sequences 001111 and 000112, + - graphs 1007 and 1008 with degree sequences 3333444 and 3333336, + - graphs 1012 and 1213 with degree sequences 1244555 and 1244456. + + References + ---------- + .. [atlas] Ronald C. Read and Robin J. Wilson, + *An Atlas of Graphs*. + Oxford University Press, 1998. + + """ + return list(_generate_graphs()) diff --git a/venv/lib/python3.10/site-packages/networkx/generators/classic.py b/venv/lib/python3.10/site-packages/networkx/generators/classic.py new file mode 100644 index 0000000000000000000000000000000000000000..a461e7bd23cd79000b834d3362ea3ca8cde93fb2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/classic.py @@ -0,0 +1,1068 @@ +"""Generators for some classic graphs. + +The typical graph builder function is called as follows: + +>>> G = nx.complete_graph(100) + +returning the complete graph on n nodes labeled 0, .., 99 +as a simple graph. Except for `empty_graph`, all the functions +in this module return a Graph class (i.e. a simple, undirected graph). + +""" + +import itertools +import numbers + +import networkx as nx +from networkx.classes import Graph +from networkx.exception import NetworkXError +from networkx.utils import nodes_or_number, pairwise + +__all__ = [ + "balanced_tree", + "barbell_graph", + "binomial_tree", + "complete_graph", + "complete_multipartite_graph", + "circular_ladder_graph", + "circulant_graph", + "cycle_graph", + "dorogovtsev_goltsev_mendes_graph", + "empty_graph", + "full_rary_tree", + "kneser_graph", + "ladder_graph", + "lollipop_graph", + "null_graph", + "path_graph", + "star_graph", + "tadpole_graph", + "trivial_graph", + "turan_graph", + "wheel_graph", +] + + +# ------------------------------------------------------------------- +# Some Classic Graphs +# ------------------------------------------------------------------- + + +def _tree_edges(n, r): + if n == 0: + return + # helper function for trees + # yields edges in rooted tree at 0 with n nodes and branching ratio r + nodes = iter(range(n)) + parents = [next(nodes)] # stack of max length r + while parents: + source = parents.pop(0) + for i in range(r): + try: + target = next(nodes) + parents.append(target) + yield source, target + except StopIteration: + break + + +@nx._dispatchable(graphs=None, returns_graph=True) +def full_rary_tree(r, n, create_using=None): + """Creates a full r-ary tree of `n` nodes. + + Sometimes called a k-ary, n-ary, or m-ary tree. + "... all non-leaf nodes have exactly r children and all levels + are full except for some rightmost position of the bottom level + (if a leaf at the bottom level is missing, then so are all of the + leaves to its right." [1]_ + + .. plot:: + + >>> nx.draw(nx.full_rary_tree(2, 10)) + + Parameters + ---------- + r : int + branching factor of the tree + n : int + Number of nodes in the tree + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + An r-ary tree with n nodes + + References + ---------- + .. [1] An introduction to data structures and algorithms, + James Andrew Storer, Birkhauser Boston 2001, (page 225). + """ + G = empty_graph(n, create_using) + G.add_edges_from(_tree_edges(n, r)) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def kneser_graph(n, k): + """Returns the Kneser Graph with parameters `n` and `k`. + + The Kneser Graph has nodes that are k-tuples (subsets) of the integers + between 0 and ``n-1``. Nodes are adjacent if their corresponding sets are disjoint. + + Parameters + ---------- + n: int + Number of integers from which to make node subsets. + Subsets are drawn from ``set(range(n))``. + k: int + Size of the subsets. + + Returns + ------- + G : NetworkX Graph + + Examples + -------- + >>> G = nx.kneser_graph(5, 2) + >>> G.number_of_nodes() + 10 + >>> G.number_of_edges() + 15 + >>> nx.is_isomorphic(G, nx.petersen_graph()) + True + """ + if n <= 0: + raise NetworkXError("n should be greater than zero") + if k <= 0 or k > n: + raise NetworkXError("k should be greater than zero and smaller than n") + + G = nx.Graph() + # Create all k-subsets of [0, 1, ..., n-1] + subsets = list(itertools.combinations(range(n), k)) + + if 2 * k > n: + G.add_nodes_from(subsets) + + universe = set(range(n)) + comb = itertools.combinations # only to make it all fit on one line + G.add_edges_from((s, t) for s in subsets for t in comb(universe - set(s), k)) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def balanced_tree(r, h, create_using=None): + """Returns the perfectly balanced `r`-ary tree of height `h`. + + .. plot:: + + >>> nx.draw(nx.balanced_tree(2, 3)) + + Parameters + ---------- + r : int + Branching factor of the tree; each node will have `r` + children. + + h : int + Height of the tree. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : NetworkX graph + A balanced `r`-ary tree of height `h`. + + Notes + ----- + This is the rooted tree where all leaves are at distance `h` from + the root. The root has degree `r` and all other internal nodes + have degree `r + 1`. + + Node labels are integers, starting from zero. + + A balanced tree is also known as a *complete r-ary tree*. + + """ + # The number of nodes in the balanced tree is `1 + r + ... + r^h`, + # which is computed by using the closed-form formula for a geometric + # sum with ratio `r`. In the special case that `r` is 1, the number + # of nodes is simply `h + 1` (since the tree is actually a path + # graph). + if r == 1: + n = h + 1 + else: + # This must be an integer if both `r` and `h` are integers. If + # they are not, we force integer division anyway. + n = (1 - r ** (h + 1)) // (1 - r) + return full_rary_tree(r, n, create_using=create_using) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def barbell_graph(m1, m2, create_using=None): + """Returns the Barbell Graph: two complete graphs connected by a path. + + .. plot:: + + >>> nx.draw(nx.barbell_graph(4, 2)) + + Parameters + ---------- + m1 : int + Size of the left and right barbells, must be greater than 2. + + m2 : int + Length of the path connecting the barbells. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Only undirected Graphs are supported. + + Returns + ------- + G : NetworkX graph + A barbell graph. + + Notes + ----- + + + Two identical complete graphs $K_{m1}$ form the left and right bells, + and are connected by a path $P_{m2}$. + + The `2*m1+m2` nodes are numbered + `0, ..., m1-1` for the left barbell, + `m1, ..., m1+m2-1` for the path, + and `m1+m2, ..., 2*m1+m2-1` for the right barbell. + + The 3 subgraphs are joined via the edges `(m1-1, m1)` and + `(m1+m2-1, m1+m2)`. If `m2=0`, this is merely two complete + graphs joined together. + + This graph is an extremal example in David Aldous + and Jim Fill's e-text on Random Walks on Graphs. + + """ + if m1 < 2: + raise NetworkXError("Invalid graph description, m1 should be >=2") + if m2 < 0: + raise NetworkXError("Invalid graph description, m2 should be >=0") + + # left barbell + G = complete_graph(m1, create_using) + if G.is_directed(): + raise NetworkXError("Directed Graph not supported") + + # connecting path + G.add_nodes_from(range(m1, m1 + m2 - 1)) + if m2 > 1: + G.add_edges_from(pairwise(range(m1, m1 + m2))) + + # right barbell + G.add_edges_from( + (u, v) for u in range(m1 + m2, 2 * m1 + m2) for v in range(u + 1, 2 * m1 + m2) + ) + + # connect it up + G.add_edge(m1 - 1, m1) + if m2 > 0: + G.add_edge(m1 + m2 - 1, m1 + m2) + + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def binomial_tree(n, create_using=None): + """Returns the Binomial Tree of order n. + + The binomial tree of order 0 consists of a single node. A binomial tree of order k + is defined recursively by linking two binomial trees of order k-1: the root of one is + the leftmost child of the root of the other. + + .. plot:: + + >>> nx.draw(nx.binomial_tree(3)) + + Parameters + ---------- + n : int + Order of the binomial tree. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : NetworkX graph + A binomial tree of $2^n$ nodes and $2^n - 1$ edges. + + """ + G = nx.empty_graph(1, create_using) + + N = 1 + for i in range(n): + # Use G.edges() to ensure 2-tuples. G.edges is 3-tuple for MultiGraph + edges = [(u + N, v + N) for (u, v) in G.edges()] + G.add_edges_from(edges) + G.add_edge(0, N) + N *= 2 + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number(0) +def complete_graph(n, create_using=None): + """Return the complete graph `K_n` with n nodes. + + A complete graph on `n` nodes means that all pairs + of distinct nodes have an edge connecting them. + + .. plot:: + + >>> nx.draw(nx.complete_graph(5)) + + Parameters + ---------- + n : int or iterable container of nodes + If n is an integer, nodes are from range(n). + If n is a container of nodes, those nodes appear in the graph. + Warning: n is not checked for duplicates and if present the + resulting graph may not be as desired. Make sure you have no duplicates. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Examples + -------- + >>> G = nx.complete_graph(9) + >>> len(G) + 9 + >>> G.size() + 36 + >>> G = nx.complete_graph(range(11, 14)) + >>> list(G.nodes()) + [11, 12, 13] + >>> G = nx.complete_graph(4, nx.DiGraph()) + >>> G.is_directed() + True + + """ + _, nodes = n + G = empty_graph(nodes, create_using) + if len(nodes) > 1: + if G.is_directed(): + edges = itertools.permutations(nodes, 2) + else: + edges = itertools.combinations(nodes, 2) + G.add_edges_from(edges) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def circular_ladder_graph(n, create_using=None): + """Returns the circular ladder graph $CL_n$ of length n. + + $CL_n$ consists of two concentric n-cycles in which + each of the n pairs of concentric nodes are joined by an edge. + + Node labels are the integers 0 to n-1 + + .. plot:: + + >>> nx.draw(nx.circular_ladder_graph(5)) + + """ + G = ladder_graph(n, create_using) + G.add_edge(0, n - 1) + G.add_edge(n, 2 * n - 1) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def circulant_graph(n, offsets, create_using=None): + r"""Returns the circulant graph $Ci_n(x_1, x_2, ..., x_m)$ with $n$ nodes. + + The circulant graph $Ci_n(x_1, ..., x_m)$ consists of $n$ nodes $0, ..., n-1$ + such that node $i$ is connected to nodes $(i + x) \mod n$ and $(i - x) \mod n$ + for all $x$ in $x_1, ..., x_m$. Thus $Ci_n(1)$ is a cycle graph. + + .. plot:: + + >>> nx.draw(nx.circulant_graph(10, [1])) + + Parameters + ---------- + n : integer + The number of nodes in the graph. + offsets : list of integers + A list of node offsets, $x_1$ up to $x_m$, as described above. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + NetworkX Graph of type create_using + + Examples + -------- + Many well-known graph families are subfamilies of the circulant graphs; + for example, to create the cycle graph on n points, we connect every + node to nodes on either side (with offset plus or minus one). For n = 10, + + >>> G = nx.circulant_graph(10, [1]) + >>> edges = [ + ... (0, 9), + ... (0, 1), + ... (1, 2), + ... (2, 3), + ... (3, 4), + ... (4, 5), + ... (5, 6), + ... (6, 7), + ... (7, 8), + ... (8, 9), + ... ] + >>> sorted(edges) == sorted(G.edges()) + True + + Similarly, we can create the complete graph + on 5 points with the set of offsets [1, 2]: + + >>> G = nx.circulant_graph(5, [1, 2]) + >>> edges = [ + ... (0, 1), + ... (0, 2), + ... (0, 3), + ... (0, 4), + ... (1, 2), + ... (1, 3), + ... (1, 4), + ... (2, 3), + ... (2, 4), + ... (3, 4), + ... ] + >>> sorted(edges) == sorted(G.edges()) + True + + """ + G = empty_graph(n, create_using) + for i in range(n): + for j in offsets: + G.add_edge(i, (i - j) % n) + G.add_edge(i, (i + j) % n) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number(0) +def cycle_graph(n, create_using=None): + """Returns the cycle graph $C_n$ of cyclically connected nodes. + + $C_n$ is a path with its two end-nodes connected. + + .. plot:: + + >>> nx.draw(nx.cycle_graph(5)) + + Parameters + ---------- + n : int or iterable container of nodes + If n is an integer, nodes are from `range(n)`. + If n is a container of nodes, those nodes appear in the graph. + Warning: n is not checked for duplicates and if present the + resulting graph may not be as desired. Make sure you have no duplicates. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Notes + ----- + If create_using is directed, the direction is in increasing order. + + """ + _, nodes = n + G = empty_graph(nodes, create_using) + G.add_edges_from(pairwise(nodes, cyclic=True)) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def dorogovtsev_goltsev_mendes_graph(n, create_using=None): + """Returns the hierarchically constructed Dorogovtsev--Goltsev--Mendes graph. + + The Dorogovtsev--Goltsev--Mendes [1]_ procedure deterministically produces a + scale-free graph with ``3/2 * (3**(n-1) + 1)`` nodes + and ``3**n`` edges for a given `n`. + + Note that `n` denotes the number of times the state transition is applied, + starting from the base graph with ``n = 0`` (no transitions), as in [2]_. + This is different from the parameter ``t = n - 1`` in [1]_. + + .. plot:: + + >>> nx.draw(nx.dorogovtsev_goltsev_mendes_graph(3)) + + Parameters + ---------- + n : integer + The generation number. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. Directed graphs and multigraphs are not supported. + + Returns + ------- + G : NetworkX `Graph` + + Raises + ------ + NetworkXError + If `n` is less than zero. + + If `create_using` is a directed graph or multigraph. + + Examples + -------- + >>> G = nx.dorogovtsev_goltsev_mendes_graph(3) + >>> G.number_of_nodes() + 15 + >>> G.number_of_edges() + 27 + >>> nx.is_planar(G) + True + + References + ---------- + .. [1] S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes, + "Pseudofractal scale-free web", Physical Review E 65, 066122, 2002. + https://arxiv.org/pdf/cond-mat/0112143.pdf + .. [2] Weisstein, Eric W. "Dorogovtsev--Goltsev--Mendes Graph". + From MathWorld--A Wolfram Web Resource. + https://mathworld.wolfram.com/Dorogovtsev-Goltsev-MendesGraph.html + """ + if n < 0: + raise NetworkXError("n must be greater than or equal to 0") + + G = empty_graph(0, create_using) + if G.is_directed(): + raise NetworkXError("directed graph not supported") + if G.is_multigraph(): + raise NetworkXError("multigraph not supported") + + G.add_edge(0, 1) + new_node = 2 # next node to be added + for _ in range(n): # iterate over number of generations. + new_edges = [] + for u, v in G.edges(): + new_edges.append((u, new_node)) + new_edges.append((v, new_node)) + new_node += 1 + + G.add_edges_from(new_edges) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number(0) +def empty_graph(n=0, create_using=None, default=Graph): + """Returns the empty graph with n nodes and zero edges. + + .. plot:: + + >>> nx.draw(nx.empty_graph(5)) + + Parameters + ---------- + n : int or iterable container of nodes (default = 0) + If n is an integer, nodes are from `range(n)`. + If n is a container of nodes, those nodes appear in the graph. + create_using : Graph Instance, Constructor or None + Indicator of type of graph to return. + If a Graph-type instance, then clear and use it. + If None, use the `default` constructor. + If a constructor, call it to create an empty graph. + default : Graph constructor (optional, default = nx.Graph) + The constructor to use if create_using is None. + If None, then nx.Graph is used. + This is used when passing an unknown `create_using` value + through your home-grown function to `empty_graph` and + you want a default constructor other than nx.Graph. + + Examples + -------- + >>> G = nx.empty_graph(10) + >>> G.number_of_nodes() + 10 + >>> G.number_of_edges() + 0 + >>> G = nx.empty_graph("ABC") + >>> G.number_of_nodes() + 3 + >>> sorted(G) + ['A', 'B', 'C'] + + Notes + ----- + The variable create_using should be a Graph Constructor or a + "graph"-like object. Constructors, e.g. `nx.Graph` or `nx.MultiGraph` + will be used to create the returned graph. "graph"-like objects + will be cleared (nodes and edges will be removed) and refitted as + an empty "graph" with nodes specified in n. This capability + is useful for specifying the class-nature of the resulting empty + "graph" (i.e. Graph, DiGraph, MyWeirdGraphClass, etc.). + + The variable create_using has three main uses: + Firstly, the variable create_using can be used to create an + empty digraph, multigraph, etc. For example, + + >>> n = 10 + >>> G = nx.empty_graph(n, create_using=nx.DiGraph) + + will create an empty digraph on n nodes. + + Secondly, one can pass an existing graph (digraph, multigraph, + etc.) via create_using. For example, if G is an existing graph + (resp. digraph, multigraph, etc.), then empty_graph(n, create_using=G) + will empty G (i.e. delete all nodes and edges using G.clear()) + and then add n nodes and zero edges, and return the modified graph. + + Thirdly, when constructing your home-grown graph creation function + you can use empty_graph to construct the graph by passing a user + defined create_using to empty_graph. In this case, if you want the + default constructor to be other than nx.Graph, specify `default`. + + >>> def mygraph(n, create_using=None): + ... G = nx.empty_graph(n, create_using, nx.MultiGraph) + ... G.add_edges_from([(0, 1), (0, 1)]) + ... return G + >>> G = mygraph(3) + >>> G.is_multigraph() + True + >>> G = mygraph(3, nx.Graph) + >>> G.is_multigraph() + False + + See also create_empty_copy(G). + + """ + if create_using is None: + G = default() + elif isinstance(create_using, type): + G = create_using() + elif not hasattr(create_using, "adj"): + raise TypeError("create_using is not a valid NetworkX graph type or instance") + else: + # create_using is a NetworkX style Graph + create_using.clear() + G = create_using + + _, nodes = n + G.add_nodes_from(nodes) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def ladder_graph(n, create_using=None): + """Returns the Ladder graph of length n. + + This is two paths of n nodes, with + each pair connected by a single edge. + + Node labels are the integers 0 to 2*n - 1. + + .. plot:: + + >>> nx.draw(nx.ladder_graph(5)) + + """ + G = empty_graph(2 * n, create_using) + if G.is_directed(): + raise NetworkXError("Directed Graph not supported") + G.add_edges_from(pairwise(range(n))) + G.add_edges_from(pairwise(range(n, 2 * n))) + G.add_edges_from((v, v + n) for v in range(n)) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number([0, 1]) +def lollipop_graph(m, n, create_using=None): + """Returns the Lollipop Graph; ``K_m`` connected to ``P_n``. + + This is the Barbell Graph without the right barbell. + + .. plot:: + + >>> nx.draw(nx.lollipop_graph(3, 4)) + + Parameters + ---------- + m, n : int or iterable container of nodes + If an integer, nodes are from ``range(m)`` and ``range(m, m+n)``. + If a container of nodes, those nodes appear in the graph. + Warning: `m` and `n` are not checked for duplicates and if present the + resulting graph may not be as desired. Make sure you have no duplicates. + + The nodes for `m` appear in the complete graph $K_m$ and the nodes + for `n` appear in the path $P_n$ + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + Networkx graph + A complete graph with `m` nodes connected to a path of length `n`. + + Notes + ----- + The 2 subgraphs are joined via an edge ``(m-1, m)``. + If ``n=0``, this is merely a complete graph. + + (This graph is an extremal example in David Aldous and Jim + Fill's etext on Random Walks on Graphs.) + + """ + m, m_nodes = m + M = len(m_nodes) + if M < 2: + raise NetworkXError("Invalid description: m should indicate at least 2 nodes") + + n, n_nodes = n + if isinstance(m, numbers.Integral) and isinstance(n, numbers.Integral): + n_nodes = list(range(M, M + n)) + N = len(n_nodes) + + # the ball + G = complete_graph(m_nodes, create_using) + if G.is_directed(): + raise NetworkXError("Directed Graph not supported") + + # the stick + G.add_nodes_from(n_nodes) + if N > 1: + G.add_edges_from(pairwise(n_nodes)) + + if len(G) != M + N: + raise NetworkXError("Nodes must be distinct in containers m and n") + + # connect ball to stick + if M > 0 and N > 0: + G.add_edge(m_nodes[-1], n_nodes[0]) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def null_graph(create_using=None): + """Returns the Null graph with no nodes or edges. + + See empty_graph for the use of create_using. + + """ + G = empty_graph(0, create_using) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number(0) +def path_graph(n, create_using=None): + """Returns the Path graph `P_n` of linearly connected nodes. + + .. plot:: + + >>> nx.draw(nx.path_graph(5)) + + Parameters + ---------- + n : int or iterable + If an integer, nodes are 0 to n - 1. + If an iterable of nodes, in the order they appear in the path. + Warning: n is not checked for duplicates and if present the + resulting graph may not be as desired. Make sure you have no duplicates. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + """ + _, nodes = n + G = empty_graph(nodes, create_using) + G.add_edges_from(pairwise(nodes)) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number(0) +def star_graph(n, create_using=None): + """Return the star graph + + The star graph consists of one center node connected to n outer nodes. + + .. plot:: + + >>> nx.draw(nx.star_graph(6)) + + Parameters + ---------- + n : int or iterable + If an integer, node labels are 0 to n with center 0. + If an iterable of nodes, the center is the first. + Warning: n is not checked for duplicates and if present the + resulting graph may not be as desired. Make sure you have no duplicates. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Notes + ----- + The graph has n+1 nodes for integer n. + So star_graph(3) is the same as star_graph(range(4)). + """ + n, nodes = n + if isinstance(n, numbers.Integral): + nodes.append(int(n)) # there should be n+1 nodes + G = empty_graph(nodes, create_using) + if G.is_directed(): + raise NetworkXError("Directed Graph not supported") + + if len(nodes) > 1: + hub, *spokes = nodes + G.add_edges_from((hub, node) for node in spokes) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number([0, 1]) +def tadpole_graph(m, n, create_using=None): + """Returns the (m,n)-tadpole graph; ``C_m`` connected to ``P_n``. + + This graph on m+n nodes connects a cycle of size `m` to a path of length `n`. + It looks like a tadpole. It is also called a kite graph or a dragon graph. + + .. plot:: + + >>> nx.draw(nx.tadpole_graph(3, 5)) + + Parameters + ---------- + m, n : int or iterable container of nodes + If an integer, nodes are from ``range(m)`` and ``range(m,m+n)``. + If a container of nodes, those nodes appear in the graph. + Warning: `m` and `n` are not checked for duplicates and if present the + resulting graph may not be as desired. + + The nodes for `m` appear in the cycle graph $C_m$ and the nodes + for `n` appear in the path $P_n$. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + Networkx graph + A cycle of size `m` connected to a path of length `n`. + + Raises + ------ + NetworkXError + If ``m < 2``. The tadpole graph is undefined for ``m<2``. + + Notes + ----- + The 2 subgraphs are joined via an edge ``(m-1, m)``. + If ``n=0``, this is a cycle graph. + `m` and/or `n` can be a container of nodes instead of an integer. + + """ + m, m_nodes = m + M = len(m_nodes) + if M < 2: + raise NetworkXError("Invalid description: m should indicate at least 2 nodes") + + n, n_nodes = n + if isinstance(m, numbers.Integral) and isinstance(n, numbers.Integral): + n_nodes = list(range(M, M + n)) + + # the circle + G = cycle_graph(m_nodes, create_using) + if G.is_directed(): + raise NetworkXError("Directed Graph not supported") + + # the stick + nx.add_path(G, [m_nodes[-1]] + list(n_nodes)) + + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def trivial_graph(create_using=None): + """Return the Trivial graph with one node (with label 0) and no edges. + + .. plot:: + + >>> nx.draw(nx.trivial_graph(), with_labels=True) + + """ + G = empty_graph(1, create_using) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def turan_graph(n, r): + r"""Return the Turan Graph + + The Turan Graph is a complete multipartite graph on $n$ nodes + with $r$ disjoint subsets. That is, edges connect each node to + every node not in its subset. + + Given $n$ and $r$, we create a complete multipartite graph with + $r-(n \mod r)$ partitions of size $n/r$, rounded down, and + $n \mod r$ partitions of size $n/r+1$, rounded down. + + .. plot:: + + >>> nx.draw(nx.turan_graph(6, 2)) + + Parameters + ---------- + n : int + The number of nodes. + r : int + The number of partitions. + Must be less than or equal to n. + + Notes + ----- + Must satisfy $1 <= r <= n$. + The graph has $(r-1)(n^2)/(2r)$ edges, rounded down. + """ + + if not 1 <= r <= n: + raise NetworkXError("Must satisfy 1 <= r <= n") + + partitions = [n // r] * (r - (n % r)) + [n // r + 1] * (n % r) + G = complete_multipartite_graph(*partitions) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number(0) +def wheel_graph(n, create_using=None): + """Return the wheel graph + + The wheel graph consists of a hub node connected to a cycle of (n-1) nodes. + + .. plot:: + + >>> nx.draw(nx.wheel_graph(5)) + + Parameters + ---------- + n : int or iterable + If an integer, node labels are 0 to n with center 0. + If an iterable of nodes, the center is the first. + Warning: n is not checked for duplicates and if present the + resulting graph may not be as desired. Make sure you have no duplicates. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Node labels are the integers 0 to n - 1. + """ + _, nodes = n + G = empty_graph(nodes, create_using) + if G.is_directed(): + raise NetworkXError("Directed Graph not supported") + + if len(nodes) > 1: + hub, *rim = nodes + G.add_edges_from((hub, node) for node in rim) + if len(rim) > 1: + G.add_edges_from(pairwise(rim, cyclic=True)) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def complete_multipartite_graph(*subset_sizes): + """Returns the complete multipartite graph with the specified subset sizes. + + .. plot:: + + >>> nx.draw(nx.complete_multipartite_graph(1, 2, 3)) + + Parameters + ---------- + subset_sizes : tuple of integers or tuple of node iterables + The arguments can either all be integer number of nodes or they + can all be iterables of nodes. If integers, they represent the + number of nodes in each subset of the multipartite graph. + If iterables, each is used to create the nodes for that subset. + The length of subset_sizes is the number of subsets. + + Returns + ------- + G : NetworkX Graph + Returns the complete multipartite graph with the specified subsets. + + For each node, the node attribute 'subset' is an integer + indicating which subset contains the node. + + Examples + -------- + Creating a complete tripartite graph, with subsets of one, two, and three + nodes, respectively. + + >>> G = nx.complete_multipartite_graph(1, 2, 3) + >>> [G.nodes[u]["subset"] for u in G] + [0, 1, 1, 2, 2, 2] + >>> list(G.edges(0)) + [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)] + >>> list(G.edges(2)) + [(2, 0), (2, 3), (2, 4), (2, 5)] + >>> list(G.edges(4)) + [(4, 0), (4, 1), (4, 2)] + + >>> G = nx.complete_multipartite_graph("a", "bc", "def") + >>> [G.nodes[u]["subset"] for u in sorted(G)] + [0, 1, 1, 2, 2, 2] + + Notes + ----- + This function generalizes several other graph builder functions. + + - If no subset sizes are given, this returns the null graph. + - If a single subset size `n` is given, this returns the empty graph on + `n` nodes. + - If two subset sizes `m` and `n` are given, this returns the complete + bipartite graph on `m + n` nodes. + - If subset sizes `1` and `n` are given, this returns the star graph on + `n + 1` nodes. + + See also + -------- + complete_bipartite_graph + """ + # The complete multipartite graph is an undirected simple graph. + G = Graph() + + if len(subset_sizes) == 0: + return G + + # set up subsets of nodes + try: + extents = pairwise(itertools.accumulate((0,) + subset_sizes)) + subsets = [range(start, end) for start, end in extents] + except TypeError: + subsets = subset_sizes + else: + if any(size < 0 for size in subset_sizes): + raise NetworkXError(f"Negative number of nodes not valid: {subset_sizes}") + + # add nodes with subset attribute + # while checking that ints are not mixed with iterables + try: + for i, subset in enumerate(subsets): + G.add_nodes_from(subset, subset=i) + except TypeError as err: + raise NetworkXError("Arguments must be all ints or all iterables") from err + + # Across subsets, all nodes should be adjacent. + # We can use itertools.combinations() because undirected. + for subset1, subset2 in itertools.combinations(subsets, 2): + G.add_edges_from(itertools.product(subset1, subset2)) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/cographs.py b/venv/lib/python3.10/site-packages/networkx/generators/cographs.py new file mode 100644 index 0000000000000000000000000000000000000000..6635b32f691696c1b6f309ad0da81c3cbc43bed9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/cographs.py @@ -0,0 +1,68 @@ +r"""Generators for cographs + +A cograph is a graph containing no path on four vertices. +Cographs or $P_4$-free graphs can be obtained from a single vertex +by disjoint union and complementation operations. + +References +---------- +.. [0] D.G. Corneil, H. Lerchs, L.Stewart Burlingham, + "Complement reducible graphs", + Discrete Applied Mathematics, Volume 3, Issue 3, 1981, Pages 163-174, + ISSN 0166-218X. +""" + +import networkx as nx +from networkx.utils import py_random_state + +__all__ = ["random_cograph"] + + +@py_random_state(1) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_cograph(n, seed=None): + r"""Returns a random cograph with $2 ^ n$ nodes. + + A cograph is a graph containing no path on four vertices. + Cographs or $P_4$-free graphs can be obtained from a single vertex + by disjoint union and complementation operations. + + This generator starts off from a single vertex and performs disjoint + union and full join operations on itself. + The decision on which operation will take place is random. + + Parameters + ---------- + n : int + The order of the cograph. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : A random graph containing no path on four vertices. + + See Also + -------- + full_join + union + + References + ---------- + .. [1] D.G. Corneil, H. Lerchs, L.Stewart Burlingham, + "Complement reducible graphs", + Discrete Applied Mathematics, Volume 3, Issue 3, 1981, Pages 163-174, + ISSN 0166-218X. + """ + R = nx.empty_graph(1) + + for i in range(n): + RR = nx.relabel_nodes(R.copy(), lambda x: x + len(R)) + + if seed.randint(0, 1) == 0: + R = nx.full_join(R, RR) + else: + R = nx.disjoint_union(R, RR) + + return R diff --git a/venv/lib/python3.10/site-packages/networkx/generators/community.py b/venv/lib/python3.10/site-packages/networkx/generators/community.py new file mode 100644 index 0000000000000000000000000000000000000000..a7f2294c5cf9137dc1fab2a50d7ffcd6c59b6dec --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/community.py @@ -0,0 +1,1070 @@ +"""Generators for classes of graphs used in studying social networks.""" + +import itertools +import math + +import networkx as nx +from networkx.utils import py_random_state + +__all__ = [ + "caveman_graph", + "connected_caveman_graph", + "relaxed_caveman_graph", + "random_partition_graph", + "planted_partition_graph", + "gaussian_random_partition_graph", + "ring_of_cliques", + "windmill_graph", + "stochastic_block_model", + "LFR_benchmark_graph", +] + + +@nx._dispatchable(graphs=None, returns_graph=True) +def caveman_graph(l, k): + """Returns a caveman graph of `l` cliques of size `k`. + + Parameters + ---------- + l : int + Number of cliques + k : int + Size of cliques + + Returns + ------- + G : NetworkX Graph + caveman graph + + Notes + ----- + This returns an undirected graph, it can be converted to a directed + graph using :func:`nx.to_directed`, or a multigraph using + ``nx.MultiGraph(nx.caveman_graph(l, k))``. Only the undirected version is + described in [1]_ and it is unclear which of the directed + generalizations is most useful. + + Examples + -------- + >>> G = nx.caveman_graph(3, 3) + + See also + -------- + + connected_caveman_graph + + References + ---------- + .. [1] Watts, D. J. 'Networks, Dynamics, and the Small-World Phenomenon.' + Amer. J. Soc. 105, 493-527, 1999. + """ + # l disjoint cliques of size k + G = nx.empty_graph(l * k) + if k > 1: + for start in range(0, l * k, k): + edges = itertools.combinations(range(start, start + k), 2) + G.add_edges_from(edges) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def connected_caveman_graph(l, k): + """Returns a connected caveman graph of `l` cliques of size `k`. + + The connected caveman graph is formed by creating `n` cliques of size + `k`, then a single edge in each clique is rewired to a node in an + adjacent clique. + + Parameters + ---------- + l : int + number of cliques + k : int + size of cliques (k at least 2 or NetworkXError is raised) + + Returns + ------- + G : NetworkX Graph + connected caveman graph + + Raises + ------ + NetworkXError + If the size of cliques `k` is smaller than 2. + + Notes + ----- + This returns an undirected graph, it can be converted to a directed + graph using :func:`nx.to_directed`, or a multigraph using + ``nx.MultiGraph(nx.caveman_graph(l, k))``. Only the undirected version is + described in [1]_ and it is unclear which of the directed + generalizations is most useful. + + Examples + -------- + >>> G = nx.connected_caveman_graph(3, 3) + + References + ---------- + .. [1] Watts, D. J. 'Networks, Dynamics, and the Small-World Phenomenon.' + Amer. J. Soc. 105, 493-527, 1999. + """ + if k < 2: + raise nx.NetworkXError( + "The size of cliques in a connected caveman graph must be at least 2." + ) + + G = nx.caveman_graph(l, k) + for start in range(0, l * k, k): + G.remove_edge(start, start + 1) + G.add_edge(start, (start - 1) % (l * k)) + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def relaxed_caveman_graph(l, k, p, seed=None): + """Returns a relaxed caveman graph. + + A relaxed caveman graph starts with `l` cliques of size `k`. Edges are + then randomly rewired with probability `p` to link different cliques. + + Parameters + ---------- + l : int + Number of groups + k : int + Size of cliques + p : float + Probability of rewiring each edge. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : NetworkX Graph + Relaxed Caveman Graph + + Raises + ------ + NetworkXError + If p is not in [0,1] + + Examples + -------- + >>> G = nx.relaxed_caveman_graph(2, 3, 0.1, seed=42) + + References + ---------- + .. [1] Santo Fortunato, Community Detection in Graphs, + Physics Reports Volume 486, Issues 3-5, February 2010, Pages 75-174. + https://arxiv.org/abs/0906.0612 + """ + G = nx.caveman_graph(l, k) + nodes = list(G) + for u, v in G.edges(): + if seed.random() < p: # rewire the edge + x = seed.choice(nodes) + if G.has_edge(u, x): + continue + G.remove_edge(u, v) + G.add_edge(u, x) + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_partition_graph(sizes, p_in, p_out, seed=None, directed=False): + """Returns the random partition graph with a partition of sizes. + + A partition graph is a graph of communities with sizes defined by + s in sizes. Nodes in the same group are connected with probability + p_in and nodes of different groups are connected with probability + p_out. + + Parameters + ---------- + sizes : list of ints + Sizes of groups + p_in : float + probability of edges with in groups + p_out : float + probability of edges between groups + directed : boolean optional, default=False + Whether to create a directed graph + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : NetworkX Graph or DiGraph + random partition graph of size sum(gs) + + Raises + ------ + NetworkXError + If p_in or p_out is not in [0,1] + + Examples + -------- + >>> G = nx.random_partition_graph([10, 10, 10], 0.25, 0.01) + >>> len(G) + 30 + >>> partition = G.graph["partition"] + >>> len(partition) + 3 + + Notes + ----- + This is a generalization of the planted-l-partition described in + [1]_. It allows for the creation of groups of any size. + + The partition is store as a graph attribute 'partition'. + + References + ---------- + .. [1] Santo Fortunato 'Community Detection in Graphs' Physical Reports + Volume 486, Issue 3-5 p. 75-174. https://arxiv.org/abs/0906.0612 + """ + # Use geometric method for O(n+m) complexity algorithm + # partition = nx.community_sets(nx.get_node_attributes(G, 'affiliation')) + if not 0.0 <= p_in <= 1.0: + raise nx.NetworkXError("p_in must be in [0,1]") + if not 0.0 <= p_out <= 1.0: + raise nx.NetworkXError("p_out must be in [0,1]") + + # create connection matrix + num_blocks = len(sizes) + p = [[p_out for s in range(num_blocks)] for r in range(num_blocks)] + for r in range(num_blocks): + p[r][r] = p_in + + return stochastic_block_model( + sizes, + p, + nodelist=None, + seed=seed, + directed=directed, + selfloops=False, + sparse=True, + ) + + +@py_random_state(4) +@nx._dispatchable(graphs=None, returns_graph=True) +def planted_partition_graph(l, k, p_in, p_out, seed=None, directed=False): + """Returns the planted l-partition graph. + + This model partitions a graph with n=l*k vertices in + l groups with k vertices each. Vertices of the same + group are linked with a probability p_in, and vertices + of different groups are linked with probability p_out. + + Parameters + ---------- + l : int + Number of groups + k : int + Number of vertices in each group + p_in : float + probability of connecting vertices within a group + p_out : float + probability of connected vertices between groups + 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 + + Returns + ------- + G : NetworkX Graph or DiGraph + planted l-partition graph + + Raises + ------ + NetworkXError + If `p_in`, `p_out` are not in `[0, 1]` + + Examples + -------- + >>> G = nx.planted_partition_graph(4, 3, 0.5, 0.1, seed=42) + + See Also + -------- + random_partition_model + + References + ---------- + .. [1] A. Condon, R.M. Karp, Algorithms for graph partitioning + on the planted partition model, + Random Struct. Algor. 18 (2001) 116-140. + + .. [2] Santo Fortunato 'Community Detection in Graphs' Physical Reports + Volume 486, Issue 3-5 p. 75-174. https://arxiv.org/abs/0906.0612 + """ + return random_partition_graph([k] * l, p_in, p_out, seed=seed, directed=directed) + + +@py_random_state(6) +@nx._dispatchable(graphs=None, returns_graph=True) +def gaussian_random_partition_graph(n, s, v, p_in, p_out, directed=False, seed=None): + """Generate a Gaussian random partition graph. + + A Gaussian random partition graph is created by creating k partitions + each with a size drawn from a normal distribution with mean s and variance + s/v. Nodes are connected within clusters with probability p_in and + between clusters with probability p_out[1] + + Parameters + ---------- + n : int + Number of nodes in the graph + s : float + Mean cluster size + v : float + Shape parameter. The variance of cluster size distribution is s/v. + p_in : float + Probability of intra cluster connection. + p_out : float + Probability of inter cluster connection. + directed : boolean, optional default=False + Whether to create a directed graph or not + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : NetworkX Graph or DiGraph + gaussian random partition graph + + Raises + ------ + NetworkXError + If s is > n + If p_in or p_out is not in [0,1] + + Notes + ----- + Note the number of partitions is dependent on s,v and n, and that the + last partition may be considerably smaller, as it is sized to simply + fill out the nodes [1] + + See Also + -------- + random_partition_graph + + Examples + -------- + >>> G = nx.gaussian_random_partition_graph(100, 10, 10, 0.25, 0.1) + >>> len(G) + 100 + + References + ---------- + .. [1] Ulrik Brandes, Marco Gaertler, Dorothea Wagner, + Experiments on Graph Clustering Algorithms, + In the proceedings of the 11th Europ. Symp. Algorithms, 2003. + """ + if s > n: + raise nx.NetworkXError("s must be <= n") + assigned = 0 + sizes = [] + while True: + size = int(seed.gauss(s, s / v + 0.5)) + if size < 1: # how to handle 0 or negative sizes? + continue + if assigned + size >= n: + sizes.append(n - assigned) + break + assigned += size + sizes.append(size) + return random_partition_graph(sizes, p_in, p_out, seed=seed, directed=directed) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def ring_of_cliques(num_cliques, clique_size): + """Defines a "ring of cliques" graph. + + A ring of cliques graph is consisting of cliques, connected through single + links. Each clique is a complete graph. + + Parameters + ---------- + num_cliques : int + Number of cliques + clique_size : int + Size of cliques + + Returns + ------- + G : NetworkX Graph + ring of cliques graph + + Raises + ------ + NetworkXError + If the number of cliques is lower than 2 or + if the size of cliques is smaller than 2. + + Examples + -------- + >>> G = nx.ring_of_cliques(8, 4) + + See Also + -------- + connected_caveman_graph + + Notes + ----- + The `connected_caveman_graph` graph removes a link from each clique to + connect it with the next clique. Instead, the `ring_of_cliques` graph + simply adds the link without removing any link from the cliques. + """ + if num_cliques < 2: + raise nx.NetworkXError("A ring of cliques must have at least two cliques") + if clique_size < 2: + raise nx.NetworkXError("The cliques must have at least two nodes") + + G = nx.Graph() + for i in range(num_cliques): + edges = itertools.combinations( + range(i * clique_size, i * clique_size + clique_size), 2 + ) + G.add_edges_from(edges) + G.add_edge( + i * clique_size + 1, (i + 1) * clique_size % (num_cliques * clique_size) + ) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def windmill_graph(n, k): + """Generate a windmill graph. + A windmill graph is a graph of `n` cliques each of size `k` that are all + joined at one node. + It can be thought of as taking a disjoint union of `n` cliques of size `k`, + selecting one point from each, and contracting all of the selected points. + Alternatively, one could generate `n` cliques of size `k-1` and one node + that is connected to all other nodes in the graph. + + Parameters + ---------- + n : int + Number of cliques + k : int + Size of cliques + + Returns + ------- + G : NetworkX Graph + windmill graph with n cliques of size k + + Raises + ------ + NetworkXError + If the number of cliques is less than two + If the size of the cliques are less than two + + Examples + -------- + >>> G = nx.windmill_graph(4, 5) + + Notes + ----- + The node labeled `0` will be the node connected to all other nodes. + Note that windmill graphs are usually denoted `Wd(k,n)`, so the parameters + are in the opposite order as the parameters of this method. + """ + if n < 2: + msg = "A windmill graph must have at least two cliques" + raise nx.NetworkXError(msg) + if k < 2: + raise nx.NetworkXError("The cliques must have at least two nodes") + + G = nx.disjoint_union_all( + itertools.chain( + [nx.complete_graph(k)], (nx.complete_graph(k - 1) for _ in range(n - 1)) + ) + ) + G.add_edges_from((0, i) for i in range(k, G.number_of_nodes())) + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def stochastic_block_model( + sizes, p, nodelist=None, seed=None, directed=False, selfloops=False, sparse=True +): + """Returns a stochastic block model graph. + + This model partitions the nodes in blocks of arbitrary sizes, and places + edges between pairs of nodes independently, with a probability that depends + on the blocks. + + Parameters + ---------- + sizes : list of ints + Sizes of blocks + p : list of list of floats + Element (r,s) gives the density of edges going from the nodes + of group r to nodes of group s. + p must match the number of groups (len(sizes) == len(p)), + and it must be symmetric if the graph is undirected. + nodelist : list, optional + The block tags are assigned according to the node identifiers + in nodelist. If nodelist is None, then the ordering is the + range [0,sum(sizes)-1]. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + directed : boolean optional, default=False + Whether to create a directed graph or not. + selfloops : boolean optional, default=False + Whether to include self-loops or not. + sparse: boolean optional, default=True + Use the sparse heuristic to speed up the generator. + + Returns + ------- + g : NetworkX Graph or DiGraph + Stochastic block model graph of size sum(sizes) + + Raises + ------ + NetworkXError + If probabilities are not in [0,1]. + If the probability matrix is not square (directed case). + If the probability matrix is not symmetric (undirected case). + If the sizes list does not match nodelist or the probability matrix. + If nodelist contains duplicate. + + Examples + -------- + >>> sizes = [75, 75, 300] + >>> probs = [[0.25, 0.05, 0.02], [0.05, 0.35, 0.07], [0.02, 0.07, 0.40]] + >>> g = nx.stochastic_block_model(sizes, probs, seed=0) + >>> len(g) + 450 + >>> H = nx.quotient_graph(g, g.graph["partition"], relabel=True) + >>> for v in H.nodes(data=True): + ... print(round(v[1]["density"], 3)) + 0.245 + 0.348 + 0.405 + >>> for v in H.edges(data=True): + ... print(round(1.0 * v[2]["weight"] / (sizes[v[0]] * sizes[v[1]]), 3)) + 0.051 + 0.022 + 0.07 + + See Also + -------- + random_partition_graph + planted_partition_graph + gaussian_random_partition_graph + gnp_random_graph + + References + ---------- + .. [1] Holland, P. W., Laskey, K. B., & Leinhardt, S., + "Stochastic blockmodels: First steps", + Social networks, 5(2), 109-137, 1983. + """ + # Check if dimensions match + if len(sizes) != len(p): + raise nx.NetworkXException("'sizes' and 'p' do not match.") + # Check for probability symmetry (undirected) and shape (directed) + for row in p: + if len(p) != len(row): + raise nx.NetworkXException("'p' must be a square matrix.") + if not directed: + p_transpose = [list(i) for i in zip(*p)] + for i in zip(p, p_transpose): + for j in zip(i[0], i[1]): + if abs(j[0] - j[1]) > 1e-08: + raise nx.NetworkXException("'p' must be symmetric.") + # Check for probability range + for row in p: + for prob in row: + if prob < 0 or prob > 1: + raise nx.NetworkXException("Entries of 'p' not in [0,1].") + # Check for nodelist consistency + if nodelist is not None: + if len(nodelist) != sum(sizes): + raise nx.NetworkXException("'nodelist' and 'sizes' do not match.") + if len(nodelist) != len(set(nodelist)): + raise nx.NetworkXException("nodelist contains duplicate.") + else: + nodelist = range(sum(sizes)) + + # Setup the graph conditionally to the directed switch. + block_range = range(len(sizes)) + if directed: + g = nx.DiGraph() + block_iter = itertools.product(block_range, block_range) + else: + g = nx.Graph() + block_iter = itertools.combinations_with_replacement(block_range, 2) + # Split nodelist in a partition (list of sets). + size_cumsum = [sum(sizes[0:x]) for x in range(len(sizes) + 1)] + g.graph["partition"] = [ + set(nodelist[size_cumsum[x] : size_cumsum[x + 1]]) + for x in range(len(size_cumsum) - 1) + ] + # Setup nodes and graph name + for block_id, nodes in enumerate(g.graph["partition"]): + for node in nodes: + g.add_node(node, block=block_id) + + g.name = "stochastic_block_model" + + # Test for edge existence + parts = g.graph["partition"] + for i, j in block_iter: + if i == j: + if directed: + if selfloops: + edges = itertools.product(parts[i], parts[i]) + else: + edges = itertools.permutations(parts[i], 2) + else: + edges = itertools.combinations(parts[i], 2) + if selfloops: + edges = itertools.chain(edges, zip(parts[i], parts[i])) + for e in edges: + if seed.random() < p[i][j]: + g.add_edge(*e) + else: + edges = itertools.product(parts[i], parts[j]) + if sparse: + if p[i][j] == 1: # Test edges cases p_ij = 0 or 1 + for e in edges: + g.add_edge(*e) + elif p[i][j] > 0: + while True: + try: + logrand = math.log(seed.random()) + skip = math.floor(logrand / math.log(1 - p[i][j])) + # consume "skip" edges + next(itertools.islice(edges, skip, skip), None) + e = next(edges) + g.add_edge(*e) # __safe + except StopIteration: + break + else: + for e in edges: + if seed.random() < p[i][j]: + g.add_edge(*e) # __safe + return g + + +def _zipf_rv_below(gamma, xmin, threshold, seed): + """Returns a random value chosen from the bounded Zipf distribution. + + Repeatedly draws values from the Zipf distribution until the + threshold is met, then returns that value. + """ + result = nx.utils.zipf_rv(gamma, xmin, seed) + while result > threshold: + result = nx.utils.zipf_rv(gamma, xmin, seed) + return result + + +def _powerlaw_sequence(gamma, low, high, condition, length, max_iters, seed): + """Returns a list of numbers obeying a constrained power law distribution. + + ``gamma`` and ``low`` are the parameters for the Zipf distribution. + + ``high`` is the maximum allowed value for values draw from the Zipf + distribution. For more information, see :func:`_zipf_rv_below`. + + ``condition`` and ``length`` are Boolean-valued functions on + lists. While generating the list, random values are drawn and + appended to the list until ``length`` is satisfied by the created + list. Once ``condition`` is satisfied, the sequence generated in + this way is returned. + + ``max_iters`` indicates the number of times to generate a list + satisfying ``length``. If the number of iterations exceeds this + value, :exc:`~networkx.exception.ExceededMaxIterations` is raised. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + """ + for i in range(max_iters): + seq = [] + while not length(seq): + seq.append(_zipf_rv_below(gamma, low, high, seed)) + if condition(seq): + return seq + raise nx.ExceededMaxIterations("Could not create power law sequence") + + +def _hurwitz_zeta(x, q, tolerance): + """The Hurwitz zeta function, or the Riemann zeta function of two arguments. + + ``x`` must be greater than one and ``q`` must be positive. + + This function repeatedly computes subsequent partial sums until + convergence, as decided by ``tolerance``. + """ + z = 0 + z_prev = -float("inf") + k = 0 + while abs(z - z_prev) > tolerance: + z_prev = z + z += 1 / ((k + q) ** x) + k += 1 + return z + + +def _generate_min_degree(gamma, average_degree, max_degree, tolerance, max_iters): + """Returns a minimum degree from the given average degree.""" + # Defines zeta function whether or not Scipy is available + try: + from scipy.special import zeta + except ImportError: + + def zeta(x, q): + return _hurwitz_zeta(x, q, tolerance) + + min_deg_top = max_degree + min_deg_bot = 1 + min_deg_mid = (min_deg_top - min_deg_bot) / 2 + min_deg_bot + itrs = 0 + mid_avg_deg = 0 + while abs(mid_avg_deg - average_degree) > tolerance: + if itrs > max_iters: + raise nx.ExceededMaxIterations("Could not match average_degree") + mid_avg_deg = 0 + for x in range(int(min_deg_mid), max_degree + 1): + mid_avg_deg += (x ** (-gamma + 1)) / zeta(gamma, min_deg_mid) + if mid_avg_deg > average_degree: + min_deg_top = min_deg_mid + min_deg_mid = (min_deg_top - min_deg_bot) / 2 + min_deg_bot + else: + min_deg_bot = min_deg_mid + min_deg_mid = (min_deg_top - min_deg_bot) / 2 + min_deg_bot + itrs += 1 + # return int(min_deg_mid + 0.5) + return round(min_deg_mid) + + +def _generate_communities(degree_seq, community_sizes, mu, max_iters, seed): + """Returns a list of sets, each of which represents a community. + + ``degree_seq`` is the degree sequence that must be met by the + graph. + + ``community_sizes`` is the community size distribution that must be + met by the generated list of sets. + + ``mu`` is a float in the interval [0, 1] indicating the fraction of + intra-community edges incident to each node. + + ``max_iters`` is the number of times to try to add a node to a + community. This must be greater than the length of + ``degree_seq``, otherwise this function will always fail. If + the number of iterations exceeds this value, + :exc:`~networkx.exception.ExceededMaxIterations` is raised. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + The communities returned by this are sets of integers in the set {0, + ..., *n* - 1}, where *n* is the length of ``degree_seq``. + + """ + # This assumes the nodes in the graph will be natural numbers. + result = [set() for _ in community_sizes] + n = len(degree_seq) + free = list(range(n)) + for i in range(max_iters): + v = free.pop() + c = seed.choice(range(len(community_sizes))) + # s = int(degree_seq[v] * (1 - mu) + 0.5) + s = round(degree_seq[v] * (1 - mu)) + # If the community is large enough, add the node to the chosen + # community. Otherwise, return it to the list of unaffiliated + # nodes. + if s < community_sizes[c]: + result[c].add(v) + else: + free.append(v) + # If the community is too big, remove a node from it. + if len(result[c]) > community_sizes[c]: + free.append(result[c].pop()) + if not free: + return result + msg = "Could not assign communities; try increasing min_community" + raise nx.ExceededMaxIterations(msg) + + +@py_random_state(11) +@nx._dispatchable(graphs=None, returns_graph=True) +def LFR_benchmark_graph( + n, + tau1, + tau2, + mu, + average_degree=None, + min_degree=None, + max_degree=None, + min_community=None, + max_community=None, + tol=1.0e-7, + max_iters=500, + seed=None, +): + r"""Returns the LFR benchmark graph. + + This algorithm proceeds as follows: + + 1) Find a degree sequence with a power law distribution, and minimum + value ``min_degree``, which has approximate average degree + ``average_degree``. This is accomplished by either + + a) specifying ``min_degree`` and not ``average_degree``, + b) specifying ``average_degree`` and not ``min_degree``, in which + case a suitable minimum degree will be found. + + ``max_degree`` can also be specified, otherwise it will be set to + ``n``. Each node *u* will have $\mu \mathrm{deg}(u)$ edges + joining it to nodes in communities other than its own and $(1 - + \mu) \mathrm{deg}(u)$ edges joining it to nodes in its own + community. + 2) Generate community sizes according to a power law distribution + with exponent ``tau2``. If ``min_community`` and + ``max_community`` are not specified they will be selected to be + ``min_degree`` and ``max_degree``, respectively. Community sizes + are generated until the sum of their sizes equals ``n``. + 3) Each node will be randomly assigned a community with the + condition that the community is large enough for the node's + intra-community degree, $(1 - \mu) \mathrm{deg}(u)$ as + described in step 2. If a community grows too large, a random node + will be selected for reassignment to a new community, until all + nodes have been assigned a community. + 4) Each node *u* then adds $(1 - \mu) \mathrm{deg}(u)$ + intra-community edges and $\mu \mathrm{deg}(u)$ inter-community + edges. + + Parameters + ---------- + n : int + Number of nodes in the created graph. + + tau1 : float + Power law exponent for the degree distribution of the created + graph. This value must be strictly greater than one. + + tau2 : float + Power law exponent for the community size distribution in the + created graph. This value must be strictly greater than one. + + mu : float + Fraction of inter-community edges incident to each node. This + value must be in the interval [0, 1]. + + average_degree : float + Desired average degree of nodes in the created graph. This value + must be in the interval [0, *n*]. Exactly one of this and + ``min_degree`` must be specified, otherwise a + :exc:`NetworkXError` is raised. + + min_degree : int + Minimum degree of nodes in the created graph. This value must be + in the interval [0, *n*]. Exactly one of this and + ``average_degree`` must be specified, otherwise a + :exc:`NetworkXError` is raised. + + max_degree : int + Maximum degree of nodes in the created graph. If not specified, + this is set to ``n``, the total number of nodes in the graph. + + min_community : int + Minimum size of communities in the graph. If not specified, this + is set to ``min_degree``. + + max_community : int + Maximum size of communities in the graph. If not specified, this + is set to ``n``, the total number of nodes in the graph. + + tol : float + Tolerance when comparing floats, specifically when comparing + average degree values. + + max_iters : int + Maximum number of iterations to try to create the community sizes, + degree distribution, and community affiliations. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : NetworkX graph + The LFR benchmark graph generated according to the specified + parameters. + + Each node in the graph has a node attribute ``'community'`` that + stores the community (that is, the set of nodes) that includes + it. + + Raises + ------ + NetworkXError + If any of the parameters do not meet their upper and lower bounds: + + - ``tau1`` and ``tau2`` must be strictly greater than 1. + - ``mu`` must be in [0, 1]. + - ``max_degree`` must be in {1, ..., *n*}. + - ``min_community`` and ``max_community`` must be in {0, ..., + *n*}. + + If not exactly one of ``average_degree`` and ``min_degree`` is + specified. + + If ``min_degree`` is not specified and a suitable ``min_degree`` + cannot be found. + + ExceededMaxIterations + If a valid degree sequence cannot be created within + ``max_iters`` number of iterations. + + If a valid set of community sizes cannot be created within + ``max_iters`` number of iterations. + + If a valid community assignment cannot be created within ``10 * + n * max_iters`` number of iterations. + + Examples + -------- + Basic usage:: + + >>> from networkx.generators.community import LFR_benchmark_graph + >>> n = 250 + >>> tau1 = 3 + >>> tau2 = 1.5 + >>> mu = 0.1 + >>> G = LFR_benchmark_graph( + ... n, tau1, tau2, mu, average_degree=5, min_community=20, seed=10 + ... ) + + Continuing the example above, you can get the communities from the + node attributes of the graph:: + + >>> communities = {frozenset(G.nodes[v]["community"]) for v in G} + + Notes + ----- + This algorithm differs slightly from the original way it was + presented in [1]. + + 1) Rather than connecting the graph via a configuration model then + rewiring to match the intra-community and inter-community + degrees, we do this wiring explicitly at the end, which should be + equivalent. + 2) The code posted on the author's website [2] calculates the random + power law distributed variables and their average using + continuous approximations, whereas we use the discrete + distributions here as both degree and community size are + discrete. + + Though the authors describe the algorithm as quite robust, testing + during development indicates that a somewhat narrower parameter set + is likely to successfully produce a graph. Some suggestions have + been provided in the event of exceptions. + + References + ---------- + .. [1] "Benchmark graphs for testing community detection algorithms", + Andrea Lancichinetti, Santo Fortunato, and Filippo Radicchi, + Phys. Rev. E 78, 046110 2008 + .. [2] https://www.santofortunato.net/resources + + """ + # Perform some basic parameter validation. + if not tau1 > 1: + raise nx.NetworkXError("tau1 must be greater than one") + if not tau2 > 1: + raise nx.NetworkXError("tau2 must be greater than one") + if not 0 <= mu <= 1: + raise nx.NetworkXError("mu must be in the interval [0, 1]") + + # Validate parameters for generating the degree sequence. + if max_degree is None: + max_degree = n + elif not 0 < max_degree <= n: + raise nx.NetworkXError("max_degree must be in the interval (0, n]") + if not ((min_degree is None) ^ (average_degree is None)): + raise nx.NetworkXError( + "Must assign exactly one of min_degree and average_degree" + ) + if min_degree is None: + min_degree = _generate_min_degree( + tau1, average_degree, max_degree, tol, max_iters + ) + + # Generate a degree sequence with a power law distribution. + low, high = min_degree, max_degree + + def condition(seq): + return sum(seq) % 2 == 0 + + def length(seq): + return len(seq) >= n + + deg_seq = _powerlaw_sequence(tau1, low, high, condition, length, max_iters, seed) + + # Validate parameters for generating the community size sequence. + if min_community is None: + min_community = min(deg_seq) + if max_community is None: + max_community = max(deg_seq) + + # Generate a community size sequence with a power law distribution. + # + # TODO The original code incremented the number of iterations each + # time a new Zipf random value was drawn from the distribution. This + # differed from the way the number of iterations was incremented in + # `_powerlaw_degree_sequence`, so this code was changed to match + # that one. As a result, this code is allowed many more chances to + # generate a valid community size sequence. + low, high = min_community, max_community + + def condition(seq): + return sum(seq) == n + + def length(seq): + return sum(seq) >= n + + comms = _powerlaw_sequence(tau2, low, high, condition, length, max_iters, seed) + + # Generate the communities based on the given degree sequence and + # community sizes. + max_iters *= 10 * n + communities = _generate_communities(deg_seq, comms, mu, max_iters, seed) + + # Finally, generate the benchmark graph based on the given + # communities, joining nodes according to the intra- and + # inter-community degrees. + G = nx.Graph() + G.add_nodes_from(range(n)) + for c in communities: + for u in c: + while G.degree(u) < round(deg_seq[u] * (1 - mu)): + v = seed.choice(list(c)) + G.add_edge(u, v) + while G.degree(u) < deg_seq[u]: + v = seed.choice(range(n)) + if v not in c: + G.add_edge(u, v) + G.nodes[u]["community"] = c + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/degree_seq.py b/venv/lib/python3.10/site-packages/networkx/generators/degree_seq.py new file mode 100644 index 0000000000000000000000000000000000000000..a27dd22e80fedc56b07d4ed96e076e5e37b63b3d --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/degree_seq.py @@ -0,0 +1,867 @@ +"""Generate graphs with a given degree sequence or expected degree sequence.""" + +import heapq +import math +from itertools import chain, combinations, zip_longest +from operator import itemgetter + +import networkx as nx +from networkx.utils import py_random_state, random_weighted_sample + +__all__ = [ + "configuration_model", + "directed_configuration_model", + "expected_degree_graph", + "havel_hakimi_graph", + "directed_havel_hakimi_graph", + "degree_sequence_tree", + "random_degree_sequence_graph", +] + +chaini = chain.from_iterable + + +def _to_stublist(degree_sequence): + """Returns a list of degree-repeated node numbers. + + ``degree_sequence`` is a list of nonnegative integers representing + the degrees of nodes in a graph. + + This function returns a list of node numbers with multiplicities + according to the given degree sequence. For example, if the first + element of ``degree_sequence`` is ``3``, then the first node number, + ``0``, will appear at the head of the returned list three times. The + node numbers are assumed to be the numbers zero through + ``len(degree_sequence) - 1``. + + Examples + -------- + + >>> degree_sequence = [1, 2, 3] + >>> _to_stublist(degree_sequence) + [0, 1, 1, 2, 2, 2] + + If a zero appears in the sequence, that means the node exists but + has degree zero, so that number will be skipped in the returned + list:: + + >>> degree_sequence = [2, 0, 1] + >>> _to_stublist(degree_sequence) + [0, 0, 2] + + """ + return list(chaini([n] * d for n, d in enumerate(degree_sequence))) + + +def _configuration_model( + deg_sequence, create_using, directed=False, in_deg_sequence=None, seed=None +): + """Helper function for generating either undirected or directed + configuration model graphs. + + ``deg_sequence`` is a list of nonnegative integers representing the + degree of the node whose label is the index of the list element. + + ``create_using`` see :func:`~networkx.empty_graph`. + + ``directed`` and ``in_deg_sequence`` are required if you want the + returned graph to be generated using the directed configuration + model algorithm. If ``directed`` is ``False``, then ``deg_sequence`` + is interpreted as the degree sequence of an undirected graph and + ``in_deg_sequence`` is ignored. Otherwise, if ``directed`` is + ``True``, then ``deg_sequence`` is interpreted as the out-degree + sequence and ``in_deg_sequence`` as the in-degree sequence of a + directed graph. + + .. note:: + + ``deg_sequence`` and ``in_deg_sequence`` need not be the same + length. + + ``seed`` is a random.Random or numpy.random.RandomState instance + + This function returns a graph, directed if and only if ``directed`` + is ``True``, generated according to the configuration model + algorithm. For more information on the algorithm, see the + :func:`configuration_model` or :func:`directed_configuration_model` + functions. + + """ + n = len(deg_sequence) + G = nx.empty_graph(n, create_using) + # If empty, return the null graph immediately. + if n == 0: + return G + # Build a list of available degree-repeated nodes. For example, + # for degree sequence [3, 2, 1, 1, 1], the "stub list" is + # initially [0, 0, 0, 1, 1, 2, 3, 4], that is, node 0 has degree + # 3 and thus is repeated 3 times, etc. + # + # Also, shuffle the stub list in order to get a random sequence of + # node pairs. + if directed: + pairs = zip_longest(deg_sequence, in_deg_sequence, fillvalue=0) + # Unzip the list of pairs into a pair of lists. + out_deg, in_deg = zip(*pairs) + + out_stublist = _to_stublist(out_deg) + in_stublist = _to_stublist(in_deg) + + seed.shuffle(out_stublist) + seed.shuffle(in_stublist) + else: + stublist = _to_stublist(deg_sequence) + # Choose a random balanced bipartition of the stublist, which + # gives a random pairing of nodes. In this implementation, we + # shuffle the list and then split it in half. + n = len(stublist) + half = n // 2 + seed.shuffle(stublist) + out_stublist, in_stublist = stublist[:half], stublist[half:] + G.add_edges_from(zip(out_stublist, in_stublist)) + return G + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def configuration_model(deg_sequence, create_using=None, seed=None): + """Returns a random graph with the given degree sequence. + + The configuration model generates a random pseudograph (graph with + parallel edges and self loops) by randomly assigning edges to + match the given degree sequence. + + Parameters + ---------- + deg_sequence : list of nonnegative integers + Each list entry corresponds to the degree of a node. + create_using : NetworkX graph constructor, optional (default MultiGraph) + Graph type to create. If graph instance, then cleared before populated. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : MultiGraph + A graph with the specified degree sequence. + Nodes are labeled starting at 0 with an index + corresponding to the position in deg_sequence. + + Raises + ------ + NetworkXError + If the degree sequence does not have an even sum. + + See Also + -------- + is_graphical + + Notes + ----- + As described by Newman [1]_. + + A non-graphical degree sequence (not realizable by some simple + graph) is allowed since this function returns graphs with self + loops and parallel edges. An exception is raised if the degree + sequence does not have an even sum. + + This configuration model construction process can lead to + duplicate edges and loops. You can remove the self-loops and + parallel edges (see below) which will likely result in a graph + that doesn't have the exact degree sequence specified. + + The density of self-loops and parallel edges tends to decrease as + the number of nodes increases. However, typically the number of + self-loops will approach a Poisson distribution with a nonzero mean, + and similarly for the number of parallel edges. Consider a node + with *k* stubs. The probability of being joined to another stub of + the same node is basically (*k* - *1*) / *N*, where *k* is the + degree and *N* is the number of nodes. So the probability of a + self-loop scales like *c* / *N* for some constant *c*. As *N* grows, + this means we expect *c* self-loops. Similarly for parallel edges. + + References + ---------- + .. [1] M.E.J. Newman, "The structure and function of complex networks", + SIAM REVIEW 45-2, pp 167-256, 2003. + + Examples + -------- + You can create a degree sequence following a particular distribution + by using the one of the distribution functions in + :mod:`~networkx.utils.random_sequence` (or one of your own). For + example, to create an undirected multigraph on one hundred nodes + with degree sequence chosen from the power law distribution: + + >>> sequence = nx.random_powerlaw_tree_sequence(100, tries=5000) + >>> G = nx.configuration_model(sequence) + >>> len(G) + 100 + >>> actual_degrees = [d for v, d in G.degree()] + >>> actual_degrees == sequence + True + + The returned graph is a multigraph, which may have parallel + edges. To remove any parallel edges from the returned graph: + + >>> G = nx.Graph(G) + + Similarly, to remove self-loops: + + >>> G.remove_edges_from(nx.selfloop_edges(G)) + + """ + if sum(deg_sequence) % 2 != 0: + msg = "Invalid degree sequence: sum of degrees must be even, not odd" + raise nx.NetworkXError(msg) + + G = nx.empty_graph(0, create_using, default=nx.MultiGraph) + if G.is_directed(): + raise nx.NetworkXNotImplemented("not implemented for directed graphs") + + G = _configuration_model(deg_sequence, G, seed=seed) + + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def directed_configuration_model( + in_degree_sequence, out_degree_sequence, create_using=None, seed=None +): + """Returns a directed_random graph with the given degree sequences. + + The configuration model generates a random directed pseudograph + (graph with parallel edges and self loops) by randomly assigning + edges to match the given degree sequences. + + Parameters + ---------- + in_degree_sequence : list of nonnegative integers + Each list entry corresponds to the in-degree of a node. + out_degree_sequence : list of nonnegative integers + Each list entry corresponds to the out-degree of a node. + create_using : NetworkX graph constructor, optional (default MultiDiGraph) + Graph type to create. If graph instance, then cleared before populated. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : MultiDiGraph + A graph with the specified degree sequences. + Nodes are labeled starting at 0 with an index + corresponding to the position in deg_sequence. + + Raises + ------ + NetworkXError + If the degree sequences do not have the same sum. + + See Also + -------- + configuration_model + + Notes + ----- + Algorithm as described by Newman [1]_. + + A non-graphical degree sequence (not realizable by some simple + graph) is allowed since this function returns graphs with self + loops and parallel edges. An exception is raised if the degree + sequences does not have the same sum. + + This configuration model construction process can lead to + duplicate edges and loops. You can remove the self-loops and + parallel edges (see below) which will likely result in a graph + that doesn't have the exact degree sequence specified. This + "finite-size effect" decreases as the size of the graph increases. + + References + ---------- + .. [1] Newman, M. E. J. and Strogatz, S. H. and Watts, D. J. + Random graphs with arbitrary degree distributions and their applications + Phys. Rev. E, 64, 026118 (2001) + + Examples + -------- + One can modify the in- and out-degree sequences from an existing + directed graph in order to create a new directed graph. For example, + here we modify the directed path graph: + + >>> D = nx.DiGraph([(0, 1), (1, 2), (2, 3)]) + >>> din = list(d for n, d in D.in_degree()) + >>> dout = list(d for n, d in D.out_degree()) + >>> din.append(1) + >>> dout[0] = 2 + >>> # We now expect an edge from node 0 to a new node, node 3. + ... D = nx.directed_configuration_model(din, dout) + + The returned graph is a directed multigraph, which may have parallel + edges. To remove any parallel edges from the returned graph: + + >>> D = nx.DiGraph(D) + + Similarly, to remove self-loops: + + >>> D.remove_edges_from(nx.selfloop_edges(D)) + + """ + if sum(in_degree_sequence) != sum(out_degree_sequence): + msg = "Invalid degree sequences: sequences must have equal sums" + raise nx.NetworkXError(msg) + + if create_using is None: + create_using = nx.MultiDiGraph + + G = _configuration_model( + out_degree_sequence, + create_using, + directed=True, + in_deg_sequence=in_degree_sequence, + seed=seed, + ) + + name = "directed configuration_model {} nodes {} edges" + return G + + +@py_random_state(1) +@nx._dispatchable(graphs=None, returns_graph=True) +def expected_degree_graph(w, seed=None, selfloops=True): + r"""Returns a random graph with given expected degrees. + + Given a sequence of expected degrees $W=(w_0,w_1,\ldots,w_{n-1})$ + of length $n$ this algorithm assigns an edge between node $u$ and + node $v$ with probability + + .. math:: + + p_{uv} = \frac{w_u w_v}{\sum_k w_k} . + + Parameters + ---------- + w : list + The list of expected degrees. + selfloops: bool (default=True) + Set to False to remove the possibility of self-loop edges. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + Graph + + Examples + -------- + >>> z = [10 for i in range(100)] + >>> G = nx.expected_degree_graph(z) + + Notes + ----- + The nodes have integer labels corresponding to index of expected degrees + input sequence. + + The complexity of this algorithm is $\mathcal{O}(n+m)$ where $n$ is the + number of nodes and $m$ is the expected number of edges. + + The model in [1]_ includes the possibility of self-loop edges. + Set selfloops=False to produce a graph without self loops. + + For finite graphs this model doesn't produce exactly the given + expected degree sequence. Instead the expected degrees are as + follows. + + For the case without self loops (selfloops=False), + + .. math:: + + E[deg(u)] = \sum_{v \ne u} p_{uv} + = w_u \left( 1 - \frac{w_u}{\sum_k w_k} \right) . + + + NetworkX uses the standard convention that a self-loop edge counts 2 + in the degree of a node, so with self loops (selfloops=True), + + .. math:: + + E[deg(u)] = \sum_{v \ne u} p_{uv} + 2 p_{uu} + = w_u \left( 1 + \frac{w_u}{\sum_k w_k} \right) . + + References + ---------- + .. [1] Fan Chung and L. Lu, Connected components in random graphs with + given expected degree sequences, Ann. Combinatorics, 6, + pp. 125-145, 2002. + .. [2] Joel Miller and Aric Hagberg, + Efficient generation of networks with given expected degrees, + in Algorithms and Models for the Web-Graph (WAW 2011), + Alan Frieze, Paul Horn, and Paweł Prałat (Eds), LNCS 6732, + pp. 115-126, 2011. + """ + n = len(w) + G = nx.empty_graph(n) + + # If there are no nodes are no edges in the graph, return the empty graph. + if n == 0 or max(w) == 0: + return G + + rho = 1 / sum(w) + # Sort the weights in decreasing order. The original order of the + # weights dictates the order of the (integer) node labels, so we + # need to remember the permutation applied in the sorting. + order = sorted(enumerate(w), key=itemgetter(1), reverse=True) + mapping = {c: u for c, (u, v) in enumerate(order)} + seq = [v for u, v in order] + last = n + if not selfloops: + last -= 1 + for u in range(last): + v = u + if not selfloops: + v += 1 + factor = seq[u] * rho + p = min(seq[v] * factor, 1) + while v < n and p > 0: + if p != 1: + r = seed.random() + v += math.floor(math.log(r, 1 - p)) + if v < n: + q = min(seq[v] * factor, 1) + if seed.random() < q / p: + G.add_edge(mapping[u], mapping[v]) + v += 1 + p = q + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def havel_hakimi_graph(deg_sequence, create_using=None): + """Returns a simple graph with given degree sequence constructed + using the Havel-Hakimi algorithm. + + Parameters + ---------- + deg_sequence: list of integers + Each integer corresponds to the degree of a node (need not be sorted). + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Directed graphs are not allowed. + + Raises + ------ + NetworkXException + For a non-graphical degree sequence (i.e. one + not realizable by some simple graph). + + Notes + ----- + The Havel-Hakimi algorithm constructs a simple graph by + successively connecting the node of highest degree to other nodes + of highest degree, resorting remaining nodes by degree, and + repeating the process. The resulting graph has a high + degree-associativity. Nodes are labeled 1,.., len(deg_sequence), + corresponding to their position in deg_sequence. + + The basic algorithm is from Hakimi [1]_ and was generalized by + Kleitman and Wang [2]_. + + References + ---------- + .. [1] Hakimi S., On Realizability of a Set of Integers as + Degrees of the Vertices of a Linear Graph. I, + Journal of SIAM, 10(3), pp. 496-506 (1962) + .. [2] Kleitman D.J. and Wang D.L. + Algorithms for Constructing Graphs and Digraphs with Given Valences + and Factors Discrete Mathematics, 6(1), pp. 79-88 (1973) + """ + if not nx.is_graphical(deg_sequence): + raise nx.NetworkXError("Invalid degree sequence") + + p = len(deg_sequence) + G = nx.empty_graph(p, create_using) + if G.is_directed(): + raise nx.NetworkXError("Directed graphs are not supported") + num_degs = [[] for i in range(p)] + dmax, dsum, n = 0, 0, 0 + for d in deg_sequence: + # Process only the non-zero integers + if d > 0: + num_degs[d].append(n) + dmax, dsum, n = max(dmax, d), dsum + d, n + 1 + # Return graph if no edges + if n == 0: + return G + + modstubs = [(0, 0)] * (dmax + 1) + # Successively reduce degree sequence by removing the maximum degree + while n > 0: + # Retrieve the maximum degree in the sequence + while len(num_degs[dmax]) == 0: + dmax -= 1 + # If there are not enough stubs to connect to, then the sequence is + # not graphical + if dmax > n - 1: + raise nx.NetworkXError("Non-graphical integer sequence") + + # Remove largest stub in list + source = num_degs[dmax].pop() + n -= 1 + # Reduce the next dmax largest stubs + mslen = 0 + k = dmax + for i in range(dmax): + while len(num_degs[k]) == 0: + k -= 1 + target = num_degs[k].pop() + G.add_edge(source, target) + n -= 1 + if k > 1: + modstubs[mslen] = (k - 1, target) + mslen += 1 + # Add back to the list any nonzero stubs that were removed + for i in range(mslen): + (stubval, stubtarget) = modstubs[i] + num_degs[stubval].append(stubtarget) + n += 1 + + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def directed_havel_hakimi_graph(in_deg_sequence, out_deg_sequence, create_using=None): + """Returns a directed graph with the given degree sequences. + + Parameters + ---------- + in_deg_sequence : list of integers + Each list entry corresponds to the in-degree of a node. + out_deg_sequence : list of integers + Each list entry corresponds to the out-degree of a node. + create_using : NetworkX graph constructor, optional (default DiGraph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : DiGraph + A graph with the specified degree sequences. + Nodes are labeled starting at 0 with an index + corresponding to the position in deg_sequence + + Raises + ------ + NetworkXError + If the degree sequences are not digraphical. + + See Also + -------- + configuration_model + + Notes + ----- + Algorithm as described by Kleitman and Wang [1]_. + + References + ---------- + .. [1] D.J. Kleitman and D.L. Wang + Algorithms for Constructing Graphs and Digraphs with Given Valences + and Factors Discrete Mathematics, 6(1), pp. 79-88 (1973) + """ + in_deg_sequence = nx.utils.make_list_of_ints(in_deg_sequence) + out_deg_sequence = nx.utils.make_list_of_ints(out_deg_sequence) + + # Process the sequences and form two heaps to store degree pairs with + # either zero or nonzero out degrees + sumin, sumout = 0, 0 + nin, nout = len(in_deg_sequence), len(out_deg_sequence) + maxn = max(nin, nout) + G = nx.empty_graph(maxn, create_using, default=nx.DiGraph) + if maxn == 0: + return G + maxin = 0 + stubheap, zeroheap = [], [] + for n in range(maxn): + in_deg, out_deg = 0, 0 + if n < nout: + out_deg = out_deg_sequence[n] + if n < nin: + in_deg = in_deg_sequence[n] + if in_deg < 0 or out_deg < 0: + raise nx.NetworkXError( + "Invalid degree sequences. Sequence values must be positive." + ) + sumin, sumout, maxin = sumin + in_deg, sumout + out_deg, max(maxin, in_deg) + if in_deg > 0: + stubheap.append((-1 * out_deg, -1 * in_deg, n)) + elif out_deg > 0: + zeroheap.append((-1 * out_deg, n)) + if sumin != sumout: + raise nx.NetworkXError( + "Invalid degree sequences. Sequences must have equal sums." + ) + heapq.heapify(stubheap) + heapq.heapify(zeroheap) + + modstubs = [(0, 0, 0)] * (maxin + 1) + # Successively reduce degree sequence by removing the maximum + while stubheap: + # Remove first value in the sequence with a non-zero in degree + (freeout, freein, target) = heapq.heappop(stubheap) + freein *= -1 + if freein > len(stubheap) + len(zeroheap): + raise nx.NetworkXError("Non-digraphical integer sequence") + + # Attach arcs from the nodes with the most stubs + mslen = 0 + for i in range(freein): + if zeroheap and (not stubheap or stubheap[0][0] > zeroheap[0][0]): + (stubout, stubsource) = heapq.heappop(zeroheap) + stubin = 0 + else: + (stubout, stubin, stubsource) = heapq.heappop(stubheap) + if stubout == 0: + raise nx.NetworkXError("Non-digraphical integer sequence") + G.add_edge(stubsource, target) + # Check if source is now totally connected + if stubout + 1 < 0 or stubin < 0: + modstubs[mslen] = (stubout + 1, stubin, stubsource) + mslen += 1 + + # Add the nodes back to the heaps that still have available stubs + for i in range(mslen): + stub = modstubs[i] + if stub[1] < 0: + heapq.heappush(stubheap, stub) + else: + heapq.heappush(zeroheap, (stub[0], stub[2])) + if freeout < 0: + heapq.heappush(zeroheap, (freeout, target)) + + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def degree_sequence_tree(deg_sequence, create_using=None): + """Make a tree for the given degree sequence. + + A tree has #nodes-#edges=1 so + the degree sequence must have + len(deg_sequence)-sum(deg_sequence)/2=1 + """ + # The sum of the degree sequence must be even (for any undirected graph). + degree_sum = sum(deg_sequence) + if degree_sum % 2 != 0: + msg = "Invalid degree sequence: sum of degrees must be even, not odd" + raise nx.NetworkXError(msg) + if len(deg_sequence) - degree_sum // 2 != 1: + msg = ( + "Invalid degree sequence: tree must have number of nodes equal" + " to one less than the number of edges" + ) + raise nx.NetworkXError(msg) + G = nx.empty_graph(0, create_using) + if G.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + # Sort all degrees greater than 1 in decreasing order. + # + # TODO Does this need to be sorted in reverse order? + deg = sorted((s for s in deg_sequence if s > 1), reverse=True) + + # make path graph as backbone + n = len(deg) + 2 + nx.add_path(G, range(n)) + last = n + + # add the leaves + for source in range(1, n - 1): + nedges = deg.pop() - 2 + for target in range(last, last + nedges): + G.add_edge(source, target) + last += nedges + + # in case we added one too many + if len(G) > len(deg_sequence): + G.remove_node(0) + return G + + +@py_random_state(1) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_degree_sequence_graph(sequence, seed=None, tries=10): + r"""Returns a simple random graph with the given degree sequence. + + If the maximum degree $d_m$ in the sequence is $O(m^{1/4})$ then the + algorithm produces almost uniform random graphs in $O(m d_m)$ time + where $m$ is the number of edges. + + Parameters + ---------- + sequence : list of integers + Sequence of degrees + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + tries : int, optional + Maximum number of tries to create a graph + + Returns + ------- + G : Graph + A graph with the specified degree sequence. + Nodes are labeled starting at 0 with an index + corresponding to the position in the sequence. + + Raises + ------ + NetworkXUnfeasible + If the degree sequence is not graphical. + NetworkXError + If a graph is not produced in specified number of tries + + See Also + -------- + is_graphical, configuration_model + + Notes + ----- + The generator algorithm [1]_ is not guaranteed to produce a graph. + + References + ---------- + .. [1] Moshen Bayati, Jeong Han Kim, and Amin Saberi, + A sequential algorithm for generating random graphs. + Algorithmica, Volume 58, Number 4, 860-910, + DOI: 10.1007/s00453-009-9340-1 + + Examples + -------- + >>> sequence = [1, 2, 2, 3] + >>> G = nx.random_degree_sequence_graph(sequence, seed=42) + >>> sorted(d for n, d in G.degree()) + [1, 2, 2, 3] + """ + DSRG = DegreeSequenceRandomGraph(sequence, seed) + for try_n in range(tries): + try: + return DSRG.generate() + except nx.NetworkXUnfeasible: + pass + raise nx.NetworkXError(f"failed to generate graph in {tries} tries") + + +class DegreeSequenceRandomGraph: + # class to generate random graphs with a given degree sequence + # use random_degree_sequence_graph() + def __init__(self, degree, rng): + if not nx.is_graphical(degree): + raise nx.NetworkXUnfeasible("degree sequence is not graphical") + self.rng = rng + self.degree = list(degree) + # node labels are integers 0,...,n-1 + self.m = sum(self.degree) / 2.0 # number of edges + try: + self.dmax = max(self.degree) # maximum degree + except ValueError: + self.dmax = 0 + + def generate(self): + # remaining_degree is mapping from int->remaining degree + self.remaining_degree = dict(enumerate(self.degree)) + # add all nodes to make sure we get isolated nodes + self.graph = nx.Graph() + self.graph.add_nodes_from(self.remaining_degree) + # remove zero degree nodes + for n, d in list(self.remaining_degree.items()): + if d == 0: + del self.remaining_degree[n] + if len(self.remaining_degree) > 0: + # build graph in three phases according to how many unmatched edges + self.phase1() + self.phase2() + self.phase3() + return self.graph + + def update_remaining(self, u, v, aux_graph=None): + # decrement remaining nodes, modify auxiliary graph if in phase3 + if aux_graph is not None: + # remove edges from auxiliary graph + aux_graph.remove_edge(u, v) + if self.remaining_degree[u] == 1: + del self.remaining_degree[u] + if aux_graph is not None: + aux_graph.remove_node(u) + else: + self.remaining_degree[u] -= 1 + if self.remaining_degree[v] == 1: + del self.remaining_degree[v] + if aux_graph is not None: + aux_graph.remove_node(v) + else: + self.remaining_degree[v] -= 1 + + def p(self, u, v): + # degree probability + return 1 - self.degree[u] * self.degree[v] / (4.0 * self.m) + + def q(self, u, v): + # remaining degree probability + norm = max(self.remaining_degree.values()) ** 2 + return self.remaining_degree[u] * self.remaining_degree[v] / norm + + def suitable_edge(self): + """Returns True if and only if an arbitrary remaining node can + potentially be joined with some other remaining node. + + """ + nodes = iter(self.remaining_degree) + u = next(nodes) + return any(v not in self.graph[u] for v in nodes) + + def phase1(self): + # choose node pairs from (degree) weighted distribution + rem_deg = self.remaining_degree + while sum(rem_deg.values()) >= 2 * self.dmax**2: + u, v = sorted(random_weighted_sample(rem_deg, 2, self.rng)) + if self.graph.has_edge(u, v): + continue + if self.rng.random() < self.p(u, v): # accept edge + self.graph.add_edge(u, v) + self.update_remaining(u, v) + + def phase2(self): + # choose remaining nodes uniformly at random and use rejection sampling + remaining_deg = self.remaining_degree + rng = self.rng + while len(remaining_deg) >= 2 * self.dmax: + while True: + u, v = sorted(rng.sample(list(remaining_deg.keys()), 2)) + if self.graph.has_edge(u, v): + continue + if rng.random() < self.q(u, v): + break + if rng.random() < self.p(u, v): # accept edge + self.graph.add_edge(u, v) + self.update_remaining(u, v) + + def phase3(self): + # build potential remaining edges and choose with rejection sampling + potential_edges = combinations(self.remaining_degree, 2) + # build auxiliary graph of potential edges not already in graph + H = nx.Graph( + [(u, v) for (u, v) in potential_edges if not self.graph.has_edge(u, v)] + ) + rng = self.rng + while self.remaining_degree: + if not self.suitable_edge(): + raise nx.NetworkXUnfeasible("no suitable edges left") + while True: + u, v = sorted(rng.choice(list(H.edges()))) + if rng.random() < self.q(u, v): + break + if rng.random() < self.p(u, v): # accept edge + self.graph.add_edge(u, v) + self.update_remaining(u, v, aux_graph=H) diff --git a/venv/lib/python3.10/site-packages/networkx/generators/directed.py b/venv/lib/python3.10/site-packages/networkx/generators/directed.py new file mode 100644 index 0000000000000000000000000000000000000000..4548726b9fe2cbcb1210750db36a0a5e460f96c1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/directed.py @@ -0,0 +1,501 @@ +""" +Generators for some directed graphs, including growing network (GN) graphs and +scale-free graphs. + +""" + +import numbers +from collections import Counter + +import networkx as nx +from networkx.generators.classic import empty_graph +from networkx.utils import discrete_sequence, py_random_state, weighted_choice + +__all__ = [ + "gn_graph", + "gnc_graph", + "gnr_graph", + "random_k_out_graph", + "scale_free_graph", +] + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def gn_graph(n, kernel=None, create_using=None, seed=None): + """Returns the growing network (GN) digraph with `n` nodes. + + The GN graph is built by adding nodes one at a time with a link to one + previously added node. The target node for the link is chosen with + probability based on degree. The default attachment kernel is a linear + function of the degree of a node. + + The graph is always a (directed) tree. + + Parameters + ---------- + n : int + The number of nodes for the generated graph. + kernel : function + The attachment kernel. + create_using : NetworkX graph constructor, optional (default DiGraph) + Graph type to create. If graph instance, then cleared before populated. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Examples + -------- + To create the undirected GN graph, use the :meth:`~DiGraph.to_directed` + method:: + + >>> D = nx.gn_graph(10) # the GN graph + >>> G = D.to_undirected() # the undirected version + + To specify an attachment kernel, use the `kernel` keyword argument:: + + >>> D = nx.gn_graph(10, kernel=lambda x: x**1.5) # A_k = k^1.5 + + References + ---------- + .. [1] P. L. Krapivsky and S. Redner, + Organization of Growing Random Networks, + Phys. Rev. E, 63, 066123, 2001. + """ + G = empty_graph(1, create_using, default=nx.DiGraph) + if not G.is_directed(): + raise nx.NetworkXError("create_using must indicate a Directed Graph") + + if kernel is None: + + def kernel(x): + return x + + if n == 1: + return G + + G.add_edge(1, 0) # get started + ds = [1, 1] # degree sequence + + for source in range(2, n): + # compute distribution from kernel and degree + dist = [kernel(d) for d in ds] + # choose target from discrete distribution + target = discrete_sequence(1, distribution=dist, seed=seed)[0] + G.add_edge(source, target) + ds.append(1) # the source has only one link (degree one) + ds[target] += 1 # add one to the target link degree + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def gnr_graph(n, p, create_using=None, seed=None): + """Returns the growing network with redirection (GNR) digraph with `n` + nodes and redirection probability `p`. + + The GNR graph is built by adding nodes one at a time with a link to one + previously added node. The previous target node is chosen uniformly at + random. With probability `p` the link is instead "redirected" to the + successor node of the target. + + The graph is always a (directed) tree. + + Parameters + ---------- + n : int + The number of nodes for the generated graph. + p : float + The redirection probability. + create_using : NetworkX graph constructor, optional (default DiGraph) + Graph type to create. If graph instance, then cleared before populated. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Examples + -------- + To create the undirected GNR graph, use the :meth:`~DiGraph.to_directed` + method:: + + >>> D = nx.gnr_graph(10, 0.5) # the GNR graph + >>> G = D.to_undirected() # the undirected version + + References + ---------- + .. [1] P. L. Krapivsky and S. Redner, + Organization of Growing Random Networks, + Phys. Rev. E, 63, 066123, 2001. + """ + G = empty_graph(1, create_using, default=nx.DiGraph) + if not G.is_directed(): + raise nx.NetworkXError("create_using must indicate a Directed Graph") + + if n == 1: + return G + + for source in range(1, n): + target = seed.randrange(0, source) + if seed.random() < p and target != 0: + target = next(G.successors(target)) + G.add_edge(source, target) + return G + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def gnc_graph(n, create_using=None, seed=None): + """Returns the growing network with copying (GNC) digraph with `n` nodes. + + The GNC graph is built by adding nodes one at a time with a link to one + previously added node (chosen uniformly at random) and to all of that + node's successors. + + Parameters + ---------- + n : int + The number of nodes for the generated graph. + create_using : NetworkX graph constructor, optional (default DiGraph) + Graph type to create. If graph instance, then cleared before populated. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + References + ---------- + .. [1] P. L. Krapivsky and S. Redner, + Network Growth by Copying, + Phys. Rev. E, 71, 036118, 2005k.}, + """ + G = empty_graph(1, create_using, default=nx.DiGraph) + if not G.is_directed(): + raise nx.NetworkXError("create_using must indicate a Directed Graph") + + if n == 1: + return G + + for source in range(1, n): + target = seed.randrange(0, source) + for succ in G.successors(target): + G.add_edge(source, succ) + G.add_edge(source, target) + return G + + +@py_random_state(6) +@nx._dispatchable(graphs=None, returns_graph=True) +def scale_free_graph( + n, + alpha=0.41, + beta=0.54, + gamma=0.05, + delta_in=0.2, + delta_out=0, + seed=None, + initial_graph=None, +): + """Returns a scale-free directed graph. + + Parameters + ---------- + n : integer + Number of nodes in graph + alpha : float + Probability for adding a new node connected to an existing node + chosen randomly according to the in-degree distribution. + beta : float + Probability for adding an edge between two existing nodes. + One existing node is chosen randomly according the in-degree + distribution and the other chosen randomly according to the out-degree + distribution. + gamma : float + Probability for adding a new node connected to an existing node + chosen randomly according to the out-degree distribution. + delta_in : float + Bias for choosing nodes from in-degree distribution. + delta_out : float + Bias for choosing nodes from out-degree distribution. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + initial_graph : MultiDiGraph instance, optional + Build the scale-free graph starting from this initial MultiDiGraph, + if provided. + + Returns + ------- + MultiDiGraph + + Examples + -------- + Create a scale-free graph on one hundred nodes:: + + >>> G = nx.scale_free_graph(100) + + Notes + ----- + The sum of `alpha`, `beta`, and `gamma` must be 1. + + References + ---------- + .. [1] B. Bollobás, C. Borgs, J. Chayes, and O. Riordan, + Directed scale-free graphs, + Proceedings of the fourteenth annual ACM-SIAM Symposium on + Discrete Algorithms, 132--139, 2003. + """ + + def _choose_node(candidates, node_list, delta): + if delta > 0: + bias_sum = len(node_list) * delta + p_delta = bias_sum / (bias_sum + len(candidates)) + if seed.random() < p_delta: + return seed.choice(node_list) + return seed.choice(candidates) + + if initial_graph is not None and hasattr(initial_graph, "_adj"): + if not isinstance(initial_graph, nx.MultiDiGraph): + raise nx.NetworkXError("initial_graph must be a MultiDiGraph.") + G = initial_graph + else: + # Start with 3-cycle + G = nx.MultiDiGraph([(0, 1), (1, 2), (2, 0)]) + + if alpha <= 0: + raise ValueError("alpha must be > 0.") + if beta <= 0: + raise ValueError("beta must be > 0.") + if gamma <= 0: + raise ValueError("gamma must be > 0.") + + if abs(alpha + beta + gamma - 1.0) >= 1e-9: + raise ValueError("alpha+beta+gamma must equal 1.") + + if delta_in < 0: + raise ValueError("delta_in must be >= 0.") + + if delta_out < 0: + raise ValueError("delta_out must be >= 0.") + + # pre-populate degree states + vs = sum((count * [idx] for idx, count in G.out_degree()), []) + ws = sum((count * [idx] for idx, count in G.in_degree()), []) + + # pre-populate node state + node_list = list(G.nodes()) + + # see if there already are number-based nodes + numeric_nodes = [n for n in node_list if isinstance(n, numbers.Number)] + if len(numeric_nodes) > 0: + # set cursor for new nodes appropriately + cursor = max(int(n.real) for n in numeric_nodes) + 1 + else: + # or start at zero + cursor = 0 + + while len(G) < n: + r = seed.random() + + # random choice in alpha,beta,gamma ranges + if r < alpha: + # alpha + # add new node v + v = cursor + cursor += 1 + # also add to node state + node_list.append(v) + # choose w according to in-degree and delta_in + w = _choose_node(ws, node_list, delta_in) + + elif r < alpha + beta: + # beta + # choose v according to out-degree and delta_out + v = _choose_node(vs, node_list, delta_out) + # choose w according to in-degree and delta_in + w = _choose_node(ws, node_list, delta_in) + + else: + # gamma + # choose v according to out-degree and delta_out + v = _choose_node(vs, node_list, delta_out) + # add new node w + w = cursor + cursor += 1 + # also add to node state + node_list.append(w) + + # add edge to graph + G.add_edge(v, w) + + # update degree states + vs.append(v) + ws.append(w) + + return G + + +@py_random_state(4) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_uniform_k_out_graph(n, k, self_loops=True, with_replacement=True, seed=None): + """Returns a random `k`-out graph with uniform attachment. + + A random `k`-out graph with uniform attachment is a multidigraph + generated by the following algorithm. For each node *u*, choose + `k` nodes *v* uniformly at random (with replacement). Add a + directed edge joining *u* to *v*. + + Parameters + ---------- + n : int + The number of nodes in the returned graph. + + k : int + The out-degree of each node in the returned graph. + + self_loops : bool + If True, self-loops are allowed when generating the graph. + + with_replacement : bool + If True, neighbors are chosen with replacement and the + returned graph will be a directed multigraph. Otherwise, + neighbors are chosen without replacement and the returned graph + will be a directed graph. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + NetworkX graph + A `k`-out-regular directed graph generated according to the + above algorithm. It will be a multigraph if and only if + `with_replacement` is True. + + Raises + ------ + ValueError + If `with_replacement` is False and `k` is greater than + `n`. + + See also + -------- + random_k_out_graph + + Notes + ----- + The return digraph or multidigraph may not be strongly connected, or + even weakly connected. + + If `with_replacement` is True, this function is similar to + :func:`random_k_out_graph`, if that function had parameter `alpha` + set to positive infinity. + + """ + if with_replacement: + create_using = nx.MultiDiGraph() + + def sample(v, nodes): + if not self_loops: + nodes = nodes - {v} + return (seed.choice(list(nodes)) for i in range(k)) + + else: + create_using = nx.DiGraph() + + def sample(v, nodes): + if not self_loops: + nodes = nodes - {v} + return seed.sample(list(nodes), k) + + G = nx.empty_graph(n, create_using) + nodes = set(G) + for u in G: + G.add_edges_from((u, v) for v in sample(u, nodes)) + return G + + +@py_random_state(4) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_k_out_graph(n, k, alpha, self_loops=True, seed=None): + """Returns a random `k`-out graph with preferential attachment. + + A random `k`-out graph with preferential attachment is a + multidigraph generated by the following algorithm. + + 1. Begin with an empty digraph, and initially set each node to have + weight `alpha`. + 2. Choose a node `u` with out-degree less than `k` uniformly at + random. + 3. Choose a node `v` from with probability proportional to its + weight. + 4. Add a directed edge from `u` to `v`, and increase the weight + of `v` by one. + 5. If each node has out-degree `k`, halt, otherwise repeat from + step 2. + + For more information on this model of random graph, see [1]. + + Parameters + ---------- + n : int + The number of nodes in the returned graph. + + k : int + The out-degree of each node in the returned graph. + + alpha : float + A positive :class:`float` representing the initial weight of + each vertex. A higher number means that in step 3 above, nodes + will be chosen more like a true uniformly random sample, and a + lower number means that nodes are more likely to be chosen as + their in-degree increases. If this parameter is not positive, a + :exc:`ValueError` is raised. + + self_loops : bool + If True, self-loops are allowed when generating the graph. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + :class:`~networkx.classes.MultiDiGraph` + A `k`-out-regular multidigraph generated according to the above + algorithm. + + Raises + ------ + ValueError + If `alpha` is not positive. + + Notes + ----- + The returned multidigraph may not be strongly connected, or even + weakly connected. + + References + ---------- + [1]: Peterson, Nicholas R., and Boris Pittel. + "Distance between two random `k`-out digraphs, with and without + preferential attachment." + arXiv preprint arXiv:1311.5961 (2013). + + + """ + if alpha < 0: + raise ValueError("alpha must be positive") + G = nx.empty_graph(n, create_using=nx.MultiDiGraph) + weights = Counter({v: alpha for v in G}) + for i in range(k * n): + u = seed.choice([v for v, d in G.out_degree() if d < k]) + # If self-loops are not allowed, make the source node `u` have + # weight zero. + if not self_loops: + adjustment = Counter({u: weights[u]}) + else: + adjustment = Counter() + v = weighted_choice(weights - adjustment, seed=seed) + G.add_edge(u, v) + weights[v] += 1 + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/duplication.py b/venv/lib/python3.10/site-packages/networkx/generators/duplication.py new file mode 100644 index 0000000000000000000000000000000000000000..3c3ade63f58237eeb927ff631b25f025d7d83fc1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/duplication.py @@ -0,0 +1,174 @@ +"""Functions for generating graphs based on the "duplication" method. + +These graph generators start with a small initial graph then duplicate +nodes and (partially) duplicate their edges. These functions are +generally inspired by biological networks. + +""" + +import networkx as nx +from networkx.exception import NetworkXError +from networkx.utils import py_random_state +from networkx.utils.misc import check_create_using + +__all__ = ["partial_duplication_graph", "duplication_divergence_graph"] + + +@py_random_state(4) +@nx._dispatchable(graphs=None, returns_graph=True) +def partial_duplication_graph(N, n, p, q, seed=None, *, create_using=None): + """Returns a random graph using the partial duplication model. + + Parameters + ---------- + N : int + The total number of nodes in the final graph. + + n : int + The number of nodes in the initial clique. + + p : float + The probability of joining each neighbor of a node to the + duplicate node. Must be a number in the between zero and one, + inclusive. + + q : float + The probability of joining the source node to the duplicate + node. Must be a number in the between zero and one, inclusive. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Notes + ----- + A graph of nodes is grown by creating a fully connected graph + of size `n`. The following procedure is then repeated until + a total of `N` nodes have been reached. + + 1. A random node, *u*, is picked and a new node, *v*, is created. + 2. For each neighbor of *u* an edge from the neighbor to *v* is created + with probability `p`. + 3. An edge from *u* to *v* is created with probability `q`. + + This algorithm appears in [1]. + + This implementation allows the possibility of generating + disconnected graphs. + + References + ---------- + .. [1] Knudsen Michael, and Carsten Wiuf. "A Markov chain approach to + randomly grown graphs." Journal of Applied Mathematics 2008. + + + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if p < 0 or p > 1 or q < 0 or q > 1: + msg = "partial duplication graph must have 0 <= p, q <= 1." + raise NetworkXError(msg) + if n > N: + raise NetworkXError("partial duplication graph must have n <= N.") + + G = nx.complete_graph(n, create_using) + for new_node in range(n, N): + # Pick a random vertex, u, already in the graph. + src_node = seed.randint(0, new_node - 1) + + # Add a new vertex, v, to the graph. + G.add_node(new_node) + + # For each neighbor of u... + for nbr_node in list(nx.all_neighbors(G, src_node)): + # Add the neighbor to v with probability p. + if seed.random() < p: + G.add_edge(new_node, nbr_node) + + # Join v and u with probability q. + if seed.random() < q: + G.add_edge(new_node, src_node) + return G + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def duplication_divergence_graph(n, p, seed=None, *, create_using=None): + """Returns an undirected graph using the duplication-divergence model. + + A graph of `n` nodes is created by duplicating the initial nodes + and retaining edges incident to the original nodes with a retention + probability `p`. + + Parameters + ---------- + n : int + The desired number of nodes in the graph. + p : float + The probability for retaining the edge of the replicated node. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Returns + ------- + G : Graph + + Raises + ------ + NetworkXError + If `p` is not a valid probability. + If `n` is less than 2. + + Notes + ----- + This algorithm appears in [1]. + + This implementation disallows the possibility of generating + disconnected graphs. + + References + ---------- + .. [1] I. Ispolatov, P. L. Krapivsky, A. Yuryev, + "Duplication-divergence model of protein interaction network", + Phys. Rev. E, 71, 061911, 2005. + + """ + if p > 1 or p < 0: + msg = f"NetworkXError p={p} is not in [0,1]." + raise nx.NetworkXError(msg) + if n < 2: + msg = "n must be greater than or equal to 2" + raise nx.NetworkXError(msg) + + create_using = check_create_using(create_using, directed=False, multigraph=False) + G = nx.empty_graph(create_using=create_using) + + # Initialize the graph with two connected nodes. + G.add_edge(0, 1) + i = 2 + while i < n: + # Choose a random node from current graph to duplicate. + random_node = seed.choice(list(G)) + # Make the replica. + G.add_node(i) + # flag indicates whether at least one edge is connected on the replica. + flag = False + for nbr in G.neighbors(random_node): + if seed.random() < p: + # Link retention step. + G.add_edge(i, nbr) + flag = True + if not flag: + # Delete replica if no edges retained. + G.remove_node(i) + else: + # Successful duplication. + i += 1 + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/ego.py b/venv/lib/python3.10/site-packages/networkx/generators/ego.py new file mode 100644 index 0000000000000000000000000000000000000000..1c705430d8b52cf47d1e1b681397c929221e58a0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/ego.py @@ -0,0 +1,66 @@ +""" +Ego graph. +""" + +__all__ = ["ego_graph"] + +import networkx as nx + + +@nx._dispatchable(preserve_all_attrs=True, returns_graph=True) +def ego_graph(G, n, radius=1, center=True, undirected=False, distance=None): + """Returns induced subgraph of neighbors centered at node n within + a given radius. + + Parameters + ---------- + G : graph + A NetworkX Graph or DiGraph + + n : node + A single node + + radius : number, optional + Include all neighbors of distance<=radius from n. + + center : bool, optional + If False, do not include center node in graph + + undirected : bool, optional + If True use both in- and out-neighbors of directed graphs. + + distance : key, optional + Use specified edge data key as distance. For example, setting + distance='weight' will use the edge weight to measure the + distance from the node n. + + Notes + ----- + For directed graphs D this produces the "out" neighborhood + or successors. If you want the neighborhood of predecessors + first reverse the graph with D.reverse(). If you want both + directions use the keyword argument undirected=True. + + Node, edge, and graph attributes are copied to the returned subgraph. + """ + if undirected: + if distance is not None: + sp, _ = nx.single_source_dijkstra( + G.to_undirected(), n, cutoff=radius, weight=distance + ) + else: + sp = dict( + nx.single_source_shortest_path_length( + G.to_undirected(), n, cutoff=radius + ) + ) + else: + if distance is not None: + sp, _ = nx.single_source_dijkstra(G, n, cutoff=radius, weight=distance) + else: + sp = dict(nx.single_source_shortest_path_length(G, n, cutoff=radius)) + + H = G.subgraph(sp).copy() + if not center: + H.remove_node(n) + return H diff --git a/venv/lib/python3.10/site-packages/networkx/generators/expanders.py b/venv/lib/python3.10/site-packages/networkx/generators/expanders.py new file mode 100644 index 0000000000000000000000000000000000000000..befdb0e4b0f3579a272491d8ea6b970716c57f6e --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/expanders.py @@ -0,0 +1,474 @@ +"""Provides explicit constructions of expander graphs.""" + +import itertools + +import networkx as nx + +__all__ = [ + "margulis_gabber_galil_graph", + "chordal_cycle_graph", + "paley_graph", + "maybe_regular_expander", + "is_regular_expander", + "random_regular_expander_graph", +] + + +# Other discrete torus expanders can be constructed by using the following edge +# sets. For more information, see Chapter 4, "Expander Graphs", in +# "Pseudorandomness", by Salil Vadhan. +# +# For a directed expander, add edges from (x, y) to: +# +# (x, y), +# ((x + 1) % n, y), +# (x, (y + 1) % n), +# (x, (x + y) % n), +# (-y % n, x) +# +# For an undirected expander, add the reverse edges. +# +# Also appearing in the paper of Gabber and Galil: +# +# (x, y), +# (x, (x + y) % n), +# (x, (x + y + 1) % n), +# ((x + y) % n, y), +# ((x + y + 1) % n, y) +# +# and: +# +# (x, y), +# ((x + 2*y) % n, y), +# ((x + (2*y + 1)) % n, y), +# ((x + (2*y + 2)) % n, y), +# (x, (y + 2*x) % n), +# (x, (y + (2*x + 1)) % n), +# (x, (y + (2*x + 2)) % n), +# +@nx._dispatchable(graphs=None, returns_graph=True) +def margulis_gabber_galil_graph(n, create_using=None): + r"""Returns the Margulis-Gabber-Galil undirected MultiGraph on `n^2` nodes. + + The undirected MultiGraph is regular with degree `8`. Nodes are integer + pairs. The second-largest eigenvalue of the adjacency matrix of the graph + is at most `5 \sqrt{2}`, regardless of `n`. + + Parameters + ---------- + n : int + Determines the number of nodes in the graph: `n^2`. + create_using : NetworkX graph constructor, optional (default MultiGraph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : graph + The constructed undirected multigraph. + + Raises + ------ + NetworkXError + If the graph is directed or not a multigraph. + + """ + G = nx.empty_graph(0, create_using, default=nx.MultiGraph) + if G.is_directed() or not G.is_multigraph(): + msg = "`create_using` must be an undirected multigraph." + raise nx.NetworkXError(msg) + + for x, y in itertools.product(range(n), repeat=2): + for u, v in ( + ((x + 2 * y) % n, y), + ((x + (2 * y + 1)) % n, y), + (x, (y + 2 * x) % n), + (x, (y + (2 * x + 1)) % n), + ): + G.add_edge((x, y), (u, v)) + G.graph["name"] = f"margulis_gabber_galil_graph({n})" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def chordal_cycle_graph(p, create_using=None): + """Returns the chordal cycle graph on `p` nodes. + + The returned graph is a cycle graph on `p` nodes with chords joining each + vertex `x` to its inverse modulo `p`. This graph is a (mildly explicit) + 3-regular expander [1]_. + + `p` *must* be a prime number. + + Parameters + ---------- + p : a prime number + + The number of vertices in the graph. This also indicates where the + chordal edges in the cycle will be created. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : graph + The constructed undirected multigraph. + + Raises + ------ + NetworkXError + + If `create_using` indicates directed or not a multigraph. + + References + ---------- + + .. [1] Theorem 4.4.2 in A. Lubotzky. "Discrete groups, expanding graphs and + invariant measures", volume 125 of Progress in Mathematics. + Birkhäuser Verlag, Basel, 1994. + + """ + G = nx.empty_graph(0, create_using, default=nx.MultiGraph) + if G.is_directed() or not G.is_multigraph(): + msg = "`create_using` must be an undirected multigraph." + raise nx.NetworkXError(msg) + + for x in range(p): + left = (x - 1) % p + right = (x + 1) % p + # Here we apply Fermat's Little Theorem to compute the multiplicative + # inverse of x in Z/pZ. By Fermat's Little Theorem, + # + # x^p = x (mod p) + # + # Therefore, + # + # x * x^(p - 2) = 1 (mod p) + # + # The number 0 is a special case: we just let its inverse be itself. + chord = pow(x, p - 2, p) if x > 0 else 0 + for y in (left, right, chord): + G.add_edge(x, y) + G.graph["name"] = f"chordal_cycle_graph({p})" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def paley_graph(p, create_using=None): + r"""Returns the Paley $\frac{(p-1)}{2}$ -regular graph on $p$ nodes. + + The returned graph is a graph on $\mathbb{Z}/p\mathbb{Z}$ with edges between $x$ and $y$ + if and only if $x-y$ is a nonzero square in $\mathbb{Z}/p\mathbb{Z}$. + + If $p \equiv 1 \pmod 4$, $-1$ is a square in $\mathbb{Z}/p\mathbb{Z}$ and therefore $x-y$ is a square if and + only if $y-x$ is also a square, i.e the edges in the Paley graph are symmetric. + + If $p \equiv 3 \pmod 4$, $-1$ is not a square in $\mathbb{Z}/p\mathbb{Z}$ and therefore either $x-y$ or $y-x$ + is a square in $\mathbb{Z}/p\mathbb{Z}$ but not both. + + Note that a more general definition of Paley graphs extends this construction + to graphs over $q=p^n$ vertices, by using the finite field $F_q$ instead of $\mathbb{Z}/p\mathbb{Z}$. + This construction requires to compute squares in general finite fields and is + not what is implemented here (i.e `paley_graph(25)` does not return the true + Paley graph associated with $5^2$). + + Parameters + ---------- + p : int, an odd prime number. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : graph + The constructed directed graph. + + Raises + ------ + NetworkXError + If the graph is a multigraph. + + References + ---------- + Chapter 13 in B. Bollobas, Random Graphs. Second edition. + Cambridge Studies in Advanced Mathematics, 73. + Cambridge University Press, Cambridge (2001). + """ + G = nx.empty_graph(0, create_using, default=nx.DiGraph) + if G.is_multigraph(): + msg = "`create_using` cannot be a multigraph." + raise nx.NetworkXError(msg) + + # Compute the squares in Z/pZ. + # Make it a set to uniquify (there are exactly (p-1)/2 squares in Z/pZ + # when is prime). + square_set = {(x**2) % p for x in range(1, p) if (x**2) % p != 0} + + for x in range(p): + for x2 in square_set: + G.add_edge(x, (x + x2) % p) + G.graph["name"] = f"paley({p})" + return G + + +@nx.utils.decorators.np_random_state("seed") +@nx._dispatchable(graphs=None, returns_graph=True) +def maybe_regular_expander(n, d, *, create_using=None, max_tries=100, seed=None): + r"""Utility for creating a random regular expander. + + Returns a random $d$-regular graph on $n$ nodes which is an expander + graph with very good probability. + + Parameters + ---------- + n : int + The number of nodes. + d : int + The degree of each node. + create_using : Graph Instance or Constructor + Indicator of type of graph to return. + If a Graph-type instance, then clear and use it. + If a constructor, call it to create an empty graph. + Use the Graph constructor by default. + max_tries : int. (default: 100) + The number of allowed loops when generating each independent cycle + seed : (default: None) + Seed used to set random number generation state. See :ref`Randomness`. + + Notes + ----- + The nodes are numbered from $0$ to $n - 1$. + + The graph is generated by taking $d / 2$ random independent cycles. + + Joel Friedman proved that in this model the resulting + graph is an expander with probability + $1 - O(n^{-\tau})$ where $\tau = \lceil (\sqrt{d - 1}) / 2 \rceil - 1$. [1]_ + + Examples + -------- + >>> G = nx.maybe_regular_expander(n=200, d=6, seed=8020) + + Returns + ------- + G : graph + The constructed undirected graph. + + Raises + ------ + NetworkXError + If $d % 2 != 0$ as the degree must be even. + If $n - 1$ is less than $ 2d $ as the graph is complete at most. + If max_tries is reached + + See Also + -------- + is_regular_expander + random_regular_expander_graph + + References + ---------- + .. [1] Joel Friedman, + A Proof of Alon’s Second Eigenvalue Conjecture and Related Problems, 2004 + https://arxiv.org/abs/cs/0405020 + + """ + + import numpy as np + + if n < 1: + raise nx.NetworkXError("n must be a positive integer") + + if not (d >= 2): + raise nx.NetworkXError("d must be greater than or equal to 2") + + if not (d % 2 == 0): + raise nx.NetworkXError("d must be even") + + if not (n - 1 >= d): + raise nx.NetworkXError( + f"Need n-1>= d to have room for {d//2} independent cycles with {n} nodes" + ) + + G = nx.empty_graph(n, create_using) + + if n < 2: + return G + + cycles = [] + edges = set() + + # Create d / 2 cycles + for i in range(d // 2): + iterations = max_tries + # Make sure the cycles are independent to have a regular graph + while len(edges) != (i + 1) * n: + iterations -= 1 + # Faster than random.permutation(n) since there are only + # (n-1)! distinct cycles against n! permutations of size n + cycle = seed.permutation(n - 1).tolist() + cycle.append(n - 1) + + new_edges = { + (u, v) + for u, v in nx.utils.pairwise(cycle, cyclic=True) + if (u, v) not in edges and (v, u) not in edges + } + # If the new cycle has no edges in common with previous cycles + # then add it to the list otherwise try again + if len(new_edges) == n: + cycles.append(cycle) + edges.update(new_edges) + + if iterations == 0: + raise nx.NetworkXError("Too many iterations in maybe_regular_expander") + + G.add_edges_from(edges) + + return G + + +@nx.utils.not_implemented_for("directed") +@nx.utils.not_implemented_for("multigraph") +@nx._dispatchable(preserve_edge_attrs={"G": {"weight": 1}}) +def is_regular_expander(G, *, epsilon=0): + r"""Determines whether the graph G is a regular expander. [1]_ + + An expander graph is a sparse graph with strong connectivity properties. + + More precisely, this helper checks whether the graph is a + regular $(n, d, \lambda)$-expander with $\lambda$ close to + the Alon-Boppana bound and given by + $\lambda = 2 \sqrt{d - 1} + \epsilon$. [2]_ + + In the case where $\epsilon = 0$ then if the graph successfully passes the test + it is a Ramanujan graph. [3]_ + + A Ramanujan graph has spectral gap almost as large as possible, which makes them + excellent expanders. + + Parameters + ---------- + G : NetworkX graph + epsilon : int, float, default=0 + + Returns + ------- + bool + Whether the given graph is a regular $(n, d, \lambda)$-expander + where $\lambda = 2 \sqrt{d - 1} + \epsilon$. + + Examples + -------- + >>> G = nx.random_regular_expander_graph(20, 4) + >>> nx.is_regular_expander(G) + True + + See Also + -------- + maybe_regular_expander + random_regular_expander_graph + + References + ---------- + .. [1] Expander graph, https://en.wikipedia.org/wiki/Expander_graph + .. [2] Alon-Boppana bound, https://en.wikipedia.org/wiki/Alon%E2%80%93Boppana_bound + .. [3] Ramanujan graphs, https://en.wikipedia.org/wiki/Ramanujan_graph + + """ + + import numpy as np + from scipy.sparse.linalg import eigsh + + if epsilon < 0: + raise nx.NetworkXError("epsilon must be non negative") + + if not nx.is_regular(G): + return False + + _, d = nx.utils.arbitrary_element(G.degree) + + A = nx.adjacency_matrix(G, dtype=float) + lams = eigsh(A, which="LM", k=2, return_eigenvectors=False) + + # lambda2 is the second biggest eigenvalue + lambda2 = min(lams) + + # Use bool() to convert numpy scalar to Python Boolean + return bool(abs(lambda2) < 2 ** np.sqrt(d - 1) + epsilon) + + +@nx.utils.decorators.np_random_state("seed") +@nx._dispatchable(graphs=None, returns_graph=True) +def random_regular_expander_graph( + n, d, *, epsilon=0, create_using=None, max_tries=100, seed=None +): + r"""Returns a random regular expander graph on $n$ nodes with degree $d$. + + An expander graph is a sparse graph with strong connectivity properties. [1]_ + + More precisely the returned graph is a $(n, d, \lambda)$-expander with + $\lambda = 2 \sqrt{d - 1} + \epsilon$, close to the Alon-Boppana bound. [2]_ + + In the case where $\epsilon = 0$ it returns a Ramanujan graph. + A Ramanujan graph has spectral gap almost as large as possible, + which makes them excellent expanders. [3]_ + + Parameters + ---------- + n : int + The number of nodes. + d : int + The degree of each node. + epsilon : int, float, default=0 + max_tries : int, (default: 100) + The number of allowed loops, also used in the maybe_regular_expander utility + seed : (default: None) + Seed used to set random number generation state. See :ref`Randomness`. + + Raises + ------ + NetworkXError + If max_tries is reached + + Examples + -------- + >>> G = nx.random_regular_expander_graph(20, 4) + >>> nx.is_regular_expander(G) + True + + Notes + ----- + This loops over `maybe_regular_expander` and can be slow when + $n$ is too big or $\epsilon$ too small. + + See Also + -------- + maybe_regular_expander + is_regular_expander + + References + ---------- + .. [1] Expander graph, https://en.wikipedia.org/wiki/Expander_graph + .. [2] Alon-Boppana bound, https://en.wikipedia.org/wiki/Alon%E2%80%93Boppana_bound + .. [3] Ramanujan graphs, https://en.wikipedia.org/wiki/Ramanujan_graph + + """ + G = maybe_regular_expander( + n, d, create_using=create_using, max_tries=max_tries, seed=seed + ) + iterations = max_tries + + while not is_regular_expander(G, epsilon=epsilon): + iterations -= 1 + G = maybe_regular_expander( + n=n, d=d, create_using=create_using, max_tries=max_tries, seed=seed + ) + + if iterations == 0: + raise nx.NetworkXError( + "Too many iterations in random_regular_expander_graph" + ) + + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/geometric.py b/venv/lib/python3.10/site-packages/networkx/generators/geometric.py new file mode 100644 index 0000000000000000000000000000000000000000..7f19281b81fd9d109565c005174b27513ce6ae02 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/geometric.py @@ -0,0 +1,1048 @@ +"""Generators for geometric graphs.""" + +import math +from bisect import bisect_left +from itertools import accumulate, combinations, product + +import networkx as nx +from networkx.utils import py_random_state + +__all__ = [ + "geometric_edges", + "geographical_threshold_graph", + "navigable_small_world_graph", + "random_geometric_graph", + "soft_random_geometric_graph", + "thresholded_random_geometric_graph", + "waxman_graph", + "geometric_soft_configuration_graph", +] + + +@nx._dispatchable(node_attrs="pos_name") +def geometric_edges(G, radius, p=2, *, pos_name="pos"): + """Returns edge list of node pairs within `radius` of each other. + + Parameters + ---------- + G : networkx graph + The graph from which to generate the edge list. The nodes in `G` should + have an attribute ``pos`` corresponding to the node position, which is + used to compute the distance to other nodes. + radius : scalar + The distance threshold. Edges are included in the edge list if the + distance between the two nodes is less than `radius`. + pos_name : string, default="pos" + The name of the node attribute which represents the position of each + node in 2D coordinates. Every node in the Graph must have this attribute. + p : scalar, default=2 + The `Minkowski distance metric + `_ used to compute + distances. The default value is 2, i.e. Euclidean distance. + + Returns + ------- + edges : list + List of edges whose distances are less than `radius` + + Notes + ----- + Radius uses Minkowski distance metric `p`. + If scipy is available, `scipy.spatial.cKDTree` is used to speed computation. + + Examples + -------- + Create a graph with nodes that have a "pos" attribute representing 2D + coordinates. + + >>> G = nx.Graph() + >>> G.add_nodes_from( + ... [ + ... (0, {"pos": (0, 0)}), + ... (1, {"pos": (3, 0)}), + ... (2, {"pos": (8, 0)}), + ... ] + ... ) + >>> nx.geometric_edges(G, radius=1) + [] + >>> nx.geometric_edges(G, radius=4) + [(0, 1)] + >>> nx.geometric_edges(G, radius=6) + [(0, 1), (1, 2)] + >>> nx.geometric_edges(G, radius=9) + [(0, 1), (0, 2), (1, 2)] + """ + # Input validation - every node must have a "pos" attribute + for n, pos in G.nodes(data=pos_name): + if pos is None: + raise nx.NetworkXError( + f"Node {n} (and all nodes) must have a '{pos_name}' attribute." + ) + + # NOTE: See _geometric_edges for the actual implementation. The reason this + # is split into two functions is to avoid the overhead of input validation + # every time the function is called internally in one of the other + # geometric generators + return _geometric_edges(G, radius, p, pos_name) + + +def _geometric_edges(G, radius, p, pos_name): + """ + Implements `geometric_edges` without input validation. See `geometric_edges` + for complete docstring. + """ + nodes_pos = G.nodes(data=pos_name) + try: + import scipy as sp + except ImportError: + # no scipy KDTree so compute by for-loop + radius_p = radius**p + edges = [ + (u, v) + for (u, pu), (v, pv) in combinations(nodes_pos, 2) + if sum(abs(a - b) ** p for a, b in zip(pu, pv)) <= radius_p + ] + return edges + # scipy KDTree is available + nodes, coords = list(zip(*nodes_pos)) + kdtree = sp.spatial.cKDTree(coords) # Cannot provide generator. + edge_indexes = kdtree.query_pairs(radius, p) + edges = [(nodes[u], nodes[v]) for u, v in sorted(edge_indexes)] + return edges + + +@py_random_state(5) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_geometric_graph( + n, radius, dim=2, pos=None, p=2, seed=None, *, pos_name="pos" +): + """Returns a random geometric graph in the unit cube of dimensions `dim`. + + The random geometric graph model places `n` nodes uniformly at + random in the unit cube. Two nodes are joined by an edge if the + distance between the nodes is at most `radius`. + + Edges are determined using a KDTree when SciPy is available. + This reduces the time complexity from $O(n^2)$ to $O(n)$. + + Parameters + ---------- + n : int or iterable + Number of nodes or iterable of nodes + radius: float + Distance threshold value + dim : int, optional + Dimension of graph + pos : dict, optional + A dictionary keyed by node with node positions as values. + p : float, optional + Which Minkowski distance metric to use. `p` has to meet the condition + ``1 <= p <= infinity``. + + If this argument is not specified, the :math:`L^2` metric + (the Euclidean distance metric), p = 2 is used. + This should not be confused with the `p` of an Erdős-Rényi random + graph, which represents probability. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + pos_name : string, default="pos" + The name of the node attribute which represents the position + in 2D coordinates of the node in the returned graph. + + Returns + ------- + Graph + A random geometric graph, undirected and without self-loops. + Each node has a node attribute ``'pos'`` that stores the + position of that node in Euclidean space as provided by the + ``pos`` keyword argument or, if ``pos`` was not provided, as + generated by this function. + + Examples + -------- + Create a random geometric graph on twenty nodes where nodes are joined by + an edge if their distance is at most 0.1:: + + >>> G = nx.random_geometric_graph(20, 0.1) + + Notes + ----- + This uses a *k*-d tree to build the graph. + + The `pos` keyword argument can be used to specify node positions so you + can create an arbitrary distribution and domain for positions. + + For example, to use a 2D Gaussian distribution of node positions with mean + (0, 0) and standard deviation 2:: + + >>> import random + >>> n = 20 + >>> pos = {i: (random.gauss(0, 2), random.gauss(0, 2)) for i in range(n)} + >>> G = nx.random_geometric_graph(n, 0.2, pos=pos) + + References + ---------- + .. [1] Penrose, Mathew, *Random Geometric Graphs*, + Oxford Studies in Probability, 5, 2003. + + """ + # TODO Is this function just a special case of the geographical + # threshold graph? + # + # half_radius = {v: radius / 2 for v in n} + # return geographical_threshold_graph(nodes, theta=1, alpha=1, + # weight=half_radius) + # + G = nx.empty_graph(n) + # If no positions are provided, choose uniformly random vectors in + # Euclidean space of the specified dimension. + if pos is None: + pos = {v: [seed.random() for i in range(dim)] for v in G} + nx.set_node_attributes(G, pos, pos_name) + + G.add_edges_from(_geometric_edges(G, radius, p, pos_name)) + return G + + +@py_random_state(6) +@nx._dispatchable(graphs=None, returns_graph=True) +def soft_random_geometric_graph( + n, radius, dim=2, pos=None, p=2, p_dist=None, seed=None, *, pos_name="pos" +): + r"""Returns a soft random geometric graph in the unit cube. + + The soft random geometric graph [1] model places `n` nodes uniformly at + random in the unit cube in dimension `dim`. Two nodes of distance, `dist`, + computed by the `p`-Minkowski distance metric are joined by an edge with + probability `p_dist` if the computed distance metric value of the nodes + is at most `radius`, otherwise they are not joined. + + Edges within `radius` of each other are determined using a KDTree when + SciPy is available. This reduces the time complexity from :math:`O(n^2)` + to :math:`O(n)`. + + Parameters + ---------- + n : int or iterable + Number of nodes or iterable of nodes + radius: float + Distance threshold value + dim : int, optional + Dimension of graph + pos : dict, optional + A dictionary keyed by node with node positions as values. + p : float, optional + Which Minkowski distance metric to use. + `p` has to meet the condition ``1 <= p <= infinity``. + + If this argument is not specified, the :math:`L^2` metric + (the Euclidean distance metric), p = 2 is used. + + This should not be confused with the `p` of an Erdős-Rényi random + graph, which represents probability. + p_dist : function, optional + A probability density function computing the probability of + connecting two nodes that are of distance, dist, computed by the + Minkowski distance metric. The probability density function, `p_dist`, + must be any function that takes the metric value as input + and outputs a single probability value between 0-1. The scipy.stats + package has many probability distribution functions implemented and + tools for custom probability distribution definitions [2], and passing + the .pdf method of scipy.stats distributions can be used here. If the + probability function, `p_dist`, is not supplied, the default function + is an exponential distribution with rate parameter :math:`\lambda=1`. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + pos_name : string, default="pos" + The name of the node attribute which represents the position + in 2D coordinates of the node in the returned graph. + + Returns + ------- + Graph + A soft random geometric graph, undirected and without self-loops. + Each node has a node attribute ``'pos'`` that stores the + position of that node in Euclidean space as provided by the + ``pos`` keyword argument or, if ``pos`` was not provided, as + generated by this function. + + Examples + -------- + Default Graph: + + G = nx.soft_random_geometric_graph(50, 0.2) + + Custom Graph: + + Create a soft random geometric graph on 100 uniformly distributed nodes + where nodes are joined by an edge with probability computed from an + exponential distribution with rate parameter :math:`\lambda=1` if their + Euclidean distance is at most 0.2. + + Notes + ----- + This uses a *k*-d tree to build the graph. + + The `pos` keyword argument can be used to specify node positions so you + can create an arbitrary distribution and domain for positions. + + For example, to use a 2D Gaussian distribution of node positions with mean + (0, 0) and standard deviation 2 + + The scipy.stats package can be used to define the probability distribution + with the .pdf method used as `p_dist`. + + :: + + >>> import random + >>> import math + >>> n = 100 + >>> pos = {i: (random.gauss(0, 2), random.gauss(0, 2)) for i in range(n)} + >>> p_dist = lambda dist: math.exp(-dist) + >>> G = nx.soft_random_geometric_graph(n, 0.2, pos=pos, p_dist=p_dist) + + References + ---------- + .. [1] Penrose, Mathew D. "Connectivity of soft random geometric graphs." + The Annals of Applied Probability 26.2 (2016): 986-1028. + .. [2] scipy.stats - + https://docs.scipy.org/doc/scipy/reference/tutorial/stats.html + + """ + G = nx.empty_graph(n) + G.name = f"soft_random_geometric_graph({n}, {radius}, {dim})" + # If no positions are provided, choose uniformly random vectors in + # Euclidean space of the specified dimension. + if pos is None: + pos = {v: [seed.random() for i in range(dim)] for v in G} + nx.set_node_attributes(G, pos, pos_name) + + # if p_dist function not supplied the default function is an exponential + # distribution with rate parameter :math:`\lambda=1`. + if p_dist is None: + + def p_dist(dist): + return math.exp(-dist) + + def should_join(edge): + u, v = edge + dist = (sum(abs(a - b) ** p for a, b in zip(pos[u], pos[v]))) ** (1 / p) + return seed.random() < p_dist(dist) + + G.add_edges_from(filter(should_join, _geometric_edges(G, radius, p, pos_name))) + return G + + +@py_random_state(7) +@nx._dispatchable(graphs=None, returns_graph=True) +def geographical_threshold_graph( + n, + theta, + dim=2, + pos=None, + weight=None, + metric=None, + p_dist=None, + seed=None, + *, + pos_name="pos", + weight_name="weight", +): + r"""Returns a geographical threshold graph. + + The geographical threshold graph model places $n$ nodes uniformly at + random in a rectangular domain. Each node $u$ is assigned a weight + $w_u$. Two nodes $u$ and $v$ are joined by an edge if + + .. math:: + + (w_u + w_v)p_{dist}(r) \ge \theta + + where `r` is the distance between `u` and `v`, `p_dist` is any function of + `r`, and :math:`\theta` as the threshold parameter. `p_dist` is used to + give weight to the distance between nodes when deciding whether or not + they should be connected. The larger `p_dist` is, the more prone nodes + separated by `r` are to be connected, and vice versa. + + Parameters + ---------- + n : int or iterable + Number of nodes or iterable of nodes + theta: float + Threshold value + dim : int, optional + Dimension of graph + pos : dict + Node positions as a dictionary of tuples keyed by node. + weight : dict + Node weights as a dictionary of numbers keyed by node. + metric : function + A metric on vectors of numbers (represented as lists or + tuples). This must be a function that accepts two lists (or + tuples) as input and yields a number as output. The function + must also satisfy the four requirements of a `metric`_. + Specifically, if $d$ is the function and $x$, $y$, + and $z$ are vectors in the graph, then $d$ must satisfy + + 1. $d(x, y) \ge 0$, + 2. $d(x, y) = 0$ if and only if $x = y$, + 3. $d(x, y) = d(y, x)$, + 4. $d(x, z) \le d(x, y) + d(y, z)$. + + If this argument is not specified, the Euclidean distance metric is + used. + + .. _metric: https://en.wikipedia.org/wiki/Metric_%28mathematics%29 + p_dist : function, optional + Any function used to give weight to the distance between nodes when + deciding whether or not they should be connected. `p_dist` was + originally conceived as a probability density function giving the + probability of connecting two nodes that are of metric distance `r` + apart. The implementation here allows for more arbitrary definitions + of `p_dist` that do not need to correspond to valid probability + density functions. The :mod:`scipy.stats` package has many + probability density functions implemented and tools for custom + probability density definitions, and passing the ``.pdf`` method of + scipy.stats distributions can be used here. If ``p_dist=None`` + (the default), the exponential function :math:`r^{-2}` is used. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + pos_name : string, default="pos" + The name of the node attribute which represents the position + in 2D coordinates of the node in the returned graph. + weight_name : string, default="weight" + The name of the node attribute which represents the weight + of the node in the returned graph. + + Returns + ------- + Graph + A random geographic threshold graph, undirected and without + self-loops. + + Each node has a node attribute ``pos`` that stores the + position of that node in Euclidean space as provided by the + ``pos`` keyword argument or, if ``pos`` was not provided, as + generated by this function. Similarly, each node has a node + attribute ``weight`` that stores the weight of that node as + provided or as generated. + + Examples + -------- + Specify an alternate distance metric using the ``metric`` keyword + argument. For example, to use the `taxicab metric`_ instead of the + default `Euclidean metric`_:: + + >>> dist = lambda x, y: sum(abs(a - b) for a, b in zip(x, y)) + >>> G = nx.geographical_threshold_graph(10, 0.1, metric=dist) + + .. _taxicab metric: https://en.wikipedia.org/wiki/Taxicab_geometry + .. _Euclidean metric: https://en.wikipedia.org/wiki/Euclidean_distance + + Notes + ----- + If weights are not specified they are assigned to nodes by drawing randomly + from the exponential distribution with rate parameter $\lambda=1$. + To specify weights from a different distribution, use the `weight` keyword + argument:: + + >>> import random + >>> n = 20 + >>> w = {i: random.expovariate(5.0) for i in range(n)} + >>> G = nx.geographical_threshold_graph(20, 50, weight=w) + + If node positions are not specified they are randomly assigned from the + uniform distribution. + + References + ---------- + .. [1] Masuda, N., Miwa, H., Konno, N.: + Geographical threshold graphs with small-world and scale-free + properties. + Physical Review E 71, 036108 (2005) + .. [2] Milan Bradonjić, Aric Hagberg and Allon G. Percus, + Giant component and connectivity in geographical threshold graphs, + in Algorithms and Models for the Web-Graph (WAW 2007), + Antony Bonato and Fan Chung (Eds), pp. 209--216, 2007 + """ + G = nx.empty_graph(n) + # If no weights are provided, choose them from an exponential + # distribution. + if weight is None: + weight = {v: seed.expovariate(1) for v in G} + # If no positions are provided, choose uniformly random vectors in + # Euclidean space of the specified dimension. + if pos is None: + pos = {v: [seed.random() for i in range(dim)] for v in G} + # If no distance metric is provided, use Euclidean distance. + if metric is None: + metric = math.dist + nx.set_node_attributes(G, weight, weight_name) + nx.set_node_attributes(G, pos, pos_name) + + # if p_dist is not supplied, use default r^-2 + if p_dist is None: + + def p_dist(r): + return r**-2 + + # Returns ``True`` if and only if the nodes whose attributes are + # ``du`` and ``dv`` should be joined, according to the threshold + # condition. + def should_join(pair): + u, v = pair + u_pos, v_pos = pos[u], pos[v] + u_weight, v_weight = weight[u], weight[v] + return (u_weight + v_weight) * p_dist(metric(u_pos, v_pos)) >= theta + + G.add_edges_from(filter(should_join, combinations(G, 2))) + return G + + +@py_random_state(6) +@nx._dispatchable(graphs=None, returns_graph=True) +def waxman_graph( + n, + beta=0.4, + alpha=0.1, + L=None, + domain=(0, 0, 1, 1), + metric=None, + seed=None, + *, + pos_name="pos", +): + r"""Returns a Waxman random graph. + + The Waxman random graph model places `n` nodes uniformly at random + in a rectangular domain. Each pair of nodes at distance `d` is + joined by an edge with probability + + .. math:: + p = \beta \exp(-d / \alpha L). + + This function implements both Waxman models, using the `L` keyword + argument. + + * Waxman-1: if `L` is not specified, it is set to be the maximum distance + between any pair of nodes. + * Waxman-2: if `L` is specified, the distance between a pair of nodes is + chosen uniformly at random from the interval `[0, L]`. + + Parameters + ---------- + n : int or iterable + Number of nodes or iterable of nodes + beta: float + Model parameter + alpha: float + Model parameter + L : float, optional + Maximum distance between nodes. If not specified, the actual distance + is calculated. + domain : four-tuple of numbers, optional + Domain size, given as a tuple of the form `(x_min, y_min, x_max, + y_max)`. + metric : function + A metric on vectors of numbers (represented as lists or + tuples). This must be a function that accepts two lists (or + tuples) as input and yields a number as output. The function + must also satisfy the four requirements of a `metric`_. + Specifically, if $d$ is the function and $x$, $y$, + and $z$ are vectors in the graph, then $d$ must satisfy + + 1. $d(x, y) \ge 0$, + 2. $d(x, y) = 0$ if and only if $x = y$, + 3. $d(x, y) = d(y, x)$, + 4. $d(x, z) \le d(x, y) + d(y, z)$. + + If this argument is not specified, the Euclidean distance metric is + used. + + .. _metric: https://en.wikipedia.org/wiki/Metric_%28mathematics%29 + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + pos_name : string, default="pos" + The name of the node attribute which represents the position + in 2D coordinates of the node in the returned graph. + + Returns + ------- + Graph + A random Waxman graph, undirected and without self-loops. Each + node has a node attribute ``'pos'`` that stores the position of + that node in Euclidean space as generated by this function. + + Examples + -------- + Specify an alternate distance metric using the ``metric`` keyword + argument. For example, to use the "`taxicab metric`_" instead of the + default `Euclidean metric`_:: + + >>> dist = lambda x, y: sum(abs(a - b) for a, b in zip(x, y)) + >>> G = nx.waxman_graph(10, 0.5, 0.1, metric=dist) + + .. _taxicab metric: https://en.wikipedia.org/wiki/Taxicab_geometry + .. _Euclidean metric: https://en.wikipedia.org/wiki/Euclidean_distance + + Notes + ----- + Starting in NetworkX 2.0 the parameters alpha and beta align with their + usual roles in the probability distribution. In earlier versions their + positions in the expression were reversed. Their position in the calling + sequence reversed as well to minimize backward incompatibility. + + References + ---------- + .. [1] B. M. Waxman, *Routing of multipoint connections*. + IEEE J. Select. Areas Commun. 6(9),(1988) 1617--1622. + """ + G = nx.empty_graph(n) + (xmin, ymin, xmax, ymax) = domain + # Each node gets a uniformly random position in the given rectangle. + pos = {v: (seed.uniform(xmin, xmax), seed.uniform(ymin, ymax)) for v in G} + nx.set_node_attributes(G, pos, pos_name) + # If no distance metric is provided, use Euclidean distance. + if metric is None: + metric = math.dist + # If the maximum distance L is not specified (that is, we are in the + # Waxman-1 model), then find the maximum distance between any pair + # of nodes. + # + # In the Waxman-1 model, join nodes randomly based on distance. In + # the Waxman-2 model, join randomly based on random l. + if L is None: + L = max(metric(x, y) for x, y in combinations(pos.values(), 2)) + + def dist(u, v): + return metric(pos[u], pos[v]) + + else: + + def dist(u, v): + return seed.random() * L + + # `pair` is the pair of nodes to decide whether to join. + def should_join(pair): + return seed.random() < beta * math.exp(-dist(*pair) / (alpha * L)) + + G.add_edges_from(filter(should_join, combinations(G, 2))) + return G + + +@py_random_state(5) +@nx._dispatchable(graphs=None, returns_graph=True) +def navigable_small_world_graph(n, p=1, q=1, r=2, dim=2, seed=None): + r"""Returns a navigable small-world graph. + + A navigable small-world graph is a directed grid with additional long-range + connections that are chosen randomly. + + [...] we begin with a set of nodes [...] that are identified with the set + of lattice points in an $n \times n$ square, + $\{(i, j): i \in \{1, 2, \ldots, n\}, j \in \{1, 2, \ldots, n\}\}$, + and we define the *lattice distance* between two nodes $(i, j)$ and + $(k, l)$ to be the number of "lattice steps" separating them: + $d((i, j), (k, l)) = |k - i| + |l - j|$. + + For a universal constant $p >= 1$, the node $u$ has a directed edge to + every other node within lattice distance $p$---these are its *local + contacts*. For universal constants $q >= 0$ and $r >= 0$ we also + construct directed edges from $u$ to $q$ other nodes (the *long-range + contacts*) using independent random trials; the $i$th directed edge from + $u$ has endpoint $v$ with probability proportional to $[d(u,v)]^{-r}$. + + -- [1]_ + + Parameters + ---------- + n : int + The length of one side of the lattice; the number of nodes in + the graph is therefore $n^2$. + p : int + The diameter of short range connections. Each node is joined with every + other node within this lattice distance. + q : int + The number of long-range connections for each node. + r : float + Exponent for decaying probability of connections. The probability of + connecting to a node at lattice distance $d$ is $1/d^r$. + dim : int + Dimension of grid + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + References + ---------- + .. [1] J. Kleinberg. The small-world phenomenon: An algorithmic + perspective. Proc. 32nd ACM Symposium on Theory of Computing, 2000. + """ + if p < 1: + raise nx.NetworkXException("p must be >= 1") + if q < 0: + raise nx.NetworkXException("q must be >= 0") + if r < 0: + raise nx.NetworkXException("r must be >= 0") + + G = nx.DiGraph() + nodes = list(product(range(n), repeat=dim)) + for p1 in nodes: + probs = [0] + for p2 in nodes: + if p1 == p2: + continue + d = sum((abs(b - a) for a, b in zip(p1, p2))) + if d <= p: + G.add_edge(p1, p2) + probs.append(d**-r) + cdf = list(accumulate(probs)) + for _ in range(q): + target = nodes[bisect_left(cdf, seed.uniform(0, cdf[-1]))] + G.add_edge(p1, target) + return G + + +@py_random_state(7) +@nx._dispatchable(graphs=None, returns_graph=True) +def thresholded_random_geometric_graph( + n, + radius, + theta, + dim=2, + pos=None, + weight=None, + p=2, + seed=None, + *, + pos_name="pos", + weight_name="weight", +): + r"""Returns a thresholded random geometric graph in the unit cube. + + The thresholded random geometric graph [1] model places `n` nodes + uniformly at random in the unit cube of dimensions `dim`. Each node + `u` is assigned a weight :math:`w_u`. Two nodes `u` and `v` are + joined by an edge if they are within the maximum connection distance, + `radius` computed by the `p`-Minkowski distance and the summation of + weights :math:`w_u` + :math:`w_v` is greater than or equal + to the threshold parameter `theta`. + + Edges within `radius` of each other are determined using a KDTree when + SciPy is available. This reduces the time complexity from :math:`O(n^2)` + to :math:`O(n)`. + + Parameters + ---------- + n : int or iterable + Number of nodes or iterable of nodes + radius: float + Distance threshold value + theta: float + Threshold value + dim : int, optional + Dimension of graph + pos : dict, optional + A dictionary keyed by node with node positions as values. + weight : dict, optional + Node weights as a dictionary of numbers keyed by node. + p : float, optional (default 2) + Which Minkowski distance metric to use. `p` has to meet the condition + ``1 <= p <= infinity``. + + If this argument is not specified, the :math:`L^2` metric + (the Euclidean distance metric), p = 2 is used. + + This should not be confused with the `p` of an Erdős-Rényi random + graph, which represents probability. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + pos_name : string, default="pos" + The name of the node attribute which represents the position + in 2D coordinates of the node in the returned graph. + weight_name : string, default="weight" + The name of the node attribute which represents the weight + of the node in the returned graph. + + Returns + ------- + Graph + A thresholded random geographic graph, undirected and without + self-loops. + + Each node has a node attribute ``'pos'`` that stores the + position of that node in Euclidean space as provided by the + ``pos`` keyword argument or, if ``pos`` was not provided, as + generated by this function. Similarly, each node has a nodethre + attribute ``'weight'`` that stores the weight of that node as + provided or as generated. + + Examples + -------- + Default Graph: + + G = nx.thresholded_random_geometric_graph(50, 0.2, 0.1) + + Custom Graph: + + Create a thresholded random geometric graph on 50 uniformly distributed + nodes where nodes are joined by an edge if their sum weights drawn from + a exponential distribution with rate = 5 are >= theta = 0.1 and their + Euclidean distance is at most 0.2. + + Notes + ----- + This uses a *k*-d tree to build the graph. + + The `pos` keyword argument can be used to specify node positions so you + can create an arbitrary distribution and domain for positions. + + For example, to use a 2D Gaussian distribution of node positions with mean + (0, 0) and standard deviation 2 + + If weights are not specified they are assigned to nodes by drawing randomly + from the exponential distribution with rate parameter :math:`\lambda=1`. + To specify weights from a different distribution, use the `weight` keyword + argument:: + + :: + + >>> import random + >>> import math + >>> n = 50 + >>> pos = {i: (random.gauss(0, 2), random.gauss(0, 2)) for i in range(n)} + >>> w = {i: random.expovariate(5.0) for i in range(n)} + >>> G = nx.thresholded_random_geometric_graph(n, 0.2, 0.1, 2, pos, w) + + References + ---------- + .. [1] http://cole-maclean.github.io/blog/files/thesis.pdf + + """ + G = nx.empty_graph(n) + G.name = f"thresholded_random_geometric_graph({n}, {radius}, {theta}, {dim})" + # If no weights are provided, choose them from an exponential + # distribution. + if weight is None: + weight = {v: seed.expovariate(1) for v in G} + # If no positions are provided, choose uniformly random vectors in + # Euclidean space of the specified dimension. + if pos is None: + pos = {v: [seed.random() for i in range(dim)] for v in G} + # If no distance metric is provided, use Euclidean distance. + nx.set_node_attributes(G, weight, weight_name) + nx.set_node_attributes(G, pos, pos_name) + + edges = ( + (u, v) + for u, v in _geometric_edges(G, radius, p, pos_name) + if weight[u] + weight[v] >= theta + ) + G.add_edges_from(edges) + return G + + +@py_random_state(5) +@nx._dispatchable(graphs=None, returns_graph=True) +def geometric_soft_configuration_graph( + *, beta, n=None, gamma=None, mean_degree=None, kappas=None, seed=None +): + r"""Returns a random graph from the geometric soft configuration model. + + The $\mathbb{S}^1$ model [1]_ is the geometric soft configuration model + which is able to explain many fundamental features of real networks such as + small-world property, heteregenous degree distributions, high level of + clustering, and self-similarity. + + In the geometric soft configuration model, a node $i$ is assigned two hidden + variables: a hidden degree $\kappa_i$, quantifying its popularity, influence, + or importance, and an angular position $\theta_i$ in a circle abstracting the + similarity space, where angular distances between nodes are a proxy for their + similarity. Focusing on the angular position, this model is often called + the $\mathbb{S}^1$ model (a one-dimensional sphere). The circle's radius is + adjusted to $R = N/2\pi$, where $N$ is the number of nodes, so that the density + is set to 1 without loss of generality. + + The connection probability between any pair of nodes increases with + the product of their hidden degrees (i.e., their combined popularities), + and decreases with the angular distance between the two nodes. + Specifically, nodes $i$ and $j$ are connected with the probability + + $p_{ij} = \frac{1}{1 + \frac{d_{ij}^\beta}{\left(\mu \kappa_i \kappa_j\right)^{\max(1, \beta)}}}$ + + where $d_{ij} = R\Delta\theta_{ij}$ is the arc length of the circle between + nodes $i$ and $j$ separated by an angular distance $\Delta\theta_{ij}$. + Parameters $\mu$ and $\beta$ (also called inverse temperature) control the + average degree and the clustering coefficient, respectively. + + It can be shown [2]_ that the model undergoes a structural phase transition + at $\beta=1$ so that for $\beta<1$ networks are unclustered in the thermodynamic + limit (when $N\to \infty$) whereas for $\beta>1$ the ensemble generates + networks with finite clustering coefficient. + + The $\mathbb{S}^1$ model can be expressed as a purely geometric model + $\mathbb{H}^2$ in the hyperbolic plane [3]_ by mapping the hidden degree of + each node into a radial coordinate as + + $r_i = \hat{R} - \frac{2 \max(1, \beta)}{\beta \zeta} \ln \left(\frac{\kappa_i}{\kappa_0}\right)$ + + where $\hat{R}$ is the radius of the hyperbolic disk and $\zeta$ is the curvature, + + $\hat{R} = \frac{2}{\zeta} \ln \left(\frac{N}{\pi}\right) + - \frac{2\max(1, \beta)}{\beta \zeta} \ln (\mu \kappa_0^2)$ + + The connection probability then reads + + $p_{ij} = \frac{1}{1 + \exp\left({\frac{\beta\zeta}{2} (x_{ij} - \hat{R})}\right)}$ + + where + + $x_{ij} = r_i + r_j + \frac{2}{\zeta} \ln \frac{\Delta\theta_{ij}}{2}$ + + is a good approximation of the hyperbolic distance between two nodes separated + by an angular distance $\Delta\theta_{ij}$ with radial coordinates $r_i$ and $r_j$. + For $\beta > 1$, the curvature $\zeta = 1$, for $\beta < 1$, $\zeta = \beta^{-1}$. + + + Parameters + ---------- + Either `n`, `gamma`, `mean_degree` are provided or `kappas`. The values of + `n`, `gamma`, `mean_degree` (if provided) are used to construct a random + kappa-dict keyed by node with values sampled from a power-law distribution. + + beta : positive number + Inverse temperature, controlling the clustering coefficient. + n : int (default: None) + Size of the network (number of nodes). + If not provided, `kappas` must be provided and holds the nodes. + gamma : float (default: None) + Exponent of the power-law distribution for hidden degrees `kappas`. + If not provided, `kappas` must be provided directly. + mean_degree : float (default: None) + The mean degree in the network. + If not provided, `kappas` must be provided directly. + kappas : dict (default: None) + A dict keyed by node to its hidden degree value. + If not provided, random values are computed based on a power-law + distribution using `n`, `gamma` and `mean_degree`. + seed : int, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + Graph + A random geometric soft configuration graph (undirected with no self-loops). + Each node has three node-attributes: + + - ``kappa`` that represents the hidden degree. + + - ``theta`` the position in the similarity space ($\mathbb{S}^1$) which is + also the angular position in the hyperbolic plane. + + - ``radius`` the radial position in the hyperbolic plane + (based on the hidden degree). + + + Examples + -------- + Generate a network with specified parameters: + + >>> G = nx.geometric_soft_configuration_graph( + ... beta=1.5, n=100, gamma=2.7, mean_degree=5 + ... ) + + Create a geometric soft configuration graph with 100 nodes. The $\beta$ parameter + is set to 1.5 and the exponent of the powerlaw distribution of the hidden + degrees is 2.7 with mean value of 5. + + Generate a network with predefined hidden degrees: + + >>> kappas = {i: 10 for i in range(100)} + >>> G = nx.geometric_soft_configuration_graph(beta=2.5, kappas=kappas) + + Create a geometric soft configuration graph with 100 nodes. The $\beta$ parameter + is set to 2.5 and all nodes with hidden degree $\kappa=10$. + + + References + ---------- + .. [1] Serrano, M. Á., Krioukov, D., & Boguñá, M. (2008). Self-similarity + of complex networks and hidden metric spaces. Physical review letters, 100(7), 078701. + + .. [2] van der Kolk, J., Serrano, M. Á., & Boguñá, M. (2022). An anomalous + topological phase transition in spatial random graphs. Communications Physics, 5(1), 245. + + .. [3] Krioukov, D., Papadopoulos, F., Kitsak, M., Vahdat, A., & Boguná, M. (2010). + Hyperbolic geometry of complex networks. Physical Review E, 82(3), 036106. + + """ + if beta <= 0: + raise nx.NetworkXError("The parameter beta cannot be smaller or equal to 0.") + + if kappas is not None: + if not all((n is None, gamma is None, mean_degree is None)): + raise nx.NetworkXError( + "When kappas is input, n, gamma and mean_degree must not be." + ) + + n = len(kappas) + mean_degree = sum(kappas) / len(kappas) + else: + if any((n is None, gamma is None, mean_degree is None)): + raise nx.NetworkXError( + "Please provide either kappas, or all 3 of: n, gamma and mean_degree." + ) + + # Generate `n` hidden degrees from a powerlaw distribution + # with given exponent `gamma` and mean value `mean_degree` + gam_ratio = (gamma - 2) / (gamma - 1) + kappa_0 = mean_degree * gam_ratio * (1 - 1 / n) / (1 - 1 / n**gam_ratio) + base = 1 - 1 / n + power = 1 / (1 - gamma) + kappas = {i: kappa_0 * (1 - seed.random() * base) ** power for i in range(n)} + + G = nx.Graph() + R = n / (2 * math.pi) + + # Approximate values for mu in the thermodynamic limit (when n -> infinity) + if beta > 1: + mu = beta * math.sin(math.pi / beta) / (2 * math.pi * mean_degree) + elif beta == 1: + mu = 1 / (2 * mean_degree * math.log(n)) + else: + mu = (1 - beta) / (2**beta * mean_degree * n ** (1 - beta)) + + # Generate random positions on a circle + thetas = {k: seed.uniform(0, 2 * math.pi) for k in kappas} + + for u in kappas: + for v in list(G): + angle = math.pi - math.fabs(math.pi - math.fabs(thetas[u] - thetas[v])) + dij = math.pow(R * angle, beta) + mu_kappas = math.pow(mu * kappas[u] * kappas[v], max(1, beta)) + p_ij = 1 / (1 + dij / mu_kappas) + + # Create an edge with a certain connection probability + if seed.random() < p_ij: + G.add_edge(u, v) + G.add_node(u) + + nx.set_node_attributes(G, thetas, "theta") + nx.set_node_attributes(G, kappas, "kappa") + + # Map hidden degrees into the radial coordinates + zeta = 1 if beta > 1 else 1 / beta + kappa_min = min(kappas.values()) + R_c = 2 * max(1, beta) / (beta * zeta) + R_hat = (2 / zeta) * math.log(n / math.pi) - R_c * math.log(mu * kappa_min) + radii = {node: R_hat - R_c * math.log(kappa) for node, kappa in kappas.items()} + nx.set_node_attributes(G, radii, "radius") + + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/harary_graph.py b/venv/lib/python3.10/site-packages/networkx/generators/harary_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..591587d3aca68176a1d781eb1900b0feb04567e4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/harary_graph.py @@ -0,0 +1,199 @@ +"""Generators for Harary graphs + +This module gives two generators for the Harary graph, which was +introduced by the famous mathematician Frank Harary in his 1962 work [H]_. +The first generator gives the Harary graph that maximizes the node +connectivity with given number of nodes and given number of edges. +The second generator gives the Harary graph that minimizes +the number of edges in the graph with given node connectivity and +number of nodes. + +References +---------- +.. [H] Harary, F. "The Maximum Connectivity of a Graph." + Proc. Nat. Acad. Sci. USA 48, 1142-1146, 1962. + +""" + +import networkx as nx +from networkx.exception import NetworkXError + +__all__ = ["hnm_harary_graph", "hkn_harary_graph"] + + +@nx._dispatchable(graphs=None, returns_graph=True) +def hnm_harary_graph(n, m, create_using=None): + """Returns the Harary graph with given numbers of nodes and edges. + + The Harary graph $H_{n,m}$ is the graph that maximizes node connectivity + with $n$ nodes and $m$ edges. + + This maximum node connectivity is known to be floor($2m/n$). [1]_ + + Parameters + ---------- + n: integer + The number of nodes the generated graph is to contain + + m: integer + The number of edges the generated graph is to contain + + create_using : NetworkX graph constructor, optional Graph type + to create (default=nx.Graph). If graph instance, then cleared + before populated. + + Returns + ------- + NetworkX graph + The Harary graph $H_{n,m}$. + + See Also + -------- + hkn_harary_graph + + Notes + ----- + This algorithm runs in $O(m)$ time. + It is implemented by following the Reference [2]_. + + References + ---------- + .. [1] F. T. Boesch, A. Satyanarayana, and C. L. Suffel, + "A Survey of Some Network Reliability Analysis and Synthesis Results," + Networks, pp. 99-107, 2009. + + .. [2] Harary, F. "The Maximum Connectivity of a Graph." + Proc. Nat. Acad. Sci. USA 48, 1142-1146, 1962. + """ + + if n < 1: + raise NetworkXError("The number of nodes must be >= 1!") + if m < n - 1: + raise NetworkXError("The number of edges must be >= n - 1 !") + if m > n * (n - 1) // 2: + raise NetworkXError("The number of edges must be <= n(n-1)/2") + + # Construct an empty graph with n nodes first + H = nx.empty_graph(n, create_using) + # Get the floor of average node degree + d = 2 * m // n + + # Test the parity of n and d + if (n % 2 == 0) or (d % 2 == 0): + # Start with a regular graph of d degrees + offset = d // 2 + for i in range(n): + for j in range(1, offset + 1): + H.add_edge(i, (i - j) % n) + H.add_edge(i, (i + j) % n) + if d & 1: + # in case d is odd; n must be even in this case + half = n // 2 + for i in range(half): + # add edges diagonally + H.add_edge(i, i + half) + # Get the remainder of 2*m modulo n + r = 2 * m % n + if r > 0: + # add remaining edges at offset+1 + for i in range(r // 2): + H.add_edge(i, i + offset + 1) + else: + # Start with a regular graph of (d - 1) degrees + offset = (d - 1) // 2 + for i in range(n): + for j in range(1, offset + 1): + H.add_edge(i, (i - j) % n) + H.add_edge(i, (i + j) % n) + half = n // 2 + for i in range(m - n * offset): + # add the remaining m - n*offset edges between i and i+half + H.add_edge(i, (i + half) % n) + + return H + + +@nx._dispatchable(graphs=None, returns_graph=True) +def hkn_harary_graph(k, n, create_using=None): + """Returns the Harary graph with given node connectivity and node number. + + The Harary graph $H_{k,n}$ is the graph that minimizes the number of + edges needed with given node connectivity $k$ and node number $n$. + + This smallest number of edges is known to be ceil($kn/2$) [1]_. + + Parameters + ---------- + k: integer + The node connectivity of the generated graph + + n: integer + The number of nodes the generated graph is to contain + + create_using : NetworkX graph constructor, optional Graph type + to create (default=nx.Graph). If graph instance, then cleared + before populated. + + Returns + ------- + NetworkX graph + The Harary graph $H_{k,n}$. + + See Also + -------- + hnm_harary_graph + + Notes + ----- + This algorithm runs in $O(kn)$ time. + It is implemented by following the Reference [2]_. + + References + ---------- + .. [1] Weisstein, Eric W. "Harary Graph." From MathWorld--A Wolfram Web + Resource. http://mathworld.wolfram.com/HararyGraph.html. + + .. [2] Harary, F. "The Maximum Connectivity of a Graph." + Proc. Nat. Acad. Sci. USA 48, 1142-1146, 1962. + """ + + if k < 1: + raise NetworkXError("The node connectivity must be >= 1!") + if n < k + 1: + raise NetworkXError("The number of nodes must be >= k+1 !") + + # in case of connectivity 1, simply return the path graph + if k == 1: + H = nx.path_graph(n, create_using) + return H + + # Construct an empty graph with n nodes first + H = nx.empty_graph(n, create_using) + + # Test the parity of k and n + if (k % 2 == 0) or (n % 2 == 0): + # Construct a regular graph with k degrees + offset = k // 2 + for i in range(n): + for j in range(1, offset + 1): + H.add_edge(i, (i - j) % n) + H.add_edge(i, (i + j) % n) + if k & 1: + # odd degree; n must be even in this case + half = n // 2 + for i in range(half): + # add edges diagonally + H.add_edge(i, i + half) + else: + # Construct a regular graph with (k - 1) degrees + offset = (k - 1) // 2 + for i in range(n): + for j in range(1, offset + 1): + H.add_edge(i, (i - j) % n) + H.add_edge(i, (i + j) % n) + half = n // 2 + for i in range(half + 1): + # add half+1 edges between i and i+half + H.add_edge(i, (i + half) % n) + + return H diff --git a/venv/lib/python3.10/site-packages/networkx/generators/internet_as_graphs.py b/venv/lib/python3.10/site-packages/networkx/generators/internet_as_graphs.py new file mode 100644 index 0000000000000000000000000000000000000000..449d54376af4981a94ba6324f6d373962a9daaaa --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/internet_as_graphs.py @@ -0,0 +1,441 @@ +"""Generates graphs resembling the Internet Autonomous System network""" + +import networkx as nx +from networkx.utils import py_random_state + +__all__ = ["random_internet_as_graph"] + + +def uniform_int_from_avg(a, m, seed): + """Pick a random integer with uniform probability. + + Returns a random integer uniformly taken from a distribution with + minimum value 'a' and average value 'm', X~U(a,b), E[X]=m, X in N where + b = 2*m - a. + + Notes + ----- + p = (b-floor(b))/2 + X = X1 + X2; X1~U(a,floor(b)), X2~B(p) + E[X] = E[X1] + E[X2] = (floor(b)+a)/2 + (b-floor(b))/2 = (b+a)/2 = m + """ + + from math import floor + + assert m >= a + b = 2 * m - a + p = (b - floor(b)) / 2 + X1 = round(seed.random() * (floor(b) - a) + a) + if seed.random() < p: + X2 = 1 + else: + X2 = 0 + return X1 + X2 + + +def choose_pref_attach(degs, seed): + """Pick a random value, with a probability given by its weight. + + Returns a random choice among degs keys, each of which has a + probability proportional to the corresponding dictionary value. + + Parameters + ---------- + degs: dictionary + It contains the possible values (keys) and the corresponding + probabilities (values) + seed: random state + + Returns + ------- + v: object + A key of degs or None if degs is empty + """ + + if len(degs) == 0: + return None + s = sum(degs.values()) + if s == 0: + return seed.choice(list(degs.keys())) + v = seed.random() * s + + nodes = list(degs.keys()) + i = 0 + acc = degs[nodes[i]] + while v > acc: + i += 1 + acc += degs[nodes[i]] + return nodes[i] + + +class AS_graph_generator: + """Generates random internet AS graphs.""" + + def __init__(self, n, seed): + """Initializes variables. Immediate numbers are taken from [1]. + + Parameters + ---------- + n: integer + Number of graph nodes + seed: random state + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + GG: AS_graph_generator object + + References + ---------- + [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of + BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas + in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010. + """ + + self.seed = seed + self.n_t = min(n, round(self.seed.random() * 2 + 4)) # num of T nodes + self.n_m = round(0.15 * n) # number of M nodes + self.n_cp = round(0.05 * n) # number of CP nodes + self.n_c = max(0, n - self.n_t - self.n_m - self.n_cp) # number of C nodes + + self.d_m = 2 + (2.5 * n) / 10000 # average multihoming degree for M nodes + self.d_cp = 2 + (1.5 * n) / 10000 # avg multihoming degree for CP nodes + self.d_c = 1 + (5 * n) / 100000 # average multihoming degree for C nodes + + self.p_m_m = 1 + (2 * n) / 10000 # avg num of peer edges between M and M + self.p_cp_m = 0.2 + (2 * n) / 10000 # avg num of peer edges between CP, M + self.p_cp_cp = 0.05 + (2 * n) / 100000 # avg num of peer edges btwn CP, CP + + self.t_m = 0.375 # probability M's provider is T + self.t_cp = 0.375 # probability CP's provider is T + self.t_c = 0.125 # probability C's provider is T + + def t_graph(self): + """Generates the core mesh network of tier one nodes of a AS graph. + + Returns + ------- + G: Networkx Graph + Core network + """ + + self.G = nx.Graph() + for i in range(self.n_t): + self.G.add_node(i, type="T") + for r in self.regions: + self.regions[r].add(i) + for j in self.G.nodes(): + if i != j: + self.add_edge(i, j, "peer") + self.customers[i] = set() + self.providers[i] = set() + return self.G + + def add_edge(self, i, j, kind): + if kind == "transit": + customer = str(i) + else: + customer = "none" + self.G.add_edge(i, j, type=kind, customer=customer) + + def choose_peer_pref_attach(self, node_list): + """Pick a node with a probability weighted by its peer degree. + + Pick a node from node_list with preferential attachment + computed only on their peer degree + """ + + d = {} + for n in node_list: + d[n] = self.G.nodes[n]["peers"] + return choose_pref_attach(d, self.seed) + + def choose_node_pref_attach(self, node_list): + """Pick a node with a probability weighted by its degree. + + Pick a node from node_list with preferential attachment + computed on their degree + """ + + degs = dict(self.G.degree(node_list)) + return choose_pref_attach(degs, self.seed) + + def add_customer(self, i, j): + """Keep the dictionaries 'customers' and 'providers' consistent.""" + + self.customers[j].add(i) + self.providers[i].add(j) + for z in self.providers[j]: + self.customers[z].add(i) + self.providers[i].add(z) + + def add_node(self, i, kind, reg2prob, avg_deg, t_edge_prob): + """Add a node and its customer transit edges to the graph. + + Parameters + ---------- + i: object + Identifier of the new node + kind: string + Type of the new node. Options are: 'M' for middle node, 'CP' for + content provider and 'C' for customer. + reg2prob: float + Probability the new node can be in two different regions. + avg_deg: float + Average number of transit nodes of which node i is customer. + t_edge_prob: float + Probability node i establish a customer transit edge with a tier + one (T) node + + Returns + ------- + i: object + Identifier of the new node + """ + + regs = 1 # regions in which node resides + if self.seed.random() < reg2prob: # node is in two regions + regs = 2 + node_options = set() + + self.G.add_node(i, type=kind, peers=0) + self.customers[i] = set() + self.providers[i] = set() + self.nodes[kind].add(i) + for r in self.seed.sample(list(self.regions), regs): + node_options = node_options.union(self.regions[r]) + self.regions[r].add(i) + + edge_num = uniform_int_from_avg(1, avg_deg, self.seed) + + t_options = node_options.intersection(self.nodes["T"]) + m_options = node_options.intersection(self.nodes["M"]) + if i in m_options: + m_options.remove(i) + d = 0 + while d < edge_num and (len(t_options) > 0 or len(m_options) > 0): + if len(m_options) == 0 or ( + len(t_options) > 0 and self.seed.random() < t_edge_prob + ): # add edge to a T node + j = self.choose_node_pref_attach(t_options) + t_options.remove(j) + else: + j = self.choose_node_pref_attach(m_options) + m_options.remove(j) + self.add_edge(i, j, "transit") + self.add_customer(i, j) + d += 1 + + return i + + def add_m_peering_link(self, m, to_kind): + """Add a peering link between two middle tier (M) nodes. + + Target node j is drawn considering a preferential attachment based on + other M node peering degree. + + Parameters + ---------- + m: object + Node identifier + to_kind: string + type for target node j (must be always M) + + Returns + ------- + success: boolean + """ + + # candidates are of type 'M' and are not customers of m + node_options = self.nodes["M"].difference(self.customers[m]) + # candidates are not providers of m + node_options = node_options.difference(self.providers[m]) + # remove self + if m in node_options: + node_options.remove(m) + + # remove candidates we are already connected to + for j in self.G.neighbors(m): + if j in node_options: + node_options.remove(j) + + if len(node_options) > 0: + j = self.choose_peer_pref_attach(node_options) + self.add_edge(m, j, "peer") + self.G.nodes[m]["peers"] += 1 + self.G.nodes[j]["peers"] += 1 + return True + else: + return False + + def add_cp_peering_link(self, cp, to_kind): + """Add a peering link to a content provider (CP) node. + + Target node j can be CP or M and it is drawn uniformly among the nodes + belonging to the same region as cp. + + Parameters + ---------- + cp: object + Node identifier + to_kind: string + type for target node j (must be M or CP) + + Returns + ------- + success: boolean + """ + + node_options = set() + for r in self.regions: # options include nodes in the same region(s) + if cp in self.regions[r]: + node_options = node_options.union(self.regions[r]) + + # options are restricted to the indicated kind ('M' or 'CP') + node_options = self.nodes[to_kind].intersection(node_options) + + # remove self + if cp in node_options: + node_options.remove(cp) + + # remove nodes that are cp's providers + node_options = node_options.difference(self.providers[cp]) + + # remove nodes we are already connected to + for j in self.G.neighbors(cp): + if j in node_options: + node_options.remove(j) + + if len(node_options) > 0: + j = self.seed.sample(list(node_options), 1)[0] + self.add_edge(cp, j, "peer") + self.G.nodes[cp]["peers"] += 1 + self.G.nodes[j]["peers"] += 1 + return True + else: + return False + + def graph_regions(self, rn): + """Initializes AS network regions. + + Parameters + ---------- + rn: integer + Number of regions + """ + + self.regions = {} + for i in range(rn): + self.regions["REG" + str(i)] = set() + + def add_peering_links(self, from_kind, to_kind): + """Utility function to add peering links among node groups.""" + peer_link_method = None + if from_kind == "M": + peer_link_method = self.add_m_peering_link + m = self.p_m_m + if from_kind == "CP": + peer_link_method = self.add_cp_peering_link + if to_kind == "M": + m = self.p_cp_m + else: + m = self.p_cp_cp + + for i in self.nodes[from_kind]: + num = uniform_int_from_avg(0, m, self.seed) + for _ in range(num): + peer_link_method(i, to_kind) + + def generate(self): + """Generates a random AS network graph as described in [1]. + + Returns + ------- + G: Graph object + + Notes + ----- + The process steps are the following: first we create the core network + of tier one nodes, then we add the middle tier (M), the content + provider (CP) and the customer (C) nodes along with their transit edges + (link i,j means i is customer of j). Finally we add peering links + between M nodes, between M and CP nodes and between CP node couples. + For a detailed description of the algorithm, please refer to [1]. + + References + ---------- + [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of + BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas + in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010. + """ + + self.graph_regions(5) + self.customers = {} + self.providers = {} + self.nodes = {"T": set(), "M": set(), "CP": set(), "C": set()} + + self.t_graph() + self.nodes["T"] = set(self.G.nodes()) + + i = len(self.nodes["T"]) + for _ in range(self.n_m): + self.nodes["M"].add(self.add_node(i, "M", 0.2, self.d_m, self.t_m)) + i += 1 + for _ in range(self.n_cp): + self.nodes["CP"].add(self.add_node(i, "CP", 0.05, self.d_cp, self.t_cp)) + i += 1 + for _ in range(self.n_c): + self.nodes["C"].add(self.add_node(i, "C", 0, self.d_c, self.t_c)) + i += 1 + + self.add_peering_links("M", "M") + self.add_peering_links("CP", "M") + self.add_peering_links("CP", "CP") + + return self.G + + +@py_random_state(1) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_internet_as_graph(n, seed=None): + """Generates a random undirected graph resembling the Internet AS network + + Parameters + ---------- + n: integer in [1000, 10000] + Number of graph nodes + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G: Networkx Graph object + A randomly generated undirected graph + + Notes + ----- + This algorithm returns an undirected graph resembling the Internet + Autonomous System (AS) network, it uses the approach by Elmokashfi et al. + [1]_ and it grants the properties described in the related paper [1]_. + + Each node models an autonomous system, with an attribute 'type' specifying + its kind; tier-1 (T), mid-level (M), customer (C) or content-provider (CP). + Each edge models an ADV communication link (hence, bidirectional) with + attributes: + + - type: transit|peer, the kind of commercial agreement between nodes; + - customer: , the identifier of the node acting as customer + ('none' if type is peer). + + References + ---------- + .. [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of + BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas + in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010. + """ + + GG = AS_graph_generator(n, seed) + G = GG.generate() + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/intersection.py b/venv/lib/python3.10/site-packages/networkx/generators/intersection.py new file mode 100644 index 0000000000000000000000000000000000000000..e63af5be8eed2e65b9cced2cc50827ffe6a802ba --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/intersection.py @@ -0,0 +1,125 @@ +""" +Generators for random intersection graphs. +""" + +import networkx as nx +from networkx.utils import py_random_state + +__all__ = [ + "uniform_random_intersection_graph", + "k_random_intersection_graph", + "general_random_intersection_graph", +] + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def uniform_random_intersection_graph(n, m, p, seed=None): + """Returns a uniform random intersection graph. + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set (nodes) + m : int + The number of nodes in the second bipartite set (attributes) + p : float + Probability of connecting nodes between bipartite sets + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + See Also + -------- + gnp_random_graph + + References + ---------- + .. [1] K.B. Singer-Cohen, Random Intersection Graphs, 1995, + PhD thesis, Johns Hopkins University + .. [2] Fill, J. A., Scheinerman, E. R., and Singer-Cohen, K. B., + Random intersection graphs when m = !(n): + An equivalence theorem relating the evolution of the g(n, m, p) + and g(n, p) models. Random Struct. Algorithms 16, 2 (2000), 156–176. + """ + from networkx.algorithms import bipartite + + G = bipartite.random_graph(n, m, p, seed) + return nx.projected_graph(G, range(n)) + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def k_random_intersection_graph(n, m, k, seed=None): + """Returns a intersection graph with randomly chosen attribute sets for + each node that are of equal size (k). + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set (nodes) + m : int + The number of nodes in the second bipartite set (attributes) + k : float + Size of attribute set to assign to each node. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + See Also + -------- + gnp_random_graph, uniform_random_intersection_graph + + References + ---------- + .. [1] Godehardt, E., and Jaworski, J. + Two models of random intersection graphs and their applications. + Electronic Notes in Discrete Mathematics 10 (2001), 129--132. + """ + G = nx.empty_graph(n + m) + mset = range(n, n + m) + for v in range(n): + targets = seed.sample(mset, k) + G.add_edges_from(zip([v] * len(targets), targets)) + return nx.projected_graph(G, range(n)) + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def general_random_intersection_graph(n, m, p, seed=None): + """Returns a random intersection graph with independent probabilities + for connections between node and attribute sets. + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set (nodes) + m : int + The number of nodes in the second bipartite set (attributes) + p : list of floats of length m + Probabilities for connecting nodes to each attribute + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + See Also + -------- + gnp_random_graph, uniform_random_intersection_graph + + References + ---------- + .. [1] Nikoletseas, S. E., Raptopoulos, C., and Spirakis, P. G. + The existence and efficient construction of large independent sets + in general random intersection graphs. In ICALP (2004), J. D´ıaz, + J. Karhum¨aki, A. Lepist¨o, and D. Sannella, Eds., vol. 3142 + of Lecture Notes in Computer Science, Springer, pp. 1029–1040. + """ + if len(p) != m: + raise ValueError("Probability list p must have m elements.") + G = nx.empty_graph(n + m) + mset = range(n, n + m) + for u in range(n): + for v, q in zip(mset, p): + if seed.random() < q: + G.add_edge(u, v) + return nx.projected_graph(G, range(n)) diff --git a/venv/lib/python3.10/site-packages/networkx/generators/interval_graph.py b/venv/lib/python3.10/site-packages/networkx/generators/interval_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..6a3fda45acec52af6a5f060b96d9af1067fc002b --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/interval_graph.py @@ -0,0 +1,70 @@ +""" +Generators for interval graph. +""" + +from collections.abc import Sequence + +import networkx as nx + +__all__ = ["interval_graph"] + + +@nx._dispatchable(graphs=None, returns_graph=True) +def interval_graph(intervals): + """Generates an interval graph for a list of intervals given. + + In graph theory, an interval graph is an undirected graph formed from a set + of closed intervals on the real line, with a vertex for each interval + and an edge between vertices whose intervals intersect. + It is the intersection graph of the intervals. + + More information can be found at: + https://en.wikipedia.org/wiki/Interval_graph + + Parameters + ---------- + intervals : a sequence of intervals, say (l, r) where l is the left end, + and r is the right end of the closed interval. + + Returns + ------- + G : networkx graph + + Examples + -------- + >>> intervals = [(-2, 3), [1, 4], (2, 3), (4, 6)] + >>> G = nx.interval_graph(intervals) + >>> sorted(G.edges) + [((-2, 3), (1, 4)), ((-2, 3), (2, 3)), ((1, 4), (2, 3)), ((1, 4), (4, 6))] + + Raises + ------ + :exc:`TypeError` + if `intervals` contains None or an element which is not + collections.abc.Sequence or not a length of 2. + :exc:`ValueError` + if `intervals` contains an interval such that min1 > max1 + where min1,max1 = interval + """ + intervals = list(intervals) + for interval in intervals: + if not (isinstance(interval, Sequence) and len(interval) == 2): + raise TypeError( + "Each interval must have length 2, and be a " + "collections.abc.Sequence such as tuple or list." + ) + if interval[0] > interval[1]: + raise ValueError(f"Interval must have lower value first. Got {interval}") + + graph = nx.Graph() + + tupled_intervals = [tuple(interval) for interval in intervals] + graph.add_nodes_from(tupled_intervals) + + while tupled_intervals: + min1, max1 = interval1 = tupled_intervals.pop() + for interval2 in tupled_intervals: + min2, max2 = interval2 + if max1 >= min2 and max2 >= min1: + graph.add_edge(interval1, interval2) + return graph diff --git a/venv/lib/python3.10/site-packages/networkx/generators/joint_degree_seq.py b/venv/lib/python3.10/site-packages/networkx/generators/joint_degree_seq.py new file mode 100644 index 0000000000000000000000000000000000000000..c426df944ad27aef4584371838a6ddb280b90dca --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/joint_degree_seq.py @@ -0,0 +1,664 @@ +"""Generate graphs with a given joint degree and directed joint degree""" + +import networkx as nx +from networkx.utils import py_random_state + +__all__ = [ + "is_valid_joint_degree", + "is_valid_directed_joint_degree", + "joint_degree_graph", + "directed_joint_degree_graph", +] + + +@nx._dispatchable(graphs=None) +def is_valid_joint_degree(joint_degrees): + """Checks whether the given joint degree dictionary is realizable. + + A *joint degree dictionary* is a dictionary of dictionaries, in + which entry ``joint_degrees[k][l]`` is an integer representing the + number of edges joining nodes of degree *k* with nodes of degree + *l*. Such a dictionary is realizable as a simple graph if and only + if the following conditions are satisfied. + + - each entry must be an integer, + - the total number of nodes of degree *k*, computed by + ``sum(joint_degrees[k].values()) / k``, must be an integer, + - the total number of edges joining nodes of degree *k* with + nodes of degree *l* cannot exceed the total number of possible edges, + - each diagonal entry ``joint_degrees[k][k]`` must be even (this is + a convention assumed by the :func:`joint_degree_graph` function). + + + Parameters + ---------- + joint_degrees : dictionary of dictionary of integers + A joint degree dictionary in which entry ``joint_degrees[k][l]`` + is the number of edges joining nodes of degree *k* with nodes of + degree *l*. + + Returns + ------- + bool + Whether the given joint degree dictionary is realizable as a + simple graph. + + References + ---------- + .. [1] M. Gjoka, M. Kurant, A. Markopoulou, "2.5K Graphs: from Sampling + to Generation", IEEE Infocom, 2013. + .. [2] I. Stanton, A. Pinar, "Constructing and sampling graphs with a + prescribed joint degree distribution", Journal of Experimental + Algorithmics, 2012. + """ + + degree_count = {} + for k in joint_degrees: + if k > 0: + k_size = sum(joint_degrees[k].values()) / k + if not k_size.is_integer(): + return False + degree_count[k] = k_size + + for k in joint_degrees: + for l in joint_degrees[k]: + if not float(joint_degrees[k][l]).is_integer(): + return False + + if (k != l) and (joint_degrees[k][l] > degree_count[k] * degree_count[l]): + return False + elif k == l: + if joint_degrees[k][k] > degree_count[k] * (degree_count[k] - 1): + return False + if joint_degrees[k][k] % 2 != 0: + return False + + # if all above conditions have been satisfied then the input + # joint degree is realizable as a simple graph. + return True + + +def _neighbor_switch(G, w, unsat, h_node_residual, avoid_node_id=None): + """Releases one free stub for ``w``, while preserving joint degree in G. + + Parameters + ---------- + G : NetworkX graph + Graph in which the neighbor switch will take place. + w : integer + Node id for which we will execute this neighbor switch. + unsat : set of integers + Set of unsaturated node ids that have the same degree as w. + h_node_residual: dictionary of integers + Keeps track of the remaining stubs for a given node. + avoid_node_id: integer + Node id to avoid when selecting w_prime. + + Notes + ----- + First, it selects *w_prime*, an unsaturated node that has the same degree + as ``w``. Second, it selects *switch_node*, a neighbor node of ``w`` that + is not connected to *w_prime*. Then it executes an edge swap i.e. removes + (``w``,*switch_node*) and adds (*w_prime*,*switch_node*). Gjoka et. al. [1] + prove that such an edge swap is always possible. + + References + ---------- + .. [1] M. Gjoka, B. Tillman, A. Markopoulou, "Construction of Simple + Graphs with a Target Joint Degree Matrix and Beyond", IEEE Infocom, '15 + """ + + if (avoid_node_id is None) or (h_node_residual[avoid_node_id] > 1): + # select unsaturated node w_prime that has the same degree as w + w_prime = next(iter(unsat)) + else: + # assume that the node pair (v,w) has been selected for connection. if + # - neighbor_switch is called for node w, + # - nodes v and w have the same degree, + # - node v=avoid_node_id has only one stub left, + # then prevent v=avoid_node_id from being selected as w_prime. + + iter_var = iter(unsat) + while True: + w_prime = next(iter_var) + if w_prime != avoid_node_id: + break + + # select switch_node, a neighbor of w, that is not connected to w_prime + w_prime_neighbs = G[w_prime] # slightly faster declaring this variable + for v in G[w]: + if (v not in w_prime_neighbs) and (v != w_prime): + switch_node = v + break + + # remove edge (w,switch_node), add edge (w_prime,switch_node) and update + # data structures + G.remove_edge(w, switch_node) + G.add_edge(w_prime, switch_node) + h_node_residual[w] += 1 + h_node_residual[w_prime] -= 1 + if h_node_residual[w_prime] == 0: + unsat.remove(w_prime) + + +@py_random_state(1) +@nx._dispatchable(graphs=None, returns_graph=True) +def joint_degree_graph(joint_degrees, seed=None): + """Generates a random simple graph with the given joint degree dictionary. + + Parameters + ---------- + joint_degrees : dictionary of dictionary of integers + A joint degree dictionary in which entry ``joint_degrees[k][l]`` is the + number of edges joining nodes of degree *k* with nodes of degree *l*. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : Graph + A graph with the specified joint degree dictionary. + + Raises + ------ + NetworkXError + If *joint_degrees* dictionary is not realizable. + + Notes + ----- + In each iteration of the "while loop" the algorithm picks two disconnected + nodes *v* and *w*, of degree *k* and *l* correspondingly, for which + ``joint_degrees[k][l]`` has not reached its target yet. It then adds + edge (*v*, *w*) and increases the number of edges in graph G by one. + + The intelligence of the algorithm lies in the fact that it is always + possible to add an edge between such disconnected nodes *v* and *w*, + even if one or both nodes do not have free stubs. That is made possible by + executing a "neighbor switch", an edge rewiring move that releases + a free stub while keeping the joint degree of G the same. + + The algorithm continues for E (number of edges) iterations of + the "while loop", at the which point all entries of the given + ``joint_degrees[k][l]`` have reached their target values and the + construction is complete. + + References + ---------- + .. [1] M. Gjoka, B. Tillman, A. Markopoulou, "Construction of Simple + Graphs with a Target Joint Degree Matrix and Beyond", IEEE Infocom, '15 + + Examples + -------- + >>> joint_degrees = { + ... 1: {4: 1}, + ... 2: {2: 2, 3: 2, 4: 2}, + ... 3: {2: 2, 4: 1}, + ... 4: {1: 1, 2: 2, 3: 1}, + ... } + >>> G = nx.joint_degree_graph(joint_degrees) + >>> + """ + + if not is_valid_joint_degree(joint_degrees): + msg = "Input joint degree dict not realizable as a simple graph" + raise nx.NetworkXError(msg) + + # compute degree count from joint_degrees + degree_count = {k: sum(l.values()) // k for k, l in joint_degrees.items() if k > 0} + + # start with empty N-node graph + N = sum(degree_count.values()) + G = nx.empty_graph(N) + + # for a given degree group, keep the list of all node ids + h_degree_nodelist = {} + + # for a given node, keep track of the remaining stubs + h_node_residual = {} + + # populate h_degree_nodelist and h_node_residual + nodeid = 0 + for degree, num_nodes in degree_count.items(): + h_degree_nodelist[degree] = range(nodeid, nodeid + num_nodes) + for v in h_degree_nodelist[degree]: + h_node_residual[v] = degree + nodeid += int(num_nodes) + + # iterate over every degree pair (k,l) and add the number of edges given + # for each pair + for k in joint_degrees: + for l in joint_degrees[k]: + # n_edges_add is the number of edges to add for the + # degree pair (k,l) + n_edges_add = joint_degrees[k][l] + + if (n_edges_add > 0) and (k >= l): + # number of nodes with degree k and l + k_size = degree_count[k] + l_size = degree_count[l] + + # k_nodes and l_nodes consist of all nodes of degree k and l + k_nodes = h_degree_nodelist[k] + l_nodes = h_degree_nodelist[l] + + # k_unsat and l_unsat consist of nodes of degree k and l that + # are unsaturated (nodes that have at least 1 available stub) + k_unsat = {v for v in k_nodes if h_node_residual[v] > 0} + + if k != l: + l_unsat = {w for w in l_nodes if h_node_residual[w] > 0} + else: + l_unsat = k_unsat + n_edges_add = joint_degrees[k][l] // 2 + + while n_edges_add > 0: + # randomly pick nodes v and w that have degrees k and l + v = k_nodes[seed.randrange(k_size)] + w = l_nodes[seed.randrange(l_size)] + + # if nodes v and w are disconnected then attempt to connect + if not G.has_edge(v, w) and (v != w): + # if node v has no free stubs then do neighbor switch + if h_node_residual[v] == 0: + _neighbor_switch(G, v, k_unsat, h_node_residual) + + # if node w has no free stubs then do neighbor switch + if h_node_residual[w] == 0: + if k != l: + _neighbor_switch(G, w, l_unsat, h_node_residual) + else: + _neighbor_switch( + G, w, l_unsat, h_node_residual, avoid_node_id=v + ) + + # add edge (v, w) and update data structures + G.add_edge(v, w) + h_node_residual[v] -= 1 + h_node_residual[w] -= 1 + n_edges_add -= 1 + + if h_node_residual[v] == 0: + k_unsat.discard(v) + if h_node_residual[w] == 0: + l_unsat.discard(w) + return G + + +@nx._dispatchable(graphs=None) +def is_valid_directed_joint_degree(in_degrees, out_degrees, nkk): + """Checks whether the given directed joint degree input is realizable + + Parameters + ---------- + in_degrees : list of integers + in degree sequence contains the in degrees of nodes. + out_degrees : list of integers + out degree sequence contains the out degrees of nodes. + nkk : dictionary of dictionary of integers + directed joint degree dictionary. for nodes of out degree k (first + level of dict) and nodes of in degree l (second level of dict) + describes the number of edges. + + Returns + ------- + boolean + returns true if given input is realizable, else returns false. + + Notes + ----- + Here is the list of conditions that the inputs (in/out degree sequences, + nkk) need to satisfy for simple directed graph realizability: + + - Condition 0: in_degrees and out_degrees have the same length + - Condition 1: nkk[k][l] is integer for all k,l + - Condition 2: sum(nkk[k])/k = number of nodes with partition id k, is an + integer and matching degree sequence + - Condition 3: number of edges and non-chords between k and l cannot exceed + maximum possible number of edges + + + References + ---------- + [1] B. Tillman, A. Markopoulou, C. T. Butts & M. Gjoka, + "Construction of Directed 2K Graphs". In Proc. of KDD 2017. + """ + V = {} # number of nodes with in/out degree. + forbidden = {} + if len(in_degrees) != len(out_degrees): + return False + + for idx in range(len(in_degrees)): + i = in_degrees[idx] + o = out_degrees[idx] + V[(i, 0)] = V.get((i, 0), 0) + 1 + V[(o, 1)] = V.get((o, 1), 0) + 1 + + forbidden[(o, i)] = forbidden.get((o, i), 0) + 1 + + S = {} # number of edges going from in/out degree nodes. + for k in nkk: + for l in nkk[k]: + val = nkk[k][l] + if not float(val).is_integer(): # condition 1 + return False + + if val > 0: + S[(k, 1)] = S.get((k, 1), 0) + val + S[(l, 0)] = S.get((l, 0), 0) + val + # condition 3 + if val + forbidden.get((k, l), 0) > V[(k, 1)] * V[(l, 0)]: + return False + + return all(S[s] / s[0] == V[s] for s in S) + + +def _directed_neighbor_switch( + G, w, unsat, h_node_residual_out, chords, h_partition_in, partition +): + """Releases one free stub for node w, while preserving joint degree in G. + + Parameters + ---------- + G : networkx directed graph + graph within which the edge swap will take place. + w : integer + node id for which we need to perform a neighbor switch. + unsat: set of integers + set of node ids that have the same degree as w and are unsaturated. + h_node_residual_out: dict of integers + for a given node, keeps track of the remaining stubs to be added. + chords: set of tuples + keeps track of available positions to add edges. + h_partition_in: dict of integers + for a given node, keeps track of its partition id (in degree). + partition: integer + partition id to check if chords have to be updated. + + Notes + ----- + First, it selects node w_prime that (1) has the same degree as w and + (2) is unsaturated. Then, it selects node v, a neighbor of w, that is + not connected to w_prime and does an edge swap i.e. removes (w,v) and + adds (w_prime,v). If neighbor switch is not possible for w using + w_prime and v, then return w_prime; in [1] it's proven that + such unsaturated nodes can be used. + + References + ---------- + [1] B. Tillman, A. Markopoulou, C. T. Butts & M. Gjoka, + "Construction of Directed 2K Graphs". In Proc. of KDD 2017. + """ + w_prime = unsat.pop() + unsat.add(w_prime) + # select node t, a neighbor of w, that is not connected to w_prime + w_neighbs = list(G.successors(w)) + # slightly faster declaring this variable + w_prime_neighbs = list(G.successors(w_prime)) + + for v in w_neighbs: + if (v not in w_prime_neighbs) and w_prime != v: + # removes (w,v), add (w_prime,v) and update data structures + G.remove_edge(w, v) + G.add_edge(w_prime, v) + + if h_partition_in[v] == partition: + chords.add((w, v)) + chords.discard((w_prime, v)) + + h_node_residual_out[w] += 1 + h_node_residual_out[w_prime] -= 1 + if h_node_residual_out[w_prime] == 0: + unsat.remove(w_prime) + return None + + # If neighbor switch didn't work, use unsaturated node + return w_prime + + +def _directed_neighbor_switch_rev( + G, w, unsat, h_node_residual_in, chords, h_partition_out, partition +): + """The reverse of directed_neighbor_switch. + + Parameters + ---------- + G : networkx directed graph + graph within which the edge swap will take place. + w : integer + node id for which we need to perform a neighbor switch. + unsat: set of integers + set of node ids that have the same degree as w and are unsaturated. + h_node_residual_in: dict of integers + for a given node, keeps track of the remaining stubs to be added. + chords: set of tuples + keeps track of available positions to add edges. + h_partition_out: dict of integers + for a given node, keeps track of its partition id (out degree). + partition: integer + partition id to check if chords have to be updated. + + Notes + ----- + Same operation as directed_neighbor_switch except it handles this operation + for incoming edges instead of outgoing. + """ + w_prime = unsat.pop() + unsat.add(w_prime) + # slightly faster declaring these as variables. + w_neighbs = list(G.predecessors(w)) + w_prime_neighbs = list(G.predecessors(w_prime)) + # select node v, a neighbor of w, that is not connected to w_prime. + for v in w_neighbs: + if (v not in w_prime_neighbs) and w_prime != v: + # removes (v,w), add (v,w_prime) and update data structures. + G.remove_edge(v, w) + G.add_edge(v, w_prime) + if h_partition_out[v] == partition: + chords.add((v, w)) + chords.discard((v, w_prime)) + + h_node_residual_in[w] += 1 + h_node_residual_in[w_prime] -= 1 + if h_node_residual_in[w_prime] == 0: + unsat.remove(w_prime) + return None + + # If neighbor switch didn't work, use the unsaturated node. + return w_prime + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def directed_joint_degree_graph(in_degrees, out_degrees, nkk, seed=None): + """Generates a random simple directed graph with the joint degree. + + Parameters + ---------- + degree_seq : list of tuples (of size 3) + degree sequence contains tuples of nodes with node id, in degree and + out degree. + nkk : dictionary of dictionary of integers + directed joint degree dictionary, for nodes of out degree k (first + level of dict) and nodes of in degree l (second level of dict) + describes the number of edges. + seed : hashable object, optional + Seed for random number generator. + + Returns + ------- + G : Graph + A directed graph with the specified inputs. + + Raises + ------ + NetworkXError + If degree_seq and nkk are not realizable as a simple directed graph. + + + Notes + ----- + Similarly to the undirected version: + In each iteration of the "while loop" the algorithm picks two disconnected + nodes v and w, of degree k and l correspondingly, for which nkk[k][l] has + not reached its target yet i.e. (for given k,l): n_edges_add < nkk[k][l]. + It then adds edge (v,w) and always increases the number of edges in graph G + by one. + + The intelligence of the algorithm lies in the fact that it is always + possible to add an edge between disconnected nodes v and w, for which + nkk[degree(v)][degree(w)] has not reached its target, even if one or both + nodes do not have free stubs. If either node v or w does not have a free + stub, we perform a "neighbor switch", an edge rewiring move that releases a + free stub while keeping nkk the same. + + The difference for the directed version lies in the fact that neighbor + switches might not be able to rewire, but in these cases unsaturated nodes + can be reassigned to use instead, see [1] for detailed description and + proofs. + + The algorithm continues for E (number of edges in the graph) iterations of + the "while loop", at which point all entries of the given nkk[k][l] have + reached their target values and the construction is complete. + + References + ---------- + [1] B. Tillman, A. Markopoulou, C. T. Butts & M. Gjoka, + "Construction of Directed 2K Graphs". In Proc. of KDD 2017. + + Examples + -------- + >>> in_degrees = [0, 1, 1, 2] + >>> out_degrees = [1, 1, 1, 1] + >>> nkk = {1: {1: 2, 2: 2}} + >>> G = nx.directed_joint_degree_graph(in_degrees, out_degrees, nkk) + >>> + """ + if not is_valid_directed_joint_degree(in_degrees, out_degrees, nkk): + msg = "Input is not realizable as a simple graph" + raise nx.NetworkXError(msg) + + # start with an empty directed graph. + G = nx.DiGraph() + + # for a given group, keep the list of all node ids. + h_degree_nodelist_in = {} + h_degree_nodelist_out = {} + # for a given group, keep the list of all unsaturated node ids. + h_degree_nodelist_in_unsat = {} + h_degree_nodelist_out_unsat = {} + # for a given node, keep track of the remaining stubs to be added. + h_node_residual_out = {} + h_node_residual_in = {} + # for a given node, keep track of the partition id. + h_partition_out = {} + h_partition_in = {} + # keep track of non-chords between pairs of partition ids. + non_chords = {} + + # populate data structures + for idx, i in enumerate(in_degrees): + idx = int(idx) + if i > 0: + h_degree_nodelist_in.setdefault(i, []) + h_degree_nodelist_in_unsat.setdefault(i, set()) + h_degree_nodelist_in[i].append(idx) + h_degree_nodelist_in_unsat[i].add(idx) + h_node_residual_in[idx] = i + h_partition_in[idx] = i + + for idx, o in enumerate(out_degrees): + o = out_degrees[idx] + non_chords[(o, in_degrees[idx])] = non_chords.get((o, in_degrees[idx]), 0) + 1 + idx = int(idx) + if o > 0: + h_degree_nodelist_out.setdefault(o, []) + h_degree_nodelist_out_unsat.setdefault(o, set()) + h_degree_nodelist_out[o].append(idx) + h_degree_nodelist_out_unsat[o].add(idx) + h_node_residual_out[idx] = o + h_partition_out[idx] = o + + G.add_node(idx) + + nk_in = {} + nk_out = {} + for p in h_degree_nodelist_in: + nk_in[p] = len(h_degree_nodelist_in[p]) + for p in h_degree_nodelist_out: + nk_out[p] = len(h_degree_nodelist_out[p]) + + # iterate over every degree pair (k,l) and add the number of edges given + # for each pair. + for k in nkk: + for l in nkk[k]: + n_edges_add = nkk[k][l] + + if n_edges_add > 0: + # chords contains a random set of potential edges. + chords = set() + + k_len = nk_out[k] + l_len = nk_in[l] + chords_sample = seed.sample( + range(k_len * l_len), n_edges_add + non_chords.get((k, l), 0) + ) + + num = 0 + while len(chords) < n_edges_add: + i = h_degree_nodelist_out[k][chords_sample[num] % k_len] + j = h_degree_nodelist_in[l][chords_sample[num] // k_len] + num += 1 + if i != j: + chords.add((i, j)) + + # k_unsat and l_unsat consist of nodes of in/out degree k and l + # that are unsaturated i.e. those nodes that have at least one + # available stub + k_unsat = h_degree_nodelist_out_unsat[k] + l_unsat = h_degree_nodelist_in_unsat[l] + + while n_edges_add > 0: + v, w = chords.pop() + chords.add((v, w)) + + # if node v has no free stubs then do neighbor switch. + if h_node_residual_out[v] == 0: + _v = _directed_neighbor_switch( + G, + v, + k_unsat, + h_node_residual_out, + chords, + h_partition_in, + l, + ) + if _v is not None: + v = _v + + # if node w has no free stubs then do neighbor switch. + if h_node_residual_in[w] == 0: + _w = _directed_neighbor_switch_rev( + G, + w, + l_unsat, + h_node_residual_in, + chords, + h_partition_out, + k, + ) + if _w is not None: + w = _w + + # add edge (v,w) and update data structures. + G.add_edge(v, w) + h_node_residual_out[v] -= 1 + h_node_residual_in[w] -= 1 + n_edges_add -= 1 + chords.discard((v, w)) + + if h_node_residual_out[v] == 0: + k_unsat.discard(v) + if h_node_residual_in[w] == 0: + l_unsat.discard(w) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/lattice.py b/venv/lib/python3.10/site-packages/networkx/generators/lattice.py new file mode 100644 index 0000000000000000000000000000000000000000..95e520d2ce1fbbd16c10f42348807062f855e50b --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/lattice.py @@ -0,0 +1,367 @@ +"""Functions for generating grid graphs and lattices + +The :func:`grid_2d_graph`, :func:`triangular_lattice_graph`, and +:func:`hexagonal_lattice_graph` functions correspond to the three +`regular tilings of the plane`_, the square, triangular, and hexagonal +tilings, respectively. :func:`grid_graph` and :func:`hypercube_graph` +are similar for arbitrary dimensions. Useful relevant discussion can +be found about `Triangular Tiling`_, and `Square, Hex and Triangle Grids`_ + +.. _regular tilings of the plane: https://en.wikipedia.org/wiki/List_of_regular_polytopes_and_compounds#Euclidean_tilings +.. _Square, Hex and Triangle Grids: http://www-cs-students.stanford.edu/~amitp/game-programming/grids/ +.. _Triangular Tiling: https://en.wikipedia.org/wiki/Triangular_tiling + +""" + +from itertools import repeat +from math import sqrt + +import networkx as nx +from networkx.classes import set_node_attributes +from networkx.exception import NetworkXError +from networkx.generators.classic import cycle_graph, empty_graph, path_graph +from networkx.relabel import relabel_nodes +from networkx.utils import flatten, nodes_or_number, pairwise + +__all__ = [ + "grid_2d_graph", + "grid_graph", + "hypercube_graph", + "triangular_lattice_graph", + "hexagonal_lattice_graph", +] + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number([0, 1]) +def grid_2d_graph(m, n, periodic=False, create_using=None): + """Returns the two-dimensional grid graph. + + The grid graph has each node connected to its four nearest neighbors. + + Parameters + ---------- + m, n : int or iterable container of nodes + If an integer, nodes are from `range(n)`. + If a container, elements become the coordinate of the nodes. + + periodic : bool or iterable + If `periodic` is True, both dimensions are periodic. If False, none + are periodic. If `periodic` is iterable, it should yield 2 bool + values indicating whether the 1st and 2nd axes, respectively, are + periodic. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + NetworkX graph + The (possibly periodic) grid graph of the specified dimensions. + + """ + G = empty_graph(0, create_using) + row_name, rows = m + col_name, cols = n + G.add_nodes_from((i, j) for i in rows for j in cols) + G.add_edges_from(((i, j), (pi, j)) for pi, i in pairwise(rows) for j in cols) + G.add_edges_from(((i, j), (i, pj)) for i in rows for pj, j in pairwise(cols)) + + try: + periodic_r, periodic_c = periodic + except TypeError: + periodic_r = periodic_c = periodic + + if periodic_r and len(rows) > 2: + first = rows[0] + last = rows[-1] + G.add_edges_from(((first, j), (last, j)) for j in cols) + if periodic_c and len(cols) > 2: + first = cols[0] + last = cols[-1] + G.add_edges_from(((i, first), (i, last)) for i in rows) + # both directions for directed + if G.is_directed(): + G.add_edges_from((v, u) for u, v in G.edges()) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def grid_graph(dim, periodic=False): + """Returns the *n*-dimensional grid graph. + + The dimension *n* is the length of the list `dim` and the size in + each dimension is the value of the corresponding list element. + + Parameters + ---------- + dim : list or tuple of numbers or iterables of nodes + 'dim' is a tuple or list with, for each dimension, either a number + that is the size of that dimension or an iterable of nodes for + that dimension. The dimension of the grid_graph is the length + of `dim`. + + periodic : bool or iterable + If `periodic` is True, all dimensions are periodic. If False all + dimensions are not periodic. If `periodic` is iterable, it should + yield `dim` bool values each of which indicates whether the + corresponding axis is periodic. + + Returns + ------- + NetworkX graph + The (possibly periodic) grid graph of the specified dimensions. + + Examples + -------- + To produce a 2 by 3 by 4 grid graph, a graph on 24 nodes: + + >>> from networkx import grid_graph + >>> G = grid_graph(dim=(2, 3, 4)) + >>> len(G) + 24 + >>> G = grid_graph(dim=(range(7, 9), range(3, 6))) + >>> len(G) + 6 + """ + from networkx.algorithms.operators.product import cartesian_product + + if not dim: + return empty_graph(0) + + try: + func = (cycle_graph if p else path_graph for p in periodic) + except TypeError: + func = repeat(cycle_graph if periodic else path_graph) + + G = next(func)(dim[0]) + for current_dim in dim[1:]: + Gnew = next(func)(current_dim) + G = cartesian_product(Gnew, G) + # graph G is done but has labels of the form (1, (2, (3, 1))) so relabel + H = relabel_nodes(G, flatten) + return H + + +@nx._dispatchable(graphs=None, returns_graph=True) +def hypercube_graph(n): + """Returns the *n*-dimensional hypercube graph. + + The nodes are the integers between 0 and ``2 ** n - 1``, inclusive. + + For more information on the hypercube graph, see the Wikipedia + article `Hypercube graph`_. + + .. _Hypercube graph: https://en.wikipedia.org/wiki/Hypercube_graph + + Parameters + ---------- + n : int + The dimension of the hypercube. + The number of nodes in the graph will be ``2 ** n``. + + Returns + ------- + NetworkX graph + The hypercube graph of dimension *n*. + """ + dim = n * [2] + G = grid_graph(dim) + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def triangular_lattice_graph( + m, n, periodic=False, with_positions=True, create_using=None +): + r"""Returns the $m$ by $n$ triangular lattice graph. + + The `triangular lattice graph`_ is a two-dimensional `grid graph`_ in + which each square unit has a diagonal edge (each grid unit has a chord). + + The returned graph has $m$ rows and $n$ columns of triangles. Rows and + columns include both triangles pointing up and down. Rows form a strip + of constant height. Columns form a series of diamond shapes, staggered + with the columns on either side. Another way to state the size is that + the nodes form a grid of `m+1` rows and `(n + 1) // 2` columns. + The odd row nodes are shifted horizontally relative to the even rows. + + Directed graph types have edges pointed up or right. + + Positions of nodes are computed by default or `with_positions is True`. + The position of each node (embedded in a euclidean plane) is stored in + the graph using equilateral triangles with sidelength 1. + The height between rows of nodes is thus $\sqrt(3)/2$. + Nodes lie in the first quadrant with the node $(0, 0)$ at the origin. + + .. _triangular lattice graph: http://mathworld.wolfram.com/TriangularGrid.html + .. _grid graph: http://www-cs-students.stanford.edu/~amitp/game-programming/grids/ + .. _Triangular Tiling: https://en.wikipedia.org/wiki/Triangular_tiling + + Parameters + ---------- + m : int + The number of rows in the lattice. + + n : int + The number of columns in the lattice. + + periodic : bool (default: False) + If True, join the boundary vertices of the grid using periodic + boundary conditions. The join between boundaries is the final row + and column of triangles. This means there is one row and one column + fewer nodes for the periodic lattice. Periodic lattices require + `m >= 3`, `n >= 5` and are allowed but misaligned if `m` or `n` are odd + + with_positions : bool (default: True) + Store the coordinates of each node in the graph node attribute 'pos'. + The coordinates provide a lattice with equilateral triangles. + Periodic positions shift the nodes vertically in a nonlinear way so + the edges don't overlap so much. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + NetworkX graph + The *m* by *n* triangular lattice graph. + """ + H = empty_graph(0, create_using) + if n == 0 or m == 0: + return H + if periodic: + if n < 5 or m < 3: + msg = f"m > 2 and n > 4 required for periodic. m={m}, n={n}" + raise NetworkXError(msg) + + N = (n + 1) // 2 # number of nodes in row + rows = range(m + 1) + cols = range(N + 1) + # Make grid + H.add_edges_from(((i, j), (i + 1, j)) for j in rows for i in cols[:N]) + H.add_edges_from(((i, j), (i, j + 1)) for j in rows[:m] for i in cols) + # add diagonals + H.add_edges_from(((i, j), (i + 1, j + 1)) for j in rows[1:m:2] for i in cols[:N]) + H.add_edges_from(((i + 1, j), (i, j + 1)) for j in rows[:m:2] for i in cols[:N]) + # identify boundary nodes if periodic + from networkx.algorithms.minors import contracted_nodes + + if periodic is True: + for i in cols: + H = contracted_nodes(H, (i, 0), (i, m)) + for j in rows[:m]: + H = contracted_nodes(H, (0, j), (N, j)) + elif n % 2: + # remove extra nodes + H.remove_nodes_from((N, j) for j in rows[1::2]) + + # Add position node attributes + if with_positions: + ii = (i for i in cols for j in rows) + jj = (j for i in cols for j in rows) + xx = (0.5 * (j % 2) + i for i in cols for j in rows) + h = sqrt(3) / 2 + if periodic: + yy = (h * j + 0.01 * i * i for i in cols for j in rows) + else: + yy = (h * j for i in cols for j in rows) + pos = {(i, j): (x, y) for i, j, x, y in zip(ii, jj, xx, yy) if (i, j) in H} + set_node_attributes(H, pos, "pos") + return H + + +@nx._dispatchable(graphs=None, returns_graph=True) +def hexagonal_lattice_graph( + m, n, periodic=False, with_positions=True, create_using=None +): + """Returns an `m` by `n` hexagonal lattice graph. + + The *hexagonal lattice graph* is a graph whose nodes and edges are + the `hexagonal tiling`_ of the plane. + + The returned graph will have `m` rows and `n` columns of hexagons. + `Odd numbered columns`_ are shifted up relative to even numbered columns. + + Positions of nodes are computed by default or `with_positions is True`. + Node positions creating the standard embedding in the plane + with sidelength 1 and are stored in the node attribute 'pos'. + `pos = nx.get_node_attributes(G, 'pos')` creates a dict ready for drawing. + + .. _hexagonal tiling: https://en.wikipedia.org/wiki/Hexagonal_tiling + .. _Odd numbered columns: http://www-cs-students.stanford.edu/~amitp/game-programming/grids/ + + Parameters + ---------- + m : int + The number of rows of hexagons in the lattice. + + n : int + The number of columns of hexagons in the lattice. + + periodic : bool + Whether to make a periodic grid by joining the boundary vertices. + For this to work `n` must be even and both `n > 1` and `m > 1`. + The periodic connections create another row and column of hexagons + so these graphs have fewer nodes as boundary nodes are identified. + + with_positions : bool (default: True) + Store the coordinates of each node in the graph node attribute 'pos'. + The coordinates provide a lattice with vertical columns of hexagons + offset to interleave and cover the plane. + Periodic positions shift the nodes vertically in a nonlinear way so + the edges don't overlap so much. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + If graph is directed, edges will point up or right. + + Returns + ------- + NetworkX graph + The *m* by *n* hexagonal lattice graph. + """ + G = empty_graph(0, create_using) + if m == 0 or n == 0: + return G + if periodic and (n % 2 == 1 or m < 2 or n < 2): + msg = "periodic hexagonal lattice needs m > 1, n > 1 and even n" + raise NetworkXError(msg) + + M = 2 * m # twice as many nodes as hexagons vertically + rows = range(M + 2) + cols = range(n + 1) + # make lattice + col_edges = (((i, j), (i, j + 1)) for i in cols for j in rows[: M + 1]) + row_edges = (((i, j), (i + 1, j)) for i in cols[:n] for j in rows if i % 2 == j % 2) + G.add_edges_from(col_edges) + G.add_edges_from(row_edges) + # Remove corner nodes with one edge + G.remove_node((0, M + 1)) + G.remove_node((n, (M + 1) * (n % 2))) + + # identify boundary nodes if periodic + from networkx.algorithms.minors import contracted_nodes + + if periodic: + for i in cols[:n]: + G = contracted_nodes(G, (i, 0), (i, M)) + for i in cols[1:]: + G = contracted_nodes(G, (i, 1), (i, M + 1)) + for j in rows[1:M]: + G = contracted_nodes(G, (0, j), (n, j)) + G.remove_node((n, M)) + + # calc position in embedded space + ii = (i for i in cols for j in rows) + jj = (j for i in cols for j in rows) + xx = (0.5 + i + i // 2 + (j % 2) * ((i % 2) - 0.5) for i in cols for j in rows) + h = sqrt(3) / 2 + if periodic: + yy = (h * j + 0.01 * i * i for i in cols for j in rows) + else: + yy = (h * j for i in cols for j in rows) + # exclude nodes not in G + pos = {(i, j): (x, y) for i, j, x, y in zip(ii, jj, xx, yy) if (i, j) in G} + set_node_attributes(G, pos, "pos") + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/line.py b/venv/lib/python3.10/site-packages/networkx/generators/line.py new file mode 100644 index 0000000000000000000000000000000000000000..87d251827f5118cd97d7e575a8553f44b7e041c3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/line.py @@ -0,0 +1,500 @@ +"""Functions for generating line graphs.""" + +from collections import defaultdict +from functools import partial +from itertools import combinations + +import networkx as nx +from networkx.utils import arbitrary_element +from networkx.utils.decorators import not_implemented_for + +__all__ = ["line_graph", "inverse_line_graph"] + + +@nx._dispatchable(returns_graph=True) +def line_graph(G, create_using=None): + r"""Returns the line graph of the graph or digraph `G`. + + The line graph of a graph `G` has a node for each edge in `G` and an + edge joining those nodes if the two edges in `G` share a common node. For + directed graphs, nodes are adjacent exactly when the edges they represent + form a directed path of length two. + + The nodes of the line graph are 2-tuples of nodes in the original graph (or + 3-tuples for multigraphs, with the key of the edge as the third element). + + For information about self-loops and more discussion, see the **Notes** + section below. + + Parameters + ---------- + G : graph + A NetworkX Graph, DiGraph, MultiGraph, or MultiDigraph. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + L : graph + The line graph of G. + + Examples + -------- + >>> G = nx.star_graph(3) + >>> L = nx.line_graph(G) + >>> print(sorted(map(sorted, L.edges()))) # makes a 3-clique, K3 + [[(0, 1), (0, 2)], [(0, 1), (0, 3)], [(0, 2), (0, 3)]] + + Edge attributes from `G` are not copied over as node attributes in `L`, but + attributes can be copied manually: + + >>> G = nx.path_graph(4) + >>> G.add_edges_from((u, v, {"tot": u + v}) for u, v in G.edges) + >>> G.edges(data=True) + EdgeDataView([(0, 1, {'tot': 1}), (1, 2, {'tot': 3}), (2, 3, {'tot': 5})]) + >>> H = nx.line_graph(G) + >>> H.add_nodes_from((node, G.edges[node]) for node in H) + >>> H.nodes(data=True) + NodeDataView({(0, 1): {'tot': 1}, (2, 3): {'tot': 5}, (1, 2): {'tot': 3}}) + + Notes + ----- + Graph, node, and edge data are not propagated to the new graph. For + undirected graphs, the nodes in G must be sortable, otherwise the + constructed line graph may not be correct. + + *Self-loops in undirected graphs* + + For an undirected graph `G` without multiple edges, each edge can be + written as a set `\{u, v\}`. Its line graph `L` has the edges of `G` as + its nodes. If `x` and `y` are two nodes in `L`, then `\{x, y\}` is an edge + in `L` if and only if the intersection of `x` and `y` is nonempty. Thus, + the set of all edges is determined by the set of all pairwise intersections + of edges in `G`. + + Trivially, every edge in G would have a nonzero intersection with itself, + and so every node in `L` should have a self-loop. This is not so + interesting, and the original context of line graphs was with simple + graphs, which had no self-loops or multiple edges. The line graph was also + meant to be a simple graph and thus, self-loops in `L` are not part of the + standard definition of a line graph. In a pairwise intersection matrix, + this is analogous to excluding the diagonal entries from the line graph + definition. + + Self-loops and multiple edges in `G` add nodes to `L` in a natural way, and + do not require any fundamental changes to the definition. It might be + argued that the self-loops we excluded before should now be included. + However, the self-loops are still "trivial" in some sense and thus, are + usually excluded. + + *Self-loops in directed graphs* + + For a directed graph `G` without multiple edges, each edge can be written + as a tuple `(u, v)`. Its line graph `L` has the edges of `G` as its + nodes. If `x` and `y` are two nodes in `L`, then `(x, y)` is an edge in `L` + if and only if the tail of `x` matches the head of `y`, for example, if `x + = (a, b)` and `y = (b, c)` for some vertices `a`, `b`, and `c` in `G`. + + Due to the directed nature of the edges, it is no longer the case that + every edge in `G` should have a self-loop in `L`. Now, the only time + self-loops arise is if a node in `G` itself has a self-loop. So such + self-loops are no longer "trivial" but instead, represent essential + features of the topology of `G`. For this reason, the historical + development of line digraphs is such that self-loops are included. When the + graph `G` has multiple edges, once again only superficial changes are + required to the definition. + + References + ---------- + * Harary, Frank, and Norman, Robert Z., "Some properties of line digraphs", + Rend. Circ. Mat. Palermo, II. Ser. 9 (1960), 161--168. + * Hemminger, R. L.; Beineke, L. W. (1978), "Line graphs and line digraphs", + in Beineke, L. W.; Wilson, R. J., Selected Topics in Graph Theory, + Academic Press Inc., pp. 271--305. + + """ + if G.is_directed(): + L = _lg_directed(G, create_using=create_using) + else: + L = _lg_undirected(G, selfloops=False, create_using=create_using) + return L + + +def _lg_directed(G, create_using=None): + """Returns the line graph L of the (multi)digraph G. + + Edges in G appear as nodes in L, represented as tuples of the form (u,v) + or (u,v,key) if G is a multidigraph. A node in L corresponding to the edge + (u,v) is connected to every node corresponding to an edge (v,w). + + Parameters + ---------- + G : digraph + A directed graph or directed multigraph. + create_using : NetworkX graph constructor, optional + Graph type to create. If graph instance, then cleared before populated. + Default is to use the same graph class as `G`. + + """ + L = nx.empty_graph(0, create_using, default=G.__class__) + + # Create a graph specific edge function. + get_edges = partial(G.edges, keys=True) if G.is_multigraph() else G.edges + + for from_node in get_edges(): + # from_node is: (u,v) or (u,v,key) + L.add_node(from_node) + for to_node in get_edges(from_node[1]): + L.add_edge(from_node, to_node) + + return L + + +def _lg_undirected(G, selfloops=False, create_using=None): + """Returns the line graph L of the (multi)graph G. + + Edges in G appear as nodes in L, represented as sorted tuples of the form + (u,v), or (u,v,key) if G is a multigraph. A node in L corresponding to + the edge {u,v} is connected to every node corresponding to an edge that + involves u or v. + + Parameters + ---------- + G : graph + An undirected graph or multigraph. + selfloops : bool + If `True`, then self-loops are included in the line graph. If `False`, + they are excluded. + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Notes + ----- + The standard algorithm for line graphs of undirected graphs does not + produce self-loops. + + """ + L = nx.empty_graph(0, create_using, default=G.__class__) + + # Graph specific functions for edges. + get_edges = partial(G.edges, keys=True) if G.is_multigraph() else G.edges + + # Determine if we include self-loops or not. + shift = 0 if selfloops else 1 + + # Introduce numbering of nodes + node_index = {n: i for i, n in enumerate(G)} + + # Lift canonical representation of nodes to edges in line graph + edge_key_function = lambda edge: (node_index[edge[0]], node_index[edge[1]]) + + edges = set() + for u in G: + # Label nodes as a sorted tuple of nodes in original graph. + # Decide on representation of {u, v} as (u, v) or (v, u) depending on node_index. + # -> This ensures a canonical representation and avoids comparing values of different types. + nodes = [tuple(sorted(x[:2], key=node_index.get)) + x[2:] for x in get_edges(u)] + + if len(nodes) == 1: + # Then the edge will be an isolated node in L. + L.add_node(nodes[0]) + + # Add a clique of `nodes` to graph. To prevent double adding edges, + # especially important for multigraphs, we store the edges in + # canonical form in a set. + for i, a in enumerate(nodes): + edges.update( + [ + tuple(sorted((a, b), key=edge_key_function)) + for b in nodes[i + shift :] + ] + ) + + L.add_edges_from(edges) + return L + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable(returns_graph=True) +def inverse_line_graph(G): + """Returns the inverse line graph of graph G. + + If H is a graph, and G is the line graph of H, such that G = L(H). + Then H is the inverse line graph of G. + + Not all graphs are line graphs and these do not have an inverse line graph. + In these cases this function raises a NetworkXError. + + Parameters + ---------- + G : graph + A NetworkX Graph + + Returns + ------- + H : graph + The inverse line graph of G. + + Raises + ------ + NetworkXNotImplemented + If G is directed or a multigraph + + NetworkXError + If G is not a line graph + + Notes + ----- + This is an implementation of the Roussopoulos algorithm[1]_. + + If G consists of multiple components, then the algorithm doesn't work. + You should invert every component separately: + + >>> K5 = nx.complete_graph(5) + >>> P4 = nx.Graph([("a", "b"), ("b", "c"), ("c", "d")]) + >>> G = nx.union(K5, P4) + >>> root_graphs = [] + >>> for comp in nx.connected_components(G): + ... root_graphs.append(nx.inverse_line_graph(G.subgraph(comp))) + >>> len(root_graphs) + 2 + + References + ---------- + .. [1] Roussopoulos, N.D. , "A max {m, n} algorithm for determining the graph H from + its line graph G", Information Processing Letters 2, (1973), 108--112, ISSN 0020-0190, + `DOI link `_ + + """ + if G.number_of_nodes() == 0: + return nx.empty_graph(1) + elif G.number_of_nodes() == 1: + v = arbitrary_element(G) + a = (v, 0) + b = (v, 1) + H = nx.Graph([(a, b)]) + return H + elif G.number_of_nodes() > 1 and G.number_of_edges() == 0: + msg = ( + "inverse_line_graph() doesn't work on an edgeless graph. " + "Please use this function on each component separately." + ) + raise nx.NetworkXError(msg) + + if nx.number_of_selfloops(G) != 0: + msg = ( + "A line graph as generated by NetworkX has no selfloops, so G has no " + "inverse line graph. Please remove the selfloops from G and try again." + ) + raise nx.NetworkXError(msg) + + starting_cell = _select_starting_cell(G) + P = _find_partition(G, starting_cell) + # count how many times each vertex appears in the partition set + P_count = {u: 0 for u in G.nodes} + for p in P: + for u in p: + P_count[u] += 1 + + if max(P_count.values()) > 2: + msg = "G is not a line graph (vertex found in more than two partition cells)" + raise nx.NetworkXError(msg) + W = tuple((u,) for u in P_count if P_count[u] == 1) + H = nx.Graph() + H.add_nodes_from(P) + H.add_nodes_from(W) + for a, b in combinations(H.nodes, 2): + if any(a_bit in b for a_bit in a): + H.add_edge(a, b) + return H + + +def _triangles(G, e): + """Return list of all triangles containing edge e""" + u, v = e + if u not in G: + raise nx.NetworkXError(f"Vertex {u} not in graph") + if v not in G[u]: + raise nx.NetworkXError(f"Edge ({u}, {v}) not in graph") + triangle_list = [] + for x in G[u]: + if x in G[v]: + triangle_list.append((u, v, x)) + return triangle_list + + +def _odd_triangle(G, T): + """Test whether T is an odd triangle in G + + Parameters + ---------- + G : NetworkX Graph + T : 3-tuple of vertices forming triangle in G + + Returns + ------- + True is T is an odd triangle + False otherwise + + Raises + ------ + NetworkXError + T is not a triangle in G + + Notes + ----- + An odd triangle is one in which there exists another vertex in G which is + adjacent to either exactly one or exactly all three of the vertices in the + triangle. + + """ + for u in T: + if u not in G.nodes(): + raise nx.NetworkXError(f"Vertex {u} not in graph") + for e in list(combinations(T, 2)): + if e[0] not in G[e[1]]: + raise nx.NetworkXError(f"Edge ({e[0]}, {e[1]}) not in graph") + + T_nbrs = defaultdict(int) + for t in T: + for v in G[t]: + if v not in T: + T_nbrs[v] += 1 + return any(T_nbrs[v] in [1, 3] for v in T_nbrs) + + +def _find_partition(G, starting_cell): + """Find a partition of the vertices of G into cells of complete graphs + + Parameters + ---------- + G : NetworkX Graph + starting_cell : tuple of vertices in G which form a cell + + Returns + ------- + List of tuples of vertices of G + + Raises + ------ + NetworkXError + If a cell is not a complete subgraph then G is not a line graph + """ + G_partition = G.copy() + P = [starting_cell] # partition set + G_partition.remove_edges_from(list(combinations(starting_cell, 2))) + # keep list of partitioned nodes which might have an edge in G_partition + partitioned_vertices = list(starting_cell) + while G_partition.number_of_edges() > 0: + # there are still edges left and so more cells to be made + u = partitioned_vertices.pop() + deg_u = len(G_partition[u]) + if deg_u != 0: + # if u still has edges then we need to find its other cell + # this other cell must be a complete subgraph or else G is + # not a line graph + new_cell = [u] + list(G_partition[u]) + for u in new_cell: + for v in new_cell: + if (u != v) and (v not in G_partition[u]): + msg = ( + "G is not a line graph " + "(partition cell not a complete subgraph)" + ) + raise nx.NetworkXError(msg) + P.append(tuple(new_cell)) + G_partition.remove_edges_from(list(combinations(new_cell, 2))) + partitioned_vertices += new_cell + return P + + +def _select_starting_cell(G, starting_edge=None): + """Select a cell to initiate _find_partition + + Parameters + ---------- + G : NetworkX Graph + starting_edge: an edge to build the starting cell from + + Returns + ------- + Tuple of vertices in G + + Raises + ------ + NetworkXError + If it is determined that G is not a line graph + + Notes + ----- + If starting edge not specified then pick an arbitrary edge - doesn't + matter which. However, this function may call itself requiring a + specific starting edge. Note that the r, s notation for counting + triangles is the same as in the Roussopoulos paper cited above. + """ + if starting_edge is None: + e = arbitrary_element(G.edges()) + else: + e = starting_edge + if e[0] not in G.nodes(): + raise nx.NetworkXError(f"Vertex {e[0]} not in graph") + if e[1] not in G[e[0]]: + msg = f"starting_edge ({e[0]}, {e[1]}) is not in the Graph" + raise nx.NetworkXError(msg) + e_triangles = _triangles(G, e) + r = len(e_triangles) + if r == 0: + # there are no triangles containing e, so the starting cell is just e + starting_cell = e + elif r == 1: + # there is exactly one triangle, T, containing e. If other 2 edges + # of T belong only to this triangle then T is starting cell + T = e_triangles[0] + a, b, c = T + # ab was original edge so check the other 2 edges + ac_edges = len(_triangles(G, (a, c))) + bc_edges = len(_triangles(G, (b, c))) + if ac_edges == 1: + if bc_edges == 1: + starting_cell = T + else: + return _select_starting_cell(G, starting_edge=(b, c)) + else: + return _select_starting_cell(G, starting_edge=(a, c)) + else: + # r >= 2 so we need to count the number of odd triangles, s + s = 0 + odd_triangles = [] + for T in e_triangles: + if _odd_triangle(G, T): + s += 1 + odd_triangles.append(T) + if r == 2 and s == 0: + # in this case either triangle works, so just use T + starting_cell = T + elif r - 1 <= s <= r: + # check if odd triangles containing e form complete subgraph + triangle_nodes = set() + for T in odd_triangles: + for x in T: + triangle_nodes.add(x) + + for u in triangle_nodes: + for v in triangle_nodes: + if u != v and (v not in G[u]): + msg = ( + "G is not a line graph (odd triangles " + "do not form complete subgraph)" + ) + raise nx.NetworkXError(msg) + # otherwise then we can use this as the starting cell + starting_cell = tuple(triangle_nodes) + + else: + msg = ( + "G is not a line graph (incorrect number of " + "odd triangles around starting edge)" + ) + raise nx.NetworkXError(msg) + return starting_cell diff --git a/venv/lib/python3.10/site-packages/networkx/generators/mycielski.py b/venv/lib/python3.10/site-packages/networkx/generators/mycielski.py new file mode 100644 index 0000000000000000000000000000000000000000..804b903692853d3c45b3b1b20898efeee9b71a5e --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/mycielski.py @@ -0,0 +1,110 @@ +"""Functions related to the Mycielski Operation and the Mycielskian family +of graphs. + +""" + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = ["mycielskian", "mycielski_graph"] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable(returns_graph=True) +def mycielskian(G, iterations=1): + r"""Returns the Mycielskian of a simple, undirected graph G + + The Mycielskian of graph preserves a graph's triangle free + property while increasing the chromatic number by 1. + + The Mycielski Operation on a graph, :math:`G=(V, E)`, constructs a new + graph with :math:`2|V| + 1` nodes and :math:`3|E| + |V|` edges. + + The construction is as follows: + + Let :math:`V = {0, ..., n-1}`. Construct another vertex set + :math:`U = {n, ..., 2n}` and a vertex, `w`. + Construct a new graph, `M`, with vertices :math:`U \bigcup V \bigcup w`. + For edges, :math:`(u, v) \in E` add edges :math:`(u, v), (u, v + n)`, and + :math:`(u + n, v)` to M. Finally, for all vertices :math:`u \in U`, add + edge :math:`(u, w)` to M. + + The Mycielski Operation can be done multiple times by repeating the above + process iteratively. + + More information can be found at https://en.wikipedia.org/wiki/Mycielskian + + Parameters + ---------- + G : graph + A simple, undirected NetworkX graph + iterations : int + The number of iterations of the Mycielski operation to + perform on G. Defaults to 1. Must be a non-negative integer. + + Returns + ------- + M : graph + The Mycielskian of G after the specified number of iterations. + + Notes + ----- + Graph, node, and edge data are not necessarily propagated to the new graph. + + """ + + M = nx.convert_node_labels_to_integers(G) + + for i in range(iterations): + n = M.number_of_nodes() + M.add_nodes_from(range(n, 2 * n)) + old_edges = list(M.edges()) + M.add_edges_from((u, v + n) for u, v in old_edges) + M.add_edges_from((u + n, v) for u, v in old_edges) + M.add_node(2 * n) + M.add_edges_from((u + n, 2 * n) for u in range(n)) + + return M + + +@nx._dispatchable(graphs=None, returns_graph=True) +def mycielski_graph(n): + """Generator for the n_th Mycielski Graph. + + The Mycielski family of graphs is an infinite set of graphs. + :math:`M_1` is the singleton graph, :math:`M_2` is two vertices with an + edge, and, for :math:`i > 2`, :math:`M_i` is the Mycielskian of + :math:`M_{i-1}`. + + More information can be found at + http://mathworld.wolfram.com/MycielskiGraph.html + + Parameters + ---------- + n : int + The desired Mycielski Graph. + + Returns + ------- + M : graph + The n_th Mycielski Graph + + Notes + ----- + The first graph in the Mycielski sequence is the singleton graph. + The Mycielskian of this graph is not the :math:`P_2` graph, but rather the + :math:`P_2` graph with an extra, isolated vertex. The second Mycielski + graph is the :math:`P_2` graph, so the first two are hard coded. + The remaining graphs are generated using the Mycielski operation. + + """ + + if n < 1: + raise nx.NetworkXError("must satisfy n >= 1") + + if n == 1: + return nx.empty_graph(1) + + else: + return mycielskian(nx.path_graph(2), n - 2) diff --git a/venv/lib/python3.10/site-packages/networkx/generators/nonisomorphic_trees.py b/venv/lib/python3.10/site-packages/networkx/generators/nonisomorphic_trees.py new file mode 100644 index 0000000000000000000000000000000000000000..9716cf33834ac5d083c4dd402224dd3df1011af3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/nonisomorphic_trees.py @@ -0,0 +1,212 @@ +""" +Implementation of the Wright, Richmond, Odlyzko and McKay (WROM) +algorithm for the enumeration of all non-isomorphic free trees of a +given order. Rooted trees are represented by level sequences, i.e., +lists in which the i-th element specifies the distance of vertex i to +the root. + +""" + +__all__ = ["nonisomorphic_trees", "number_of_nonisomorphic_trees"] + +import networkx as nx + + +@nx._dispatchable(graphs=None, returns_graph=True) +def nonisomorphic_trees(order, create="graph"): + """Generates lists of nonisomorphic trees + + Parameters + ---------- + order : int + order of the desired tree(s) + + create : one of {"graph", "matrix"} (default="graph") + If ``"graph"`` is selected a list of ``Graph`` instances will be returned, + if matrix is selected a list of adjacency matrices will be returned. + + .. deprecated:: 3.3 + + The `create` argument is deprecated and will be removed in NetworkX + version 3.5. In the future, `nonisomorphic_trees` will yield graph + instances by default. To generate adjacency matrices, call + ``nx.to_numpy_array`` on the output, e.g.:: + + [nx.to_numpy_array(G) for G in nx.nonisomorphic_trees(N)] + + Yields + ------ + list + A list of nonisomorphic trees, in one of two formats depending on the + value of the `create` parameter: + - ``create="graph"``: yields a list of `networkx.Graph` instances + - ``create="matrix"``: yields a list of list-of-lists representing adjacency matrices + """ + + if order < 2: + raise ValueError + # start at the path graph rooted at its center + layout = list(range(order // 2 + 1)) + list(range(1, (order + 1) // 2)) + + while layout is not None: + layout = _next_tree(layout) + if layout is not None: + if create == "graph": + yield _layout_to_graph(layout) + elif create == "matrix": + import warnings + + warnings.warn( + ( + "\n\nThe 'create=matrix' argument of nonisomorphic_trees\n" + "is deprecated and will be removed in version 3.5.\n" + "Use ``nx.to_numpy_array`` to convert graphs to adjacency " + "matrices, e.g.::\n\n" + " [nx.to_numpy_array(G) for G in nx.nonisomorphic_trees(N)]" + ), + category=DeprecationWarning, + stacklevel=2, + ) + + yield _layout_to_matrix(layout) + layout = _next_rooted_tree(layout) + + +@nx._dispatchable(graphs=None) +def number_of_nonisomorphic_trees(order): + """Returns the number of nonisomorphic trees + + Parameters + ---------- + order : int + order of the desired tree(s) + + Returns + ------- + length : Number of nonisomorphic graphs for the given order + + References + ---------- + + """ + return sum(1 for _ in nonisomorphic_trees(order)) + + +def _next_rooted_tree(predecessor, p=None): + """One iteration of the Beyer-Hedetniemi algorithm.""" + + if p is None: + p = len(predecessor) - 1 + while predecessor[p] == 1: + p -= 1 + if p == 0: + return None + + q = p - 1 + while predecessor[q] != predecessor[p] - 1: + q -= 1 + result = list(predecessor) + for i in range(p, len(result)): + result[i] = result[i - p + q] + return result + + +def _next_tree(candidate): + """One iteration of the Wright, Richmond, Odlyzko and McKay + algorithm.""" + + # valid representation of a free tree if: + # there are at least two vertices at layer 1 + # (this is always the case because we start at the path graph) + left, rest = _split_tree(candidate) + + # and the left subtree of the root + # is less high than the tree with the left subtree removed + left_height = max(left) + rest_height = max(rest) + valid = rest_height >= left_height + + if valid and rest_height == left_height: + # and, if left and rest are of the same height, + # if left does not encompass more vertices + if len(left) > len(rest): + valid = False + # and, if they have the same number or vertices, + # if left does not come after rest lexicographically + elif len(left) == len(rest) and left > rest: + valid = False + + if valid: + return candidate + else: + # jump to the next valid free tree + p = len(left) + new_candidate = _next_rooted_tree(candidate, p) + if candidate[p] > 2: + new_left, new_rest = _split_tree(new_candidate) + new_left_height = max(new_left) + suffix = range(1, new_left_height + 2) + new_candidate[-len(suffix) :] = suffix + return new_candidate + + +def _split_tree(layout): + """Returns a tuple of two layouts, one containing the left + subtree of the root vertex, and one containing the original tree + with the left subtree removed.""" + + one_found = False + m = None + for i in range(len(layout)): + if layout[i] == 1: + if one_found: + m = i + break + else: + one_found = True + + if m is None: + m = len(layout) + + left = [layout[i] - 1 for i in range(1, m)] + rest = [0] + [layout[i] for i in range(m, len(layout))] + return (left, rest) + + +def _layout_to_matrix(layout): + """Create the adjacency matrix for the tree specified by the + given layout (level sequence).""" + + result = [[0] * len(layout) for i in range(len(layout))] + stack = [] + for i in range(len(layout)): + i_level = layout[i] + if stack: + j = stack[-1] + j_level = layout[j] + while j_level >= i_level: + stack.pop() + j = stack[-1] + j_level = layout[j] + result[i][j] = result[j][i] = 1 + stack.append(i) + return result + + +def _layout_to_graph(layout): + """Create a NetworkX Graph for the tree specified by the + given layout(level sequence)""" + G = nx.Graph() + stack = [] + for i in range(len(layout)): + i_level = layout[i] + if stack: + j = stack[-1] + j_level = layout[j] + while j_level >= i_level: + stack.pop() + j = stack[-1] + j_level = layout[j] + G.add_edge(i, j) + stack.append(i) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/random_clustered.py b/venv/lib/python3.10/site-packages/networkx/generators/random_clustered.py new file mode 100644 index 0000000000000000000000000000000000000000..8fbf855e672d3c50f1e74952cc2272143fbac57a --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/random_clustered.py @@ -0,0 +1,117 @@ +"""Generate graphs with given degree and triangle sequence.""" + +import networkx as nx +from networkx.utils import py_random_state + +__all__ = ["random_clustered_graph"] + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_clustered_graph(joint_degree_sequence, create_using=None, seed=None): + r"""Generate a random graph with the given joint independent edge degree and + triangle degree sequence. + + This uses a configuration model-like approach to generate a random graph + (with parallel edges and self-loops) by randomly assigning edges to match + the given joint degree sequence. + + The joint degree sequence is a list of pairs of integers of the form + $[(d_{1,i}, d_{1,t}), \dotsc, (d_{n,i}, d_{n,t})]$. According to this list, + vertex $u$ is a member of $d_{u,t}$ triangles and has $d_{u, i}$ other + edges. The number $d_{u,t}$ is the *triangle degree* of $u$ and the number + $d_{u,i}$ is the *independent edge degree*. + + Parameters + ---------- + joint_degree_sequence : list of integer pairs + Each list entry corresponds to the independent edge degree and + triangle degree of a node. + create_using : NetworkX graph constructor, optional (default MultiGraph) + Graph type to create. If graph instance, then cleared before populated. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + G : MultiGraph + A graph with the specified degree sequence. Nodes are labeled + starting at 0 with an index corresponding to the position in + deg_sequence. + + Raises + ------ + NetworkXError + If the independent edge degree sequence sum is not even + or the triangle degree sequence sum is not divisible by 3. + + Notes + ----- + As described by Miller [1]_ (see also Newman [2]_ for an equivalent + description). + + A non-graphical degree sequence (not realizable by some simple + graph) is allowed since this function returns graphs with self + loops and parallel edges. An exception is raised if the + independent degree sequence does not have an even sum or the + triangle degree sequence sum is not divisible by 3. + + This configuration model-like construction process can lead to + duplicate edges and loops. You can remove the self-loops and + parallel edges (see below) which will likely result in a graph + that doesn't have the exact degree sequence specified. This + "finite-size effect" decreases as the size of the graph increases. + + References + ---------- + .. [1] Joel C. Miller. "Percolation and epidemics in random clustered + networks". In: Physical review. E, Statistical, nonlinear, and soft + matter physics 80 (2 Part 1 August 2009). + .. [2] M. E. J. Newman. "Random Graphs with Clustering". + In: Physical Review Letters 103 (5 July 2009) + + Examples + -------- + >>> deg = [(1, 0), (1, 0), (1, 0), (2, 0), (1, 0), (2, 1), (0, 1), (0, 1)] + >>> G = nx.random_clustered_graph(deg) + + To remove parallel edges: + + >>> G = nx.Graph(G) + + To remove self loops: + + >>> G.remove_edges_from(nx.selfloop_edges(G)) + + """ + # In Python 3, zip() returns an iterator. Make this into a list. + joint_degree_sequence = list(joint_degree_sequence) + + N = len(joint_degree_sequence) + G = nx.empty_graph(N, create_using, default=nx.MultiGraph) + if G.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + ilist = [] + tlist = [] + for n in G: + degrees = joint_degree_sequence[n] + for icount in range(degrees[0]): + ilist.append(n) + for tcount in range(degrees[1]): + tlist.append(n) + + if len(ilist) % 2 != 0 or len(tlist) % 3 != 0: + raise nx.NetworkXError("Invalid degree sequence") + + seed.shuffle(ilist) + seed.shuffle(tlist) + while ilist: + G.add_edge(ilist.pop(), ilist.pop()) + while tlist: + n1 = tlist.pop() + n2 = tlist.pop() + n3 = tlist.pop() + G.add_edges_from([(n1, n2), (n1, n3), (n2, n3)]) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/random_graphs.py b/venv/lib/python3.10/site-packages/networkx/generators/random_graphs.py new file mode 100644 index 0000000000000000000000000000000000000000..90ae0d973ac42550280bf74db0d2fef3117d58da --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/random_graphs.py @@ -0,0 +1,1400 @@ +""" +Generators for random graphs. + +""" + +import itertools +import math +from collections import defaultdict + +import networkx as nx +from networkx.utils import py_random_state + +from ..utils.misc import check_create_using +from .classic import complete_graph, empty_graph, path_graph, star_graph +from .degree_seq import degree_sequence_tree + +__all__ = [ + "fast_gnp_random_graph", + "gnp_random_graph", + "dense_gnm_random_graph", + "gnm_random_graph", + "erdos_renyi_graph", + "binomial_graph", + "newman_watts_strogatz_graph", + "watts_strogatz_graph", + "connected_watts_strogatz_graph", + "random_regular_graph", + "barabasi_albert_graph", + "dual_barabasi_albert_graph", + "extended_barabasi_albert_graph", + "powerlaw_cluster_graph", + "random_lobster", + "random_shell_graph", + "random_powerlaw_tree", + "random_powerlaw_tree_sequence", + "random_kernel_graph", +] + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def fast_gnp_random_graph(n, p, seed=None, directed=False, *, create_using=None): + """Returns a $G_{n,p}$ random graph, also known as an Erdős-Rényi graph or + a binomial graph. + + Parameters + ---------- + n : int + The number of nodes. + 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, this function returns a directed graph. + create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph types are not supported and raise a ``NetworkXError``. + By default NetworkX Graph or DiGraph are used depending on `directed`. + + Notes + ----- + The $G_{n,p}$ graph algorithm chooses each of the $[n (n - 1)] / 2$ + (undirected) or $n (n - 1)$ (directed) possible edges with probability $p$. + + This algorithm [1]_ runs in $O(n + m)$ time, where `m` is the expected number of + edges, which equals $p n (n - 1) / 2$. This should be faster than + :func:`gnp_random_graph` when $p$ is small and the expected number of edges + is small (that is, the graph is sparse). + + See Also + -------- + gnp_random_graph + + References + ---------- + .. [1] Vladimir Batagelj and Ulrik Brandes, + "Efficient generation of large random networks", + Phys. Rev. E, 71, 036113, 2005. + """ + default = nx.DiGraph if directed else nx.Graph + create_using = check_create_using( + create_using, directed=directed, multigraph=False, default=default + ) + if p <= 0 or p >= 1: + return nx.gnp_random_graph( + n, p, seed=seed, directed=directed, create_using=create_using + ) + + G = empty_graph(n, create_using=create_using) + + lp = math.log(1.0 - p) + + if directed: + v = 1 + w = -1 + while v < n: + lr = math.log(1.0 - seed.random()) + w = w + 1 + int(lr / lp) + while w >= v and v < n: + w = w - v + v = v + 1 + if v < n: + G.add_edge(w, v) + + # Nodes in graph are from 0,n-1 (start with v as the second node index). + v = 1 + w = -1 + while v < n: + lr = math.log(1.0 - seed.random()) + w = w + 1 + int(lr / lp) + while w >= v and v < n: + w = w - v + v = v + 1 + if v < n: + G.add_edge(v, w) + return G + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def gnp_random_graph(n, p, seed=None, directed=False, *, create_using=None): + """Returns a $G_{n,p}$ random graph, also known as an Erdős-Rényi graph + or a binomial graph. + + The $G_{n,p}$ model chooses each of the possible edges with probability $p$. + + Parameters + ---------- + n : int + The number of nodes. + 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, this function returns a directed graph. + create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph types are not supported and raise a ``NetworkXError``. + By default NetworkX Graph or DiGraph are used depending on `directed`. + + See Also + -------- + fast_gnp_random_graph + + Notes + ----- + This algorithm [2]_ runs in $O(n^2)$ time. For sparse graphs (that is, for + small values of $p$), :func:`fast_gnp_random_graph` is a faster algorithm. + + :func:`binomial_graph` and :func:`erdos_renyi_graph` are + aliases for :func:`gnp_random_graph`. + + >>> nx.binomial_graph is nx.gnp_random_graph + True + >>> nx.erdos_renyi_graph is nx.gnp_random_graph + True + + References + ---------- + .. [1] P. Erdős and A. Rényi, On Random Graphs, Publ. Math. 6, 290 (1959). + .. [2] E. N. Gilbert, Random Graphs, Ann. Math. Stat., 30, 1141 (1959). + """ + default = nx.DiGraph if directed else nx.Graph + create_using = check_create_using( + create_using, directed=directed, multigraph=False, default=default + ) + if p >= 1: + return complete_graph(n, create_using=create_using) + + G = nx.empty_graph(n, create_using=create_using) + if p <= 0: + return G + + edgetool = itertools.permutations if directed else itertools.combinations + for e in edgetool(range(n), 2): + if seed.random() < p: + G.add_edge(*e) + return G + + +# add some aliases to common names +binomial_graph = gnp_random_graph +erdos_renyi_graph = gnp_random_graph + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def dense_gnm_random_graph(n, m, seed=None, *, create_using=None): + """Returns a $G_{n,m}$ random graph. + + In the $G_{n,m}$ model, a graph is chosen uniformly at random from the set + of all graphs with $n$ nodes and $m$ edges. + + This algorithm should be faster than :func:`gnm_random_graph` for dense + graphs. + + Parameters + ---------- + n : int + The number of nodes. + m : int + The number of edges. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + See Also + -------- + gnm_random_graph + + Notes + ----- + Algorithm by Keith M. Briggs Mar 31, 2006. + Inspired by Knuth's Algorithm S (Selection sampling technique), + in section 3.4.2 of [1]_. + + References + ---------- + .. [1] Donald E. Knuth, The Art of Computer Programming, + Volume 2/Seminumerical algorithms, Third Edition, Addison-Wesley, 1997. + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + mmax = n * (n - 1) // 2 + if m >= mmax: + return complete_graph(n, create_using) + G = empty_graph(n, create_using) + + if n == 1: + return G + + u = 0 + v = 1 + t = 0 + k = 0 + while True: + if seed.randrange(mmax - t) < m - k: + G.add_edge(u, v) + k += 1 + if k == m: + return G + t += 1 + v += 1 + if v == n: # go to next row of adjacency matrix + u += 1 + v = u + 1 + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def gnm_random_graph(n, m, seed=None, directed=False, *, create_using=None): + """Returns a $G_{n,m}$ random graph. + + In the $G_{n,m}$ model, a graph is chosen uniformly at random from the set + of all graphs with $n$ nodes and $m$ edges. + + This algorithm should be faster than :func:`dense_gnm_random_graph` for + sparse graphs. + + Parameters + ---------- + n : int + The number of nodes. + m : 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 + create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph types are not supported and raise a ``NetworkXError``. + By default NetworkX Graph or DiGraph are used depending on `directed`. + + See also + -------- + dense_gnm_random_graph + + """ + default = nx.DiGraph if directed else nx.Graph + create_using = check_create_using( + create_using, directed=directed, multigraph=False, default=default + ) + if n == 1: + return nx.empty_graph(n, create_using=create_using) + max_edges = n * (n - 1) if directed else n * (n - 1) / 2.0 + if m >= max_edges: + return complete_graph(n, create_using=create_using) + + G = nx.empty_graph(n, create_using=create_using) + nlist = list(G) + edge_count = 0 + while edge_count < m: + # generate random edge,u,v + u = seed.choice(nlist) + v = seed.choice(nlist) + if u == v or G.has_edge(u, v): + continue + else: + G.add_edge(u, v) + edge_count = edge_count + 1 + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def newman_watts_strogatz_graph(n, k, p, seed=None, *, create_using=None): + """Returns a Newman–Watts–Strogatz small-world graph. + + Parameters + ---------- + n : int + The number of nodes. + k : int + Each node is joined with its `k` nearest neighbors in a ring + topology. + p : float + The probability of adding a new edge for each edge. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Notes + ----- + First create a ring over $n$ nodes [1]_. Then each node in the ring is + connected with its $k$ nearest neighbors (or $k - 1$ neighbors if $k$ + is odd). Then shortcuts are created by adding new edges as follows: for + each edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest + neighbors" with probability $p$ add a new edge $(u, w)$ with + randomly-chosen existing node $w$. In contrast with + :func:`watts_strogatz_graph`, no edges are removed. + + See Also + -------- + watts_strogatz_graph + + References + ---------- + .. [1] M. E. J. Newman and D. J. Watts, + Renormalization group analysis of the small-world network model, + Physics Letters A, 263, 341, 1999. + https://doi.org/10.1016/S0375-9601(99)00757-4 + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if k > n: + raise nx.NetworkXError("k>=n, choose smaller k or larger n") + + # If k == n the graph return is a complete graph + if k == n: + return nx.complete_graph(n, create_using) + + G = empty_graph(n, create_using) + nlist = list(G.nodes()) + fromv = nlist + # connect the k/2 neighbors + for j in range(1, k // 2 + 1): + tov = fromv[j:] + fromv[0:j] # the first j are now last + for i in range(len(fromv)): + G.add_edge(fromv[i], tov[i]) + # for each edge u-v, with probability p, randomly select existing + # node w and add new edge u-w + e = list(G.edges()) + for u, v in e: + if seed.random() < p: + w = seed.choice(nlist) + # no self-loops and reject if edge u-w exists + # is that the correct NWS model? + while w == u or G.has_edge(u, w): + w = seed.choice(nlist) + if G.degree(u) >= n - 1: + break # skip this rewiring + else: + G.add_edge(u, w) + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def watts_strogatz_graph(n, k, p, seed=None, *, create_using=None): + """Returns a Watts–Strogatz small-world graph. + + Parameters + ---------- + n : int + The number of nodes + k : int + Each node is joined with its `k` nearest neighbors in a ring + topology. + p : float + The probability of rewiring each edge + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + See Also + -------- + newman_watts_strogatz_graph + connected_watts_strogatz_graph + + Notes + ----- + First create a ring over $n$ nodes [1]_. Then each node in the ring is joined + to its $k$ nearest neighbors (or $k - 1$ neighbors if $k$ is odd). + Then shortcuts are created by replacing some edges as follows: for each + edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest neighbors" + with probability $p$ replace it with a new edge $(u, w)$ with uniformly + random choice of existing node $w$. + + In contrast with :func:`newman_watts_strogatz_graph`, the random rewiring + does not increase the number of edges. The rewired graph is not guaranteed + to be connected as in :func:`connected_watts_strogatz_graph`. + + References + ---------- + .. [1] Duncan J. Watts and Steven H. Strogatz, + Collective dynamics of small-world networks, + Nature, 393, pp. 440--442, 1998. + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if k > n: + raise nx.NetworkXError("k>n, choose smaller k or larger n") + + # If k == n, the graph is complete not Watts-Strogatz + if k == n: + G = nx.complete_graph(n, create_using) + return G + + G = nx.empty_graph(n, create_using=create_using) + nodes = list(range(n)) # nodes are labeled 0 to n-1 + # connect each node to k/2 neighbors + for j in range(1, k // 2 + 1): + targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list + G.add_edges_from(zip(nodes, targets)) + # rewire edges from each node + # loop over all nodes in order (label) and neighbors in order (distance) + # no self loops or multiple edges allowed + for j in range(1, k // 2 + 1): # outer loop is neighbors + targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list + # inner loop in node order + for u, v in zip(nodes, targets): + if seed.random() < p: + w = seed.choice(nodes) + # Enforce no self-loops or multiple edges + while w == u or G.has_edge(u, w): + w = seed.choice(nodes) + if G.degree(u) >= n - 1: + break # skip this rewiring + else: + G.remove_edge(u, v) + G.add_edge(u, w) + return G + + +@py_random_state(4) +@nx._dispatchable(graphs=None, returns_graph=True) +def connected_watts_strogatz_graph(n, k, p, tries=100, seed=None, *, create_using=None): + """Returns a connected Watts–Strogatz small-world graph. + + Attempts to generate a connected graph by repeated generation of + Watts–Strogatz small-world graphs. An exception is raised if the maximum + number of tries is exceeded. + + Parameters + ---------- + n : int + The number of nodes + k : int + Each node is joined with its `k` nearest neighbors in a ring + topology. + p : float + The probability of rewiring each edge + tries : int + Number of attempts to generate a connected graph. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Notes + ----- + First create a ring over $n$ nodes [1]_. Then each node in the ring is joined + to its $k$ nearest neighbors (or $k - 1$ neighbors if $k$ is odd). + Then shortcuts are created by replacing some edges as follows: for each + edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest neighbors" + with probability $p$ replace it with a new edge $(u, w)$ with uniformly + random choice of existing node $w$. + The entire process is repeated until a connected graph results. + + See Also + -------- + newman_watts_strogatz_graph + watts_strogatz_graph + + References + ---------- + .. [1] Duncan J. Watts and Steven H. Strogatz, + Collective dynamics of small-world networks, + Nature, 393, pp. 440--442, 1998. + """ + for i in range(tries): + # seed is an RNG so should change sequence each call + G = watts_strogatz_graph(n, k, p, seed, create_using=create_using) + if nx.is_connected(G): + return G + raise nx.NetworkXError("Maximum number of tries exceeded") + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_regular_graph(d, n, seed=None, *, create_using=None): + r"""Returns a random $d$-regular graph on $n$ nodes. + + A regular graph is a graph where each node has the same number of neighbors. + + The resulting graph has no self-loops or parallel edges. + + Parameters + ---------- + d : int + The degree of each node. + n : integer + The number of nodes. The value of $n \times d$ must be even. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Notes + ----- + The nodes are numbered from $0$ to $n - 1$. + + Kim and Vu's paper [2]_ shows that this algorithm samples in an + asymptotically uniform way from the space of random graphs when + $d = O(n^{1 / 3 - \epsilon})$. + + Raises + ------ + + NetworkXError + If $n \times d$ is odd or $d$ is greater than or equal to $n$. + + References + ---------- + .. [1] A. Steger and N. Wormald, + Generating random regular graphs quickly, + Probability and Computing 8 (1999), 377-396, 1999. + https://doi.org/10.1017/S0963548399003867 + + .. [2] Jeong Han Kim and Van H. Vu, + Generating random regular graphs, + Proceedings of the thirty-fifth ACM symposium on Theory of computing, + San Diego, CA, USA, pp 213--222, 2003. + http://portal.acm.org/citation.cfm?id=780542.780576 + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if (n * d) % 2 != 0: + raise nx.NetworkXError("n * d must be even") + + if not 0 <= d < n: + raise nx.NetworkXError("the 0 <= d < n inequality must be satisfied") + + G = nx.empty_graph(n, create_using=create_using) + + if d == 0: + return G + + def _suitable(edges, potential_edges): + # Helper subroutine to check if there are suitable edges remaining + # If False, the generation of the graph has failed + if not potential_edges: + return True + for s1 in potential_edges: + for s2 in potential_edges: + # Two iterators on the same dictionary are guaranteed + # to visit it in the same order if there are no + # intervening modifications. + if s1 == s2: + # Only need to consider s1-s2 pair one time + break + if s1 > s2: + s1, s2 = s2, s1 + if (s1, s2) not in edges: + return True + return False + + def _try_creation(): + # Attempt to create an edge set + + edges = set() + stubs = list(range(n)) * d + + while stubs: + potential_edges = defaultdict(lambda: 0) + seed.shuffle(stubs) + stubiter = iter(stubs) + for s1, s2 in zip(stubiter, stubiter): + if s1 > s2: + s1, s2 = s2, s1 + if s1 != s2 and ((s1, s2) not in edges): + edges.add((s1, s2)) + else: + potential_edges[s1] += 1 + potential_edges[s2] += 1 + + if not _suitable(edges, potential_edges): + return None # failed to find suitable edge set + + stubs = [ + node + for node, potential in potential_edges.items() + for _ in range(potential) + ] + return edges + + # Even though a suitable edge set exists, + # the generation of such a set is not guaranteed. + # Try repeatedly to find one. + edges = _try_creation() + while edges is None: + edges = _try_creation() + G.add_edges_from(edges) + + return G + + +def _random_subset(seq, m, rng): + """Return m unique elements from seq. + + This differs from random.sample which can return repeated + elements if seq holds repeated elements. + + Note: rng is a random.Random or numpy.random.RandomState instance. + """ + targets = set() + while len(targets) < m: + x = rng.choice(seq) + targets.add(x) + return targets + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def barabasi_albert_graph(n, m, seed=None, initial_graph=None, *, create_using=None): + """Returns a random graph using Barabási–Albert preferential attachment + + A graph of $n$ nodes is grown by attaching new nodes each with $m$ + edges that are preferentially attached to existing nodes with high degree. + + Parameters + ---------- + n : int + Number of nodes + m : int + Number of edges to attach from a new node to existing nodes + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + initial_graph : Graph or None (default) + Initial network for Barabási–Albert algorithm. + It should be a connected graph for most use cases. + A copy of `initial_graph` is used. + If None, starts from a star graph on (m+1) nodes. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Returns + ------- + G : Graph + + Raises + ------ + NetworkXError + If `m` does not satisfy ``1 <= m < n``, or + the initial graph number of nodes m0 does not satisfy ``m <= m0 <= n``. + + References + ---------- + .. [1] A. L. Barabási and R. Albert "Emergence of scaling in + random networks", Science 286, pp 509-512, 1999. + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if m < 1 or m >= n: + raise nx.NetworkXError( + f"Barabási–Albert network must have m >= 1 and m < n, m = {m}, n = {n}" + ) + + if initial_graph is None: + # Default initial graph : star graph on (m + 1) nodes + G = star_graph(m, create_using) + else: + if len(initial_graph) < m or len(initial_graph) > n: + raise nx.NetworkXError( + f"Barabási–Albert initial graph needs between m={m} and n={n} nodes" + ) + G = initial_graph.copy() + + # List of existing nodes, with nodes repeated once for each adjacent edge + repeated_nodes = [n for n, d in G.degree() for _ in range(d)] + # Start adding the other n - m0 nodes. + source = len(G) + while source < n: + # Now choose m unique nodes from the existing nodes + # Pick uniformly from repeated_nodes (preferential attachment) + targets = _random_subset(repeated_nodes, m, seed) + # Add edges to m nodes from the source. + G.add_edges_from(zip([source] * m, targets)) + # Add one node to the list for each new edge just created. + repeated_nodes.extend(targets) + # And the new node "source" has m edges to add to the list. + repeated_nodes.extend([source] * m) + + source += 1 + return G + + +@py_random_state(4) +@nx._dispatchable(graphs=None, returns_graph=True) +def dual_barabasi_albert_graph( + n, m1, m2, p, seed=None, initial_graph=None, *, create_using=None +): + """Returns a random graph using dual Barabási–Albert preferential attachment + + A graph of $n$ nodes is grown by attaching new nodes each with either $m_1$ + edges (with probability $p$) or $m_2$ edges (with probability $1-p$) that + are preferentially attached to existing nodes with high degree. + + Parameters + ---------- + n : int + Number of nodes + m1 : int + Number of edges to link each new node to existing nodes with probability $p$ + m2 : int + Number of edges to link each new node to existing nodes with probability $1-p$ + p : float + The probability of attaching $m_1$ edges (as opposed to $m_2$ edges) + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + initial_graph : Graph or None (default) + Initial network for Barabási–Albert algorithm. + A copy of `initial_graph` is used. + It should be connected for most use cases. + If None, starts from an star graph on max(m1, m2) + 1 nodes. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Returns + ------- + G : Graph + + Raises + ------ + NetworkXError + If `m1` and `m2` do not satisfy ``1 <= m1,m2 < n``, or + `p` does not satisfy ``0 <= p <= 1``, or + the initial graph number of nodes m0 does not satisfy m1, m2 <= m0 <= n. + + References + ---------- + .. [1] N. Moshiri "The dual-Barabasi-Albert model", arXiv:1810.10538. + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if m1 < 1 or m1 >= n: + raise nx.NetworkXError( + f"Dual Barabási–Albert must have m1 >= 1 and m1 < n, m1 = {m1}, n = {n}" + ) + if m2 < 1 or m2 >= n: + raise nx.NetworkXError( + f"Dual Barabási–Albert must have m2 >= 1 and m2 < n, m2 = {m2}, n = {n}" + ) + if p < 0 or p > 1: + raise nx.NetworkXError( + f"Dual Barabási–Albert network must have 0 <= p <= 1, p = {p}" + ) + + # For simplicity, if p == 0 or 1, just return BA + if p == 1: + return barabasi_albert_graph(n, m1, seed, create_using=create_using) + elif p == 0: + return barabasi_albert_graph(n, m2, seed, create_using=create_using) + + if initial_graph is None: + # Default initial graph : star graph on max(m1, m2) nodes + G = star_graph(max(m1, m2), create_using) + else: + if len(initial_graph) < max(m1, m2) or len(initial_graph) > n: + raise nx.NetworkXError( + f"Barabási–Albert initial graph must have between " + f"max(m1, m2) = {max(m1, m2)} and n = {n} nodes" + ) + G = initial_graph.copy() + + # Target nodes for new edges + targets = list(G) + # List of existing nodes, with nodes repeated once for each adjacent edge + repeated_nodes = [n for n, d in G.degree() for _ in range(d)] + # Start adding the remaining nodes. + source = len(G) + while source < n: + # Pick which m to use (m1 or m2) + if seed.random() < p: + m = m1 + else: + m = m2 + # Now choose m unique nodes from the existing nodes + # Pick uniformly from repeated_nodes (preferential attachment) + targets = _random_subset(repeated_nodes, m, seed) + # Add edges to m nodes from the source. + G.add_edges_from(zip([source] * m, targets)) + # Add one node to the list for each new edge just created. + repeated_nodes.extend(targets) + # And the new node "source" has m edges to add to the list. + repeated_nodes.extend([source] * m) + + source += 1 + return G + + +@py_random_state(4) +@nx._dispatchable(graphs=None, returns_graph=True) +def extended_barabasi_albert_graph(n, m, p, q, seed=None, *, create_using=None): + """Returns an extended Barabási–Albert model graph. + + An extended Barabási–Albert model graph is a random graph constructed + using preferential attachment. The extended model allows new edges, + rewired edges or new nodes. Based on the probabilities $p$ and $q$ + with $p + q < 1$, the growing behavior of the graph is determined as: + + 1) With $p$ probability, $m$ new edges are added to the graph, + starting from randomly chosen existing nodes and attached preferentially at the + other end. + + 2) With $q$ probability, $m$ existing edges are rewired + by randomly choosing an edge and rewiring one end to a preferentially chosen node. + + 3) With $(1 - p - q)$ probability, $m$ new nodes are added to the graph + with edges attached preferentially. + + When $p = q = 0$, the model behaves just like the Barabási–Alber model. + + Parameters + ---------- + n : int + Number of nodes + m : int + Number of edges with which a new node attaches to existing nodes + p : float + Probability value for adding an edge between existing nodes. p + q < 1 + q : float + Probability value of rewiring of existing edges. p + q < 1 + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Returns + ------- + G : Graph + + Raises + ------ + NetworkXError + If `m` does not satisfy ``1 <= m < n`` or ``1 >= p + q`` + + References + ---------- + .. [1] Albert, R., & Barabási, A. L. (2000) + Topology of evolving networks: local events and universality + Physical review letters, 85(24), 5234. + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if m < 1 or m >= n: + msg = f"Extended Barabasi-Albert network needs m>=1 and m= 1: + msg = f"Extended Barabasi-Albert network needs p + q <= 1, p={p}, q={q}" + raise nx.NetworkXError(msg) + + # Add m initial nodes (m0 in barabasi-speak) + G = empty_graph(m, create_using) + + # List of nodes to represent the preferential attachment random selection. + # At the creation of the graph, all nodes are added to the list + # so that even nodes that are not connected have a chance to get selected, + # for rewiring and adding of edges. + # With each new edge, nodes at the ends of the edge are added to the list. + attachment_preference = [] + attachment_preference.extend(range(m)) + + # Start adding the other n-m nodes. The first node is m. + new_node = m + while new_node < n: + a_probability = seed.random() + + # Total number of edges of a Clique of all the nodes + clique_degree = len(G) - 1 + clique_size = (len(G) * clique_degree) / 2 + + # Adding m new edges, if there is room to add them + if a_probability < p and G.size() <= clique_size - m: + # Select the nodes where an edge can be added + eligible_nodes = [nd for nd, deg in G.degree() if deg < clique_degree] + for i in range(m): + # Choosing a random source node from eligible_nodes + src_node = seed.choice(eligible_nodes) + + # Picking a possible node that is not 'src_node' or + # neighbor with 'src_node', with preferential attachment + prohibited_nodes = list(G[src_node]) + prohibited_nodes.append(src_node) + # This will raise an exception if the sequence is empty + dest_node = seed.choice( + [nd for nd in attachment_preference if nd not in prohibited_nodes] + ) + # Adding the new edge + G.add_edge(src_node, dest_node) + + # Appending both nodes to add to their preferential attachment + attachment_preference.append(src_node) + attachment_preference.append(dest_node) + + # Adjusting the eligible nodes. Degree may be saturated. + if G.degree(src_node) == clique_degree: + eligible_nodes.remove(src_node) + if G.degree(dest_node) == clique_degree and dest_node in eligible_nodes: + eligible_nodes.remove(dest_node) + + # Rewiring m edges, if there are enough edges + elif p <= a_probability < (p + q) and m <= G.size() < clique_size: + # Selecting nodes that have at least 1 edge but that are not + # fully connected to ALL other nodes (center of star). + # These nodes are the pivot nodes of the edges to rewire + eligible_nodes = [nd for nd, deg in G.degree() if 0 < deg < clique_degree] + for i in range(m): + # Choosing a random source node + node = seed.choice(eligible_nodes) + + # The available nodes do have a neighbor at least. + nbr_nodes = list(G[node]) + + # Choosing the other end that will get detached + src_node = seed.choice(nbr_nodes) + + # Picking a target node that is not 'node' or + # neighbor with 'node', with preferential attachment + nbr_nodes.append(node) + dest_node = seed.choice( + [nd for nd in attachment_preference if nd not in nbr_nodes] + ) + # Rewire + G.remove_edge(node, src_node) + G.add_edge(node, dest_node) + + # Adjusting the preferential attachment list + attachment_preference.remove(src_node) + attachment_preference.append(dest_node) + + # Adjusting the eligible nodes. + # nodes may be saturated or isolated. + if G.degree(src_node) == 0 and src_node in eligible_nodes: + eligible_nodes.remove(src_node) + if dest_node in eligible_nodes: + if G.degree(dest_node) == clique_degree: + eligible_nodes.remove(dest_node) + else: + if G.degree(dest_node) == 1: + eligible_nodes.append(dest_node) + + # Adding new node with m edges + else: + # Select the edges' nodes by preferential attachment + targets = _random_subset(attachment_preference, m, seed) + G.add_edges_from(zip([new_node] * m, targets)) + + # Add one node to the list for each new edge just created. + attachment_preference.extend(targets) + # The new node has m edges to it, plus itself: m + 1 + attachment_preference.extend([new_node] * (m + 1)) + new_node += 1 + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def powerlaw_cluster_graph(n, m, p, seed=None, *, create_using=None): + """Holme and Kim algorithm for growing graphs with powerlaw + degree distribution and approximate average clustering. + + Parameters + ---------- + n : int + the number of nodes + m : int + the number of random edges to add for each new node + p : float, + Probability of adding a triangle after adding a random edge + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Notes + ----- + The average clustering has a hard time getting above a certain + cutoff that depends on `m`. This cutoff is often quite low. The + transitivity (fraction of triangles to possible triangles) seems to + decrease with network size. + + It is essentially the Barabási–Albert (BA) growth model with an + extra step that each random edge is followed by a chance of + making an edge to one of its neighbors too (and thus a triangle). + + This algorithm improves on BA in the sense that it enables a + higher average clustering to be attained if desired. + + It seems possible to have a disconnected graph with this algorithm + since the initial `m` nodes may not be all linked to a new node + on the first iteration like the BA model. + + Raises + ------ + NetworkXError + If `m` does not satisfy ``1 <= m <= n`` or `p` does not + satisfy ``0 <= p <= 1``. + + References + ---------- + .. [1] P. Holme and B. J. Kim, + "Growing scale-free networks with tunable clustering", + Phys. Rev. E, 65, 026107, 2002. + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if m < 1 or n < m: + raise nx.NetworkXError(f"NetworkXError must have m>1 and m 1 or p < 0: + raise nx.NetworkXError(f"NetworkXError p must be in [0,1], p={p}") + + G = empty_graph(m, create_using) # add m initial nodes (m0 in barabasi-speak) + repeated_nodes = list(G) # list of existing nodes to sample from + # with nodes repeated once for each adjacent edge + source = m # next node is m + while source < n: # Now add the other n-1 nodes + possible_targets = _random_subset(repeated_nodes, m, seed) + # do one preferential attachment for new node + target = possible_targets.pop() + G.add_edge(source, target) + repeated_nodes.append(target) # add one node to list for each new link + count = 1 + while count < m: # add m-1 more new links + if seed.random() < p: # clustering step: add triangle + neighborhood = [ + nbr + for nbr in G.neighbors(target) + if not G.has_edge(source, nbr) and nbr != source + ] + if neighborhood: # if there is a neighbor without a link + nbr = seed.choice(neighborhood) + G.add_edge(source, nbr) # add triangle + repeated_nodes.append(nbr) + count = count + 1 + continue # go to top of while loop + # else do preferential attachment step if above fails + target = possible_targets.pop() + G.add_edge(source, target) + repeated_nodes.append(target) + count = count + 1 + + repeated_nodes.extend([source] * m) # add source node to list m times + source += 1 + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_lobster(n, p1, p2, seed=None, *, create_using=None): + """Returns a random lobster graph. + + A lobster is a tree that reduces to a caterpillar when pruning all + leaf nodes. A caterpillar is a tree that reduces to a path graph + when pruning all leaf nodes; setting `p2` to zero produces a caterpillar. + + This implementation iterates on the probabilities `p1` and `p2` to add + edges at levels 1 and 2, respectively. Graphs are therefore constructed + iteratively with uniform randomness at each level rather than being selected + uniformly at random from the set of all possible lobsters. + + Parameters + ---------- + n : int + The expected number of nodes in the backbone + p1 : float + Probability of adding an edge to the backbone + p2 : float + Probability of adding an edge one level beyond backbone + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Grap) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Raises + ------ + NetworkXError + If `p1` or `p2` parameters are >= 1 because the while loops would never finish. + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + p1, p2 = abs(p1), abs(p2) + if any(p >= 1 for p in [p1, p2]): + raise nx.NetworkXError("Probability values for `p1` and `p2` must both be < 1.") + + # a necessary ingredient in any self-respecting graph library + llen = int(2 * seed.random() * n + 0.5) + L = path_graph(llen, create_using) + # build caterpillar: add edges to path graph with probability p1 + current_node = llen - 1 + for n in range(llen): + while seed.random() < p1: # add fuzzy caterpillar parts + current_node += 1 + L.add_edge(n, current_node) + cat_node = current_node + while seed.random() < p2: # add crunchy lobster bits + current_node += 1 + L.add_edge(cat_node, current_node) + return L # voila, un lobster! + + +@py_random_state(1) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_shell_graph(constructor, seed=None, *, create_using=None): + """Returns a random shell graph for the constructor given. + + Parameters + ---------- + constructor : list of three-tuples + Represents the parameters for a shell, starting at the center + shell. Each element of the list must be of the form `(n, m, + d)`, where `n` is the number of nodes in the shell, `m` is + the number of edges in the shell, and `d` is the ratio of + inter-shell (next) edges to intra-shell edges. If `d` is zero, + there will be no intra-shell edges, and if `d` is one there + will be all possible intra-shell edges. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. Graph instances are not supported. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Examples + -------- + >>> constructor = [(10, 20, 0.8), (20, 40, 0.8)] + >>> G = nx.random_shell_graph(constructor) + + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + G = empty_graph(0, create_using) + + glist = [] + intra_edges = [] + nnodes = 0 + # create gnm graphs for each shell + for n, m, d in constructor: + inter_edges = int(m * d) + intra_edges.append(m - inter_edges) + g = nx.convert_node_labels_to_integers( + gnm_random_graph(n, inter_edges, seed=seed, create_using=G.__class__), + first_label=nnodes, + ) + glist.append(g) + nnodes += n + G = nx.operators.union(G, g) + + # connect the shells randomly + for gi in range(len(glist) - 1): + nlist1 = list(glist[gi]) + nlist2 = list(glist[gi + 1]) + total_edges = intra_edges[gi] + edge_count = 0 + while edge_count < total_edges: + u = seed.choice(nlist1) + v = seed.choice(nlist2) + if u == v or G.has_edge(u, v): + continue + else: + G.add_edge(u, v) + edge_count = edge_count + 1 + return G + + +@py_random_state(2) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_powerlaw_tree(n, gamma=3, seed=None, tries=100, *, create_using=None): + """Returns a tree with a power law degree distribution. + + Parameters + ---------- + n : int + The number of nodes. + gamma : float + Exponent of the power law. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + tries : int + Number of attempts to adjust the sequence to make it a tree. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Raises + ------ + NetworkXError + If no valid sequence is found within the maximum number of + attempts. + + Notes + ----- + A trial power law degree sequence is chosen and then elements are + swapped with new elements from a powerlaw distribution until the + sequence makes a tree (by checking, for example, that the number of + edges is one smaller than the number of nodes). + + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + # This call may raise a NetworkXError if the number of tries is succeeded. + seq = random_powerlaw_tree_sequence(n, gamma=gamma, seed=seed, tries=tries) + G = degree_sequence_tree(seq, create_using) + return G + + +@py_random_state(2) +@nx._dispatchable(graphs=None) +def random_powerlaw_tree_sequence(n, gamma=3, seed=None, tries=100): + """Returns a degree sequence for a tree with a power law distribution. + + Parameters + ---------- + n : int, + The number of nodes. + gamma : float + Exponent of the power law. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + tries : int + Number of attempts to adjust the sequence to make it a tree. + + Raises + ------ + NetworkXError + If no valid sequence is found within the maximum number of + attempts. + + Notes + ----- + A trial power law degree sequence is chosen and then elements are + swapped with new elements from a power law distribution until + the sequence makes a tree (by checking, for example, that the number of + edges is one smaller than the number of nodes). + + """ + # get trial sequence + z = nx.utils.powerlaw_sequence(n, exponent=gamma, seed=seed) + # round to integer values in the range [0,n] + zseq = [min(n, max(round(s), 0)) for s in z] + + # another sequence to swap values from + z = nx.utils.powerlaw_sequence(tries, exponent=gamma, seed=seed) + # round to integer values in the range [0,n] + swap = [min(n, max(round(s), 0)) for s in z] + + for deg in swap: + # If this degree sequence can be the degree sequence of a tree, return + # it. It can be a tree if the number of edges is one fewer than the + # number of nodes, or in other words, `n - sum(zseq) / 2 == 1`. We + # use an equivalent condition below that avoids floating point + # operations. + if 2 * n - sum(zseq) == 2: + return zseq + index = seed.randint(0, n - 1) + zseq[index] = swap.pop() + + raise nx.NetworkXError( + f"Exceeded max ({tries}) attempts for a valid tree sequence." + ) + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_kernel_graph( + n, kernel_integral, kernel_root=None, seed=None, *, create_using=None +): + r"""Returns an random graph based on the specified kernel. + + The algorithm chooses each of the $[n(n-1)]/2$ possible edges with + probability specified by a kernel $\kappa(x,y)$ [1]_. The kernel + $\kappa(x,y)$ must be a symmetric (in $x,y$), non-negative, + bounded function. + + Parameters + ---------- + n : int + The number of nodes + kernel_integral : function + Function that returns the definite integral of the kernel $\kappa(x,y)$, + $F(y,a,b) := \int_a^b \kappa(x,y)dx$ + kernel_root: function (optional) + Function that returns the root $b$ of the equation $F(y,a,b) = r$. + If None, the root is found using :func:`scipy.optimize.brentq` + (this requires SciPy). + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + create_using : Graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + Multigraph and directed types are not supported and raise a ``NetworkXError``. + + Notes + ----- + The kernel is specified through its definite integral which must be + provided as one of the arguments. If the integral and root of the + kernel integral can be found in $O(1)$ time then this algorithm runs in + time $O(n+m)$ where m is the expected number of edges [2]_. + + The nodes are set to integers from $0$ to $n-1$. + + Examples + -------- + Generate an Erdős–Rényi random graph $G(n,c/n)$, with kernel + $\kappa(x,y)=c$ where $c$ is the mean expected degree. + + >>> def integral(u, w, z): + ... return c * (z - w) + >>> def root(u, w, r): + ... return r / c + w + >>> c = 1 + >>> graph = nx.random_kernel_graph(1000, integral, root) + + See Also + -------- + gnp_random_graph + expected_degree_graph + + References + ---------- + .. [1] Bollobás, Béla, Janson, S. and Riordan, O. + "The phase transition in inhomogeneous random graphs", + *Random Structures Algorithms*, 31, 3--122, 2007. + + .. [2] Hagberg A, Lemons N (2015), + "Fast Generation of Sparse Random Kernel Graphs". + PLoS ONE 10(9): e0135177, 2015. doi:10.1371/journal.pone.0135177 + """ + create_using = check_create_using(create_using, directed=False, multigraph=False) + if kernel_root is None: + import scipy as sp + + def kernel_root(y, a, r): + def my_function(b): + return kernel_integral(y, a, b) - r + + return sp.optimize.brentq(my_function, a, 1) + + graph = nx.empty_graph(create_using=create_using) + graph.add_nodes_from(range(n)) + (i, j) = (1, 1) + while i < n: + r = -math.log(1 - seed.random()) # (1-seed.random()) in (0, 1] + if kernel_integral(i / n, j / n, 1) <= r: + i, j = i + 1, i + 1 + else: + j = math.ceil(n * kernel_root(i / n, j / n, r)) + graph.add_edge(i - 1, j - 1) + return graph diff --git a/venv/lib/python3.10/site-packages/networkx/generators/small.py b/venv/lib/python3.10/site-packages/networkx/generators/small.py new file mode 100644 index 0000000000000000000000000000000000000000..acd2fbc7a34e16023253e15ffb6a156416e4ce83 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/small.py @@ -0,0 +1,993 @@ +""" +Various small and named graphs, together with some compact generators. + +""" + +__all__ = [ + "LCF_graph", + "bull_graph", + "chvatal_graph", + "cubical_graph", + "desargues_graph", + "diamond_graph", + "dodecahedral_graph", + "frucht_graph", + "heawood_graph", + "hoffman_singleton_graph", + "house_graph", + "house_x_graph", + "icosahedral_graph", + "krackhardt_kite_graph", + "moebius_kantor_graph", + "octahedral_graph", + "pappus_graph", + "petersen_graph", + "sedgewick_maze_graph", + "tetrahedral_graph", + "truncated_cube_graph", + "truncated_tetrahedron_graph", + "tutte_graph", +] + +from functools import wraps + +import networkx as nx +from networkx.exception import NetworkXError +from networkx.generators.classic import ( + complete_graph, + cycle_graph, + empty_graph, + path_graph, +) + + +def _raise_on_directed(func): + """ + A decorator which inspects the `create_using` argument and raises a + NetworkX exception when `create_using` is a DiGraph (class or instance) for + graph generators that do not support directed outputs. + """ + + @wraps(func) + def wrapper(*args, **kwargs): + if kwargs.get("create_using") is not None: + G = nx.empty_graph(create_using=kwargs["create_using"]) + if G.is_directed(): + raise NetworkXError("Directed Graph not supported") + return func(*args, **kwargs) + + return wrapper + + +@nx._dispatchable(graphs=None, returns_graph=True) +def LCF_graph(n, shift_list, repeats, create_using=None): + """ + Return the cubic graph specified in LCF notation. + + LCF (Lederberg-Coxeter-Fruchte) notation[1]_ is a compressed + notation used in the generation of various cubic Hamiltonian + graphs of high symmetry. See, for example, `dodecahedral_graph`, + `desargues_graph`, `heawood_graph` and `pappus_graph`. + + Nodes are drawn from ``range(n)``. Each node ``n_i`` is connected with + node ``n_i + shift % n`` where ``shift`` is given by cycling through + the input `shift_list` `repeat` s times. + + Parameters + ---------- + n : int + The starting graph is the `n`-cycle with nodes ``0, ..., n-1``. + The null graph is returned if `n` < 1. + + shift_list : list + A list of integer shifts mod `n`, ``[s1, s2, .., sk]`` + + repeats : int + Integer specifying the number of times that shifts in `shift_list` + are successively applied to each current node in the n-cycle + to generate an edge between ``n_current`` and ``n_current + shift mod n``. + + Returns + ------- + G : Graph + A graph instance created from the specified LCF notation. + + Examples + -------- + The utility graph $K_{3,3}$ + + >>> G = nx.LCF_graph(6, [3, -3], 3) + >>> G.edges() + EdgeView([(0, 1), (0, 5), (0, 3), (1, 2), (1, 4), (2, 3), (2, 5), (3, 4), (4, 5)]) + + The Heawood graph: + + >>> G = nx.LCF_graph(14, [5, -5], 7) + >>> nx.is_isomorphic(G, nx.heawood_graph()) + True + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/LCF_notation + + """ + if n <= 0: + return empty_graph(0, create_using) + + # start with the n-cycle + G = cycle_graph(n, create_using) + if G.is_directed(): + raise NetworkXError("Directed Graph not supported") + G.name = "LCF_graph" + nodes = sorted(G) + + n_extra_edges = repeats * len(shift_list) + # edges are added n_extra_edges times + # (not all of these need be new) + if n_extra_edges < 1: + return G + + for i in range(n_extra_edges): + shift = shift_list[i % len(shift_list)] # cycle through shift_list + v1 = nodes[i % n] # cycle repeatedly through nodes + v2 = nodes[(i + shift) % n] + G.add_edge(v1, v2) + return G + + +# ------------------------------------------------------------------------------- +# Various small and named graphs +# ------------------------------------------------------------------------------- + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def bull_graph(create_using=None): + """ + Returns the Bull Graph + + The Bull Graph has 5 nodes and 5 edges. It is a planar undirected + graph in the form of a triangle with two disjoint pendant edges [1]_ + The name comes from the triangle and pendant edges representing + respectively the body and legs of a bull. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + A bull graph with 5 nodes + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Bull_graph. + + """ + G = nx.from_dict_of_lists( + {0: [1, 2], 1: [0, 2, 3], 2: [0, 1, 4], 3: [1], 4: [2]}, + create_using=create_using, + ) + G.name = "Bull Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def chvatal_graph(create_using=None): + """ + Returns the Chvátal Graph + + The Chvátal Graph is an undirected graph with 12 nodes and 24 edges [1]_. + It has 370 distinct (directed) Hamiltonian cycles, giving a unique generalized + LCF notation of order 4, two of order 6 , and 43 of order 1 [2]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + The Chvátal graph with 12 nodes and 24 edges + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Chv%C3%A1tal_graph + .. [2] https://mathworld.wolfram.com/ChvatalGraph.html + + """ + G = nx.from_dict_of_lists( + { + 0: [1, 4, 6, 9], + 1: [2, 5, 7], + 2: [3, 6, 8], + 3: [4, 7, 9], + 4: [5, 8], + 5: [10, 11], + 6: [10, 11], + 7: [8, 11], + 8: [10], + 9: [10, 11], + }, + create_using=create_using, + ) + G.name = "Chvatal Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def cubical_graph(create_using=None): + """ + Returns the 3-regular Platonic Cubical Graph + + The skeleton of the cube (the nodes and edges) form a graph, with 8 + nodes, and 12 edges. It is a special case of the hypercube graph. + It is one of 5 Platonic graphs, each a skeleton of its + Platonic solid [1]_. + Such graphs arise in parallel processing in computers. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + A cubical graph with 8 nodes and 12 edges + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Cube#Cubical_graph + + """ + G = nx.from_dict_of_lists( + { + 0: [1, 3, 4], + 1: [0, 2, 7], + 2: [1, 3, 6], + 3: [0, 2, 5], + 4: [0, 5, 7], + 5: [3, 4, 6], + 6: [2, 5, 7], + 7: [1, 4, 6], + }, + create_using=create_using, + ) + G.name = "Platonic Cubical Graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def desargues_graph(create_using=None): + """ + Returns the Desargues Graph + + The Desargues Graph is a non-planar, distance-transitive cubic graph + with 20 nodes and 30 edges [1]_. + It is a symmetric graph. It can be represented in LCF notation + as [5,-5,9,-9]^5 [2]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Desargues Graph with 20 nodes and 30 edges + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Desargues_graph + .. [2] https://mathworld.wolfram.com/DesarguesGraph.html + """ + G = LCF_graph(20, [5, -5, 9, -9], 5, create_using) + G.name = "Desargues Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def diamond_graph(create_using=None): + """ + Returns the Diamond graph + + The Diamond Graph is planar undirected graph with 4 nodes and 5 edges. + It is also sometimes known as the double triangle graph or kite graph [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Diamond Graph with 4 nodes and 5 edges + + References + ---------- + .. [1] https://mathworld.wolfram.com/DiamondGraph.html + """ + G = nx.from_dict_of_lists( + {0: [1, 2], 1: [0, 2, 3], 2: [0, 1, 3], 3: [1, 2]}, create_using=create_using + ) + G.name = "Diamond Graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def dodecahedral_graph(create_using=None): + """ + Returns the Platonic Dodecahedral graph. + + The dodecahedral graph has 20 nodes and 30 edges. The skeleton of the + dodecahedron forms a graph. It is one of 5 Platonic graphs [1]_. + It can be described in LCF notation as: + ``[10, 7, 4, -4, -7, 10, -4, 7, -7, 4]^2`` [2]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Dodecahedral Graph with 20 nodes and 30 edges + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Regular_dodecahedron#Dodecahedral_graph + .. [2] https://mathworld.wolfram.com/DodecahedralGraph.html + + """ + G = LCF_graph(20, [10, 7, 4, -4, -7, 10, -4, 7, -7, 4], 2, create_using) + G.name = "Dodecahedral Graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def frucht_graph(create_using=None): + """ + Returns the Frucht Graph. + + The Frucht Graph is the smallest cubical graph whose + automorphism group consists only of the identity element [1]_. + It has 12 nodes and 18 edges and no nontrivial symmetries. + It is planar and Hamiltonian [2]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Frucht Graph with 12 nodes and 18 edges + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Frucht_graph + .. [2] https://mathworld.wolfram.com/FruchtGraph.html + + """ + G = cycle_graph(7, create_using) + G.add_edges_from( + [ + [0, 7], + [1, 7], + [2, 8], + [3, 9], + [4, 9], + [5, 10], + [6, 10], + [7, 11], + [8, 11], + [8, 9], + [10, 11], + ] + ) + + G.name = "Frucht Graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def heawood_graph(create_using=None): + """ + Returns the Heawood Graph, a (3,6) cage. + + The Heawood Graph is an undirected graph with 14 nodes and 21 edges, + named after Percy John Heawood [1]_. + It is cubic symmetric, nonplanar, Hamiltonian, and can be represented + in LCF notation as ``[5,-5]^7`` [2]_. + It is the unique (3,6)-cage: the regular cubic graph of girth 6 with + minimal number of vertices [3]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Heawood Graph with 14 nodes and 21 edges + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Heawood_graph + .. [2] https://mathworld.wolfram.com/HeawoodGraph.html + .. [3] https://www.win.tue.nl/~aeb/graphs/Heawood.html + + """ + G = LCF_graph(14, [5, -5], 7, create_using) + G.name = "Heawood Graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def hoffman_singleton_graph(): + """ + Returns the Hoffman-Singleton Graph. + + The Hoffman–Singleton graph is a symmetrical undirected graph + with 50 nodes and 175 edges. + All indices lie in ``Z % 5``: that is, the integers mod 5 [1]_. + It is the only regular graph of vertex degree 7, diameter 2, and girth 5. + It is the unique (7,5)-cage graph and Moore graph, and contains many + copies of the Petersen graph [2]_. + + Returns + ------- + G : networkx Graph + Hoffman–Singleton Graph with 50 nodes and 175 edges + + Notes + ----- + Constructed from pentagon and pentagram as follows: Take five pentagons $P_h$ + and five pentagrams $Q_i$ . Join vertex $j$ of $P_h$ to vertex $h·i+j$ of $Q_i$ [3]_. + + References + ---------- + .. [1] https://blogs.ams.org/visualinsight/2016/02/01/hoffman-singleton-graph/ + .. [2] https://mathworld.wolfram.com/Hoffman-SingletonGraph.html + .. [3] https://en.wikipedia.org/wiki/Hoffman%E2%80%93Singleton_graph + + """ + G = nx.Graph() + for i in range(5): + for j in range(5): + G.add_edge(("pentagon", i, j), ("pentagon", i, (j - 1) % 5)) + G.add_edge(("pentagon", i, j), ("pentagon", i, (j + 1) % 5)) + G.add_edge(("pentagram", i, j), ("pentagram", i, (j - 2) % 5)) + G.add_edge(("pentagram", i, j), ("pentagram", i, (j + 2) % 5)) + for k in range(5): + G.add_edge(("pentagon", i, j), ("pentagram", k, (i * k + j) % 5)) + G = nx.convert_node_labels_to_integers(G) + G.name = "Hoffman-Singleton Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def house_graph(create_using=None): + """ + Returns the House graph (square with triangle on top) + + The house graph is a simple undirected graph with + 5 nodes and 6 edges [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + House graph in the form of a square with a triangle on top + + References + ---------- + .. [1] https://mathworld.wolfram.com/HouseGraph.html + """ + G = nx.from_dict_of_lists( + {0: [1, 2], 1: [0, 3], 2: [0, 3, 4], 3: [1, 2, 4], 4: [2, 3]}, + create_using=create_using, + ) + G.name = "House Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def house_x_graph(create_using=None): + """ + Returns the House graph with a cross inside the house square. + + The House X-graph is the House graph plus the two edges connecting diagonally + opposite vertices of the square base. It is also one of the two graphs + obtained by removing two edges from the pentatope graph [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + House graph with diagonal vertices connected + + References + ---------- + .. [1] https://mathworld.wolfram.com/HouseGraph.html + """ + G = house_graph(create_using) + G.add_edges_from([(0, 3), (1, 2)]) + G.name = "House-with-X-inside Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def icosahedral_graph(create_using=None): + """ + Returns the Platonic Icosahedral graph. + + The icosahedral graph has 12 nodes and 30 edges. It is a Platonic graph + whose nodes have the connectivity of the icosahedron. It is undirected, + regular and Hamiltonian [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Icosahedral graph with 12 nodes and 30 edges. + + References + ---------- + .. [1] https://mathworld.wolfram.com/IcosahedralGraph.html + """ + G = nx.from_dict_of_lists( + { + 0: [1, 5, 7, 8, 11], + 1: [2, 5, 6, 8], + 2: [3, 6, 8, 9], + 3: [4, 6, 9, 10], + 4: [5, 6, 10, 11], + 5: [6, 11], + 7: [8, 9, 10, 11], + 8: [9], + 9: [10], + 10: [11], + }, + create_using=create_using, + ) + G.name = "Platonic Icosahedral Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def krackhardt_kite_graph(create_using=None): + """ + Returns the Krackhardt Kite Social Network. + + A 10 actor social network introduced by David Krackhardt + to illustrate different centrality measures [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Krackhardt Kite graph with 10 nodes and 18 edges + + Notes + ----- + The traditional labeling is: + Andre=1, Beverley=2, Carol=3, Diane=4, + Ed=5, Fernando=6, Garth=7, Heather=8, Ike=9, Jane=10. + + References + ---------- + .. [1] Krackhardt, David. "Assessing the Political Landscape: Structure, + Cognition, and Power in Organizations". Administrative Science Quarterly. + 35 (2): 342–369. doi:10.2307/2393394. JSTOR 2393394. June 1990. + + """ + G = nx.from_dict_of_lists( + { + 0: [1, 2, 3, 5], + 1: [0, 3, 4, 6], + 2: [0, 3, 5], + 3: [0, 1, 2, 4, 5, 6], + 4: [1, 3, 6], + 5: [0, 2, 3, 6, 7], + 6: [1, 3, 4, 5, 7], + 7: [5, 6, 8], + 8: [7, 9], + 9: [8], + }, + create_using=create_using, + ) + G.name = "Krackhardt Kite Social Network" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def moebius_kantor_graph(create_using=None): + """ + Returns the Moebius-Kantor graph. + + The Möbius-Kantor graph is the cubic symmetric graph on 16 nodes. + Its LCF notation is [5,-5]^8, and it is isomorphic to the generalized + Petersen graph [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Moebius-Kantor graph + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/M%C3%B6bius%E2%80%93Kantor_graph + + """ + G = LCF_graph(16, [5, -5], 8, create_using) + G.name = "Moebius-Kantor Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def octahedral_graph(create_using=None): + """ + Returns the Platonic Octahedral graph. + + The octahedral graph is the 6-node 12-edge Platonic graph having the + connectivity of the octahedron [1]_. If 6 couples go to a party, + and each person shakes hands with every person except his or her partner, + then this graph describes the set of handshakes that take place; + for this reason it is also called the cocktail party graph [2]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Octahedral graph + + References + ---------- + .. [1] https://mathworld.wolfram.com/OctahedralGraph.html + .. [2] https://en.wikipedia.org/wiki/Tur%C3%A1n_graph#Special_cases + + """ + G = nx.from_dict_of_lists( + {0: [1, 2, 3, 4], 1: [2, 3, 5], 2: [4, 5], 3: [4, 5], 4: [5]}, + create_using=create_using, + ) + G.name = "Platonic Octahedral Graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def pappus_graph(): + """ + Returns the Pappus graph. + + The Pappus graph is a cubic symmetric distance-regular graph with 18 nodes + and 27 edges. It is Hamiltonian and can be represented in LCF notation as + [5,7,-7,7,-7,-5]^3 [1]_. + + Returns + ------- + G : networkx Graph + Pappus graph + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Pappus_graph + """ + G = LCF_graph(18, [5, 7, -7, 7, -7, -5], 3) + G.name = "Pappus Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def petersen_graph(create_using=None): + """ + Returns the Petersen graph. + + The Peterson graph is a cubic, undirected graph with 10 nodes and 15 edges [1]_. + Julius Petersen constructed the graph as the smallest counterexample + against the claim that a connected bridgeless cubic graph + has an edge colouring with three colours [2]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Petersen graph + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Petersen_graph + .. [2] https://www.win.tue.nl/~aeb/drg/graphs/Petersen.html + """ + G = nx.from_dict_of_lists( + { + 0: [1, 4, 5], + 1: [0, 2, 6], + 2: [1, 3, 7], + 3: [2, 4, 8], + 4: [3, 0, 9], + 5: [0, 7, 8], + 6: [1, 8, 9], + 7: [2, 5, 9], + 8: [3, 5, 6], + 9: [4, 6, 7], + }, + create_using=create_using, + ) + G.name = "Petersen Graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def sedgewick_maze_graph(create_using=None): + """ + Return a small maze with a cycle. + + This is the maze used in Sedgewick, 3rd Edition, Part 5, Graph + Algorithms, Chapter 18, e.g. Figure 18.2 and following [1]_. + Nodes are numbered 0,..,7 + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Small maze with a cycle + + References + ---------- + .. [1] Figure 18.2, Chapter 18, Graph Algorithms (3rd Ed), Sedgewick + """ + G = empty_graph(0, create_using) + G.add_nodes_from(range(8)) + G.add_edges_from([[0, 2], [0, 7], [0, 5]]) + G.add_edges_from([[1, 7], [2, 6]]) + G.add_edges_from([[3, 4], [3, 5]]) + G.add_edges_from([[4, 5], [4, 7], [4, 6]]) + G.name = "Sedgewick Maze" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def tetrahedral_graph(create_using=None): + """ + Returns the 3-regular Platonic Tetrahedral graph. + + Tetrahedral graph has 4 nodes and 6 edges. It is a + special case of the complete graph, K4, and wheel graph, W4. + It is one of the 5 platonic graphs [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Tetrahedral Graph + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Tetrahedron#Tetrahedral_graph + + """ + G = complete_graph(4, create_using) + G.name = "Platonic Tetrahedral Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def truncated_cube_graph(create_using=None): + """ + Returns the skeleton of the truncated cube. + + The truncated cube is an Archimedean solid with 14 regular + faces (6 octagonal and 8 triangular), 36 edges and 24 nodes [1]_. + The truncated cube is created by truncating (cutting off) the tips + of the cube one third of the way into each edge [2]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Skeleton of the truncated cube + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Truncated_cube + .. [2] https://www.coolmath.com/reference/polyhedra-truncated-cube + + """ + G = nx.from_dict_of_lists( + { + 0: [1, 2, 4], + 1: [11, 14], + 2: [3, 4], + 3: [6, 8], + 4: [5], + 5: [16, 18], + 6: [7, 8], + 7: [10, 12], + 8: [9], + 9: [17, 20], + 10: [11, 12], + 11: [14], + 12: [13], + 13: [21, 22], + 14: [15], + 15: [19, 23], + 16: [17, 18], + 17: [20], + 18: [19], + 19: [23], + 20: [21], + 21: [22], + 22: [23], + }, + create_using=create_using, + ) + G.name = "Truncated Cube Graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def truncated_tetrahedron_graph(create_using=None): + """ + Returns the skeleton of the truncated Platonic tetrahedron. + + The truncated tetrahedron is an Archimedean solid with 4 regular hexagonal faces, + 4 equilateral triangle faces, 12 nodes and 18 edges. It can be constructed by truncating + all 4 vertices of a regular tetrahedron at one third of the original edge length [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Skeleton of the truncated tetrahedron + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Truncated_tetrahedron + + """ + G = path_graph(12, create_using) + G.add_edges_from([(0, 2), (0, 9), (1, 6), (3, 11), (4, 11), (5, 7), (8, 10)]) + G.name = "Truncated Tetrahedron Graph" + return G + + +@_raise_on_directed +@nx._dispatchable(graphs=None, returns_graph=True) +def tutte_graph(create_using=None): + """ + Returns the Tutte graph. + + The Tutte graph is a cubic polyhedral, non-Hamiltonian graph. It has + 46 nodes and 69 edges. + It is a counterexample to Tait's conjecture that every 3-regular polyhedron + has a Hamiltonian cycle. + It can be realized geometrically from a tetrahedron by multiply truncating + three of its vertices [1]_. + + Parameters + ---------- + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + G : networkx Graph + Tutte graph + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Tutte_graph + """ + G = nx.from_dict_of_lists( + { + 0: [1, 2, 3], + 1: [4, 26], + 2: [10, 11], + 3: [18, 19], + 4: [5, 33], + 5: [6, 29], + 6: [7, 27], + 7: [8, 14], + 8: [9, 38], + 9: [10, 37], + 10: [39], + 11: [12, 39], + 12: [13, 35], + 13: [14, 15], + 14: [34], + 15: [16, 22], + 16: [17, 44], + 17: [18, 43], + 18: [45], + 19: [20, 45], + 20: [21, 41], + 21: [22, 23], + 22: [40], + 23: [24, 27], + 24: [25, 32], + 25: [26, 31], + 26: [33], + 27: [28], + 28: [29, 32], + 29: [30], + 30: [31, 33], + 31: [32], + 34: [35, 38], + 35: [36], + 36: [37, 39], + 37: [38], + 40: [41, 44], + 41: [42], + 42: [43, 45], + 43: [44], + }, + create_using=create_using, + ) + G.name = "Tutte's Graph" + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/social.py b/venv/lib/python3.10/site-packages/networkx/generators/social.py new file mode 100644 index 0000000000000000000000000000000000000000..f41b2d88b4bc09f4e45967731f1dc8acfb48420b --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/social.py @@ -0,0 +1,554 @@ +""" +Famous social networks. +""" + +import networkx as nx + +__all__ = [ + "karate_club_graph", + "davis_southern_women_graph", + "florentine_families_graph", + "les_miserables_graph", +] + + +@nx._dispatchable(graphs=None, returns_graph=True) +def karate_club_graph(): + """Returns Zachary's Karate Club graph. + + Each node in the returned graph has a node attribute 'club' that + indicates the name of the club to which the member represented by that node + belongs, either 'Mr. Hi' or 'Officer'. Each edge has a weight based on the + number of contexts in which that edge's incident node members interacted. + + The dataset is derived from the 'Club After Split From Data' column of Table 3 in [1]_. + This was in turn derived from the 'Club After Fission' column of Table 1 in the + same paper. Note that the nodes are 0-indexed in NetworkX, but 1-indexed in the + paper (the 'Individual Number in Matrix C' column of Table 3 starts at 1). This + means, for example, that ``G.nodes[9]["club"]`` returns 'Officer', which + corresponds to row 10 of Table 3 in the paper. + + Examples + -------- + To get the name of the club to which a node belongs:: + + >>> G = nx.karate_club_graph() + >>> G.nodes[5]["club"] + 'Mr. Hi' + >>> G.nodes[9]["club"] + 'Officer' + + References + ---------- + .. [1] Zachary, Wayne W. + "An Information Flow Model for Conflict and Fission in Small Groups." + *Journal of Anthropological Research*, 33, 452--473, (1977). + """ + # Create the set of all members, and the members of each club. + all_members = set(range(34)) + club1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 16, 17, 19, 21} + # club2 = all_members - club1 + + G = nx.Graph() + G.add_nodes_from(all_members) + G.name = "Zachary's Karate Club" + + zacharydat = """\ +0 4 5 3 3 3 3 2 2 0 2 3 2 3 0 0 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 2 0 0 +4 0 6 3 0 0 0 4 0 0 0 0 0 5 0 0 0 1 0 2 0 2 0 0 0 0 0 0 0 0 2 0 0 0 +5 6 0 3 0 0 0 4 5 1 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 0 0 0 3 0 +3 3 3 0 0 0 0 3 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +3 0 0 0 0 0 2 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +3 0 0 0 0 0 5 0 0 0 3 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +3 0 0 0 2 5 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +2 4 4 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +2 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 4 3 +0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 +2 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +3 5 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 4 +0 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 +2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 1 +2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 4 0 2 0 0 5 4 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 3 0 0 0 2 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 2 0 0 0 0 0 0 7 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 2 +0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 3 0 0 0 0 0 0 0 0 4 +0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 4 0 0 0 0 0 3 2 +0 2 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 +2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 7 0 0 2 0 0 0 4 4 +0 0 2 0 0 0 0 0 3 0 0 0 0 0 3 3 0 0 1 0 3 0 2 5 0 0 0 0 0 4 3 4 0 5 +0 0 0 0 0 0 0 0 4 2 0 0 0 3 2 4 0 0 2 1 1 0 3 4 0 0 2 4 2 2 3 4 5 0""" + + for row, line in enumerate(zacharydat.split("\n")): + thisrow = [int(b) for b in line.split()] + for col, entry in enumerate(thisrow): + if entry >= 1: + G.add_edge(row, col, weight=entry) + + # Add the name of each member's club as a node attribute. + for v in G: + G.nodes[v]["club"] = "Mr. Hi" if v in club1 else "Officer" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def davis_southern_women_graph(): + """Returns Davis Southern women social network. + + This is a bipartite graph. + + References + ---------- + .. [1] A. Davis, Gardner, B. B., Gardner, M. R., 1941. Deep South. + University of Chicago Press, Chicago, IL. + """ + G = nx.Graph() + # Top nodes + women = [ + "Evelyn Jefferson", + "Laura Mandeville", + "Theresa Anderson", + "Brenda Rogers", + "Charlotte McDowd", + "Frances Anderson", + "Eleanor Nye", + "Pearl Oglethorpe", + "Ruth DeSand", + "Verne Sanderson", + "Myra Liddel", + "Katherina Rogers", + "Sylvia Avondale", + "Nora Fayette", + "Helen Lloyd", + "Dorothy Murchison", + "Olivia Carleton", + "Flora Price", + ] + G.add_nodes_from(women, bipartite=0) + # Bottom nodes + events = [ + "E1", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "E10", + "E11", + "E12", + "E13", + "E14", + ] + G.add_nodes_from(events, bipartite=1) + + G.add_edges_from( + [ + ("Evelyn Jefferson", "E1"), + ("Evelyn Jefferson", "E2"), + ("Evelyn Jefferson", "E3"), + ("Evelyn Jefferson", "E4"), + ("Evelyn Jefferson", "E5"), + ("Evelyn Jefferson", "E6"), + ("Evelyn Jefferson", "E8"), + ("Evelyn Jefferson", "E9"), + ("Laura Mandeville", "E1"), + ("Laura Mandeville", "E2"), + ("Laura Mandeville", "E3"), + ("Laura Mandeville", "E5"), + ("Laura Mandeville", "E6"), + ("Laura Mandeville", "E7"), + ("Laura Mandeville", "E8"), + ("Theresa Anderson", "E2"), + ("Theresa Anderson", "E3"), + ("Theresa Anderson", "E4"), + ("Theresa Anderson", "E5"), + ("Theresa Anderson", "E6"), + ("Theresa Anderson", "E7"), + ("Theresa Anderson", "E8"), + ("Theresa Anderson", "E9"), + ("Brenda Rogers", "E1"), + ("Brenda Rogers", "E3"), + ("Brenda Rogers", "E4"), + ("Brenda Rogers", "E5"), + ("Brenda Rogers", "E6"), + ("Brenda Rogers", "E7"), + ("Brenda Rogers", "E8"), + ("Charlotte McDowd", "E3"), + ("Charlotte McDowd", "E4"), + ("Charlotte McDowd", "E5"), + ("Charlotte McDowd", "E7"), + ("Frances Anderson", "E3"), + ("Frances Anderson", "E5"), + ("Frances Anderson", "E6"), + ("Frances Anderson", "E8"), + ("Eleanor Nye", "E5"), + ("Eleanor Nye", "E6"), + ("Eleanor Nye", "E7"), + ("Eleanor Nye", "E8"), + ("Pearl Oglethorpe", "E6"), + ("Pearl Oglethorpe", "E8"), + ("Pearl Oglethorpe", "E9"), + ("Ruth DeSand", "E5"), + ("Ruth DeSand", "E7"), + ("Ruth DeSand", "E8"), + ("Ruth DeSand", "E9"), + ("Verne Sanderson", "E7"), + ("Verne Sanderson", "E8"), + ("Verne Sanderson", "E9"), + ("Verne Sanderson", "E12"), + ("Myra Liddel", "E8"), + ("Myra Liddel", "E9"), + ("Myra Liddel", "E10"), + ("Myra Liddel", "E12"), + ("Katherina Rogers", "E8"), + ("Katherina Rogers", "E9"), + ("Katherina Rogers", "E10"), + ("Katherina Rogers", "E12"), + ("Katherina Rogers", "E13"), + ("Katherina Rogers", "E14"), + ("Sylvia Avondale", "E7"), + ("Sylvia Avondale", "E8"), + ("Sylvia Avondale", "E9"), + ("Sylvia Avondale", "E10"), + ("Sylvia Avondale", "E12"), + ("Sylvia Avondale", "E13"), + ("Sylvia Avondale", "E14"), + ("Nora Fayette", "E6"), + ("Nora Fayette", "E7"), + ("Nora Fayette", "E9"), + ("Nora Fayette", "E10"), + ("Nora Fayette", "E11"), + ("Nora Fayette", "E12"), + ("Nora Fayette", "E13"), + ("Nora Fayette", "E14"), + ("Helen Lloyd", "E7"), + ("Helen Lloyd", "E8"), + ("Helen Lloyd", "E10"), + ("Helen Lloyd", "E11"), + ("Helen Lloyd", "E12"), + ("Dorothy Murchison", "E8"), + ("Dorothy Murchison", "E9"), + ("Olivia Carleton", "E9"), + ("Olivia Carleton", "E11"), + ("Flora Price", "E9"), + ("Flora Price", "E11"), + ] + ) + G.graph["top"] = women + G.graph["bottom"] = events + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def florentine_families_graph(): + """Returns Florentine families graph. + + References + ---------- + .. [1] Ronald L. Breiger and Philippa E. Pattison + Cumulated social roles: The duality of persons and their algebras,1 + Social Networks, Volume 8, Issue 3, September 1986, Pages 215-256 + """ + G = nx.Graph() + G.add_edge("Acciaiuoli", "Medici") + G.add_edge("Castellani", "Peruzzi") + G.add_edge("Castellani", "Strozzi") + G.add_edge("Castellani", "Barbadori") + G.add_edge("Medici", "Barbadori") + G.add_edge("Medici", "Ridolfi") + G.add_edge("Medici", "Tornabuoni") + G.add_edge("Medici", "Albizzi") + G.add_edge("Medici", "Salviati") + G.add_edge("Salviati", "Pazzi") + G.add_edge("Peruzzi", "Strozzi") + G.add_edge("Peruzzi", "Bischeri") + G.add_edge("Strozzi", "Ridolfi") + G.add_edge("Strozzi", "Bischeri") + G.add_edge("Ridolfi", "Tornabuoni") + G.add_edge("Tornabuoni", "Guadagni") + G.add_edge("Albizzi", "Ginori") + G.add_edge("Albizzi", "Guadagni") + G.add_edge("Bischeri", "Guadagni") + G.add_edge("Guadagni", "Lamberteschi") + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def les_miserables_graph(): + """Returns coappearance network of characters in the novel Les Miserables. + + References + ---------- + .. [1] D. E. Knuth, 1993. + The Stanford GraphBase: a platform for combinatorial computing, + pp. 74-87. New York: AcM Press. + """ + G = nx.Graph() + G.add_edge("Napoleon", "Myriel", weight=1) + G.add_edge("MlleBaptistine", "Myriel", weight=8) + G.add_edge("MmeMagloire", "Myriel", weight=10) + G.add_edge("MmeMagloire", "MlleBaptistine", weight=6) + G.add_edge("CountessDeLo", "Myriel", weight=1) + G.add_edge("Geborand", "Myriel", weight=1) + G.add_edge("Champtercier", "Myriel", weight=1) + G.add_edge("Cravatte", "Myriel", weight=1) + G.add_edge("Count", "Myriel", weight=2) + G.add_edge("OldMan", "Myriel", weight=1) + G.add_edge("Valjean", "Labarre", weight=1) + G.add_edge("Valjean", "MmeMagloire", weight=3) + G.add_edge("Valjean", "MlleBaptistine", weight=3) + G.add_edge("Valjean", "Myriel", weight=5) + G.add_edge("Marguerite", "Valjean", weight=1) + G.add_edge("MmeDeR", "Valjean", weight=1) + G.add_edge("Isabeau", "Valjean", weight=1) + G.add_edge("Gervais", "Valjean", weight=1) + G.add_edge("Listolier", "Tholomyes", weight=4) + G.add_edge("Fameuil", "Tholomyes", weight=4) + G.add_edge("Fameuil", "Listolier", weight=4) + G.add_edge("Blacheville", "Tholomyes", weight=4) + G.add_edge("Blacheville", "Listolier", weight=4) + G.add_edge("Blacheville", "Fameuil", weight=4) + G.add_edge("Favourite", "Tholomyes", weight=3) + G.add_edge("Favourite", "Listolier", weight=3) + G.add_edge("Favourite", "Fameuil", weight=3) + G.add_edge("Favourite", "Blacheville", weight=4) + G.add_edge("Dahlia", "Tholomyes", weight=3) + G.add_edge("Dahlia", "Listolier", weight=3) + G.add_edge("Dahlia", "Fameuil", weight=3) + G.add_edge("Dahlia", "Blacheville", weight=3) + G.add_edge("Dahlia", "Favourite", weight=5) + G.add_edge("Zephine", "Tholomyes", weight=3) + G.add_edge("Zephine", "Listolier", weight=3) + G.add_edge("Zephine", "Fameuil", weight=3) + G.add_edge("Zephine", "Blacheville", weight=3) + G.add_edge("Zephine", "Favourite", weight=4) + G.add_edge("Zephine", "Dahlia", weight=4) + G.add_edge("Fantine", "Tholomyes", weight=3) + G.add_edge("Fantine", "Listolier", weight=3) + G.add_edge("Fantine", "Fameuil", weight=3) + G.add_edge("Fantine", "Blacheville", weight=3) + G.add_edge("Fantine", "Favourite", weight=4) + G.add_edge("Fantine", "Dahlia", weight=4) + G.add_edge("Fantine", "Zephine", weight=4) + G.add_edge("Fantine", "Marguerite", weight=2) + G.add_edge("Fantine", "Valjean", weight=9) + G.add_edge("MmeThenardier", "Fantine", weight=2) + G.add_edge("MmeThenardier", "Valjean", weight=7) + G.add_edge("Thenardier", "MmeThenardier", weight=13) + G.add_edge("Thenardier", "Fantine", weight=1) + G.add_edge("Thenardier", "Valjean", weight=12) + G.add_edge("Cosette", "MmeThenardier", weight=4) + G.add_edge("Cosette", "Valjean", weight=31) + G.add_edge("Cosette", "Tholomyes", weight=1) + G.add_edge("Cosette", "Thenardier", weight=1) + G.add_edge("Javert", "Valjean", weight=17) + G.add_edge("Javert", "Fantine", weight=5) + G.add_edge("Javert", "Thenardier", weight=5) + G.add_edge("Javert", "MmeThenardier", weight=1) + G.add_edge("Javert", "Cosette", weight=1) + G.add_edge("Fauchelevent", "Valjean", weight=8) + G.add_edge("Fauchelevent", "Javert", weight=1) + G.add_edge("Bamatabois", "Fantine", weight=1) + G.add_edge("Bamatabois", "Javert", weight=1) + G.add_edge("Bamatabois", "Valjean", weight=2) + G.add_edge("Perpetue", "Fantine", weight=1) + G.add_edge("Simplice", "Perpetue", weight=2) + G.add_edge("Simplice", "Valjean", weight=3) + G.add_edge("Simplice", "Fantine", weight=2) + G.add_edge("Simplice", "Javert", weight=1) + G.add_edge("Scaufflaire", "Valjean", weight=1) + G.add_edge("Woman1", "Valjean", weight=2) + G.add_edge("Woman1", "Javert", weight=1) + G.add_edge("Judge", "Valjean", weight=3) + G.add_edge("Judge", "Bamatabois", weight=2) + G.add_edge("Champmathieu", "Valjean", weight=3) + G.add_edge("Champmathieu", "Judge", weight=3) + G.add_edge("Champmathieu", "Bamatabois", weight=2) + G.add_edge("Brevet", "Judge", weight=2) + G.add_edge("Brevet", "Champmathieu", weight=2) + G.add_edge("Brevet", "Valjean", weight=2) + G.add_edge("Brevet", "Bamatabois", weight=1) + G.add_edge("Chenildieu", "Judge", weight=2) + G.add_edge("Chenildieu", "Champmathieu", weight=2) + G.add_edge("Chenildieu", "Brevet", weight=2) + G.add_edge("Chenildieu", "Valjean", weight=2) + G.add_edge("Chenildieu", "Bamatabois", weight=1) + G.add_edge("Cochepaille", "Judge", weight=2) + G.add_edge("Cochepaille", "Champmathieu", weight=2) + G.add_edge("Cochepaille", "Brevet", weight=2) + G.add_edge("Cochepaille", "Chenildieu", weight=2) + G.add_edge("Cochepaille", "Valjean", weight=2) + G.add_edge("Cochepaille", "Bamatabois", weight=1) + G.add_edge("Pontmercy", "Thenardier", weight=1) + G.add_edge("Boulatruelle", "Thenardier", weight=1) + G.add_edge("Eponine", "MmeThenardier", weight=2) + G.add_edge("Eponine", "Thenardier", weight=3) + G.add_edge("Anzelma", "Eponine", weight=2) + G.add_edge("Anzelma", "Thenardier", weight=2) + G.add_edge("Anzelma", "MmeThenardier", weight=1) + G.add_edge("Woman2", "Valjean", weight=3) + G.add_edge("Woman2", "Cosette", weight=1) + G.add_edge("Woman2", "Javert", weight=1) + G.add_edge("MotherInnocent", "Fauchelevent", weight=3) + G.add_edge("MotherInnocent", "Valjean", weight=1) + G.add_edge("Gribier", "Fauchelevent", weight=2) + G.add_edge("MmeBurgon", "Jondrette", weight=1) + G.add_edge("Gavroche", "MmeBurgon", weight=2) + G.add_edge("Gavroche", "Thenardier", weight=1) + G.add_edge("Gavroche", "Javert", weight=1) + G.add_edge("Gavroche", "Valjean", weight=1) + G.add_edge("Gillenormand", "Cosette", weight=3) + G.add_edge("Gillenormand", "Valjean", weight=2) + G.add_edge("Magnon", "Gillenormand", weight=1) + G.add_edge("Magnon", "MmeThenardier", weight=1) + G.add_edge("MlleGillenormand", "Gillenormand", weight=9) + G.add_edge("MlleGillenormand", "Cosette", weight=2) + G.add_edge("MlleGillenormand", "Valjean", weight=2) + G.add_edge("MmePontmercy", "MlleGillenormand", weight=1) + G.add_edge("MmePontmercy", "Pontmercy", weight=1) + G.add_edge("MlleVaubois", "MlleGillenormand", weight=1) + G.add_edge("LtGillenormand", "MlleGillenormand", weight=2) + G.add_edge("LtGillenormand", "Gillenormand", weight=1) + G.add_edge("LtGillenormand", "Cosette", weight=1) + G.add_edge("Marius", "MlleGillenormand", weight=6) + G.add_edge("Marius", "Gillenormand", weight=12) + G.add_edge("Marius", "Pontmercy", weight=1) + G.add_edge("Marius", "LtGillenormand", weight=1) + G.add_edge("Marius", "Cosette", weight=21) + G.add_edge("Marius", "Valjean", weight=19) + G.add_edge("Marius", "Tholomyes", weight=1) + G.add_edge("Marius", "Thenardier", weight=2) + G.add_edge("Marius", "Eponine", weight=5) + G.add_edge("Marius", "Gavroche", weight=4) + G.add_edge("BaronessT", "Gillenormand", weight=1) + G.add_edge("BaronessT", "Marius", weight=1) + G.add_edge("Mabeuf", "Marius", weight=1) + G.add_edge("Mabeuf", "Eponine", weight=1) + G.add_edge("Mabeuf", "Gavroche", weight=1) + G.add_edge("Enjolras", "Marius", weight=7) + G.add_edge("Enjolras", "Gavroche", weight=7) + G.add_edge("Enjolras", "Javert", weight=6) + G.add_edge("Enjolras", "Mabeuf", weight=1) + G.add_edge("Enjolras", "Valjean", weight=4) + G.add_edge("Combeferre", "Enjolras", weight=15) + G.add_edge("Combeferre", "Marius", weight=5) + G.add_edge("Combeferre", "Gavroche", weight=6) + G.add_edge("Combeferre", "Mabeuf", weight=2) + G.add_edge("Prouvaire", "Gavroche", weight=1) + G.add_edge("Prouvaire", "Enjolras", weight=4) + G.add_edge("Prouvaire", "Combeferre", weight=2) + G.add_edge("Feuilly", "Gavroche", weight=2) + G.add_edge("Feuilly", "Enjolras", weight=6) + G.add_edge("Feuilly", "Prouvaire", weight=2) + G.add_edge("Feuilly", "Combeferre", weight=5) + G.add_edge("Feuilly", "Mabeuf", weight=1) + G.add_edge("Feuilly", "Marius", weight=1) + G.add_edge("Courfeyrac", "Marius", weight=9) + G.add_edge("Courfeyrac", "Enjolras", weight=17) + G.add_edge("Courfeyrac", "Combeferre", weight=13) + G.add_edge("Courfeyrac", "Gavroche", weight=7) + G.add_edge("Courfeyrac", "Mabeuf", weight=2) + G.add_edge("Courfeyrac", "Eponine", weight=1) + G.add_edge("Courfeyrac", "Feuilly", weight=6) + G.add_edge("Courfeyrac", "Prouvaire", weight=3) + G.add_edge("Bahorel", "Combeferre", weight=5) + G.add_edge("Bahorel", "Gavroche", weight=5) + G.add_edge("Bahorel", "Courfeyrac", weight=6) + G.add_edge("Bahorel", "Mabeuf", weight=2) + G.add_edge("Bahorel", "Enjolras", weight=4) + G.add_edge("Bahorel", "Feuilly", weight=3) + G.add_edge("Bahorel", "Prouvaire", weight=2) + G.add_edge("Bahorel", "Marius", weight=1) + G.add_edge("Bossuet", "Marius", weight=5) + G.add_edge("Bossuet", "Courfeyrac", weight=12) + G.add_edge("Bossuet", "Gavroche", weight=5) + G.add_edge("Bossuet", "Bahorel", weight=4) + G.add_edge("Bossuet", "Enjolras", weight=10) + G.add_edge("Bossuet", "Feuilly", weight=6) + G.add_edge("Bossuet", "Prouvaire", weight=2) + G.add_edge("Bossuet", "Combeferre", weight=9) + G.add_edge("Bossuet", "Mabeuf", weight=1) + G.add_edge("Bossuet", "Valjean", weight=1) + G.add_edge("Joly", "Bahorel", weight=5) + G.add_edge("Joly", "Bossuet", weight=7) + G.add_edge("Joly", "Gavroche", weight=3) + G.add_edge("Joly", "Courfeyrac", weight=5) + G.add_edge("Joly", "Enjolras", weight=5) + G.add_edge("Joly", "Feuilly", weight=5) + G.add_edge("Joly", "Prouvaire", weight=2) + G.add_edge("Joly", "Combeferre", weight=5) + G.add_edge("Joly", "Mabeuf", weight=1) + G.add_edge("Joly", "Marius", weight=2) + G.add_edge("Grantaire", "Bossuet", weight=3) + G.add_edge("Grantaire", "Enjolras", weight=3) + G.add_edge("Grantaire", "Combeferre", weight=1) + G.add_edge("Grantaire", "Courfeyrac", weight=2) + G.add_edge("Grantaire", "Joly", weight=2) + G.add_edge("Grantaire", "Gavroche", weight=1) + G.add_edge("Grantaire", "Bahorel", weight=1) + G.add_edge("Grantaire", "Feuilly", weight=1) + G.add_edge("Grantaire", "Prouvaire", weight=1) + G.add_edge("MotherPlutarch", "Mabeuf", weight=3) + G.add_edge("Gueulemer", "Thenardier", weight=5) + G.add_edge("Gueulemer", "Valjean", weight=1) + G.add_edge("Gueulemer", "MmeThenardier", weight=1) + G.add_edge("Gueulemer", "Javert", weight=1) + G.add_edge("Gueulemer", "Gavroche", weight=1) + G.add_edge("Gueulemer", "Eponine", weight=1) + G.add_edge("Babet", "Thenardier", weight=6) + G.add_edge("Babet", "Gueulemer", weight=6) + G.add_edge("Babet", "Valjean", weight=1) + G.add_edge("Babet", "MmeThenardier", weight=1) + G.add_edge("Babet", "Javert", weight=2) + G.add_edge("Babet", "Gavroche", weight=1) + G.add_edge("Babet", "Eponine", weight=1) + G.add_edge("Claquesous", "Thenardier", weight=4) + G.add_edge("Claquesous", "Babet", weight=4) + G.add_edge("Claquesous", "Gueulemer", weight=4) + G.add_edge("Claquesous", "Valjean", weight=1) + G.add_edge("Claquesous", "MmeThenardier", weight=1) + G.add_edge("Claquesous", "Javert", weight=1) + G.add_edge("Claquesous", "Eponine", weight=1) + G.add_edge("Claquesous", "Enjolras", weight=1) + G.add_edge("Montparnasse", "Javert", weight=1) + G.add_edge("Montparnasse", "Babet", weight=2) + G.add_edge("Montparnasse", "Gueulemer", weight=2) + G.add_edge("Montparnasse", "Claquesous", weight=2) + G.add_edge("Montparnasse", "Valjean", weight=1) + G.add_edge("Montparnasse", "Gavroche", weight=1) + G.add_edge("Montparnasse", "Eponine", weight=1) + G.add_edge("Montparnasse", "Thenardier", weight=1) + G.add_edge("Toussaint", "Cosette", weight=2) + G.add_edge("Toussaint", "Javert", weight=1) + G.add_edge("Toussaint", "Valjean", weight=1) + G.add_edge("Child1", "Gavroche", weight=2) + G.add_edge("Child2", "Gavroche", weight=2) + G.add_edge("Child2", "Child1", weight=3) + G.add_edge("Brujon", "Babet", weight=3) + G.add_edge("Brujon", "Gueulemer", weight=3) + G.add_edge("Brujon", "Thenardier", weight=3) + G.add_edge("Brujon", "Gavroche", weight=1) + G.add_edge("Brujon", "Eponine", weight=1) + G.add_edge("Brujon", "Claquesous", weight=1) + G.add_edge("Brujon", "Montparnasse", weight=1) + G.add_edge("MmeHucheloup", "Bossuet", weight=1) + G.add_edge("MmeHucheloup", "Joly", weight=1) + G.add_edge("MmeHucheloup", "Grantaire", weight=1) + G.add_edge("MmeHucheloup", "Bahorel", weight=1) + G.add_edge("MmeHucheloup", "Courfeyrac", weight=1) + G.add_edge("MmeHucheloup", "Gavroche", weight=1) + G.add_edge("MmeHucheloup", "Enjolras", weight=1) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/spectral_graph_forge.py b/venv/lib/python3.10/site-packages/networkx/generators/spectral_graph_forge.py new file mode 100644 index 0000000000000000000000000000000000000000..39a87f748307ac7884419a58dce84d0fdf498a88 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/spectral_graph_forge.py @@ -0,0 +1,120 @@ +"""Generates graphs with a given eigenvector structure""" + +import networkx as nx +from networkx.utils import np_random_state + +__all__ = ["spectral_graph_forge"] + + +@np_random_state(3) +@nx._dispatchable(returns_graph=True) +def spectral_graph_forge(G, alpha, transformation="identity", seed=None): + """Returns a random simple graph with spectrum resembling that of `G` + + This algorithm, called Spectral Graph Forge (SGF), computes the + eigenvectors of a given graph adjacency matrix, filters them and + builds a random graph with a similar eigenstructure. + SGF has been proved to be particularly useful for synthesizing + realistic social networks and it can also be used to anonymize + graph sensitive data. + + Parameters + ---------- + G : Graph + alpha : float + Ratio representing the percentage of eigenvectors of G to consider, + values in [0,1]. + transformation : string, optional + Represents the intended matrix linear transformation, possible values + are 'identity' and 'modularity' + seed : integer, random_state, or None (default) + Indicator of numpy random number generation state. + See :ref:`Randomness`. + + Returns + ------- + H : Graph + A graph with a similar eigenvector structure of the input one. + + Raises + ------ + NetworkXError + If transformation has a value different from 'identity' or 'modularity' + + Notes + ----- + Spectral Graph Forge (SGF) generates a random simple graph resembling the + global properties of the given one. + It leverages the low-rank approximation of the associated adjacency matrix + driven by the *alpha* precision parameter. + SGF preserves the number of nodes of the input graph and their ordering. + This way, nodes of output graphs resemble the properties of the input one + and attributes can be directly mapped. + + It considers the graph adjacency matrices which can optionally be + transformed to other symmetric real matrices (currently transformation + options include *identity* and *modularity*). + The *modularity* transformation, in the sense of Newman's modularity matrix + allows the focusing on community structure related properties of the graph. + + SGF applies a low-rank approximation whose fixed rank is computed from the + ratio *alpha* of the input graph adjacency matrix dimension. + This step performs a filtering on the input eigenvectors similar to the low + pass filtering common in telecommunications. + + The filtered values (after truncation) are used as input to a Bernoulli + sampling for constructing a random adjacency matrix. + + References + ---------- + .. [1] L. Baldesi, C. T. Butts, A. Markopoulou, "Spectral Graph Forge: + Graph Generation Targeting Modularity", IEEE Infocom, '18. + https://arxiv.org/abs/1801.01715 + .. [2] M. Newman, "Networks: an introduction", Oxford university press, + 2010 + + Examples + -------- + >>> G = nx.karate_club_graph() + >>> H = nx.spectral_graph_forge(G, 0.3) + >>> + """ + import numpy as np + import scipy as sp + + available_transformations = ["identity", "modularity"] + alpha = np.clip(alpha, 0, 1) + A = nx.to_numpy_array(G) + n = A.shape[1] + level = round(n * alpha) + + if transformation not in available_transformations: + msg = f"{transformation!r} is not a valid transformation. " + msg += f"Transformations: {available_transformations}" + raise nx.NetworkXError(msg) + + K = np.ones((1, n)) @ A + + B = A + if transformation == "modularity": + B -= K.T @ K / K.sum() + + # Compute low-rank approximation of B + evals, evecs = np.linalg.eigh(B) + k = np.argsort(np.abs(evals))[::-1] # indices of evals in descending order + evecs[:, k[np.arange(level, n)]] = 0 # set smallest eigenvectors to 0 + B = evecs @ np.diag(evals) @ evecs.T + + if transformation == "modularity": + B += K.T @ K / K.sum() + + B = np.clip(B, 0, 1) + np.fill_diagonal(B, 0) + + for i in range(n - 1): + B[i, i + 1 :] = sp.stats.bernoulli.rvs(B[i, i + 1 :], random_state=seed) + B[i + 1 :, i] = np.transpose(B[i, i + 1 :]) + + H = nx.from_numpy_array(B) + + return H diff --git a/venv/lib/python3.10/site-packages/networkx/generators/stochastic.py b/venv/lib/python3.10/site-packages/networkx/generators/stochastic.py new file mode 100644 index 0000000000000000000000000000000000000000..f53e2315470f8ffcdea0380026a933e06ddf6ea7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/stochastic.py @@ -0,0 +1,54 @@ +"""Functions for generating stochastic graphs from a given weighted directed +graph. + +""" + +import networkx as nx +from networkx.classes import DiGraph, MultiDiGraph +from networkx.utils import not_implemented_for + +__all__ = ["stochastic_graph"] + + +@not_implemented_for("undirected") +@nx._dispatchable( + edge_attrs="weight", mutates_input={"not copy": 1}, returns_graph=True +) +def stochastic_graph(G, copy=True, weight="weight"): + """Returns a right-stochastic representation of directed graph `G`. + + A right-stochastic graph is a weighted digraph in which for each + node, the sum of the weights of all the out-edges of that node is + 1. If the graph is already weighted (for example, via a 'weight' + edge attribute), the reweighting takes that into account. + + Parameters + ---------- + G : directed graph + A :class:`~networkx.DiGraph` or :class:`~networkx.MultiDiGraph`. + + copy : boolean, optional + If this is True, then this function returns a new graph with + the stochastic reweighting. Otherwise, the original graph is + modified in-place (and also returned, for convenience). + + weight : edge attribute key (optional, default='weight') + Edge attribute key used for reading the existing weight and + setting the new weight. If no attribute with this key is found + for an edge, then the edge weight is assumed to be 1. If an edge + has a weight, it must be a positive number. + + """ + if copy: + G = MultiDiGraph(G) if G.is_multigraph() else DiGraph(G) + # There is a tradeoff here: the dictionary of node degrees may + # require a lot of memory, whereas making a call to `G.out_degree` + # inside the loop may be costly in computation time. + degree = dict(G.out_degree(weight=weight)) + for u, v, d in G.edges(data=True): + if degree[u] == 0: + d[weight] = 0 + else: + d[weight] = d.get(weight, 1) / degree[u] + nx._clear_cache(G) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/sudoku.py b/venv/lib/python3.10/site-packages/networkx/generators/sudoku.py new file mode 100644 index 0000000000000000000000000000000000000000..f288ed24d1f189588de7e1e0bba61f50bbad0003 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/sudoku.py @@ -0,0 +1,131 @@ +"""Generator for Sudoku graphs + +This module gives a generator for n-Sudoku graphs. It can be used to develop +algorithms for solving or generating Sudoku puzzles. + +A completed Sudoku grid is a 9x9 array of integers between 1 and 9, with no +number appearing twice in the same row, column, or 3x3 box. + ++---------+---------+---------+ +| | 8 6 4 | | 3 7 1 | | 2 5 9 | +| | 3 2 5 | | 8 4 9 | | 7 6 1 | +| | 9 7 1 | | 2 6 5 | | 8 4 3 | ++---------+---------+---------+ +| | 4 3 6 | | 1 9 2 | | 5 8 7 | +| | 1 9 8 | | 6 5 7 | | 4 3 2 | +| | 2 5 7 | | 4 8 3 | | 9 1 6 | ++---------+---------+---------+ +| | 6 8 9 | | 7 3 4 | | 1 2 5 | +| | 7 1 3 | | 5 2 8 | | 6 9 4 | +| | 5 4 2 | | 9 1 6 | | 3 7 8 | ++---------+---------+---------+ + + +The Sudoku graph is an undirected graph with 81 vertices, corresponding to +the cells of a Sudoku grid. It is a regular graph of degree 20. Two distinct +vertices are adjacent if and only if the corresponding cells belong to the +same row, column, or box. A completed Sudoku grid corresponds to a vertex +coloring of the Sudoku graph with nine colors. + +More generally, the n-Sudoku graph is a graph with n^4 vertices, corresponding +to the cells of an n^2 by n^2 grid. Two distinct vertices are adjacent if and +only if they belong to the same row, column, or n by n box. + +References +---------- +.. [1] Herzberg, A. M., & Murty, M. R. (2007). Sudoku squares and chromatic + polynomials. Notices of the AMS, 54(6), 708-717. +.. [2] Sander, Torsten (2009), "Sudoku graphs are integral", + Electronic Journal of Combinatorics, 16 (1): Note 25, 7pp, MR 2529816 +.. [3] Wikipedia contributors. "Glossary of Sudoku." Wikipedia, The Free + Encyclopedia, 3 Dec. 2019. Web. 22 Dec. 2019. +""" + +import networkx as nx +from networkx.exception import NetworkXError + +__all__ = ["sudoku_graph"] + + +@nx._dispatchable(graphs=None, returns_graph=True) +def sudoku_graph(n=3): + """Returns the n-Sudoku graph. The default value of n is 3. + + The n-Sudoku graph is a graph with n^4 vertices, corresponding to the + cells of an n^2 by n^2 grid. Two distinct vertices are adjacent if and + only if they belong to the same row, column, or n-by-n box. + + Parameters + ---------- + n: integer + The order of the Sudoku graph, equal to the square root of the + number of rows. The default is 3. + + Returns + ------- + NetworkX graph + The n-Sudoku graph Sud(n). + + Examples + -------- + >>> G = nx.sudoku_graph() + >>> G.number_of_nodes() + 81 + >>> G.number_of_edges() + 810 + >>> sorted(G.neighbors(42)) + [6, 15, 24, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 51, 52, 53, 60, 69, 78] + >>> G = nx.sudoku_graph(2) + >>> G.number_of_nodes() + 16 + >>> G.number_of_edges() + 56 + + References + ---------- + .. [1] Herzberg, A. M., & Murty, M. R. (2007). Sudoku squares and chromatic + polynomials. Notices of the AMS, 54(6), 708-717. + .. [2] Sander, Torsten (2009), "Sudoku graphs are integral", + Electronic Journal of Combinatorics, 16 (1): Note 25, 7pp, MR 2529816 + .. [3] Wikipedia contributors. "Glossary of Sudoku." Wikipedia, The Free + Encyclopedia, 3 Dec. 2019. Web. 22 Dec. 2019. + """ + + if n < 0: + raise NetworkXError("The order must be greater than or equal to zero.") + + n2 = n * n + n3 = n2 * n + n4 = n3 * n + + # Construct an empty graph with n^4 nodes + G = nx.empty_graph(n4) + + # A Sudoku graph of order 0 or 1 has no edges + if n < 2: + return G + + # Add edges for cells in the same row + for row_no in range(n2): + row_start = row_no * n2 + for j in range(1, n2): + for i in range(j): + G.add_edge(row_start + i, row_start + j) + + # Add edges for cells in the same column + for col_no in range(n2): + for j in range(col_no, n4, n2): + for i in range(col_no, j, n2): + G.add_edge(i, j) + + # Add edges for cells in the same box + for band_no in range(n): + for stack_no in range(n): + box_start = n3 * band_no + n * stack_no + for j in range(1, n2): + for i in range(j): + u = box_start + (i % n) + n2 * (i // n) + v = box_start + (j % n) + n2 * (j // n) + G.add_edge(u, v) + + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/tests/test_cographs.py b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_cographs.py new file mode 100644 index 0000000000000000000000000000000000000000..a71849b019e7fc3f198a240fc137de0cfddaed0d --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_cographs.py @@ -0,0 +1,18 @@ +"""Unit tests for the :mod:`networkx.generators.cographs` module.""" + +import networkx as nx + + +def test_random_cograph(): + n = 3 + G = nx.random_cograph(n) + + assert len(G) == 2**n + + # Every connected subgraph of G has diameter <= 2 + if nx.is_connected(G): + assert nx.diameter(G) <= 2 + else: + components = nx.connected_components(G) + for component in components: + assert nx.diameter(G.subgraph(component)) <= 2 diff --git a/venv/lib/python3.10/site-packages/networkx/generators/tests/test_degree_seq.py b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_degree_seq.py new file mode 100644 index 0000000000000000000000000000000000000000..39ed59a5f32270242b8d069c57229d3e10ba7f43 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_degree_seq.py @@ -0,0 +1,230 @@ +import pytest + +import networkx as nx + + +class TestConfigurationModel: + """Unit tests for the :func:`~networkx.configuration_model` + function. + + """ + + def test_empty_degree_sequence(self): + """Tests that an empty degree sequence yields the null graph.""" + G = nx.configuration_model([]) + assert len(G) == 0 + + def test_degree_zero(self): + """Tests that a degree sequence of all zeros yields the empty + graph. + + """ + G = nx.configuration_model([0, 0, 0]) + assert len(G) == 3 + assert G.number_of_edges() == 0 + + def test_degree_sequence(self): + """Tests that the degree sequence of the generated graph matches + the input degree sequence. + + """ + deg_seq = [5, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1] + G = nx.configuration_model(deg_seq, seed=12345678) + assert sorted((d for n, d in G.degree()), reverse=True) == [ + 5, + 3, + 3, + 3, + 3, + 2, + 2, + 2, + 1, + 1, + 1, + ] + assert sorted((d for n, d in G.degree(range(len(deg_seq)))), reverse=True) == [ + 5, + 3, + 3, + 3, + 3, + 2, + 2, + 2, + 1, + 1, + 1, + ] + + def test_random_seed(self): + """Tests that each call with the same random seed generates the + same graph. + + """ + deg_seq = [3] * 12 + G1 = nx.configuration_model(deg_seq, seed=1000) + G2 = nx.configuration_model(deg_seq, seed=1000) + assert nx.is_isomorphic(G1, G2) + G1 = nx.configuration_model(deg_seq, seed=10) + G2 = nx.configuration_model(deg_seq, seed=10) + assert nx.is_isomorphic(G1, G2) + + def test_directed_disallowed(self): + """Tests that attempting to create a configuration model graph + using a directed graph yields an exception. + + """ + with pytest.raises(nx.NetworkXNotImplemented): + nx.configuration_model([], create_using=nx.DiGraph()) + + def test_odd_degree_sum(self): + """Tests that a degree sequence whose sum is odd yields an + exception. + + """ + with pytest.raises(nx.NetworkXError): + nx.configuration_model([1, 2]) + + +def test_directed_configuration_raise_unequal(): + with pytest.raises(nx.NetworkXError): + zin = [5, 3, 3, 3, 3, 2, 2, 2, 1, 1] + zout = [5, 3, 3, 3, 3, 2, 2, 2, 1, 2] + nx.directed_configuration_model(zin, zout) + + +def test_directed_configuration_model(): + G = nx.directed_configuration_model([], [], seed=0) + assert len(G) == 0 + + +def test_simple_directed_configuration_model(): + G = nx.directed_configuration_model([1, 1], [1, 1], seed=0) + assert len(G) == 2 + + +def test_expected_degree_graph_empty(): + # empty graph has empty degree sequence + deg_seq = [] + G = nx.expected_degree_graph(deg_seq) + assert dict(G.degree()) == {} + + +def test_expected_degree_graph(): + # test that fixed seed delivers the same graph + deg_seq = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] + G1 = nx.expected_degree_graph(deg_seq, seed=1000) + assert len(G1) == 12 + + G2 = nx.expected_degree_graph(deg_seq, seed=1000) + assert nx.is_isomorphic(G1, G2) + + G1 = nx.expected_degree_graph(deg_seq, seed=10) + G2 = nx.expected_degree_graph(deg_seq, seed=10) + assert nx.is_isomorphic(G1, G2) + + +def test_expected_degree_graph_selfloops(): + deg_seq = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] + G1 = nx.expected_degree_graph(deg_seq, seed=1000, selfloops=False) + G2 = nx.expected_degree_graph(deg_seq, seed=1000, selfloops=False) + assert nx.is_isomorphic(G1, G2) + assert len(G1) == 12 + + +def test_expected_degree_graph_skew(): + deg_seq = [10, 2, 2, 2, 2] + G1 = nx.expected_degree_graph(deg_seq, seed=1000) + G2 = nx.expected_degree_graph(deg_seq, seed=1000) + assert nx.is_isomorphic(G1, G2) + assert len(G1) == 5 + + +def test_havel_hakimi_construction(): + G = nx.havel_hakimi_graph([]) + assert len(G) == 0 + + z = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1] + pytest.raises(nx.NetworkXError, nx.havel_hakimi_graph, z) + z = ["A", 3, 3, 3, 3, 2, 2, 2, 1, 1, 1] + pytest.raises(nx.NetworkXError, nx.havel_hakimi_graph, z) + + z = [5, 4, 3, 3, 3, 2, 2, 2] + G = nx.havel_hakimi_graph(z) + G = nx.configuration_model(z) + z = [6, 5, 4, 4, 2, 1, 1, 1] + pytest.raises(nx.NetworkXError, nx.havel_hakimi_graph, z) + + z = [10, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2] + + G = nx.havel_hakimi_graph(z) + + pytest.raises(nx.NetworkXError, nx.havel_hakimi_graph, z, create_using=nx.DiGraph()) + + +def test_directed_havel_hakimi(): + # Test range of valid directed degree sequences + n, r = 100, 10 + p = 1.0 / r + for i in range(r): + G1 = nx.erdos_renyi_graph(n, p * (i + 1), None, True) + din1 = [d for n, d in G1.in_degree()] + dout1 = [d for n, d in G1.out_degree()] + G2 = nx.directed_havel_hakimi_graph(din1, dout1) + din2 = [d for n, d in G2.in_degree()] + dout2 = [d for n, d in G2.out_degree()] + assert sorted(din1) == sorted(din2) + assert sorted(dout1) == sorted(dout2) + + # Test non-graphical sequence + dout = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1] + din = [103, 102, 102, 102, 102, 102, 102, 102, 102, 102] + pytest.raises(nx.exception.NetworkXError, nx.directed_havel_hakimi_graph, din, dout) + # Test valid sequences + dout = [1, 1, 1, 1, 1, 2, 2, 2, 3, 4] + din = [2, 2, 2, 2, 2, 2, 2, 2, 0, 2] + G2 = nx.directed_havel_hakimi_graph(din, dout) + dout2 = (d for n, d in G2.out_degree()) + din2 = (d for n, d in G2.in_degree()) + assert sorted(dout) == sorted(dout2) + assert sorted(din) == sorted(din2) + # Test unequal sums + din = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] + pytest.raises(nx.exception.NetworkXError, nx.directed_havel_hakimi_graph, din, dout) + # Test for negative values + din = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -2] + pytest.raises(nx.exception.NetworkXError, nx.directed_havel_hakimi_graph, din, dout) + + +def test_degree_sequence_tree(): + z = [1, 1, 1, 1, 1, 2, 2, 2, 3, 4] + G = nx.degree_sequence_tree(z) + assert len(G) == len(z) + assert len(list(G.edges())) == sum(z) / 2 + + pytest.raises( + nx.NetworkXError, nx.degree_sequence_tree, z, create_using=nx.DiGraph() + ) + + z = [1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4] + pytest.raises(nx.NetworkXError, nx.degree_sequence_tree, z) + + +def test_random_degree_sequence_graph(): + d = [1, 2, 2, 3] + G = nx.random_degree_sequence_graph(d, seed=42) + assert d == sorted(d for n, d in G.degree()) + + +def test_random_degree_sequence_graph_raise(): + z = [1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4] + pytest.raises(nx.NetworkXUnfeasible, nx.random_degree_sequence_graph, z) + + +def test_random_degree_sequence_large(): + G1 = nx.fast_gnp_random_graph(100, 0.1, seed=42) + d1 = (d for n, d in G1.degree()) + G2 = nx.random_degree_sequence_graph(d1, seed=42) + d2 = (d for n, d in G2.degree()) + assert sorted(d1) == sorted(d2) diff --git a/venv/lib/python3.10/site-packages/networkx/generators/tests/test_joint_degree_seq.py b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_joint_degree_seq.py new file mode 100644 index 0000000000000000000000000000000000000000..1bc0df5cf5f51215c8dd653d0609a5c884595a81 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_joint_degree_seq.py @@ -0,0 +1,125 @@ +import time + +from networkx.algorithms.assortativity import degree_mixing_dict +from networkx.generators import gnm_random_graph, powerlaw_cluster_graph +from networkx.generators.joint_degree_seq import ( + directed_joint_degree_graph, + is_valid_directed_joint_degree, + is_valid_joint_degree, + joint_degree_graph, +) + + +def test_is_valid_joint_degree(): + """Tests for conditions that invalidate a joint degree dict""" + + # valid joint degree that satisfies all five conditions + joint_degrees = { + 1: {4: 1}, + 2: {2: 2, 3: 2, 4: 2}, + 3: {2: 2, 4: 1}, + 4: {1: 1, 2: 2, 3: 1}, + } + assert is_valid_joint_degree(joint_degrees) + + # test condition 1 + # joint_degrees_1[1][4] not integer + joint_degrees_1 = { + 1: {4: 1.5}, + 2: {2: 2, 3: 2, 4: 2}, + 3: {2: 2, 4: 1}, + 4: {1: 1.5, 2: 2, 3: 1}, + } + assert not is_valid_joint_degree(joint_degrees_1) + + # test condition 2 + # degree_count[2] = sum(joint_degrees_2[2][j)/2, is not an int + # degree_count[4] = sum(joint_degrees_2[4][j)/4, is not an int + joint_degrees_2 = { + 1: {4: 1}, + 2: {2: 2, 3: 2, 4: 3}, + 3: {2: 2, 4: 1}, + 4: {1: 1, 2: 3, 3: 1}, + } + assert not is_valid_joint_degree(joint_degrees_2) + + # test conditions 3 and 4 + # joint_degrees_3[1][4]>degree_count[1]*degree_count[4] + joint_degrees_3 = { + 1: {4: 2}, + 2: {2: 2, 3: 2, 4: 2}, + 3: {2: 2, 4: 1}, + 4: {1: 2, 2: 2, 3: 1}, + } + assert not is_valid_joint_degree(joint_degrees_3) + + # test condition 5 + # joint_degrees_5[1][1] not even + joint_degrees_5 = {1: {1: 9}} + assert not is_valid_joint_degree(joint_degrees_5) + + +def test_joint_degree_graph(ntimes=10): + for _ in range(ntimes): + seed = int(time.time()) + + n, m, p = 20, 10, 1 + # generate random graph with model powerlaw_cluster and calculate + # its joint degree + g = powerlaw_cluster_graph(n, m, p, seed=seed) + joint_degrees_g = degree_mixing_dict(g, normalized=False) + + # generate simple undirected graph with given joint degree + # joint_degrees_g + G = joint_degree_graph(joint_degrees_g) + joint_degrees_G = degree_mixing_dict(G, normalized=False) + + # assert that the given joint degree is equal to the generated + # graph's joint degree + assert joint_degrees_g == joint_degrees_G + + +def test_is_valid_directed_joint_degree(): + in_degrees = [0, 1, 1, 2] + out_degrees = [1, 1, 1, 1] + nkk = {1: {1: 2, 2: 2}} + assert is_valid_directed_joint_degree(in_degrees, out_degrees, nkk) + + # not realizable, values are not integers. + nkk = {1: {1: 1.5, 2: 2.5}} + assert not is_valid_directed_joint_degree(in_degrees, out_degrees, nkk) + + # not realizable, number of edges between 1-2 are insufficient. + nkk = {1: {1: 2, 2: 1}} + assert not is_valid_directed_joint_degree(in_degrees, out_degrees, nkk) + + # not realizable, in/out degree sequences have different number of nodes. + out_degrees = [1, 1, 1] + nkk = {1: {1: 2, 2: 2}} + assert not is_valid_directed_joint_degree(in_degrees, out_degrees, nkk) + + # not realizable, degree sequences have fewer than required nodes. + in_degrees = [0, 1, 2] + assert not is_valid_directed_joint_degree(in_degrees, out_degrees, nkk) + + +def test_directed_joint_degree_graph(n=15, m=100, ntimes=1000): + for _ in range(ntimes): + # generate gnm random graph and calculate its joint degree. + g = gnm_random_graph(n, m, None, directed=True) + + # in-degree sequence of g as a list of integers. + in_degrees = list(dict(g.in_degree()).values()) + # out-degree sequence of g as a list of integers. + out_degrees = list(dict(g.out_degree()).values()) + nkk = degree_mixing_dict(g) + + # generate simple directed graph with given degree sequence and joint + # degree matrix. + G = directed_joint_degree_graph(in_degrees, out_degrees, nkk) + + # assert degree sequence correctness. + assert in_degrees == list(dict(G.in_degree()).values()) + assert out_degrees == list(dict(G.out_degree()).values()) + # assert joint degree matrix correctness. + assert nkk == degree_mixing_dict(G) diff --git a/venv/lib/python3.10/site-packages/networkx/generators/tests/test_sudoku.py b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_sudoku.py new file mode 100644 index 0000000000000000000000000000000000000000..7c3560aa81890d0dc308219d7f0983d3950f9fd5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_sudoku.py @@ -0,0 +1,92 @@ +"""Unit tests for the :mod:`networkx.generators.sudoku_graph` module.""" + +import pytest + +import networkx as nx + + +def test_sudoku_negative(): + """Raise an error when generating a Sudoku graph of order -1.""" + pytest.raises(nx.NetworkXError, nx.sudoku_graph, n=-1) + + +@pytest.mark.parametrize("n", [0, 1, 2, 3, 4]) +def test_sudoku_generator(n): + """Generate Sudoku graphs of various sizes and verify their properties.""" + G = nx.sudoku_graph(n) + expected_nodes = n**4 + expected_degree = (n - 1) * (3 * n + 1) + expected_edges = expected_nodes * expected_degree // 2 + assert not G.is_directed() + assert not G.is_multigraph() + assert G.number_of_nodes() == expected_nodes + assert G.number_of_edges() == expected_edges + assert all(d == expected_degree for _, d in G.degree) + + if n == 2: + assert sorted(G.neighbors(6)) == [2, 3, 4, 5, 7, 10, 14] + elif n == 3: + assert sorted(G.neighbors(42)) == [ + 6, + 15, + 24, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 43, + 44, + 51, + 52, + 53, + 60, + 69, + 78, + ] + elif n == 4: + assert sorted(G.neighbors(0)) == [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 32, + 33, + 34, + 35, + 48, + 49, + 50, + 51, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + ] diff --git a/venv/lib/python3.10/site-packages/networkx/generators/tests/test_triads.py b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_triads.py new file mode 100644 index 0000000000000000000000000000000000000000..463844be23a07f71375873bbc71e09c402d51118 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/tests/test_triads.py @@ -0,0 +1,15 @@ +"""Unit tests for the :mod:`networkx.generators.triads` module.""" + +import pytest + +from networkx import triad_graph + + +def test_triad_graph(): + G = triad_graph("030T") + assert [tuple(e) for e in ("ab", "ac", "cb")] == sorted(G.edges()) + + +def test_invalid_name(): + with pytest.raises(ValueError): + triad_graph("bogus") diff --git a/venv/lib/python3.10/site-packages/networkx/generators/time_series.py b/venv/lib/python3.10/site-packages/networkx/generators/time_series.py new file mode 100644 index 0000000000000000000000000000000000000000..592d7734a408bf33e58aad20cb117be674558ad2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/time_series.py @@ -0,0 +1,74 @@ +""" +Time Series Graphs +""" + +import itertools + +import networkx as nx + +__all__ = ["visibility_graph"] + + +@nx._dispatchable(graphs=None, returns_graph=True) +def visibility_graph(series): + """ + Return a Visibility Graph of an input Time Series. + + A visibility graph converts a time series into a graph. The constructed graph + uses integer nodes to indicate which event in the series the node represents. + Edges are formed as follows: consider a bar plot of the series and view that + as a side view of a landscape with a node at the top of each bar. An edge + means that the nodes can be connected by a straight "line-of-sight" without + being obscured by any bars between the nodes. + + The resulting graph inherits several properties of the series in its structure. + Thereby, periodic series convert into regular graphs, random series convert + into random graphs, and fractal series convert into scale-free networks [1]_. + + Parameters + ---------- + series : Sequence[Number] + A Time Series sequence (iterable and sliceable) of numeric values + representing times. + + Returns + ------- + NetworkX Graph + The Visibility Graph of the input series + + Examples + -------- + >>> series_list = [range(10), [2, 1, 3, 2, 1, 3, 2, 1, 3, 2, 1, 3]] + >>> for s in series_list: + ... g = nx.visibility_graph(s) + ... print(g) + Graph with 10 nodes and 9 edges + Graph with 12 nodes and 18 edges + + References + ---------- + .. [1] Lacasa, Lucas, Bartolo Luque, Fernando Ballesteros, Jordi Luque, and Juan Carlos Nuno. + "From time series to complex networks: The visibility graph." Proceedings of the + National Academy of Sciences 105, no. 13 (2008): 4972-4975. + https://www.pnas.org/doi/10.1073/pnas.0709247105 + """ + + # Sequential values are always connected + G = nx.path_graph(len(series)) + nx.set_node_attributes(G, dict(enumerate(series)), "value") + + # Check all combinations of nodes n series + for (n1, t1), (n2, t2) in itertools.combinations(enumerate(series), 2): + # check if any value between obstructs line of sight + slope = (t2 - t1) / (n2 - n1) + offset = t2 - slope * n2 + + obstructed = any( + t >= slope * n + offset + for n, t in enumerate(series[n1 + 1 : n2], start=n1 + 1) + ) + + if not obstructed: + G.add_edge(n1, n2) + + return G diff --git a/venv/lib/python3.10/site-packages/networkx/generators/trees.py b/venv/lib/python3.10/site-packages/networkx/generators/trees.py new file mode 100644 index 0000000000000000000000000000000000000000..30849a8d4884603b766fc7954cc310afe7338fa2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/trees.py @@ -0,0 +1,1071 @@ +"""Functions for generating trees. + +The functions sampling trees at random in this module come +in two variants: labeled and unlabeled. The labeled variants +sample from every possible tree with the given number of nodes +uniformly at random. The unlabeled variants sample from every +possible *isomorphism class* of trees with the given number +of nodes uniformly at random. + +To understand the difference, consider the following example. +There are two isomorphism classes of trees with four nodes. +One is that of the path graph, the other is that of the +star graph. The unlabeled variant will return a line graph or +a star graph with probability 1/2. + +The labeled variant will return the line graph +with probability 3/4 and the star graph with probability 1/4, +because there are more labeled variants of the line graph +than of the star graph. More precisely, the line graph has +an automorphism group of order 2, whereas the star graph has +an automorphism group of order 6, so the line graph has three +times as many labeled variants as the star graph, and thus +three more chances to be drawn. + +Additionally, some functions in this module can sample rooted +trees and forests uniformly at random. A rooted tree is a tree +with a designated root node. A rooted forest is a disjoint union +of rooted trees. +""" + +import warnings +from collections import Counter, defaultdict +from math import comb, factorial + +import networkx as nx +from networkx.utils import py_random_state + +__all__ = [ + "prefix_tree", + "prefix_tree_recursive", + "random_labeled_tree", + "random_labeled_rooted_tree", + "random_labeled_rooted_forest", + "random_unlabeled_tree", + "random_unlabeled_rooted_tree", + "random_unlabeled_rooted_forest", +] + + +@nx._dispatchable(graphs=None, returns_graph=True) +def prefix_tree(paths): + """Creates a directed prefix tree from a list of paths. + + Usually the paths are described as strings or lists of integers. + + A "prefix tree" represents the prefix structure of the strings. + Each node represents a prefix of some string. The root represents + the empty prefix with children for the single letter prefixes which + in turn have children for each double letter prefix starting with + the single letter corresponding to the parent node, and so on. + + More generally the prefixes do not need to be strings. A prefix refers + to the start of a sequence. The root has children for each one element + prefix and they have children for each two element prefix that starts + with the one element sequence of the parent, and so on. + + Note that this implementation uses integer nodes with an attribute. + Each node has an attribute "source" whose value is the original element + of the path to which this node corresponds. For example, suppose `paths` + consists of one path: "can". Then the nodes `[1, 2, 3]` which represent + this path have "source" values "c", "a" and "n". + + All the descendants of a node have a common prefix in the sequence/path + associated with that node. From the returned tree, the prefix for each + node can be constructed by traversing the tree up to the root and + accumulating the "source" values along the way. + + The root node is always `0` and has "source" attribute `None`. + The root is the only node with in-degree zero. + The nil node is always `-1` and has "source" attribute `"NIL"`. + The nil node is the only node with out-degree zero. + + + Parameters + ---------- + paths: iterable of paths + An iterable of paths which are themselves sequences. + Matching prefixes among these sequences are identified with + nodes of the prefix tree. One leaf of the tree is associated + with each path. (Identical paths are associated with the same + leaf of the tree.) + + + Returns + ------- + tree: DiGraph + A directed graph representing an arborescence consisting of the + prefix tree generated by `paths`. Nodes are directed "downward", + from parent to child. A special "synthetic" root node is added + to be the parent of the first node in each path. A special + "synthetic" leaf node, the "nil" node `-1`, is added to be the child + of all nodes representing the last element in a path. (The + addition of this nil node technically makes this not an + arborescence but a directed acyclic graph; removing the nil node + makes it an arborescence.) + + + Notes + ----- + The prefix tree is also known as a *trie*. + + + Examples + -------- + Create a prefix tree from a list of strings with common prefixes:: + + >>> paths = ["ab", "abs", "ad"] + >>> T = nx.prefix_tree(paths) + >>> list(T.edges) + [(0, 1), (1, 2), (1, 4), (2, -1), (2, 3), (3, -1), (4, -1)] + + The leaf nodes can be obtained as predecessors of the nil node:: + + >>> root, NIL = 0, -1 + >>> list(T.predecessors(NIL)) + [2, 3, 4] + + To recover the original paths that generated the prefix tree, + traverse up the tree from the node `-1` to the node `0`:: + + >>> recovered = [] + >>> for v in T.predecessors(NIL): + ... prefix = "" + ... while v != root: + ... prefix = str(T.nodes[v]["source"]) + prefix + ... v = next(T.predecessors(v)) # only one predecessor + ... recovered.append(prefix) + >>> sorted(recovered) + ['ab', 'abs', 'ad'] + """ + + def get_children(parent, paths): + children = defaultdict(list) + # Populate dictionary with key(s) as the child/children of the root and + # value(s) as the remaining paths of the corresponding child/children + for path in paths: + # If path is empty, we add an edge to the NIL node. + if not path: + tree.add_edge(parent, NIL) + continue + child, *rest = path + # `child` may exist as the head of more than one path in `paths`. + children[child].append(rest) + return children + + # Initialize the prefix tree with a root node and a nil node. + tree = nx.DiGraph() + root = 0 + tree.add_node(root, source=None) + NIL = -1 + tree.add_node(NIL, source="NIL") + children = get_children(root, paths) + stack = [(root, iter(children.items()))] + while stack: + parent, remaining_children = stack[-1] + try: + child, remaining_paths = next(remaining_children) + # Pop item off stack if there are no remaining children + except StopIteration: + stack.pop() + continue + # We relabel each child with an unused name. + new_name = len(tree) - 1 + # The "source" node attribute stores the original node name. + tree.add_node(new_name, source=child) + tree.add_edge(parent, new_name) + children = get_children(new_name, remaining_paths) + stack.append((new_name, iter(children.items()))) + + return tree + + +@nx._dispatchable(graphs=None, returns_graph=True) +def prefix_tree_recursive(paths): + """Recursively creates a directed prefix tree from a list of paths. + + The original recursive version of prefix_tree for comparison. It is + the same algorithm but the recursion is unrolled onto a stack. + + Usually the paths are described as strings or lists of integers. + + A "prefix tree" represents the prefix structure of the strings. + Each node represents a prefix of some string. The root represents + the empty prefix with children for the single letter prefixes which + in turn have children for each double letter prefix starting with + the single letter corresponding to the parent node, and so on. + + More generally the prefixes do not need to be strings. A prefix refers + to the start of a sequence. The root has children for each one element + prefix and they have children for each two element prefix that starts + with the one element sequence of the parent, and so on. + + Note that this implementation uses integer nodes with an attribute. + Each node has an attribute "source" whose value is the original element + of the path to which this node corresponds. For example, suppose `paths` + consists of one path: "can". Then the nodes `[1, 2, 3]` which represent + this path have "source" values "c", "a" and "n". + + All the descendants of a node have a common prefix in the sequence/path + associated with that node. From the returned tree, ehe prefix for each + node can be constructed by traversing the tree up to the root and + accumulating the "source" values along the way. + + The root node is always `0` and has "source" attribute `None`. + The root is the only node with in-degree zero. + The nil node is always `-1` and has "source" attribute `"NIL"`. + The nil node is the only node with out-degree zero. + + + Parameters + ---------- + paths: iterable of paths + An iterable of paths which are themselves sequences. + Matching prefixes among these sequences are identified with + nodes of the prefix tree. One leaf of the tree is associated + with each path. (Identical paths are associated with the same + leaf of the tree.) + + + Returns + ------- + tree: DiGraph + A directed graph representing an arborescence consisting of the + prefix tree generated by `paths`. Nodes are directed "downward", + from parent to child. A special "synthetic" root node is added + to be the parent of the first node in each path. A special + "synthetic" leaf node, the "nil" node `-1`, is added to be the child + of all nodes representing the last element in a path. (The + addition of this nil node technically makes this not an + arborescence but a directed acyclic graph; removing the nil node + makes it an arborescence.) + + + Notes + ----- + The prefix tree is also known as a *trie*. + + + Examples + -------- + Create a prefix tree from a list of strings with common prefixes:: + + >>> paths = ["ab", "abs", "ad"] + >>> T = nx.prefix_tree(paths) + >>> list(T.edges) + [(0, 1), (1, 2), (1, 4), (2, -1), (2, 3), (3, -1), (4, -1)] + + The leaf nodes can be obtained as predecessors of the nil node. + + >>> root, NIL = 0, -1 + >>> list(T.predecessors(NIL)) + [2, 3, 4] + + To recover the original paths that generated the prefix tree, + traverse up the tree from the node `-1` to the node `0`:: + + >>> recovered = [] + >>> for v in T.predecessors(NIL): + ... prefix = "" + ... while v != root: + ... prefix = str(T.nodes[v]["source"]) + prefix + ... v = next(T.predecessors(v)) # only one predecessor + ... recovered.append(prefix) + >>> sorted(recovered) + ['ab', 'abs', 'ad'] + """ + + def _helper(paths, root, tree): + """Recursively create a trie from the given list of paths. + + `paths` is a list of paths, each of which is itself a list of + nodes, relative to the given `root` (but not including it). This + list of paths will be interpreted as a tree-like structure, in + which two paths that share a prefix represent two branches of + the tree with the same initial segment. + + `root` is the parent of the node at index 0 in each path. + + `tree` is the "accumulator", the :class:`networkx.DiGraph` + representing the branching to which the new nodes and edges will + be added. + + """ + # For each path, remove the first node and make it a child of root. + # Any remaining paths then get processed recursively. + children = defaultdict(list) + for path in paths: + # If path is empty, we add an edge to the NIL node. + if not path: + tree.add_edge(root, NIL) + continue + child, *rest = path + # `child` may exist as the head of more than one path in `paths`. + children[child].append(rest) + # Add a node for each child, connect root, recurse to remaining paths + for child, remaining_paths in children.items(): + # We relabel each child with an unused name. + new_name = len(tree) - 1 + # The "source" node attribute stores the original node name. + tree.add_node(new_name, source=child) + tree.add_edge(root, new_name) + _helper(remaining_paths, new_name, tree) + + # Initialize the prefix tree with a root node and a nil node. + tree = nx.DiGraph() + root = 0 + tree.add_node(root, source=None) + NIL = -1 + tree.add_node(NIL, source="NIL") + # Populate the tree. + _helper(paths, root, tree) + return tree + + +@py_random_state("seed") +@nx._dispatchable(graphs=None, returns_graph=True) +def random_labeled_tree(n, *, seed=None): + """Returns a labeled tree on `n` nodes chosen uniformly at random. + + Generating uniformly distributed random Prüfer sequences and + converting them into the corresponding trees is a straightforward + method of generating uniformly distributed random labeled trees. + This function implements this method. + + Parameters + ---------- + n : int + The number of nodes, greater than zero. + seed : random_state + Indicator of random number generation state. + See :ref:`Randomness` + + Returns + ------- + :class:`networkx.Graph` + A `networkx.Graph` with nodes in the set {0, …, *n* - 1}. + + Raises + ------ + NetworkXPointlessConcept + If `n` is zero (because the null graph is not a tree). + + Examples + -------- + >>> G = nx.random_labeled_tree(5, seed=42) + >>> nx.is_tree(G) + True + >>> G.edges + EdgeView([(0, 1), (0, 3), (0, 2), (2, 4)]) + + A tree with *arbitrarily directed* edges can be created by assigning + generated edges to a ``DiGraph``: + + >>> DG = nx.DiGraph() + >>> DG.add_edges_from(G.edges) + >>> nx.is_tree(DG) + True + >>> DG.edges + OutEdgeView([(0, 1), (0, 3), (0, 2), (2, 4)]) + """ + # Cannot create a Prüfer sequence unless `n` is at least two. + if n == 0: + raise nx.NetworkXPointlessConcept("the null graph is not a tree") + if n == 1: + return nx.empty_graph(1) + return nx.from_prufer_sequence([seed.choice(range(n)) for i in range(n - 2)]) + + +@py_random_state("seed") +@nx._dispatchable(graphs=None, returns_graph=True) +def random_labeled_rooted_tree(n, *, seed=None): + """Returns a labeled rooted tree with `n` nodes. + + The returned tree is chosen uniformly at random from all labeled rooted trees. + + Parameters + ---------- + n : int + The number of nodes + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + :class:`networkx.Graph` + A `networkx.Graph` with integer nodes 0 <= node <= `n` - 1. + The root of the tree is selected uniformly from the nodes. + The "root" graph attribute identifies the root of the tree. + + Notes + ----- + This function returns the result of :func:`random_labeled_tree` + with a randomly selected root. + + Raises + ------ + NetworkXPointlessConcept + If `n` is zero (because the null graph is not a tree). + """ + t = random_labeled_tree(n, seed=seed) + t.graph["root"] = seed.randint(0, n - 1) + return t + + +@py_random_state("seed") +@nx._dispatchable(graphs=None, returns_graph=True) +def random_labeled_rooted_forest(n, *, seed=None): + """Returns a labeled rooted forest with `n` nodes. + + The returned forest is chosen uniformly at random using a + generalization of Prüfer sequences [1]_ in the form described in [2]_. + + Parameters + ---------- + n : int + The number of nodes. + seed : random_state + See :ref:`Randomness`. + + Returns + ------- + :class:`networkx.Graph` + A `networkx.Graph` with integer nodes 0 <= node <= `n` - 1. + The "roots" graph attribute is a set of integers containing the roots. + + References + ---------- + .. [1] Knuth, Donald E. "Another Enumeration of Trees." + Canadian Journal of Mathematics, 20 (1968): 1077-1086. + https://doi.org/10.4153/CJM-1968-104-8 + .. [2] Rubey, Martin. "Counting Spanning Trees". Diplomarbeit + zur Erlangung des akademischen Grades Magister der + Naturwissenschaften an der Formal- und Naturwissenschaftlichen + Fakultät der Universität Wien. Wien, May 2000. + """ + + # Select the number of roots by iterating over the cumulative count of trees + # with at most k roots + def _select_k(n, seed): + r = seed.randint(0, (n + 1) ** (n - 1) - 1) + cum_sum = 0 + for k in range(1, n): + cum_sum += (factorial(n - 1) * n ** (n - k)) // ( + factorial(k - 1) * factorial(n - k) + ) + if r < cum_sum: + return k + + return n + + F = nx.empty_graph(n) + if n == 0: + F.graph["roots"] = {} + return F + # Select the number of roots k + k = _select_k(n, seed) + if k == n: + F.graph["roots"] = set(range(n)) + return F # Nothing to do + # Select the roots + roots = seed.sample(range(n), k) + # Nonroots + p = set(range(n)).difference(roots) + # Coding sequence + N = [seed.randint(0, n - 1) for i in range(n - k - 1)] + # Multiset of elements in N also in p + degree = Counter([x for x in N if x in p]) + # Iterator over the elements of p with degree zero + iterator = iter(x for x in p if degree[x] == 0) + u = last = next(iterator) + # This loop is identical to that for Prüfer sequences, + # except that we can draw nodes only from p + for v in N: + F.add_edge(u, v) + degree[v] -= 1 + if v < last and degree[v] == 0: + u = v + else: + last = u = next(iterator) + + F.add_edge(u, roots[0]) + F.graph["roots"] = set(roots) + return F + + +# The following functions support generation of unlabeled trees and forests. + + +def _to_nx(edges, n_nodes, root=None, roots=None): + """ + Converts the (edges, n_nodes) input to a :class:`networkx.Graph`. + The (edges, n_nodes) input is a list of even length, where each pair + of consecutive integers represents an edge, and an integer `n_nodes`. + Integers in the list are elements of `range(n_nodes)`. + + Parameters + ---------- + edges : list of ints + The flattened list of edges of the graph. + n_nodes : int + The number of nodes of the graph. + root: int (default=None) + If not None, the "root" attribute of the graph will be set to this value. + roots: collection of ints (default=None) + If not None, he "roots" attribute of the graph will be set to this value. + + Returns + ------- + :class:`networkx.Graph` + The graph with `n_nodes` nodes and edges given by `edges`. + """ + G = nx.empty_graph(n_nodes) + G.add_edges_from(edges) + if root is not None: + G.graph["root"] = root + if roots is not None: + G.graph["roots"] = roots + return G + + +def _num_rooted_trees(n, cache_trees): + """Returns the number of unlabeled rooted trees with `n` nodes. + + See also https://oeis.org/A000081. + + Parameters + ---------- + n : int + The number of nodes + cache_trees : list of ints + The $i$-th element is the number of unlabeled rooted trees with $i$ nodes, + which is used as a cache (and is extended to length $n+1$ if needed) + + Returns + ------- + int + The number of unlabeled rooted trees with `n` nodes. + """ + for n_i in range(len(cache_trees), n + 1): + cache_trees.append( + sum( + [ + d * cache_trees[n_i - j * d] * cache_trees[d] + for d in range(1, n_i) + for j in range(1, (n_i - 1) // d + 1) + ] + ) + // (n_i - 1) + ) + return cache_trees[n] + + +def _select_jd_trees(n, cache_trees, seed): + """Returns a pair $(j,d)$ with a specific probability + + Given $n$, returns a pair of positive integers $(j,d)$ with the probability + specified in formula (5) of Chapter 29 of [1]_. + + Parameters + ---------- + n : int + The number of nodes + cache_trees : list of ints + Cache for :func:`_num_rooted_trees`. + seed : random_state + See :ref:`Randomness`. + + Returns + ------- + (int, int) + A pair of positive integers $(j,d)$ satisfying formula (5) of + Chapter 29 of [1]_. + + References + ---------- + .. [1] Nijenhuis, Albert, and Wilf, Herbert S. + "Combinatorial algorithms: for computers and calculators." + Academic Press, 1978. + https://doi.org/10.1016/C2013-0-11243-3 + """ + p = seed.randint(0, _num_rooted_trees(n, cache_trees) * (n - 1) - 1) + cumsum = 0 + for d in range(n - 1, 0, -1): + for j in range(1, (n - 1) // d + 1): + cumsum += ( + d + * _num_rooted_trees(n - j * d, cache_trees) + * _num_rooted_trees(d, cache_trees) + ) + if p < cumsum: + return (j, d) + + +def _random_unlabeled_rooted_tree(n, cache_trees, seed): + """Returns an unlabeled rooted tree with `n` nodes. + + Returns an unlabeled rooted tree with `n` nodes chosen uniformly + at random using the "RANRUT" algorithm from [1]_. + The tree is returned in the form: (list_of_edges, number_of_nodes) + + Parameters + ---------- + n : int + The number of nodes, greater than zero. + cache_trees : list ints + Cache for :func:`_num_rooted_trees`. + seed : random_state + See :ref:`Randomness`. + + Returns + ------- + (list_of_edges, number_of_nodes) : list, int + A random unlabeled rooted tree with `n` nodes as a 2-tuple + ``(list_of_edges, number_of_nodes)``. + The root is node 0. + + References + ---------- + .. [1] Nijenhuis, Albert, and Wilf, Herbert S. + "Combinatorial algorithms: for computers and calculators." + Academic Press, 1978. + https://doi.org/10.1016/C2013-0-11243-3 + """ + if n == 1: + edges, n_nodes = [], 1 + return edges, n_nodes + if n == 2: + edges, n_nodes = [(0, 1)], 2 + return edges, n_nodes + + j, d = _select_jd_trees(n, cache_trees, seed) + t1, t1_nodes = _random_unlabeled_rooted_tree(n - j * d, cache_trees, seed) + t2, t2_nodes = _random_unlabeled_rooted_tree(d, cache_trees, seed) + t12 = [(0, t2_nodes * i + t1_nodes) for i in range(j)] + t1.extend(t12) + for _ in range(j): + t1.extend((n1 + t1_nodes, n2 + t1_nodes) for n1, n2 in t2) + t1_nodes += t2_nodes + + return t1, t1_nodes + + +@py_random_state("seed") +@nx._dispatchable(graphs=None, returns_graph=True) +def random_unlabeled_rooted_tree(n, *, number_of_trees=None, seed=None): + """Returns a number of unlabeled rooted trees uniformly at random + + Returns one or more (depending on `number_of_trees`) + unlabeled rooted trees with `n` nodes drawn uniformly + at random. + + Parameters + ---------- + n : int + The number of nodes + number_of_trees : int or None (default) + If not None, this number of trees is generated and returned. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + :class:`networkx.Graph` or list of :class:`networkx.Graph` + A single `networkx.Graph` (or a list thereof, if `number_of_trees` + is specified) with nodes in the set {0, …, *n* - 1}. + The "root" graph attribute identifies the root of the tree. + + Notes + ----- + The trees are generated using the "RANRUT" algorithm from [1]_. + The algorithm needs to compute some counting functions + that are relatively expensive: in case several trees are needed, + it is advisable to use the `number_of_trees` optional argument + to reuse the counting functions. + + Raises + ------ + NetworkXPointlessConcept + If `n` is zero (because the null graph is not a tree). + + References + ---------- + .. [1] Nijenhuis, Albert, and Wilf, Herbert S. + "Combinatorial algorithms: for computers and calculators." + Academic Press, 1978. + https://doi.org/10.1016/C2013-0-11243-3 + """ + if n == 0: + raise nx.NetworkXPointlessConcept("the null graph is not a tree") + cache_trees = [0, 1] # initial cache of number of rooted trees + if number_of_trees is None: + return _to_nx(*_random_unlabeled_rooted_tree(n, cache_trees, seed), root=0) + return [ + _to_nx(*_random_unlabeled_rooted_tree(n, cache_trees, seed), root=0) + for i in range(number_of_trees) + ] + + +def _num_rooted_forests(n, q, cache_forests): + """Returns the number of unlabeled rooted forests with `n` nodes, and with + no more than `q` nodes per tree. A recursive formula for this is (2) in + [1]_. This function is implemented using dynamic programming instead of + recursion. + + Parameters + ---------- + n : int + The number of nodes. + q : int + The maximum number of nodes for each tree of the forest. + cache_forests : list of ints + The $i$-th element is the number of unlabeled rooted forests with + $i$ nodes, and with no more than `q` nodes per tree; this is used + as a cache (and is extended to length `n` + 1 if needed). + + Returns + ------- + int + The number of unlabeled rooted forests with `n` nodes with no more than + `q` nodes per tree. + + References + ---------- + .. [1] Wilf, Herbert S. "The uniform selection of free trees." + Journal of Algorithms 2.2 (1981): 204-207. + https://doi.org/10.1016/0196-6774(81)90021-3 + """ + for n_i in range(len(cache_forests), n + 1): + q_i = min(n_i, q) + cache_forests.append( + sum( + [ + d * cache_forests[n_i - j * d] * cache_forests[d - 1] + for d in range(1, q_i + 1) + for j in range(1, n_i // d + 1) + ] + ) + // n_i + ) + + return cache_forests[n] + + +def _select_jd_forests(n, q, cache_forests, seed): + """Given `n` and `q`, returns a pair of positive integers $(j,d)$ + such that $j\\leq d$, with probability satisfying (F1) of [1]_. + + Parameters + ---------- + n : int + The number of nodes. + q : int + The maximum number of nodes for each tree of the forest. + cache_forests : list of ints + Cache for :func:`_num_rooted_forests`. + seed : random_state + See :ref:`Randomness`. + + Returns + ------- + (int, int) + A pair of positive integers $(j,d)$ + + References + ---------- + .. [1] Wilf, Herbert S. "The uniform selection of free trees." + Journal of Algorithms 2.2 (1981): 204-207. + https://doi.org/10.1016/0196-6774(81)90021-3 + """ + p = seed.randint(0, _num_rooted_forests(n, q, cache_forests) * n - 1) + cumsum = 0 + for d in range(q, 0, -1): + for j in range(1, n // d + 1): + cumsum += ( + d + * _num_rooted_forests(n - j * d, q, cache_forests) + * _num_rooted_forests(d - 1, q, cache_forests) + ) + if p < cumsum: + return (j, d) + + +def _random_unlabeled_rooted_forest(n, q, cache_trees, cache_forests, seed): + """Returns an unlabeled rooted forest with `n` nodes, and with no more + than `q` nodes per tree, drawn uniformly at random. It is an implementation + of the algorithm "Forest" of [1]_. + + Parameters + ---------- + n : int + The number of nodes. + q : int + The maximum number of nodes per tree. + cache_trees : + Cache for :func:`_num_rooted_trees`. + cache_forests : + Cache for :func:`_num_rooted_forests`. + seed : random_state + See :ref:`Randomness`. + + Returns + ------- + (edges, n, r) : (list, int, list) + The forest (edges, n) and a list r of root nodes. + + References + ---------- + .. [1] Wilf, Herbert S. "The uniform selection of free trees." + Journal of Algorithms 2.2 (1981): 204-207. + https://doi.org/10.1016/0196-6774(81)90021-3 + """ + if n == 0: + return ([], 0, []) + + j, d = _select_jd_forests(n, q, cache_forests, seed) + t1, t1_nodes, r1 = _random_unlabeled_rooted_forest( + n - j * d, q, cache_trees, cache_forests, seed + ) + t2, t2_nodes = _random_unlabeled_rooted_tree(d, cache_trees, seed) + for _ in range(j): + r1.append(t1_nodes) + t1.extend((n1 + t1_nodes, n2 + t1_nodes) for n1, n2 in t2) + t1_nodes += t2_nodes + return t1, t1_nodes, r1 + + +@py_random_state("seed") +@nx._dispatchable(graphs=None, returns_graph=True) +def random_unlabeled_rooted_forest(n, *, q=None, number_of_forests=None, seed=None): + """Returns a forest or list of forests selected at random. + + Returns one or more (depending on `number_of_forests`) + unlabeled rooted forests with `n` nodes, and with no more than + `q` nodes per tree, drawn uniformly at random. + The "roots" graph attribute identifies the roots of the forest. + + Parameters + ---------- + n : int + The number of nodes + q : int or None (default) + The maximum number of nodes per tree. + number_of_forests : int or None (default) + If not None, this number of forests is generated and returned. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + :class:`networkx.Graph` or list of :class:`networkx.Graph` + A single `networkx.Graph` (or a list thereof, if `number_of_forests` + is specified) with nodes in the set {0, …, *n* - 1}. + The "roots" graph attribute is a set containing the roots + of the trees in the forest. + + Notes + ----- + This function implements the algorithm "Forest" of [1]_. + The algorithm needs to compute some counting functions + that are relatively expensive: in case several trees are needed, + it is advisable to use the `number_of_forests` optional argument + to reuse the counting functions. + + Raises + ------ + ValueError + If `n` is non-zero but `q` is zero. + + References + ---------- + .. [1] Wilf, Herbert S. "The uniform selection of free trees." + Journal of Algorithms 2.2 (1981): 204-207. + https://doi.org/10.1016/0196-6774(81)90021-3 + """ + if q is None: + q = n + if q == 0 and n != 0: + raise ValueError("q must be a positive integer if n is positive.") + + cache_trees = [0, 1] # initial cache of number of rooted trees + cache_forests = [1] # initial cache of number of rooted forests + + if number_of_forests is None: + g, nodes, rs = _random_unlabeled_rooted_forest( + n, q, cache_trees, cache_forests, seed + ) + return _to_nx(g, nodes, roots=set(rs)) + + res = [] + for i in range(number_of_forests): + g, nodes, rs = _random_unlabeled_rooted_forest( + n, q, cache_trees, cache_forests, seed + ) + res.append(_to_nx(g, nodes, roots=set(rs))) + return res + + +def _num_trees(n, cache_trees): + """Returns the number of unlabeled trees with `n` nodes. + + See also https://oeis.org/A000055. + + Parameters + ---------- + n : int + The number of nodes. + cache_trees : list of ints + Cache for :func:`_num_rooted_trees`. + + Returns + ------- + int + The number of unlabeled trees with `n` nodes. + """ + r = _num_rooted_trees(n, cache_trees) - sum( + [ + _num_rooted_trees(j, cache_trees) * _num_rooted_trees(n - j, cache_trees) + for j in range(1, n // 2 + 1) + ] + ) + if n % 2 == 0: + r += comb(_num_rooted_trees(n // 2, cache_trees) + 1, 2) + return r + + +def _bicenter(n, cache, seed): + """Returns a bi-centroidal tree on `n` nodes drawn uniformly at random. + + This function implements the algorithm Bicenter of [1]_. + + Parameters + ---------- + n : int + The number of nodes (must be even). + cache : list of ints. + Cache for :func:`_num_rooted_trees`. + seed : random_state + See :ref:`Randomness` + + Returns + ------- + (edges, n) + The tree as a list of edges and number of nodes. + + References + ---------- + .. [1] Wilf, Herbert S. "The uniform selection of free trees." + Journal of Algorithms 2.2 (1981): 204-207. + https://doi.org/10.1016/0196-6774(81)90021-3 + """ + t, t_nodes = _random_unlabeled_rooted_tree(n // 2, cache, seed) + if seed.randint(0, _num_rooted_trees(n // 2, cache)) == 0: + t2, t2_nodes = t, t_nodes + else: + t2, t2_nodes = _random_unlabeled_rooted_tree(n // 2, cache, seed) + t.extend([(n1 + (n // 2), n2 + (n // 2)) for n1, n2 in t2]) + t.append((0, n // 2)) + return t, t_nodes + t2_nodes + + +def _random_unlabeled_tree(n, cache_trees, cache_forests, seed): + """Returns a tree on `n` nodes drawn uniformly at random. + It implements the Wilf's algorithm "Free" of [1]_. + + Parameters + ---------- + n : int + The number of nodes, greater than zero. + cache_trees : list of ints + Cache for :func:`_num_rooted_trees`. + cache_forests : list of ints + Cache for :func:`_num_rooted_forests`. + seed : random_state + Indicator of random number generation state. + See :ref:`Randomness` + + Returns + ------- + (edges, n) + The tree as a list of edges and number of nodes. + + References + ---------- + .. [1] Wilf, Herbert S. "The uniform selection of free trees." + Journal of Algorithms 2.2 (1981): 204-207. + https://doi.org/10.1016/0196-6774(81)90021-3 + """ + if n % 2 == 1: + p = 0 + else: + p = comb(_num_rooted_trees(n // 2, cache_trees) + 1, 2) + if seed.randint(0, _num_trees(n, cache_trees) - 1) < p: + return _bicenter(n, cache_trees, seed) + else: + f, n_f, r = _random_unlabeled_rooted_forest( + n - 1, (n - 1) // 2, cache_trees, cache_forests, seed + ) + for i in r: + f.append((i, n_f)) + return f, n_f + 1 + + +@py_random_state("seed") +@nx._dispatchable(graphs=None, returns_graph=True) +def random_unlabeled_tree(n, *, number_of_trees=None, seed=None): + """Returns a tree or list of trees chosen randomly. + + Returns one or more (depending on `number_of_trees`) + unlabeled trees with `n` nodes drawn uniformly at random. + + Parameters + ---------- + n : int + The number of nodes + number_of_trees : int or None (default) + If not None, this number of trees is generated and returned. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + :class:`networkx.Graph` or list of :class:`networkx.Graph` + A single `networkx.Graph` (or a list thereof, if + `number_of_trees` is specified) with nodes in the set {0, …, *n* - 1}. + + Raises + ------ + NetworkXPointlessConcept + If `n` is zero (because the null graph is not a tree). + + Notes + ----- + This function generates an unlabeled tree uniformly at random using + Wilf's algorithm "Free" of [1]_. The algorithm needs to + compute some counting functions that are relatively expensive: + in case several trees are needed, it is advisable to use the + `number_of_trees` optional argument to reuse the counting + functions. + + References + ---------- + .. [1] Wilf, Herbert S. "The uniform selection of free trees." + Journal of Algorithms 2.2 (1981): 204-207. + https://doi.org/10.1016/0196-6774(81)90021-3 + """ + if n == 0: + raise nx.NetworkXPointlessConcept("the null graph is not a tree") + + cache_trees = [0, 1] # initial cache of number of rooted trees + cache_forests = [1] # initial cache of number of rooted forests + if number_of_trees is None: + return _to_nx(*_random_unlabeled_tree(n, cache_trees, cache_forests, seed)) + else: + return [ + _to_nx(*_random_unlabeled_tree(n, cache_trees, cache_forests, seed)) + for i in range(number_of_trees) + ] diff --git a/venv/lib/python3.10/site-packages/networkx/generators/triads.py b/venv/lib/python3.10/site-packages/networkx/generators/triads.py new file mode 100644 index 0000000000000000000000000000000000000000..09b722dd1bd49dddae16086115d170ec989f8b06 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/generators/triads.py @@ -0,0 +1,94 @@ +# See https://github.com/networkx/networkx/pull/1474 +# Copyright 2011 Reya Group +# Copyright 2011 Alex Levenson +# Copyright 2011 Diederik van Liere +"""Functions that generate the triad graphs, that is, the possible +digraphs on three nodes. + +""" + +import networkx as nx +from networkx.classes import DiGraph + +__all__ = ["triad_graph"] + +#: Dictionary mapping triad name to list of directed edges in the +#: digraph representation of that triad (with nodes 'a', 'b', and 'c'). +TRIAD_EDGES = { + "003": [], + "012": ["ab"], + "102": ["ab", "ba"], + "021D": ["ba", "bc"], + "021U": ["ab", "cb"], + "021C": ["ab", "bc"], + "111D": ["ac", "ca", "bc"], + "111U": ["ac", "ca", "cb"], + "030T": ["ab", "cb", "ac"], + "030C": ["ba", "cb", "ac"], + "201": ["ab", "ba", "ac", "ca"], + "120D": ["bc", "ba", "ac", "ca"], + "120U": ["ab", "cb", "ac", "ca"], + "120C": ["ab", "bc", "ac", "ca"], + "210": ["ab", "bc", "cb", "ac", "ca"], + "300": ["ab", "ba", "bc", "cb", "ac", "ca"], +} + + +@nx._dispatchable(graphs=None, returns_graph=True) +def triad_graph(triad_name): + """Returns the triad graph with the given name. + + Each string in the following tuple is a valid triad name:: + + ( + "003", + "012", + "102", + "021D", + "021U", + "021C", + "111D", + "111U", + "030T", + "030C", + "201", + "120D", + "120U", + "120C", + "210", + "300", + ) + + Each triad name corresponds to one of the possible valid digraph on + three nodes. + + Parameters + ---------- + triad_name : string + The name of a triad, as described above. + + Returns + ------- + :class:`~networkx.DiGraph` + The digraph on three nodes with the given name. The nodes of the + graph are the single-character strings 'a', 'b', and 'c'. + + Raises + ------ + ValueError + If `triad_name` is not the name of a triad. + + See also + -------- + triadic_census + + """ + if triad_name not in TRIAD_EDGES: + raise ValueError( + f'unknown triad name "{triad_name}"; use one of the triad names' + " in the TRIAD_NAMES constant" + ) + G = DiGraph() + G.add_nodes_from("abc") + G.add_edges_from(TRIAD_EDGES[triad_name]) + return G diff --git a/venv/lib/python3.10/site-packages/networkx/lazy_imports.py b/venv/lib/python3.10/site-packages/networkx/lazy_imports.py new file mode 100644 index 0000000000000000000000000000000000000000..396404ba38f5885bfcc65af36d7b4655e94ccc27 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/lazy_imports.py @@ -0,0 +1,188 @@ +import importlib +import importlib.util +import inspect +import os +import sys +import types + +__all__ = ["attach", "_lazy_import"] + + +def attach(module_name, submodules=None, submod_attrs=None): + """Attach lazily loaded submodules, and functions or other attributes. + + Typically, modules import submodules and attributes as follows:: + + import mysubmodule + import anothersubmodule + + from .foo import someattr + + The idea of this function is to replace the `__init__.py` + module's `__getattr__`, `__dir__`, and `__all__` attributes such that + all imports work exactly the way they normally would, except that the + actual import is delayed until the resulting module object is first used. + + The typical way to call this function, replacing the above imports, is:: + + __getattr__, __lazy_dir__, __all__ = lazy.attach( + __name__, ["mysubmodule", "anothersubmodule"], {"foo": "someattr"} + ) + + This functionality requires Python 3.7 or higher. + + Parameters + ---------- + module_name : str + Typically use __name__. + submodules : set + List of submodules to lazily import. + submod_attrs : dict + Dictionary of submodule -> list of attributes / functions. + These attributes are imported as they are used. + + Returns + ------- + __getattr__, __dir__, __all__ + + """ + if submod_attrs is None: + submod_attrs = {} + + if submodules is None: + submodules = set() + else: + submodules = set(submodules) + + attr_to_modules = { + attr: mod for mod, attrs in submod_attrs.items() for attr in attrs + } + + __all__ = list(submodules | attr_to_modules.keys()) + + def __getattr__(name): + if name in submodules: + return importlib.import_module(f"{module_name}.{name}") + elif name in attr_to_modules: + submod = importlib.import_module(f"{module_name}.{attr_to_modules[name]}") + return getattr(submod, name) + else: + raise AttributeError(f"No {module_name} attribute {name}") + + def __dir__(): + return __all__ + + if os.environ.get("EAGER_IMPORT", ""): + for attr in set(attr_to_modules.keys()) | submodules: + __getattr__(attr) + + return __getattr__, __dir__, list(__all__) + + +class DelayedImportErrorModule(types.ModuleType): + def __init__(self, frame_data, *args, **kwargs): + self.__frame_data = frame_data + super().__init__(*args, **kwargs) + + def __getattr__(self, x): + if x in ("__class__", "__file__", "__frame_data"): + super().__getattr__(x) + else: + fd = self.__frame_data + raise ModuleNotFoundError( + f"No module named '{fd['spec']}'\n\n" + "This error is lazily reported, having originally occurred in\n" + f' File {fd["filename"]}, line {fd["lineno"]}, in {fd["function"]}\n\n' + f'----> {"".join(fd["code_context"] or "").strip()}' + ) + + +def _lazy_import(fullname): + """Return a lazily imported proxy for a module or library. + + Warning + ------- + Importing using this function can currently cause trouble + when the user tries to import from a subpackage of a module before + the package is fully imported. In particular, this idiom may not work: + + np = lazy_import("numpy") + from numpy.lib import recfunctions + + This is due to a difference in the way Python's LazyLoader handles + subpackage imports compared to the normal import process. Hopefully + we will get Python's LazyLoader to fix this, or find a workaround. + In the meantime, this is a potential problem. + + The workaround is to import numpy before importing from the subpackage. + + Notes + ----- + We often see the following pattern:: + + def myfunc(): + import scipy as sp + sp.argmin(...) + .... + + This is to prevent a library, in this case `scipy`, from being + imported at function definition time, since that can be slow. + + This function provides a proxy module that, upon access, imports + the actual module. So the idiom equivalent to the above example is:: + + sp = lazy.load("scipy") + + def myfunc(): + sp.argmin(...) + .... + + The initial import time is fast because the actual import is delayed + until the first attribute is requested. The overall import time may + decrease as well for users that don't make use of large portions + of the library. + + Parameters + ---------- + fullname : str + The full name of the package or subpackage to import. For example:: + + sp = lazy.load("scipy") # import scipy as sp + spla = lazy.load("scipy.linalg") # import scipy.linalg as spla + + Returns + ------- + pm : importlib.util._LazyModule + Proxy module. Can be used like any regularly imported module. + Actual loading of the module occurs upon first attribute request. + + """ + try: + return sys.modules[fullname] + except: + pass + + # Not previously loaded -- look it up + spec = importlib.util.find_spec(fullname) + + if spec is None: + try: + parent = inspect.stack()[1] + frame_data = { + "spec": fullname, + "filename": parent.filename, + "lineno": parent.lineno, + "function": parent.function, + "code_context": parent.code_context, + } + return DelayedImportErrorModule(frame_data, "DelayedImportErrorModule") + finally: + del parent + + module = importlib.util.module_from_spec(spec) + sys.modules[fullname] = module + + loader = importlib.util.LazyLoader(spec.loader) + loader.exec_module(module) + + return module diff --git a/venv/lib/python3.10/site-packages/networkx/relabel.py b/venv/lib/python3.10/site-packages/networkx/relabel.py new file mode 100644 index 0000000000000000000000000000000000000000..4b870f726ef42e0bcaa7bf724e2ae6ab4145f288 --- /dev/null +++ b/venv/lib/python3.10/site-packages/networkx/relabel.py @@ -0,0 +1,285 @@ +import networkx as nx + +__all__ = ["convert_node_labels_to_integers", "relabel_nodes"] + + +@nx._dispatchable( + preserve_all_attrs=True, mutates_input={"not copy": 2}, returns_graph=True +) +def relabel_nodes(G, mapping, copy=True): + """Relabel the nodes of the graph G according to a given mapping. + + The original node ordering may not be preserved if `copy` is `False` and the + mapping includes overlap between old and new labels. + + Parameters + ---------- + G : graph + A NetworkX graph + + mapping : dictionary + A dictionary with the old labels as keys and new labels as values. + A partial mapping is allowed. Mapping 2 nodes to a single node is allowed. + Any non-node keys in the mapping are ignored. + + copy : bool (optional, default=True) + If True return a copy, or if False relabel the nodes in place. + + Examples + -------- + To create a new graph with nodes relabeled according to a given + dictionary: + + >>> G = nx.path_graph(3) + >>> sorted(G) + [0, 1, 2] + >>> mapping = {0: "a", 1: "b", 2: "c"} + >>> H = nx.relabel_nodes(G, mapping) + >>> sorted(H) + ['a', 'b', 'c'] + + Nodes can be relabeled with any hashable object, including numbers + and strings: + + >>> import string + >>> G = nx.path_graph(26) # nodes are integers 0 through 25 + >>> sorted(G)[:3] + [0, 1, 2] + >>> mapping = dict(zip(G, string.ascii_lowercase)) + >>> G = nx.relabel_nodes(G, mapping) # nodes are characters a through z + >>> sorted(G)[:3] + ['a', 'b', 'c'] + >>> mapping = dict(zip(G, range(1, 27))) + >>> G = nx.relabel_nodes(G, mapping) # nodes are integers 1 through 26 + >>> sorted(G)[:3] + [1, 2, 3] + + To perform a partial in-place relabeling, provide a dictionary + mapping only a subset of the nodes, and set the `copy` keyword + argument to False: + + >>> G = nx.path_graph(3) # nodes 0-1-2 + >>> mapping = {0: "a", 1: "b"} # 0->'a' and 1->'b' + >>> G = nx.relabel_nodes(G, mapping, copy=False) + >>> sorted(G, key=str) + [2, 'a', 'b'] + + A mapping can also be given as a function: + + >>> G = nx.path_graph(3) + >>> H = nx.relabel_nodes(G, lambda x: x**2) + >>> list(H) + [0, 1, 4] + + In a multigraph, relabeling two or more nodes to the same new node + will retain all edges, but may change the edge keys in the process: + + >>> G = nx.MultiGraph() + >>> G.add_edge(0, 1, value="a") # returns the key for this edge + 0 + >>> G.add_edge(0, 2, value="b") + 0 + >>> G.add_edge(0, 3, value="c") + 0 + >>> mapping = {1: 4, 2: 4, 3: 4} + >>> H = nx.relabel_nodes(G, mapping, copy=True) + >>> print(H[0]) + {4: {0: {'value': 'a'}, 1: {'value': 'b'}, 2: {'value': 'c'}}} + + This works for in-place relabeling too: + + >>> G = nx.relabel_nodes(G, mapping, copy=False) + >>> print(G[0]) + {4: {0: {'value': 'a'}, 1: {'value': 'b'}, 2: {'value': 'c'}}} + + Notes + ----- + Only the nodes specified in the mapping will be relabeled. + Any non-node keys in the mapping are ignored. + + The keyword setting copy=False modifies the graph in place. + Relabel_nodes avoids naming collisions by building a + directed graph from ``mapping`` which specifies the order of + relabelings. Naming collisions, such as a->b, b->c, are ordered + such that "b" gets renamed to "c" before "a" gets renamed "b". + In cases of circular mappings (e.g. a->b, b->a), modifying the + graph is not possible in-place and an exception is raised. + In that case, use copy=True. + + If a relabel operation on a multigraph would cause two or more + edges to have the same source, target and key, the second edge must + be assigned a new key to retain all edges. The new key is set + to the lowest non-negative integer not already used as a key + for edges between these two nodes. Note that this means non-numeric + keys may be replaced by numeric keys. + + See Also + -------- + convert_node_labels_to_integers + """ + # you can pass any callable e.g. f(old_label) -> new_label or + # e.g. str(old_label) -> new_label, but we'll just make a dictionary here regardless + m = {n: mapping(n) for n in G} if callable(mapping) else mapping + + if copy: + return _relabel_copy(G, m) + else: + return _relabel_inplace(G, m) + + +def _relabel_inplace(G, mapping): + if len(mapping.keys() & mapping.values()) > 0: + # labels sets overlap + # can we topological sort and still do the relabeling? + D = nx.DiGraph(list(mapping.items())) + D.remove_edges_from(nx.selfloop_edges(D)) + try: + nodes = reversed(list(nx.topological_sort(D))) + except nx.NetworkXUnfeasible as err: + raise nx.NetworkXUnfeasible( + "The node label sets are overlapping and no ordering can " + "resolve the mapping. Use copy=True." + ) from err + else: + # non-overlapping label sets, sort them in the order of G nodes + nodes = [n for n in G if n in mapping] + + multigraph = G.is_multigraph() + directed = G.is_directed() + + for old in nodes: + # Test that old is in both mapping and G, otherwise ignore. + try: + new = mapping[old] + G.add_node(new, **G.nodes[old]) + except KeyError: + continue + if new == old: + continue + if multigraph: + new_edges = [ + (new, new if old == target else target, key, data) + for (_, target, key, data) in G.edges(old, data=True, keys=True) + ] + if directed: + new_edges += [ + (new if old == source else source, new, key, data) + for (source, _, key, data) in G.in_edges(old, data=True, keys=True) + ] + # Ensure new edges won't overwrite existing ones + seen = set() + for i, (source, target, key, data) in enumerate(new_edges): + if target in G[source] and key in G[source][target]: + new_key = 0 if not isinstance(key, int | float) else key + while new_key in G[source][target] or (target, new_key) in seen: + new_key += 1 + new_edges[i] = (source, target, new_key, data) + seen.add((target, new_key)) + else: + new_edges = [ + (new, new if old == target else target, data) + for (_, target, data) in G.edges(old, data=True) + ] + if directed: + new_edges += [ + (new if old == source else source, new, data) + for (source, _, data) in G.in_edges(old, data=True) + ] + G.remove_node(old) + G.add_edges_from(new_edges) + return G + + +def _relabel_copy(G, mapping): + H = G.__class__() + H.add_nodes_from(mapping.get(n, n) for n in G) + H._node.update((mapping.get(n, n), d.copy()) for n, d in G.nodes.items()) + if G.is_multigraph(): + new_edges = [ + (mapping.get(n1, n1), mapping.get(n2, n2), k, d.copy()) + for (n1, n2, k, d) in G.edges(keys=True, data=True) + ] + + # check for conflicting edge-keys + undirected = not G.is_directed() + seen_edges = set() + for i, (source, target, key, data) in enumerate(new_edges): + while (source, target, key) in seen_edges: + if not isinstance(key, int | float): + key = 0 + key += 1 + seen_edges.add((source, target, key)) + if undirected: + seen_edges.add((target, source, key)) + new_edges[i] = (source, target, key, data) + + H.add_edges_from(new_edges) + else: + H.add_edges_from( + (mapping.get(n1, n1), mapping.get(n2, n2), d.copy()) + for (n1, n2, d) in G.edges(data=True) + ) + H.graph.update(G.graph) + return H + + +@nx._dispatchable(preserve_all_attrs=True, returns_graph=True) +def convert_node_labels_to_integers( + G, first_label=0, ordering="default", label_attribute=None +): + """Returns a copy of the graph G with the nodes relabeled using + consecutive integers. + + Parameters + ---------- + G : graph + A NetworkX graph + + first_label : int, optional (default=0) + An integer specifying the starting offset in numbering nodes. + The new integer labels are numbered first_label, ..., n-1+first_label. + + ordering : string + "default" : inherit node ordering from G.nodes() + "sorted" : inherit node ordering from sorted(G.nodes()) + "increasing degree" : nodes are sorted by increasing degree + "decreasing degree" : nodes are sorted by decreasing degree + + label_attribute : string, optional (default=None) + Name of node attribute to store old label. If None no attribute + is created. + + Notes + ----- + Node and edge attribute data are copied to the new (relabeled) graph. + + There is no guarantee that the relabeling of nodes to integers will + give the same two integers for two (even identical graphs). + Use the `ordering` argument to try to preserve the order. + + See Also + -------- + relabel_nodes + """ + N = G.number_of_nodes() + first_label + if ordering == "default": + mapping = dict(zip(G.nodes(), range(first_label, N))) + elif ordering == "sorted": + nlist = sorted(G.nodes()) + mapping = dict(zip(nlist, range(first_label, N))) + elif ordering == "increasing degree": + dv_pairs = [(d, n) for (n, d) in G.degree()] + dv_pairs.sort() # in-place sort from lowest to highest degree + mapping = dict(zip([n for d, n in dv_pairs], range(first_label, N))) + elif ordering == "decreasing degree": + dv_pairs = [(d, n) for (n, d) in G.degree()] + dv_pairs.sort() # in-place sort from lowest to highest degree + dv_pairs.reverse() + mapping = dict(zip([n for d, n in dv_pairs], range(first_label, N))) + else: + raise nx.NetworkXError(f"Unknown node ordering: {ordering}") + H = relabel_nodes(G, mapping) + # create node attribute with the old label + if label_attribute is not None: + nx.set_node_attributes(H, {v: k for k, v in mapping.items()}, label_attribute) + return H diff --git a/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/License.txt b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/License.txt new file mode 100644 index 0000000000000000000000000000000000000000..b491c70e0aef319022ded661e111ddbd45b8a17f --- /dev/null +++ b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/License.txt @@ -0,0 +1,1568 @@ +End User License Agreement +-------------------------- + + +Preface +------- + +The Software License Agreement in Chapter 1 and the Supplement +in Chapter 2 contain license terms and conditions that govern +the use of NVIDIA software. By accepting this agreement, you +agree to comply with all the terms and conditions applicable +to the product(s) included herein. + + +NVIDIA Driver + + +Description + +This package contains the operating system driver and +fundamental system software components for NVIDIA GPUs. + + +NVIDIA CUDA Toolkit + + +Description + +The NVIDIA CUDA Toolkit provides command-line and graphical +tools for building, debugging and optimizing the performance +of applications accelerated by NVIDIA GPUs, runtime and math +libraries, and documentation including programming guides, +user manuals, and API references. + + +Default Install Location of CUDA Toolkit + +Windows platform: + +%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v#.# + +Linux platform: + +/usr/local/cuda-#.# + +Mac platform: + +/Developer/NVIDIA/CUDA-#.# + + +NVIDIA CUDA Samples + + +Description + +This package includes over 100+ CUDA examples that demonstrate +various CUDA programming principles, and efficient CUDA +implementation of algorithms in specific application domains. + + +Default Install Location of CUDA Samples + +Windows platform: + +%ProgramData%\NVIDIA Corporation\CUDA Samples\v#.# + +Linux platform: + +/usr/local/cuda-#.#/samples + +and + +$HOME/NVIDIA_CUDA-#.#_Samples + +Mac platform: + +/Developer/NVIDIA/CUDA-#.#/samples + + +NVIDIA Nsight Visual Studio Edition (Windows only) + + +Description + +NVIDIA Nsight Development Platform, Visual Studio Edition is a +development environment integrated into Microsoft Visual +Studio that provides tools for debugging, profiling, analyzing +and optimizing your GPU computing and graphics applications. + + +Default Install Location of Nsight Visual Studio Edition + +Windows platform: + +%ProgramFiles(x86)%\NVIDIA Corporation\Nsight Visual Studio Edition #.# + + +1. License Agreement for NVIDIA Software Development Kits +--------------------------------------------------------- + + +Release Date: July 26, 2018 +--------------------------- + + +Important NoticeRead before downloading, installing, +copying or using the licensed software: +------------------------------------------------------- + +This license agreement, including exhibits attached +("Agreement”) is a legal agreement between you and NVIDIA +Corporation ("NVIDIA") and governs your use of a NVIDIA +software development kit (“SDK”). + +Each SDK has its own set of software and materials, but here +is a description of the types of items that may be included in +a SDK: source code, header files, APIs, data sets and assets +(examples include images, textures, models, scenes, videos, +native API input/output files), binary software, sample code, +libraries, utility programs, programming code and +documentation. + +This Agreement can be accepted only by an adult of legal age +of majority in the country in which the SDK is used. + +If you are entering into this Agreement on behalf of a company +or other legal entity, you represent that you have the legal +authority to bind the entity to this Agreement, in which case +“you” will mean the entity you represent. + +If you don’t have the required age or authority to accept +this Agreement, or if you don’t accept all the terms and +conditions of this Agreement, do not download, install or use +the SDK. + +You agree to use the SDK only for purposes that are permitted +by (a) this Agreement, and (b) any applicable law, regulation +or generally accepted practices or guidelines in the relevant +jurisdictions. + + +1.1. License + + +1.1.1. License Grant + +Subject to the terms of this Agreement, NVIDIA hereby grants +you a non-exclusive, non-transferable license, without the +right to sublicense (except as expressly provided in this +Agreement) to: + + 1. Install and use the SDK, + + 2. Modify and create derivative works of sample source code + delivered in the SDK, and + + 3. Distribute those portions of the SDK that are identified + in this Agreement as distributable, as incorporated in + object code format into a software application that meets + the distribution requirements indicated in this Agreement. + + +1.1.2. Distribution Requirements + +These are the distribution requirements for you to exercise +the distribution grant: + + 1. Your application must have material additional + functionality, beyond the included portions of the SDK. + + 2. The distributable portions of the SDK shall only be + accessed by your application. + + 3. The following notice shall be included in modifications + and derivative works of sample source code distributed: + “This software contains source code provided by NVIDIA + Corporation.” + + 4. Unless a developer tool is identified in this Agreement + as distributable, it is delivered for your internal use + only. + + 5. The terms under which you distribute your application + must be consistent with the terms of this Agreement, + including (without limitation) terms relating to the + license grant and license restrictions and protection of + NVIDIA’s intellectual property rights. Additionally, you + agree that you will protect the privacy, security and + legal rights of your application users. + + 6. You agree to notify NVIDIA in writing of any known or + suspected distribution or use of the SDK not in compliance + with the requirements of this Agreement, and to enforce + the terms of your agreements with respect to distributed + SDK. + + +1.1.3. Authorized Users + +You may allow employees and contractors of your entity or of +your subsidiary(ies) to access and use the SDK from your +secure network to perform work on your behalf. + +If you are an academic institution you may allow users +enrolled or employed by the academic institution to access and +use the SDK from your secure network. + +You are responsible for the compliance with the terms of this +Agreement by your authorized users. If you become aware that +your authorized users didn’t follow the terms of this +Agreement, you agree to take reasonable steps to resolve the +non-compliance and prevent new occurrences. + + +1.1.4. Pre-Release SDK + +The SDK versions identified as alpha, beta, preview or +otherwise as pre-release, may not be fully functional, may +contain errors or design flaws, and may have reduced or +different security, privacy, accessibility, availability, and +reliability standards relative to commercial versions of +NVIDIA software and materials. Use of a pre-release SDK may +result in unexpected results, loss of data, project delays or +other unpredictable damage or loss. + +You may use a pre-release SDK at your own risk, understanding +that pre-release SDKs are not intended for use in production +or business-critical systems. + +NVIDIA may choose not to make available a commercial version +of any pre-release SDK. NVIDIA may also choose to abandon +development and terminate the availability of a pre-release +SDK at any time without liability. + + +1.1.5. Updates + +NVIDIA may, at its option, make available patches, workarounds +or other updates to this SDK. Unless the updates are provided +with their separate governing terms, they are deemed part of +the SDK licensed to you as provided in this Agreement. You +agree that the form and content of the SDK that NVIDIA +provides may change without prior notice to you. While NVIDIA +generally maintains compatibility between versions, NVIDIA may +in some cases make changes that introduce incompatibilities in +future versions of the SDK. + + +1.1.6. Third Party Licenses + +The SDK may come bundled with, or otherwise include or be +distributed with, third party software licensed by a NVIDIA +supplier and/or open source software provided under an open +source license. Use of third party software is subject to the +third-party license terms, or in the absence of third party +terms, the terms of this Agreement. Copyright to third party +software is held by the copyright holders indicated in the +third-party software or license. + + +1.1.7. Reservation of Rights + +NVIDIA reserves all rights, title, and interest in and to the +SDK, not expressly granted to you under this Agreement. + + +1.2. Limitations + +The following license limitations apply to your use of the +SDK: + + 1. You may not reverse engineer, decompile or disassemble, + or remove copyright or other proprietary notices from any + portion of the SDK or copies of the SDK. + + 2. Except as expressly provided in this Agreement, you may + not copy, sell, rent, sublicense, transfer, distribute, + modify, or create derivative works of any portion of the + SDK. For clarity, you may not distribute or sublicense the + SDK as a stand-alone product. + + 3. Unless you have an agreement with NVIDIA for this + purpose, you may not indicate that an application created + with the SDK is sponsored or endorsed by NVIDIA. + + 4. You may not bypass, disable, or circumvent any + encryption, security, digital rights management or + authentication mechanism in the SDK. + + 5. You may not use the SDK in any manner that would cause it + to become subject to an open source software license. As + examples, licenses that require as a condition of use, + modification, and/or distribution that the SDK be: + + a. Disclosed or distributed in source code form; + + b. Licensed for the purpose of making derivative works; + or + + c. Redistributable at no charge. + + 6. Unless you have an agreement with NVIDIA for this + purpose, you may not use the SDK with any system or + application where the use or failure of the system or + application can reasonably be expected to threaten or + result in personal injury, death, or catastrophic loss. + Examples include use in avionics, navigation, military, + medical, life support or other life critical applications. + NVIDIA does not design, test or manufacture the SDK for + these critical uses and NVIDIA shall not be liable to you + or any third party, in whole or in part, for any claims or + damages arising from such uses. + + 7. You agree to defend, indemnify and hold harmless NVIDIA + and its affiliates, and their respective employees, + contractors, agents, officers and directors, from and + against any and all claims, damages, obligations, losses, + liabilities, costs or debt, fines, restitutions and + expenses (including but not limited to attorney’s fees + and costs incident to establishing the right of + indemnification) arising out of or related to your use of + the SDK outside of the scope of this Agreement, or not in + compliance with its terms. + + +1.3. Ownership + + 1. NVIDIA or its licensors hold all rights, title and + interest in and to the SDK and its modifications and + derivative works, including their respective intellectual + property rights, subject to your rights described in this + section. This SDK may include software and materials from + NVIDIA’s licensors, and these licensors are intended + third party beneficiaries that may enforce this Agreement + with respect to their intellectual property rights. + + 2. You hold all rights, title and interest in and to your + applications and your derivative works of the sample + source code delivered in the SDK, including their + respective intellectual property rights, subject to + NVIDIA’s rights described in this section. + + 3. You may, but don’t have to, provide to NVIDIA + suggestions, feature requests or other feedback regarding + the SDK, including possible enhancements or modifications + to the SDK. For any feedback that you voluntarily provide, + you hereby grant NVIDIA and its affiliates a perpetual, + non-exclusive, worldwide, irrevocable license to use, + reproduce, modify, license, sublicense (through multiple + tiers of sublicensees), and distribute (through multiple + tiers of distributors) it without the payment of any + royalties or fees to you. NVIDIA will use feedback at its + choice. NVIDIA is constantly looking for ways to improve + its products, so you may send feedback to NVIDIA through + the developer portal at https://developer.nvidia.com. + + +1.4. No Warranties + +THE SDK IS PROVIDED BY NVIDIA “AS IS” AND “WITH ALL +FAULTS.” TO THE MAXIMUM EXTENT PERMITTED BY LAW, NVIDIA AND +ITS AFFILIATES EXPRESSLY DISCLAIM ALL WARRANTIES OF ANY KIND +OR NATURE, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, +BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, OR THE +ABSENCE OF ANY DEFECTS THEREIN, WHETHER LATENT OR PATENT. NO +WARRANTY IS MADE ON THE BASIS OF TRADE USAGE, COURSE OF +DEALING OR COURSE OF TRADE. + + +1.5. Limitation of Liability + +TO THE MAXIMUM EXTENT PERMITTED BY LAW, NVIDIA AND ITS +AFFILIATES SHALL NOT BE LIABLE FOR ANY SPECIAL, INCIDENTAL, +PUNITIVE OR CONSEQUENTIAL DAMAGES, OR ANY LOST PROFITS, LOSS +OF USE, LOSS OF DATA OR LOSS OF GOODWILL, OR THE COSTS OF +PROCURING SUBSTITUTE PRODUCTS, ARISING OUT OF OR IN CONNECTION +WITH THIS AGREEMENT OR THE USE OR PERFORMANCE OF THE SDK, +WHETHER SUCH LIABILITY ARISES FROM ANY CLAIM BASED UPON BREACH +OF CONTRACT, BREACH OF WARRANTY, TORT (INCLUDING NEGLIGENCE), +PRODUCT LIABILITY OR ANY OTHER CAUSE OF ACTION OR THEORY OF +LIABILITY. IN NO EVENT WILL NVIDIA’S AND ITS AFFILIATES +TOTAL CUMULATIVE LIABILITY UNDER OR ARISING OUT OF THIS +AGREEMENT EXCEED US$10.00. THE NATURE OF THE LIABILITY OR THE +NUMBER OF CLAIMS OR SUITS SHALL NOT ENLARGE OR EXTEND THIS +LIMIT. + +These exclusions and limitations of liability shall apply +regardless if NVIDIA or its affiliates have been advised of +the possibility of such damages, and regardless of whether a +remedy fails its essential purpose. These exclusions and +limitations of liability form an essential basis of the +bargain between the parties, and, absent any of these +exclusions or limitations of liability, the provisions of this +Agreement, including, without limitation, the economic terms, +would be substantially different. + + +1.6. Termination + + 1. This Agreement will continue to apply until terminated by + either you or NVIDIA as described below. + + 2. If you want to terminate this Agreement, you may do so by + stopping to use the SDK. + + 3. NVIDIA may, at any time, terminate this Agreement if: + + a. (i) you fail to comply with any term of this + Agreement and the non-compliance is not fixed within + thirty (30) days following notice from NVIDIA (or + immediately if you violate NVIDIA’s intellectual + property rights); + + b. (ii) you commence or participate in any legal + proceeding against NVIDIA with respect to the SDK; or + + c. (iii) NVIDIA decides to no longer provide the SDK in + a country or, in NVIDIA’s sole discretion, the + continued use of it is no longer commercially viable. + + 4. Upon any termination of this Agreement, you agree to + promptly discontinue use of the SDK and destroy all copies + in your possession or control. Your prior distributions in + accordance with this Agreement are not affected by the + termination of this Agreement. Upon written request, you + will certify in writing that you have complied with your + commitments under this section. Upon any termination of + this Agreement all provisions survive except for the + license grant provisions. + + +1.7. General + +If you wish to assign this Agreement or your rights and +obligations, including by merger, consolidation, dissolution +or operation of law, contact NVIDIA to ask for permission. Any +attempted assignment not approved by NVIDIA in writing shall +be void and of no effect. NVIDIA may assign, delegate or +transfer this Agreement and its rights and obligations, and if +to a non-affiliate you will be notified. + +You agree to cooperate with NVIDIA and provide reasonably +requested information to verify your compliance with this +Agreement. + +This Agreement will be governed in all respects by the laws of +the United States and of the State of Delaware as those laws +are applied to contracts entered into and performed entirely +within Delaware by Delaware residents, without regard to the +conflicts of laws principles. The United Nations Convention on +Contracts for the International Sale of Goods is specifically +disclaimed. You agree to all terms of this Agreement in the +English language. + +The state or federal courts residing in Santa Clara County, +California shall have exclusive jurisdiction over any dispute +or claim arising out of this Agreement. Notwithstanding this, +you agree that NVIDIA shall still be allowed to apply for +injunctive remedies or an equivalent type of urgent legal +relief in any jurisdiction. + +If any court of competent jurisdiction determines that any +provision of this Agreement is illegal, invalid or +unenforceable, such provision will be construed as limited to +the extent necessary to be consistent with and fully +enforceable under the law and the remaining provisions will +remain in full force and effect. Unless otherwise specified, +remedies are cumulative. + +Each party acknowledges and agrees that the other is an +independent contractor in the performance of this Agreement. + +The SDK has been developed entirely at private expense and is +“commercial items” consisting of “commercial computer +software” and “commercial computer software +documentation” provided with RESTRICTED RIGHTS. Use, +duplication or disclosure by the U.S. Government or a U.S. +Government subcontractor is subject to the restrictions in +this Agreement pursuant to DFARS 227.7202-3(a) or as set forth +in subparagraphs (c)(1) and (2) of the Commercial Computer +Software - Restricted Rights clause at FAR 52.227-19, as +applicable. Contractor/manufacturer is NVIDIA, 2788 San Tomas +Expressway, Santa Clara, CA 95051. + +The SDK is subject to United States export laws and +regulations. You agree that you will not ship, transfer or +export the SDK into any country, or use the SDK in any manner, +prohibited by the United States Bureau of Industry and +Security or economic sanctions regulations administered by the +U.S. Department of Treasury’s Office of Foreign Assets +Control (OFAC), or any applicable export laws, restrictions or +regulations. These laws include restrictions on destinations, +end users and end use. By accepting this Agreement, you +confirm that you are not a resident or citizen of any country +currently embargoed by the U.S. and that you are not otherwise +prohibited from receiving the SDK. + +Any notice delivered by NVIDIA to you under this Agreement +will be delivered via mail, email or fax. You agree that any +notices that NVIDIA sends you electronically will satisfy any +legal communication requirements. Please direct your legal +notices or other correspondence to NVIDIA Corporation, 2788 +San Tomas Expressway, Santa Clara, California 95051, United +States of America, Attention: Legal Department. + +This Agreement and any exhibits incorporated into this +Agreement constitute the entire agreement of the parties with +respect to the subject matter of this Agreement and supersede +all prior negotiations or documentation exchanged between the +parties relating to this SDK license. Any additional and/or +conflicting terms on documents issued by you are null, void, +and invalid. Any amendment or waiver under this Agreement +shall be in writing and signed by representatives of both +parties. + + +2. CUDA Toolkit Supplement to Software License Agreement for +NVIDIA Software Development Kits +------------------------------------------------------------ + + +Release date: August 16, 2018 +----------------------------- + +The terms in this supplement govern your use of the NVIDIA +CUDA Toolkit SDK under the terms of your license agreement +(“Agreement”) as modified by this supplement. Capitalized +terms used but not defined below have the meaning assigned to +them in the Agreement. + +This supplement is an exhibit to the Agreement and is +incorporated as an integral part of the Agreement. In the +event of conflict between the terms in this supplement and the +terms in the Agreement, the terms in this supplement govern. + + +2.1. License Scope + +The SDK is licensed for you to develop applications only for +use in systems with NVIDIA GPUs. + + +2.2. Distribution + +The portions of the SDK that are distributable under the +Agreement are listed in Attachment A. + + +2.3. Operating Systems + +Those portions of the SDK designed exclusively for use on the +Linux or FreeBSD operating systems, or other operating systems +derived from the source code to these operating systems, may +be copied and redistributed for use in accordance with this +Agreement, provided that the object code files are not +modified in any way (except for unzipping of compressed +files). + + +2.4. Audio and Video Encoders and Decoders + +You acknowledge and agree that it is your sole responsibility +to obtain any additional third-party licenses required to +make, have made, use, have used, sell, import, and offer for +sale your products or services that include or incorporate any +third-party software and content relating to audio and/or +video encoders and decoders from, including but not limited +to, Microsoft, Thomson, Fraunhofer IIS, Sisvel S.p.A., +MPEG-LA, and Coding Technologies. NVIDIA does not grant to you +under this Agreement any necessary patent or other rights with +respect to any audio and/or video encoders and decoders. + + +2.5. Licensing + +If the distribution terms in this Agreement are not suitable +for your organization, or for any questions regarding this +Agreement, please contact NVIDIA at +nvidia-compute-license-questions@nvidia.com. + + +2.6. Attachment A + +The following portions of the SDK are distributable under the +Agreement: + +Component + +CUDA Runtime + +Windows + +cudart.dll, cudart_static.lib, cudadevrt.lib + +Mac OSX + +libcudart.dylib, libcudart_static.a, libcudadevrt.a + +Linux + +libcudart.so, libcudart_static.a, libcudadevrt.a + +Android + +libcudart.so, libcudart_static.a, libcudadevrt.a + +Component + +CUDA FFT Library + +Windows + +cufft.dll, cufftw.dll, cufft.lib, cufftw.lib + +Mac OSX + +libcufft.dylib, libcufft_static.a, libcufftw.dylib, +libcufftw_static.a + +Linux + +libcufft.so, libcufft_static.a, libcufftw.so, +libcufftw_static.a + +Android + +libcufft.so, libcufft_static.a, libcufftw.so, +libcufftw_static.a + +Component + +CUDA BLAS Library + +Windows + +cublas.dll, cublasLt.dll + +Mac OSX + +libcublas.dylib, libcublasLt.dylib, libcublas_static.a, +libcublasLt_static.a + +Linux + +libcublas.so, libcublasLt.so, libcublas_static.a, +libcublasLt_static.a + +Android + +libcublas.so, libcublasLt.so, libcublas_static.a, +libcublasLt_static.a + +Component + +NVIDIA "Drop-in" BLAS Library + +Windows + +nvblas.dll + +Mac OSX + +libnvblas.dylib + +Linux + +libnvblas.so + +Component + +CUDA Sparse Matrix Library + +Windows + +cusparse.dll, cusparse.lib + +Mac OSX + +libcusparse.dylib, libcusparse_static.a + +Linux + +libcusparse.so, libcusparse_static.a + +Android + +libcusparse.so, libcusparse_static.a + +Component + +CUDA Linear Solver Library + +Windows + +cusolver.dll, cusolver.lib + +Mac OSX + +libcusolver.dylib, libcusolver_static.a + +Linux + +libcusolver.so, libcusolver_static.a + +Android + +libcusolver.so, libcusolver_static.a + +Component + +CUDA Random Number Generation Library + +Windows + +curand.dll, curand.lib + +Mac OSX + +libcurand.dylib, libcurand_static.a + +Linux + +libcurand.so, libcurand_static.a + +Android + +libcurand.so, libcurand_static.a + +Component + +CUDA Accelerated Graph Library + +Component + +NVIDIA Performance Primitives Library + +Windows + +nppc.dll, nppc.lib, nppial.dll, nppial.lib, nppicc.dll, +nppicc.lib, nppicom.dll, nppicom.lib, nppidei.dll, +nppidei.lib, nppif.dll, nppif.lib, nppig.dll, nppig.lib, +nppim.dll, nppim.lib, nppist.dll, nppist.lib, nppisu.dll, +nppisu.lib, nppitc.dll, nppitc.lib, npps.dll, npps.lib + +Mac OSX + +libnppc.dylib, libnppc_static.a, libnppial.dylib, +libnppial_static.a, libnppicc.dylib, libnppicc_static.a, +libnppicom.dylib, libnppicom_static.a, libnppidei.dylib, +libnppidei_static.a, libnppif.dylib, libnppif_static.a, +libnppig.dylib, libnppig_static.a, libnppim.dylib, +libnppisu_static.a, libnppitc.dylib, libnppitc_static.a, +libnpps.dylib, libnpps_static.a + +Linux + +libnppc.so, libnppc_static.a, libnppial.so, +libnppial_static.a, libnppicc.so, libnppicc_static.a, +libnppicom.so, libnppicom_static.a, libnppidei.so, +libnppidei_static.a, libnppif.so, libnppif_static.a +libnppig.so, libnppig_static.a, libnppim.so, +libnppim_static.a, libnppist.so, libnppist_static.a, +libnppisu.so, libnppisu_static.a, libnppitc.so +libnppitc_static.a, libnpps.so, libnpps_static.a + +Android + +libnppc.so, libnppc_static.a, libnppial.so, +libnppial_static.a, libnppicc.so, libnppicc_static.a, +libnppicom.so, libnppicom_static.a, libnppidei.so, +libnppidei_static.a, libnppif.so, libnppif_static.a +libnppig.so, libnppig_static.a, libnppim.so, +libnppim_static.a, libnppist.so, libnppist_static.a, +libnppisu.so, libnppisu_static.a, libnppitc.so +libnppitc_static.a, libnpps.so, libnpps_static.a + +Component + +NVIDIA JPEG Library + +Linux + +libnvjpeg.so, libnvjpeg_static.a + +Component + +Internal common library required for statically linking to +cuBLAS, cuSPARSE, cuFFT, cuRAND, nvJPEG and NPP + +Mac OSX + +libculibos.a + +Linux + +libculibos.a + +Component + +NVIDIA Runtime Compilation Library and Header + +All + +nvrtc.h + +Windows + +nvrtc.dll, nvrtc-builtins.dll + +Mac OSX + +libnvrtc.dylib, libnvrtc-builtins.dylib + +Linux + +libnvrtc.so, libnvrtc-builtins.so + +Component + +NVIDIA Optimizing Compiler Library + +Windows + +nvvm.dll + +Mac OSX + +libnvvm.dylib + +Linux + +libnvvm.so + +Component + +NVIDIA Common Device Math Functions Library + +Windows + +libdevice.10.bc + +Mac OSX + +libdevice.10.bc + +Linux + +libdevice.10.bc + +Component + +CUDA Occupancy Calculation Header Library + +All + +cuda_occupancy.h + +Component + +CUDA Half Precision Headers + +All + +cuda_fp16.h, cuda_fp16.hpp + +Component + +CUDA Profiling Tools Interface (CUPTI) Library + +Windows + +cupti.dll + +Mac OSX + +libcupti.dylib + +Linux + +libcupti.so + +Component + +NVIDIA Tools Extension Library + +Windows + +nvToolsExt.dll, nvToolsExt.lib + +Mac OSX + +libnvToolsExt.dylib + +Linux + +libnvToolsExt.so + +Component + +NVIDIA CUDA Driver Libraries + +Linux + +libcuda.so, libnvidia-fatbinaryloader.so, +libnvidia-ptxjitcompiler.so + +The NVIDIA CUDA Driver Libraries are only distributable in +applications that meet this criteria: + + 1. The application was developed starting from a NVIDIA CUDA + container obtained from Docker Hub or the NVIDIA GPU + Cloud, and + + 2. The resulting application is packaged as a Docker + container and distributed to users on Docker Hub or the + NVIDIA GPU Cloud only. + + +2.7. Attachment B + + +Additional Licensing Obligations + +The following third party components included in the SOFTWARE +are licensed to Licensee pursuant to the following terms and +conditions: + + 1. Licensee's use of the GDB third party component is + subject to the terms and conditions of GNU GPL v3: + + This product includes copyrighted third-party software licensed + under the terms of the GNU General Public License v3 ("GPL v3"). + All third-party software packages are copyright by their respective + authors. GPL v3 terms and conditions are hereby incorporated into + the Agreement by this reference: http://www.gnu.org/licenses/gpl.txt + + Consistent with these licensing requirements, the software + listed below is provided under the terms of the specified + open source software licenses. To obtain source code for + software provided under licenses that require + redistribution of source code, including the GNU General + Public License (GPL) and GNU Lesser General Public License + (LGPL), contact oss-requests@nvidia.com. This offer is + valid for a period of three (3) years from the date of the + distribution of this product by NVIDIA CORPORATION. + + Component License + CUDA-GDB GPL v3 + + 2. Licensee represents and warrants that any and all third + party licensing and/or royalty payment obligations in + connection with Licensee's use of the H.264 video codecs + are solely the responsibility of Licensee. + + 3. Licensee's use of the Thrust library is subject to the + terms and conditions of the Apache License Version 2.0. + All third-party software packages are copyright by their + respective authors. Apache License Version 2.0 terms and + conditions are hereby incorporated into the Agreement by + this reference. + http://www.apache.org/licenses/LICENSE-2.0.html + + In addition, Licensee acknowledges the following notice: + Thrust includes source code from the Boost Iterator, + Tuple, System, and Random Number libraries. + + Boost Software License - Version 1.0 - August 17th, 2003 + . . . . + + Permission is hereby granted, free of charge, to any person or + organization obtaining a copy of the software and accompanying + documentation covered by this license (the "Software") to use, + reproduce, display, distribute, execute, and transmit the Software, + and to prepare derivative works of the Software, and to permit + third-parties to whom the Software is furnished to do so, all + subject to the following: + + The copyright notices in the Software and this entire statement, + including the above license grant, this restriction and the following + disclaimer, must be included in all copies of the Software, in whole + or in part, and all derivative works of the Software, unless such + copies or derivative works are solely in the form of machine-executable + object code generated by a source language processor. + + 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, TITLE AND + NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR + OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + 4. Licensee's use of the LLVM third party component is + subject to the following terms and conditions: + + ====================================================== + LLVM Release License + ====================================================== + University of Illinois/NCSA + Open Source License + + Copyright (c) 2003-2010 University of Illinois at Urbana-Champaign. + All rights reserved. + + Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal with 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: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at Urbana- + Champaign, nor the names of its contributors may be used to endorse or + promote products derived from this Software without specific prior + written permission. + + 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 CONTRIBUTORS 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 WITH THE SOFTWARE. + + 5. Licensee's use (e.g. nvprof) of the PCRE third party + component is subject to the following terms and + conditions: + + ------------ + PCRE LICENCE + ------------ + PCRE is a library of functions to support regular expressions whose syntax + and semantics are as close as possible to those of the Perl 5 language. + Release 8 of PCRE is distributed under the terms of the "BSD" licence, as + specified below. The documentation for PCRE, supplied in the "doc" + directory, is distributed under the same terms as the software itself. The + basic library functions are written in C and are freestanding. Also + included in the distribution is a set of C++ wrapper functions, and a just- + in-time compiler that can be used to optimize pattern matching. These are + both optional features that can be omitted when the library is built. + + THE BASIC LIBRARY FUNCTIONS + --------------------------- + Written by: Philip Hazel + Email local part: ph10 + Email domain: cam.ac.uk + University of Cambridge Computing Service, + Cambridge, England. + Copyright (c) 1997-2012 University of Cambridge + All rights reserved. + + PCRE JUST-IN-TIME COMPILATION SUPPORT + ------------------------------------- + Written by: Zoltan Herczeg + Email local part: hzmester + Emain domain: freemail.hu + Copyright(c) 2010-2012 Zoltan Herczeg + All rights reserved. + + STACK-LESS JUST-IN-TIME COMPILER + -------------------------------- + Written by: Zoltan Herczeg + Email local part: hzmester + Emain domain: freemail.hu + Copyright(c) 2009-2012 Zoltan Herczeg + All rights reserved. + + THE C++ WRAPPER FUNCTIONS + ------------------------- + Contributed by: Google Inc. + Copyright (c) 2007-2012, Google Inc. + All rights reserved. + + THE "BSD" LICENCE + ----------------- + 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 University of Cambridge nor the name of Google + Inc. 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 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. + + 6. Some of the cuBLAS library routines were written by or + derived from code written by Vasily Volkov and are subject + to the Modified Berkeley Software Distribution License as + follows: + + Copyright (c) 2007-2009, Regents of the University of California + + 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 University of California, Berkeley 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 AUTHOR "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 AUTHOR 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. + + 7. Some of the cuBLAS library routines were written by or + derived from code written by Davide Barbieri and are + subject to the Modified Berkeley Software Distribution + License as follows: + + Copyright (c) 2008-2009 Davide Barbieri @ University of Rome Tor Vergata. + + 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. + * The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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. + + 8. Some of the cuBLAS library routines were derived from + code developed by the University of Tennessee and are + subject to the Modified Berkeley Software Distribution + License as follows: + + Copyright (c) 2010 The University of Tennessee. + + 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 listed in this license in the documentation and/or + other materials provided with the distribution. + * Neither the name of the copyright holders 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. + + 9. Some of the cuBLAS library routines were written by or + derived from code written by Jonathan Hogg and are subject + to the Modified Berkeley Software Distribution License as + follows: + + Copyright (c) 2012, The Science and Technology Facilities Council (STFC). + + 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 STFC 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 STFC 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. + + 10. Some of the cuBLAS library routines were written by or + derived from code written by Ahmad M. Abdelfattah, David + Keyes, and Hatem Ltaief, and are subject to the Apache + License, Version 2.0, as follows: + + -- (C) Copyright 2013 King Abdullah University of Science and Technology + Authors: + Ahmad Abdelfattah (ahmad.ahmad@kaust.edu.sa) + David Keyes (david.keyes@kaust.edu.sa) + Hatem Ltaief (hatem.ltaief@kaust.edu.sa) + + 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 King Abdullah University of Science and + Technology 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 + HOLDERS 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 + + 11. Some of the cuSPARSE library routines were written by or + derived from code written by Li-Wen Chang and are subject + to the NCSA Open Source License as follows: + + Copyright (c) 2012, University of Illinois. + + All rights reserved. + + Developed by: IMPACT Group, University of Illinois, http://impact.crhc.illinois.edu + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal with 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: + * 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 + disclaimers in the documentation and/or other materials provided + with the distribution. + * Neither the names of IMPACT Group, University of Illinois, nor + the names of its contributors may be used to endorse or promote + products derived from this Software without specific prior + written permission. + + 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 CONTRIBUTORS 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 WITH THE + SOFTWARE. + + 12. Some of the cuRAND library routines were written by or + derived from code written by Mutsuo Saito and Makoto + Matsumoto and are subject to the following license: + + Copyright (c) 2009, 2010 Mutsuo Saito, Makoto Matsumoto and Hiroshima + University. All rights reserved. + + Copyright (c) 2011 Mutsuo Saito, Makoto Matsumoto, Hiroshima + University and University of Tokyo. 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 Hiroshima University 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. + + 13. Some of the cuRAND library routines were derived from + code developed by D. E. Shaw Research and are subject to + the following license: + + Copyright 2010-2011, D. E. Shaw Research. + + 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 D. E. Shaw Research 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. + + 14. Some of the Math library routines were written by or + derived from code developed by Norbert Juffa and are + subject to the following license: + + Copyright (c) 2015-2017, Norbert Juffa + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. 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. + + 15. Licensee's use of the lz4 third party component is + subject to the following terms and conditions: + + Copyright (C) 2011-2013, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + 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 + 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. + + 16. The NPP library uses code from the Boost Math Toolkit, + and is subject to the following license: + + Boost Software License - Version 1.0 - August 17th, 2003 + . . . . + + Permission is hereby granted, free of charge, to any person or + organization obtaining a copy of the software and accompanying + documentation covered by this license (the "Software") to use, + reproduce, display, distribute, execute, and transmit the Software, + and to prepare derivative works of the Software, and to permit + third-parties to whom the Software is furnished to do so, all + subject to the following: + + The copyright notices in the Software and this entire statement, + including the above license grant, this restriction and the following + disclaimer, must be included in all copies of the Software, in whole + or in part, and all derivative works of the Software, unless such + copies or derivative works are solely in the form of machine-executable + object code generated by a source language processor. + + 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, TITLE AND + NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR + OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + 17. Portions of the Nsight Eclipse Edition is subject to the + following license: + + The Eclipse Foundation makes available all content in this plug-in + ("Content"). Unless otherwise indicated below, the Content is provided + to you under the terms and conditions of the Eclipse Public License + Version 1.0 ("EPL"). A copy of the EPL is available at http:// + www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program" + will mean the Content. + + If you did not receive this Content directly from the Eclipse + Foundation, the Content is being redistributed by another party + ("Redistributor") and different terms and conditions may apply to your + use of any object code in the Content. Check the Redistributor's + license that was provided with the Content. If no such license exists, + contact the Redistributor. Unless otherwise indicated below, the terms + and conditions of the EPL still apply to any source code in the + Content and such source code may be obtained at http://www.eclipse.org. + + 18. Some of the cuBLAS library routines uses code from + OpenAI, which is subject to the following license: + + License URL + https://github.com/openai/openai-gemm/blob/master/LICENSE + + License Text + The MIT License + + Copyright (c) 2016 OpenAI (http://openai.com), 2016 Google Inc. + + 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. + + 19. Licensee's use of the Visual Studio Setup Configuration + Samples is subject to the following license: + + The MIT License (MIT) + Copyright (C) Microsoft Corporation. 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. + + 20. Licensee's use of linmath.h header for CPU functions for + GL vector/matrix operations from lunarG is subject to the + Apache License Version 2.0. + + 21. The DX12-CUDA sample uses the d3dx12.h header, which is + subject to the MIT license . + +----------------- diff --git a/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/METADATA b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..cb533db38cb400ef0b0e71cea45e7ce506422bfd --- /dev/null +++ b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/METADATA @@ -0,0 +1,44 @@ +Metadata-Version: 2.2 +Name: nvidia-cublas-cu12 +Version: 12.8.4.1 +Summary: CUBLAS native runtime libraries +Home-page: https://developer.nvidia.com/cuda-zone +Author: Nvidia CUDA Installer Team +Author-email: compute_installer@nvidia.com +License: NVIDIA Proprietary Software +Keywords: cuda,nvidia,runtime,machine learning,deep learning +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Education +Classifier: Intended Audience :: Science/Research +Classifier: License :: Other/Proprietary License +Classifier: Natural Language :: English +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Scientific/Engineering +Classifier: Topic :: Scientific/Engineering :: Mathematics +Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence +Classifier: Topic :: Software Development +Classifier: Topic :: Software Development :: Libraries +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX :: Linux +Requires-Python: >=3 +License-File: License.txt +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: requires-python +Dynamic: summary + +CUBLAS native runtime libraries diff --git a/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/RECORD b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..6399a8b99b6ef1f812626c985f1426baaa5551c8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/RECORD @@ -0,0 +1,24 @@ +nvidia/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +nvidia/__pycache__/__init__.cpython-310.pyc,, +nvidia/cublas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +nvidia/cublas/__pycache__/__init__.cpython-310.pyc,, +nvidia/cublas/include/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +nvidia/cublas/include/__pycache__/__init__.cpython-310.pyc,, +nvidia/cublas/include/cublas.h,sha256=a0lLqy-k47NuwyDjuueC3W0Mpc908MTU7o5sMJqE-1w,41246 +nvidia/cublas/include/cublasLt.h,sha256=oH9TR01H5CWfGPIjJk6-Ljg8eQOu0gho7TNt83gCmwg,102451 +nvidia/cublas/include/cublasXt.h,sha256=CW9dyXYGSUW1wEXrVVyhU6OxBK1PUvMoYdVGlQT7L9A,37380 +nvidia/cublas/include/cublas_api.h,sha256=A4Jvv9elvoJIDw8ReUP7qBnSxEvdslc-ghW_ycq_qAU,374363 +nvidia/cublas/include/cublas_v2.h,sha256=qxMdB5jb97luEfw61LEAB-Wlr8A9DLBvO4rRypDCNKw,15460 +nvidia/cublas/include/nvblas.h,sha256=dXCLR-2oUiJFzLsDtIAK09m42ct4G0HWdYzBUuDPXpc,23341 +nvidia/cublas/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +nvidia/cublas/lib/__pycache__/__init__.cpython-310.pyc,, +nvidia/cublas/lib/libcublas.so.12,sha256=Axzmwsv7uUaPBAUnyrXFmQac5WCec-KPh1A4gQY-rCE,116388640 +nvidia/cublas/lib/libcublasLt.so.12,sha256=ELXmYxz4EVxmHriV7RUzgmMItY95VkZvU9I2pAybYiw,751771728 +nvidia/cublas/lib/libnvblas.so.12,sha256=nSRIINMpZvCvR6_mye5vNLIw-v6H6gFB3HEkGl0rNAw,753824 +nvidia_cublas_cu12-12.8.4.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +nvidia_cublas_cu12-12.8.4.1.dist-info/License.txt,sha256=rW9YU_ugyg0VnQ9Y1JrkmDDC-Mk_epJki5zpCttMbM0,59262 +nvidia_cublas_cu12-12.8.4.1.dist-info/METADATA,sha256=MQVW8cLDHsUX1LBJ6vEjdAgdHFLI702Ozqsqrx7f8t8,1683 +nvidia_cublas_cu12-12.8.4.1.dist-info/RECORD,, +nvidia_cublas_cu12-12.8.4.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +nvidia_cublas_cu12-12.8.4.1.dist-info/WHEEL,sha256=wwQXGCXJQEUF59fKjBnVVzOkzi78E6rE_-QuUR4ZT4w,109 +nvidia_cublas_cu12-12.8.4.1.dist-info/top_level.txt,sha256=fTkAtiFuL16nUrB9ytDDtpytz2t0B4NvYTnRzwAhO14,7 diff --git a/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/WHEEL b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..6a0d9dc1b21a540a280fda3e78767d22a3b7ed6e --- /dev/null +++ b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.8.1) +Root-Is-Purelib: true +Tag: py3-none-manylinux_2_27_x86_64 + diff --git a/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/top_level.txt b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..862f7abf232cdfbb928609856247292e81c9decb --- /dev/null +++ b/venv/lib/python3.10/site-packages/nvidia_cublas_cu12-12.8.4.1.dist-info/top_level.txt @@ -0,0 +1 @@ +nvidia diff --git a/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/METADATA b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..a50d2aa48f97864d1e0d0a0f0534e89115ff4bf9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/METADATA @@ -0,0 +1,132 @@ +Metadata-Version: 2.4 +Name: safetensors +Version: 0.6.2 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Education +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence +Classifier: Typing :: Typed +Requires-Dist: numpy>=1.21.6 ; extra == 'numpy' +Requires-Dist: safetensors[numpy] ; extra == 'torch' +Requires-Dist: torch>=1.10 ; extra == 'torch' +Requires-Dist: safetensors[numpy] ; extra == 'tensorflow' +Requires-Dist: tensorflow>=2.11.0 ; extra == 'tensorflow' +Requires-Dist: safetensors[numpy] ; extra == 'pinned-tf' +Requires-Dist: tensorflow==2.18.0 ; extra == 'pinned-tf' +Requires-Dist: safetensors[numpy] ; extra == 'jax' +Requires-Dist: flax>=0.6.3 ; extra == 'jax' +Requires-Dist: jax>=0.3.25 ; extra == 'jax' +Requires-Dist: jaxlib>=0.3.25 ; extra == 'jax' +Requires-Dist: mlx>=0.0.9 ; extra == 'mlx' +Requires-Dist: safetensors[numpy] ; extra == 'paddlepaddle' +Requires-Dist: paddlepaddle>=2.4.1 ; extra == 'paddlepaddle' +Requires-Dist: ruff ; extra == 'quality' +Requires-Dist: safetensors[numpy] ; extra == 'testing' +Requires-Dist: h5py>=3.7.0 ; extra == 'testing' +Requires-Dist: huggingface-hub>=0.12.1 ; extra == 'testing' +Requires-Dist: setuptools-rust>=1.5.2 ; extra == 'testing' +Requires-Dist: pytest>=7.2.0 ; extra == 'testing' +Requires-Dist: pytest-benchmark>=4.0.0 ; extra == 'testing' +Requires-Dist: hypothesis>=6.70.2 ; extra == 'testing' +Requires-Dist: safetensors[numpy] ; extra == 'testingfree' +Requires-Dist: huggingface-hub>=0.12.1 ; extra == 'testingfree' +Requires-Dist: setuptools-rust>=1.5.2 ; extra == 'testingfree' +Requires-Dist: pytest>=7.2.0 ; extra == 'testingfree' +Requires-Dist: pytest-benchmark>=4.0.0 ; extra == 'testingfree' +Requires-Dist: hypothesis>=6.70.2 ; extra == 'testingfree' +Requires-Dist: safetensors[torch] ; extra == 'all' +Requires-Dist: safetensors[numpy] ; extra == 'all' +Requires-Dist: safetensors[pinned-tf] ; extra == 'all' +Requires-Dist: safetensors[jax] ; extra == 'all' +Requires-Dist: safetensors[paddlepaddle] ; extra == 'all' +Requires-Dist: safetensors[quality] ; extra == 'all' +Requires-Dist: safetensors[testing] ; extra == 'all' +Requires-Dist: safetensors[all] ; extra == 'dev' +Provides-Extra: numpy +Provides-Extra: torch +Provides-Extra: tensorflow +Provides-Extra: pinned-tf +Provides-Extra: jax +Provides-Extra: mlx +Provides-Extra: paddlepaddle +Provides-Extra: quality +Provides-Extra: testing +Provides-Extra: testingfree +Provides-Extra: all +Provides-Extra: dev +License-File: LICENSE +Author-email: Nicolas Patry +Requires-Python: >=3.9 +Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM +Project-URL: Homepage, https://github.com/huggingface/safetensors +Project-URL: Source, https://github.com/huggingface/safetensors + +## Installation + +``` +pip install safetensors +``` + + +## Usage + +### Numpy + +```python +from safetensors.numpy import save_file, load_file +import numpy as np + +tensors = { + "a": np.zeros((2, 2)), + "b": np.zeros((2, 3), dtype=np.uint8) +} + +save_file(tensors, "./model.safetensors") + + +# Now loading +loaded = load_file("./model.safetensors") +``` + +### Torch + +```python +from safetensors.torch import save_file, load_file +import torch + +tensors = { + "a": torch.zeros((2, 2)), + "b": torch.zeros((2, 3), dtype=torch.uint8) +} + +save_file(tensors, "./model.safetensors") + + +# Now loading +loaded = load_file("./model.safetensors") +``` + +### Developing + +``` +# inside ./safetensors/bindings/python +pip install .[dev] +``` +Should be enough to install this library locally. + +### Testing + +``` +# inside ./safetensors/bindings/python +pip install .[dev] +pytest -sv tests/ +``` + diff --git a/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/RECORD b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..72bd733d652fa530f7b4f6d4bc86007a446cba68 --- /dev/null +++ b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/RECORD @@ -0,0 +1,23 @@ +safetensors-0.6.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +safetensors-0.6.2.dist-info/METADATA,sha256=h2j5dDn9hGrJvsSKXky6RQsmgUJSnbUnzkaB0pgzpzY,4081 +safetensors-0.6.2.dist-info/RECORD,, +safetensors-0.6.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +safetensors-0.6.2.dist-info/WHEEL,sha256=VZBTcAHj346we8v5sHqEYWJqpANtl5II9P-eXqA1tjs,127 +safetensors-0.6.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357 +safetensors/__init__.py,sha256=wbzKZlAVgnAsjHmqryuSJCiADvpDZxNGCfj8VzY0At0,194 +safetensors/__init__.pyi,sha256=IpwsrzRWJA2yR8TxMEC3RHgM_5TiDgSFqyvrAxAa15U,4019 +safetensors/__pycache__/__init__.cpython-310.pyc,, +safetensors/__pycache__/flax.cpython-310.pyc,, +safetensors/__pycache__/mlx.cpython-310.pyc,, +safetensors/__pycache__/numpy.cpython-310.pyc,, +safetensors/__pycache__/paddle.cpython-310.pyc,, +safetensors/__pycache__/tensorflow.cpython-310.pyc,, +safetensors/__pycache__/torch.cpython-310.pyc,, +safetensors/_safetensors_rust.abi3.so,sha256=zHx4cdjz9HqcPx-_xA9l-9a1emlVRd5h7ubT1Ypsfeo,1159824 +safetensors/flax.py,sha256=T59elUqzVDyGYGdR78QzNEuwyAc8KrEO0EuLBSKOnUs,3853 +safetensors/mlx.py,sha256=IR51jRpcJq6epb0Agj8VsxI9xqBS6NjeAJnr-Ny0jJU,3850 +safetensors/numpy.py,sha256=pIVTnGxAYUSzktkr6xop8B1JK2t3zBH29Ws-oDzo2hA,5003 +safetensors/paddle.py,sha256=0IZqQi_VGKE5Vx179aBOfeD0UBsEGcrndPWvqWSSEG4,4193 +safetensors/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +safetensors/tensorflow.py,sha256=AZ-O7-gM-JqTfjczZyCUAHm3Er-GSQnQWaFyY7mAIQc,3903 +safetensors/torch.py,sha256=K216e6DQEL7gLJkEd4RS-lRjoK6DfS8AXsiiu_mO78s,20259 diff --git a/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/WHEEL b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..775214dad4aae3fc38c002024a529849653cf95b --- /dev/null +++ b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: maturin (1.9.3) +Root-Is-Purelib: false +Tag: cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64 diff --git a/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/licenses/LICENSE b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/venv/lib/python3.10/site-packages/safetensors-0.6.2.dist-info/licenses/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/venv/lib/python3.10/site-packages/sympy/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b02649ec3f5477db155e51ec1e5f0c642ad76855 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/__pycache__/abc.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/__pycache__/abc.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b34e3a385786cb086120df40494c1c00e60e6616 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/__pycache__/abc.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/__pycache__/conftest.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/__pycache__/conftest.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b52f8c0662e5810fdf412c54860b8aff4bcb41ac Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/__pycache__/conftest.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/__pycache__/galgebra.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/__pycache__/galgebra.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f35f25f4218e7b4c175454447721b26be6505b0e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/__pycache__/galgebra.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/__pycache__/release.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/__pycache__/release.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c22c62ad16f6ba13bcf4a12576b431c078c1a355 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/__pycache__/release.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/__pycache__/this.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/__pycache__/this.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6bcd7297ebd71aa3ec79cc81f0385d9d5ecee24 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/__pycache__/this.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/benchmarks/__init__.py b/venv/lib/python3.10/site-packages/sympy/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc55309f6ea0f081e6f9dcf0c207c66396269ce4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_discrete_log.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_discrete_log.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4eb7e6261c9f28c0ee24f7c6cab4cf270a773a3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_discrete_log.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_meijerint.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_meijerint.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0a90ffcc6eb6a7e1b3655fe204b96e19a6d2973 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_meijerint.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_symbench.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_symbench.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86339054f11765f6566dcf6cb4cdebdaf19a5cca Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/benchmarks/__pycache__/bench_symbench.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_discrete_log.py b/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_discrete_log.py new file mode 100644 index 0000000000000000000000000000000000000000..76b273909e415318a7d3bace00ffff2a0bc53762 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_discrete_log.py @@ -0,0 +1,83 @@ +import sys +from time import time +from sympy.ntheory.residue_ntheory import (discrete_log, + _discrete_log_trial_mul, _discrete_log_shanks_steps, + _discrete_log_pollard_rho, _discrete_log_pohlig_hellman) + + +# Cyclic group (Z/pZ)* with p prime, order p - 1 and generator g +data_set_1 = [ + # p, p - 1, g + [191, 190, 19], + [46639, 46638, 6], + [14789363, 14789362, 2], + [4254225211, 4254225210, 2], + [432751500361, 432751500360, 7], + [158505390797053, 158505390797052, 2], + [6575202655312007, 6575202655312006, 5], + [8430573471995353769, 8430573471995353768, 3], + [3938471339744997827267, 3938471339744997827266, 2], + [875260951364705563393093, 875260951364705563393092, 5], + ] + + +# Cyclic sub-groups of (Z/nZ)* with prime order p and generator g +# (n, p are primes and n = 2 * p + 1) +data_set_2 = [ + # n, p, g + [227, 113, 3], + [2447, 1223, 2], + [24527, 12263, 2], + [245639, 122819, 2], + [2456747, 1228373, 3], + [24567899, 12283949, 3], + [245679023, 122839511, 2], + [2456791307, 1228395653, 3], + [24567913439, 12283956719, 2], + [245679135407, 122839567703, 2], + [2456791354763, 1228395677381, 3], + [24567913550903, 12283956775451, 2], + [245679135509519, 122839567754759, 2], + ] + + +# Cyclic sub-groups of (Z/nZ)* with smooth order o and generator g +data_set_3 = [ + # n, o, g + [2**118, 2**116, 3], + ] + + +def bench_discrete_log(data_set, algo=None): + if algo is None: + f = discrete_log + elif algo == 'trial': + f = _discrete_log_trial_mul + elif algo == 'shanks': + f = _discrete_log_shanks_steps + elif algo == 'rho': + f = _discrete_log_pollard_rho + elif algo == 'ph': + f = _discrete_log_pohlig_hellman + else: + raise ValueError("Argument 'algo' should be one" + " of ('trial', 'shanks', 'rho' or 'ph')") + + for i, data in enumerate(data_set): + for j, (n, p, g) in enumerate(data): + t = time() + l = f(n, pow(g, p - 1, n), g, p) + t = time() - t + print('[%02d-%03d] %15.10f' % (i, j, t)) + assert l == p - 1 + + +if __name__ == '__main__': + algo = sys.argv[1] \ + if len(sys.argv) > 1 else None + data_set = [ + data_set_1, + data_set_2, + data_set_3, + ] + bench_discrete_log(data_set, algo) diff --git a/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_meijerint.py b/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_meijerint.py new file mode 100644 index 0000000000000000000000000000000000000000..d648c3e02463d5a7ee1dcbe3b22af5cc22fef43d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_meijerint.py @@ -0,0 +1,261 @@ +# conceal the implicit import from the code quality tester +from sympy.core.numbers import (oo, pi) +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.bessel import besseli +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import integrate +from sympy.integrals.transforms import (mellin_transform, + inverse_fourier_transform, inverse_mellin_transform, + laplace_transform, inverse_laplace_transform, fourier_transform) + +LT = laplace_transform +FT = fourier_transform +MT = mellin_transform +IFT = inverse_fourier_transform +ILT = inverse_laplace_transform +IMT = inverse_mellin_transform + +from sympy.abc import x, y +nu, beta, rho = symbols('nu beta rho') + +apos, bpos, cpos, dpos, posk, p = symbols('a b c d k p', positive=True) +k = Symbol('k', real=True) +negk = Symbol('k', negative=True) + +mu1, mu2 = symbols('mu1 mu2', real=True, nonzero=True, finite=True) +sigma1, sigma2 = symbols('sigma1 sigma2', real=True, nonzero=True, + finite=True, positive=True) +rate = Symbol('lambda', positive=True) + + +def normal(x, mu, sigma): + return 1/sqrt(2*pi*sigma**2)*exp(-(x - mu)**2/2/sigma**2) + + +def exponential(x, rate): + return rate*exp(-rate*x) +alpha, beta = symbols('alpha beta', positive=True) +betadist = x**(alpha - 1)*(1 + x)**(-alpha - beta)*gamma(alpha + beta) \ + /gamma(alpha)/gamma(beta) +kint = Symbol('k', integer=True, positive=True) +chi = 2**(1 - kint/2)*x**(kint - 1)*exp(-x**2/2)/gamma(kint/2) +chisquared = 2**(-k/2)/gamma(k/2)*x**(k/2 - 1)*exp(-x/2) +dagum = apos*p/x*(x/bpos)**(apos*p)/(1 + x**apos/bpos**apos)**(p + 1) +d1, d2 = symbols('d1 d2', positive=True) +f = sqrt(((d1*x)**d1 * d2**d2)/(d1*x + d2)**(d1 + d2))/x \ + /gamma(d1/2)/gamma(d2/2)*gamma((d1 + d2)/2) +nupos, sigmapos = symbols('nu sigma', positive=True) +rice = x/sigmapos**2*exp(-(x**2 + nupos**2)/2/sigmapos**2)*besseli(0, x* + nupos/sigmapos**2) +mu = Symbol('mu', real=True) +laplace = exp(-abs(x - mu)/bpos)/2/bpos + +u = Symbol('u', polar=True) +tpos = Symbol('t', positive=True) + + +def E(expr): + integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1), + (x, 0, oo), (y, -oo, oo), meijerg=True) + integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1), + (y, -oo, oo), (x, 0, oo), meijerg=True) + +bench = [ + 'MT(x**nu*Heaviside(x - 1), x, s)', + 'MT(x**nu*Heaviside(1 - x), x, s)', + 'MT((1-x)**(beta - 1)*Heaviside(1-x), x, s)', + 'MT((x-1)**(beta - 1)*Heaviside(x-1), x, s)', + 'MT((1+x)**(-rho), x, s)', + 'MT(abs(1-x)**(-rho), x, s)', + 'MT((1-x)**(beta-1)*Heaviside(1-x) + a*(x-1)**(beta-1)*Heaviside(x-1), x, s)', + 'MT((x**a-b**a)/(x-b), x, s)', + 'MT((x**a-bpos**a)/(x-bpos), x, s)', + 'MT(exp(-x), x, s)', + 'MT(exp(-1/x), x, s)', + 'MT(log(x)**4*Heaviside(1-x), x, s)', + 'MT(log(x)**3*Heaviside(x-1), x, s)', + 'MT(log(x + 1), x, s)', + 'MT(log(1/x + 1), x, s)', + 'MT(log(abs(1 - x)), x, s)', + 'MT(log(abs(1 - 1/x)), x, s)', + 'MT(log(x)/(x+1), x, s)', + 'MT(log(x)**2/(x+1), x, s)', + 'MT(log(x)/(x+1)**2, x, s)', + 'MT(erf(sqrt(x)), x, s)', + + 'MT(besselj(a, 2*sqrt(x)), x, s)', + 'MT(sin(sqrt(x))*besselj(a, sqrt(x)), x, s)', + 'MT(cos(sqrt(x))*besselj(a, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))**2, x, s)', + 'MT(besselj(a, sqrt(x))*besselj(-a, sqrt(x)), x, s)', + 'MT(besselj(a - 1, sqrt(x))*besselj(a, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))*besselj(b, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))**2 + besselj(-a, sqrt(x))**2, x, s)', + 'MT(bessely(a, 2*sqrt(x)), x, s)', + 'MT(sin(sqrt(x))*bessely(a, sqrt(x)), x, s)', + 'MT(cos(sqrt(x))*bessely(a, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))*bessely(a, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))*bessely(b, sqrt(x)), x, s)', + 'MT(bessely(a, sqrt(x))**2, x, s)', + + 'MT(besselk(a, 2*sqrt(x)), x, s)', + 'MT(besselj(a, 2*sqrt(2*sqrt(x)))*besselk(a, 2*sqrt(2*sqrt(x))), x, s)', + 'MT(besseli(a, sqrt(x))*besselk(a, sqrt(x)), x, s)', + 'MT(besseli(b, sqrt(x))*besselk(a, sqrt(x)), x, s)', + 'MT(exp(-x/2)*besselk(a, x/2), x, s)', + + # later: ILT, IMT + + 'LT((t-apos)**bpos*exp(-cpos*(t-apos))*Heaviside(t-apos), t, s)', + 'LT(t**apos, t, s)', + 'LT(Heaviside(t), t, s)', + 'LT(Heaviside(t - apos), t, s)', + 'LT(1 - exp(-apos*t), t, s)', + 'LT((exp(2*t)-1)*exp(-bpos - t)*Heaviside(t)/2, t, s, noconds=True)', + 'LT(exp(t), t, s)', + 'LT(exp(2*t), t, s)', + 'LT(exp(apos*t), t, s)', + 'LT(log(t/apos), t, s)', + 'LT(erf(t), t, s)', + 'LT(sin(apos*t), t, s)', + 'LT(cos(apos*t), t, s)', + 'LT(exp(-apos*t)*sin(bpos*t), t, s)', + 'LT(exp(-apos*t)*cos(bpos*t), t, s)', + 'LT(besselj(0, t), t, s, noconds=True)', + 'LT(besselj(1, t), t, s, noconds=True)', + + 'FT(Heaviside(1 - abs(2*apos*x)), x, k)', + 'FT(Heaviside(1-abs(apos*x))*(1-abs(apos*x)), x, k)', + 'FT(exp(-apos*x)*Heaviside(x), x, k)', + 'IFT(1/(apos + 2*pi*I*x), x, posk, noconds=False)', + 'IFT(1/(apos + 2*pi*I*x), x, -posk, noconds=False)', + 'IFT(1/(apos + 2*pi*I*x), x, negk)', + 'FT(x*exp(-apos*x)*Heaviside(x), x, k)', + 'FT(exp(-apos*x)*sin(bpos*x)*Heaviside(x), x, k)', + 'FT(exp(-apos*x**2), x, k)', + 'IFT(sqrt(pi/apos)*exp(-(pi*k)**2/apos), k, x)', + 'FT(exp(-apos*abs(x)), x, k)', + + 'integrate(normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)', + 'integrate(x*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)', + 'integrate(x**2*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)', + 'integrate(x**3*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)', + 'integrate(normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(x*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(x*y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate((x+y+1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate((x+y-1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(x**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(y**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(exponential(x, rate), (x, 0, oo), meijerg=True)', + 'integrate(x*exponential(x, rate), (x, 0, oo), meijerg=True)', + 'integrate(x**2*exponential(x, rate), (x, 0, oo), meijerg=True)', + 'E(1)', + 'E(x*y)', + 'E(x*y**2)', + 'E((x+y+1)**2)', + 'E(x+y+1)', + 'E((x+y-1)**2)', + 'integrate(betadist, (x, 0, oo), meijerg=True)', + 'integrate(x*betadist, (x, 0, oo), meijerg=True)', + 'integrate(x**2*betadist, (x, 0, oo), meijerg=True)', + 'integrate(chi, (x, 0, oo), meijerg=True)', + 'integrate(x*chi, (x, 0, oo), meijerg=True)', + 'integrate(x**2*chi, (x, 0, oo), meijerg=True)', + 'integrate(chisquared, (x, 0, oo), meijerg=True)', + 'integrate(x*chisquared, (x, 0, oo), meijerg=True)', + 'integrate(x**2*chisquared, (x, 0, oo), meijerg=True)', + 'integrate(((x-k)/sqrt(2*k))**3*chisquared, (x, 0, oo), meijerg=True)', + 'integrate(dagum, (x, 0, oo), meijerg=True)', + 'integrate(x*dagum, (x, 0, oo), meijerg=True)', + 'integrate(x**2*dagum, (x, 0, oo), meijerg=True)', + 'integrate(f, (x, 0, oo), meijerg=True)', + 'integrate(x*f, (x, 0, oo), meijerg=True)', + 'integrate(x**2*f, (x, 0, oo), meijerg=True)', + 'integrate(rice, (x, 0, oo), meijerg=True)', + 'integrate(laplace, (x, -oo, oo), meijerg=True)', + 'integrate(x*laplace, (x, -oo, oo), meijerg=True)', + 'integrate(x**2*laplace, (x, -oo, oo), meijerg=True)', + 'integrate(log(x) * x**(k-1) * exp(-x) / gamma(k), (x, 0, oo))', + + 'integrate(sin(z*x)*(x**2-1)**(-(y+S(1)/2)), (x, 1, oo), meijerg=True)', + 'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)', + 'integrate(besselj(0,x)*besselj(1,x)*besselk(0,x), (x, 0, oo), meijerg=True)', + 'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)', + 'integrate(besselj(a,x)*besselj(b,x)/x, (x,0,oo), meijerg=True)', + + 'hyperexpand(meijerg((-s - a/2 + 1, -s + a/2 + 1), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), (a/2, -a/2), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), 1))', + "gammasimp(S('2**(2*s)*(-pi*gamma(-a + 1)*gamma(a + 1)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 3/2)*gamma(a + s + 1)/(a*(a + s)) - gamma(-a - 1/2)*gamma(-a + 1)*gamma(a + 1)*gamma(a + 3/2)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a + s + 1)*gamma(a - s + 1)/(a*(-a + s)))*gamma(-2*s + 1)*gamma(s + 1)/(pi*s*gamma(-a - 1/2)*gamma(a + 3/2)*gamma(-s + 1)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 1)*gamma(a - s + 3/2))'))", + + 'mellin_transform(E1(x), x, s)', + 'inverse_mellin_transform(gamma(s)/s, s, x, (0, oo))', + 'mellin_transform(expint(a, x), x, s)', + 'mellin_transform(Si(x), x, s)', + 'inverse_mellin_transform(-2**s*sqrt(pi)*gamma((s + 1)/2)/(2*s*gamma(-s/2 + 1)), s, x, (-1, 0))', + 'mellin_transform(Ci(sqrt(x)), x, s)', + 'inverse_mellin_transform(-4**s*sqrt(pi)*gamma(s)/(2*s*gamma(-s + S(1)/2)),s, u, (0, 1))', + 'laplace_transform(Ci(x), x, s)', + 'laplace_transform(expint(a, x), x, s)', + 'laplace_transform(expint(1, x), x, s)', + 'laplace_transform(expint(2, x), x, s)', + 'inverse_laplace_transform(-log(1 + s**2)/2/s, s, u)', + 'inverse_laplace_transform(log(s + 1)/s, s, x)', + 'inverse_laplace_transform((s - log(s + 1))/s**2, s, x)', + 'laplace_transform(Chi(x), x, s)', + 'laplace_transform(Shi(x), x, s)', + + 'integrate(exp(-z*x)/x, (x, 1, oo), meijerg=True, conds="none")', + 'integrate(exp(-z*x)/x**2, (x, 1, oo), meijerg=True, conds="none")', + 'integrate(exp(-z*x)/x**3, (x, 1, oo), meijerg=True,conds="none")', + 'integrate(-cos(x)/x, (x, tpos, oo), meijerg=True)', + 'integrate(-sin(x)/x, (x, tpos, oo), meijerg=True)', + 'integrate(sin(x)/x, (x, 0, z), meijerg=True)', + 'integrate(sinh(x)/x, (x, 0, z), meijerg=True)', + 'integrate(exp(-x)/x, x, meijerg=True)', + 'integrate(exp(-x)/x**2, x, meijerg=True)', + 'integrate(cos(u)/u, u, meijerg=True)', + 'integrate(cosh(u)/u, u, meijerg=True)', + 'integrate(expint(1, x), x, meijerg=True)', + 'integrate(expint(2, x), x, meijerg=True)', + 'integrate(Si(x), x, meijerg=True)', + 'integrate(Ci(u), u, meijerg=True)', + 'integrate(Shi(x), x, meijerg=True)', + 'integrate(Chi(u), u, meijerg=True)', + 'integrate(Si(x)*exp(-x), (x, 0, oo), meijerg=True)', + 'integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True)' +] + +from time import time +from sympy.core.cache import clear_cache +import sys + +timings = [] + +if __name__ == '__main__': + for n, string in enumerate(bench): + clear_cache() + _t = time() + exec(string) + _t = time() - _t + timings += [(_t, string)] + sys.stdout.write('.') + sys.stdout.flush() + if n % (len(bench) // 10) == 0: + sys.stdout.write('%s' % (10*n // len(bench))) + print() + + timings.sort(key=lambda x: -x[0]) + + for ti, string in timings: + print('%.2fs %s' % (ti, string)) diff --git a/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_symbench.py b/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_symbench.py new file mode 100644 index 0000000000000000000000000000000000000000..8ea700b44b677107f5345196a8895e8ed5a9d56d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/benchmarks/bench_symbench.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +from sympy.core.random import random +from sympy.core.numbers import (I, Integer, pi) +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin +from sympy.polys.polytools import factor +from sympy.simplify.simplify import simplify +from sympy.abc import x, y, z +from timeit import default_timer as clock + + +def bench_R1(): + "real(f(f(f(f(f(f(f(f(f(f(i/2)))))))))))" + def f(z): + return sqrt(Integer(1)/3)*z**2 + I/3 + f(f(f(f(f(f(f(f(f(f(I/2)))))))))).as_real_imag()[0] + + +def bench_R2(): + "Hermite polynomial hermite(15, y)" + def hermite(n, y): + if n == 1: + return 2*y + if n == 0: + return 1 + return (2*y*hermite(n - 1, y) - 2*(n - 1)*hermite(n - 2, y)).expand() + + hermite(15, y) + + +def bench_R3(): + "a = [bool(f==f) for _ in range(10)]" + f = x + y + z + [bool(f == f) for _ in range(10)] + + +def bench_R4(): + # we don't have Tuples + pass + + +def bench_R5(): + "blowup(L, 8); L=uniq(L)" + def blowup(L, n): + for i in range(n): + L.append( (L[i] + L[i + 1]) * L[i + 2] ) + + def uniq(x): + v = set(x) + return v + L = [x, y, z] + blowup(L, 8) + L = uniq(L) + + +def bench_R6(): + "sum(simplify((x+sin(i))/x+(x-sin(i))/x) for i in range(100))" + sum(simplify((x + sin(i))/x + (x - sin(i))/x) for i in range(100)) + + +def bench_R7(): + "[f.subs(x, random()) for _ in range(10**4)]" + f = x**24 + 34*x**12 + 45*x**3 + 9*x**18 + 34*x**10 + 32*x**21 + [f.subs(x, random()) for _ in range(10**4)] + + +def bench_R8(): + "right(x^2,0,5,10^4)" + def right(f, a, b, n): + a = sympify(a) + b = sympify(b) + n = sympify(n) + x = f.atoms(Symbol).pop() + Deltax = (b - a)/n + c = a + est = 0 + for i in range(n): + c += Deltax + est += f.subs(x, c) + return est*Deltax + + right(x**2, 0, 5, 10**4) + + +def _bench_R9(): + "factor(x^20 - pi^5*y^20)" + factor(x**20 - pi**5*y**20) + + +def bench_R10(): + "v = [-pi,-pi+1/10..,pi]" + def srange(min, max, step): + v = [min] + while (max - v[-1]).evalf() > 0: + v.append(v[-1] + step) + return v[:-1] + srange(-pi, pi, sympify(1)/10) + + +def bench_R11(): + "a = [random() + random()*I for w in [0..1000]]" + [random() + random()*I for w in range(1000)] + + +def bench_S1(): + "e=(x+y+z+1)**7;f=e*(e+1);f.expand()" + e = (x + y + z + 1)**7 + f = e*(e + 1) + f.expand() + + +if __name__ == '__main__': + benchmarks = [ + bench_R1, + bench_R2, + bench_R3, + bench_R5, + bench_R6, + bench_R7, + bench_R8, + #_bench_R9, + bench_R10, + bench_R11, + #bench_S1, + ] + + report = [] + for b in benchmarks: + t = clock() + b() + t = clock() - t + print("%s%65s: %f" % (b.__name__, b.__doc__, t)) diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/__init__.py b/venv/lib/python3.10/site-packages/sympy/diffgeom/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8846a99510601c9675103e21ef5a0a1e839fdd11 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/diffgeom/__init__.py @@ -0,0 +1,19 @@ +from .diffgeom import ( + BaseCovarDerivativeOp, BaseScalarField, BaseVectorField, Commutator, + contravariant_order, CoordSystem, CoordinateSymbol, + CovarDerivativeOp, covariant_order, Differential, intcurve_diffequ, + intcurve_series, LieDerivative, Manifold, metric_to_Christoffel_1st, + metric_to_Christoffel_2nd, metric_to_Ricci_components, + metric_to_Riemann_components, Patch, Point, TensorProduct, twoform_to_matrix, + vectors_in_basis, WedgeProduct, +) + +__all__ = [ + 'BaseCovarDerivativeOp', 'BaseScalarField', 'BaseVectorField', 'Commutator', + 'contravariant_order', 'CoordSystem', 'CoordinateSymbol', + 'CovarDerivativeOp', 'covariant_order', 'Differential', 'intcurve_diffequ', + 'intcurve_series', 'LieDerivative', 'Manifold', 'metric_to_Christoffel_1st', + 'metric_to_Christoffel_2nd', 'metric_to_Ricci_components', + 'metric_to_Riemann_components', 'Patch', 'Point', 'TensorProduct', + 'twoform_to_matrix', 'vectors_in_basis', 'WedgeProduct', +] diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28145e735d5b4f6d7e3356c6d67ff3284b9131fc Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/diffgeom.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/diffgeom.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54ad2d5014d2e7b4feb2f31607bf86a79cd4e230 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/diffgeom.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/rn.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/rn.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d29d12100284f167a6b99efe8ea9c907be1a5180 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/diffgeom/__pycache__/rn.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/diffgeom.py b/venv/lib/python3.10/site-packages/sympy/diffgeom/diffgeom.py new file mode 100644 index 0000000000000000000000000000000000000000..a95f83122d6de0b7015b9a3ad0573cbfd97a7ef3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/diffgeom/diffgeom.py @@ -0,0 +1,2270 @@ +from __future__ import annotations +from typing import Any + +from functools import reduce +from itertools import permutations + +from sympy.combinatorics import Permutation +from sympy.core import ( + Basic, Expr, Function, diff, + Pow, Mul, Add, Lambda, S, Tuple, Dict +) +from sympy.core.cache import cacheit + +from sympy.core.symbol import Symbol, Dummy +from sympy.core.symbol import Str +from sympy.core.sympify import _sympify +from sympy.functions import factorial +from sympy.matrices import ImmutableDenseMatrix as Matrix +from sympy.solvers import solve + +from sympy.utilities.exceptions import (sympy_deprecation_warning, + SymPyDeprecationWarning, + ignore_warnings) + + +# TODO you are a bit excessive in the use of Dummies +# TODO dummy point, literal field +# TODO too often one needs to call doit or simplify on the output, check the +# tests and find out why +from sympy.tensor.array import ImmutableDenseNDimArray + + +class Manifold(Basic): + """ + A mathematical manifold. + + Explanation + =========== + + A manifold is a topological space that locally resembles + Euclidean space near each point [1]. + This class does not provide any means to study the topological + characteristics of the manifold that it represents, though. + + Parameters + ========== + + name : str + The name of the manifold. + + dim : int + The dimension of the manifold. + + Examples + ======== + + >>> from sympy.diffgeom import Manifold + >>> m = Manifold('M', 2) + >>> m + M + >>> m.dim + 2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Manifold + """ + + def __new__(cls, name, dim, **kwargs): + if not isinstance(name, Str): + name = Str(name) + dim = _sympify(dim) + obj = super().__new__(cls, name, dim) + + obj.patches = _deprecated_list( + """ + Manifold.patches is deprecated. The Manifold object is now + immutable. Instead use a separate list to keep track of the + patches. + """, []) + return obj + + @property + def name(self): + return self.args[0] + + @property + def dim(self): + return self.args[1] + + +class Patch(Basic): + """ + A patch on a manifold. + + Explanation + =========== + + Coordinate patch, or patch in short, is a simply-connected open set around + a point in the manifold [1]. On a manifold one can have many patches that + do not always include the whole manifold. On these patches coordinate + charts can be defined that permit the parameterization of any point on the + patch in terms of a tuple of real numbers (the coordinates). + + This class does not provide any means to study the topological + characteristics of the patch that it represents. + + Parameters + ========== + + name : str + The name of the patch. + + manifold : Manifold + The manifold on which the patch is defined. + + Examples + ======== + + >>> from sympy.diffgeom import Manifold, Patch + >>> m = Manifold('M', 2) + >>> p = Patch('P', m) + >>> p + P + >>> p.dim + 2 + + References + ========== + + .. [1] G. Sussman, J. Wisdom, W. Farr, Functional Differential Geometry + (2013) + + """ + def __new__(cls, name, manifold, **kwargs): + if not isinstance(name, Str): + name = Str(name) + obj = super().__new__(cls, name, manifold) + + obj.manifold.patches.append(obj) # deprecated + obj.coord_systems = _deprecated_list( + """ + Patch.coord_systms is deprecated. The Patch class is now + immutable. Instead use a separate list to keep track of coordinate + systems. + """, []) + return obj + + @property + def name(self): + return self.args[0] + + @property + def manifold(self): + return self.args[1] + + @property + def dim(self): + return self.manifold.dim + + +class CoordSystem(Basic): + """ + A coordinate system defined on the patch. + + Explanation + =========== + + Coordinate system is a system that uses one or more coordinates to uniquely + determine the position of the points or other geometric elements on a + manifold [1]. + + By passing ``Symbols`` to *symbols* parameter, user can define the name and + assumptions of coordinate symbols of the coordinate system. If not passed, + these symbols are generated automatically and are assumed to be real valued. + + By passing *relations* parameter, user can define the transform relations of + coordinate systems. Inverse transformation and indirect transformation can + be found automatically. If this parameter is not passed, coordinate + transformation cannot be done. + + Parameters + ========== + + name : str + The name of the coordinate system. + + patch : Patch + The patch where the coordinate system is defined. + + symbols : list of Symbols, optional + Defines the names and assumptions of coordinate symbols. + + relations : dict, optional + Key is a tuple of two strings, who are the names of the systems where + the coordinates transform from and transform to. + Value is a tuple of the symbols before transformation and a tuple of + the expressions after transformation. + + Examples + ======== + + We define two-dimensional Cartesian coordinate system and polar coordinate + system. + + >>> from sympy import symbols, pi, sqrt, atan2, cos, sin + >>> from sympy.diffgeom import Manifold, Patch, CoordSystem + >>> m = Manifold('M', 2) + >>> p = Patch('P', m) + >>> x, y = symbols('x y', real=True) + >>> r, theta = symbols('r theta', nonnegative=True) + >>> relation_dict = { + ... ('Car2D', 'Pol'): [(x, y), (sqrt(x**2 + y**2), atan2(y, x))], + ... ('Pol', 'Car2D'): [(r, theta), (r*cos(theta), r*sin(theta))] + ... } + >>> Car2D = CoordSystem('Car2D', p, (x, y), relation_dict) + >>> Pol = CoordSystem('Pol', p, (r, theta), relation_dict) + + ``symbols`` property returns ``CoordinateSymbol`` instances. These symbols + are not same with the symbols used to construct the coordinate system. + + >>> Car2D + Car2D + >>> Car2D.dim + 2 + >>> Car2D.symbols + (x, y) + >>> _[0].func + + + ``transformation()`` method returns the transformation function from + one coordinate system to another. ``transform()`` method returns the + transformed coordinates. + + >>> Car2D.transformation(Pol) + Lambda((x, y), Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]])) + >>> Car2D.transform(Pol) + Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]]) + >>> Car2D.transform(Pol, [1, 2]) + Matrix([ + [sqrt(5)], + [atan(2)]]) + + ``jacobian()`` method returns the Jacobian matrix of coordinate + transformation between two systems. ``jacobian_determinant()`` method + returns the Jacobian determinant of coordinate transformation between two + systems. + + >>> Pol.jacobian(Car2D) + Matrix([ + [cos(theta), -r*sin(theta)], + [sin(theta), r*cos(theta)]]) + >>> Pol.jacobian(Car2D, [1, pi/2]) + Matrix([ + [0, -1], + [1, 0]]) + >>> Car2D.jacobian_determinant(Pol) + 1/sqrt(x**2 + y**2) + >>> Car2D.jacobian_determinant(Pol, [1,0]) + 1 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Coordinate_system + + """ + def __new__(cls, name, patch, symbols=None, relations={}, **kwargs): + if not isinstance(name, Str): + name = Str(name) + + # canonicallize the symbols + if symbols is None: + names = kwargs.get('names', None) + if names is None: + symbols = Tuple( + *[Symbol('%s_%s' % (name.name, i), real=True) + for i in range(patch.dim)] + ) + else: + sympy_deprecation_warning( + f""" +The 'names' argument to CoordSystem is deprecated. Use 'symbols' instead. That +is, replace + + CoordSystem(..., names={names}) + +with + + CoordSystem(..., symbols=[{', '.join(["Symbol(" + repr(n) + ", real=True)" for n in names])}]) + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + ) + symbols = Tuple( + *[Symbol(n, real=True) for n in names] + ) + else: + syms = [] + for s in symbols: + if isinstance(s, Symbol): + syms.append(Symbol(s.name, **s._assumptions.generator)) + elif isinstance(s, str): + sympy_deprecation_warning( + f""" + +Passing a string as the coordinate symbol name to CoordSystem is deprecated. +Pass a Symbol with the appropriate name and assumptions instead. + +That is, replace {s} with Symbol({s!r}, real=True). + """, + + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + ) + syms.append(Symbol(s, real=True)) + symbols = Tuple(*syms) + + # canonicallize the relations + rel_temp = {} + for k,v in relations.items(): + s1, s2 = k + if not isinstance(s1, Str): + s1 = Str(s1) + if not isinstance(s2, Str): + s2 = Str(s2) + key = Tuple(s1, s2) + + # Old version used Lambda as a value. + if isinstance(v, Lambda): + v = (tuple(v.signature), tuple(v.expr)) + else: + v = (tuple(v[0]), tuple(v[1])) + rel_temp[key] = v + relations = Dict(rel_temp) + + # construct the object + obj = super().__new__(cls, name, patch, symbols, relations) + + # Add deprecated attributes + obj.transforms = _deprecated_dict( + """ + CoordSystem.transforms is deprecated. The CoordSystem class is now + immutable. Use the 'relations' keyword argument to the + CoordSystems() constructor to specify relations. + """, {}) + obj._names = [str(n) for n in symbols] + obj.patch.coord_systems.append(obj) # deprecated + obj._dummies = [Dummy(str(n)) for n in symbols] # deprecated + obj._dummy = Dummy() + + return obj + + @property + def name(self): + return self.args[0] + + @property + def patch(self): + return self.args[1] + + @property + def manifold(self): + return self.patch.manifold + + @property + def symbols(self): + return tuple(CoordinateSymbol(self, i, **s._assumptions.generator) + for i,s in enumerate(self.args[2])) + + @property + def relations(self): + return self.args[3] + + @property + def dim(self): + return self.patch.dim + + ########################################################################## + # Finding transformation relation + ########################################################################## + + def transformation(self, sys): + """ + Return coordinate transformation function from *self* to *sys*. + + Parameters + ========== + + sys : CoordSystem + + Returns + ======= + + sympy.Lambda + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> R2_r.transformation(R2_p) + Lambda((x, y), Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]])) + + """ + signature = self.args[2] + + key = Tuple(self.name, sys.name) + if self == sys: + expr = Matrix(self.symbols) + elif key in self.relations: + expr = Matrix(self.relations[key][1]) + elif key[::-1] in self.relations: + expr = Matrix(self._inverse_transformation(sys, self)) + else: + expr = Matrix(self._indirect_transformation(self, sys)) + return Lambda(signature, expr) + + @staticmethod + def _solve_inverse(sym1, sym2, exprs, sys1_name, sys2_name): + ret = solve( + [t[0] - t[1] for t in zip(sym2, exprs)], + list(sym1), dict=True) + + if len(ret) == 0: + temp = "Cannot solve inverse relation from {} to {}." + raise NotImplementedError(temp.format(sys1_name, sys2_name)) + elif len(ret) > 1: + temp = "Obtained multiple inverse relation from {} to {}." + raise ValueError(temp.format(sys1_name, sys2_name)) + + return ret[0] + + @classmethod + def _inverse_transformation(cls, sys1, sys2): + # Find the transformation relation from sys2 to sys1 + forward = sys1.transform(sys2) + inv_results = cls._solve_inverse(sys1.symbols, sys2.symbols, forward, + sys1.name, sys2.name) + signature = tuple(sys1.symbols) + return [inv_results[s] for s in signature] + + @classmethod + @cacheit + def _indirect_transformation(cls, sys1, sys2): + # Find the transformation relation between two indirectly connected + # coordinate systems + rel = sys1.relations + path = cls._dijkstra(sys1, sys2) + + transforms = [] + for s1, s2 in zip(path, path[1:]): + if (s1, s2) in rel: + transforms.append(rel[(s1, s2)]) + else: + sym2, inv_exprs = rel[(s2, s1)] + sym1 = tuple(Dummy() for i in sym2) + ret = cls._solve_inverse(sym2, sym1, inv_exprs, s2, s1) + ret = tuple(ret[s] for s in sym2) + transforms.append((sym1, ret)) + syms = sys1.args[2] + exprs = syms + for newsyms, newexprs in transforms: + exprs = tuple(e.subs(zip(newsyms, exprs)) for e in newexprs) + return exprs + + @staticmethod + def _dijkstra(sys1, sys2): + # Use Dijkstra algorithm to find the shortest path between two indirectly-connected + # coordinate systems + # return value is the list of the names of the systems. + relations = sys1.relations + graph = {} + for s1, s2 in relations.keys(): + if s1 not in graph: + graph[s1] = {s2} + else: + graph[s1].add(s2) + if s2 not in graph: + graph[s2] = {s1} + else: + graph[s2].add(s1) + + path_dict = {sys:[0, [], 0] for sys in graph} # minimum distance, path, times of visited + + def visit(sys): + path_dict[sys][2] = 1 + for newsys in graph[sys]: + distance = path_dict[sys][0] + 1 + if path_dict[newsys][0] >= distance or not path_dict[newsys][1]: + path_dict[newsys][0] = distance + path_dict[newsys][1] = list(path_dict[sys][1]) + path_dict[newsys][1].append(sys) + + visit(sys1.name) + + while True: + min_distance = max(path_dict.values(), key=lambda x:x[0])[0] + newsys = None + for sys, lst in path_dict.items(): + if 0 < lst[0] <= min_distance and not lst[2]: + min_distance = lst[0] + newsys = sys + if newsys is None: + break + visit(newsys) + + result = path_dict[sys2.name][1] + result.append(sys2.name) + + if result == [sys2.name]: + raise KeyError("Two coordinate systems are not connected.") + return result + + def connect_to(self, to_sys, from_coords, to_exprs, inverse=True, fill_in_gaps=False): + sympy_deprecation_warning( + """ + The CoordSystem.connect_to() method is deprecated. Instead, + generate a new instance of CoordSystem with the 'relations' + keyword argument (CoordSystem classes are now immutable). + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + ) + + from_coords, to_exprs = dummyfy(from_coords, to_exprs) + self.transforms[to_sys] = Matrix(from_coords), Matrix(to_exprs) + + if inverse: + to_sys.transforms[self] = self._inv_transf(from_coords, to_exprs) + + if fill_in_gaps: + self._fill_gaps_in_transformations() + + @staticmethod + def _inv_transf(from_coords, to_exprs): + # Will be removed when connect_to is removed + inv_from = [i.as_dummy() for i in from_coords] + inv_to = solve( + [t[0] - t[1] for t in zip(inv_from, to_exprs)], + list(from_coords), dict=True)[0] + inv_to = [inv_to[fc] for fc in from_coords] + return Matrix(inv_from), Matrix(inv_to) + + @staticmethod + def _fill_gaps_in_transformations(): + # Will be removed when connect_to is removed + raise NotImplementedError + + ########################################################################## + # Coordinate transformations + ########################################################################## + + def transform(self, sys, coordinates=None): + """ + Return the result of coordinate transformation from *self* to *sys*. + If coordinates are not given, coordinate symbols of *self* are used. + + Parameters + ========== + + sys : CoordSystem + + coordinates : Any iterable, optional. + + Returns + ======= + + sympy.ImmutableDenseMatrix containing CoordinateSymbol + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> R2_r.transform(R2_p) + Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]]) + >>> R2_r.transform(R2_p, [0, 1]) + Matrix([ + [ 1], + [pi/2]]) + + """ + if coordinates is None: + coordinates = self.symbols + if self != sys: + transf = self.transformation(sys) + coordinates = transf(*coordinates) + else: + coordinates = Matrix(coordinates) + return coordinates + + def coord_tuple_transform_to(self, to_sys, coords): + """Transform ``coords`` to coord system ``to_sys``.""" + sympy_deprecation_warning( + """ + The CoordSystem.coord_tuple_transform_to() method is deprecated. + Use the CoordSystem.transform() method instead. + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + ) + + coords = Matrix(coords) + if self != to_sys: + with ignore_warnings(SymPyDeprecationWarning): + transf = self.transforms[to_sys] + coords = transf[1].subs(list(zip(transf[0], coords))) + return coords + + def jacobian(self, sys, coordinates=None): + """ + Return the jacobian matrix of a transformation on given coordinates. + If coordinates are not given, coordinate symbols of *self* are used. + + Parameters + ========== + + sys : CoordSystem + + coordinates : Any iterable, optional. + + Returns + ======= + + sympy.ImmutableDenseMatrix + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> R2_p.jacobian(R2_r) + Matrix([ + [cos(theta), -rho*sin(theta)], + [sin(theta), rho*cos(theta)]]) + >>> R2_p.jacobian(R2_r, [1, 0]) + Matrix([ + [1, 0], + [0, 1]]) + + """ + result = self.transform(sys).jacobian(self.symbols) + if coordinates is not None: + result = result.subs(list(zip(self.symbols, coordinates))) + return result + jacobian_matrix = jacobian + + def jacobian_determinant(self, sys, coordinates=None): + """ + Return the jacobian determinant of a transformation on given + coordinates. If coordinates are not given, coordinate symbols of *self* + are used. + + Parameters + ========== + + sys : CoordSystem + + coordinates : Any iterable, optional. + + Returns + ======= + + sympy.Expr + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> R2_r.jacobian_determinant(R2_p) + 1/sqrt(x**2 + y**2) + >>> R2_r.jacobian_determinant(R2_p, [1, 0]) + 1 + + """ + return self.jacobian(sys, coordinates).det() + + + ########################################################################## + # Points + ########################################################################## + + def point(self, coords): + """Create a ``Point`` with coordinates given in this coord system.""" + return Point(self, coords) + + def point_to_coords(self, point): + """Calculate the coordinates of a point in this coord system.""" + return point.coords(self) + + ########################################################################## + # Base fields. + ########################################################################## + + def base_scalar(self, coord_index): + """Return ``BaseScalarField`` that takes a point and returns one of the coordinates.""" + return BaseScalarField(self, coord_index) + coord_function = base_scalar + + def base_scalars(self): + """Returns a list of all coordinate functions. + For more details see the ``base_scalar`` method of this class.""" + return [self.base_scalar(i) for i in range(self.dim)] + coord_functions = base_scalars + + def base_vector(self, coord_index): + """Return a basis vector field. + The basis vector field for this coordinate system. It is also an + operator on scalar fields.""" + return BaseVectorField(self, coord_index) + + def base_vectors(self): + """Returns a list of all base vectors. + For more details see the ``base_vector`` method of this class.""" + return [self.base_vector(i) for i in range(self.dim)] + + def base_oneform(self, coord_index): + """Return a basis 1-form field. + The basis one-form field for this coordinate system. It is also an + operator on vector fields.""" + return Differential(self.coord_function(coord_index)) + + def base_oneforms(self): + """Returns a list of all base oneforms. + For more details see the ``base_oneform`` method of this class.""" + return [self.base_oneform(i) for i in range(self.dim)] + + +class CoordinateSymbol(Symbol): + """A symbol which denotes an abstract value of i-th coordinate of + the coordinate system with given context. + + Explanation + =========== + + Each coordinates in coordinate system are represented by unique symbol, + such as x, y, z in Cartesian coordinate system. + + You may not construct this class directly. Instead, use `symbols` method + of CoordSystem. + + Parameters + ========== + + coord_sys : CoordSystem + + index : integer + + Examples + ======== + + >>> from sympy import symbols, Lambda, Matrix, sqrt, atan2, cos, sin + >>> from sympy.diffgeom import Manifold, Patch, CoordSystem + >>> m = Manifold('M', 2) + >>> p = Patch('P', m) + >>> x, y = symbols('x y', real=True) + >>> r, theta = symbols('r theta', nonnegative=True) + >>> relation_dict = { + ... ('Car2D', 'Pol'): Lambda((x, y), Matrix([sqrt(x**2 + y**2), atan2(y, x)])), + ... ('Pol', 'Car2D'): Lambda((r, theta), Matrix([r*cos(theta), r*sin(theta)])) + ... } + >>> Car2D = CoordSystem('Car2D', p, [x, y], relation_dict) + >>> Pol = CoordSystem('Pol', p, [r, theta], relation_dict) + >>> x, y = Car2D.symbols + + ``CoordinateSymbol`` contains its coordinate symbol and index. + + >>> x.name + 'x' + >>> x.coord_sys == Car2D + True + >>> x.index + 0 + >>> x.is_real + True + + You can transform ``CoordinateSymbol`` into other coordinate system using + ``rewrite()`` method. + + >>> x.rewrite(Pol) + r*cos(theta) + >>> sqrt(x**2 + y**2).rewrite(Pol).simplify() + r + + """ + def __new__(cls, coord_sys, index, **assumptions): + name = coord_sys.args[2][index].name + obj = super().__new__(cls, name, **assumptions) + obj.coord_sys = coord_sys + obj.index = index + return obj + + def __getnewargs__(self): + return (self.coord_sys, self.index) + + def _hashable_content(self): + return ( + self.coord_sys, self.index + ) + tuple(sorted(self.assumptions0.items())) + + def _eval_rewrite(self, rule, args, **hints): + if isinstance(rule, CoordSystem): + return rule.transform(self.coord_sys)[self.index] + return super()._eval_rewrite(rule, args, **hints) + + +class Point(Basic): + """Point defined in a coordinate system. + + Explanation + =========== + + Mathematically, point is defined in the manifold and does not have any coordinates + by itself. Coordinate system is what imbues the coordinates to the point by coordinate + chart. However, due to the difficulty of realizing such logic, you must supply + a coordinate system and coordinates to define a Point here. + + The usage of this object after its definition is independent of the + coordinate system that was used in order to define it, however due to + limitations in the simplification routines you can arrive at complicated + expressions if you use inappropriate coordinate systems. + + Parameters + ========== + + coord_sys : CoordSystem + + coords : list + The coordinates of the point. + + Examples + ======== + + >>> from sympy import pi + >>> from sympy.diffgeom import Point + >>> from sympy.diffgeom.rn import R2, R2_r, R2_p + >>> rho, theta = R2_p.symbols + + >>> p = Point(R2_p, [rho, 3*pi/4]) + + >>> p.manifold == R2 + True + + >>> p.coords() + Matrix([ + [ rho], + [3*pi/4]]) + >>> p.coords(R2_r) + Matrix([ + [-sqrt(2)*rho/2], + [ sqrt(2)*rho/2]]) + + """ + + def __new__(cls, coord_sys, coords, **kwargs): + coords = Matrix(coords) + obj = super().__new__(cls, coord_sys, coords) + obj._coord_sys = coord_sys + obj._coords = coords + return obj + + @property + def patch(self): + return self._coord_sys.patch + + @property + def manifold(self): + return self._coord_sys.manifold + + @property + def dim(self): + return self.manifold.dim + + def coords(self, sys=None): + """ + Coordinates of the point in given coordinate system. If coordinate system + is not passed, it returns the coordinates in the coordinate system in which + the point was defined. + """ + if sys is None: + return self._coords + else: + return self._coord_sys.transform(sys, self._coords) + + @property + def free_symbols(self): + return self._coords.free_symbols + + +class BaseScalarField(Expr): + """Base scalar field over a manifold for a given coordinate system. + + Explanation + =========== + + A scalar field takes a point as an argument and returns a scalar. + A base scalar field of a coordinate system takes a point and returns one of + the coordinates of that point in the coordinate system in question. + + To define a scalar field you need to choose the coordinate system and the + index of the coordinate. + + The use of the scalar field after its definition is independent of the + coordinate system in which it was defined, however due to limitations in + the simplification routines you may arrive at more complicated + expression if you use unappropriate coordinate systems. + You can build complicated scalar fields by just building up SymPy + expressions containing ``BaseScalarField`` instances. + + Parameters + ========== + + coord_sys : CoordSystem + + index : integer + + Examples + ======== + + >>> from sympy import Function, pi + >>> from sympy.diffgeom import BaseScalarField + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> rho, _ = R2_p.symbols + >>> point = R2_p.point([rho, 0]) + >>> fx, fy = R2_r.base_scalars() + >>> ftheta = BaseScalarField(R2_r, 1) + + >>> fx(point) + rho + >>> fy(point) + 0 + + >>> (fx**2+fy**2).rcall(point) + rho**2 + + >>> g = Function('g') + >>> fg = g(ftheta-pi) + >>> fg.rcall(point) + g(-pi) + + """ + + is_commutative = True + + def __new__(cls, coord_sys, index, **kwargs): + index = _sympify(index) + obj = super().__new__(cls, coord_sys, index) + obj._coord_sys = coord_sys + obj._index = index + return obj + + @property + def coord_sys(self): + return self.args[0] + + @property + def index(self): + return self.args[1] + + @property + def patch(self): + return self.coord_sys.patch + + @property + def manifold(self): + return self.coord_sys.manifold + + @property + def dim(self): + return self.manifold.dim + + def __call__(self, *args): + """Evaluating the field at a point or doing nothing. + If the argument is a ``Point`` instance, the field is evaluated at that + point. The field is returned itself if the argument is any other + object. It is so in order to have working recursive calling mechanics + for all fields (check the ``__call__`` method of ``Expr``). + """ + point = args[0] + if len(args) != 1 or not isinstance(point, Point): + return self + coords = point.coords(self._coord_sys) + # XXX Calling doit is necessary with all the Subs expressions + # XXX Calling simplify is necessary with all the trig expressions + return simplify(coords[self._index]).doit() + + # XXX Workaround for limitations on the content of args + free_symbols: set[Any] = set() + + +class BaseVectorField(Expr): + r"""Base vector field over a manifold for a given coordinate system. + + Explanation + =========== + + A vector field is an operator taking a scalar field and returning a + directional derivative (which is also a scalar field). + A base vector field is the same type of operator, however the derivation is + specifically done with respect to a chosen coordinate. + + To define a base vector field you need to choose the coordinate system and + the index of the coordinate. + + The use of the vector field after its definition is independent of the + coordinate system in which it was defined, however due to limitations in the + simplification routines you may arrive at more complicated expression if you + use unappropriate coordinate systems. + + Parameters + ========== + coord_sys : CoordSystem + + index : integer + + Examples + ======== + + >>> from sympy import Function + >>> from sympy.diffgeom.rn import R2_p, R2_r + >>> from sympy.diffgeom import BaseVectorField + >>> from sympy import pprint + + >>> x, y = R2_r.symbols + >>> rho, theta = R2_p.symbols + >>> fx, fy = R2_r.base_scalars() + >>> point_p = R2_p.point([rho, theta]) + >>> point_r = R2_r.point([x, y]) + + >>> g = Function('g') + >>> s_field = g(fx, fy) + + >>> v = BaseVectorField(R2_r, 1) + >>> pprint(v(s_field)) + / d \| + |---(g(x, xi))|| + \dxi /|xi=y + >>> pprint(v(s_field).rcall(point_r).doit()) + d + --(g(x, y)) + dy + >>> pprint(v(s_field).rcall(point_p)) + / d \| + |---(g(rho*cos(theta), xi))|| + \dxi /|xi=rho*sin(theta) + + """ + + is_commutative = False + + def __new__(cls, coord_sys, index, **kwargs): + index = _sympify(index) + obj = super().__new__(cls, coord_sys, index) + obj._coord_sys = coord_sys + obj._index = index + return obj + + @property + def coord_sys(self): + return self.args[0] + + @property + def index(self): + return self.args[1] + + @property + def patch(self): + return self.coord_sys.patch + + @property + def manifold(self): + return self.coord_sys.manifold + + @property + def dim(self): + return self.manifold.dim + + def __call__(self, scalar_field): + """Apply on a scalar field. + The action of a vector field on a scalar field is a directional + differentiation. + If the argument is not a scalar field an error is raised. + """ + if covariant_order(scalar_field) or contravariant_order(scalar_field): + raise ValueError('Only scalar fields can be supplied as arguments to vector fields.') + + if scalar_field is None: + return self + + base_scalars = list(scalar_field.atoms(BaseScalarField)) + + # First step: e_x(x+r**2) -> e_x(x) + 2*r*e_x(r) + d_var = self._coord_sys._dummy + # TODO: you need a real dummy function for the next line + d_funcs = [Function('_#_%s' % i)(d_var) for i, + b in enumerate(base_scalars)] + d_result = scalar_field.subs(list(zip(base_scalars, d_funcs))) + d_result = d_result.diff(d_var) + + # Second step: e_x(x) -> 1 and e_x(r) -> cos(atan2(x, y)) + coords = self._coord_sys.symbols + d_funcs_deriv = [f.diff(d_var) for f in d_funcs] + d_funcs_deriv_sub = [] + for b in base_scalars: + jac = self._coord_sys.jacobian(b._coord_sys, coords) + d_funcs_deriv_sub.append(jac[b._index, self._index]) + d_result = d_result.subs(list(zip(d_funcs_deriv, d_funcs_deriv_sub))) + + # Remove the dummies + result = d_result.subs(list(zip(d_funcs, base_scalars))) + result = result.subs(list(zip(coords, self._coord_sys.coord_functions()))) + return result.doit() + + +def _find_coords(expr): + # Finds CoordinateSystems existing in expr + fields = expr.atoms(BaseScalarField, BaseVectorField) + return {f._coord_sys for f in fields} + + +class Commutator(Expr): + r"""Commutator of two vector fields. + + Explanation + =========== + + The commutator of two vector fields `v_1` and `v_2` is defined as the + vector field `[v_1, v_2]` that evaluated on each scalar field `f` is equal + to `v_1(v_2(f)) - v_2(v_1(f))`. + + Examples + ======== + + + >>> from sympy.diffgeom.rn import R2_p, R2_r + >>> from sympy.diffgeom import Commutator + >>> from sympy import simplify + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> e_r = R2_p.base_vector(0) + + >>> c_xy = Commutator(e_x, e_y) + >>> c_xr = Commutator(e_x, e_r) + >>> c_xy + 0 + + Unfortunately, the current code is not able to compute everything: + + >>> c_xr + Commutator(e_x, e_rho) + >>> simplify(c_xr(fy**2)) + -2*cos(theta)*y**2/(x**2 + y**2) + + """ + def __new__(cls, v1, v2): + if (covariant_order(v1) or contravariant_order(v1) != 1 + or covariant_order(v2) or contravariant_order(v2) != 1): + raise ValueError( + 'Only commutators of vector fields are supported.') + if v1 == v2: + return S.Zero + coord_sys = set().union(*[_find_coords(v) for v in (v1, v2)]) + if len(coord_sys) == 1: + # Only one coordinate systems is used, hence it is easy enough to + # actually evaluate the commutator. + if all(isinstance(v, BaseVectorField) for v in (v1, v2)): + return S.Zero + bases_1, bases_2 = [list(v.atoms(BaseVectorField)) + for v in (v1, v2)] + coeffs_1 = [v1.expand().coeff(b) for b in bases_1] + coeffs_2 = [v2.expand().coeff(b) for b in bases_2] + res = 0 + for c1, b1 in zip(coeffs_1, bases_1): + for c2, b2 in zip(coeffs_2, bases_2): + res += c1*b1(c2)*b2 - c2*b2(c1)*b1 + return res + else: + obj = super().__new__(cls, v1, v2) + obj._v1 = v1 # deprecated assignment + obj._v2 = v2 # deprecated assignment + return obj + + @property + def v1(self): + return self.args[0] + + @property + def v2(self): + return self.args[1] + + def __call__(self, scalar_field): + """Apply on a scalar field. + If the argument is not a scalar field an error is raised. + """ + return self.v1(self.v2(scalar_field)) - self.v2(self.v1(scalar_field)) + + +class Differential(Expr): + r"""Return the differential (exterior derivative) of a form field. + + Explanation + =========== + + The differential of a form (i.e. the exterior derivative) has a complicated + definition in the general case. + The differential `df` of the 0-form `f` is defined for any vector field `v` + as `df(v) = v(f)`. + + Examples + ======== + + >>> from sympy import Function + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import Differential + >>> from sympy import pprint + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> g = Function('g') + >>> s_field = g(fx, fy) + >>> dg = Differential(s_field) + + >>> dg + d(g(x, y)) + >>> pprint(dg(e_x)) + / d \| + |---(g(xi, y))|| + \dxi /|xi=x + >>> pprint(dg(e_y)) + / d \| + |---(g(x, xi))|| + \dxi /|xi=y + + Applying the exterior derivative operator twice always results in: + + >>> Differential(dg) + 0 + """ + + is_commutative = False + + def __new__(cls, form_field): + if contravariant_order(form_field): + raise ValueError( + 'A vector field was supplied as an argument to Differential.') + if isinstance(form_field, Differential): + return S.Zero + else: + obj = super().__new__(cls, form_field) + obj._form_field = form_field # deprecated assignment + return obj + + @property + def form_field(self): + return self.args[0] + + def __call__(self, *vector_fields): + """Apply on a list of vector_fields. + + Explanation + =========== + + If the number of vector fields supplied is not equal to 1 + the order of + the form field inside the differential the result is undefined. + + For 1-forms (i.e. differentials of scalar fields) the evaluation is + done as `df(v)=v(f)`. However if `v` is ``None`` instead of a vector + field, the differential is returned unchanged. This is done in order to + permit partial contractions for higher forms. + + In the general case the evaluation is done by applying the form field + inside the differential on a list with one less elements than the number + of elements in the original list. Lowering the number of vector fields + is achieved through replacing each pair of fields by their + commutator. + + If the arguments are not vectors or ``None``s an error is raised. + """ + if any((contravariant_order(a) != 1 or covariant_order(a)) and a is not None + for a in vector_fields): + raise ValueError('The arguments supplied to Differential should be vector fields or Nones.') + k = len(vector_fields) + if k == 1: + if vector_fields[0]: + return vector_fields[0].rcall(self._form_field) + return self + else: + # For higher form it is more complicated: + # Invariant formula: + # https://en.wikipedia.org/wiki/Exterior_derivative#Invariant_formula + # df(v1, ... vn) = +/- vi(f(v1..no i..vn)) + # +/- f([vi,vj],v1..no i, no j..vn) + f = self._form_field + v = vector_fields + ret = 0 + for i in range(k): + t = v[i].rcall(f.rcall(*v[:i] + v[i + 1:])) + ret += (-1)**i*t + for j in range(i + 1, k): + c = Commutator(v[i], v[j]) + if c: # TODO this is ugly - the Commutator can be Zero and + # this causes the next line to fail + t = f.rcall(*(c,) + v[:i] + v[i + 1:j] + v[j + 1:]) + ret += (-1)**(i + j)*t + return ret + + +class TensorProduct(Expr): + """Tensor product of forms. + + Explanation + =========== + + The tensor product permits the creation of multilinear functionals (i.e. + higher order tensors) out of lower order fields (e.g. 1-forms and vector + fields). However, the higher tensors thus created lack the interesting + features provided by the other type of product, the wedge product, namely + they are not antisymmetric and hence are not form fields. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import TensorProduct + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + + >>> TensorProduct(dx, dy)(e_x, e_y) + 1 + >>> TensorProduct(dx, dy)(e_y, e_x) + 0 + >>> TensorProduct(dx, fx*dy)(fx*e_x, e_y) + x**2 + >>> TensorProduct(e_x, e_y)(fx**2, fy**2) + 4*x*y + >>> TensorProduct(e_y, dx)(fy) + dx + + You can nest tensor products. + + >>> tp1 = TensorProduct(dx, dy) + >>> TensorProduct(tp1, dx)(e_x, e_y, e_x) + 1 + + You can make partial contraction for instance when 'raising an index'. + Putting ``None`` in the second argument of ``rcall`` means that the + respective position in the tensor product is left as it is. + + >>> TP = TensorProduct + >>> metric = TP(dx, dx) + 3*TP(dy, dy) + >>> metric.rcall(e_y, None) + 3*dy + + Or automatically pad the args with ``None`` without specifying them. + + >>> metric.rcall(e_y) + 3*dy + + """ + def __new__(cls, *args): + scalar = Mul(*[m for m in args if covariant_order(m) + contravariant_order(m) == 0]) + multifields = [m for m in args if covariant_order(m) + contravariant_order(m)] + if multifields: + if len(multifields) == 1: + return scalar*multifields[0] + return scalar*super().__new__(cls, *multifields) + else: + return scalar + + def __call__(self, *fields): + """Apply on a list of fields. + + If the number of input fields supplied is not equal to the order of + the tensor product field, the list of arguments is padded with ``None``'s. + + The list of arguments is divided in sublists depending on the order of + the forms inside the tensor product. The sublists are provided as + arguments to these forms and the resulting expressions are given to the + constructor of ``TensorProduct``. + + """ + tot_order = covariant_order(self) + contravariant_order(self) + tot_args = len(fields) + if tot_args != tot_order: + fields = list(fields) + [None]*(tot_order - tot_args) + orders = [covariant_order(f) + contravariant_order(f) for f in self._args] + indices = [sum(orders[:i + 1]) for i in range(len(orders) - 1)] + fields = [fields[i:j] for i, j in zip([0] + indices, indices + [None])] + multipliers = [t[0].rcall(*t[1]) for t in zip(self._args, fields)] + return TensorProduct(*multipliers) + + +class WedgeProduct(TensorProduct): + """Wedge product of forms. + + Explanation + =========== + + In the context of integration only completely antisymmetric forms make + sense. The wedge product permits the creation of such forms. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import WedgeProduct + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + + >>> WedgeProduct(dx, dy)(e_x, e_y) + 1 + >>> WedgeProduct(dx, dy)(e_y, e_x) + -1 + >>> WedgeProduct(dx, fx*dy)(fx*e_x, e_y) + x**2 + >>> WedgeProduct(e_x, e_y)(fy, None) + -e_x + + You can nest wedge products. + + >>> wp1 = WedgeProduct(dx, dy) + >>> WedgeProduct(wp1, dx)(e_x, e_y, e_x) + 0 + + """ + # TODO the calculation of signatures is slow + # TODO you do not need all these permutations (neither the prefactor) + def __call__(self, *fields): + """Apply on a list of vector_fields. + The expression is rewritten internally in terms of tensor products and evaluated.""" + orders = (covariant_order(e) + contravariant_order(e) for e in self.args) + mul = 1/Mul(*(factorial(o) for o in orders)) + perms = permutations(fields) + perms_par = (Permutation( + p).signature() for p in permutations(range(len(fields)))) + tensor_prod = TensorProduct(*self.args) + return mul*Add(*[tensor_prod(*p[0])*p[1] for p in zip(perms, perms_par)]) + + +class LieDerivative(Expr): + """Lie derivative with respect to a vector field. + + Explanation + =========== + + The transport operator that defines the Lie derivative is the pushforward of + the field to be derived along the integral curve of the field with respect + to which one derives. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> from sympy.diffgeom import (LieDerivative, TensorProduct) + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> e_rho, e_theta = R2_p.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + + >>> LieDerivative(e_x, fy) + 0 + >>> LieDerivative(e_x, fx) + 1 + >>> LieDerivative(e_x, e_x) + 0 + + The Lie derivative of a tensor field by another tensor field is equal to + their commutator: + + >>> LieDerivative(e_x, e_rho) + Commutator(e_x, e_rho) + >>> LieDerivative(e_x + e_y, fx) + 1 + + >>> tp = TensorProduct(dx, dy) + >>> LieDerivative(e_x, tp) + LieDerivative(e_x, TensorProduct(dx, dy)) + >>> LieDerivative(e_x, tp) + LieDerivative(e_x, TensorProduct(dx, dy)) + + """ + def __new__(cls, v_field, expr): + expr_form_ord = covariant_order(expr) + if contravariant_order(v_field) != 1 or covariant_order(v_field): + raise ValueError('Lie derivatives are defined only with respect to' + ' vector fields. The supplied argument was not a ' + 'vector field.') + if expr_form_ord > 0: + obj = super().__new__(cls, v_field, expr) + # deprecated assignments + obj._v_field = v_field + obj._expr = expr + return obj + if expr.atoms(BaseVectorField): + return Commutator(v_field, expr) + else: + return v_field.rcall(expr) + + @property + def v_field(self): + return self.args[0] + + @property + def expr(self): + return self.args[1] + + def __call__(self, *args): + v = self.v_field + expr = self.expr + lead_term = v(expr(*args)) + rest = Add(*[Mul(*args[:i] + (Commutator(v, args[i]),) + args[i + 1:]) + for i in range(len(args))]) + return lead_term - rest + + +class BaseCovarDerivativeOp(Expr): + """Covariant derivative operator with respect to a base vector. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import BaseCovarDerivativeOp + >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct + + >>> TP = TensorProduct + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + + >>> ch = metric_to_Christoffel_2nd(TP(dx, dx) + TP(dy, dy)) + >>> ch + [[[0, 0], [0, 0]], [[0, 0], [0, 0]]] + >>> cvd = BaseCovarDerivativeOp(R2_r, 0, ch) + >>> cvd(fx) + 1 + >>> cvd(fx*e_x) + e_x + """ + + def __new__(cls, coord_sys, index, christoffel): + index = _sympify(index) + christoffel = ImmutableDenseNDimArray(christoffel) + obj = super().__new__(cls, coord_sys, index, christoffel) + # deprecated assignments + obj._coord_sys = coord_sys + obj._index = index + obj._christoffel = christoffel + return obj + + @property + def coord_sys(self): + return self.args[0] + + @property + def index(self): + return self.args[1] + + @property + def christoffel(self): + return self.args[2] + + def __call__(self, field): + """Apply on a scalar field. + + The action of a vector field on a scalar field is a directional + differentiation. + If the argument is not a scalar field the behaviour is undefined. + """ + if covariant_order(field) != 0: + raise NotImplementedError() + + field = vectors_in_basis(field, self._coord_sys) + + wrt_vector = self._coord_sys.base_vector(self._index) + wrt_scalar = self._coord_sys.coord_function(self._index) + vectors = list(field.atoms(BaseVectorField)) + + # First step: replace all vectors with something susceptible to + # derivation and do the derivation + # TODO: you need a real dummy function for the next line + d_funcs = [Function('_#_%s' % i)(wrt_scalar) for i, + b in enumerate(vectors)] + d_result = field.subs(list(zip(vectors, d_funcs))) + d_result = wrt_vector(d_result) + + # Second step: backsubstitute the vectors in + d_result = d_result.subs(list(zip(d_funcs, vectors))) + + # Third step: evaluate the derivatives of the vectors + derivs = [] + for v in vectors: + d = Add(*[(self._christoffel[k, wrt_vector._index, v._index] + *v._coord_sys.base_vector(k)) + for k in range(v._coord_sys.dim)]) + derivs.append(d) + to_subs = [wrt_vector(d) for d in d_funcs] + # XXX: This substitution can fail when there are Dummy symbols and the + # cache is disabled: https://github.com/sympy/sympy/issues/17794 + result = d_result.subs(list(zip(to_subs, derivs))) + + # Remove the dummies + result = result.subs(list(zip(d_funcs, vectors))) + return result.doit() + + +class CovarDerivativeOp(Expr): + """Covariant derivative operator. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import CovarDerivativeOp + >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct + >>> TP = TensorProduct + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + >>> ch = metric_to_Christoffel_2nd(TP(dx, dx) + TP(dy, dy)) + + >>> ch + [[[0, 0], [0, 0]], [[0, 0], [0, 0]]] + >>> cvd = CovarDerivativeOp(fx*e_x, ch) + >>> cvd(fx) + x + >>> cvd(fx*e_x) + x*e_x + + """ + + def __new__(cls, wrt, christoffel): + if len({v._coord_sys for v in wrt.atoms(BaseVectorField)}) > 1: + raise NotImplementedError() + if contravariant_order(wrt) != 1 or covariant_order(wrt): + raise ValueError('Covariant derivatives are defined only with ' + 'respect to vector fields. The supplied argument ' + 'was not a vector field.') + christoffel = ImmutableDenseNDimArray(christoffel) + obj = super().__new__(cls, wrt, christoffel) + # deprecated assignments + obj._wrt = wrt + obj._christoffel = christoffel + return obj + + @property + def wrt(self): + return self.args[0] + + @property + def christoffel(self): + return self.args[1] + + def __call__(self, field): + vectors = list(self._wrt.atoms(BaseVectorField)) + base_ops = [BaseCovarDerivativeOp(v._coord_sys, v._index, self._christoffel) + for v in vectors] + return self._wrt.subs(list(zip(vectors, base_ops))).rcall(field) + + +############################################################################### +# Integral curves on vector fields +############################################################################### +def intcurve_series(vector_field, param, start_point, n=6, coord_sys=None, coeffs=False): + r"""Return the series expansion for an integral curve of the field. + + Explanation + =========== + + Integral curve is a function `\gamma` taking a parameter in `R` to a point + in the manifold. It verifies the equation: + + `V(f)\big(\gamma(t)\big) = \frac{d}{dt}f\big(\gamma(t)\big)` + + where the given ``vector_field`` is denoted as `V`. This holds for any + value `t` for the parameter and any scalar field `f`. + + This equation can also be decomposed of a basis of coordinate functions + `V(f_i)\big(\gamma(t)\big) = \frac{d}{dt}f_i\big(\gamma(t)\big) \quad \forall i` + + This function returns a series expansion of `\gamma(t)` in terms of the + coordinate system ``coord_sys``. The equations and expansions are necessarily + done in coordinate-system-dependent way as there is no other way to + represent movement between points on the manifold (i.e. there is no such + thing as a difference of points for a general manifold). + + Parameters + ========== + vector_field + the vector field for which an integral curve will be given + + param + the argument of the function `\gamma` from R to the curve + + start_point + the point which corresponds to `\gamma(0)` + + n + the order to which to expand + + coord_sys + the coordinate system in which to expand + coeffs (default False) - if True return a list of elements of the expansion + + Examples + ======== + + Use the predefined R2 manifold: + + >>> from sympy.abc import t, x, y + >>> from sympy.diffgeom.rn import R2_p, R2_r + >>> from sympy.diffgeom import intcurve_series + + Specify a starting point and a vector field: + + >>> start_point = R2_r.point([x, y]) + >>> vector_field = R2_r.e_x + + Calculate the series: + + >>> intcurve_series(vector_field, t, start_point, n=3) + Matrix([ + [t + x], + [ y]]) + + Or get the elements of the expansion in a list: + + >>> series = intcurve_series(vector_field, t, start_point, n=3, coeffs=True) + >>> series[0] + Matrix([ + [x], + [y]]) + >>> series[1] + Matrix([ + [t], + [0]]) + >>> series[2] + Matrix([ + [0], + [0]]) + + The series in the polar coordinate system: + + >>> series = intcurve_series(vector_field, t, start_point, + ... n=3, coord_sys=R2_p, coeffs=True) + >>> series[0] + Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]]) + >>> series[1] + Matrix([ + [t*x/sqrt(x**2 + y**2)], + [ -t*y/(x**2 + y**2)]]) + >>> series[2] + Matrix([ + [t**2*(-x**2/(x**2 + y**2)**(3/2) + 1/sqrt(x**2 + y**2))/2], + [ t**2*x*y/(x**2 + y**2)**2]]) + + See Also + ======== + + intcurve_diffequ + + """ + if contravariant_order(vector_field) != 1 or covariant_order(vector_field): + raise ValueError('The supplied field was not a vector field.') + + def iter_vfield(scalar_field, i): + """Return ``vector_field`` called `i` times on ``scalar_field``.""" + return reduce(lambda s, v: v.rcall(s), [vector_field, ]*i, scalar_field) + + def taylor_terms_per_coord(coord_function): + """Return the series for one of the coordinates.""" + return [param**i*iter_vfield(coord_function, i).rcall(start_point)/factorial(i) + for i in range(n)] + coord_sys = coord_sys if coord_sys else start_point._coord_sys + coord_functions = coord_sys.coord_functions() + taylor_terms = [taylor_terms_per_coord(f) for f in coord_functions] + if coeffs: + return [Matrix(t) for t in zip(*taylor_terms)] + else: + return Matrix([sum(c) for c in taylor_terms]) + + +def intcurve_diffequ(vector_field, param, start_point, coord_sys=None): + r"""Return the differential equation for an integral curve of the field. + + Explanation + =========== + + Integral curve is a function `\gamma` taking a parameter in `R` to a point + in the manifold. It verifies the equation: + + `V(f)\big(\gamma(t)\big) = \frac{d}{dt}f\big(\gamma(t)\big)` + + where the given ``vector_field`` is denoted as `V`. This holds for any + value `t` for the parameter and any scalar field `f`. + + This function returns the differential equation of `\gamma(t)` in terms of the + coordinate system ``coord_sys``. The equations and expansions are necessarily + done in coordinate-system-dependent way as there is no other way to + represent movement between points on the manifold (i.e. there is no such + thing as a difference of points for a general manifold). + + Parameters + ========== + + vector_field + the vector field for which an integral curve will be given + + param + the argument of the function `\gamma` from R to the curve + + start_point + the point which corresponds to `\gamma(0)` + + coord_sys + the coordinate system in which to give the equations + + Returns + ======= + + a tuple of (equations, initial conditions) + + Examples + ======== + + Use the predefined R2 manifold: + + >>> from sympy.abc import t + >>> from sympy.diffgeom.rn import R2, R2_p, R2_r + >>> from sympy.diffgeom import intcurve_diffequ + + Specify a starting point and a vector field: + + >>> start_point = R2_r.point([0, 1]) + >>> vector_field = -R2.y*R2.e_x + R2.x*R2.e_y + + Get the equation: + + >>> equations, init_cond = intcurve_diffequ(vector_field, t, start_point) + >>> equations + [f_1(t) + Derivative(f_0(t), t), -f_0(t) + Derivative(f_1(t), t)] + >>> init_cond + [f_0(0), f_1(0) - 1] + + The series in the polar coordinate system: + + >>> equations, init_cond = intcurve_diffequ(vector_field, t, start_point, R2_p) + >>> equations + [Derivative(f_0(t), t), Derivative(f_1(t), t) - 1] + >>> init_cond + [f_0(0) - 1, f_1(0) - pi/2] + + See Also + ======== + + intcurve_series + + """ + if contravariant_order(vector_field) != 1 or covariant_order(vector_field): + raise ValueError('The supplied field was not a vector field.') + coord_sys = coord_sys if coord_sys else start_point._coord_sys + gammas = [Function('f_%d' % i)(param) for i in range( + start_point._coord_sys.dim)] + arbitrary_p = Point(coord_sys, gammas) + coord_functions = coord_sys.coord_functions() + equations = [simplify(diff(cf.rcall(arbitrary_p), param) - vector_field.rcall(cf).rcall(arbitrary_p)) + for cf in coord_functions] + init_cond = [simplify(cf.rcall(arbitrary_p).subs(param, 0) - cf.rcall(start_point)) + for cf in coord_functions] + return equations, init_cond + + +############################################################################### +# Helpers +############################################################################### +def dummyfy(args, exprs): + # TODO Is this a good idea? + d_args = Matrix([s.as_dummy() for s in args]) + reps = dict(zip(args, d_args)) + d_exprs = Matrix([_sympify(expr).subs(reps) for expr in exprs]) + return d_args, d_exprs + +############################################################################### +# Helpers +############################################################################### +def contravariant_order(expr, _strict=False): + """Return the contravariant order of an expression. + + Examples + ======== + + >>> from sympy.diffgeom import contravariant_order + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.abc import a + + >>> contravariant_order(a) + 0 + >>> contravariant_order(a*R2.x + 2) + 0 + >>> contravariant_order(a*R2.x*R2.e_y + R2.e_x) + 1 + + """ + # TODO move some of this to class methods. + # TODO rewrite using the .as_blah_blah methods + if isinstance(expr, Add): + orders = [contravariant_order(e) for e in expr.args] + if len(set(orders)) != 1: + raise ValueError('Misformed expression containing contravariant fields of varying order.') + return orders[0] + elif isinstance(expr, Mul): + orders = [contravariant_order(e) for e in expr.args] + not_zero = [o for o in orders if o != 0] + if len(not_zero) > 1: + raise ValueError('Misformed expression containing multiplication between vectors.') + return 0 if not not_zero else not_zero[0] + elif isinstance(expr, Pow): + if covariant_order(expr.base) or covariant_order(expr.exp): + raise ValueError( + 'Misformed expression containing a power of a vector.') + return 0 + elif isinstance(expr, BaseVectorField): + return 1 + elif isinstance(expr, TensorProduct): + return sum(contravariant_order(a) for a in expr.args) + elif not _strict or expr.atoms(BaseScalarField): + return 0 + else: # If it does not contain anything related to the diffgeom module and it is _strict + return -1 + + +def covariant_order(expr, _strict=False): + """Return the covariant order of an expression. + + Examples + ======== + + >>> from sympy.diffgeom import covariant_order + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.abc import a + + >>> covariant_order(a) + 0 + >>> covariant_order(a*R2.x + 2) + 0 + >>> covariant_order(a*R2.x*R2.dy + R2.dx) + 1 + + """ + # TODO move some of this to class methods. + # TODO rewrite using the .as_blah_blah methods + if isinstance(expr, Add): + orders = [covariant_order(e) for e in expr.args] + if len(set(orders)) != 1: + raise ValueError('Misformed expression containing form fields of varying order.') + return orders[0] + elif isinstance(expr, Mul): + orders = [covariant_order(e) for e in expr.args] + not_zero = [o for o in orders if o != 0] + if len(not_zero) > 1: + raise ValueError('Misformed expression containing multiplication between forms.') + return 0 if not not_zero else not_zero[0] + elif isinstance(expr, Pow): + if covariant_order(expr.base) or covariant_order(expr.exp): + raise ValueError( + 'Misformed expression containing a power of a form.') + return 0 + elif isinstance(expr, Differential): + return covariant_order(*expr.args) + 1 + elif isinstance(expr, TensorProduct): + return sum(covariant_order(a) for a in expr.args) + elif not _strict or expr.atoms(BaseScalarField): + return 0 + else: # If it does not contain anything related to the diffgeom module and it is _strict + return -1 + + +############################################################################### +# Coordinate transformation functions +############################################################################### +def vectors_in_basis(expr, to_sys): + """Transform all base vectors in base vectors of a specified coord basis. + While the new base vectors are in the new coordinate system basis, any + coefficients are kept in the old system. + + Examples + ======== + + >>> from sympy.diffgeom import vectors_in_basis + >>> from sympy.diffgeom.rn import R2_r, R2_p + + >>> vectors_in_basis(R2_r.e_x, R2_p) + -y*e_theta/(x**2 + y**2) + x*e_rho/sqrt(x**2 + y**2) + >>> vectors_in_basis(R2_p.e_r, R2_r) + sin(theta)*e_y + cos(theta)*e_x + + """ + vectors = list(expr.atoms(BaseVectorField)) + new_vectors = [] + for v in vectors: + cs = v._coord_sys + jac = cs.jacobian(to_sys, cs.coord_functions()) + new = (jac.T*Matrix(to_sys.base_vectors()))[v._index] + new_vectors.append(new) + return expr.subs(list(zip(vectors, new_vectors))) + + +############################################################################### +# Coordinate-dependent functions +############################################################################### +def twoform_to_matrix(expr): + """Return the matrix representing the twoform. + + For the twoform `w` return the matrix `M` such that `M[i,j]=w(e_i, e_j)`, + where `e_i` is the i-th base vector field for the coordinate system in + which the expression of `w` is given. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import twoform_to_matrix, TensorProduct + >>> TP = TensorProduct + + >>> twoform_to_matrix(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + Matrix([ + [1, 0], + [0, 1]]) + >>> twoform_to_matrix(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + Matrix([ + [x, 0], + [0, 1]]) + >>> twoform_to_matrix(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy) - TP(R2.dx, R2.dy)/2) + Matrix([ + [ 1, 0], + [-1/2, 1]]) + + """ + if covariant_order(expr) != 2 or contravariant_order(expr): + raise ValueError('The input expression is not a two-form.') + coord_sys = _find_coords(expr) + if len(coord_sys) != 1: + raise ValueError('The input expression concerns more than one ' + 'coordinate systems, hence there is no unambiguous ' + 'way to choose a coordinate system for the matrix.') + coord_sys = coord_sys.pop() + vectors = coord_sys.base_vectors() + expr = expr.expand() + matrix_content = [[expr.rcall(v1, v2) for v1 in vectors] + for v2 in vectors] + return Matrix(matrix_content) + + +def metric_to_Christoffel_1st(expr): + """Return the nested list of Christoffel symbols for the given metric. + This returns the Christoffel symbol of first kind that represents the + Levi-Civita connection for the given metric. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import metric_to_Christoffel_1st, TensorProduct + >>> TP = TensorProduct + + >>> metric_to_Christoffel_1st(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[0, 0], [0, 0]], [[0, 0], [0, 0]]] + >>> metric_to_Christoffel_1st(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[1/2, 0], [0, 0]], [[0, 0], [0, 0]]] + + """ + matrix = twoform_to_matrix(expr) + if not matrix.is_symmetric(): + raise ValueError( + 'The two-form representing the metric is not symmetric.') + coord_sys = _find_coords(expr).pop() + deriv_matrices = [matrix.applyfunc(d) for d in coord_sys.base_vectors()] + indices = list(range(coord_sys.dim)) + christoffel = [[[(deriv_matrices[k][i, j] + deriv_matrices[j][i, k] - deriv_matrices[i][j, k])/2 + for k in indices] + for j in indices] + for i in indices] + return ImmutableDenseNDimArray(christoffel) + + +def metric_to_Christoffel_2nd(expr): + """Return the nested list of Christoffel symbols for the given metric. + This returns the Christoffel symbol of second kind that represents the + Levi-Civita connection for the given metric. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct + >>> TP = TensorProduct + + >>> metric_to_Christoffel_2nd(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[0, 0], [0, 0]], [[0, 0], [0, 0]]] + >>> metric_to_Christoffel_2nd(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[1/(2*x), 0], [0, 0]], [[0, 0], [0, 0]]] + + """ + ch_1st = metric_to_Christoffel_1st(expr) + coord_sys = _find_coords(expr).pop() + indices = list(range(coord_sys.dim)) + # XXX workaround, inverting a matrix does not work if it contains non + # symbols + #matrix = twoform_to_matrix(expr).inv() + matrix = twoform_to_matrix(expr) + s_fields = set() + for e in matrix: + s_fields.update(e.atoms(BaseScalarField)) + s_fields = list(s_fields) + dums = coord_sys.symbols + matrix = matrix.subs(list(zip(s_fields, dums))).inv().subs(list(zip(dums, s_fields))) + # XXX end of workaround + christoffel = [[[Add(*[matrix[i, l]*ch_1st[l, j, k] for l in indices]) + for k in indices] + for j in indices] + for i in indices] + return ImmutableDenseNDimArray(christoffel) + + +def metric_to_Riemann_components(expr): + """Return the components of the Riemann tensor expressed in a given basis. + + Given a metric it calculates the components of the Riemann tensor in the + canonical basis of the coordinate system in which the metric expression is + given. + + Examples + ======== + + >>> from sympy import exp + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import metric_to_Riemann_components, TensorProduct + >>> TP = TensorProduct + + >>> metric_to_Riemann_components(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[[0, 0], [0, 0]], [[0, 0], [0, 0]]], [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]] + >>> non_trivial_metric = exp(2*R2.r)*TP(R2.dr, R2.dr) + \ + R2.r**2*TP(R2.dtheta, R2.dtheta) + >>> non_trivial_metric + exp(2*rho)*TensorProduct(drho, drho) + rho**2*TensorProduct(dtheta, dtheta) + >>> riemann = metric_to_Riemann_components(non_trivial_metric) + >>> riemann[0, :, :, :] + [[[0, 0], [0, 0]], [[0, exp(-2*rho)*rho], [-exp(-2*rho)*rho, 0]]] + >>> riemann[1, :, :, :] + [[[0, -1/rho], [1/rho, 0]], [[0, 0], [0, 0]]] + + """ + ch_2nd = metric_to_Christoffel_2nd(expr) + coord_sys = _find_coords(expr).pop() + indices = list(range(coord_sys.dim)) + deriv_ch = [[[[d(ch_2nd[i, j, k]) + for d in coord_sys.base_vectors()] + for k in indices] + for j in indices] + for i in indices] + riemann_a = [[[[deriv_ch[rho][sig][nu][mu] - deriv_ch[rho][sig][mu][nu] + for nu in indices] + for mu in indices] + for sig in indices] + for rho in indices] + riemann_b = [[[[Add(*[ch_2nd[rho, l, mu]*ch_2nd[l, sig, nu] - ch_2nd[rho, l, nu]*ch_2nd[l, sig, mu] for l in indices]) + for nu in indices] + for mu in indices] + for sig in indices] + for rho in indices] + riemann = [[[[riemann_a[rho][sig][mu][nu] + riemann_b[rho][sig][mu][nu] + for nu in indices] + for mu in indices] + for sig in indices] + for rho in indices] + return ImmutableDenseNDimArray(riemann) + + +def metric_to_Ricci_components(expr): + + """Return the components of the Ricci tensor expressed in a given basis. + + Given a metric it calculates the components of the Ricci tensor in the + canonical basis of the coordinate system in which the metric expression is + given. + + Examples + ======== + + >>> from sympy import exp + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import metric_to_Ricci_components, TensorProduct + >>> TP = TensorProduct + + >>> metric_to_Ricci_components(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[0, 0], [0, 0]] + >>> non_trivial_metric = exp(2*R2.r)*TP(R2.dr, R2.dr) + \ + R2.r**2*TP(R2.dtheta, R2.dtheta) + >>> non_trivial_metric + exp(2*rho)*TensorProduct(drho, drho) + rho**2*TensorProduct(dtheta, dtheta) + >>> metric_to_Ricci_components(non_trivial_metric) + [[1/rho, 0], [0, exp(-2*rho)*rho]] + + """ + riemann = metric_to_Riemann_components(expr) + coord_sys = _find_coords(expr).pop() + indices = list(range(coord_sys.dim)) + ricci = [[Add(*[riemann[k, i, k, j] for k in indices]) + for j in indices] + for i in indices] + return ImmutableDenseNDimArray(ricci) + +############################################################################### +# Classes for deprecation +############################################################################### + +class _deprecated_container: + # This class gives deprecation warning. + # When deprecated features are completely deleted, this should be removed as well. + # See https://github.com/sympy/sympy/pull/19368 + def __init__(self, message, data): + super().__init__(data) + self.message = message + + def warn(self): + sympy_deprecation_warning( + self.message, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + stacklevel=4 + ) + + def __iter__(self): + self.warn() + return super().__iter__() + + def __getitem__(self, key): + self.warn() + return super().__getitem__(key) + + def __contains__(self, key): + self.warn() + return super().__contains__(key) + + +class _deprecated_list(_deprecated_container, list): + pass + + +class _deprecated_dict(_deprecated_container, dict): + pass + + +# Import at end to avoid cyclic imports +from sympy.simplify.simplify import simplify diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/rn.py b/venv/lib/python3.10/site-packages/sympy/diffgeom/rn.py new file mode 100644 index 0000000000000000000000000000000000000000..897c7e82bc804d260612f79c820af92632f3b281 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/diffgeom/rn.py @@ -0,0 +1,143 @@ +"""Predefined R^n manifolds together with common coord. systems. + +Coordinate systems are predefined as well as the transformation laws between +them. + +Coordinate functions can be accessed as attributes of the manifold (eg `R2.x`), +as attributes of the coordinate systems (eg `R2_r.x` and `R2_p.theta`), or by +using the usual `coord_sys.coord_function(index, name)` interface. +""" + +from typing import Any +import warnings + +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, atan2, cos, sin) +from .diffgeom import Manifold, Patch, CoordSystem + +__all__ = [ + 'R2', 'R2_origin', 'relations_2d', 'R2_r', 'R2_p', + 'R3', 'R3_origin', 'relations_3d', 'R3_r', 'R3_c', 'R3_s' +] + +############################################################################### +# R2 +############################################################################### +R2: Any = Manifold('R^2', 2) + +R2_origin: Any = Patch('origin', R2) + +x, y = symbols('x y', real=True) +r, theta = symbols('rho theta', nonnegative=True) + +relations_2d = { + ('rectangular', 'polar'): [(x, y), (sqrt(x**2 + y**2), atan2(y, x))], + ('polar', 'rectangular'): [(r, theta), (r*cos(theta), r*sin(theta))], +} + +R2_r: Any = CoordSystem('rectangular', R2_origin, (x, y), relations_2d) +R2_p: Any = CoordSystem('polar', R2_origin, (r, theta), relations_2d) + +# support deprecated feature +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + x, y, r, theta = symbols('x y r theta', cls=Dummy) + R2_r.connect_to(R2_p, [x, y], + [sqrt(x**2 + y**2), atan2(y, x)], + inverse=False, fill_in_gaps=False) + R2_p.connect_to(R2_r, [r, theta], + [r*cos(theta), r*sin(theta)], + inverse=False, fill_in_gaps=False) + +# Defining the basis coordinate functions and adding shortcuts for them to the +# manifold and the patch. +R2.x, R2.y = R2_origin.x, R2_origin.y = R2_r.x, R2_r.y = R2_r.coord_functions() +R2.r, R2.theta = R2_origin.r, R2_origin.theta = R2_p.r, R2_p.theta = R2_p.coord_functions() + +# Defining the basis vector fields and adding shortcuts for them to the +# manifold and the patch. +R2.e_x, R2.e_y = R2_origin.e_x, R2_origin.e_y = R2_r.e_x, R2_r.e_y = R2_r.base_vectors() +R2.e_r, R2.e_theta = R2_origin.e_r, R2_origin.e_theta = R2_p.e_r, R2_p.e_theta = R2_p.base_vectors() + +# Defining the basis oneform fields and adding shortcuts for them to the +# manifold and the patch. +R2.dx, R2.dy = R2_origin.dx, R2_origin.dy = R2_r.dx, R2_r.dy = R2_r.base_oneforms() +R2.dr, R2.dtheta = R2_origin.dr, R2_origin.dtheta = R2_p.dr, R2_p.dtheta = R2_p.base_oneforms() + +############################################################################### +# R3 +############################################################################### +R3: Any = Manifold('R^3', 3) + +R3_origin: Any = Patch('origin', R3) + +x, y, z = symbols('x y z', real=True) +rho, psi, r, theta, phi = symbols('rho psi r theta phi', nonnegative=True) + +relations_3d = { + ('rectangular', 'cylindrical'): [(x, y, z), + (sqrt(x**2 + y**2), atan2(y, x), z)], + ('cylindrical', 'rectangular'): [(rho, psi, z), + (rho*cos(psi), rho*sin(psi), z)], + ('rectangular', 'spherical'): [(x, y, z), + (sqrt(x**2 + y**2 + z**2), + acos(z/sqrt(x**2 + y**2 + z**2)), + atan2(y, x))], + ('spherical', 'rectangular'): [(r, theta, phi), + (r*sin(theta)*cos(phi), + r*sin(theta)*sin(phi), + r*cos(theta))], + ('cylindrical', 'spherical'): [(rho, psi, z), + (sqrt(rho**2 + z**2), + acos(z/sqrt(rho**2 + z**2)), + psi)], + ('spherical', 'cylindrical'): [(r, theta, phi), + (r*sin(theta), phi, r*cos(theta))], +} + +R3_r: Any = CoordSystem('rectangular', R3_origin, (x, y, z), relations_3d) +R3_c: Any = CoordSystem('cylindrical', R3_origin, (rho, psi, z), relations_3d) +R3_s: Any = CoordSystem('spherical', R3_origin, (r, theta, phi), relations_3d) + +# support deprecated feature +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + x, y, z, rho, psi, r, theta, phi = symbols('x y z rho psi r theta phi', cls=Dummy) + R3_r.connect_to(R3_c, [x, y, z], + [sqrt(x**2 + y**2), atan2(y, x), z], + inverse=False, fill_in_gaps=False) + R3_c.connect_to(R3_r, [rho, psi, z], + [rho*cos(psi), rho*sin(psi), z], + inverse=False, fill_in_gaps=False) + ## rectangular <-> spherical + R3_r.connect_to(R3_s, [x, y, z], + [sqrt(x**2 + y**2 + z**2), acos(z/ + sqrt(x**2 + y**2 + z**2)), atan2(y, x)], + inverse=False, fill_in_gaps=False) + R3_s.connect_to(R3_r, [r, theta, phi], + [r*sin(theta)*cos(phi), r*sin( + theta)*sin(phi), r*cos(theta)], + inverse=False, fill_in_gaps=False) + ## cylindrical <-> spherical + R3_c.connect_to(R3_s, [rho, psi, z], + [sqrt(rho**2 + z**2), acos(z/sqrt(rho**2 + z**2)), psi], + inverse=False, fill_in_gaps=False) + R3_s.connect_to(R3_c, [r, theta, phi], + [r*sin(theta), phi, r*cos(theta)], + inverse=False, fill_in_gaps=False) + +# Defining the basis coordinate functions. +R3_r.x, R3_r.y, R3_r.z = R3_r.coord_functions() +R3_c.rho, R3_c.psi, R3_c.z = R3_c.coord_functions() +R3_s.r, R3_s.theta, R3_s.phi = R3_s.coord_functions() + +# Defining the basis vector fields. +R3_r.e_x, R3_r.e_y, R3_r.e_z = R3_r.base_vectors() +R3_c.e_rho, R3_c.e_psi, R3_c.e_z = R3_c.base_vectors() +R3_s.e_r, R3_s.e_theta, R3_s.e_phi = R3_s.base_vectors() + +# Defining the basis oneform fields. +R3_r.dx, R3_r.dy, R3_r.dz = R3_r.base_oneforms() +R3_c.drho, R3_c.dpsi, R3_c.dz = R3_c.base_oneforms() +R3_s.dr, R3_s.dtheta, R3_s.dphi = R3_s.base_oneforms() diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62fec1d0bf9c7ece1d79668d79a3b666bf4c4c4e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_class_structure.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_class_structure.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52485ae89c15334fa43ca1a39d2dd3250d49274a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_class_structure.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_diffgeom.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_diffgeom.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b14633d74d0b3ea63d5ff69307014acff4f1285a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_diffgeom.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_function_diffgeom_book.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_function_diffgeom_book.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b70c3f06ec6353f4dd6c6efafd3c954bc9461019 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_function_diffgeom_book.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_hyperbolic_space.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_hyperbolic_space.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7fd1c07fa98c68091325846b4cd39ae87171f709 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/__pycache__/test_hyperbolic_space.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_class_structure.py b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_class_structure.py new file mode 100644 index 0000000000000000000000000000000000000000..c649fd9fcb9acdf1f410a021966c6e0fee62cc2b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_class_structure.py @@ -0,0 +1,33 @@ +from sympy.diffgeom import Manifold, Patch, CoordSystem, Point +from sympy.core.function import Function +from sympy.core.symbol import symbols +from sympy.testing.pytest import warns_deprecated_sympy + +m = Manifold('m', 2) +p = Patch('p', m) +a, b = symbols('a b') +cs = CoordSystem('cs', p, [a, b]) +x, y = symbols('x y') +f = Function('f') +s1, s2 = cs.coord_functions() +v1, v2 = cs.base_vectors() +f1, f2 = cs.base_oneforms() + +def test_point(): + point = Point(cs, [x, y]) + assert point != Point(cs, [2, y]) + #TODO assert point.subs(x, 2) == Point(cs, [2, y]) + #TODO assert point.free_symbols == set([x, y]) + +def test_subs(): + assert s1.subs(s1, s2) == s2 + assert v1.subs(v1, v2) == v2 + assert f1.subs(f1, f2) == f2 + assert (x*f(s1) + y).subs(s1, s2) == x*f(s2) + y + assert (f(s1)*v1).subs(v1, v2) == f(s1)*v2 + assert (y*f(s1)*f1).subs(f1, f2) == y*f(s1)*f2 + +def test_deprecated(): + with warns_deprecated_sympy(): + cs_wname = CoordSystem('cs', p, ['a', 'b']) + assert cs_wname == cs_wname.func(*cs_wname.args) diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_diffgeom.py b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_diffgeom.py new file mode 100644 index 0000000000000000000000000000000000000000..7c3c9265785896b8f4ffa3a2b41816ca90579758 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_diffgeom.py @@ -0,0 +1,342 @@ +from sympy.core import Lambda, Symbol, symbols +from sympy.diffgeom.rn import R2, R2_p, R2_r, R3_r, R3_c, R3_s, R2_origin +from sympy.diffgeom import (Manifold, Patch, CoordSystem, Commutator, Differential, TensorProduct, + WedgeProduct, BaseCovarDerivativeOp, CovarDerivativeOp, LieDerivative, + covariant_order, contravariant_order, twoform_to_matrix, metric_to_Christoffel_1st, + metric_to_Christoffel_2nd, metric_to_Riemann_components, + metric_to_Ricci_components, intcurve_diffequ, intcurve_series) +from sympy.simplify import trigsimp, simplify +from sympy.functions import sqrt, atan2, sin +from sympy.matrices import Matrix +from sympy.testing.pytest import raises, nocache_fail +from sympy.testing.pytest import warns_deprecated_sympy + +TP = TensorProduct + + +def test_coordsys_transform(): + # test inverse transforms + p, q, r, s = symbols('p q r s') + rel = {('first', 'second'): [(p, q), (q, -p)]} + R2_pq = CoordSystem('first', R2_origin, [p, q], rel) + R2_rs = CoordSystem('second', R2_origin, [r, s], rel) + r, s = R2_rs.symbols + assert R2_rs.transform(R2_pq) == Matrix([[-s], [r]]) + + # inverse transform impossible case + a, b = symbols('a b', positive=True) + rel = {('first', 'second'): [(a,), (-a,)]} + R2_a = CoordSystem('first', R2_origin, [a], rel) + R2_b = CoordSystem('second', R2_origin, [b], rel) + # This transformation is uninvertible because there is no positive a, b satisfying a = -b + with raises(NotImplementedError): + R2_b.transform(R2_a) + + # inverse transform ambiguous case + c, d = symbols('c d') + rel = {('first', 'second'): [(c,), (c**2,)]} + R2_c = CoordSystem('first', R2_origin, [c], rel) + R2_d = CoordSystem('second', R2_origin, [d], rel) + # The transform method should throw if it finds multiple inverses for a coordinate transformation. + with raises(ValueError): + R2_d.transform(R2_c) + + # test indirect transformation + a, b, c, d, e, f = symbols('a, b, c, d, e, f') + rel = {('C1', 'C2'): [(a, b), (2*a, 3*b)], + ('C2', 'C3'): [(c, d), (3*c, 2*d)]} + C1 = CoordSystem('C1', R2_origin, (a, b), rel) + C2 = CoordSystem('C2', R2_origin, (c, d), rel) + C3 = CoordSystem('C3', R2_origin, (e, f), rel) + a, b = C1.symbols + c, d = C2.symbols + e, f = C3.symbols + assert C2.transform(C1) == Matrix([c/2, d/3]) + assert C1.transform(C3) == Matrix([6*a, 6*b]) + assert C3.transform(C1) == Matrix([e/6, f/6]) + assert C3.transform(C2) == Matrix([e/3, f/2]) + + a, b, c, d, e, f = symbols('a, b, c, d, e, f') + rel = {('C1', 'C2'): [(a, b), (2*a, 3*b + 1)], + ('C3', 'C2'): [(e, f), (-e - 2, 2*f)]} + C1 = CoordSystem('C1', R2_origin, (a, b), rel) + C2 = CoordSystem('C2', R2_origin, (c, d), rel) + C3 = CoordSystem('C3', R2_origin, (e, f), rel) + a, b = C1.symbols + c, d = C2.symbols + e, f = C3.symbols + assert C2.transform(C1) == Matrix([c/2, (d - 1)/3]) + assert C1.transform(C3) == Matrix([-2*a - 2, (3*b + 1)/2]) + assert C3.transform(C1) == Matrix([-e/2 - 1, (2*f - 1)/3]) + assert C3.transform(C2) == Matrix([-e - 2, 2*f]) + + # old signature uses Lambda + a, b, c, d, e, f = symbols('a, b, c, d, e, f') + rel = {('C1', 'C2'): Lambda((a, b), (2*a, 3*b + 1)), + ('C3', 'C2'): Lambda((e, f), (-e - 2, 2*f))} + C1 = CoordSystem('C1', R2_origin, (a, b), rel) + C2 = CoordSystem('C2', R2_origin, (c, d), rel) + C3 = CoordSystem('C3', R2_origin, (e, f), rel) + a, b = C1.symbols + c, d = C2.symbols + e, f = C3.symbols + assert C2.transform(C1) == Matrix([c/2, (d - 1)/3]) + assert C1.transform(C3) == Matrix([-2*a - 2, (3*b + 1)/2]) + assert C3.transform(C1) == Matrix([-e/2 - 1, (2*f - 1)/3]) + assert C3.transform(C2) == Matrix([-e - 2, 2*f]) + + +def test_R2(): + x0, y0, r0, theta0 = symbols('x0, y0, r0, theta0', real=True) + point_r = R2_r.point([x0, y0]) + point_p = R2_p.point([r0, theta0]) + + # r**2 = x**2 + y**2 + assert (R2.r**2 - R2.x**2 - R2.y**2).rcall(point_r) == 0 + assert trigsimp( (R2.r**2 - R2.x**2 - R2.y**2).rcall(point_p) ) == 0 + assert trigsimp(R2.e_r(R2.x**2 + R2.y**2).rcall(point_p).doit()) == 2*r0 + + # polar->rect->polar == Id + a, b = symbols('a b', positive=True) + m = Matrix([[a], [b]]) + + #TODO assert m == R2_r.transform(R2_p, R2_p.transform(R2_r, [a, b])).applyfunc(simplify) + assert m == R2_p.transform(R2_r, R2_r.transform(R2_p, m)).applyfunc(simplify) + + # deprecated method + with warns_deprecated_sympy(): + assert m == R2_p.coord_tuple_transform_to( + R2_r, R2_r.coord_tuple_transform_to(R2_p, m)).applyfunc(simplify) + + +def test_R3(): + a, b, c = symbols('a b c', positive=True) + m = Matrix([[a], [b], [c]]) + + assert m == R3_c.transform(R3_r, R3_r.transform(R3_c, m)).applyfunc(simplify) + #TODO assert m == R3_r.transform(R3_c, R3_c.transform(R3_r, m)).applyfunc(simplify) + assert m == R3_s.transform( + R3_r, R3_r.transform(R3_s, m)).applyfunc(simplify) + #TODO assert m == R3_r.transform(R3_s, R3_s.transform(R3_r, m)).applyfunc(simplify) + assert m == R3_s.transform( + R3_c, R3_c.transform(R3_s, m)).applyfunc(simplify) + #TODO assert m == R3_c.transform(R3_s, R3_s.transform(R3_c, m)).applyfunc(simplify) + + with warns_deprecated_sympy(): + assert m == R3_c.coord_tuple_transform_to( + R3_r, R3_r.coord_tuple_transform_to(R3_c, m)).applyfunc(simplify) + #TODO assert m == R3_r.coord_tuple_transform_to(R3_c, R3_c.coord_tuple_transform_to(R3_r, m)).applyfunc(simplify) + assert m == R3_s.coord_tuple_transform_to( + R3_r, R3_r.coord_tuple_transform_to(R3_s, m)).applyfunc(simplify) + #TODO assert m == R3_r.coord_tuple_transform_to(R3_s, R3_s.coord_tuple_transform_to(R3_r, m)).applyfunc(simplify) + assert m == R3_s.coord_tuple_transform_to( + R3_c, R3_c.coord_tuple_transform_to(R3_s, m)).applyfunc(simplify) + #TODO assert m == R3_c.coord_tuple_transform_to(R3_s, R3_s.coord_tuple_transform_to(R3_c, m)).applyfunc(simplify) + + +def test_CoordinateSymbol(): + x, y = R2_r.symbols + r, theta = R2_p.symbols + assert y.rewrite(R2_p) == r*sin(theta) + + +def test_point(): + x, y = symbols('x, y') + p = R2_r.point([x, y]) + assert p.free_symbols == {x, y} + assert p.coords(R2_r) == p.coords() == Matrix([x, y]) + assert p.coords(R2_p) == Matrix([sqrt(x**2 + y**2), atan2(y, x)]) + + +def test_commutator(): + assert Commutator(R2.e_x, R2.e_y) == 0 + assert Commutator(R2.x*R2.e_x, R2.x*R2.e_x) == 0 + assert Commutator(R2.x*R2.e_x, R2.x*R2.e_y) == R2.x*R2.e_y + c = Commutator(R2.e_x, R2.e_r) + assert c(R2.x) == R2.y*(R2.x**2 + R2.y**2)**(-1)*sin(R2.theta) + + +def test_differential(): + xdy = R2.x*R2.dy + dxdy = Differential(xdy) + assert xdy.rcall(None) == xdy + assert dxdy(R2.e_x, R2.e_y) == 1 + assert dxdy(R2.e_x, R2.x*R2.e_y) == R2.x + assert Differential(dxdy) == 0 + + +def test_products(): + assert TensorProduct( + R2.dx, R2.dy)(R2.e_x, R2.e_y) == R2.dx(R2.e_x)*R2.dy(R2.e_y) == 1 + assert TensorProduct(R2.dx, R2.dy)(None, R2.e_y) == R2.dx + assert TensorProduct(R2.dx, R2.dy)(R2.e_x, None) == R2.dy + assert TensorProduct(R2.dx, R2.dy)(R2.e_x) == R2.dy + assert TensorProduct(R2.x, R2.dx) == R2.x*R2.dx + assert TensorProduct( + R2.e_x, R2.e_y)(R2.x, R2.y) == R2.e_x(R2.x) * R2.e_y(R2.y) == 1 + assert TensorProduct(R2.e_x, R2.e_y)(None, R2.y) == R2.e_x + assert TensorProduct(R2.e_x, R2.e_y)(R2.x, None) == R2.e_y + assert TensorProduct(R2.e_x, R2.e_y)(R2.x) == R2.e_y + assert TensorProduct(R2.x, R2.e_x) == R2.x * R2.e_x + assert TensorProduct( + R2.dx, R2.e_y)(R2.e_x, R2.y) == R2.dx(R2.e_x) * R2.e_y(R2.y) == 1 + assert TensorProduct(R2.dx, R2.e_y)(None, R2.y) == R2.dx + assert TensorProduct(R2.dx, R2.e_y)(R2.e_x, None) == R2.e_y + assert TensorProduct(R2.dx, R2.e_y)(R2.e_x) == R2.e_y + assert TensorProduct(R2.x, R2.e_x) == R2.x * R2.e_x + assert TensorProduct( + R2.e_x, R2.dy)(R2.x, R2.e_y) == R2.e_x(R2.x) * R2.dy(R2.e_y) == 1 + assert TensorProduct(R2.e_x, R2.dy)(None, R2.e_y) == R2.e_x + assert TensorProduct(R2.e_x, R2.dy)(R2.x, None) == R2.dy + assert TensorProduct(R2.e_x, R2.dy)(R2.x) == R2.dy + assert TensorProduct(R2.e_y,R2.e_x)(R2.x**2 + R2.y**2,R2.x**2 + R2.y**2) == 4*R2.x*R2.y + + assert WedgeProduct(R2.dx, R2.dy)(R2.e_x, R2.e_y) == 1 + assert WedgeProduct(R2.e_x, R2.e_y)(R2.x, R2.y) == 1 + + +def test_lie_derivative(): + assert LieDerivative(R2.e_x, R2.y) == R2.e_x(R2.y) == 0 + assert LieDerivative(R2.e_x, R2.x) == R2.e_x(R2.x) == 1 + assert LieDerivative(R2.e_x, R2.e_x) == Commutator(R2.e_x, R2.e_x) == 0 + assert LieDerivative(R2.e_x, R2.e_r) == Commutator(R2.e_x, R2.e_r) + assert LieDerivative(R2.e_x + R2.e_y, R2.x) == 1 + assert LieDerivative( + R2.e_x, TensorProduct(R2.dx, R2.dy))(R2.e_x, R2.e_y) == 0 + + +@nocache_fail +def test_covar_deriv(): + ch = metric_to_Christoffel_2nd(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + cvd = BaseCovarDerivativeOp(R2_r, 0, ch) + assert cvd(R2.x) == 1 + # This line fails if the cache is disabled: + assert cvd(R2.x*R2.e_x) == R2.e_x + cvd = CovarDerivativeOp(R2.x*R2.e_x, ch) + assert cvd(R2.x) == R2.x + assert cvd(R2.x*R2.e_x) == R2.x*R2.e_x + + +def test_intcurve_diffequ(): + t = symbols('t') + start_point = R2_r.point([1, 0]) + vector_field = -R2.y*R2.e_x + R2.x*R2.e_y + equations, init_cond = intcurve_diffequ(vector_field, t, start_point) + assert str(equations) == '[f_1(t) + Derivative(f_0(t), t), -f_0(t) + Derivative(f_1(t), t)]' + assert str(init_cond) == '[f_0(0) - 1, f_1(0)]' + equations, init_cond = intcurve_diffequ(vector_field, t, start_point, R2_p) + assert str( + equations) == '[Derivative(f_0(t), t), Derivative(f_1(t), t) - 1]' + assert str(init_cond) == '[f_0(0) - 1, f_1(0)]' + + +def test_helpers_and_coordinate_dependent(): + one_form = R2.dr + R2.dx + two_form = Differential(R2.x*R2.dr + R2.r*R2.dx) + three_form = Differential( + R2.y*two_form) + Differential(R2.x*Differential(R2.r*R2.dr)) + metric = TensorProduct(R2.dx, R2.dx) + TensorProduct(R2.dy, R2.dy) + metric_ambig = TensorProduct(R2.dx, R2.dx) + TensorProduct(R2.dr, R2.dr) + misform_a = TensorProduct(R2.dr, R2.dr) + R2.dr + misform_b = R2.dr**4 + misform_c = R2.dx*R2.dy + twoform_not_sym = TensorProduct(R2.dx, R2.dx) + TensorProduct(R2.dx, R2.dy) + twoform_not_TP = WedgeProduct(R2.dx, R2.dy) + + one_vector = R2.e_x + R2.e_y + two_vector = TensorProduct(R2.e_x, R2.e_y) + three_vector = TensorProduct(R2.e_x, R2.e_y, R2.e_x) + two_wp = WedgeProduct(R2.e_x,R2.e_y) + + assert covariant_order(one_form) == 1 + assert covariant_order(two_form) == 2 + assert covariant_order(three_form) == 3 + assert covariant_order(two_form + metric) == 2 + assert covariant_order(two_form + metric_ambig) == 2 + assert covariant_order(two_form + twoform_not_sym) == 2 + assert covariant_order(two_form + twoform_not_TP) == 2 + + assert contravariant_order(one_vector) == 1 + assert contravariant_order(two_vector) == 2 + assert contravariant_order(three_vector) == 3 + assert contravariant_order(two_vector + two_wp) == 2 + + raises(ValueError, lambda: covariant_order(misform_a)) + raises(ValueError, lambda: covariant_order(misform_b)) + raises(ValueError, lambda: covariant_order(misform_c)) + + assert twoform_to_matrix(metric) == Matrix([[1, 0], [0, 1]]) + assert twoform_to_matrix(twoform_not_sym) == Matrix([[1, 0], [1, 0]]) + assert twoform_to_matrix(twoform_not_TP) == Matrix([[0, -1], [1, 0]]) + + raises(ValueError, lambda: twoform_to_matrix(one_form)) + raises(ValueError, lambda: twoform_to_matrix(three_form)) + raises(ValueError, lambda: twoform_to_matrix(metric_ambig)) + + raises(ValueError, lambda: metric_to_Christoffel_1st(twoform_not_sym)) + raises(ValueError, lambda: metric_to_Christoffel_2nd(twoform_not_sym)) + raises(ValueError, lambda: metric_to_Riemann_components(twoform_not_sym)) + raises(ValueError, lambda: metric_to_Ricci_components(twoform_not_sym)) + + +def test_correct_arguments(): + raises(ValueError, lambda: R2.e_x(R2.e_x)) + raises(ValueError, lambda: R2.e_x(R2.dx)) + + raises(ValueError, lambda: Commutator(R2.e_x, R2.x)) + raises(ValueError, lambda: Commutator(R2.dx, R2.e_x)) + + raises(ValueError, lambda: Differential(Differential(R2.e_x))) + + raises(ValueError, lambda: R2.dx(R2.x)) + + raises(ValueError, lambda: LieDerivative(R2.dx, R2.dx)) + raises(ValueError, lambda: LieDerivative(R2.x, R2.dx)) + + raises(ValueError, lambda: CovarDerivativeOp(R2.dx, [])) + raises(ValueError, lambda: CovarDerivativeOp(R2.x, [])) + + a = Symbol('a') + raises(ValueError, lambda: intcurve_series(R2.dx, a, R2_r.point([1, 2]))) + raises(ValueError, lambda: intcurve_series(R2.x, a, R2_r.point([1, 2]))) + + raises(ValueError, lambda: intcurve_diffequ(R2.dx, a, R2_r.point([1, 2]))) + raises(ValueError, lambda: intcurve_diffequ(R2.x, a, R2_r.point([1, 2]))) + + raises(ValueError, lambda: contravariant_order(R2.e_x + R2.dx)) + raises(ValueError, lambda: covariant_order(R2.e_x + R2.dx)) + + raises(ValueError, lambda: contravariant_order(R2.e_x*R2.e_y)) + raises(ValueError, lambda: covariant_order(R2.dx*R2.dy)) + +def test_simplify(): + x, y = R2_r.coord_functions() + dx, dy = R2_r.base_oneforms() + ex, ey = R2_r.base_vectors() + assert simplify(x) == x + assert simplify(x*y) == x*y + assert simplify(dx*dy) == dx*dy + assert simplify(ex*ey) == ex*ey + assert ((1-x)*dx)/(1-x)**2 == dx/(1-x) + + +def test_issue_17917(): + X = R2.x*R2.e_x - R2.y*R2.e_y + Y = (R2.x**2 + R2.y**2)*R2.e_x - R2.x*R2.y*R2.e_y + assert LieDerivative(X, Y).expand() == ( + R2.x**2*R2.e_x - 3*R2.y**2*R2.e_x - R2.x*R2.y*R2.e_y) + +def test_deprecations(): + m = Manifold('M', 2) + p = Patch('P', m) + with warns_deprecated_sympy(): + CoordSystem('Car2d', p, names=['x', 'y']) + + with warns_deprecated_sympy(): + c = CoordSystem('Car2d', p, ['x', 'y']) + + with warns_deprecated_sympy(): + list(m.patches) + + with warns_deprecated_sympy(): + list(c.transforms) diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_function_diffgeom_book.py b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_function_diffgeom_book.py new file mode 100644 index 0000000000000000000000000000000000000000..44d9623bc34ab73c7d575d9d9fd5b6d84f8e4a94 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_function_diffgeom_book.py @@ -0,0 +1,145 @@ +from sympy.diffgeom.rn import R2, R2_p, R2_r, R3_r +from sympy.diffgeom import intcurve_series, Differential, WedgeProduct +from sympy.core import symbols, Function, Derivative +from sympy.simplify import trigsimp, simplify +from sympy.functions import sqrt, atan2, sin, cos +from sympy.matrices import Matrix + +# Most of the functionality is covered in the +# test_functional_diffgeom_ch* tests which are based on the +# example from the paper of Sussman and Wisdom. +# If they do not cover something, additional tests are added in other test +# functions. + +# From "Functional Differential Geometry" as of 2011 +# by Sussman and Wisdom. + + +def test_functional_diffgeom_ch2(): + x0, y0, r0, theta0 = symbols('x0, y0, r0, theta0', real=True) + x, y = symbols('x, y', real=True) + f = Function('f') + + assert (R2_p.point_to_coords(R2_r.point([x0, y0])) == + Matrix([sqrt(x0**2 + y0**2), atan2(y0, x0)])) + assert (R2_r.point_to_coords(R2_p.point([r0, theta0])) == + Matrix([r0*cos(theta0), r0*sin(theta0)])) + + assert R2_p.jacobian(R2_r, [r0, theta0]) == Matrix( + [[cos(theta0), -r0*sin(theta0)], [sin(theta0), r0*cos(theta0)]]) + + field = f(R2.x, R2.y) + p1_in_rect = R2_r.point([x0, y0]) + p1_in_polar = R2_p.point([sqrt(x0**2 + y0**2), atan2(y0, x0)]) + assert field.rcall(p1_in_rect) == f(x0, y0) + assert field.rcall(p1_in_polar) == f(x0, y0) + + p_r = R2_r.point([x0, y0]) + p_p = R2_p.point([r0, theta0]) + assert R2.x(p_r) == x0 + assert R2.x(p_p) == r0*cos(theta0) + assert R2.r(p_p) == r0 + assert R2.r(p_r) == sqrt(x0**2 + y0**2) + assert R2.theta(p_r) == atan2(y0, x0) + + h = R2.x*R2.r**2 + R2.y**3 + assert h.rcall(p_r) == x0*(x0**2 + y0**2) + y0**3 + assert h.rcall(p_p) == r0**3*sin(theta0)**3 + r0**3*cos(theta0) + + +def test_functional_diffgeom_ch3(): + x0, y0 = symbols('x0, y0', real=True) + x, y, t = symbols('x, y, t', real=True) + f = Function('f') + b1 = Function('b1') + b2 = Function('b2') + p_r = R2_r.point([x0, y0]) + + s_field = f(R2.x, R2.y) + v_field = b1(R2.x)*R2.e_x + b2(R2.y)*R2.e_y + assert v_field.rcall(s_field).rcall(p_r).doit() == b1( + x0)*Derivative(f(x0, y0), x0) + b2(y0)*Derivative(f(x0, y0), y0) + + assert R2.e_x(R2.r**2).rcall(p_r) == 2*x0 + v = R2.e_x + 2*R2.e_y + s = R2.r**2 + 3*R2.x + assert v.rcall(s).rcall(p_r).doit() == 2*x0 + 4*y0 + 3 + + circ = -R2.y*R2.e_x + R2.x*R2.e_y + series = intcurve_series(circ, t, R2_r.point([1, 0]), coeffs=True) + series_x, series_y = zip(*series) + assert all( + term == cos(t).taylor_term(i, t) for i, term in enumerate(series_x)) + assert all( + term == sin(t).taylor_term(i, t) for i, term in enumerate(series_y)) + + +def test_functional_diffgeom_ch4(): + x0, y0, theta0 = symbols('x0, y0, theta0', real=True) + x, y, r, theta = symbols('x, y, r, theta', real=True) + r0 = symbols('r0', positive=True) + f = Function('f') + b1 = Function('b1') + b2 = Function('b2') + p_r = R2_r.point([x0, y0]) + p_p = R2_p.point([r0, theta0]) + + f_field = b1(R2.x, R2.y)*R2.dx + b2(R2.x, R2.y)*R2.dy + assert f_field.rcall(R2.e_x).rcall(p_r) == b1(x0, y0) + assert f_field.rcall(R2.e_y).rcall(p_r) == b2(x0, y0) + + s_field_r = f(R2.x, R2.y) + df = Differential(s_field_r) + assert df(R2.e_x).rcall(p_r).doit() == Derivative(f(x0, y0), x0) + assert df(R2.e_y).rcall(p_r).doit() == Derivative(f(x0, y0), y0) + + s_field_p = f(R2.r, R2.theta) + df = Differential(s_field_p) + assert trigsimp(df(R2.e_x).rcall(p_p).doit()) == ( + cos(theta0)*Derivative(f(r0, theta0), r0) - + sin(theta0)*Derivative(f(r0, theta0), theta0)/r0) + assert trigsimp(df(R2.e_y).rcall(p_p).doit()) == ( + sin(theta0)*Derivative(f(r0, theta0), r0) + + cos(theta0)*Derivative(f(r0, theta0), theta0)/r0) + + assert R2.dx(R2.e_x).rcall(p_r) == 1 + assert R2.dx(R2.e_x) == 1 + assert R2.dx(R2.e_y).rcall(p_r) == 0 + assert R2.dx(R2.e_y) == 0 + + circ = -R2.y*R2.e_x + R2.x*R2.e_y + assert R2.dx(circ).rcall(p_r).doit() == -y0 + assert R2.dy(circ).rcall(p_r) == x0 + assert R2.dr(circ).rcall(p_r) == 0 + assert simplify(R2.dtheta(circ).rcall(p_r)) == 1 + + assert (circ - R2.e_theta).rcall(s_field_r).rcall(p_r) == 0 + + +def test_functional_diffgeom_ch6(): + u0, u1, u2, v0, v1, v2, w0, w1, w2 = symbols('u0:3, v0:3, w0:3', real=True) + + u = u0*R2.e_x + u1*R2.e_y + v = v0*R2.e_x + v1*R2.e_y + wp = WedgeProduct(R2.dx, R2.dy) + assert wp(u, v) == u0*v1 - u1*v0 + + u = u0*R3_r.e_x + u1*R3_r.e_y + u2*R3_r.e_z + v = v0*R3_r.e_x + v1*R3_r.e_y + v2*R3_r.e_z + w = w0*R3_r.e_x + w1*R3_r.e_y + w2*R3_r.e_z + wp = WedgeProduct(R3_r.dx, R3_r.dy, R3_r.dz) + assert wp( + u, v, w) == Matrix(3, 3, [u0, u1, u2, v0, v1, v2, w0, w1, w2]).det() + + a, b, c = symbols('a, b, c', cls=Function) + a_f = a(R3_r.x, R3_r.y, R3_r.z) + b_f = b(R3_r.x, R3_r.y, R3_r.z) + c_f = c(R3_r.x, R3_r.y, R3_r.z) + theta = a_f*R3_r.dx + b_f*R3_r.dy + c_f*R3_r.dz + dtheta = Differential(theta) + da = Differential(a_f) + db = Differential(b_f) + dc = Differential(c_f) + expr = dtheta - WedgeProduct( + da, R3_r.dx) - WedgeProduct(db, R3_r.dy) - WedgeProduct(dc, R3_r.dz) + assert expr.rcall(R3_r.e_x, R3_r.e_y) == 0 diff --git a/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_hyperbolic_space.py b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_hyperbolic_space.py new file mode 100644 index 0000000000000000000000000000000000000000..48ddc7f8065f2b69bcd8eca4726a21c5901514ec --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/diffgeom/tests/test_hyperbolic_space.py @@ -0,0 +1,91 @@ +r''' +unit test describing the hyperbolic half-plane with the Poincare metric. This +is a basic model of hyperbolic geometry on the (positive) half-space + +{(x,y) \in R^2 | y > 0} + +with the Riemannian metric + +ds^2 = (dx^2 + dy^2)/y^2 + +It has constant negative scalar curvature = -2 + +https://en.wikipedia.org/wiki/Poincare_half-plane_model +''' +from sympy.matrices.dense import diag +from sympy.diffgeom import (twoform_to_matrix, + metric_to_Christoffel_1st, metric_to_Christoffel_2nd, + metric_to_Riemann_components, metric_to_Ricci_components) +import sympy.diffgeom.rn +from sympy.tensor.array import ImmutableDenseNDimArray + + +def test_H2(): + TP = sympy.diffgeom.TensorProduct + R2 = sympy.diffgeom.rn.R2 + y = R2.y + dy = R2.dy + dx = R2.dx + g = (TP(dx, dx) + TP(dy, dy))*y**(-2) + automat = twoform_to_matrix(g) + mat = diag(y**(-2), y**(-2)) + assert mat == automat + + gamma1 = metric_to_Christoffel_1st(g) + assert gamma1[0, 0, 0] == 0 + assert gamma1[0, 0, 1] == -y**(-3) + assert gamma1[0, 1, 0] == -y**(-3) + assert gamma1[0, 1, 1] == 0 + + assert gamma1[1, 1, 1] == -y**(-3) + assert gamma1[1, 1, 0] == 0 + assert gamma1[1, 0, 1] == 0 + assert gamma1[1, 0, 0] == y**(-3) + + gamma2 = metric_to_Christoffel_2nd(g) + assert gamma2[0, 0, 0] == 0 + assert gamma2[0, 0, 1] == -y**(-1) + assert gamma2[0, 1, 0] == -y**(-1) + assert gamma2[0, 1, 1] == 0 + + assert gamma2[1, 1, 1] == -y**(-1) + assert gamma2[1, 1, 0] == 0 + assert gamma2[1, 0, 1] == 0 + assert gamma2[1, 0, 0] == y**(-1) + + Rm = metric_to_Riemann_components(g) + assert Rm[0, 0, 0, 0] == 0 + assert Rm[0, 0, 0, 1] == 0 + assert Rm[0, 0, 1, 0] == 0 + assert Rm[0, 0, 1, 1] == 0 + + assert Rm[0, 1, 0, 0] == 0 + assert Rm[0, 1, 0, 1] == -y**(-2) + assert Rm[0, 1, 1, 0] == y**(-2) + assert Rm[0, 1, 1, 1] == 0 + + assert Rm[1, 0, 0, 0] == 0 + assert Rm[1, 0, 0, 1] == y**(-2) + assert Rm[1, 0, 1, 0] == -y**(-2) + assert Rm[1, 0, 1, 1] == 0 + + assert Rm[1, 1, 0, 0] == 0 + assert Rm[1, 1, 0, 1] == 0 + assert Rm[1, 1, 1, 0] == 0 + assert Rm[1, 1, 1, 1] == 0 + + Ric = metric_to_Ricci_components(g) + assert Ric[0, 0] == -y**(-2) + assert Ric[0, 1] == 0 + assert Ric[1, 0] == 0 + assert Ric[0, 0] == -y**(-2) + + assert Ric == ImmutableDenseNDimArray([-y**(-2), 0, 0, -y**(-2)], (2, 2)) + + ## scalar curvature is -2 + #TODO - it would be nice to have index contraction built-in + R = (Ric[0, 0] + Ric[1, 1])*y**2 + assert R == -2 + + ## Gauss curvature is -1 + assert R/2 == -1 diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__init__.py b/venv/lib/python3.10/site-packages/sympy/matrices/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..37b558f3f03f149dae6af20254e9b88192f7f1ed --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/__init__.py @@ -0,0 +1,72 @@ +"""A module that handles matrices. + +Includes functions for fast creating matrices like zero, one/eye, random +matrix, etc. +""" +from .exceptions import ShapeError, NonSquareMatrixError +from .kind import MatrixKind +from .dense import ( + GramSchmidt, casoratian, diag, eye, hessian, jordan_cell, + list2numpy, matrix2numpy, matrix_multiply_elementwise, ones, + randMatrix, rot_axis1, rot_axis2, rot_axis3, rot_ccw_axis1, + rot_ccw_axis2, rot_ccw_axis3, rot_givens, + symarray, wronskian, zeros) +from .dense import MutableDenseMatrix +from .matrixbase import DeferredVector, MatrixBase + +MutableMatrix = MutableDenseMatrix +Matrix = MutableMatrix + +from .sparse import MutableSparseMatrix +from .sparsetools import banded +from .immutable import ImmutableDenseMatrix, ImmutableSparseMatrix + +ImmutableMatrix = ImmutableDenseMatrix +SparseMatrix = MutableSparseMatrix + +from .expressions import ( + MatrixSlice, BlockDiagMatrix, BlockMatrix, FunctionMatrix, Identity, + Inverse, MatAdd, MatMul, MatPow, MatrixExpr, MatrixSymbol, Trace, + Transpose, ZeroMatrix, OneMatrix, blockcut, block_collapse, matrix_symbols, Adjoint, + hadamard_product, HadamardProduct, HadamardPower, Determinant, det, + diagonalize_vector, DiagMatrix, DiagonalMatrix, DiagonalOf, trace, + DotProduct, kronecker_product, KroneckerProduct, + PermutationMatrix, MatrixPermute, MatrixSet, Permanent, per) + +from .utilities import dotprodsimp + +__all__ = [ + 'ShapeError', 'NonSquareMatrixError', 'MatrixKind', + + 'GramSchmidt', 'casoratian', 'diag', 'eye', 'hessian', 'jordan_cell', + 'list2numpy', 'matrix2numpy', 'matrix_multiply_elementwise', 'ones', + 'randMatrix', 'rot_axis1', 'rot_axis2', 'rot_axis3', 'symarray', + 'wronskian', 'zeros', 'rot_ccw_axis1', 'rot_ccw_axis2', 'rot_ccw_axis3', + 'rot_givens', + + 'MutableDenseMatrix', + + 'DeferredVector', 'MatrixBase', + + 'Matrix', 'MutableMatrix', + + 'MutableSparseMatrix', + + 'banded', + + 'ImmutableDenseMatrix', 'ImmutableSparseMatrix', + + 'ImmutableMatrix', 'SparseMatrix', + + 'MatrixSlice', 'BlockDiagMatrix', 'BlockMatrix', 'FunctionMatrix', + 'Identity', 'Inverse', 'MatAdd', 'MatMul', 'MatPow', 'MatrixExpr', + 'MatrixSymbol', 'Trace', 'Transpose', 'ZeroMatrix', 'OneMatrix', + 'blockcut', 'block_collapse', 'matrix_symbols', 'Adjoint', + 'hadamard_product', 'HadamardProduct', 'HadamardPower', 'Determinant', + 'det', 'diagonalize_vector', 'DiagMatrix', 'DiagonalMatrix', + 'DiagonalOf', 'trace', 'DotProduct', 'kronecker_product', + 'KroneckerProduct', 'PermutationMatrix', 'MatrixPermute', 'MatrixSet', + 'Permanent', 'per', + + 'dotprodsimp', +] diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ed6b13ca982fbe58aefd1c118cae14b04f5e058 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/common.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/common.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f10d87d0c51830cb86451c851d4a3dc82aaa8a9f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/common.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/decompositions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/decompositions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ac32bc8596c70443189afb7100fe490497e033d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/decompositions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/dense.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/dense.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01be601ee125fd1efb461a4975eb8c339b685335 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/dense.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/determinant.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/determinant.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37787f92e02ca0577d512d9411863cb6c1d27c29 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/determinant.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/eigen.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/eigen.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a7bde29362721f4015f7b9b0e6ce516a70a8794 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/eigen.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/exceptions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/exceptions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4a20e9fdaa055afd6e0abb07d89b6e8507a95c2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/exceptions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/graph.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/graph.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4eb40573bfc3b34a52e935dcf509bb1dcf35dc1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/graph.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/immutable.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/immutable.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eca0c2f5d06345c1c6bdd7cd6b3965b1dfc037c0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/immutable.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/inverse.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/inverse.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7e59788dd86d0a4009f9bb815d0e49495e84c18 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/inverse.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/kind.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/kind.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38260e2c0f951ee270f4463632a4d1ac57bb3e13 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/kind.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/matrices.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/matrices.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f8767257965b53473e5b8f00462f1a709a7fae5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/matrices.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/normalforms.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/normalforms.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99ed7593f0a027a04c96cacd762b15475b52fb02 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/normalforms.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/reductions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/reductions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3238941bf10fc867981078445497e1121abee566 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/reductions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/repmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/repmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ff1cd81c3731540275018b196b47f0c1bfdab6b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/repmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/solvers.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/solvers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c23304323c19ef2bbd3636c97d099b73d0bb9542 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/solvers.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/sparse.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/sparse.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8d30d13d5272e325a362f31075d585185555e17 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/sparse.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/sparsetools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/sparsetools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97f5371edad991af5330f874a1945111821e7f53 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/sparsetools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/subspaces.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/subspaces.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22a6bcd5636b6faa56cf6831997f7ac8025979ad Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/subspaces.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/utilities.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/utilities.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..305158f0073396d3d47ccad55c3149dbb8d3109a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/__pycache__/utilities.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/__init__.py b/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8d5d12aa2c217ff674a86cd336cbbb0acf9bb2d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/__pycache__/bench_matrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/__pycache__/bench_matrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bca7d85c4477b103d52ad1c43f4b6401b2dc4ea5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/__pycache__/bench_matrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/bench_matrix.py b/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/bench_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..4fb845600533c4c6fef196fe5a45b98890f4ad78 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/benchmarks/bench_matrix.py @@ -0,0 +1,21 @@ +from sympy.core.numbers import Integer +from sympy.matrices.dense import (eye, zeros) + +i3 = Integer(3) +M = eye(100) + + +def timeit_Matrix__getitem_ii(): + M[3, 3] + + +def timeit_Matrix__getitem_II(): + M[i3, i3] + + +def timeit_Matrix__getslice(): + M[:, :] + + +def timeit_Matrix_zeronm(): + zeros(100, 100) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/common.py b/venv/lib/python3.10/site-packages/sympy/matrices/common.py new file mode 100644 index 0000000000000000000000000000000000000000..bcb54726fe1a0c36658d8bf63b974db5a3ce8bad --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/common.py @@ -0,0 +1,3258 @@ +""" +A module containing deprecated matrix mixin classes. + +The classes in this module are deprecated and will be removed in a future +release. They are kept here for backwards compatibility in case downstream +code was subclassing them. + +Importing anything else from this module is deprecated so anything here +should either not be used or should be imported from somewhere else. +""" +from __future__ import annotations +from collections import defaultdict +from collections.abc import Iterable +from inspect import isfunction +from functools import reduce + +from sympy.assumptions.refine import refine +from sympy.core import SympifyError, Add +from sympy.core.basic import Atom +from sympy.core.decorators import call_highest_priority +from sympy.core.logic import fuzzy_and, FuzzyBool +from sympy.core.numbers import Integer +from sympy.core.mod import Mod +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.functions.elementary.complexes import Abs, re, im +from sympy.utilities.exceptions import sympy_deprecation_warning +from .utilities import _dotprodsimp, _simplify +from sympy.polys.polytools import Poly +from sympy.utilities.iterables import flatten, is_sequence +from sympy.utilities.misc import as_int, filldedent +from sympy.tensor.array import NDimArray + +from .utilities import _get_intermediate_simp_bool + + +# These exception types were previously defined in this module but were moved +# to exceptions.py. We reimport them here for backwards compatibility in case +# downstream code was importing them from here. +from .exceptions import ( # noqa: F401 + MatrixError, ShapeError, NonSquareMatrixError, NonInvertibleMatrixError, + NonPositiveDefiniteMatrixError +) + + +_DEPRECATED_MIXINS = ( + 'MatrixShaping', + 'MatrixSpecial', + 'MatrixProperties', + 'MatrixOperations', + 'MatrixArithmetic', + 'MatrixCommon', + 'MatrixDeterminant', + 'MatrixReductions', + 'MatrixSubspaces', + 'MatrixEigen', + 'MatrixCalculus', + 'MatrixDeprecated', +) + + +class _MatrixDeprecatedMeta(type): + + # + # Override the default __instancecheck__ implementation to ensure that + # e.g. isinstance(M, MatrixCommon) still works when M is one of the + # matrix classes. Matrix no longer inherits from MatrixCommon so + # isinstance(M, MatrixCommon) would now return False by default. + # + # There were lots of places in the codebase where this was being done + # so it seems likely that downstream code may be doing it too. All use + # of these mixins is deprecated though so we give a deprecation warning + # unconditionally if they are being used with isinstance. + # + # Any code seeing this deprecation warning should be changed to use + # isinstance(M, MatrixBase) instead which also works in previous versions + # of SymPy. + # + + def __instancecheck__(cls, instance): + + sympy_deprecation_warning( + f""" + Checking whether an object is an instance of {cls.__name__} is + deprecated. + + Use `isinstance(obj, Matrix)` instead of `isinstance(obj, {cls.__name__})`. + """, + deprecated_since_version="1.13", + active_deprecations_target="deprecated-matrix-mixins", + stacklevel=3, + ) + + from sympy.matrices.matrixbase import MatrixBase + from sympy.matrices.matrices import ( + MatrixDeterminant, + MatrixReductions, + MatrixSubspaces, + MatrixEigen, + MatrixCalculus, + MatrixDeprecated + ) + + all_mixins = ( + MatrixRequired, + MatrixShaping, + MatrixSpecial, + MatrixProperties, + MatrixOperations, + MatrixArithmetic, + MatrixCommon, + MatrixDeterminant, + MatrixReductions, + MatrixSubspaces, + MatrixEigen, + MatrixCalculus, + MatrixDeprecated + ) + + if cls in all_mixins and isinstance(instance, MatrixBase): + return True + else: + return super().__instancecheck__(instance) + + +class MatrixRequired(metaclass=_MatrixDeprecatedMeta): + """Deprecated mixin class for making matrix classes.""" + + rows: int + cols: int + _simplify = None + + def __init_subclass__(cls, **kwargs): + + # Warn if any downstream code is subclassing this class or any of the + # deprecated mixin classes that are all ultimately subclasses of this + # class. + # + # We don't want to warn about the deprecated mixins themselves being + # created, but only about them being used as mixins by downstream code. + # Otherwise just importing this module would trigger a warning. + # Ultimately the whole module should be deprecated and removed but for + # SymPy 1.13 it is premature to do that given that this module was the + # main way to import matrix exception types in all previous versions. + + if cls.__name__ not in _DEPRECATED_MIXINS: + sympy_deprecation_warning( + f""" + Inheriting from the Matrix mixin classes is deprecated. + + The class {cls.__name__} is subclassing a deprecated mixin. + """, + deprecated_since_version="1.13", + active_deprecations_target="deprecated-matrix-mixins", + stacklevel=3, + ) + + super().__init_subclass__(**kwargs) + + @classmethod + def _new(cls, *args, **kwargs): + """`_new` must, at minimum, be callable as + `_new(rows, cols, mat) where mat is a flat list of the + elements of the matrix.""" + raise NotImplementedError("Subclasses must implement this.") + + def __eq__(self, other): + raise NotImplementedError("Subclasses must implement this.") + + def __getitem__(self, key): + """Implementations of __getitem__ should accept ints, in which + case the matrix is indexed as a flat list, tuples (i,j) in which + case the (i,j) entry is returned, slices, or mixed tuples (a,b) + where a and b are any combination of slices and integers.""" + raise NotImplementedError("Subclasses must implement this.") + + def __len__(self): + """The total number of entries in the matrix.""" + raise NotImplementedError("Subclasses must implement this.") + + @property + def shape(self): + raise NotImplementedError("Subclasses must implement this.") + + +class MatrixShaping(MatrixRequired): + """Provides basic matrix shaping and extracting of submatrices""" + + def _eval_col_del(self, col): + def entry(i, j): + return self[i, j] if j < col else self[i, j + 1] + return self._new(self.rows, self.cols - 1, entry) + + def _eval_col_insert(self, pos, other): + + def entry(i, j): + if j < pos: + return self[i, j] + elif pos <= j < pos + other.cols: + return other[i, j - pos] + return self[i, j - other.cols] + + return self._new(self.rows, self.cols + other.cols, entry) + + def _eval_col_join(self, other): + rows = self.rows + + def entry(i, j): + if i < rows: + return self[i, j] + return other[i - rows, j] + + return classof(self, other)._new(self.rows + other.rows, self.cols, + entry) + + def _eval_extract(self, rowsList, colsList): + mat = list(self) + cols = self.cols + indices = (i * cols + j for i in rowsList for j in colsList) + return self._new(len(rowsList), len(colsList), + [mat[i] for i in indices]) + + def _eval_get_diag_blocks(self): + sub_blocks = [] + + def recurse_sub_blocks(M): + for i in range(1, M.shape[0] + 1): + if i == 1: + to_the_right = M[0, i:] + to_the_bottom = M[i:, 0] + else: + to_the_right = M[:i, i:] + to_the_bottom = M[i:, :i] + if any(to_the_right) or any(to_the_bottom): + continue + sub_blocks.append(M[:i, :i]) + if M.shape != M[:i, :i].shape: + recurse_sub_blocks(M[i:, i:]) + return + + recurse_sub_blocks(self) + return sub_blocks + + def _eval_row_del(self, row): + def entry(i, j): + return self[i, j] if i < row else self[i + 1, j] + return self._new(self.rows - 1, self.cols, entry) + + def _eval_row_insert(self, pos, other): + entries = list(self) + insert_pos = pos * self.cols + entries[insert_pos:insert_pos] = list(other) + return self._new(self.rows + other.rows, self.cols, entries) + + def _eval_row_join(self, other): + cols = self.cols + + def entry(i, j): + if j < cols: + return self[i, j] + return other[i, j - cols] + + return classof(self, other)._new(self.rows, self.cols + other.cols, + entry) + + def _eval_tolist(self): + return [list(self[i,:]) for i in range(self.rows)] + + def _eval_todok(self): + dok = {} + rows, cols = self.shape + for i in range(rows): + for j in range(cols): + val = self[i, j] + if val != self.zero: + dok[i, j] = val + return dok + + def _eval_vec(self): + rows = self.rows + + def entry(n, _): + # we want to read off the columns first + j = n // rows + i = n - j * rows + return self[i, j] + + return self._new(len(self), 1, entry) + + def _eval_vech(self, diagonal): + c = self.cols + v = [] + if diagonal: + for j in range(c): + for i in range(j, c): + v.append(self[i, j]) + else: + for j in range(c): + for i in range(j + 1, c): + v.append(self[i, j]) + return self._new(len(v), 1, v) + + def col_del(self, col): + """Delete the specified column.""" + if col < 0: + col += self.cols + if not 0 <= col < self.cols: + raise IndexError("Column {} is out of range.".format(col)) + return self._eval_col_del(col) + + def col_insert(self, pos, other): + """Insert one or more columns at the given column position. + + Examples + ======== + + >>> from sympy import zeros, ones + >>> M = zeros(3) + >>> V = ones(3, 1) + >>> M.col_insert(1, V) + Matrix([ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0]]) + + See Also + ======== + + col + row_insert + """ + # Allows you to build a matrix even if it is null matrix + if not self: + return type(self)(other) + + pos = as_int(pos) + + if pos < 0: + pos = self.cols + pos + if pos < 0: + pos = 0 + elif pos > self.cols: + pos = self.cols + + if self.rows != other.rows: + raise ShapeError( + "The matrices have incompatible number of rows ({} and {})" + .format(self.rows, other.rows)) + + return self._eval_col_insert(pos, other) + + def col_join(self, other): + """Concatenates two matrices along self's last and other's first row. + + Examples + ======== + + >>> from sympy import zeros, ones + >>> M = zeros(3) + >>> V = ones(1, 3) + >>> M.col_join(V) + Matrix([ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [1, 1, 1]]) + + See Also + ======== + + col + row_join + """ + # A null matrix can always be stacked (see #10770) + if self.rows == 0 and self.cols != other.cols: + return self._new(0, other.cols, []).col_join(other) + + if self.cols != other.cols: + raise ShapeError( + "The matrices have incompatible number of columns ({} and {})" + .format(self.cols, other.cols)) + return self._eval_col_join(other) + + def col(self, j): + """Elementary column selector. + + Examples + ======== + + >>> from sympy import eye + >>> eye(2).col(0) + Matrix([ + [1], + [0]]) + + See Also + ======== + + row + col_del + col_join + col_insert + """ + return self[:, j] + + def extract(self, rowsList, colsList): + r"""Return a submatrix by specifying a list of rows and columns. + Negative indices can be given. All indices must be in the range + $-n \le i < n$ where $n$ is the number of rows or columns. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(4, 3, range(12)) + >>> m + Matrix([ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11]]) + >>> m.extract([0, 1, 3], [0, 1]) + Matrix([ + [0, 1], + [3, 4], + [9, 10]]) + + Rows or columns can be repeated: + + >>> m.extract([0, 0, 1], [-1]) + Matrix([ + [2], + [2], + [5]]) + + Every other row can be taken by using range to provide the indices: + + >>> m.extract(range(0, m.rows, 2), [-1]) + Matrix([ + [2], + [8]]) + + RowsList or colsList can also be a list of booleans, in which case + the rows or columns corresponding to the True values will be selected: + + >>> m.extract([0, 1, 2, 3], [True, False, True]) + Matrix([ + [0, 2], + [3, 5], + [6, 8], + [9, 11]]) + """ + + if not is_sequence(rowsList) or not is_sequence(colsList): + raise TypeError("rowsList and colsList must be iterable") + # ensure rowsList and colsList are lists of integers + if rowsList and all(isinstance(i, bool) for i in rowsList): + rowsList = [index for index, item in enumerate(rowsList) if item] + if colsList and all(isinstance(i, bool) for i in colsList): + colsList = [index for index, item in enumerate(colsList) if item] + + # ensure everything is in range + rowsList = [a2idx(k, self.rows) for k in rowsList] + colsList = [a2idx(k, self.cols) for k in colsList] + + return self._eval_extract(rowsList, colsList) + + def get_diag_blocks(self): + """Obtains the square sub-matrices on the main diagonal of a square matrix. + + Useful for inverting symbolic matrices or solving systems of + linear equations which may be decoupled by having a block diagonal + structure. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y, z + >>> A = Matrix([[1, 3, 0, 0], [y, z*z, 0, 0], [0, 0, x, 0], [0, 0, 0, 0]]) + >>> a1, a2, a3 = A.get_diag_blocks() + >>> a1 + Matrix([ + [1, 3], + [y, z**2]]) + >>> a2 + Matrix([[x]]) + >>> a3 + Matrix([[0]]) + + """ + return self._eval_get_diag_blocks() + + @classmethod + def hstack(cls, *args): + """Return a matrix formed by joining args horizontally (i.e. + by repeated application of row_join). + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> Matrix.hstack(eye(2), 2*eye(2)) + Matrix([ + [1, 0, 2, 0], + [0, 1, 0, 2]]) + """ + if len(args) == 0: + return cls._new() + + kls = type(args[0]) + return reduce(kls.row_join, args) + + def reshape(self, rows, cols): + """Reshape the matrix. Total number of elements must remain the same. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 3, lambda i, j: 1) + >>> m + Matrix([ + [1, 1, 1], + [1, 1, 1]]) + >>> m.reshape(1, 6) + Matrix([[1, 1, 1, 1, 1, 1]]) + >>> m.reshape(3, 2) + Matrix([ + [1, 1], + [1, 1], + [1, 1]]) + + """ + if self.rows * self.cols != rows * cols: + raise ValueError("Invalid reshape parameters %d %d" % (rows, cols)) + return self._new(rows, cols, lambda i, j: self[i * cols + j]) + + def row_del(self, row): + """Delete the specified row.""" + if row < 0: + row += self.rows + if not 0 <= row < self.rows: + raise IndexError("Row {} is out of range.".format(row)) + + return self._eval_row_del(row) + + def row_insert(self, pos, other): + """Insert one or more rows at the given row position. + + Examples + ======== + + >>> from sympy import zeros, ones + >>> M = zeros(3) + >>> V = ones(1, 3) + >>> M.row_insert(1, V) + Matrix([ + [0, 0, 0], + [1, 1, 1], + [0, 0, 0], + [0, 0, 0]]) + + See Also + ======== + + row + col_insert + """ + # Allows you to build a matrix even if it is null matrix + if not self: + return self._new(other) + + pos = as_int(pos) + + if pos < 0: + pos = self.rows + pos + if pos < 0: + pos = 0 + elif pos > self.rows: + pos = self.rows + + if self.cols != other.cols: + raise ShapeError( + "The matrices have incompatible number of columns ({} and {})" + .format(self.cols, other.cols)) + + return self._eval_row_insert(pos, other) + + def row_join(self, other): + """Concatenates two matrices along self's last and rhs's first column + + Examples + ======== + + >>> from sympy import zeros, ones + >>> M = zeros(3) + >>> V = ones(3, 1) + >>> M.row_join(V) + Matrix([ + [0, 0, 0, 1], + [0, 0, 0, 1], + [0, 0, 0, 1]]) + + See Also + ======== + + row + col_join + """ + # A null matrix can always be stacked (see #10770) + if self.cols == 0 and self.rows != other.rows: + return self._new(other.rows, 0, []).row_join(other) + + if self.rows != other.rows: + raise ShapeError( + "The matrices have incompatible number of rows ({} and {})" + .format(self.rows, other.rows)) + return self._eval_row_join(other) + + def diagonal(self, k=0): + """Returns the kth diagonal of self. The main diagonal + corresponds to `k=0`; diagonals above and below correspond to + `k > 0` and `k < 0`, respectively. The values of `self[i, j]` + for which `j - i = k`, are returned in order of increasing + `i + j`, starting with `i + j = |k|`. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(3, 3, lambda i, j: j - i); m + Matrix([ + [ 0, 1, 2], + [-1, 0, 1], + [-2, -1, 0]]) + >>> _.diagonal() + Matrix([[0, 0, 0]]) + >>> m.diagonal(1) + Matrix([[1, 1]]) + >>> m.diagonal(-2) + Matrix([[-2]]) + + Even though the diagonal is returned as a Matrix, the element + retrieval can be done with a single index: + + >>> Matrix.diag(1, 2, 3).diagonal()[1] # instead of [0, 1] + 2 + + See Also + ======== + + diag + """ + rv = [] + k = as_int(k) + r = 0 if k > 0 else -k + c = 0 if r else k + while True: + if r == self.rows or c == self.cols: + break + rv.append(self[r, c]) + r += 1 + c += 1 + if not rv: + raise ValueError(filldedent(''' + The %s diagonal is out of range [%s, %s]''' % ( + k, 1 - self.rows, self.cols - 1))) + return self._new(1, len(rv), rv) + + def row(self, i): + """Elementary row selector. + + Examples + ======== + + >>> from sympy import eye + >>> eye(2).row(0) + Matrix([[1, 0]]) + + See Also + ======== + + col + row_del + row_join + row_insert + """ + return self[i, :] + + @property + def shape(self): + """The shape (dimensions) of the matrix as the 2-tuple (rows, cols). + + Examples + ======== + + >>> from sympy import zeros + >>> M = zeros(2, 3) + >>> M.shape + (2, 3) + >>> M.rows + 2 + >>> M.cols + 3 + """ + return (self.rows, self.cols) + + def todok(self): + """Return the matrix as dictionary of keys. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix.eye(3) + >>> M.todok() + {(0, 0): 1, (1, 1): 1, (2, 2): 1} + """ + return self._eval_todok() + + def tolist(self): + """Return the Matrix as a nested Python list. + + Examples + ======== + + >>> from sympy import Matrix, ones + >>> m = Matrix(3, 3, range(9)) + >>> m + Matrix([ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8]]) + >>> m.tolist() + [[0, 1, 2], [3, 4, 5], [6, 7, 8]] + >>> ones(3, 0).tolist() + [[], [], []] + + When there are no rows then it will not be possible to tell how + many columns were in the original matrix: + + >>> ones(0, 3).tolist() + [] + + """ + if not self.rows: + return [] + if not self.cols: + return [[] for i in range(self.rows)] + return self._eval_tolist() + + def todod(M): + """Returns matrix as dict of dicts containing non-zero elements of the Matrix + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[0, 1],[0, 3]]) + >>> A + Matrix([ + [0, 1], + [0, 3]]) + >>> A.todod() + {0: {1: 1}, 1: {1: 3}} + + + """ + rowsdict = {} + Mlol = M.tolist() + for i, Mi in enumerate(Mlol): + row = {j: Mij for j, Mij in enumerate(Mi) if Mij} + if row: + rowsdict[i] = row + return rowsdict + + def vec(self): + """Return the Matrix converted into a one column matrix by stacking columns + + Examples + ======== + + >>> from sympy import Matrix + >>> m=Matrix([[1, 3], [2, 4]]) + >>> m + Matrix([ + [1, 3], + [2, 4]]) + >>> m.vec() + Matrix([ + [1], + [2], + [3], + [4]]) + + See Also + ======== + + vech + """ + return self._eval_vec() + + def vech(self, diagonal=True, check_symmetry=True): + """Reshapes the matrix into a column vector by stacking the + elements in the lower triangle. + + Parameters + ========== + + diagonal : bool, optional + If ``True``, it includes the diagonal elements. + + check_symmetry : bool, optional + If ``True``, it checks whether the matrix is symmetric. + + Examples + ======== + + >>> from sympy import Matrix + >>> m=Matrix([[1, 2], [2, 3]]) + >>> m + Matrix([ + [1, 2], + [2, 3]]) + >>> m.vech() + Matrix([ + [1], + [2], + [3]]) + >>> m.vech(diagonal=False) + Matrix([[2]]) + + Notes + ===== + + This should work for symmetric matrices and ``vech`` can + represent symmetric matrices in vector form with less size than + ``vec``. + + See Also + ======== + + vec + """ + if not self.is_square: + raise NonSquareMatrixError + + if check_symmetry and not self.is_symmetric(): + raise ValueError("The matrix is not symmetric.") + + return self._eval_vech(diagonal) + + @classmethod + def vstack(cls, *args): + """Return a matrix formed by joining args vertically (i.e. + by repeated application of col_join). + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> Matrix.vstack(eye(2), 2*eye(2)) + Matrix([ + [1, 0], + [0, 1], + [2, 0], + [0, 2]]) + """ + if len(args) == 0: + return cls._new() + + kls = type(args[0]) + return reduce(kls.col_join, args) + + +class MatrixSpecial(MatrixRequired): + """Construction of special matrices""" + + @classmethod + def _eval_diag(cls, rows, cols, diag_dict): + """diag_dict is a defaultdict containing + all the entries of the diagonal matrix.""" + def entry(i, j): + return diag_dict[(i, j)] + return cls._new(rows, cols, entry) + + @classmethod + def _eval_eye(cls, rows, cols): + vals = [cls.zero]*(rows*cols) + vals[::cols+1] = [cls.one]*min(rows, cols) + return cls._new(rows, cols, vals, copy=False) + + @classmethod + def _eval_jordan_block(cls, size: int, eigenvalue, band='upper'): + if band == 'lower': + def entry(i, j): + if i == j: + return eigenvalue + elif j + 1 == i: + return cls.one + return cls.zero + else: + def entry(i, j): + if i == j: + return eigenvalue + elif i + 1 == j: + return cls.one + return cls.zero + return cls._new(size, size, entry) + + @classmethod + def _eval_ones(cls, rows, cols): + def entry(i, j): + return cls.one + return cls._new(rows, cols, entry) + + @classmethod + def _eval_zeros(cls, rows, cols): + return cls._new(rows, cols, [cls.zero]*(rows*cols), copy=False) + + @classmethod + def _eval_wilkinson(cls, n): + def entry(i, j): + return cls.one if i + 1 == j else cls.zero + + D = cls._new(2*n + 1, 2*n + 1, entry) + + wminus = cls.diag(list(range(-n, n + 1)), unpack=True) + D + D.T + wplus = abs(cls.diag(list(range(-n, n + 1)), unpack=True)) + D + D.T + + return wminus, wplus + + @classmethod + def diag(kls, *args, strict=False, unpack=True, rows=None, cols=None, **kwargs): + """Returns a matrix with the specified diagonal. + If matrices are passed, a block-diagonal matrix + is created (i.e. the "direct sum" of the matrices). + + kwargs + ====== + + rows : rows of the resulting matrix; computed if + not given. + + cols : columns of the resulting matrix; computed if + not given. + + cls : class for the resulting matrix + + unpack : bool which, when True (default), unpacks a single + sequence rather than interpreting it as a Matrix. + + strict : bool which, when False (default), allows Matrices to + have variable-length rows. + + Examples + ======== + + >>> from sympy import Matrix + >>> Matrix.diag(1, 2, 3) + Matrix([ + [1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + + The current default is to unpack a single sequence. If this is + not desired, set `unpack=False` and it will be interpreted as + a matrix. + + >>> Matrix.diag([1, 2, 3]) == Matrix.diag(1, 2, 3) + True + + When more than one element is passed, each is interpreted as + something to put on the diagonal. Lists are converted to + matrices. Filling of the diagonal always continues from + the bottom right hand corner of the previous item: this + will create a block-diagonal matrix whether the matrices + are square or not. + + >>> col = [1, 2, 3] + >>> row = [[4, 5]] + >>> Matrix.diag(col, row) + Matrix([ + [1, 0, 0], + [2, 0, 0], + [3, 0, 0], + [0, 4, 5]]) + + When `unpack` is False, elements within a list need not all be + of the same length. Setting `strict` to True would raise a + ValueError for the following: + + >>> Matrix.diag([[1, 2, 3], [4, 5], [6]], unpack=False) + Matrix([ + [1, 2, 3], + [4, 5, 0], + [6, 0, 0]]) + + The type of the returned matrix can be set with the ``cls`` + keyword. + + >>> from sympy import ImmutableMatrix + >>> from sympy.utilities.misc import func_name + >>> func_name(Matrix.diag(1, cls=ImmutableMatrix)) + 'ImmutableDenseMatrix' + + A zero dimension matrix can be used to position the start of + the filling at the start of an arbitrary row or column: + + >>> from sympy import ones + >>> r2 = ones(0, 2) + >>> Matrix.diag(r2, 1, 2) + Matrix([ + [0, 0, 1, 0], + [0, 0, 0, 2]]) + + See Also + ======== + eye + diagonal + .dense.diag + .expressions.blockmatrix.BlockMatrix + .sparsetools.banded + """ + from sympy.matrices.matrixbase import MatrixBase + from sympy.matrices.dense import Matrix + from sympy.matrices import SparseMatrix + klass = kwargs.get('cls', kls) + if unpack and len(args) == 1 and is_sequence(args[0]) and \ + not isinstance(args[0], MatrixBase): + args = args[0] + + # fill a default dict with the diagonal entries + diag_entries = defaultdict(int) + rmax = cmax = 0 # keep track of the biggest index seen + for m in args: + if isinstance(m, list): + if strict: + # if malformed, Matrix will raise an error + _ = Matrix(m) + r, c = _.shape + m = _.tolist() + else: + r, c, smat = SparseMatrix._handle_creation_inputs(m) + for (i, j), _ in smat.items(): + diag_entries[(i + rmax, j + cmax)] = _ + m = [] # to skip process below + elif hasattr(m, 'shape'): # a Matrix + # convert to list of lists + r, c = m.shape + m = m.tolist() + else: # in this case, we're a single value + diag_entries[(rmax, cmax)] = m + rmax += 1 + cmax += 1 + continue + # process list of lists + for i, mi in enumerate(m): + for j, _ in enumerate(mi): + diag_entries[(i + rmax, j + cmax)] = _ + rmax += r + cmax += c + if rows is None: + rows, cols = cols, rows + if rows is None: + rows, cols = rmax, cmax + else: + cols = rows if cols is None else cols + if rows < rmax or cols < cmax: + raise ValueError(filldedent(''' + The constructed matrix is {} x {} but a size of {} x {} + was specified.'''.format(rmax, cmax, rows, cols))) + return klass._eval_diag(rows, cols, diag_entries) + + @classmethod + def eye(kls, rows, cols=None, **kwargs): + """Returns an identity matrix. + + Parameters + ========== + + rows : rows of the matrix + cols : cols of the matrix (if None, cols=rows) + + kwargs + ====== + cls : class of the returned matrix + """ + if cols is None: + cols = rows + if rows < 0 or cols < 0: + raise ValueError("Cannot create a {} x {} matrix. " + "Both dimensions must be positive".format(rows, cols)) + klass = kwargs.get('cls', kls) + rows, cols = as_int(rows), as_int(cols) + + return klass._eval_eye(rows, cols) + + @classmethod + def jordan_block(kls, size=None, eigenvalue=None, *, band='upper', **kwargs): + """Returns a Jordan block + + Parameters + ========== + + size : Integer, optional + Specifies the shape of the Jordan block matrix. + + eigenvalue : Number or Symbol + Specifies the value for the main diagonal of the matrix. + + .. note:: + The keyword ``eigenval`` is also specified as an alias + of this keyword, but it is not recommended to use. + + We may deprecate the alias in later release. + + band : 'upper' or 'lower', optional + Specifies the position of the off-diagonal to put `1` s on. + + cls : Matrix, optional + Specifies the matrix class of the output form. + + If it is not specified, the class type where the method is + being executed on will be returned. + + Returns + ======= + + Matrix + A Jordan block matrix. + + Raises + ====== + + ValueError + If insufficient arguments are given for matrix size + specification, or no eigenvalue is given. + + Examples + ======== + + Creating a default Jordan block: + + >>> from sympy import Matrix + >>> from sympy.abc import x + >>> Matrix.jordan_block(4, x) + Matrix([ + [x, 1, 0, 0], + [0, x, 1, 0], + [0, 0, x, 1], + [0, 0, 0, x]]) + + Creating an alternative Jordan block matrix where `1` is on + lower off-diagonal: + + >>> Matrix.jordan_block(4, x, band='lower') + Matrix([ + [x, 0, 0, 0], + [1, x, 0, 0], + [0, 1, x, 0], + [0, 0, 1, x]]) + + Creating a Jordan block with keyword arguments + + >>> Matrix.jordan_block(size=4, eigenvalue=x) + Matrix([ + [x, 1, 0, 0], + [0, x, 1, 0], + [0, 0, x, 1], + [0, 0, 0, x]]) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Jordan_matrix + """ + klass = kwargs.pop('cls', kls) + + eigenval = kwargs.get('eigenval', None) + if eigenvalue is None and eigenval is None: + raise ValueError("Must supply an eigenvalue") + elif eigenvalue != eigenval and None not in (eigenval, eigenvalue): + raise ValueError( + "Inconsistent values are given: 'eigenval'={}, " + "'eigenvalue'={}".format(eigenval, eigenvalue)) + else: + if eigenval is not None: + eigenvalue = eigenval + + if size is None: + raise ValueError("Must supply a matrix size") + + size = as_int(size) + return klass._eval_jordan_block(size, eigenvalue, band) + + @classmethod + def ones(kls, rows, cols=None, **kwargs): + """Returns a matrix of ones. + + Parameters + ========== + + rows : rows of the matrix + cols : cols of the matrix (if None, cols=rows) + + kwargs + ====== + cls : class of the returned matrix + """ + if cols is None: + cols = rows + klass = kwargs.get('cls', kls) + rows, cols = as_int(rows), as_int(cols) + + return klass._eval_ones(rows, cols) + + @classmethod + def zeros(kls, rows, cols=None, **kwargs): + """Returns a matrix of zeros. + + Parameters + ========== + + rows : rows of the matrix + cols : cols of the matrix (if None, cols=rows) + + kwargs + ====== + cls : class of the returned matrix + """ + if cols is None: + cols = rows + if rows < 0 or cols < 0: + raise ValueError("Cannot create a {} x {} matrix. " + "Both dimensions must be positive".format(rows, cols)) + klass = kwargs.get('cls', kls) + rows, cols = as_int(rows), as_int(cols) + + return klass._eval_zeros(rows, cols) + + @classmethod + def companion(kls, poly): + """Returns a companion matrix of a polynomial. + + Examples + ======== + + >>> from sympy import Matrix, Poly, Symbol, symbols + >>> x = Symbol('x') + >>> c0, c1, c2, c3, c4 = symbols('c0:5') + >>> p = Poly(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + x**5, x) + >>> Matrix.companion(p) + Matrix([ + [0, 0, 0, 0, -c0], + [1, 0, 0, 0, -c1], + [0, 1, 0, 0, -c2], + [0, 0, 1, 0, -c3], + [0, 0, 0, 1, -c4]]) + """ + poly = kls._sympify(poly) + if not isinstance(poly, Poly): + raise ValueError("{} must be a Poly instance.".format(poly)) + if not poly.is_monic: + raise ValueError("{} must be a monic polynomial.".format(poly)) + if not poly.is_univariate: + raise ValueError( + "{} must be a univariate polynomial.".format(poly)) + + size = poly.degree() + if not size >= 1: + raise ValueError( + "{} must have degree not less than 1.".format(poly)) + + coeffs = poly.all_coeffs() + def entry(i, j): + if j == size - 1: + return -coeffs[-1 - i] + elif i == j + 1: + return kls.one + return kls.zero + return kls._new(size, size, entry) + + + @classmethod + def wilkinson(kls, n, **kwargs): + """Returns two square Wilkinson Matrix of size 2*n + 1 + $W_{2n + 1}^-, W_{2n + 1}^+ =$ Wilkinson(n) + + Examples + ======== + + >>> from sympy import Matrix + >>> wminus, wplus = Matrix.wilkinson(3) + >>> wminus + Matrix([ + [-3, 1, 0, 0, 0, 0, 0], + [ 1, -2, 1, 0, 0, 0, 0], + [ 0, 1, -1, 1, 0, 0, 0], + [ 0, 0, 1, 0, 1, 0, 0], + [ 0, 0, 0, 1, 1, 1, 0], + [ 0, 0, 0, 0, 1, 2, 1], + [ 0, 0, 0, 0, 0, 1, 3]]) + >>> wplus + Matrix([ + [3, 1, 0, 0, 0, 0, 0], + [1, 2, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0], + [0, 0, 0, 0, 1, 2, 1], + [0, 0, 0, 0, 0, 1, 3]]) + + References + ========== + + .. [1] https://blogs.mathworks.com/cleve/2013/04/15/wilkinsons-matrices-2/ + .. [2] J. H. Wilkinson, The Algebraic Eigenvalue Problem, Claredon Press, Oxford, 1965, 662 pp. + + """ + klass = kwargs.get('cls', kls) + n = as_int(n) + return klass._eval_wilkinson(n) + +class MatrixProperties(MatrixRequired): + """Provides basic properties of a matrix.""" + + def _eval_atoms(self, *types): + result = set() + for i in self: + result.update(i.atoms(*types)) + return result + + def _eval_free_symbols(self): + return set().union(*(i.free_symbols for i in self if i)) + + def _eval_has(self, *patterns): + return any(a.has(*patterns) for a in self) + + def _eval_is_anti_symmetric(self, simpfunc): + if not all(simpfunc(self[i, j] + self[j, i]).is_zero for i in range(self.rows) for j in range(self.cols)): + return False + return True + + def _eval_is_diagonal(self): + for i in range(self.rows): + for j in range(self.cols): + if i != j and self[i, j]: + return False + return True + + # _eval_is_hermitian is called by some general SymPy + # routines and has a different *args signature. Make + # sure the names don't clash by adding `_matrix_` in name. + def _eval_is_matrix_hermitian(self, simpfunc): + mat = self._new(self.rows, self.cols, lambda i, j: simpfunc(self[i, j] - self[j, i].conjugate())) + return mat.is_zero_matrix + + def _eval_is_Identity(self) -> FuzzyBool: + def dirac(i, j): + if i == j: + return 1 + return 0 + + return all(self[i, j] == dirac(i, j) + for i in range(self.rows) + for j in range(self.cols)) + + def _eval_is_lower_hessenberg(self): + return all(self[i, j].is_zero + for i in range(self.rows) + for j in range(i + 2, self.cols)) + + def _eval_is_lower(self): + return all(self[i, j].is_zero + for i in range(self.rows) + for j in range(i + 1, self.cols)) + + def _eval_is_symbolic(self): + return self.has(Symbol) + + def _eval_is_symmetric(self, simpfunc): + mat = self._new(self.rows, self.cols, lambda i, j: simpfunc(self[i, j] - self[j, i])) + return mat.is_zero_matrix + + def _eval_is_zero_matrix(self): + if any(i.is_zero == False for i in self): + return False + if any(i.is_zero is None for i in self): + return None + return True + + def _eval_is_upper_hessenberg(self): + return all(self[i, j].is_zero + for i in range(2, self.rows) + for j in range(min(self.cols, (i - 1)))) + + def _eval_values(self): + return [i for i in self if not i.is_zero] + + def _has_positive_diagonals(self): + diagonal_entries = (self[i, i] for i in range(self.rows)) + return fuzzy_and(x.is_positive for x in diagonal_entries) + + def _has_nonnegative_diagonals(self): + diagonal_entries = (self[i, i] for i in range(self.rows)) + return fuzzy_and(x.is_nonnegative for x in diagonal_entries) + + def atoms(self, *types): + """Returns the atoms that form the current object. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import Matrix + >>> Matrix([[x]]) + Matrix([[x]]) + >>> _.atoms() + {x} + >>> Matrix([[x, y], [y, x]]) + Matrix([ + [x, y], + [y, x]]) + >>> _.atoms() + {x, y} + """ + + types = tuple(t if isinstance(t, type) else type(t) for t in types) + if not types: + types = (Atom,) + return self._eval_atoms(*types) + + @property + def free_symbols(self): + """Returns the free symbols within the matrix. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import Matrix + >>> Matrix([[x], [1]]).free_symbols + {x} + """ + return self._eval_free_symbols() + + def has(self, *patterns): + """Test whether any subexpression matches any of the patterns. + + Examples + ======== + + >>> from sympy import Matrix, SparseMatrix, Float + >>> from sympy.abc import x, y + >>> A = Matrix(((1, x), (0.2, 3))) + >>> B = SparseMatrix(((1, x), (0.2, 3))) + >>> A.has(x) + True + >>> A.has(y) + False + >>> A.has(Float) + True + >>> B.has(x) + True + >>> B.has(y) + False + >>> B.has(Float) + True + """ + return self._eval_has(*patterns) + + def is_anti_symmetric(self, simplify=True): + """Check if matrix M is an antisymmetric matrix, + that is, M is a square matrix with all M[i, j] == -M[j, i]. + + When ``simplify=True`` (default), the sum M[i, j] + M[j, i] is + simplified before testing to see if it is zero. By default, + the SymPy simplify function is used. To use a custom function + set simplify to a function that accepts a single argument which + returns a simplified expression. To skip simplification, set + simplify to False but note that although this will be faster, + it may induce false negatives. + + Examples + ======== + + >>> from sympy import Matrix, symbols + >>> m = Matrix(2, 2, [0, 1, -1, 0]) + >>> m + Matrix([ + [ 0, 1], + [-1, 0]]) + >>> m.is_anti_symmetric() + True + >>> x, y = symbols('x y') + >>> m = Matrix(2, 3, [0, 0, x, -y, 0, 0]) + >>> m + Matrix([ + [ 0, 0, x], + [-y, 0, 0]]) + >>> m.is_anti_symmetric() + False + + >>> from sympy.abc import x, y + >>> m = Matrix(3, 3, [0, x**2 + 2*x + 1, y, + ... -(x + 1)**2, 0, x*y, + ... -y, -x*y, 0]) + + Simplification of matrix elements is done by default so even + though two elements which should be equal and opposite would not + pass an equality test, the matrix is still reported as + anti-symmetric: + + >>> m[0, 1] == -m[1, 0] + False + >>> m.is_anti_symmetric() + True + + If ``simplify=False`` is used for the case when a Matrix is already + simplified, this will speed things up. Here, we see that without + simplification the matrix does not appear anti-symmetric: + + >>> print(m.is_anti_symmetric(simplify=False)) + None + + But if the matrix were already expanded, then it would appear + anti-symmetric and simplification in the is_anti_symmetric routine + is not needed: + + >>> m = m.expand() + >>> m.is_anti_symmetric(simplify=False) + True + """ + # accept custom simplification + simpfunc = simplify + if not isfunction(simplify): + simpfunc = _simplify if simplify else lambda x: x + + if not self.is_square: + return False + return self._eval_is_anti_symmetric(simpfunc) + + def is_diagonal(self): + """Check if matrix is diagonal, + that is matrix in which the entries outside the main diagonal are all zero. + + Examples + ======== + + >>> from sympy import Matrix, diag + >>> m = Matrix(2, 2, [1, 0, 0, 2]) + >>> m + Matrix([ + [1, 0], + [0, 2]]) + >>> m.is_diagonal() + True + + >>> m = Matrix(2, 2, [1, 1, 0, 2]) + >>> m + Matrix([ + [1, 1], + [0, 2]]) + >>> m.is_diagonal() + False + + >>> m = diag(1, 2, 3) + >>> m + Matrix([ + [1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + >>> m.is_diagonal() + True + + See Also + ======== + + is_lower + is_upper + sympy.matrices.matrixbase.MatrixCommon.is_diagonalizable + diagonalize + """ + return self._eval_is_diagonal() + + @property + def is_weakly_diagonally_dominant(self): + r"""Tests if the matrix is row weakly diagonally dominant. + + Explanation + =========== + + A $n, n$ matrix $A$ is row weakly diagonally dominant if + + .. math:: + \left|A_{i, i}\right| \ge \sum_{j = 0, j \neq i}^{n-1} + \left|A_{i, j}\right| \quad {\text{for all }} + i \in \{ 0, ..., n-1 \} + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[3, -2, 1], [1, -3, 2], [-1, 2, 4]]) + >>> A.is_weakly_diagonally_dominant + True + + >>> A = Matrix([[-2, 2, 1], [1, 3, 2], [1, -2, 0]]) + >>> A.is_weakly_diagonally_dominant + False + + >>> A = Matrix([[-4, 2, 1], [1, 6, 2], [1, -2, 5]]) + >>> A.is_weakly_diagonally_dominant + True + + Notes + ===== + + If you want to test whether a matrix is column diagonally + dominant, you can apply the test after transposing the matrix. + """ + if not self.is_square: + return False + + rows, cols = self.shape + + def test_row(i): + summation = self.zero + for j in range(cols): + if i != j: + summation += Abs(self[i, j]) + return (Abs(self[i, i]) - summation).is_nonnegative + + return fuzzy_and(test_row(i) for i in range(rows)) + + @property + def is_strongly_diagonally_dominant(self): + r"""Tests if the matrix is row strongly diagonally dominant. + + Explanation + =========== + + A $n, n$ matrix $A$ is row strongly diagonally dominant if + + .. math:: + \left|A_{i, i}\right| > \sum_{j = 0, j \neq i}^{n-1} + \left|A_{i, j}\right| \quad {\text{for all }} + i \in \{ 0, ..., n-1 \} + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[3, -2, 1], [1, -3, 2], [-1, 2, 4]]) + >>> A.is_strongly_diagonally_dominant + False + + >>> A = Matrix([[-2, 2, 1], [1, 3, 2], [1, -2, 0]]) + >>> A.is_strongly_diagonally_dominant + False + + >>> A = Matrix([[-4, 2, 1], [1, 6, 2], [1, -2, 5]]) + >>> A.is_strongly_diagonally_dominant + True + + Notes + ===== + + If you want to test whether a matrix is column diagonally + dominant, you can apply the test after transposing the matrix. + """ + if not self.is_square: + return False + + rows, cols = self.shape + + def test_row(i): + summation = self.zero + for j in range(cols): + if i != j: + summation += Abs(self[i, j]) + return (Abs(self[i, i]) - summation).is_positive + + return fuzzy_and(test_row(i) for i in range(rows)) + + @property + def is_hermitian(self): + """Checks if the matrix is Hermitian. + + In a Hermitian matrix element i,j is the complex conjugate of + element j,i. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy import I + >>> from sympy.abc import x + >>> a = Matrix([[1, I], [-I, 1]]) + >>> a + Matrix([ + [ 1, I], + [-I, 1]]) + >>> a.is_hermitian + True + >>> a[0, 0] = 2*I + >>> a.is_hermitian + False + >>> a[0, 0] = x + >>> a.is_hermitian + >>> a[0, 1] = a[1, 0]*I + >>> a.is_hermitian + False + """ + if not self.is_square: + return False + + return self._eval_is_matrix_hermitian(_simplify) + + @property + def is_Identity(self) -> FuzzyBool: + if not self.is_square: + return False + return self._eval_is_Identity() + + @property + def is_lower_hessenberg(self): + r"""Checks if the matrix is in the lower-Hessenberg form. + + The lower hessenberg matrix has zero entries + above the first superdiagonal. + + Examples + ======== + + >>> from sympy import Matrix + >>> a = Matrix([[1, 2, 0, 0], [5, 2, 3, 0], [3, 4, 3, 7], [5, 6, 1, 1]]) + >>> a + Matrix([ + [1, 2, 0, 0], + [5, 2, 3, 0], + [3, 4, 3, 7], + [5, 6, 1, 1]]) + >>> a.is_lower_hessenberg + True + + See Also + ======== + + is_upper_hessenberg + is_lower + """ + return self._eval_is_lower_hessenberg() + + @property + def is_lower(self): + """Check if matrix is a lower triangular matrix. True can be returned + even if the matrix is not square. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 2, [1, 0, 0, 1]) + >>> m + Matrix([ + [1, 0], + [0, 1]]) + >>> m.is_lower + True + + >>> m = Matrix(4, 3, [0, 0, 0, 2, 0, 0, 1, 4, 0, 6, 6, 5]) + >>> m + Matrix([ + [0, 0, 0], + [2, 0, 0], + [1, 4, 0], + [6, 6, 5]]) + >>> m.is_lower + True + + >>> from sympy.abc import x, y + >>> m = Matrix(2, 2, [x**2 + y, y**2 + x, 0, x + y]) + >>> m + Matrix([ + [x**2 + y, x + y**2], + [ 0, x + y]]) + >>> m.is_lower + False + + See Also + ======== + + is_upper + is_diagonal + is_lower_hessenberg + """ + return self._eval_is_lower() + + @property + def is_square(self): + """Checks if a matrix is square. + + A matrix is square if the number of rows equals the number of columns. + The empty matrix is square by definition, since the number of rows and + the number of columns are both zero. + + Examples + ======== + + >>> from sympy import Matrix + >>> a = Matrix([[1, 2, 3], [4, 5, 6]]) + >>> b = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> c = Matrix([]) + >>> a.is_square + False + >>> b.is_square + True + >>> c.is_square + True + """ + return self.rows == self.cols + + def is_symbolic(self): + """Checks if any elements contain Symbols. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[x, y], [1, 0]]) + >>> M.is_symbolic() + True + + """ + return self._eval_is_symbolic() + + def is_symmetric(self, simplify=True): + """Check if matrix is symmetric matrix, + that is square matrix and is equal to its transpose. + + By default, simplifications occur before testing symmetry. + They can be skipped using 'simplify=False'; while speeding things a bit, + this may however induce false negatives. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 2, [0, 1, 1, 2]) + >>> m + Matrix([ + [0, 1], + [1, 2]]) + >>> m.is_symmetric() + True + + >>> m = Matrix(2, 2, [0, 1, 2, 0]) + >>> m + Matrix([ + [0, 1], + [2, 0]]) + >>> m.is_symmetric() + False + + >>> m = Matrix(2, 3, [0, 0, 0, 0, 0, 0]) + >>> m + Matrix([ + [0, 0, 0], + [0, 0, 0]]) + >>> m.is_symmetric() + False + + >>> from sympy.abc import x, y + >>> m = Matrix(3, 3, [1, x**2 + 2*x + 1, y, (x + 1)**2, 2, 0, y, 0, 3]) + >>> m + Matrix([ + [ 1, x**2 + 2*x + 1, y], + [(x + 1)**2, 2, 0], + [ y, 0, 3]]) + >>> m.is_symmetric() + True + + If the matrix is already simplified, you may speed-up is_symmetric() + test by using 'simplify=False'. + + >>> bool(m.is_symmetric(simplify=False)) + False + >>> m1 = m.expand() + >>> m1.is_symmetric(simplify=False) + True + """ + simpfunc = simplify + if not isfunction(simplify): + simpfunc = _simplify if simplify else lambda x: x + + if not self.is_square: + return False + + return self._eval_is_symmetric(simpfunc) + + @property + def is_upper_hessenberg(self): + """Checks if the matrix is the upper-Hessenberg form. + + The upper hessenberg matrix has zero entries + below the first subdiagonal. + + Examples + ======== + + >>> from sympy import Matrix + >>> a = Matrix([[1, 4, 2, 3], [3, 4, 1, 7], [0, 2, 3, 4], [0, 0, 1, 3]]) + >>> a + Matrix([ + [1, 4, 2, 3], + [3, 4, 1, 7], + [0, 2, 3, 4], + [0, 0, 1, 3]]) + >>> a.is_upper_hessenberg + True + + See Also + ======== + + is_lower_hessenberg + is_upper + """ + return self._eval_is_upper_hessenberg() + + @property + def is_upper(self): + """Check if matrix is an upper triangular matrix. True can be returned + even if the matrix is not square. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 2, [1, 0, 0, 1]) + >>> m + Matrix([ + [1, 0], + [0, 1]]) + >>> m.is_upper + True + + >>> m = Matrix(4, 3, [5, 1, 9, 0, 4, 6, 0, 0, 5, 0, 0, 0]) + >>> m + Matrix([ + [5, 1, 9], + [0, 4, 6], + [0, 0, 5], + [0, 0, 0]]) + >>> m.is_upper + True + + >>> m = Matrix(2, 3, [4, 2, 5, 6, 1, 1]) + >>> m + Matrix([ + [4, 2, 5], + [6, 1, 1]]) + >>> m.is_upper + False + + See Also + ======== + + is_lower + is_diagonal + is_upper_hessenberg + """ + return all(self[i, j].is_zero + for i in range(1, self.rows) + for j in range(min(i, self.cols))) + + @property + def is_zero_matrix(self): + """Checks if a matrix is a zero matrix. + + A matrix is zero if every element is zero. A matrix need not be square + to be considered zero. The empty matrix is zero by the principle of + vacuous truth. For a matrix that may or may not be zero (e.g. + contains a symbol), this will be None + + Examples + ======== + + >>> from sympy import Matrix, zeros + >>> from sympy.abc import x + >>> a = Matrix([[0, 0], [0, 0]]) + >>> b = zeros(3, 4) + >>> c = Matrix([[0, 1], [0, 0]]) + >>> d = Matrix([]) + >>> e = Matrix([[x, 0], [0, 0]]) + >>> a.is_zero_matrix + True + >>> b.is_zero_matrix + True + >>> c.is_zero_matrix + False + >>> d.is_zero_matrix + True + >>> e.is_zero_matrix + """ + return self._eval_is_zero_matrix() + + def values(self): + """Return non-zero values of self.""" + return self._eval_values() + + +class MatrixOperations(MatrixRequired): + """Provides basic matrix shape and elementwise + operations. Should not be instantiated directly.""" + + def _eval_adjoint(self): + return self.transpose().conjugate() + + def _eval_applyfunc(self, f): + out = self._new(self.rows, self.cols, [f(x) for x in self]) + return out + + def _eval_as_real_imag(self): # type: ignore + return (self.applyfunc(re), self.applyfunc(im)) + + def _eval_conjugate(self): + return self.applyfunc(lambda x: x.conjugate()) + + def _eval_permute_cols(self, perm): + # apply the permutation to a list + mapping = list(perm) + + def entry(i, j): + return self[i, mapping[j]] + + return self._new(self.rows, self.cols, entry) + + def _eval_permute_rows(self, perm): + # apply the permutation to a list + mapping = list(perm) + + def entry(i, j): + return self[mapping[i], j] + + return self._new(self.rows, self.cols, entry) + + def _eval_trace(self): + return sum(self[i, i] for i in range(self.rows)) + + def _eval_transpose(self): + return self._new(self.cols, self.rows, lambda i, j: self[j, i]) + + def adjoint(self): + """Conjugate transpose or Hermitian conjugation.""" + return self._eval_adjoint() + + def applyfunc(self, f): + """Apply a function to each element of the matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 2, lambda i, j: i*2+j) + >>> m + Matrix([ + [0, 1], + [2, 3]]) + >>> m.applyfunc(lambda i: 2*i) + Matrix([ + [0, 2], + [4, 6]]) + + """ + if not callable(f): + raise TypeError("`f` must be callable.") + + return self._eval_applyfunc(f) + + def as_real_imag(self, deep=True, **hints): + """Returns a tuple containing the (real, imaginary) part of matrix.""" + # XXX: Ignoring deep and hints... + return self._eval_as_real_imag() + + def conjugate(self): + """Return the by-element conjugation. + + Examples + ======== + + >>> from sympy import SparseMatrix, I + >>> a = SparseMatrix(((1, 2 + I), (3, 4), (I, -I))) + >>> a + Matrix([ + [1, 2 + I], + [3, 4], + [I, -I]]) + >>> a.C + Matrix([ + [ 1, 2 - I], + [ 3, 4], + [-I, I]]) + + See Also + ======== + + transpose: Matrix transposition + H: Hermite conjugation + sympy.matrices.matrixbase.MatrixBase.D: Dirac conjugation + """ + return self._eval_conjugate() + + def doit(self, **hints): + return self.applyfunc(lambda x: x.doit(**hints)) + + def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None, verbose=False): + """Apply evalf() to each element of self.""" + options = {'subs':subs, 'maxn':maxn, 'chop':chop, 'strict':strict, + 'quad':quad, 'verbose':verbose} + return self.applyfunc(lambda i: i.evalf(n, **options)) + + def expand(self, deep=True, modulus=None, power_base=True, power_exp=True, + mul=True, log=True, multinomial=True, basic=True, **hints): + """Apply core.function.expand to each entry of the matrix. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import Matrix + >>> Matrix(1, 1, [x*(x+1)]) + Matrix([[x*(x + 1)]]) + >>> _.expand() + Matrix([[x**2 + x]]) + + """ + return self.applyfunc(lambda x: x.expand( + deep, modulus, power_base, power_exp, mul, log, multinomial, basic, + **hints)) + + @property + def H(self): + """Return Hermite conjugate. + + Examples + ======== + + >>> from sympy import Matrix, I + >>> m = Matrix((0, 1 + I, 2, 3)) + >>> m + Matrix([ + [ 0], + [1 + I], + [ 2], + [ 3]]) + >>> m.H + Matrix([[0, 1 - I, 2, 3]]) + + See Also + ======== + + conjugate: By-element conjugation + sympy.matrices.matrixbase.MatrixBase.D: Dirac conjugation + """ + return self.T.C + + def permute(self, perm, orientation='rows', direction='forward'): + r"""Permute the rows or columns of a matrix by the given list of + swaps. + + Parameters + ========== + + perm : Permutation, list, or list of lists + A representation for the permutation. + + If it is ``Permutation``, it is used directly with some + resizing with respect to the matrix size. + + If it is specified as list of lists, + (e.g., ``[[0, 1], [0, 2]]``), then the permutation is formed + from applying the product of cycles. The direction how the + cyclic product is applied is described in below. + + If it is specified as a list, the list should represent + an array form of a permutation. (e.g., ``[1, 2, 0]``) which + would would form the swapping function + `0 \mapsto 1, 1 \mapsto 2, 2\mapsto 0`. + + orientation : 'rows', 'cols' + A flag to control whether to permute the rows or the columns + + direction : 'forward', 'backward' + A flag to control whether to apply the permutations from + the start of the list first, or from the back of the list + first. + + For example, if the permutation specification is + ``[[0, 1], [0, 2]]``, + + If the flag is set to ``'forward'``, the cycle would be + formed as `0 \mapsto 2, 2 \mapsto 1, 1 \mapsto 0`. + + If the flag is set to ``'backward'``, the cycle would be + formed as `0 \mapsto 1, 1 \mapsto 2, 2 \mapsto 0`. + + If the argument ``perm`` is not in a form of list of lists, + this flag takes no effect. + + Examples + ======== + + >>> from sympy import eye + >>> M = eye(3) + >>> M.permute([[0, 1], [0, 2]], orientation='rows', direction='forward') + Matrix([ + [0, 0, 1], + [1, 0, 0], + [0, 1, 0]]) + + >>> from sympy import eye + >>> M = eye(3) + >>> M.permute([[0, 1], [0, 2]], orientation='rows', direction='backward') + Matrix([ + [0, 1, 0], + [0, 0, 1], + [1, 0, 0]]) + + Notes + ===== + + If a bijective function + `\sigma : \mathbb{N}_0 \rightarrow \mathbb{N}_0` denotes the + permutation. + + If the matrix `A` is the matrix to permute, represented as + a horizontal or a vertical stack of vectors: + + .. math:: + A = + \begin{bmatrix} + a_0 \\ a_1 \\ \vdots \\ a_{n-1} + \end{bmatrix} = + \begin{bmatrix} + \alpha_0 & \alpha_1 & \cdots & \alpha_{n-1} + \end{bmatrix} + + If the matrix `B` is the result, the permutation of matrix rows + is defined as: + + .. math:: + B := \begin{bmatrix} + a_{\sigma(0)} \\ a_{\sigma(1)} \\ \vdots \\ a_{\sigma(n-1)} + \end{bmatrix} + + And the permutation of matrix columns is defined as: + + .. math:: + B := \begin{bmatrix} + \alpha_{\sigma(0)} & \alpha_{\sigma(1)} & + \cdots & \alpha_{\sigma(n-1)} + \end{bmatrix} + """ + from sympy.combinatorics import Permutation + + # allow british variants and `columns` + if direction == 'forwards': + direction = 'forward' + if direction == 'backwards': + direction = 'backward' + if orientation == 'columns': + orientation = 'cols' + + if direction not in ('forward', 'backward'): + raise TypeError("direction='{}' is an invalid kwarg. " + "Try 'forward' or 'backward'".format(direction)) + if orientation not in ('rows', 'cols'): + raise TypeError("orientation='{}' is an invalid kwarg. " + "Try 'rows' or 'cols'".format(orientation)) + + if not isinstance(perm, (Permutation, Iterable)): + raise ValueError( + "{} must be a list, a list of lists, " + "or a SymPy permutation object.".format(perm)) + + # ensure all swaps are in range + max_index = self.rows if orientation == 'rows' else self.cols + if not all(0 <= t <= max_index for t in flatten(list(perm))): + raise IndexError("`swap` indices out of range.") + + if perm and not isinstance(perm, Permutation) and \ + isinstance(perm[0], Iterable): + if direction == 'forward': + perm = list(reversed(perm)) + perm = Permutation(perm, size=max_index+1) + else: + perm = Permutation(perm, size=max_index+1) + + if orientation == 'rows': + return self._eval_permute_rows(perm) + if orientation == 'cols': + return self._eval_permute_cols(perm) + + def permute_cols(self, swaps, direction='forward'): + """Alias for + ``self.permute(swaps, orientation='cols', direction=direction)`` + + See Also + ======== + + permute + """ + return self.permute(swaps, orientation='cols', direction=direction) + + def permute_rows(self, swaps, direction='forward'): + """Alias for + ``self.permute(swaps, orientation='rows', direction=direction)`` + + See Also + ======== + + permute + """ + return self.permute(swaps, orientation='rows', direction=direction) + + def refine(self, assumptions=True): + """Apply refine to each element of the matrix. + + Examples + ======== + + >>> from sympy import Symbol, Matrix, Abs, sqrt, Q + >>> x = Symbol('x') + >>> Matrix([[Abs(x)**2, sqrt(x**2)],[sqrt(x**2), Abs(x)**2]]) + Matrix([ + [ Abs(x)**2, sqrt(x**2)], + [sqrt(x**2), Abs(x)**2]]) + >>> _.refine(Q.real(x)) + Matrix([ + [ x**2, Abs(x)], + [Abs(x), x**2]]) + + """ + return self.applyfunc(lambda x: refine(x, assumptions)) + + def replace(self, F, G, map=False, simultaneous=True, exact=None): + """Replaces Function F in Matrix entries with Function G. + + Examples + ======== + + >>> from sympy import symbols, Function, Matrix + >>> F, G = symbols('F, G', cls=Function) + >>> M = Matrix(2, 2, lambda i, j: F(i+j)) ; M + Matrix([ + [F(0), F(1)], + [F(1), F(2)]]) + >>> N = M.replace(F,G) + >>> N + Matrix([ + [G(0), G(1)], + [G(1), G(2)]]) + """ + return self.applyfunc( + lambda x: x.replace(F, G, map=map, simultaneous=simultaneous, exact=exact)) + + def rot90(self, k=1): + """Rotates Matrix by 90 degrees + + Parameters + ========== + + k : int + Specifies how many times the matrix is rotated by 90 degrees + (clockwise when positive, counter-clockwise when negative). + + Examples + ======== + + >>> from sympy import Matrix, symbols + >>> A = Matrix(2, 2, symbols('a:d')) + >>> A + Matrix([ + [a, b], + [c, d]]) + + Rotating the matrix clockwise one time: + + >>> A.rot90(1) + Matrix([ + [c, a], + [d, b]]) + + Rotating the matrix anticlockwise two times: + + >>> A.rot90(-2) + Matrix([ + [d, c], + [b, a]]) + """ + + mod = k%4 + if mod == 0: + return self + if mod == 1: + return self[::-1, ::].T + if mod == 2: + return self[::-1, ::-1] + if mod == 3: + return self[::, ::-1].T + + def simplify(self, **kwargs): + """Apply simplify to each element of the matrix. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import SparseMatrix, sin, cos + >>> SparseMatrix(1, 1, [x*sin(y)**2 + x*cos(y)**2]) + Matrix([[x*sin(y)**2 + x*cos(y)**2]]) + >>> _.simplify() + Matrix([[x]]) + """ + return self.applyfunc(lambda x: x.simplify(**kwargs)) + + def subs(self, *args, **kwargs): # should mirror core.basic.subs + """Return a new matrix with subs applied to each entry. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import SparseMatrix, Matrix + >>> SparseMatrix(1, 1, [x]) + Matrix([[x]]) + >>> _.subs(x, y) + Matrix([[y]]) + >>> Matrix(_).subs(y, x) + Matrix([[x]]) + """ + + if len(args) == 1 and not isinstance(args[0], (dict, set)) and iter(args[0]) and not is_sequence(args[0]): + args = (list(args[0]),) + + return self.applyfunc(lambda x: x.subs(*args, **kwargs)) + + def trace(self): + """ + Returns the trace of a square matrix i.e. the sum of the + diagonal elements. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix(2, 2, [1, 2, 3, 4]) + >>> A.trace() + 5 + + """ + if self.rows != self.cols: + raise NonSquareMatrixError() + return self._eval_trace() + + def transpose(self): + """ + Returns the transpose of the matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix(2, 2, [1, 2, 3, 4]) + >>> A.transpose() + Matrix([ + [1, 3], + [2, 4]]) + + >>> from sympy import Matrix, I + >>> m=Matrix(((1, 2+I), (3, 4))) + >>> m + Matrix([ + [1, 2 + I], + [3, 4]]) + >>> m.transpose() + Matrix([ + [ 1, 3], + [2 + I, 4]]) + >>> m.T == m.transpose() + True + + See Also + ======== + + conjugate: By-element conjugation + + """ + return self._eval_transpose() + + @property + def T(self): + '''Matrix transposition''' + return self.transpose() + + @property + def C(self): + '''By-element conjugation''' + return self.conjugate() + + def n(self, *args, **kwargs): + """Apply evalf() to each element of self.""" + return self.evalf(*args, **kwargs) + + def xreplace(self, rule): # should mirror core.basic.xreplace + """Return a new matrix with xreplace applied to each entry. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import SparseMatrix, Matrix + >>> SparseMatrix(1, 1, [x]) + Matrix([[x]]) + >>> _.xreplace({x: y}) + Matrix([[y]]) + >>> Matrix(_).xreplace({y: x}) + Matrix([[x]]) + """ + return self.applyfunc(lambda x: x.xreplace(rule)) + + def _eval_simplify(self, **kwargs): + # XXX: We can't use self.simplify here as mutable subclasses will + # override simplify and have it return None + return MatrixOperations.simplify(self, **kwargs) + + def _eval_trigsimp(self, **opts): + from sympy.simplify.trigsimp import trigsimp + return self.applyfunc(lambda x: trigsimp(x, **opts)) + + def upper_triangular(self, k=0): + """Return the elements on and above the kth diagonal of a matrix. + If k is not specified then simply returns upper-triangular portion + of a matrix + + Examples + ======== + + >>> from sympy import ones + >>> A = ones(4) + >>> A.upper_triangular() + Matrix([ + [1, 1, 1, 1], + [0, 1, 1, 1], + [0, 0, 1, 1], + [0, 0, 0, 1]]) + + >>> A.upper_triangular(2) + Matrix([ + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0], + [0, 0, 0, 0]]) + + >>> A.upper_triangular(-1) + Matrix([ + [1, 1, 1, 1], + [1, 1, 1, 1], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + + """ + + def entry(i, j): + return self[i, j] if i + k <= j else self.zero + + return self._new(self.rows, self.cols, entry) + + + def lower_triangular(self, k=0): + """Return the elements on and below the kth diagonal of a matrix. + If k is not specified then simply returns lower-triangular portion + of a matrix + + Examples + ======== + + >>> from sympy import ones + >>> A = ones(4) + >>> A.lower_triangular() + Matrix([ + [1, 0, 0, 0], + [1, 1, 0, 0], + [1, 1, 1, 0], + [1, 1, 1, 1]]) + + >>> A.lower_triangular(-2) + Matrix([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 0, 0, 0], + [1, 1, 0, 0]]) + + >>> A.lower_triangular(1) + Matrix([ + [1, 1, 0, 0], + [1, 1, 1, 0], + [1, 1, 1, 1], + [1, 1, 1, 1]]) + + """ + + def entry(i, j): + return self[i, j] if i + k >= j else self.zero + + return self._new(self.rows, self.cols, entry) + + + +class MatrixArithmetic(MatrixRequired): + """Provides basic matrix arithmetic operations. + Should not be instantiated directly.""" + + _op_priority = 10.01 + + def _eval_Abs(self): + return self._new(self.rows, self.cols, lambda i, j: Abs(self[i, j])) + + def _eval_add(self, other): + return self._new(self.rows, self.cols, + lambda i, j: self[i, j] + other[i, j]) + + def _eval_matrix_mul(self, other): + def entry(i, j): + vec = [self[i,k]*other[k,j] for k in range(self.cols)] + try: + return Add(*vec) + except (TypeError, SympifyError): + # Some matrices don't work with `sum` or `Add` + # They don't work with `sum` because `sum` tries to add `0` + # Fall back to a safe way to multiply if the `Add` fails. + return reduce(lambda a, b: a + b, vec) + + return self._new(self.rows, other.cols, entry) + + def _eval_matrix_mul_elementwise(self, other): + return self._new(self.rows, self.cols, lambda i, j: self[i,j]*other[i,j]) + + def _eval_matrix_rmul(self, other): + def entry(i, j): + return sum(other[i,k]*self[k,j] for k in range(other.cols)) + return self._new(other.rows, self.cols, entry) + + def _eval_pow_by_recursion(self, num): + if num == 1: + return self + + if num % 2 == 1: + a, b = self, self._eval_pow_by_recursion(num - 1) + else: + a = b = self._eval_pow_by_recursion(num // 2) + + return a.multiply(b) + + def _eval_pow_by_cayley(self, exp): + from sympy.discrete.recurrences import linrec_coeffs + row = self.shape[0] + p = self.charpoly() + + coeffs = (-p).all_coeffs()[1:] + coeffs = linrec_coeffs(coeffs, exp) + new_mat = self.eye(row) + ans = self.zeros(row) + + for i in range(row): + ans += coeffs[i]*new_mat + new_mat *= self + + return ans + + def _eval_pow_by_recursion_dotprodsimp(self, num, prevsimp=None): + if prevsimp is None: + prevsimp = [True]*len(self) + + if num == 1: + return self + + if num % 2 == 1: + a, b = self, self._eval_pow_by_recursion_dotprodsimp(num - 1, + prevsimp=prevsimp) + else: + a = b = self._eval_pow_by_recursion_dotprodsimp(num // 2, + prevsimp=prevsimp) + + m = a.multiply(b, dotprodsimp=False) + lenm = len(m) + elems = [None]*lenm + + for i in range(lenm): + if prevsimp[i]: + elems[i], prevsimp[i] = _dotprodsimp(m[i], withsimp=True) + else: + elems[i] = m[i] + + return m._new(m.rows, m.cols, elems) + + def _eval_scalar_mul(self, other): + return self._new(self.rows, self.cols, lambda i, j: self[i,j]*other) + + def _eval_scalar_rmul(self, other): + return self._new(self.rows, self.cols, lambda i, j: other*self[i,j]) + + def _eval_Mod(self, other): + return self._new(self.rows, self.cols, lambda i, j: Mod(self[i, j], other)) + + # Python arithmetic functions + def __abs__(self): + """Returns a new matrix with entry-wise absolute values.""" + return self._eval_Abs() + + @call_highest_priority('__radd__') + def __add__(self, other): + """Return self + other, raising ShapeError if shapes do not match.""" + if isinstance(other, NDimArray): # Matrix and array addition is currently not implemented + return NotImplemented + other = _matrixify(other) + # matrix-like objects can have shapes. This is + # our first sanity check. + if hasattr(other, 'shape'): + if self.shape != other.shape: + raise ShapeError("Matrix size mismatch: %s + %s" % ( + self.shape, other.shape)) + + # honest SymPy matrices defer to their class's routine + if getattr(other, 'is_Matrix', False): + # call the highest-priority class's _eval_add + a, b = self, other + if a.__class__ != classof(a, b): + b, a = a, b + return a._eval_add(b) + # Matrix-like objects can be passed to CommonMatrix routines directly. + if getattr(other, 'is_MatrixLike', False): + return MatrixArithmetic._eval_add(self, other) + + raise TypeError('cannot add %s and %s' % (type(self), type(other))) + + @call_highest_priority('__rtruediv__') + def __truediv__(self, other): + return self * (self.one / other) + + @call_highest_priority('__rmatmul__') + def __matmul__(self, other): + other = _matrixify(other) + if not getattr(other, 'is_Matrix', False) and not getattr(other, 'is_MatrixLike', False): + return NotImplemented + + return self.__mul__(other) + + def __mod__(self, other): + return self.applyfunc(lambda x: x % other) + + @call_highest_priority('__rmul__') + def __mul__(self, other): + """Return self*other where other is either a scalar or a matrix + of compatible dimensions. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[1, 2, 3], [4, 5, 6]]) + >>> 2*A == A*2 == Matrix([[2, 4, 6], [8, 10, 12]]) + True + >>> B = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> A*B + Matrix([ + [30, 36, 42], + [66, 81, 96]]) + >>> B*A + Traceback (most recent call last): + ... + ShapeError: Matrices size mismatch. + >>> + + See Also + ======== + + matrix_multiply_elementwise + """ + + return self.multiply(other) + + def multiply(self, other, dotprodsimp=None): + """Same as __mul__() but with optional simplification. + + Parameters + ========== + + dotprodsimp : bool, optional + Specifies whether intermediate term algebraic simplification is used + during matrix multiplications to control expression blowup and thus + speed up calculation. Default is off. + """ + + isimpbool = _get_intermediate_simp_bool(False, dotprodsimp) + other = _matrixify(other) + # matrix-like objects can have shapes. This is + # our first sanity check. Double check other is not explicitly not a Matrix. + if (hasattr(other, 'shape') and len(other.shape) == 2 and + (getattr(other, 'is_Matrix', True) or + getattr(other, 'is_MatrixLike', True))): + if self.shape[1] != other.shape[0]: + raise ShapeError("Matrix size mismatch: %s * %s." % ( + self.shape, other.shape)) + + # honest SymPy matrices defer to their class's routine + if getattr(other, 'is_Matrix', False): + m = self._eval_matrix_mul(other) + if isimpbool: + return m._new(m.rows, m.cols, [_dotprodsimp(e) for e in m]) + return m + + # Matrix-like objects can be passed to CommonMatrix routines directly. + if getattr(other, 'is_MatrixLike', False): + return MatrixArithmetic._eval_matrix_mul(self, other) + + # if 'other' is not iterable then scalar multiplication. + if not isinstance(other, Iterable): + try: + return self._eval_scalar_mul(other) + except TypeError: + pass + + return NotImplemented + + def multiply_elementwise(self, other): + """Return the Hadamard product (elementwise product) of A and B + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[0, 1, 2], [3, 4, 5]]) + >>> B = Matrix([[1, 10, 100], [100, 10, 1]]) + >>> A.multiply_elementwise(B) + Matrix([ + [ 0, 10, 200], + [300, 40, 5]]) + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.cross + sympy.matrices.matrixbase.MatrixBase.dot + multiply + """ + if self.shape != other.shape: + raise ShapeError("Matrix shapes must agree {} != {}".format(self.shape, other.shape)) + + return self._eval_matrix_mul_elementwise(other) + + def __neg__(self): + return self._eval_scalar_mul(-1) + + @call_highest_priority('__rpow__') + def __pow__(self, exp): + """Return self**exp a scalar or symbol.""" + + return self.pow(exp) + + + def pow(self, exp, method=None): + r"""Return self**exp a scalar or symbol. + + Parameters + ========== + + method : multiply, mulsimp, jordan, cayley + If multiply then it returns exponentiation using recursion. + If jordan then Jordan form exponentiation will be used. + If cayley then the exponentiation is done using Cayley-Hamilton + theorem. + If mulsimp then the exponentiation is done using recursion + with dotprodsimp. This specifies whether intermediate term + algebraic simplification is used during naive matrix power to + control expression blowup and thus speed up calculation. + If None, then it heuristically decides which method to use. + + """ + + if method is not None and method not in ['multiply', 'mulsimp', 'jordan', 'cayley']: + raise TypeError('No such method') + if self.rows != self.cols: + raise NonSquareMatrixError() + a = self + jordan_pow = getattr(a, '_matrix_pow_by_jordan_blocks', None) + exp = sympify(exp) + + if exp.is_zero: + return a._new(a.rows, a.cols, lambda i, j: int(i == j)) + if exp == 1: + return a + + diagonal = getattr(a, 'is_diagonal', None) + if diagonal is not None and diagonal(): + return a._new(a.rows, a.cols, lambda i, j: a[i,j]**exp if i == j else 0) + + if exp.is_Number and exp % 1 == 0: + if a.rows == 1: + return a._new([[a[0]**exp]]) + if exp < 0: + exp = -exp + a = a.inv() + # When certain conditions are met, + # Jordan block algorithm is faster than + # computation by recursion. + if method == 'jordan': + try: + return jordan_pow(exp) + except MatrixError: + if method == 'jordan': + raise + + elif method == 'cayley': + if not exp.is_Number or exp % 1 != 0: + raise ValueError("cayley method is only valid for integer powers") + return a._eval_pow_by_cayley(exp) + + elif method == "mulsimp": + if not exp.is_Number or exp % 1 != 0: + raise ValueError("mulsimp method is only valid for integer powers") + return a._eval_pow_by_recursion_dotprodsimp(exp) + + elif method == "multiply": + if not exp.is_Number or exp % 1 != 0: + raise ValueError("multiply method is only valid for integer powers") + return a._eval_pow_by_recursion(exp) + + elif method is None and exp.is_Number and exp % 1 == 0: + if exp.is_Float: + exp = Integer(exp) + # Decide heuristically which method to apply + if a.rows == 2 and exp > 100000: + return jordan_pow(exp) + elif _get_intermediate_simp_bool(True, None): + return a._eval_pow_by_recursion_dotprodsimp(exp) + elif exp > 10000: + return a._eval_pow_by_cayley(exp) + else: + return a._eval_pow_by_recursion(exp) + + if jordan_pow: + try: + return jordan_pow(exp) + except NonInvertibleMatrixError: + # Raised by jordan_pow on zero determinant matrix unless exp is + # definitely known to be a non-negative integer. + # Here we raise if n is definitely not a non-negative integer + # but otherwise we can leave this as an unevaluated MatPow. + if exp.is_integer is False or exp.is_nonnegative is False: + raise + + from sympy.matrices.expressions import MatPow + return MatPow(a, exp) + + @call_highest_priority('__add__') + def __radd__(self, other): + return self + other + + @call_highest_priority('__matmul__') + def __rmatmul__(self, other): + other = _matrixify(other) + if not getattr(other, 'is_Matrix', False) and not getattr(other, 'is_MatrixLike', False): + return NotImplemented + + return self.__rmul__(other) + + @call_highest_priority('__mul__') + def __rmul__(self, other): + return self.rmultiply(other) + + def rmultiply(self, other, dotprodsimp=None): + """Same as __rmul__() but with optional simplification. + + Parameters + ========== + + dotprodsimp : bool, optional + Specifies whether intermediate term algebraic simplification is used + during matrix multiplications to control expression blowup and thus + speed up calculation. Default is off. + """ + isimpbool = _get_intermediate_simp_bool(False, dotprodsimp) + other = _matrixify(other) + # matrix-like objects can have shapes. This is + # our first sanity check. Double check other is not explicitly not a Matrix. + if (hasattr(other, 'shape') and len(other.shape) == 2 and + (getattr(other, 'is_Matrix', True) or + getattr(other, 'is_MatrixLike', True))): + if self.shape[0] != other.shape[1]: + raise ShapeError("Matrix size mismatch.") + + # honest SymPy matrices defer to their class's routine + if getattr(other, 'is_Matrix', False): + m = self._eval_matrix_rmul(other) + if isimpbool: + return m._new(m.rows, m.cols, [_dotprodsimp(e) for e in m]) + return m + # Matrix-like objects can be passed to CommonMatrix routines directly. + if getattr(other, 'is_MatrixLike', False): + return MatrixArithmetic._eval_matrix_rmul(self, other) + + # if 'other' is not iterable then scalar multiplication. + if not isinstance(other, Iterable): + try: + return self._eval_scalar_rmul(other) + except TypeError: + pass + + return NotImplemented + + @call_highest_priority('__sub__') + def __rsub__(self, a): + return (-self) + a + + @call_highest_priority('__rsub__') + def __sub__(self, a): + return self + (-a) + + +class MatrixCommon(MatrixArithmetic, MatrixOperations, MatrixProperties, + MatrixSpecial, MatrixShaping): + """All common matrix operations including basic arithmetic, shaping, + and special matrices like `zeros`, and `eye`.""" + _diff_wrt: bool = True + + +class _MinimalMatrix: + """Class providing the minimum functionality + for a matrix-like object and implementing every method + required for a `MatrixRequired`. This class does not have everything + needed to become a full-fledged SymPy object, but it will satisfy the + requirements of anything inheriting from `MatrixRequired`. If you wish + to make a specialized matrix type, make sure to implement these + methods and properties with the exception of `__init__` and `__repr__` + which are included for convenience.""" + + is_MatrixLike = True + _sympify = staticmethod(sympify) + _class_priority = 3 + zero = S.Zero + one = S.One + + is_Matrix = True + is_MatrixExpr = False + + @classmethod + def _new(cls, *args, **kwargs): + return cls(*args, **kwargs) + + def __init__(self, rows, cols=None, mat=None, copy=False): + if isfunction(mat): + # if we passed in a function, use that to populate the indices + mat = [mat(i, j) for i in range(rows) for j in range(cols)] + if cols is None and mat is None: + mat = rows + rows, cols = getattr(mat, 'shape', (rows, cols)) + try: + # if we passed in a list of lists, flatten it and set the size + if cols is None and mat is None: + mat = rows + cols = len(mat[0]) + rows = len(mat) + mat = [x for l in mat for x in l] + except (IndexError, TypeError): + pass + self.mat = tuple(self._sympify(x) for x in mat) + self.rows, self.cols = rows, cols + if self.rows is None or self.cols is None: + raise NotImplementedError("Cannot initialize matrix with given parameters") + + def __getitem__(self, key): + def _normalize_slices(row_slice, col_slice): + """Ensure that row_slice and col_slice do not have + `None` in their arguments. Any integers are converted + to slices of length 1""" + if not isinstance(row_slice, slice): + row_slice = slice(row_slice, row_slice + 1, None) + row_slice = slice(*row_slice.indices(self.rows)) + + if not isinstance(col_slice, slice): + col_slice = slice(col_slice, col_slice + 1, None) + col_slice = slice(*col_slice.indices(self.cols)) + + return (row_slice, col_slice) + + def _coord_to_index(i, j): + """Return the index in _mat corresponding + to the (i,j) position in the matrix. """ + return i * self.cols + j + + if isinstance(key, tuple): + i, j = key + if isinstance(i, slice) or isinstance(j, slice): + # if the coordinates are not slices, make them so + # and expand the slices so they don't contain `None` + i, j = _normalize_slices(i, j) + + rowsList, colsList = list(range(self.rows))[i], \ + list(range(self.cols))[j] + indices = (i * self.cols + j for i in rowsList for j in + colsList) + return self._new(len(rowsList), len(colsList), + [self.mat[i] for i in indices]) + + # if the key is a tuple of ints, change + # it to an array index + key = _coord_to_index(i, j) + return self.mat[key] + + def __eq__(self, other): + try: + classof(self, other) + except TypeError: + return False + return ( + self.shape == other.shape and list(self) == list(other)) + + def __len__(self): + return self.rows*self.cols + + def __repr__(self): + return "_MinimalMatrix({}, {}, {})".format(self.rows, self.cols, + self.mat) + + @property + def shape(self): + return (self.rows, self.cols) + + +class _CastableMatrix: # this is needed here ONLY FOR TESTS. + def as_mutable(self): + return self + + def as_immutable(self): + return self + + +class _MatrixWrapper: + """Wrapper class providing the minimum functionality for a matrix-like + object: .rows, .cols, .shape, indexability, and iterability. CommonMatrix + math operations should work on matrix-like objects. This one is intended for + matrix-like objects which use the same indexing format as SymPy with respect + to returning matrix elements instead of rows for non-tuple indexes. + """ + + is_Matrix = False # needs to be here because of __getattr__ + is_MatrixLike = True + + def __init__(self, mat, shape): + self.mat = mat + self.shape = shape + self.rows, self.cols = shape + + def __getitem__(self, key): + if isinstance(key, tuple): + return sympify(self.mat.__getitem__(key)) + + return sympify(self.mat.__getitem__((key // self.rows, key % self.cols))) + + def __iter__(self): # supports numpy.matrix and numpy.array + mat = self.mat + cols = self.cols + + return iter(sympify(mat[r, c]) for r in range(self.rows) for c in range(cols)) + + +def _matrixify(mat): + """If `mat` is a Matrix or is matrix-like, + return a Matrix or MatrixWrapper object. Otherwise + `mat` is passed through without modification.""" + + if getattr(mat, 'is_Matrix', False) or getattr(mat, 'is_MatrixLike', False): + return mat + + if not(getattr(mat, 'is_Matrix', True) or getattr(mat, 'is_MatrixLike', True)): + return mat + + shape = None + + if hasattr(mat, 'shape'): # numpy, scipy.sparse + if len(mat.shape) == 2: + shape = mat.shape + elif hasattr(mat, 'rows') and hasattr(mat, 'cols'): # mpmath + shape = (mat.rows, mat.cols) + + if shape: + return _MatrixWrapper(mat, shape) + + return mat + + +def a2idx(j, n=None): + """Return integer after making positive and validating against n.""" + if not isinstance(j, int): + jindex = getattr(j, '__index__', None) + if jindex is not None: + j = jindex() + else: + raise IndexError("Invalid index a[%r]" % (j,)) + if n is not None: + if j < 0: + j += n + if not (j >= 0 and j < n): + raise IndexError("Index out of range: a[%s]" % (j,)) + return int(j) + + +def classof(A, B): + """ + Get the type of the result when combining matrices of different types. + + Currently the strategy is that immutability is contagious. + + Examples + ======== + + >>> from sympy import Matrix, ImmutableMatrix + >>> from sympy.matrices.matrixbase import classof + >>> M = Matrix([[1, 2], [3, 4]]) # a Mutable Matrix + >>> IM = ImmutableMatrix([[1, 2], [3, 4]]) + >>> classof(M, IM) + + """ + priority_A = getattr(A, '_class_priority', None) + priority_B = getattr(B, '_class_priority', None) + if None not in (priority_A, priority_B): + if A._class_priority > B._class_priority: + return A.__class__ + else: + return B.__class__ + + try: + import numpy + except ImportError: + pass + else: + if isinstance(A, numpy.ndarray): + return B.__class__ + if isinstance(B, numpy.ndarray): + return A.__class__ + + raise TypeError("Incompatible classes %s, %s" % (A.__class__, B.__class__)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/decompositions.py b/venv/lib/python3.10/site-packages/sympy/matrices/decompositions.py new file mode 100644 index 0000000000000000000000000000000000000000..a8dd466d84c957b870396a050fd25ec21e7113a3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/decompositions.py @@ -0,0 +1,1621 @@ +import copy + +from sympy.core import S +from sympy.core.function import expand_mul +from sympy.functions.elementary.miscellaneous import Min, sqrt +from sympy.functions.elementary.complexes import sign + +from .exceptions import NonSquareMatrixError, NonPositiveDefiniteMatrixError +from .utilities import _get_intermediate_simp, _iszero +from .determinant import _find_reasonable_pivot_naive + + +def _rank_decomposition(M, iszerofunc=_iszero, simplify=False): + r"""Returns a pair of matrices (`C`, `F`) with matching rank + such that `A = C F`. + + Parameters + ========== + + iszerofunc : Function, optional + A function used for detecting whether an element can + act as a pivot. ``lambda x: x.is_zero`` is used by default. + + simplify : Bool or Function, optional + A function used to simplify elements when looking for a + pivot. By default SymPy's ``simplify`` is used. + + Returns + ======= + + (C, F) : Matrices + `C` and `F` are full-rank matrices with rank as same as `A`, + whose product gives `A`. + + See Notes for additional mathematical details. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([ + ... [1, 3, 1, 4], + ... [2, 7, 3, 9], + ... [1, 5, 3, 1], + ... [1, 2, 0, 8] + ... ]) + >>> C, F = A.rank_decomposition() + >>> C + Matrix([ + [1, 3, 4], + [2, 7, 9], + [1, 5, 1], + [1, 2, 8]]) + >>> F + Matrix([ + [1, 0, -2, 0], + [0, 1, 1, 0], + [0, 0, 0, 1]]) + >>> C * F == A + True + + Notes + ===== + + Obtaining `F`, an RREF of `A`, is equivalent to creating a + product + + .. math:: + E_n E_{n-1} ... E_1 A = F + + where `E_n, E_{n-1}, \dots, E_1` are the elimination matrices or + permutation matrices equivalent to each row-reduction step. + + The inverse of the same product of elimination matrices gives + `C`: + + .. math:: + C = \left(E_n E_{n-1} \dots E_1\right)^{-1} + + It is not necessary, however, to actually compute the inverse: + the columns of `C` are those from the original matrix with the + same column indices as the indices of the pivot columns of `F`. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Rank_factorization + + .. [2] Piziak, R.; Odell, P. L. (1 June 1999). + "Full Rank Factorization of Matrices". + Mathematics Magazine. 72 (3): 193. doi:10.2307/2690882 + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.rref + """ + + F, pivot_cols = M.rref(simplify=simplify, iszerofunc=iszerofunc, + pivots=True) + rank = len(pivot_cols) + + C = M.extract(range(M.rows), pivot_cols) + F = F[:rank, :] + + return C, F + + +def _liupc(M): + """Liu's algorithm, for pre-determination of the Elimination Tree of + the given matrix, used in row-based symbolic Cholesky factorization. + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> S = SparseMatrix([ + ... [1, 0, 3, 2], + ... [0, 0, 1, 0], + ... [4, 0, 0, 5], + ... [0, 6, 7, 0]]) + >>> S.liupc() + ([[0], [], [0], [1, 2]], [4, 3, 4, 4]) + + References + ========== + + .. [1] Symbolic Sparse Cholesky Factorization using Elimination Trees, + Jeroen Van Grondelle (1999) + https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.39.7582 + """ + # Algorithm 2.4, p 17 of reference + + # get the indices of the elements that are non-zero on or below diag + R = [[] for r in range(M.rows)] + + for r, c, _ in M.row_list(): + if c <= r: + R[r].append(c) + + inf = len(R) # nothing will be this large + parent = [inf]*M.rows + virtual = [inf]*M.rows + + for r in range(M.rows): + for c in R[r][:-1]: + while virtual[c] < r: + t = virtual[c] + virtual[c] = r + c = t + + if virtual[c] == inf: + parent[c] = virtual[c] = r + + return R, parent + +def _row_structure_symbolic_cholesky(M): + """Symbolic cholesky factorization, for pre-determination of the + non-zero structure of the Cholesky factororization. + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> S = SparseMatrix([ + ... [1, 0, 3, 2], + ... [0, 0, 1, 0], + ... [4, 0, 0, 5], + ... [0, 6, 7, 0]]) + >>> S.row_structure_symbolic_cholesky() + [[0], [], [0], [1, 2]] + + References + ========== + + .. [1] Symbolic Sparse Cholesky Factorization using Elimination Trees, + Jeroen Van Grondelle (1999) + https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.39.7582 + """ + + R, parent = M.liupc() + inf = len(R) # this acts as infinity + Lrow = copy.deepcopy(R) + + for k in range(M.rows): + for j in R[k]: + while j != inf and j != k: + Lrow[k].append(j) + j = parent[j] + + Lrow[k] = sorted(set(Lrow[k])) + + return Lrow + + +def _cholesky(M, hermitian=True): + """Returns the Cholesky-type decomposition L of a matrix A + such that L * L.H == A if hermitian flag is True, + or L * L.T == A if hermitian is False. + + A must be a Hermitian positive-definite matrix if hermitian is True, + or a symmetric matrix if it is False. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + >>> A.cholesky() + Matrix([ + [ 5, 0, 0], + [ 3, 3, 0], + [-1, 1, 3]]) + >>> A.cholesky() * A.cholesky().T + Matrix([ + [25, 15, -5], + [15, 18, 0], + [-5, 0, 11]]) + + The matrix can have complex entries: + + >>> from sympy import I + >>> A = Matrix(((9, 3*I), (-3*I, 5))) + >>> A.cholesky() + Matrix([ + [ 3, 0], + [-I, 2]]) + >>> A.cholesky() * A.cholesky().H + Matrix([ + [ 9, 3*I], + [-3*I, 5]]) + + Non-hermitian Cholesky-type decomposition may be useful when the + matrix is not positive-definite. + + >>> A = Matrix([[1, 2], [2, 1]]) + >>> L = A.cholesky(hermitian=False) + >>> L + Matrix([ + [1, 0], + [2, sqrt(3)*I]]) + >>> L*L.T == A + True + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.LDLdecomposition + sympy.matrices.matrixbase.MatrixBase.LUdecomposition + QRdecomposition + """ + + from .dense import MutableDenseMatrix + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + if hermitian and not M.is_hermitian: + raise ValueError("Matrix must be Hermitian.") + if not hermitian and not M.is_symmetric(): + raise ValueError("Matrix must be symmetric.") + + L = MutableDenseMatrix.zeros(M.rows, M.rows) + + if hermitian: + for i in range(M.rows): + for j in range(i): + L[i, j] = ((1 / L[j, j])*(M[i, j] - + sum(L[i, k]*L[j, k].conjugate() for k in range(j)))) + + Lii2 = (M[i, i] - + sum(L[i, k]*L[i, k].conjugate() for k in range(i))) + + if Lii2.is_positive is False: + raise NonPositiveDefiniteMatrixError( + "Matrix must be positive-definite") + + L[i, i] = sqrt(Lii2) + + else: + for i in range(M.rows): + for j in range(i): + L[i, j] = ((1 / L[j, j])*(M[i, j] - + sum(L[i, k]*L[j, k] for k in range(j)))) + + L[i, i] = sqrt(M[i, i] - + sum(L[i, k]**2 for k in range(i))) + + return M._new(L) + +def _cholesky_sparse(M, hermitian=True): + """ + Returns the Cholesky decomposition L of a matrix A + such that L * L.T = A + + A must be a square, symmetric, positive-definite + and non-singular matrix + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> A = SparseMatrix(((25,15,-5),(15,18,0),(-5,0,11))) + >>> A.cholesky() + Matrix([ + [ 5, 0, 0], + [ 3, 3, 0], + [-1, 1, 3]]) + >>> A.cholesky() * A.cholesky().T == A + True + + The matrix can have complex entries: + + >>> from sympy import I + >>> A = SparseMatrix(((9, 3*I), (-3*I, 5))) + >>> A.cholesky() + Matrix([ + [ 3, 0], + [-I, 2]]) + >>> A.cholesky() * A.cholesky().H + Matrix([ + [ 9, 3*I], + [-3*I, 5]]) + + Non-hermitian Cholesky-type decomposition may be useful when the + matrix is not positive-definite. + + >>> A = SparseMatrix([[1, 2], [2, 1]]) + >>> L = A.cholesky(hermitian=False) + >>> L + Matrix([ + [1, 0], + [2, sqrt(3)*I]]) + >>> L*L.T == A + True + + See Also + ======== + + sympy.matrices.sparse.SparseMatrix.LDLdecomposition + sympy.matrices.matrixbase.MatrixBase.LUdecomposition + QRdecomposition + """ + + from .dense import MutableDenseMatrix + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + if hermitian and not M.is_hermitian: + raise ValueError("Matrix must be Hermitian.") + if not hermitian and not M.is_symmetric(): + raise ValueError("Matrix must be symmetric.") + + dps = _get_intermediate_simp(expand_mul, expand_mul) + Crowstruc = M.row_structure_symbolic_cholesky() + C = MutableDenseMatrix.zeros(M.rows) + + for i in range(len(Crowstruc)): + for j in Crowstruc[i]: + if i != j: + C[i, j] = M[i, j] + summ = 0 + + for p1 in Crowstruc[i]: + if p1 < j: + for p2 in Crowstruc[j]: + if p2 < j: + if p1 == p2: + if hermitian: + summ += C[i, p1]*C[j, p1].conjugate() + else: + summ += C[i, p1]*C[j, p1] + else: + break + else: + break + + C[i, j] = dps((C[i, j] - summ) / C[j, j]) + + else: # i == j + C[j, j] = M[j, j] + summ = 0 + + for k in Crowstruc[j]: + if k < j: + if hermitian: + summ += C[j, k]*C[j, k].conjugate() + else: + summ += C[j, k]**2 + else: + break + + Cjj2 = dps(C[j, j] - summ) + + if hermitian and Cjj2.is_positive is False: + raise NonPositiveDefiniteMatrixError( + "Matrix must be positive-definite") + + C[j, j] = sqrt(Cjj2) + + return M._new(C) + + +def _LDLdecomposition(M, hermitian=True): + """Returns the LDL Decomposition (L, D) of matrix A, + such that L * D * L.H == A if hermitian flag is True, or + L * D * L.T == A if hermitian is False. + This method eliminates the use of square root. + Further this ensures that all the diagonal entries of L are 1. + A must be a Hermitian positive-definite matrix if hermitian is True, + or a symmetric matrix otherwise. + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + >>> L, D = A.LDLdecomposition() + >>> L + Matrix([ + [ 1, 0, 0], + [ 3/5, 1, 0], + [-1/5, 1/3, 1]]) + >>> D + Matrix([ + [25, 0, 0], + [ 0, 9, 0], + [ 0, 0, 9]]) + >>> L * D * L.T * A.inv() == eye(A.rows) + True + + The matrix can have complex entries: + + >>> from sympy import I + >>> A = Matrix(((9, 3*I), (-3*I, 5))) + >>> L, D = A.LDLdecomposition() + >>> L + Matrix([ + [ 1, 0], + [-I/3, 1]]) + >>> D + Matrix([ + [9, 0], + [0, 4]]) + >>> L*D*L.H == A + True + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.cholesky + sympy.matrices.matrixbase.MatrixBase.LUdecomposition + QRdecomposition + """ + + from .dense import MutableDenseMatrix + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + if hermitian and not M.is_hermitian: + raise ValueError("Matrix must be Hermitian.") + if not hermitian and not M.is_symmetric(): + raise ValueError("Matrix must be symmetric.") + + D = MutableDenseMatrix.zeros(M.rows, M.rows) + L = MutableDenseMatrix.eye(M.rows) + + if hermitian: + for i in range(M.rows): + for j in range(i): + L[i, j] = (1 / D[j, j])*(M[i, j] - sum( + L[i, k]*L[j, k].conjugate()*D[k, k] for k in range(j))) + + D[i, i] = (M[i, i] - + sum(L[i, k]*L[i, k].conjugate()*D[k, k] for k in range(i))) + + if D[i, i].is_positive is False: + raise NonPositiveDefiniteMatrixError( + "Matrix must be positive-definite") + + else: + for i in range(M.rows): + for j in range(i): + L[i, j] = (1 / D[j, j])*(M[i, j] - sum( + L[i, k]*L[j, k]*D[k, k] for k in range(j))) + + D[i, i] = M[i, i] - sum(L[i, k]**2*D[k, k] for k in range(i)) + + return M._new(L), M._new(D) + +def _LDLdecomposition_sparse(M, hermitian=True): + """ + Returns the LDL Decomposition (matrices ``L`` and ``D``) of matrix + ``A``, such that ``L * D * L.T == A``. ``A`` must be a square, + symmetric, positive-definite and non-singular. + + This method eliminates the use of square root and ensures that all + the diagonal entries of L are 1. + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + >>> L, D = A.LDLdecomposition() + >>> L + Matrix([ + [ 1, 0, 0], + [ 3/5, 1, 0], + [-1/5, 1/3, 1]]) + >>> D + Matrix([ + [25, 0, 0], + [ 0, 9, 0], + [ 0, 0, 9]]) + >>> L * D * L.T == A + True + + """ + + from .dense import MutableDenseMatrix + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + if hermitian and not M.is_hermitian: + raise ValueError("Matrix must be Hermitian.") + if not hermitian and not M.is_symmetric(): + raise ValueError("Matrix must be symmetric.") + + dps = _get_intermediate_simp(expand_mul, expand_mul) + Lrowstruc = M.row_structure_symbolic_cholesky() + L = MutableDenseMatrix.eye(M.rows) + D = MutableDenseMatrix.zeros(M.rows, M.cols) + + for i in range(len(Lrowstruc)): + for j in Lrowstruc[i]: + if i != j: + L[i, j] = M[i, j] + summ = 0 + + for p1 in Lrowstruc[i]: + if p1 < j: + for p2 in Lrowstruc[j]: + if p2 < j: + if p1 == p2: + if hermitian: + summ += L[i, p1]*L[j, p1].conjugate()*D[p1, p1] + else: + summ += L[i, p1]*L[j, p1]*D[p1, p1] + else: + break + else: + break + + L[i, j] = dps((L[i, j] - summ) / D[j, j]) + + else: # i == j + D[i, i] = M[i, i] + summ = 0 + + for k in Lrowstruc[i]: + if k < i: + if hermitian: + summ += L[i, k]*L[i, k].conjugate()*D[k, k] + else: + summ += L[i, k]**2*D[k, k] + else: + break + + D[i, i] = dps(D[i, i] - summ) + + if hermitian and D[i, i].is_positive is False: + raise NonPositiveDefiniteMatrixError( + "Matrix must be positive-definite") + + return M._new(L), M._new(D) + + +def _LUdecomposition(M, iszerofunc=_iszero, simpfunc=None, rankcheck=False): + """Returns (L, U, perm) where L is a lower triangular matrix with unit + diagonal, U is an upper triangular matrix, and perm is a list of row + swap index pairs. If A is the original matrix, then + ``A = (L*U).permuteBkwd(perm)``, and the row permutation matrix P such + that $P A = L U$ can be computed by ``P = eye(A.rows).permuteFwd(perm)``. + + See documentation for LUCombined for details about the keyword argument + rankcheck, iszerofunc, and simpfunc. + + Parameters + ========== + + rankcheck : bool, optional + Determines if this function should detect the rank + deficiency of the matrixis and should raise a + ``ValueError``. + + iszerofunc : function, optional + A function which determines if a given expression is zero. + + The function should be a callable that takes a single + SymPy expression and returns a 3-valued boolean value + ``True``, ``False``, or ``None``. + + It is internally used by the pivot searching algorithm. + See the notes section for a more information about the + pivot searching algorithm. + + simpfunc : function or None, optional + A function that simplifies the input. + + If this is specified as a function, this function should be + a callable that takes a single SymPy expression and returns + an another SymPy expression that is algebraically + equivalent. + + If ``None``, it indicates that the pivot search algorithm + should not attempt to simplify any candidate pivots. + + It is internally used by the pivot searching algorithm. + See the notes section for a more information about the + pivot searching algorithm. + + Examples + ======== + + >>> from sympy import Matrix + >>> a = Matrix([[4, 3], [6, 3]]) + >>> L, U, _ = a.LUdecomposition() + >>> L + Matrix([ + [ 1, 0], + [3/2, 1]]) + >>> U + Matrix([ + [4, 3], + [0, -3/2]]) + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.cholesky + sympy.matrices.dense.DenseMatrix.LDLdecomposition + QRdecomposition + LUdecomposition_Simple + LUdecompositionFF + LUsolve + """ + + combined, p = M.LUdecomposition_Simple(iszerofunc=iszerofunc, + simpfunc=simpfunc, rankcheck=rankcheck) + + # L is lower triangular ``M.rows x M.rows`` + # U is upper triangular ``M.rows x M.cols`` + # L has unit diagonal. For each column in combined, the subcolumn + # below the diagonal of combined is shared by L. + # If L has more columns than combined, then the remaining subcolumns + # below the diagonal of L are zero. + # The upper triangular portion of L and combined are equal. + def entry_L(i, j): + if i < j: + # Super diagonal entry + return M.zero + elif i == j: + return M.one + elif j < combined.cols: + return combined[i, j] + + # Subdiagonal entry of L with no corresponding + # entry in combined + return M.zero + + def entry_U(i, j): + return M.zero if i > j else combined[i, j] + + L = M._new(combined.rows, combined.rows, entry_L) + U = M._new(combined.rows, combined.cols, entry_U) + + return L, U, p + +def _LUdecomposition_Simple(M, iszerofunc=_iszero, simpfunc=None, + rankcheck=False): + r"""Compute the PLU decomposition of the matrix. + + Parameters + ========== + + rankcheck : bool, optional + Determines if this function should detect the rank + deficiency of the matrixis and should raise a + ``ValueError``. + + iszerofunc : function, optional + A function which determines if a given expression is zero. + + The function should be a callable that takes a single + SymPy expression and returns a 3-valued boolean value + ``True``, ``False``, or ``None``. + + It is internally used by the pivot searching algorithm. + See the notes section for a more information about the + pivot searching algorithm. + + simpfunc : function or None, optional + A function that simplifies the input. + + If this is specified as a function, this function should be + a callable that takes a single SymPy expression and returns + an another SymPy expression that is algebraically + equivalent. + + If ``None``, it indicates that the pivot search algorithm + should not attempt to simplify any candidate pivots. + + It is internally used by the pivot searching algorithm. + See the notes section for a more information about the + pivot searching algorithm. + + Returns + ======= + + (lu, row_swaps) : (Matrix, list) + If the original matrix is a $m, n$ matrix: + + *lu* is a $m, n$ matrix, which contains result of the + decomposition in a compressed form. See the notes section + to see how the matrix is compressed. + + *row_swaps* is a $m$-element list where each element is a + pair of row exchange indices. + + ``A = (L*U).permute_backward(perm)``, and the row + permutation matrix $P$ from the formula $P A = L U$ can be + computed by ``P=eye(A.row).permute_forward(perm)``. + + Raises + ====== + + ValueError + Raised if ``rankcheck=True`` and the matrix is found to + be rank deficient during the computation. + + Notes + ===== + + About the PLU decomposition: + + PLU decomposition is a generalization of a LU decomposition + which can be extended for rank-deficient matrices. + + It can further be generalized for non-square matrices, and this + is the notation that SymPy is using. + + PLU decomposition is a decomposition of a $m, n$ matrix $A$ in + the form of $P A = L U$ where + + * $L$ is a $m, m$ lower triangular matrix with unit diagonal + entries. + * $U$ is a $m, n$ upper triangular matrix. + * $P$ is a $m, m$ permutation matrix. + + So, for a square matrix, the decomposition would look like: + + .. math:: + L = \begin{bmatrix} + 1 & 0 & 0 & \cdots & 0 \\ + L_{1, 0} & 1 & 0 & \cdots & 0 \\ + L_{2, 0} & L_{2, 1} & 1 & \cdots & 0 \\ + \vdots & \vdots & \vdots & \ddots & \vdots \\ + L_{n-1, 0} & L_{n-1, 1} & L_{n-1, 2} & \cdots & 1 + \end{bmatrix} + + .. math:: + U = \begin{bmatrix} + U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, n-1} \\ + 0 & U_{1, 1} & U_{1, 2} & \cdots & U_{1, n-1} \\ + 0 & 0 & U_{2, 2} & \cdots & U_{2, n-1} \\ + \vdots & \vdots & \vdots & \ddots & \vdots \\ + 0 & 0 & 0 & \cdots & U_{n-1, n-1} + \end{bmatrix} + + And for a matrix with more rows than the columns, + the decomposition would look like: + + .. math:: + L = \begin{bmatrix} + 1 & 0 & 0 & \cdots & 0 & 0 & \cdots & 0 \\ + L_{1, 0} & 1 & 0 & \cdots & 0 & 0 & \cdots & 0 \\ + L_{2, 0} & L_{2, 1} & 1 & \cdots & 0 & 0 & \cdots & 0 \\ + \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \ddots + & \vdots \\ + L_{n-1, 0} & L_{n-1, 1} & L_{n-1, 2} & \cdots & 1 & 0 + & \cdots & 0 \\ + L_{n, 0} & L_{n, 1} & L_{n, 2} & \cdots & L_{n, n-1} & 1 + & \cdots & 0 \\ + \vdots & \vdots & \vdots & \ddots & \vdots & \vdots + & \ddots & \vdots \\ + L_{m-1, 0} & L_{m-1, 1} & L_{m-1, 2} & \cdots & L_{m-1, n-1} + & 0 & \cdots & 1 \\ + \end{bmatrix} + + .. math:: + U = \begin{bmatrix} + U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, n-1} \\ + 0 & U_{1, 1} & U_{1, 2} & \cdots & U_{1, n-1} \\ + 0 & 0 & U_{2, 2} & \cdots & U_{2, n-1} \\ + \vdots & \vdots & \vdots & \ddots & \vdots \\ + 0 & 0 & 0 & \cdots & U_{n-1, n-1} \\ + 0 & 0 & 0 & \cdots & 0 \\ + \vdots & \vdots & \vdots & \ddots & \vdots \\ + 0 & 0 & 0 & \cdots & 0 + \end{bmatrix} + + Finally, for a matrix with more columns than the rows, the + decomposition would look like: + + .. math:: + L = \begin{bmatrix} + 1 & 0 & 0 & \cdots & 0 \\ + L_{1, 0} & 1 & 0 & \cdots & 0 \\ + L_{2, 0} & L_{2, 1} & 1 & \cdots & 0 \\ + \vdots & \vdots & \vdots & \ddots & \vdots \\ + L_{m-1, 0} & L_{m-1, 1} & L_{m-1, 2} & \cdots & 1 + \end{bmatrix} + + .. math:: + U = \begin{bmatrix} + U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, m-1} + & \cdots & U_{0, n-1} \\ + 0 & U_{1, 1} & U_{1, 2} & \cdots & U_{1, m-1} + & \cdots & U_{1, n-1} \\ + 0 & 0 & U_{2, 2} & \cdots & U_{2, m-1} + & \cdots & U_{2, n-1} \\ + \vdots & \vdots & \vdots & \ddots & \vdots + & \cdots & \vdots \\ + 0 & 0 & 0 & \cdots & U_{m-1, m-1} + & \cdots & U_{m-1, n-1} \\ + \end{bmatrix} + + About the compressed LU storage: + + The results of the decomposition are often stored in compressed + forms rather than returning $L$ and $U$ matrices individually. + + It may be less intiuitive, but it is commonly used for a lot of + numeric libraries because of the efficiency. + + The storage matrix is defined as following for this specific + method: + + * The subdiagonal elements of $L$ are stored in the subdiagonal + portion of $LU$, that is $LU_{i, j} = L_{i, j}$ whenever + $i > j$. + * The elements on the diagonal of $L$ are all 1, and are not + explicitly stored. + * $U$ is stored in the upper triangular portion of $LU$, that is + $LU_{i, j} = U_{i, j}$ whenever $i <= j$. + * For a case of $m > n$, the right side of the $L$ matrix is + trivial to store. + * For a case of $m < n$, the below side of the $U$ matrix is + trivial to store. + + So, for a square matrix, the compressed output matrix would be: + + .. math:: + LU = \begin{bmatrix} + U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, n-1} \\ + L_{1, 0} & U_{1, 1} & U_{1, 2} & \cdots & U_{1, n-1} \\ + L_{2, 0} & L_{2, 1} & U_{2, 2} & \cdots & U_{2, n-1} \\ + \vdots & \vdots & \vdots & \ddots & \vdots \\ + L_{n-1, 0} & L_{n-1, 1} & L_{n-1, 2} & \cdots & U_{n-1, n-1} + \end{bmatrix} + + For a matrix with more rows than the columns, the compressed + output matrix would be: + + .. math:: + LU = \begin{bmatrix} + U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, n-1} \\ + L_{1, 0} & U_{1, 1} & U_{1, 2} & \cdots & U_{1, n-1} \\ + L_{2, 0} & L_{2, 1} & U_{2, 2} & \cdots & U_{2, n-1} \\ + \vdots & \vdots & \vdots & \ddots & \vdots \\ + L_{n-1, 0} & L_{n-1, 1} & L_{n-1, 2} & \cdots + & U_{n-1, n-1} \\ + \vdots & \vdots & \vdots & \ddots & \vdots \\ + L_{m-1, 0} & L_{m-1, 1} & L_{m-1, 2} & \cdots + & L_{m-1, n-1} \\ + \end{bmatrix} + + For a matrix with more columns than the rows, the compressed + output matrix would be: + + .. math:: + LU = \begin{bmatrix} + U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, m-1} + & \cdots & U_{0, n-1} \\ + L_{1, 0} & U_{1, 1} & U_{1, 2} & \cdots & U_{1, m-1} + & \cdots & U_{1, n-1} \\ + L_{2, 0} & L_{2, 1} & U_{2, 2} & \cdots & U_{2, m-1} + & \cdots & U_{2, n-1} \\ + \vdots & \vdots & \vdots & \ddots & \vdots + & \cdots & \vdots \\ + L_{m-1, 0} & L_{m-1, 1} & L_{m-1, 2} & \cdots & U_{m-1, m-1} + & \cdots & U_{m-1, n-1} \\ + \end{bmatrix} + + About the pivot searching algorithm: + + When a matrix contains symbolic entries, the pivot search algorithm + differs from the case where every entry can be categorized as zero or + nonzero. + The algorithm searches column by column through the submatrix whose + top left entry coincides with the pivot position. + If it exists, the pivot is the first entry in the current search + column that iszerofunc guarantees is nonzero. + If no such candidate exists, then each candidate pivot is simplified + if simpfunc is not None. + The search is repeated, with the difference that a candidate may be + the pivot if ``iszerofunc()`` cannot guarantee that it is nonzero. + In the second search the pivot is the first candidate that + iszerofunc can guarantee is nonzero. + If no such candidate exists, then the pivot is the first candidate + for which iszerofunc returns None. + If no such candidate exists, then the search is repeated in the next + column to the right. + The pivot search algorithm differs from the one in ``rref()``, which + relies on ``_find_reasonable_pivot()``. + Future versions of ``LUdecomposition_simple()`` may use + ``_find_reasonable_pivot()``. + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.LUdecomposition + LUdecompositionFF + LUsolve + """ + + if rankcheck: + # https://github.com/sympy/sympy/issues/9796 + pass + + if S.Zero in M.shape: + # Define LU decomposition of a matrix with no entries as a matrix + # of the same dimensions with all zero entries. + return M.zeros(M.rows, M.cols), [] + + dps = _get_intermediate_simp() + lu = M.as_mutable() + row_swaps = [] + + pivot_col = 0 + + for pivot_row in range(0, lu.rows - 1): + # Search for pivot. Prefer entry that iszeropivot determines + # is nonzero, over entry that iszeropivot cannot guarantee + # is zero. + # XXX ``_find_reasonable_pivot`` uses slow zero testing. Blocked by bug #10279 + # Future versions of LUdecomposition_simple can pass iszerofunc and simpfunc + # to _find_reasonable_pivot(). + # In pass 3 of _find_reasonable_pivot(), the predicate in ``if x.equals(S.Zero):`` + # calls sympy.simplify(), and not the simplification function passed in via + # the keyword argument simpfunc. + iszeropivot = True + + while pivot_col != M.cols and iszeropivot: + sub_col = (lu[r, pivot_col] for r in range(pivot_row, M.rows)) + + pivot_row_offset, pivot_value, is_assumed_non_zero, ind_simplified_pairs =\ + _find_reasonable_pivot_naive(sub_col, iszerofunc, simpfunc) + + iszeropivot = pivot_value is None + + if iszeropivot: + # All candidate pivots in this column are zero. + # Proceed to next column. + pivot_col += 1 + + if rankcheck and pivot_col != pivot_row: + # All entries including and below the pivot position are + # zero, which indicates that the rank of the matrix is + # strictly less than min(num rows, num cols) + # Mimic behavior of previous implementation, by throwing a + # ValueError. + raise ValueError("Rank of matrix is strictly less than" + " number of rows or columns." + " Pass keyword argument" + " rankcheck=False to compute" + " the LU decomposition of this matrix.") + + candidate_pivot_row = None if pivot_row_offset is None else pivot_row + pivot_row_offset + + if candidate_pivot_row is None and iszeropivot: + # If candidate_pivot_row is None and iszeropivot is True + # after pivot search has completed, then the submatrix + # below and to the right of (pivot_row, pivot_col) is + # all zeros, indicating that Gaussian elimination is + # complete. + return lu, row_swaps + + # Update entries simplified during pivot search. + for offset, val in ind_simplified_pairs: + lu[pivot_row + offset, pivot_col] = val + + if pivot_row != candidate_pivot_row: + # Row swap book keeping: + # Record which rows were swapped. + # Update stored portion of L factor by multiplying L on the + # left and right with the current permutation. + # Swap rows of U. + row_swaps.append([pivot_row, candidate_pivot_row]) + + # Update L. + lu[pivot_row, 0:pivot_row], lu[candidate_pivot_row, 0:pivot_row] = \ + lu[candidate_pivot_row, 0:pivot_row], lu[pivot_row, 0:pivot_row] + + # Swap pivot row of U with candidate pivot row. + lu[pivot_row, pivot_col:lu.cols], lu[candidate_pivot_row, pivot_col:lu.cols] = \ + lu[candidate_pivot_row, pivot_col:lu.cols], lu[pivot_row, pivot_col:lu.cols] + + # Introduce zeros below the pivot by adding a multiple of the + # pivot row to a row under it, and store the result in the + # row under it. + # Only entries in the target row whose index is greater than + # start_col may be nonzero. + start_col = pivot_col + 1 + + for row in range(pivot_row + 1, lu.rows): + # Store factors of L in the subcolumn below + # (pivot_row, pivot_row). + lu[row, pivot_row] = \ + dps(lu[row, pivot_col]/lu[pivot_row, pivot_col]) + + # Form the linear combination of the pivot row and the current + # row below the pivot row that zeros the entries below the pivot. + # Employing slicing instead of a loop here raises + # NotImplementedError: Cannot add Zero to MutableSparseMatrix + # in sympy/matrices/tests/test_sparse.py. + # c = pivot_row + 1 if pivot_row == pivot_col else pivot_col + for c in range(start_col, lu.cols): + lu[row, c] = dps(lu[row, c] - lu[row, pivot_row]*lu[pivot_row, c]) + + if pivot_row != pivot_col: + # matrix rank < min(num rows, num cols), + # so factors of L are not stored directly below the pivot. + # These entries are zero by construction, so don't bother + # computing them. + for row in range(pivot_row + 1, lu.rows): + lu[row, pivot_col] = M.zero + + pivot_col += 1 + + if pivot_col == lu.cols: + # All candidate pivots are zero implies that Gaussian + # elimination is complete. + return lu, row_swaps + + if rankcheck: + if iszerofunc( + lu[Min(lu.rows, lu.cols) - 1, Min(lu.rows, lu.cols) - 1]): + raise ValueError("Rank of matrix is strictly less than" + " number of rows or columns." + " Pass keyword argument" + " rankcheck=False to compute" + " the LU decomposition of this matrix.") + + return lu, row_swaps + +def _LUdecompositionFF(M): + """Compute a fraction-free LU decomposition. + + Returns 4 matrices P, L, D, U such that PA = L D**-1 U. + If the elements of the matrix belong to some integral domain I, then all + elements of L, D and U are guaranteed to belong to I. + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.LUdecomposition + LUdecomposition_Simple + LUsolve + + References + ========== + + .. [1] W. Zhou & D.J. Jeffrey, "Fraction-free matrix factors: new forms + for LU and QR factors". Frontiers in Computer Science in China, + Vol 2, no. 1, pp. 67-80, 2008. + """ + + from sympy.matrices import SparseMatrix + + zeros = SparseMatrix.zeros + eye = SparseMatrix.eye + n, m = M.rows, M.cols + U, L, P = M.as_mutable(), eye(n), eye(n) + DD = zeros(n, n) + oldpivot = 1 + + for k in range(n - 1): + if U[k, k] == 0: + for kpivot in range(k + 1, n): + if U[kpivot, k]: + break + else: + raise ValueError("Matrix is not full rank") + + U[k, k:], U[kpivot, k:] = U[kpivot, k:], U[k, k:] + L[k, :k], L[kpivot, :k] = L[kpivot, :k], L[k, :k] + P[k, :], P[kpivot, :] = P[kpivot, :], P[k, :] + + L [k, k] = Ukk = U[k, k] + DD[k, k] = oldpivot * Ukk + + for i in range(k + 1, n): + L[i, k] = Uik = U[i, k] + + for j in range(k + 1, m): + U[i, j] = (Ukk * U[i, j] - U[k, j] * Uik) / oldpivot + + U[i, k] = 0 + + oldpivot = Ukk + + DD[n - 1, n - 1] = oldpivot + + return P, L, DD, U + +def _singular_value_decomposition(A): + r"""Returns a Condensed Singular Value decomposition. + + Explanation + =========== + + A Singular Value decomposition is a decomposition in the form $A = U \Sigma V^H$ + where + + - $U, V$ are column orthogonal matrix. + - $\Sigma$ is a diagonal matrix, where the main diagonal contains singular + values of matrix A. + + A column orthogonal matrix satisfies + $\mathbb{I} = U^H U$ while a full orthogonal matrix satisfies + relation $\mathbb{I} = U U^H = U^H U$ where $\mathbb{I}$ is an identity + matrix with matching dimensions. + + For matrices which are not square or are rank-deficient, it is + sufficient to return a column orthogonal matrix because augmenting + them may introduce redundant computations. + In condensed Singular Value Decomposition we only return column orthogonal + matrices because of this reason + + If you want to augment the results to return a full orthogonal + decomposition, you should use the following procedures. + + - Augment the $U, V$ matrices with columns that are orthogonal to every + other columns and make it square. + - Augment the $\Sigma$ matrix with zero rows to make it have the same + shape as the original matrix. + + The procedure will be illustrated in the examples section. + + Examples + ======== + + we take a full rank matrix first: + + >>> from sympy import Matrix + >>> A = Matrix([[1, 2],[2,1]]) + >>> U, S, V = A.singular_value_decomposition() + >>> U + Matrix([ + [ sqrt(2)/2, sqrt(2)/2], + [-sqrt(2)/2, sqrt(2)/2]]) + >>> S + Matrix([ + [1, 0], + [0, 3]]) + >>> V + Matrix([ + [-sqrt(2)/2, sqrt(2)/2], + [ sqrt(2)/2, sqrt(2)/2]]) + + If a matrix if square and full rank both U, V + are orthogonal in both directions + + >>> U * U.H + Matrix([ + [1, 0], + [0, 1]]) + >>> U.H * U + Matrix([ + [1, 0], + [0, 1]]) + + >>> V * V.H + Matrix([ + [1, 0], + [0, 1]]) + >>> V.H * V + Matrix([ + [1, 0], + [0, 1]]) + >>> A == U * S * V.H + True + + >>> C = Matrix([ + ... [1, 0, 0, 0, 2], + ... [0, 0, 3, 0, 0], + ... [0, 0, 0, 0, 0], + ... [0, 2, 0, 0, 0], + ... ]) + >>> U, S, V = C.singular_value_decomposition() + + >>> V.H * V + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> V * V.H + Matrix([ + [1/5, 0, 0, 0, 2/5], + [ 0, 1, 0, 0, 0], + [ 0, 0, 1, 0, 0], + [ 0, 0, 0, 0, 0], + [2/5, 0, 0, 0, 4/5]]) + + If you want to augment the results to be a full orthogonal + decomposition, you should augment $V$ with an another orthogonal + column. + + You are able to append an arbitrary standard basis that are linearly + independent to every other columns and you can run the Gram-Schmidt + process to make them augmented as orthogonal basis. + + >>> V_aug = V.row_join(Matrix([[0,0,0,0,1], + ... [0,0,0,1,0]]).H) + >>> V_aug = V_aug.QRdecomposition()[0] + >>> V_aug + Matrix([ + [0, sqrt(5)/5, 0, -2*sqrt(5)/5, 0], + [1, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 0, 1], + [0, 2*sqrt(5)/5, 0, sqrt(5)/5, 0]]) + >>> V_aug.H * V_aug + Matrix([ + [1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 1]]) + >>> V_aug * V_aug.H + Matrix([ + [1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 1]]) + + Similarly we augment U + + >>> U_aug = U.row_join(Matrix([0,0,1,0])) + >>> U_aug = U_aug.QRdecomposition()[0] + >>> U_aug + Matrix([ + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [1, 0, 0, 0]]) + + >>> U_aug.H * U_aug + Matrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + >>> U_aug * U_aug.H + Matrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + + We add 2 zero columns and one row to S + + >>> S_aug = S.col_join(Matrix([[0,0,0]])) + >>> S_aug = S_aug.row_join(Matrix([[0,0,0,0], + ... [0,0,0,0]]).H) + >>> S_aug + Matrix([ + [2, 0, 0, 0, 0], + [0, sqrt(5), 0, 0, 0], + [0, 0, 3, 0, 0], + [0, 0, 0, 0, 0]]) + + + + >>> U_aug * S_aug * V_aug.H == C + True + + """ + + AH = A.H + m, n = A.shape + if m >= n: + V, S = (AH * A).diagonalize() + + ranked = [] + for i, x in enumerate(S.diagonal()): + if not x.is_zero: + ranked.append(i) + + V = V[:, ranked] + + Singular_vals = [sqrt(S[i, i]) for i in range(S.rows) if i in ranked] + + S = S.diag(*Singular_vals) + V, _ = V.QRdecomposition() + U = A * V * S.inv() + else: + U, S = (A * AH).diagonalize() + + ranked = [] + for i, x in enumerate(S.diagonal()): + if not x.is_zero: + ranked.append(i) + + U = U[:, ranked] + Singular_vals = [sqrt(S[i, i]) for i in range(S.rows) if i in ranked] + + S = S.diag(*Singular_vals) + U, _ = U.QRdecomposition() + V = AH * U * S.inv() + + return U, S, V + +def _QRdecomposition_optional(M, normalize=True): + def dot(u, v): + return u.dot(v, hermitian=True) + + dps = _get_intermediate_simp(expand_mul, expand_mul) + + A = M.as_mutable() + ranked = [] + + Q = A + R = A.zeros(A.cols) + + for j in range(A.cols): + for i in range(j): + if Q[:, i].is_zero_matrix: + continue + + R[i, j] = dot(Q[:, i], Q[:, j]) / dot(Q[:, i], Q[:, i]) + R[i, j] = dps(R[i, j]) + Q[:, j] -= Q[:, i] * R[i, j] + + Q[:, j] = dps(Q[:, j]) + if Q[:, j].is_zero_matrix is not True: + ranked.append(j) + R[j, j] = M.one + + Q = Q.extract(range(Q.rows), ranked) + R = R.extract(ranked, range(R.cols)) + + if normalize: + # Normalization + for i in range(Q.cols): + norm = Q[:, i].norm() + Q[:, i] /= norm + R[i, :] *= norm + + return M.__class__(Q), M.__class__(R) + + +def _QRdecomposition(M): + r"""Returns a QR decomposition. + + Explanation + =========== + + A QR decomposition is a decomposition in the form $A = Q R$ + where + + - $Q$ is a column orthogonal matrix. + - $R$ is a upper triangular (trapezoidal) matrix. + + A column orthogonal matrix satisfies + $\mathbb{I} = Q^H Q$ while a full orthogonal matrix satisfies + relation $\mathbb{I} = Q Q^H = Q^H Q$ where $I$ is an identity + matrix with matching dimensions. + + For matrices which are not square or are rank-deficient, it is + sufficient to return a column orthogonal matrix because augmenting + them may introduce redundant computations. + And an another advantage of this is that you can easily inspect the + matrix rank by counting the number of columns of $Q$. + + If you want to augment the results to return a full orthogonal + decomposition, you should use the following procedures. + + - Augment the $Q$ matrix with columns that are orthogonal to every + other columns and make it square. + - Augment the $R$ matrix with zero rows to make it have the same + shape as the original matrix. + + The procedure will be illustrated in the examples section. + + Examples + ======== + + A full rank matrix example: + + >>> from sympy import Matrix + >>> A = Matrix([[12, -51, 4], [6, 167, -68], [-4, 24, -41]]) + >>> Q, R = A.QRdecomposition() + >>> Q + Matrix([ + [ 6/7, -69/175, -58/175], + [ 3/7, 158/175, 6/175], + [-2/7, 6/35, -33/35]]) + >>> R + Matrix([ + [14, 21, -14], + [ 0, 175, -70], + [ 0, 0, 35]]) + + If the matrix is square and full rank, the $Q$ matrix becomes + orthogonal in both directions, and needs no augmentation. + + >>> Q * Q.H + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> Q.H * Q + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + + >>> A == Q*R + True + + A rank deficient matrix example: + + >>> A = Matrix([[12, -51, 0], [6, 167, 0], [-4, 24, 0]]) + >>> Q, R = A.QRdecomposition() + >>> Q + Matrix([ + [ 6/7, -69/175], + [ 3/7, 158/175], + [-2/7, 6/35]]) + >>> R + Matrix([ + [14, 21, 0], + [ 0, 175, 0]]) + + QRdecomposition might return a matrix Q that is rectangular. + In this case the orthogonality condition might be satisfied as + $\mathbb{I} = Q.H*Q$ but not in the reversed product + $\mathbb{I} = Q * Q.H$. + + >>> Q.H * Q + Matrix([ + [1, 0], + [0, 1]]) + >>> Q * Q.H + Matrix([ + [27261/30625, 348/30625, -1914/6125], + [ 348/30625, 30589/30625, 198/6125], + [ -1914/6125, 198/6125, 136/1225]]) + + If you want to augment the results to be a full orthogonal + decomposition, you should augment $Q$ with an another orthogonal + column. + + You are able to append an identity matrix, + and you can run the Gram-Schmidt + process to make them augmented as orthogonal basis. + + >>> Q_aug = Q.row_join(Matrix.eye(3)) + >>> Q_aug = Q_aug.QRdecomposition()[0] + >>> Q_aug + Matrix([ + [ 6/7, -69/175, 58/175], + [ 3/7, 158/175, -6/175], + [-2/7, 6/35, 33/35]]) + >>> Q_aug.H * Q_aug + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> Q_aug * Q_aug.H + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + + Augmenting the $R$ matrix with zero row is straightforward. + + >>> R_aug = R.col_join(Matrix([[0, 0, 0]])) + >>> R_aug + Matrix([ + [14, 21, 0], + [ 0, 175, 0], + [ 0, 0, 0]]) + >>> Q_aug * R_aug == A + True + + A zero matrix example: + + >>> from sympy import Matrix + >>> A = Matrix.zeros(3, 4) + >>> Q, R = A.QRdecomposition() + + They may return matrices with zero rows and columns. + + >>> Q + Matrix(3, 0, []) + >>> R + Matrix(0, 4, []) + >>> Q*R + Matrix([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]) + + As the same augmentation rule described above, $Q$ can be augmented + with columns of an identity matrix and $R$ can be augmented with + rows of a zero matrix. + + >>> Q_aug = Q.row_join(Matrix.eye(3)) + >>> R_aug = R.col_join(Matrix.zeros(3, 4)) + >>> Q_aug * Q_aug.T + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> R_aug + Matrix([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]) + >>> Q_aug * R_aug == A + True + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.cholesky + sympy.matrices.dense.DenseMatrix.LDLdecomposition + sympy.matrices.matrixbase.MatrixBase.LUdecomposition + QRsolve + """ + return _QRdecomposition_optional(M, normalize=True) + +def _upper_hessenberg_decomposition(A): + """Converts a matrix into Hessenberg matrix H. + + Returns 2 matrices H, P s.t. + $P H P^{T} = A$, where H is an upper hessenberg matrix + and P is an orthogonal matrix + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([ + ... [1,2,3], + ... [-3,5,6], + ... [4,-8,9], + ... ]) + >>> H, P = A.upper_hessenberg_decomposition() + >>> H + Matrix([ + [1, 6/5, 17/5], + [5, 213/25, -134/25], + [0, 216/25, 137/25]]) + >>> P + Matrix([ + [1, 0, 0], + [0, -3/5, 4/5], + [0, 4/5, 3/5]]) + >>> P * H * P.H == A + True + + + References + ========== + + .. [#] https://mathworld.wolfram.com/HessenbergDecomposition.html + """ + + M = A.as_mutable() + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + + n = M.cols + P = M.eye(n) + H = M + + for j in range(n - 2): + + u = H[j + 1:, j] + + if u[1:, :].is_zero_matrix: + continue + + if sign(u[0]) != 0: + u[0] = u[0] + sign(u[0]) * u.norm() + else: + u[0] = u[0] + u.norm() + + v = u / u.norm() + + H[j + 1:, :] = H[j + 1:, :] - 2 * v * (v.H * H[j + 1:, :]) + H[:, j + 1:] = H[:, j + 1:] - (H[:, j + 1:] * (2 * v)) * v.H + P[:, j + 1:] = P[:, j + 1:] - (P[:, j + 1:] * (2 * v)) * v.H + + return H, P diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/dense.py b/venv/lib/python3.10/site-packages/sympy/matrices/dense.py new file mode 100644 index 0000000000000000000000000000000000000000..98bf9931df54f67abfd9c4dc810b46fdcf70288f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/dense.py @@ -0,0 +1,1094 @@ +from __future__ import annotations +import random + +from sympy.core.basic import Basic +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.utilities.decorator import doctest_depends_on +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import is_sequence + +from .exceptions import ShapeError +from .decompositions import _cholesky, _LDLdecomposition +from .matrixbase import MatrixBase +from .repmatrix import MutableRepMatrix, RepMatrix +from .solvers import _lower_triangular_solve, _upper_triangular_solve + + +__doctest_requires__ = {('symarray',): ['numpy']} + + +def _iszero(x): + """Returns True if x is zero.""" + return x.is_zero + + +class DenseMatrix(RepMatrix): + """Matrix implementation based on DomainMatrix as the internal representation""" + + # + # DenseMatrix is a superclass for both MutableDenseMatrix and + # ImmutableDenseMatrix. Methods shared by both classes but not for the + # Sparse classes should be implemented here. + # + + is_MatrixExpr: bool = False + + _op_priority = 10.01 + _class_priority = 4 + + @property + def _mat(self): + sympy_deprecation_warning( + """ + The private _mat attribute of Matrix is deprecated. Use the + .flat() method instead. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-private-matrix-attributes" + ) + + return self.flat() + + def _eval_inverse(self, **kwargs): + return self.inv(method=kwargs.get('method', 'GE'), + iszerofunc=kwargs.get('iszerofunc', _iszero), + try_block_diag=kwargs.get('try_block_diag', False)) + + def as_immutable(self): + """Returns an Immutable version of this Matrix + """ + from .immutable import ImmutableDenseMatrix as cls + return cls._fromrep(self._rep.copy()) + + def as_mutable(self): + """Returns a mutable version of this matrix + + Examples + ======== + + >>> from sympy import ImmutableMatrix + >>> X = ImmutableMatrix([[1, 2], [3, 4]]) + >>> Y = X.as_mutable() + >>> Y[1, 1] = 5 # Can set values in Y + >>> Y + Matrix([ + [1, 2], + [3, 5]]) + """ + return Matrix(self) + + def cholesky(self, hermitian=True): + return _cholesky(self, hermitian=hermitian) + + def LDLdecomposition(self, hermitian=True): + return _LDLdecomposition(self, hermitian=hermitian) + + def lower_triangular_solve(self, rhs): + return _lower_triangular_solve(self, rhs) + + def upper_triangular_solve(self, rhs): + return _upper_triangular_solve(self, rhs) + + cholesky.__doc__ = _cholesky.__doc__ + LDLdecomposition.__doc__ = _LDLdecomposition.__doc__ + lower_triangular_solve.__doc__ = _lower_triangular_solve.__doc__ + upper_triangular_solve.__doc__ = _upper_triangular_solve.__doc__ + + +def _force_mutable(x): + """Return a matrix as a Matrix, otherwise return x.""" + if getattr(x, 'is_Matrix', False): + return x.as_mutable() + elif isinstance(x, Basic): + return x + elif hasattr(x, '__array__'): + a = x.__array__() + if len(a.shape) == 0: + return sympify(a) + return Matrix(x) + return x + + +class MutableDenseMatrix(DenseMatrix, MutableRepMatrix): + + def simplify(self, **kwargs): + """Applies simplify to the elements of a matrix in place. + + This is a shortcut for M.applyfunc(lambda x: simplify(x, ratio, measure)) + + See Also + ======== + + sympy.simplify.simplify.simplify + """ + from sympy.simplify.simplify import simplify as _simplify + for (i, j), element in self.todok().items(): + self[i, j] = _simplify(element, **kwargs) + + +MutableMatrix = Matrix = MutableDenseMatrix + +########### +# Numpy Utility Functions: +# list2numpy, matrix2numpy, symmarray +########### + + +def list2numpy(l, dtype=object): # pragma: no cover + """Converts Python list of SymPy expressions to a NumPy array. + + See Also + ======== + + matrix2numpy + """ + from numpy import empty + a = empty(len(l), dtype) + for i, s in enumerate(l): + a[i] = s + return a + + +def matrix2numpy(m, dtype=object): # pragma: no cover + """Converts SymPy's matrix to a NumPy array. + + See Also + ======== + + list2numpy + """ + from numpy import empty + a = empty(m.shape, dtype) + for i in range(m.rows): + for j in range(m.cols): + a[i, j] = m[i, j] + return a + + +########### +# Rotation matrices: +# rot_givens, rot_axis[123], rot_ccw_axis[123] +########### + + +def rot_givens(i, j, theta, dim=3): + r"""Returns a a Givens rotation matrix, a a rotation in the + plane spanned by two coordinates axes. + + Explanation + =========== + + The Givens rotation corresponds to a generalization of rotation + matrices to any number of dimensions, given by: + + .. math:: + G(i, j, \theta) = + \begin{bmatrix} + 1 & \cdots & 0 & \cdots & 0 & \cdots & 0 \\ + \vdots & \ddots & \vdots & & \vdots & & \vdots \\ + 0 & \cdots & c & \cdots & -s & \cdots & 0 \\ + \vdots & & \vdots & \ddots & \vdots & & \vdots \\ + 0 & \cdots & s & \cdots & c & \cdots & 0 \\ + \vdots & & \vdots & & \vdots & \ddots & \vdots \\ + 0 & \cdots & 0 & \cdots & 0 & \cdots & 1 + \end{bmatrix} + + Where $c = \cos(\theta)$ and $s = \sin(\theta)$ appear at the intersections + ``i``\th and ``j``\th rows and columns. + + For fixed ``i > j``\, the non-zero elements of a Givens matrix are + given by: + + - $g_{kk} = 1$ for $k \ne i,\,j$ + - $g_{kk} = c$ for $k = i,\,j$ + - $g_{ji} = -g_{ij} = -s$ + + Parameters + ========== + + i : int between ``0`` and ``dim - 1`` + Represents first axis + j : int between ``0`` and ``dim - 1`` + Represents second axis + dim : int bigger than 1 + Number of dimensions. Defaults to 3. + + Examples + ======== + + >>> from sympy import pi, rot_givens + + A counterclockwise rotation of pi/3 (60 degrees) around + the third axis (z-axis): + + >>> rot_givens(1, 0, pi/3) + Matrix([ + [ 1/2, -sqrt(3)/2, 0], + [sqrt(3)/2, 1/2, 0], + [ 0, 0, 1]]) + + If we rotate by pi/2 (90 degrees): + + >>> rot_givens(1, 0, pi/2) + Matrix([ + [0, -1, 0], + [1, 0, 0], + [0, 0, 1]]) + + This can be generalized to any number + of dimensions: + + >>> rot_givens(1, 0, pi/2, dim=4) + Matrix([ + [0, -1, 0, 0], + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Givens_rotation + + See Also + ======== + + rot_axis1: Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis (clockwise around the x axis) + rot_axis2: Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis (clockwise around the y axis) + rot_axis3: Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis (clockwise around the z axis) + rot_ccw_axis1: Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis (counterclockwise around the x axis) + rot_ccw_axis2: Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis (counterclockwise around the y axis) + rot_ccw_axis3: Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis (counterclockwise around the z axis) + """ + if not isinstance(dim, int) or dim < 2: + raise ValueError('dim must be an integer biggen than one, ' + 'got {}.'.format(dim)) + + if i == j: + raise ValueError('i and j must be different, ' + 'got ({}, {})'.format(i, j)) + + for ij in [i, j]: + if not isinstance(ij, int) or ij < 0 or ij > dim - 1: + raise ValueError('i and j must be integers between 0 and ' + '{}, got i={} and j={}.'.format(dim-1, i, j)) + + theta = sympify(theta) + c = cos(theta) + s = sin(theta) + M = eye(dim) + M[i, i] = c + M[j, j] = c + M[i, j] = s + M[j, i] = -s + return M + + +def rot_axis3(theta): + r"""Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis. + + Explanation + =========== + + For a right-handed coordinate system, this corresponds to a + clockwise rotation around the `z`-axis, given by: + + .. math:: + + R = \begin{bmatrix} + \cos(\theta) & \sin(\theta) & 0 \\ + -\sin(\theta) & \cos(\theta) & 0 \\ + 0 & 0 & 1 + \end{bmatrix} + + Examples + ======== + + >>> from sympy import pi, rot_axis3 + + A rotation of pi/3 (60 degrees): + + >>> theta = pi/3 + >>> rot_axis3(theta) + Matrix([ + [ 1/2, sqrt(3)/2, 0], + [-sqrt(3)/2, 1/2, 0], + [ 0, 0, 1]]) + + If we rotate by pi/2 (90 degrees): + + >>> rot_axis3(pi/2) + Matrix([ + [ 0, 1, 0], + [-1, 0, 0], + [ 0, 0, 1]]) + + See Also + ======== + + rot_givens: Returns a Givens rotation matrix (generalized rotation for + any number of dimensions) + rot_ccw_axis3: Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis (counterclockwise around the z axis) + rot_axis1: Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis (clockwise around the x axis) + rot_axis2: Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis (clockwise around the y axis) + """ + return rot_givens(0, 1, theta, dim=3) + + +def rot_axis2(theta): + r"""Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis. + + Explanation + =========== + + For a right-handed coordinate system, this corresponds to a + clockwise rotation around the `y`-axis, given by: + + .. math:: + + R = \begin{bmatrix} + \cos(\theta) & 0 & -\sin(\theta) \\ + 0 & 1 & 0 \\ + \sin(\theta) & 0 & \cos(\theta) + \end{bmatrix} + + Examples + ======== + + >>> from sympy import pi, rot_axis2 + + A rotation of pi/3 (60 degrees): + + >>> theta = pi/3 + >>> rot_axis2(theta) + Matrix([ + [ 1/2, 0, -sqrt(3)/2], + [ 0, 1, 0], + [sqrt(3)/2, 0, 1/2]]) + + If we rotate by pi/2 (90 degrees): + + >>> rot_axis2(pi/2) + Matrix([ + [0, 0, -1], + [0, 1, 0], + [1, 0, 0]]) + + See Also + ======== + + rot_givens: Returns a Givens rotation matrix (generalized rotation for + any number of dimensions) + rot_ccw_axis2: Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis (clockwise around the y axis) + rot_axis1: Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis (counterclockwise around the x axis) + rot_axis3: Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis (counterclockwise around the z axis) + """ + return rot_givens(2, 0, theta, dim=3) + + +def rot_axis1(theta): + r"""Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis. + + Explanation + =========== + + For a right-handed coordinate system, this corresponds to a + clockwise rotation around the `x`-axis, given by: + + .. math:: + + R = \begin{bmatrix} + 1 & 0 & 0 \\ + 0 & \cos(\theta) & \sin(\theta) \\ + 0 & -\sin(\theta) & \cos(\theta) + \end{bmatrix} + + Examples + ======== + + >>> from sympy import pi, rot_axis1 + + A rotation of pi/3 (60 degrees): + + >>> theta = pi/3 + >>> rot_axis1(theta) + Matrix([ + [1, 0, 0], + [0, 1/2, sqrt(3)/2], + [0, -sqrt(3)/2, 1/2]]) + + If we rotate by pi/2 (90 degrees): + + >>> rot_axis1(pi/2) + Matrix([ + [1, 0, 0], + [0, 0, 1], + [0, -1, 0]]) + + See Also + ======== + + rot_givens: Returns a Givens rotation matrix (generalized rotation for + any number of dimensions) + rot_ccw_axis1: Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis (counterclockwise around the x axis) + rot_axis2: Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis (clockwise around the y axis) + rot_axis3: Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis (clockwise around the z axis) + """ + return rot_givens(1, 2, theta, dim=3) + + +def rot_ccw_axis3(theta): + r"""Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis. + + Explanation + =========== + + For a right-handed coordinate system, this corresponds to a + counterclockwise rotation around the `z`-axis, given by: + + .. math:: + + R = \begin{bmatrix} + \cos(\theta) & -\sin(\theta) & 0 \\ + \sin(\theta) & \cos(\theta) & 0 \\ + 0 & 0 & 1 + \end{bmatrix} + + Examples + ======== + + >>> from sympy import pi, rot_ccw_axis3 + + A rotation of pi/3 (60 degrees): + + >>> theta = pi/3 + >>> rot_ccw_axis3(theta) + Matrix([ + [ 1/2, -sqrt(3)/2, 0], + [sqrt(3)/2, 1/2, 0], + [ 0, 0, 1]]) + + If we rotate by pi/2 (90 degrees): + + >>> rot_ccw_axis3(pi/2) + Matrix([ + [0, -1, 0], + [1, 0, 0], + [0, 0, 1]]) + + See Also + ======== + + rot_givens: Returns a Givens rotation matrix (generalized rotation for + any number of dimensions) + rot_axis3: Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis (clockwise around the z axis) + rot_ccw_axis1: Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis (counterclockwise around the x axis) + rot_ccw_axis2: Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis (counterclockwise around the y axis) + """ + return rot_givens(1, 0, theta, dim=3) + + +def rot_ccw_axis2(theta): + r"""Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis. + + Explanation + =========== + + For a right-handed coordinate system, this corresponds to a + counterclockwise rotation around the `y`-axis, given by: + + .. math:: + + R = \begin{bmatrix} + \cos(\theta) & 0 & \sin(\theta) \\ + 0 & 1 & 0 \\ + -\sin(\theta) & 0 & \cos(\theta) + \end{bmatrix} + + Examples + ======== + + >>> from sympy import pi, rot_ccw_axis2 + + A rotation of pi/3 (60 degrees): + + >>> theta = pi/3 + >>> rot_ccw_axis2(theta) + Matrix([ + [ 1/2, 0, sqrt(3)/2], + [ 0, 1, 0], + [-sqrt(3)/2, 0, 1/2]]) + + If we rotate by pi/2 (90 degrees): + + >>> rot_ccw_axis2(pi/2) + Matrix([ + [ 0, 0, 1], + [ 0, 1, 0], + [-1, 0, 0]]) + + See Also + ======== + + rot_givens: Returns a Givens rotation matrix (generalized rotation for + any number of dimensions) + rot_axis2: Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis (clockwise around the y axis) + rot_ccw_axis1: Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis (counterclockwise around the x axis) + rot_ccw_axis3: Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis (counterclockwise around the z axis) + """ + return rot_givens(0, 2, theta, dim=3) + + +def rot_ccw_axis1(theta): + r"""Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis. + + Explanation + =========== + + For a right-handed coordinate system, this corresponds to a + counterclockwise rotation around the `x`-axis, given by: + + .. math:: + + R = \begin{bmatrix} + 1 & 0 & 0 \\ + 0 & \cos(\theta) & -\sin(\theta) \\ + 0 & \sin(\theta) & \cos(\theta) + \end{bmatrix} + + Examples + ======== + + >>> from sympy import pi, rot_ccw_axis1 + + A rotation of pi/3 (60 degrees): + + >>> theta = pi/3 + >>> rot_ccw_axis1(theta) + Matrix([ + [1, 0, 0], + [0, 1/2, -sqrt(3)/2], + [0, sqrt(3)/2, 1/2]]) + + If we rotate by pi/2 (90 degrees): + + >>> rot_ccw_axis1(pi/2) + Matrix([ + [1, 0, 0], + [0, 0, -1], + [0, 1, 0]]) + + See Also + ======== + + rot_givens: Returns a Givens rotation matrix (generalized rotation for + any number of dimensions) + rot_axis1: Returns a rotation matrix for a rotation of theta (in radians) + about the 1-axis (clockwise around the x axis) + rot_ccw_axis2: Returns a rotation matrix for a rotation of theta (in radians) + about the 2-axis (counterclockwise around the y axis) + rot_ccw_axis3: Returns a rotation matrix for a rotation of theta (in radians) + about the 3-axis (counterclockwise around the z axis) + """ + return rot_givens(2, 1, theta, dim=3) + + +@doctest_depends_on(modules=('numpy',)) +def symarray(prefix, shape, **kwargs): # pragma: no cover + r"""Create a numpy ndarray of symbols (as an object array). + + The created symbols are named ``prefix_i1_i2_``... You should thus provide a + non-empty prefix if you want your symbols to be unique for different output + arrays, as SymPy symbols with identical names are the same object. + + Parameters + ---------- + + prefix : string + A prefix prepended to the name of every symbol. + + shape : int or tuple + Shape of the created array. If an int, the array is one-dimensional; for + more than one dimension the shape must be a tuple. + + \*\*kwargs : dict + keyword arguments passed on to Symbol + + Examples + ======== + These doctests require numpy. + + >>> from sympy import symarray + >>> symarray('', 3) + [_0 _1 _2] + + If you want multiple symarrays to contain distinct symbols, you *must* + provide unique prefixes: + + >>> a = symarray('', 3) + >>> b = symarray('', 3) + >>> a[0] == b[0] + True + >>> a = symarray('a', 3) + >>> b = symarray('b', 3) + >>> a[0] == b[0] + False + + Creating symarrays with a prefix: + + >>> symarray('a', 3) + [a_0 a_1 a_2] + + For more than one dimension, the shape must be given as a tuple: + + >>> symarray('a', (2, 3)) + [[a_0_0 a_0_1 a_0_2] + [a_1_0 a_1_1 a_1_2]] + >>> symarray('a', (2, 3, 2)) + [[[a_0_0_0 a_0_0_1] + [a_0_1_0 a_0_1_1] + [a_0_2_0 a_0_2_1]] + + [[a_1_0_0 a_1_0_1] + [a_1_1_0 a_1_1_1] + [a_1_2_0 a_1_2_1]]] + + For setting assumptions of the underlying Symbols: + + >>> [s.is_real for s in symarray('a', 2, real=True)] + [True, True] + """ + from numpy import empty, ndindex + arr = empty(shape, dtype=object) + for index in ndindex(shape): + arr[index] = Symbol('%s_%s' % (prefix, '_'.join(map(str, index))), + **kwargs) + return arr + + +############### +# Functions +############### + +def casoratian(seqs, n, zero=True): + """Given linear difference operator L of order 'k' and homogeneous + equation Ly = 0 we want to compute kernel of L, which is a set + of 'k' sequences: a(n), b(n), ... z(n). + + Solutions of L are linearly independent iff their Casoratian, + denoted as C(a, b, ..., z), do not vanish for n = 0. + + Casoratian is defined by k x k determinant:: + + + a(n) b(n) . . . z(n) + + | a(n+1) b(n+1) . . . z(n+1) | + | . . . . | + | . . . . | + | . . . . | + + a(n+k-1) b(n+k-1) . . . z(n+k-1) + + + It proves very useful in rsolve_hyper() where it is applied + to a generating set of a recurrence to factor out linearly + dependent solutions and return a basis: + + >>> from sympy import Symbol, casoratian, factorial + >>> n = Symbol('n', integer=True) + + Exponential and factorial are linearly independent: + + >>> casoratian([2**n, factorial(n)], n) != 0 + True + + """ + + seqs = list(map(sympify, seqs)) + + if not zero: + f = lambda i, j: seqs[j].subs(n, n + i) + else: + f = lambda i, j: seqs[j].subs(n, i) + + k = len(seqs) + + return Matrix(k, k, f).det() + + +def eye(*args, **kwargs): + """Create square identity matrix n x n + + See Also + ======== + + diag + zeros + ones + """ + + return Matrix.eye(*args, **kwargs) + + +def diag(*values, strict=True, unpack=False, **kwargs): + """Returns a matrix with the provided values placed on the + diagonal. If non-square matrices are included, they will + produce a block-diagonal matrix. + + Examples + ======== + + This version of diag is a thin wrapper to Matrix.diag that differs + in that it treats all lists like matrices -- even when a single list + is given. If this is not desired, either put a `*` before the list or + set `unpack=True`. + + >>> from sympy import diag + + >>> diag([1, 2, 3], unpack=True) # = diag(1,2,3) or diag(*[1,2,3]) + Matrix([ + [1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + + >>> diag([1, 2, 3]) # a column vector + Matrix([ + [1], + [2], + [3]]) + + See Also + ======== + .matrixbase.MatrixBase.eye + .matrixbase.MatrixBase.diagonal + .matrixbase.MatrixBase.diag + .expressions.blockmatrix.BlockMatrix + """ + return Matrix.diag(*values, strict=strict, unpack=unpack, **kwargs) + + +def GramSchmidt(vlist, orthonormal=False): + """Apply the Gram-Schmidt process to a set of vectors. + + Parameters + ========== + + vlist : List of Matrix + Vectors to be orthogonalized for. + + orthonormal : Bool, optional + If true, return an orthonormal basis. + + Returns + ======= + + vlist : List of Matrix + Orthogonalized vectors + + Notes + ===== + + This routine is mostly duplicate from ``Matrix.orthogonalize``, + except for some difference that this always raises error when + linearly dependent vectors are found, and the keyword ``normalize`` + has been named as ``orthonormal`` in this function. + + See Also + ======== + + .matrixbase.MatrixBase.orthogonalize + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process + """ + return MutableDenseMatrix.orthogonalize( + *vlist, normalize=orthonormal, rankcheck=True + ) + + +def hessian(f, varlist, constraints=()): + """Compute Hessian matrix for a function f wrt parameters in varlist + which may be given as a sequence or a row/column vector. A list of + constraints may optionally be given. + + Examples + ======== + + >>> from sympy import Function, hessian, pprint + >>> from sympy.abc import x, y + >>> f = Function('f')(x, y) + >>> g1 = Function('g')(x, y) + >>> g2 = x**2 + 3*y + >>> pprint(hessian(f, (x, y), [g1, g2])) + [ d d ] + [ 0 0 --(g(x, y)) --(g(x, y)) ] + [ dx dy ] + [ ] + [ 0 0 2*x 3 ] + [ ] + [ 2 2 ] + [d d d ] + [--(g(x, y)) 2*x ---(f(x, y)) -----(f(x, y))] + [dx 2 dy dx ] + [ dx ] + [ ] + [ 2 2 ] + [d d d ] + [--(g(x, y)) 3 -----(f(x, y)) ---(f(x, y)) ] + [dy dy dx 2 ] + [ dy ] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hessian_matrix + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.jacobian + wronskian + """ + # f is the expression representing a function f, return regular matrix + if isinstance(varlist, MatrixBase): + if 1 not in varlist.shape: + raise ShapeError("`varlist` must be a column or row vector.") + if varlist.cols == 1: + varlist = varlist.T + varlist = varlist.tolist()[0] + if is_sequence(varlist): + n = len(varlist) + if not n: + raise ShapeError("`len(varlist)` must not be zero.") + else: + raise ValueError("Improper variable list in hessian function") + if not getattr(f, 'diff'): + # check differentiability + raise ValueError("Function `f` (%s) is not differentiable" % f) + m = len(constraints) + N = m + n + out = zeros(N) + for k, g in enumerate(constraints): + if not getattr(g, 'diff'): + # check differentiability + raise ValueError("Function `f` (%s) is not differentiable" % f) + for i in range(n): + out[k, i + m] = g.diff(varlist[i]) + for i in range(n): + for j in range(i, n): + out[i + m, j + m] = f.diff(varlist[i]).diff(varlist[j]) + for i in range(N): + for j in range(i + 1, N): + out[j, i] = out[i, j] + return out + + +def jordan_cell(eigenval, n): + """ + Create a Jordan block: + + Examples + ======== + + >>> from sympy import jordan_cell + >>> from sympy.abc import x + >>> jordan_cell(x, 4) + Matrix([ + [x, 1, 0, 0], + [0, x, 1, 0], + [0, 0, x, 1], + [0, 0, 0, x]]) + """ + + return Matrix.jordan_block(size=n, eigenvalue=eigenval) + + +def matrix_multiply_elementwise(A, B): + """Return the Hadamard product (elementwise product) of A and B + + >>> from sympy import Matrix, matrix_multiply_elementwise + >>> A = Matrix([[0, 1, 2], [3, 4, 5]]) + >>> B = Matrix([[1, 10, 100], [100, 10, 1]]) + >>> matrix_multiply_elementwise(A, B) + Matrix([ + [ 0, 10, 200], + [300, 40, 5]]) + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.__mul__ + """ + return A.multiply_elementwise(B) + + +def ones(*args, **kwargs): + """Returns a matrix of ones with ``rows`` rows and ``cols`` columns; + if ``cols`` is omitted a square matrix will be returned. + + See Also + ======== + + zeros + eye + diag + """ + + if 'c' in kwargs: + kwargs['cols'] = kwargs.pop('c') + + return Matrix.ones(*args, **kwargs) + + +def randMatrix(r, c=None, min=0, max=99, seed=None, symmetric=False, + percent=100, prng=None): + """Create random matrix with dimensions ``r`` x ``c``. If ``c`` is omitted + the matrix will be square. If ``symmetric`` is True the matrix must be + square. If ``percent`` is less than 100 then only approximately the given + percentage of elements will be non-zero. + + The pseudo-random number generator used to generate matrix is chosen in the + following way. + + * If ``prng`` is supplied, it will be used as random number generator. + It should be an instance of ``random.Random``, or at least have + ``randint`` and ``shuffle`` methods with same signatures. + * if ``prng`` is not supplied but ``seed`` is supplied, then new + ``random.Random`` with given ``seed`` will be created; + * otherwise, a new ``random.Random`` with default seed will be used. + + Examples + ======== + + >>> from sympy import randMatrix + >>> randMatrix(3) # doctest:+SKIP + [25, 45, 27] + [44, 54, 9] + [23, 96, 46] + >>> randMatrix(3, 2) # doctest:+SKIP + [87, 29] + [23, 37] + [90, 26] + >>> randMatrix(3, 3, 0, 2) # doctest:+SKIP + [0, 2, 0] + [2, 0, 1] + [0, 0, 1] + >>> randMatrix(3, symmetric=True) # doctest:+SKIP + [85, 26, 29] + [26, 71, 43] + [29, 43, 57] + >>> A = randMatrix(3, seed=1) + >>> B = randMatrix(3, seed=2) + >>> A == B + False + >>> A == randMatrix(3, seed=1) + True + >>> randMatrix(3, symmetric=True, percent=50) # doctest:+SKIP + [77, 70, 0], + [70, 0, 0], + [ 0, 0, 88] + """ + # Note that ``Random()`` is equivalent to ``Random(None)`` + prng = prng or random.Random(seed) + + if c is None: + c = r + + if symmetric and r != c: + raise ValueError('For symmetric matrices, r must equal c, but %i != %i' % (r, c)) + + ij = range(r * c) + if percent != 100: + ij = prng.sample(ij, int(len(ij)*percent // 100)) + + m = zeros(r, c) + + if not symmetric: + for ijk in ij: + i, j = divmod(ijk, c) + m[i, j] = prng.randint(min, max) + else: + for ijk in ij: + i, j = divmod(ijk, c) + if i <= j: + m[i, j] = m[j, i] = prng.randint(min, max) + + return m + + +def wronskian(functions, var, method='bareiss'): + """ + Compute Wronskian for [] of functions + + :: + + | f1 f2 ... fn | + | f1' f2' ... fn' | + | . . . . | + W(f1, ..., fn) = | . . . . | + | . . . . | + | (n) (n) (n) | + | D (f1) D (f2) ... D (fn) | + + see: https://en.wikipedia.org/wiki/Wronskian + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.jacobian + hessian + """ + + functions = [sympify(f) for f in functions] + n = len(functions) + if n == 0: + return S.One + W = Matrix(n, n, lambda i, j: functions[i].diff(var, j)) + return W.det(method) + + +def zeros(*args, **kwargs): + """Returns a matrix of zeros with ``rows`` rows and ``cols`` columns; + if ``cols`` is omitted a square matrix will be returned. + + See Also + ======== + + ones + eye + diag + """ + + if 'c' in kwargs: + kwargs['cols'] = kwargs.pop('c') + + return Matrix.zeros(*args, **kwargs) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/determinant.py b/venv/lib/python3.10/site-packages/sympy/matrices/determinant.py new file mode 100644 index 0000000000000000000000000000000000000000..9206c0714999ebe0cde5c4300d9b3293939177df --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/determinant.py @@ -0,0 +1,1021 @@ +from types import FunctionType + +from sympy.core.cache import cacheit +from sympy.core.numbers import Float, Integer +from sympy.core.singleton import S +from sympy.core.symbol import uniquely_named_symbol +from sympy.core.mul import Mul +from sympy.polys import PurePoly, cancel +from sympy.functions.combinatorial.numbers import nC +from sympy.polys.matrices.domainmatrix import DomainMatrix +from sympy.polys.matrices.ddm import DDM + +from .exceptions import NonSquareMatrixError +from .utilities import ( + _get_intermediate_simp, _get_intermediate_simp_bool, + _iszero, _is_zero_after_expand_mul, _dotprodsimp, _simplify) + + +def _find_reasonable_pivot(col, iszerofunc=_iszero, simpfunc=_simplify): + """ Find the lowest index of an item in ``col`` that is + suitable for a pivot. If ``col`` consists only of + Floats, the pivot with the largest norm is returned. + Otherwise, the first element where ``iszerofunc`` returns + False is used. If ``iszerofunc`` does not return false, + items are simplified and retested until a suitable + pivot is found. + + Returns a 4-tuple + (pivot_offset, pivot_val, assumed_nonzero, newly_determined) + where pivot_offset is the index of the pivot, pivot_val is + the (possibly simplified) value of the pivot, assumed_nonzero + is True if an assumption that the pivot was non-zero + was made without being proved, and newly_determined are + elements that were simplified during the process of pivot + finding.""" + + newly_determined = [] + col = list(col) + # a column that contains a mix of floats and integers + # but at least one float is considered a numerical + # column, and so we do partial pivoting + if all(isinstance(x, (Float, Integer)) for x in col) and any( + isinstance(x, Float) for x in col): + col_abs = [abs(x) for x in col] + max_value = max(col_abs) + if iszerofunc(max_value): + # just because iszerofunc returned True, doesn't + # mean the value is numerically zero. Make sure + # to replace all entries with numerical zeros + if max_value != 0: + newly_determined = [(i, 0) for i, x in enumerate(col) if x != 0] + return (None, None, False, newly_determined) + index = col_abs.index(max_value) + return (index, col[index], False, newly_determined) + + # PASS 1 (iszerofunc directly) + possible_zeros = [] + for i, x in enumerate(col): + is_zero = iszerofunc(x) + # is someone wrote a custom iszerofunc, it may return + # BooleanFalse or BooleanTrue instead of True or False, + # so use == for comparison instead of `is` + if is_zero == False: + # we found something that is definitely not zero + return (i, x, False, newly_determined) + possible_zeros.append(is_zero) + + # by this point, we've found no certain non-zeros + if all(possible_zeros): + # if everything is definitely zero, we have + # no pivot + return (None, None, False, newly_determined) + + # PASS 2 (iszerofunc after simplify) + # we haven't found any for-sure non-zeros, so + # go through the elements iszerofunc couldn't + # make a determination about and opportunistically + # simplify to see if we find something + for i, x in enumerate(col): + if possible_zeros[i] is not None: + continue + simped = simpfunc(x) + is_zero = iszerofunc(simped) + if is_zero in (True, False): + newly_determined.append((i, simped)) + if is_zero == False: + return (i, simped, False, newly_determined) + possible_zeros[i] = is_zero + + # after simplifying, some things that were recognized + # as zeros might be zeros + if all(possible_zeros): + # if everything is definitely zero, we have + # no pivot + return (None, None, False, newly_determined) + + # PASS 3 (.equals(0)) + # some expressions fail to simplify to zero, but + # ``.equals(0)`` evaluates to True. As a last-ditch + # attempt, apply ``.equals`` to these expressions + for i, x in enumerate(col): + if possible_zeros[i] is not None: + continue + if x.equals(S.Zero): + # ``.iszero`` may return False with + # an implicit assumption (e.g., ``x.equals(0)`` + # when ``x`` is a symbol), so only treat it + # as proved when ``.equals(0)`` returns True + possible_zeros[i] = True + newly_determined.append((i, S.Zero)) + + if all(possible_zeros): + return (None, None, False, newly_determined) + + # at this point there is nothing that could definitely + # be a pivot. To maintain compatibility with existing + # behavior, we'll assume that an illdetermined thing is + # non-zero. We should probably raise a warning in this case + i = possible_zeros.index(None) + return (i, col[i], True, newly_determined) + + +def _find_reasonable_pivot_naive(col, iszerofunc=_iszero, simpfunc=None): + """ + Helper that computes the pivot value and location from a + sequence of contiguous matrix column elements. As a side effect + of the pivot search, this function may simplify some of the elements + of the input column. A list of these simplified entries and their + indices are also returned. + This function mimics the behavior of _find_reasonable_pivot(), + but does less work trying to determine if an indeterminate candidate + pivot simplifies to zero. This more naive approach can be much faster, + with the trade-off that it may erroneously return a pivot that is zero. + + ``col`` is a sequence of contiguous column entries to be searched for + a suitable pivot. + ``iszerofunc`` is a callable that returns a Boolean that indicates + if its input is zero, or None if no such determination can be made. + ``simpfunc`` is a callable that simplifies its input. It must return + its input if it does not simplify its input. Passing in + ``simpfunc=None`` indicates that the pivot search should not attempt + to simplify any candidate pivots. + + Returns a 4-tuple: + (pivot_offset, pivot_val, assumed_nonzero, newly_determined) + ``pivot_offset`` is the sequence index of the pivot. + ``pivot_val`` is the value of the pivot. + pivot_val and col[pivot_index] are equivalent, but will be different + when col[pivot_index] was simplified during the pivot search. + ``assumed_nonzero`` is a boolean indicating if the pivot cannot be + guaranteed to be zero. If assumed_nonzero is true, then the pivot + may or may not be non-zero. If assumed_nonzero is false, then + the pivot is non-zero. + ``newly_determined`` is a list of index-value pairs of pivot candidates + that were simplified during the pivot search. + """ + + # indeterminates holds the index-value pairs of each pivot candidate + # that is neither zero or non-zero, as determined by iszerofunc(). + # If iszerofunc() indicates that a candidate pivot is guaranteed + # non-zero, or that every candidate pivot is zero then the contents + # of indeterminates are unused. + # Otherwise, the only viable candidate pivots are symbolic. + # In this case, indeterminates will have at least one entry, + # and all but the first entry are ignored when simpfunc is None. + indeterminates = [] + for i, col_val in enumerate(col): + col_val_is_zero = iszerofunc(col_val) + if col_val_is_zero == False: + # This pivot candidate is non-zero. + return i, col_val, False, [] + elif col_val_is_zero is None: + # The candidate pivot's comparison with zero + # is indeterminate. + indeterminates.append((i, col_val)) + + if len(indeterminates) == 0: + # All candidate pivots are guaranteed to be zero, i.e. there is + # no pivot. + return None, None, False, [] + + if simpfunc is None: + # Caller did not pass in a simplification function that might + # determine if an indeterminate pivot candidate is guaranteed + # to be nonzero, so assume the first indeterminate candidate + # is non-zero. + return indeterminates[0][0], indeterminates[0][1], True, [] + + # newly_determined holds index-value pairs of candidate pivots + # that were simplified during the search for a non-zero pivot. + newly_determined = [] + for i, col_val in indeterminates: + tmp_col_val = simpfunc(col_val) + if id(col_val) != id(tmp_col_val): + # simpfunc() simplified this candidate pivot. + newly_determined.append((i, tmp_col_val)) + if iszerofunc(tmp_col_val) == False: + # Candidate pivot simplified to a guaranteed non-zero value. + return i, tmp_col_val, False, newly_determined + + return indeterminates[0][0], indeterminates[0][1], True, newly_determined + + +# This functions is a candidate for caching if it gets implemented for matrices. +def _berkowitz_toeplitz_matrix(M): + """Return (A,T) where T the Toeplitz matrix used in the Berkowitz algorithm + corresponding to ``M`` and A is the first principal submatrix. + """ + + # the 0 x 0 case is trivial + if M.rows == 0 and M.cols == 0: + return M._new(1,1, [M.one]) + + # + # Partition M = [ a_11 R ] + # [ C A ] + # + + a, R = M[0,0], M[0, 1:] + C, A = M[1:, 0], M[1:,1:] + + # + # The Toeplitz matrix looks like + # + # [ 1 ] + # [ -a 1 ] + # [ -RC -a 1 ] + # [ -RAC -RC -a 1 ] + # [ -RA**2C -RAC -RC -a 1 ] + # etc. + + # Compute the diagonal entries. + # Because multiplying matrix times vector is so much + # more efficient than matrix times matrix, recursively + # compute -R * A**n * C. + diags = [C] + for i in range(M.rows - 2): + diags.append(A.multiply(diags[i], dotprodsimp=None)) + diags = [(-R).multiply(d, dotprodsimp=None)[0, 0] for d in diags] + diags = [M.one, -a] + diags + + def entry(i,j): + if j > i: + return M.zero + return diags[i - j] + + toeplitz = M._new(M.cols + 1, M.rows, entry) + return (A, toeplitz) + + +# This functions is a candidate for caching if it gets implemented for matrices. +def _berkowitz_vector(M): + """ Run the Berkowitz algorithm and return a vector whose entries + are the coefficients of the characteristic polynomial of ``M``. + + Given N x N matrix, efficiently compute + coefficients of characteristic polynomials of ``M`` + without division in the ground domain. + + This method is particularly useful for computing determinant, + principal minors and characteristic polynomial when ``M`` + has complicated coefficients e.g. polynomials. Semi-direct + usage of this algorithm is also important in computing + efficiently sub-resultant PRS. + + Assuming that M is a square matrix of dimension N x N and + I is N x N identity matrix, then the Berkowitz vector is + an N x 1 vector whose entries are coefficients of the + polynomial + + charpoly(M) = det(t*I - M) + + As a consequence, all polynomials generated by Berkowitz + algorithm are monic. + + For more information on the implemented algorithm refer to: + + [1] S.J. Berkowitz, On computing the determinant in small + parallel time using a small number of processors, ACM, + Information Processing Letters 18, 1984, pp. 147-150 + + [2] M. Keber, Division-Free computation of sub-resultants + using Bezout matrices, Tech. Report MPI-I-2006-1-006, + Saarbrucken, 2006 + """ + + # handle the trivial cases + if M.rows == 0 and M.cols == 0: + return M._new(1, 1, [M.one]) + elif M.rows == 1 and M.cols == 1: + return M._new(2, 1, [M.one, -M[0,0]]) + + submat, toeplitz = _berkowitz_toeplitz_matrix(M) + + return toeplitz.multiply(_berkowitz_vector(submat), dotprodsimp=None) + + +def _adjugate(M, method="berkowitz"): + """Returns the adjugate, or classical adjoint, of + a matrix. That is, the transpose of the matrix of cofactors. + + https://en.wikipedia.org/wiki/Adjugate + + Parameters + ========== + + method : string, optional + Method to use to find the cofactors, can be "bareiss", "berkowitz", + "bird", "laplace" or "lu". + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2], [3, 4]]) + >>> M.adjugate() + Matrix([ + [ 4, -2], + [-3, 1]]) + + See Also + ======== + + cofactor_matrix + sympy.matrices.matrixbase.MatrixBase.transpose + """ + + return M.cofactor_matrix(method=method).transpose() + + +# This functions is a candidate for caching if it gets implemented for matrices. +def _charpoly(M, x='lambda', simplify=_simplify): + """Computes characteristic polynomial det(x*I - M) where I is + the identity matrix. + + A PurePoly is returned, so using different variables for ``x`` does + not affect the comparison or the polynomials: + + Parameters + ========== + + x : string, optional + Name for the "lambda" variable, defaults to "lambda". + + simplify : function, optional + Simplification function to use on the characteristic polynomial + calculated. Defaults to ``simplify``. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[1, 3], [2, 0]]) + >>> M.charpoly() + PurePoly(lambda**2 - lambda - 6, lambda, domain='ZZ') + >>> M.charpoly(x) == M.charpoly(y) + True + >>> M.charpoly(x) == M.charpoly(y) + True + + Specifying ``x`` is optional; a symbol named ``lambda`` is used by + default (which looks good when pretty-printed in unicode): + + >>> M.charpoly().as_expr() + lambda**2 - lambda - 6 + + And if ``x`` clashes with an existing symbol, underscores will + be prepended to the name to make it unique: + + >>> M = Matrix([[1, 2], [x, 0]]) + >>> M.charpoly(x).as_expr() + _x**2 - _x - 2*x + + Whether you pass a symbol or not, the generator can be obtained + with the gen attribute since it may not be the same as the symbol + that was passed: + + >>> M.charpoly(x).gen + _x + >>> M.charpoly(x).gen == x + False + + Notes + ===== + + The Samuelson-Berkowitz algorithm is used to compute + the characteristic polynomial efficiently and without any + division operations. Thus the characteristic polynomial over any + commutative ring without zero divisors can be computed. + + If the determinant det(x*I - M) can be found out easily as + in the case of an upper or a lower triangular matrix, then + instead of Samuelson-Berkowitz algorithm, eigenvalues are computed + and the characteristic polynomial with their help. + + See Also + ======== + + det + """ + + if not M.is_square: + raise NonSquareMatrixError() + + # Use DomainMatrix. We are already going to convert this to a Poly so there + # is no need to worry about expanding powers etc. Also since this algorithm + # does not require division or zero detection it is fine to use EX. + # + # M.to_DM() will fall back on EXRAW rather than EX. EXRAW is a lot faster + # for elementary arithmetic because it does not call cancel for each + # operation but it generates large unsimplified results that are slow in + # the subsequent call to simplify. Using EX instead is faster overall + # but at least in some cases EXRAW+simplify gives a simpler result so we + # preserve that existing behaviour of charpoly for now... + dM = M.to_DM() + + K = dM.domain + + cp = dM.charpoly() + + x = uniquely_named_symbol(x, [M], modify=lambda s: '_' + s) + + if K.is_EXRAW or simplify is not _simplify: + # XXX: Converting back to Expr is expensive. We only do it if the + # caller supplied a custom simplify function for backwards + # compatibility or otherwise if the domain was EX. For any other domain + # there should be no benefit in simplifying at this stage because Poly + # will put everything into canonical form anyway. + berk_vector = [K.to_sympy(c) for c in cp] + berk_vector = [simplify(a) for a in berk_vector] + p = PurePoly(berk_vector, x) + + else: + # Convert from the list of domain elements directly to Poly. + p = PurePoly(cp, x, domain=K) + + return p + + +def _cofactor(M, i, j, method="berkowitz"): + """Calculate the cofactor of an element. + + Parameters + ========== + + method : string, optional + Method to use to find the cofactors, can be "bareiss", "berkowitz", + "bird", "laplace" or "lu". + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2], [3, 4]]) + >>> M.cofactor(0, 1) + -3 + + See Also + ======== + + cofactor_matrix + minor + minor_submatrix + """ + + if not M.is_square or M.rows < 1: + raise NonSquareMatrixError() + + return S.NegativeOne**((i + j) % 2) * M.minor(i, j, method) + + +def _cofactor_matrix(M, method="berkowitz"): + """Return a matrix containing the cofactor of each element. + + Parameters + ========== + + method : string, optional + Method to use to find the cofactors, can be "bareiss", "berkowitz", + "bird", "laplace" or "lu". + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2], [3, 4]]) + >>> M.cofactor_matrix() + Matrix([ + [ 4, -3], + [-2, 1]]) + + See Also + ======== + + cofactor + minor + minor_submatrix + """ + + if not M.is_square: + raise NonSquareMatrixError() + + return M._new(M.rows, M.cols, + lambda i, j: M.cofactor(i, j, method)) + +def _per(M): + """Returns the permanent of a matrix. Unlike determinant, + permanent is defined for both square and non-square matrices. + + For an m x n matrix, with m less than or equal to n, + it is given as the sum over the permutations s of size + less than or equal to m on [1, 2, . . . n] of the product + from i = 1 to m of M[i, s[i]]. Taking the transpose will + not affect the value of the permanent. + + In the case of a square matrix, this is the same as the permutation + definition of the determinant, but it does not take the sign of the + permutation into account. Computing the permanent with this definition + is quite inefficient, so here the Ryser formula is used. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> M.per() + 450 + >>> M = Matrix([1, 5, 7]) + >>> M.per() + 13 + + References + ========== + + .. [1] Prof. Frank Ben's notes: https://math.berkeley.edu/~bernd/ban275.pdf + .. [2] Wikipedia article on Permanent: https://en.wikipedia.org/wiki/Permanent_%28mathematics%29 + .. [3] https://reference.wolfram.com/language/ref/Permanent.html + .. [4] Permanent of a rectangular matrix : https://arxiv.org/pdf/0904.3251.pdf + """ + import itertools + + m, n = M.shape + if m > n: + M = M.T + m, n = n, m + s = list(range(n)) + + subsets = [] + for i in range(1, m + 1): + subsets += list(map(list, itertools.combinations(s, i))) + + perm = 0 + for subset in subsets: + prod = 1 + sub_len = len(subset) + for i in range(m): + prod *= sum(M[i, j] for j in subset) + perm += prod * S.NegativeOne**sub_len * nC(n - sub_len, m - sub_len) + perm *= S.NegativeOne**m + return perm.simplify() + +def _det_DOM(M): + DOM = DomainMatrix.from_Matrix(M, field=True, extension=True) + K = DOM.domain + return K.to_sympy(DOM.det()) + +# This functions is a candidate for caching if it gets implemented for matrices. +def _det(M, method="bareiss", iszerofunc=None): + """Computes the determinant of a matrix if ``M`` is a concrete matrix object + otherwise return an expressions ``Determinant(M)`` if ``M`` is a + ``MatrixSymbol`` or other expression. + + Parameters + ========== + + method : string, optional + Specifies the algorithm used for computing the matrix determinant. + + If the matrix is at most 3x3, a hard-coded formula is used and the + specified method is ignored. Otherwise, it defaults to + ``'bareiss'``. + + Also, if the matrix is an upper or a lower triangular matrix, determinant + is computed by simple multiplication of diagonal elements, and the + specified method is ignored. + + If it is set to ``'domain-ge'``, then Gaussian elimination method will + be used via using DomainMatrix. + + If it is set to ``'bareiss'``, Bareiss' fraction-free algorithm will + be used. + + If it is set to ``'berkowitz'``, Berkowitz' algorithm will be used. + + If it is set to ``'bird'``, Bird's algorithm will be used [1]_. + + If it is set to ``'laplace'``, Laplace's algorithm will be used. + + Otherwise, if it is set to ``'lu'``, LU decomposition will be used. + + .. note:: + For backward compatibility, legacy keys like "bareis" and + "det_lu" can still be used to indicate the corresponding + methods. + And the keys are also case-insensitive for now. However, it is + suggested to use the precise keys for specifying the method. + + iszerofunc : FunctionType or None, optional + If it is set to ``None``, it will be defaulted to ``_iszero`` if the + method is set to ``'bareiss'``, and ``_is_zero_after_expand_mul`` if + the method is set to ``'lu'``. + + It can also accept any user-specified zero testing function, if it + is formatted as a function which accepts a single symbolic argument + and returns ``True`` if it is tested as zero and ``False`` if it + tested as non-zero, and also ``None`` if it is undecidable. + + Returns + ======= + + det : Basic + Result of determinant. + + Raises + ====== + + ValueError + If unrecognized keys are given for ``method`` or ``iszerofunc``. + + NonSquareMatrixError + If attempted to calculate determinant from a non-square matrix. + + Examples + ======== + + >>> from sympy import Matrix, eye, det + >>> I3 = eye(3) + >>> det(I3) + 1 + >>> M = Matrix([[1, 2], [3, 4]]) + >>> det(M) + -2 + >>> det(M) == M.det() + True + >>> M.det(method="domain-ge") + -2 + + References + ========== + + .. [1] Bird, R. S. (2011). A simple division-free algorithm for computing + determinants. Inf. Process. Lett., 111(21), 1072-1074. doi: + 10.1016/j.ipl.2011.08.006 + """ + + # sanitize `method` + method = method.lower() + + if method == "bareis": + method = "bareiss" + elif method == "det_lu": + method = "lu" + + if method not in ("bareiss", "berkowitz", "lu", "domain-ge", "bird", + "laplace"): + raise ValueError("Determinant method '%s' unrecognized" % method) + + if iszerofunc is None: + if method == "bareiss": + iszerofunc = _is_zero_after_expand_mul + elif method == "lu": + iszerofunc = _iszero + + elif not isinstance(iszerofunc, FunctionType): + raise ValueError("Zero testing method '%s' unrecognized" % iszerofunc) + + n = M.rows + + if n == M.cols: # square check is done in individual method functions + if n == 0: + return M.one + elif n == 1: + return M[0, 0] + elif n == 2: + m = M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0] + return _get_intermediate_simp(_dotprodsimp)(m) + elif n == 3: + m = (M[0, 0] * M[1, 1] * M[2, 2] + + M[0, 1] * M[1, 2] * M[2, 0] + + M[0, 2] * M[1, 0] * M[2, 1] + - M[0, 2] * M[1, 1] * M[2, 0] + - M[0, 0] * M[1, 2] * M[2, 1] + - M[0, 1] * M[1, 0] * M[2, 2]) + return _get_intermediate_simp(_dotprodsimp)(m) + + dets = [] + for b in M.strongly_connected_components(): + if method == "domain-ge": # uses DomainMatrix to evaluate determinant + det = _det_DOM(M[b, b]) + elif method == "bareiss": + det = M[b, b]._eval_det_bareiss(iszerofunc=iszerofunc) + elif method == "berkowitz": + det = M[b, b]._eval_det_berkowitz() + elif method == "lu": + det = M[b, b]._eval_det_lu(iszerofunc=iszerofunc) + elif method == "bird": + det = M[b, b]._eval_det_bird() + elif method == "laplace": + det = M[b, b]._eval_det_laplace() + dets.append(det) + return Mul(*dets) + + +# This functions is a candidate for caching if it gets implemented for matrices. +def _det_bareiss(M, iszerofunc=_is_zero_after_expand_mul): + """Compute matrix determinant using Bareiss' fraction-free + algorithm which is an extension of the well known Gaussian + elimination method. This approach is best suited for dense + symbolic matrices and will result in a determinant with + minimal number of fractions. It means that less term + rewriting is needed on resulting formulae. + + Parameters + ========== + + iszerofunc : function, optional + The function to use to determine zeros when doing an LU decomposition. + Defaults to ``lambda x: x.is_zero``. + + TODO: Implement algorithm for sparse matrices (SFF), + http://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps. + """ + + # Recursively implemented Bareiss' algorithm as per Deanna Richelle Leggett's + # thesis http://www.math.usm.edu/perry/Research/Thesis_DRL.pdf + def bareiss(mat, cumm=1): + if mat.rows == 0: + return mat.one + elif mat.rows == 1: + return mat[0, 0] + + # find a pivot and extract the remaining matrix + # With the default iszerofunc, _find_reasonable_pivot slows down + # the computation by the factor of 2.5 in one test. + # Relevant issues: #10279 and #13877. + pivot_pos, pivot_val, _, _ = _find_reasonable_pivot(mat[:, 0], iszerofunc=iszerofunc) + if pivot_pos is None: + return mat.zero + + # if we have a valid pivot, we'll do a "row swap", so keep the + # sign of the det + sign = (-1) ** (pivot_pos % 2) + + # we want every row but the pivot row and every column + rows = [i for i in range(mat.rows) if i != pivot_pos] + cols = list(range(mat.cols)) + tmp_mat = mat.extract(rows, cols) + + def entry(i, j): + ret = (pivot_val*tmp_mat[i, j + 1] - mat[pivot_pos, j + 1]*tmp_mat[i, 0]) / cumm + if _get_intermediate_simp_bool(True): + return _dotprodsimp(ret) + elif not ret.is_Atom: + return cancel(ret) + return ret + + return sign*bareiss(M._new(mat.rows - 1, mat.cols - 1, entry), pivot_val) + + if not M.is_square: + raise NonSquareMatrixError() + + if M.rows == 0: + return M.one + # sympy/matrices/tests/test_matrices.py contains a test that + # suggests that the determinant of a 0 x 0 matrix is one, by + # convention. + + return bareiss(M) + + +def _det_berkowitz(M): + """ Use the Berkowitz algorithm to compute the determinant.""" + + if not M.is_square: + raise NonSquareMatrixError() + + if M.rows == 0: + return M.one + # sympy/matrices/tests/test_matrices.py contains a test that + # suggests that the determinant of a 0 x 0 matrix is one, by + # convention. + + berk_vector = _berkowitz_vector(M) + return (-1)**(len(berk_vector) - 1) * berk_vector[-1] + + +# This functions is a candidate for caching if it gets implemented for matrices. +def _det_LU(M, iszerofunc=_iszero, simpfunc=None): + """ Computes the determinant of a matrix from its LU decomposition. + This function uses the LU decomposition computed by + LUDecomposition_Simple(). + + The keyword arguments iszerofunc and simpfunc are passed to + LUDecomposition_Simple(). + iszerofunc is a callable that returns a boolean indicating if its + input is zero, or None if it cannot make the determination. + simpfunc is a callable that simplifies its input. + The default is simpfunc=None, which indicate that the pivot search + algorithm should not attempt to simplify any candidate pivots. + If simpfunc fails to simplify its input, then it must return its input + instead of a copy. + + Parameters + ========== + + iszerofunc : function, optional + The function to use to determine zeros when doing an LU decomposition. + Defaults to ``lambda x: x.is_zero``. + + simpfunc : function, optional + The simplification function to use when looking for zeros for pivots. + """ + + if not M.is_square: + raise NonSquareMatrixError() + + if M.rows == 0: + return M.one + # sympy/matrices/tests/test_matrices.py contains a test that + # suggests that the determinant of a 0 x 0 matrix is one, by + # convention. + + lu, row_swaps = M.LUdecomposition_Simple(iszerofunc=iszerofunc, + simpfunc=simpfunc) + # P*A = L*U => det(A) = det(L)*det(U)/det(P) = det(P)*det(U). + # Lower triangular factor L encoded in lu has unit diagonal => det(L) = 1. + # P is a permutation matrix => det(P) in {-1, 1} => 1/det(P) = det(P). + # LUdecomposition_Simple() returns a list of row exchange index pairs, rather + # than a permutation matrix, but det(P) = (-1)**len(row_swaps). + + # Avoid forming the potentially time consuming product of U's diagonal entries + # if the product is zero. + # Bottom right entry of U is 0 => det(A) = 0. + # It may be impossible to determine if this entry of U is zero when it is symbolic. + if iszerofunc(lu[lu.rows-1, lu.rows-1]): + return M.zero + + # Compute det(P) + det = -M.one if len(row_swaps)%2 else M.one + + # Compute det(U) by calculating the product of U's diagonal entries. + # The upper triangular portion of lu is the upper triangular portion of the + # U factor in the LU decomposition. + for k in range(lu.rows): + det *= lu[k, k] + + # return det(P)*det(U) + return det + + +@cacheit +def __det_laplace(M): + """Compute the determinant of a matrix using Laplace expansion. + + This is a recursive function, and it should not be called directly. + Use _det_laplace() instead. The reason for splitting this function + into two is to allow caching of determinants of submatrices. While + one could also define this function inside _det_laplace(), that + would remove the advantage of using caching in Cramer Solve. + """ + n = M.shape[0] + if n == 1: + return M[0] + elif n == 2: + return M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0] + else: + return sum((-1) ** i * M[0, i] * + __det_laplace(M.minor_submatrix(0, i)) for i in range(n)) + + +def _det_laplace(M): + """Compute the determinant of a matrix using Laplace expansion. + + While Laplace expansion is not the most efficient method of computing + a determinant, it is a simple one, and it has the advantage of + being division free. To improve efficiency, this function uses + caching to avoid recomputing determinants of submatrices. + """ + if not M.is_square: + raise NonSquareMatrixError() + if M.shape[0] == 0: + return M.one + # sympy/matrices/tests/test_matrices.py contains a test that + # suggests that the determinant of a 0 x 0 matrix is one, by + # convention. + return __det_laplace(M.as_immutable()) + + +def _det_bird(M): + r"""Compute the determinant of a matrix using Bird's algorithm. + + Bird's algorithm is a simple division-free algorithm for computing, which + is of lower order than the Laplace's algorithm. It is described in [1]_. + + References + ========== + + .. [1] Bird, R. S. (2011). A simple division-free algorithm for computing + determinants. Inf. Process. Lett., 111(21), 1072-1074. doi: + 10.1016/j.ipl.2011.08.006 + """ + def mu(X): + n = X.shape[0] + zero = X.domain.zero + + total = zero + diag_sums = [zero] + for i in reversed(range(1, n)): + total -= X[i][i] + diag_sums.append(total) + diag_sums = diag_sums[::-1] + + elems = [[zero] * i + [diag_sums[i]] + X_i[i + 1:] for i, X_i in + enumerate(X)] + return DDM(elems, X.shape, X.domain) + + Mddm = M._rep.to_ddm() + n = M.shape[0] + if n == 0: + return M.one + # sympy/matrices/tests/test_matrices.py contains a test that + # suggests that the determinant of a 0 x 0 matrix is one, by + # convention. + Fn1 = Mddm + for _ in range(n - 1): + Fn1 = mu(Fn1).matmul(Mddm) + detA = Fn1[0][0] + if n % 2 == 0: + detA = -detA + + return Mddm.domain.to_sympy(detA) + + +def _minor(M, i, j, method="berkowitz"): + """Return the (i,j) minor of ``M``. That is, + return the determinant of the matrix obtained by deleting + the `i`th row and `j`th column from ``M``. + + Parameters + ========== + + i, j : int + The row and column to exclude to obtain the submatrix. + + method : string, optional + Method to use to find the determinant of the submatrix, can be + "bareiss", "berkowitz", "bird", "laplace" or "lu". + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> M.minor(1, 1) + -12 + + See Also + ======== + + minor_submatrix + cofactor + det + """ + + if not M.is_square: + raise NonSquareMatrixError() + + return M.minor_submatrix(i, j).det(method=method) + + +def _minor_submatrix(M, i, j): + """Return the submatrix obtained by removing the `i`th row + and `j`th column from ``M`` (works with Pythonic negative indices). + + Parameters + ========== + + i, j : int + The row and column to exclude to obtain the submatrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> M.minor_submatrix(1, 1) + Matrix([ + [1, 3], + [7, 9]]) + + See Also + ======== + + minor + cofactor + """ + + if i < 0: + i += M.rows + if j < 0: + j += M.cols + + if not 0 <= i < M.rows or not 0 <= j < M.cols: + raise ValueError("`i` and `j` must satisfy 0 <= i < ``M.rows`` " + "(%d)" % M.rows + "and 0 <= j < ``M.cols`` (%d)." % M.cols) + + rows = [a for a in range(M.rows) if a != i] + cols = [a for a in range(M.cols) if a != j] + + return M.extract(rows, cols) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/eigen.py b/venv/lib/python3.10/site-packages/sympy/matrices/eigen.py new file mode 100644 index 0000000000000000000000000000000000000000..87b2418efcece1c0b158ec56995bb011286feb3c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/eigen.py @@ -0,0 +1,1346 @@ +from types import FunctionType +from collections import Counter + +from mpmath import mp, workprec +from mpmath.libmp.libmpf import prec_to_dps + +from sympy.core.sorting import default_sort_key +from sympy.core.evalf import DEFAULT_MAXPREC, PrecisionExhausted +from sympy.core.logic import fuzzy_and, fuzzy_or +from sympy.core.numbers import Float +from sympy.core.sympify import _sympify +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.polys import roots, CRootOf, ZZ, QQ, EX +from sympy.polys.matrices import DomainMatrix +from sympy.polys.matrices.eigen import dom_eigenvects, dom_eigenvects_to_sympy +from sympy.polys.polytools import gcd + +from .exceptions import MatrixError, NonSquareMatrixError +from .determinant import _find_reasonable_pivot + +from .utilities import _iszero, _simplify + + +__doctest_requires__ = { + ('_is_indefinite', + '_is_negative_definite', + '_is_negative_semidefinite', + '_is_positive_definite', + '_is_positive_semidefinite'): ['matplotlib'], +} + + +def _eigenvals_eigenvects_mpmath(M): + norm2 = lambda v: mp.sqrt(sum(i**2 for i in v)) + + v1 = None + prec = max(x._prec for x in M.atoms(Float)) + eps = 2**-prec + + while prec < DEFAULT_MAXPREC: + with workprec(prec): + A = mp.matrix(M.evalf(n=prec_to_dps(prec))) + E, ER = mp.eig(A) + v2 = norm2([i for e in E for i in (mp.re(e), mp.im(e))]) + if v1 is not None and mp.fabs(v1 - v2) < eps: + return E, ER + v1 = v2 + prec *= 2 + + # we get here because the next step would have taken us + # past MAXPREC or because we never took a step; in case + # of the latter, we refuse to send back a solution since + # it would not have been verified; we also resist taking + # a small step to arrive exactly at MAXPREC since then + # the two calculations might be artificially close. + raise PrecisionExhausted + + +def _eigenvals_mpmath(M, multiple=False): + """Compute eigenvalues using mpmath""" + E, _ = _eigenvals_eigenvects_mpmath(M) + result = [_sympify(x) for x in E] + if multiple: + return result + return dict(Counter(result)) + + +def _eigenvects_mpmath(M): + E, ER = _eigenvals_eigenvects_mpmath(M) + result = [] + for i in range(M.rows): + eigenval = _sympify(E[i]) + eigenvect = _sympify(ER[:, i]) + result.append((eigenval, 1, [eigenvect])) + + return result + + +# This function is a candidate for caching if it gets implemented for matrices. +def _eigenvals( + M, error_when_incomplete=True, *, simplify=False, multiple=False, + rational=False, **flags): + r"""Compute eigenvalues of the matrix. + + Parameters + ========== + + error_when_incomplete : bool, optional + If it is set to ``True``, it will raise an error if not all + eigenvalues are computed. This is caused by ``roots`` not returning + a full list of eigenvalues. + + simplify : bool or function, optional + If it is set to ``True``, it attempts to return the most + simplified form of expressions returned by applying default + simplification method in every routine. + + If it is set to ``False``, it will skip simplification in this + particular routine to save computation resources. + + If a function is passed to, it will attempt to apply + the particular function as simplification method. + + rational : bool, optional + If it is set to ``True``, every floating point numbers would be + replaced with rationals before computation. It can solve some + issues of ``roots`` routine not working well with floats. + + multiple : bool, optional + If it is set to ``True``, the result will be in the form of a + list. + + If it is set to ``False``, the result will be in the form of a + dictionary. + + Returns + ======= + + eigs : list or dict + Eigenvalues of a matrix. The return format would be specified by + the key ``multiple``. + + Raises + ====== + + MatrixError + If not enough roots had got computed. + + NonSquareMatrixError + If attempted to compute eigenvalues from a non-square matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix(3, 3, [0, 1, 1, 1, 0, 0, 1, 1, 1]) + >>> M.eigenvals() + {-1: 1, 0: 1, 2: 1} + + See Also + ======== + + MatrixBase.charpoly + eigenvects + + Notes + ===== + + Eigenvalues of a matrix $A$ can be computed by solving a matrix + equation $\det(A - \lambda I) = 0$ + + It's not always possible to return radical solutions for + eigenvalues for matrices larger than $4, 4$ shape due to + Abel-Ruffini theorem. + + If there is no radical solution is found for the eigenvalue, + it may return eigenvalues in the form of + :class:`sympy.polys.rootoftools.ComplexRootOf`. + """ + if not M: + if multiple: + return [] + return {} + + if not M.is_square: + raise NonSquareMatrixError("{} must be a square matrix.".format(M)) + + if M._rep.domain not in (ZZ, QQ): + # Skip this check for ZZ/QQ because it can be slow + if all(x.is_number for x in M) and M.has(Float): + return _eigenvals_mpmath(M, multiple=multiple) + + if rational: + from sympy.simplify import nsimplify + M = M.applyfunc( + lambda x: nsimplify(x, rational=True) if x.has(Float) else x) + + if multiple: + return _eigenvals_list( + M, error_when_incomplete=error_when_incomplete, simplify=simplify, + **flags) + return _eigenvals_dict( + M, error_when_incomplete=error_when_incomplete, simplify=simplify, + **flags) + + +eigenvals_error_message = \ +"It is not always possible to express the eigenvalues of a matrix " + \ +"of size 5x5 or higher in radicals. " + \ +"We have CRootOf, but domains other than the rationals are not " + \ +"currently supported. " + \ +"If there are no symbols in the matrix, " + \ +"it should still be possible to compute numeric approximations " + \ +"of the eigenvalues using " + \ +"M.evalf().eigenvals() or M.charpoly().nroots()." + + +def _eigenvals_list( + M, error_when_incomplete=True, simplify=False, **flags): + iblocks = M.strongly_connected_components() + all_eigs = [] + is_dom = M._rep.domain in (ZZ, QQ) + for b in iblocks: + + # Fast path for a 1x1 block: + if is_dom and len(b) == 1: + index = b[0] + val = M[index, index] + all_eigs.append(val) + continue + + block = M[b, b] + + if isinstance(simplify, FunctionType): + charpoly = block.charpoly(simplify=simplify) + else: + charpoly = block.charpoly() + + eigs = roots(charpoly, multiple=True, **flags) + + if len(eigs) != block.rows: + try: + eigs = charpoly.all_roots(multiple=True) + except NotImplementedError: + if error_when_incomplete: + raise MatrixError(eigenvals_error_message) + else: + eigs = [] + + all_eigs += eigs + + if not simplify: + return all_eigs + if not isinstance(simplify, FunctionType): + simplify = _simplify + return [simplify(value) for value in all_eigs] + + +def _eigenvals_dict( + M, error_when_incomplete=True, simplify=False, **flags): + iblocks = M.strongly_connected_components() + all_eigs = {} + is_dom = M._rep.domain in (ZZ, QQ) + for b in iblocks: + + # Fast path for a 1x1 block: + if is_dom and len(b) == 1: + index = b[0] + val = M[index, index] + all_eigs[val] = all_eigs.get(val, 0) + 1 + continue + + block = M[b, b] + + if isinstance(simplify, FunctionType): + charpoly = block.charpoly(simplify=simplify) + else: + charpoly = block.charpoly() + + eigs = roots(charpoly, multiple=False, **flags) + + if sum(eigs.values()) != block.rows: + try: + eigs = dict(charpoly.all_roots(multiple=False)) + except NotImplementedError: + if error_when_incomplete: + raise MatrixError(eigenvals_error_message) + else: + eigs = {} + + for k, v in eigs.items(): + if k in all_eigs: + all_eigs[k] += v + else: + all_eigs[k] = v + + if not simplify: + return all_eigs + if not isinstance(simplify, FunctionType): + simplify = _simplify + return {simplify(key): value for key, value in all_eigs.items()} + + +def _eigenspace(M, eigenval, iszerofunc=_iszero, simplify=False): + """Get a basis for the eigenspace for a particular eigenvalue""" + m = M - M.eye(M.rows) * eigenval + ret = m.nullspace(iszerofunc=iszerofunc) + + # The nullspace for a real eigenvalue should be non-trivial. + # If we didn't find an eigenvector, try once more a little harder + if len(ret) == 0 and simplify: + ret = m.nullspace(iszerofunc=iszerofunc, simplify=True) + if len(ret) == 0: + raise NotImplementedError( + "Can't evaluate eigenvector for eigenvalue {}".format(eigenval)) + return ret + + +def _eigenvects_DOM(M, **kwargs): + DOM = DomainMatrix.from_Matrix(M, field=True, extension=True) + DOM = DOM.to_dense() + + if DOM.domain != EX: + rational, algebraic = dom_eigenvects(DOM) + eigenvects = dom_eigenvects_to_sympy( + rational, algebraic, M.__class__, **kwargs) + eigenvects = sorted(eigenvects, key=lambda x: default_sort_key(x[0])) + + return eigenvects + return None + + +def _eigenvects_sympy(M, iszerofunc, simplify=True, **flags): + eigenvals = M.eigenvals(rational=False, **flags) + + # Make sure that we have all roots in radical form + for x in eigenvals: + if x.has(CRootOf): + raise MatrixError( + "Eigenvector computation is not implemented if the matrix have " + "eigenvalues in CRootOf form") + + eigenvals = sorted(eigenvals.items(), key=default_sort_key) + ret = [] + for val, mult in eigenvals: + vects = _eigenspace(M, val, iszerofunc=iszerofunc, simplify=simplify) + ret.append((val, mult, vects)) + return ret + + +# This functions is a candidate for caching if it gets implemented for matrices. +def _eigenvects(M, error_when_incomplete=True, iszerofunc=_iszero, *, chop=False, **flags): + """Compute eigenvectors of the matrix. + + Parameters + ========== + + error_when_incomplete : bool, optional + Raise an error when not all eigenvalues are computed. This is + caused by ``roots`` not returning a full list of eigenvalues. + + iszerofunc : function, optional + Specifies a zero testing function to be used in ``rref``. + + Default value is ``_iszero``, which uses SymPy's naive and fast + default assumption handler. + + It can also accept any user-specified zero testing function, if it + is formatted as a function which accepts a single symbolic argument + and returns ``True`` if it is tested as zero and ``False`` if it + is tested as non-zero, and ``None`` if it is undecidable. + + simplify : bool or function, optional + If ``True``, ``as_content_primitive()`` will be used to tidy up + normalization artifacts. + + It will also be used by the ``nullspace`` routine. + + chop : bool or positive number, optional + If the matrix contains any Floats, they will be changed to Rationals + for computation purposes, but the answers will be returned after + being evaluated with evalf. The ``chop`` flag is passed to ``evalf``. + When ``chop=True`` a default precision will be used; a number will + be interpreted as the desired level of precision. + + Returns + ======= + + ret : [(eigenval, multiplicity, eigenspace), ...] + A ragged list containing tuples of data obtained by ``eigenvals`` + and ``nullspace``. + + ``eigenspace`` is a list containing the ``eigenvector`` for each + eigenvalue. + + ``eigenvector`` is a vector in the form of a ``Matrix``. e.g. + a vector of length 3 is returned as ``Matrix([a_1, a_2, a_3])``. + + Raises + ====== + + NotImplementedError + If failed to compute nullspace. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix(3, 3, [0, 1, 1, 1, 0, 0, 1, 1, 1]) + >>> M.eigenvects() + [(-1, 1, [Matrix([ + [-1], + [ 1], + [ 0]])]), (0, 1, [Matrix([ + [ 0], + [-1], + [ 1]])]), (2, 1, [Matrix([ + [2/3], + [1/3], + [ 1]])])] + + See Also + ======== + + eigenvals + MatrixBase.nullspace + """ + simplify = flags.get('simplify', True) + primitive = flags.get('simplify', False) + flags.pop('simplify', None) # remove this if it's there + flags.pop('multiple', None) # remove this if it's there + + if not isinstance(simplify, FunctionType): + simpfunc = _simplify if simplify else lambda x: x + + has_floats = M.has(Float) + if has_floats: + if all(x.is_number for x in M): + return _eigenvects_mpmath(M) + from sympy.simplify import nsimplify + M = M.applyfunc(lambda x: nsimplify(x, rational=True)) + + ret = _eigenvects_DOM(M) + if ret is None: + ret = _eigenvects_sympy(M, iszerofunc, simplify=simplify, **flags) + + if primitive: + # if the primitive flag is set, get rid of any common + # integer denominators + def denom_clean(l): + return [(v / gcd(list(v))).applyfunc(simpfunc) for v in l] + + ret = [(val, mult, denom_clean(es)) for val, mult, es in ret] + + if has_floats: + # if we had floats to start with, turn the eigenvectors to floats + ret = [(val.evalf(chop=chop), mult, [v.evalf(chop=chop) for v in es]) + for val, mult, es in ret] + + return ret + + +def _is_diagonalizable_with_eigen(M, reals_only=False): + """See _is_diagonalizable. This function returns the bool along with the + eigenvectors to avoid calculating them again in functions like + ``diagonalize``.""" + + if not M.is_square: + return False, [] + + eigenvecs = M.eigenvects(simplify=True) + + for val, mult, basis in eigenvecs: + if reals_only and not val.is_real: # if we have a complex eigenvalue + return False, eigenvecs + + if mult != len(basis): # if the geometric multiplicity doesn't equal the algebraic + return False, eigenvecs + + return True, eigenvecs + +def _is_diagonalizable(M, reals_only=False, **kwargs): + """Returns ``True`` if a matrix is diagonalizable. + + Parameters + ========== + + reals_only : bool, optional + If ``True``, it tests whether the matrix can be diagonalized + to contain only real numbers on the diagonal. + + + If ``False``, it tests whether the matrix can be diagonalized + at all, even with numbers that may not be real. + + Examples + ======== + + Example of a diagonalizable matrix: + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2, 0], [0, 3, 0], [2, -4, 2]]) + >>> M.is_diagonalizable() + True + + Example of a non-diagonalizable matrix: + + >>> M = Matrix([[0, 1], [0, 0]]) + >>> M.is_diagonalizable() + False + + Example of a matrix that is diagonalized in terms of non-real entries: + + >>> M = Matrix([[0, 1], [-1, 0]]) + >>> M.is_diagonalizable(reals_only=False) + True + >>> M.is_diagonalizable(reals_only=True) + False + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.is_diagonal + diagonalize + """ + if not M.is_square: + return False + + if all(e.is_real for e in M) and M.is_symmetric(): + return True + + if all(e.is_complex for e in M) and M.is_hermitian: + return True + + return _is_diagonalizable_with_eigen(M, reals_only=reals_only)[0] + + +#G&VL, Matrix Computations, Algo 5.4.2 +def _householder_vector(x): + if not x.cols == 1: + raise ValueError("Input must be a column matrix") + v = x.copy() + v_plus = x.copy() + v_minus = x.copy() + q = x[0, 0] / abs(x[0, 0]) + norm_x = x.norm() + v_plus[0, 0] = x[0, 0] + q * norm_x + v_minus[0, 0] = x[0, 0] - q * norm_x + if x[1:, 0].norm() == 0: + bet = 0 + v[0, 0] = 1 + else: + if v_plus.norm() <= v_minus.norm(): + v = v_plus + else: + v = v_minus + v = v / v[0] + bet = 2 / (v.norm() ** 2) + return v, bet + + +def _bidiagonal_decmp_hholder(M): + m = M.rows + n = M.cols + A = M.as_mutable() + U, V = A.eye(m), A.eye(n) + for i in range(min(m, n)): + v, bet = _householder_vector(A[i:, i]) + hh_mat = A.eye(m - i) - bet * v * v.H + A[i:, i:] = hh_mat * A[i:, i:] + temp = A.eye(m) + temp[i:, i:] = hh_mat + U = U * temp + if i + 1 <= n - 2: + v, bet = _householder_vector(A[i, i+1:].T) + hh_mat = A.eye(n - i - 1) - bet * v * v.H + A[i:, i+1:] = A[i:, i+1:] * hh_mat + temp = A.eye(n) + temp[i+1:, i+1:] = hh_mat + V = temp * V + return U, A, V + + +def _eval_bidiag_hholder(M): + m = M.rows + n = M.cols + A = M.as_mutable() + for i in range(min(m, n)): + v, bet = _householder_vector(A[i:, i]) + hh_mat = A.eye(m-i) - bet * v * v.H + A[i:, i:] = hh_mat * A[i:, i:] + if i + 1 <= n - 2: + v, bet = _householder_vector(A[i, i+1:].T) + hh_mat = A.eye(n - i - 1) - bet * v * v.H + A[i:, i+1:] = A[i:, i+1:] * hh_mat + return A + + +def _bidiagonal_decomposition(M, upper=True): + """ + Returns $(U,B,V.H)$ for + + $$A = UBV^{H}$$ + + where $A$ is the input matrix, and $B$ is its Bidiagonalized form + + Note: Bidiagonal Computation can hang for symbolic matrices. + + Parameters + ========== + + upper : bool. Whether to do upper bidiagnalization or lower. + True for upper and False for lower. + + References + ========== + + .. [1] Algorithm 5.4.2, Matrix computations by Golub and Van Loan, 4th edition + .. [2] Complex Matrix Bidiagonalization, https://github.com/vslobody/Householder-Bidiagonalization + + """ + + if not isinstance(upper, bool): + raise ValueError("upper must be a boolean") + + if upper: + return _bidiagonal_decmp_hholder(M) + + X = _bidiagonal_decmp_hholder(M.H) + return X[2].H, X[1].H, X[0].H + + +def _bidiagonalize(M, upper=True): + """ + Returns $B$, the Bidiagonalized form of the input matrix. + + Note: Bidiagonal Computation can hang for symbolic matrices. + + Parameters + ========== + + upper : bool. Whether to do upper bidiagnalization or lower. + True for upper and False for lower. + + References + ========== + + .. [1] Algorithm 5.4.2, Matrix computations by Golub and Van Loan, 4th edition + .. [2] Complex Matrix Bidiagonalization : https://github.com/vslobody/Householder-Bidiagonalization + + """ + + if not isinstance(upper, bool): + raise ValueError("upper must be a boolean") + + if upper: + return _eval_bidiag_hholder(M) + return _eval_bidiag_hholder(M.H).H + + +def _diagonalize(M, reals_only=False, sort=False, normalize=False): + """ + Return (P, D), where D is diagonal and + + D = P^-1 * M * P + + where M is current matrix. + + Parameters + ========== + + reals_only : bool. Whether to throw an error if complex numbers are need + to diagonalize. (Default: False) + + sort : bool. Sort the eigenvalues along the diagonal. (Default: False) + + normalize : bool. If True, normalize the columns of P. (Default: False) + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix(3, 3, [1, 2, 0, 0, 3, 0, 2, -4, 2]) + >>> M + Matrix([ + [1, 2, 0], + [0, 3, 0], + [2, -4, 2]]) + >>> (P, D) = M.diagonalize() + >>> D + Matrix([ + [1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + >>> P + Matrix([ + [-1, 0, -1], + [ 0, 0, -1], + [ 2, 1, 2]]) + >>> P.inv() * M * P + Matrix([ + [1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.is_diagonal + is_diagonalizable + """ + + if not M.is_square: + raise NonSquareMatrixError() + + is_diagonalizable, eigenvecs = _is_diagonalizable_with_eigen(M, + reals_only=reals_only) + + if not is_diagonalizable: + raise MatrixError("Matrix is not diagonalizable") + + if sort: + eigenvecs = sorted(eigenvecs, key=default_sort_key) + + p_cols, diag = [], [] + + for val, mult, basis in eigenvecs: + diag += [val] * mult + p_cols += basis + + if normalize: + p_cols = [v / v.norm() for v in p_cols] + + return M.hstack(*p_cols), M.diag(*diag) + + +def _fuzzy_positive_definite(M): + positive_diagonals = M._has_positive_diagonals() + if positive_diagonals is False: + return False + + if positive_diagonals and M.is_strongly_diagonally_dominant: + return True + + return None + + +def _fuzzy_positive_semidefinite(M): + nonnegative_diagonals = M._has_nonnegative_diagonals() + if nonnegative_diagonals is False: + return False + + if nonnegative_diagonals and M.is_weakly_diagonally_dominant: + return True + + return None + + +def _is_positive_definite(M): + if not M.is_hermitian: + if not M.is_square: + return False + M = M + M.H + + fuzzy = _fuzzy_positive_definite(M) + if fuzzy is not None: + return fuzzy + + return _is_positive_definite_GE(M) + + +def _is_positive_semidefinite(M): + if not M.is_hermitian: + if not M.is_square: + return False + M = M + M.H + + fuzzy = _fuzzy_positive_semidefinite(M) + if fuzzy is not None: + return fuzzy + + return _is_positive_semidefinite_cholesky(M) + + +def _is_negative_definite(M): + return _is_positive_definite(-M) + + +def _is_negative_semidefinite(M): + return _is_positive_semidefinite(-M) + + +def _is_indefinite(M): + if M.is_hermitian: + eigen = M.eigenvals() + args1 = [x.is_positive for x in eigen.keys()] + any_positive = fuzzy_or(args1) + args2 = [x.is_negative for x in eigen.keys()] + any_negative = fuzzy_or(args2) + + return fuzzy_and([any_positive, any_negative]) + + elif M.is_square: + return (M + M.H).is_indefinite + + return False + + +def _is_positive_definite_GE(M): + """A division-free gaussian elimination method for testing + positive-definiteness.""" + M = M.as_mutable() + size = M.rows + + for i in range(size): + is_positive = M[i, i].is_positive + if is_positive is not True: + return is_positive + for j in range(i+1, size): + M[j, i+1:] = M[i, i] * M[j, i+1:] - M[j, i] * M[i, i+1:] + return True + + +def _is_positive_semidefinite_cholesky(M): + """Uses Cholesky factorization with complete pivoting + + References + ========== + + .. [1] http://eprints.ma.man.ac.uk/1199/1/covered/MIMS_ep2008_116.pdf + + .. [2] https://www.value-at-risk.net/cholesky-factorization/ + """ + M = M.as_mutable() + for k in range(M.rows): + diags = [M[i, i] for i in range(k, M.rows)] + pivot, pivot_val, nonzero, _ = _find_reasonable_pivot(diags) + + if nonzero: + return None + + if pivot is None: + for i in range(k+1, M.rows): + for j in range(k, M.cols): + iszero = M[i, j].is_zero + if iszero is None: + return None + elif iszero is False: + return False + return True + + if M[k, k].is_negative or pivot_val.is_negative: + return False + elif not (M[k, k].is_nonnegative and pivot_val.is_nonnegative): + return None + + if pivot > 0: + M.col_swap(k, k+pivot) + M.row_swap(k, k+pivot) + + M[k, k] = sqrt(M[k, k]) + M[k, k+1:] /= M[k, k] + M[k+1:, k+1:] -= M[k, k+1:].H * M[k, k+1:] + + return M[-1, -1].is_nonnegative + + +_doc_positive_definite = \ + r"""Finds out the definiteness of a matrix. + + Explanation + =========== + + A square real matrix $A$ is: + + - A positive definite matrix if $x^T A x > 0$ + for all non-zero real vectors $x$. + - A positive semidefinite matrix if $x^T A x \geq 0$ + for all non-zero real vectors $x$. + - A negative definite matrix if $x^T A x < 0$ + for all non-zero real vectors $x$. + - A negative semidefinite matrix if $x^T A x \leq 0$ + for all non-zero real vectors $x$. + - An indefinite matrix if there exists non-zero real vectors + $x, y$ with $x^T A x > 0 > y^T A y$. + + A square complex matrix $A$ is: + + - A positive definite matrix if $\text{re}(x^H A x) > 0$ + for all non-zero complex vectors $x$. + - A positive semidefinite matrix if $\text{re}(x^H A x) \geq 0$ + for all non-zero complex vectors $x$. + - A negative definite matrix if $\text{re}(x^H A x) < 0$ + for all non-zero complex vectors $x$. + - A negative semidefinite matrix if $\text{re}(x^H A x) \leq 0$ + for all non-zero complex vectors $x$. + - An indefinite matrix if there exists non-zero complex vectors + $x, y$ with $\text{re}(x^H A x) > 0 > \text{re}(y^H A y)$. + + A matrix need not be symmetric or hermitian to be positive definite. + + - A real non-symmetric matrix is positive definite if and only if + $\frac{A + A^T}{2}$ is positive definite. + - A complex non-hermitian matrix is positive definite if and only if + $\frac{A + A^H}{2}$ is positive definite. + + And this extension can apply for all the definitions above. + + However, for complex cases, you can restrict the definition of + $\text{re}(x^H A x) > 0$ to $x^H A x > 0$ and require the matrix + to be hermitian. + But we do not present this restriction for computation because you + can check ``M.is_hermitian`` independently with this and use + the same procedure. + + Examples + ======== + + An example of symmetric positive definite matrix: + + .. plot:: + :context: reset + :format: doctest + :include-source: True + + >>> from sympy import Matrix, symbols + >>> from sympy.plotting import plot3d + >>> a, b = symbols('a b') + >>> x = Matrix([a, b]) + + >>> A = Matrix([[1, 0], [0, 1]]) + >>> A.is_positive_definite + True + >>> A.is_positive_semidefinite + True + + >>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1)) + + An example of symmetric positive semidefinite matrix: + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> A = Matrix([[1, -1], [-1, 1]]) + >>> A.is_positive_definite + False + >>> A.is_positive_semidefinite + True + + >>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1)) + + An example of symmetric negative definite matrix: + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> A = Matrix([[-1, 0], [0, -1]]) + >>> A.is_negative_definite + True + >>> A.is_negative_semidefinite + True + >>> A.is_indefinite + False + + >>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1)) + + An example of symmetric indefinite matrix: + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> A = Matrix([[1, 2], [2, -1]]) + >>> A.is_indefinite + True + + >>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1)) + + An example of non-symmetric positive definite matrix. + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> A = Matrix([[1, 2], [-2, 1]]) + >>> A.is_positive_definite + True + >>> A.is_positive_semidefinite + True + + >>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1)) + + Notes + ===== + + Although some people trivialize the definition of positive definite + matrices only for symmetric or hermitian matrices, this restriction + is not correct because it does not classify all instances of + positive definite matrices from the definition $x^T A x > 0$ or + $\text{re}(x^H A x) > 0$. + + For instance, ``Matrix([[1, 2], [-2, 1]])`` presented in + the example above is an example of real positive definite matrix + that is not symmetric. + + However, since the following formula holds true; + + .. math:: + \text{re}(x^H A x) > 0 \iff + \text{re}(x^H \frac{A + A^H}{2} x) > 0 + + We can classify all positive definite matrices that may or may not + be symmetric or hermitian by transforming the matrix to + $\frac{A + A^T}{2}$ or $\frac{A + A^H}{2}$ + (which is guaranteed to be always real symmetric or complex + hermitian) and we can defer most of the studies to symmetric or + hermitian positive definite matrices. + + But it is a different problem for the existence of Cholesky + decomposition. Because even though a non symmetric or a non + hermitian matrix can be positive definite, Cholesky or LDL + decomposition does not exist because the decompositions require the + matrix to be symmetric or hermitian. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Definiteness_of_a_matrix#Eigenvalues + + .. [2] https://mathworld.wolfram.com/PositiveDefiniteMatrix.html + + .. [3] Johnson, C. R. "Positive Definite Matrices." Amer. + Math. Monthly 77, 259-264 1970. + """ + +_is_positive_definite.__doc__ = _doc_positive_definite +_is_positive_semidefinite.__doc__ = _doc_positive_definite +_is_negative_definite.__doc__ = _doc_positive_definite +_is_negative_semidefinite.__doc__ = _doc_positive_definite +_is_indefinite.__doc__ = _doc_positive_definite + + +def _jordan_form(M, calc_transform=True, *, chop=False): + """Return $(P, J)$ where $J$ is a Jordan block + matrix and $P$ is a matrix such that $M = P J P^{-1}$ + + Parameters + ========== + + calc_transform : bool + If ``False``, then only $J$ is returned. + + chop : bool + All matrices are converted to exact types when computing + eigenvalues and eigenvectors. As a result, there may be + approximation errors. If ``chop==True``, these errors + will be truncated. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[ 6, 5, -2, -3], [-3, -1, 3, 3], [ 2, 1, -2, -3], [-1, 1, 5, 5]]) + >>> P, J = M.jordan_form() + >>> J + Matrix([ + [2, 1, 0, 0], + [0, 2, 0, 0], + [0, 0, 2, 1], + [0, 0, 0, 2]]) + + See Also + ======== + + jordan_block + """ + + if not M.is_square: + raise NonSquareMatrixError("Only square matrices have Jordan forms") + + mat = M + has_floats = M.has(Float) + + if has_floats: + try: + max_prec = max(term._prec for term in M.values() if isinstance(term, Float)) + except ValueError: + # if no term in the matrix is explicitly a Float calling max() + # will throw a error so setting max_prec to default value of 53 + max_prec = 53 + + # setting minimum max_dps to 15 to prevent loss of precision in + # matrix containing non evaluated expressions + max_dps = max(prec_to_dps(max_prec), 15) + + def restore_floats(*args): + """If ``has_floats`` is `True`, cast all ``args`` as + matrices of floats.""" + + if has_floats: + args = [m.evalf(n=max_dps, chop=chop) for m in args] + if len(args) == 1: + return args[0] + + return args + + # cache calculations for some speedup + mat_cache = {} + + def eig_mat(val, pow): + """Cache computations of ``(M - val*I)**pow`` for quick + retrieval""" + + if (val, pow) in mat_cache: + return mat_cache[(val, pow)] + + if (val, pow - 1) in mat_cache: + mat_cache[(val, pow)] = mat_cache[(val, pow - 1)].multiply( + mat_cache[(val, 1)], dotprodsimp=None) + else: + mat_cache[(val, pow)] = (mat - val*M.eye(M.rows)).pow(pow) + + return mat_cache[(val, pow)] + + # helper functions + def nullity_chain(val, algebraic_multiplicity): + """Calculate the sequence [0, nullity(E), nullity(E**2), ...] + until it is constant where ``E = M - val*I``""" + + # mat.rank() is faster than computing the null space, + # so use the rank-nullity theorem + cols = M.cols + ret = [0] + nullity = cols - eig_mat(val, 1).rank() + i = 2 + + while nullity != ret[-1]: + ret.append(nullity) + + if nullity == algebraic_multiplicity: + break + + nullity = cols - eig_mat(val, i).rank() + i += 1 + + # Due to issues like #7146 and #15872, SymPy sometimes + # gives the wrong rank. In this case, raise an error + # instead of returning an incorrect matrix + if nullity < ret[-1] or nullity > algebraic_multiplicity: + raise MatrixError( + "SymPy had encountered an inconsistent " + "result while computing Jordan block: " + "{}".format(M)) + + return ret + + def blocks_from_nullity_chain(d): + """Return a list of the size of each Jordan block. + If d_n is the nullity of E**n, then the number + of Jordan blocks of size n is + + 2*d_n - d_(n-1) - d_(n+1)""" + + # d[0] is always the number of columns, so skip past it + mid = [2*d[n] - d[n - 1] - d[n + 1] for n in range(1, len(d) - 1)] + # d is assumed to plateau with "d[ len(d) ] == d[-1]", so + # 2*d_n - d_(n-1) - d_(n+1) == d_n - d_(n-1) + end = [d[-1] - d[-2]] if len(d) > 1 else [d[0]] + + return mid + end + + def pick_vec(small_basis, big_basis): + """Picks a vector from big_basis that isn't in + the subspace spanned by small_basis""" + + if len(small_basis) == 0: + return big_basis[0] + + for v in big_basis: + _, pivots = M.hstack(*(small_basis + [v])).echelon_form( + with_pivots=True) + + if pivots[-1] == len(small_basis): + return v + + # roots doesn't like Floats, so replace them with Rationals + if has_floats: + from sympy.simplify import nsimplify + mat = mat.applyfunc(lambda x: nsimplify(x, rational=True)) + + # first calculate the jordan block structure + eigs = mat.eigenvals() + + # Make sure that we have all roots in radical form + for x in eigs: + if x.has(CRootOf): + raise MatrixError( + "Jordan normal form is not implemented if the matrix have " + "eigenvalues in CRootOf form") + + # most matrices have distinct eigenvalues + # and so are diagonalizable. In this case, don't + # do extra work! + if len(eigs.keys()) == mat.cols: + blocks = sorted(eigs.keys(), key=default_sort_key) + jordan_mat = mat.diag(*blocks) + + if not calc_transform: + return restore_floats(jordan_mat) + + jordan_basis = [eig_mat(eig, 1).nullspace()[0] + for eig in blocks] + basis_mat = mat.hstack(*jordan_basis) + + return restore_floats(basis_mat, jordan_mat) + + block_structure = [] + + for eig in sorted(eigs.keys(), key=default_sort_key): + algebraic_multiplicity = eigs[eig] + chain = nullity_chain(eig, algebraic_multiplicity) + block_sizes = blocks_from_nullity_chain(chain) + + # if block_sizes = = [a, b, c, ...], then the number of + # Jordan blocks of size 1 is a, of size 2 is b, etc. + # create an array that has (eig, block_size) with one + # entry for each block + size_nums = [(i+1, num) for i, num in enumerate(block_sizes)] + + # we expect larger Jordan blocks to come earlier + size_nums.reverse() + + block_structure.extend( + [(eig, size) for size, num in size_nums for _ in range(num)]) + + jordan_form_size = sum(size for eig, size in block_structure) + + if jordan_form_size != M.rows: + raise MatrixError( + "SymPy had encountered an inconsistent result while " + "computing Jordan block. : {}".format(M)) + + blocks = (mat.jordan_block(size=size, eigenvalue=eig) for eig, size in block_structure) + jordan_mat = mat.diag(*blocks) + + if not calc_transform: + return restore_floats(jordan_mat) + + # For each generalized eigenspace, calculate a basis. + # We start by looking for a vector in null( (A - eig*I)**n ) + # which isn't in null( (A - eig*I)**(n-1) ) where n is + # the size of the Jordan block + # + # Ideally we'd just loop through block_structure and + # compute each generalized eigenspace. However, this + # causes a lot of unneeded computation. Instead, we + # go through the eigenvalues separately, since we know + # their generalized eigenspaces must have bases that + # are linearly independent. + jordan_basis = [] + + for eig in sorted(eigs.keys(), key=default_sort_key): + eig_basis = [] + + for block_eig, size in block_structure: + if block_eig != eig: + continue + + null_big = (eig_mat(eig, size)).nullspace() + null_small = (eig_mat(eig, size - 1)).nullspace() + + # we want to pick something that is in the big basis + # and not the small, but also something that is independent + # of any other generalized eigenvectors from a different + # generalized eigenspace sharing the same eigenvalue. + vec = pick_vec(null_small + eig_basis, null_big) + new_vecs = [eig_mat(eig, i).multiply(vec, dotprodsimp=None) + for i in range(size)] + + eig_basis.extend(new_vecs) + jordan_basis.extend(reversed(new_vecs)) + + basis_mat = mat.hstack(*jordan_basis) + + return restore_floats(basis_mat, jordan_mat) + + +def _left_eigenvects(M, **flags): + """Returns left eigenvectors and eigenvalues. + + This function returns the list of triples (eigenval, multiplicity, + basis) for the left eigenvectors. Options are the same as for + eigenvects(), i.e. the ``**flags`` arguments gets passed directly to + eigenvects(). + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[0, 1, 1], [1, 0, 0], [1, 1, 1]]) + >>> M.eigenvects() + [(-1, 1, [Matrix([ + [-1], + [ 1], + [ 0]])]), (0, 1, [Matrix([ + [ 0], + [-1], + [ 1]])]), (2, 1, [Matrix([ + [2/3], + [1/3], + [ 1]])])] + >>> M.left_eigenvects() + [(-1, 1, [Matrix([[-2, 1, 1]])]), (0, 1, [Matrix([[-1, -1, 1]])]), (2, + 1, [Matrix([[1, 1, 1]])])] + + """ + + eigs = M.transpose().eigenvects(**flags) + + return [(val, mult, [l.transpose() for l in basis]) for val, mult, basis in eigs] + + +def _singular_values(M): + """Compute the singular values of a Matrix + + Examples + ======== + + >>> from sympy import Matrix, Symbol + >>> x = Symbol('x', real=True) + >>> M = Matrix([[0, 1, 0], [0, x, 0], [-1, 0, 0]]) + >>> M.singular_values() + [sqrt(x**2 + 1), 1, 0] + + See Also + ======== + + condition_number + """ + + if M.rows >= M.cols: + valmultpairs = M.H.multiply(M).eigenvals() + else: + valmultpairs = M.multiply(M.H).eigenvals() + + # Expands result from eigenvals into a simple list + vals = [] + + for k, v in valmultpairs.items(): + vals += [sqrt(k)] * v # dangerous! same k in several spots! + + # Pad with zeros if singular values are computed in reverse way, + # to give consistent format. + if len(vals) < M.cols: + vals += [M.zero] * (M.cols - len(vals)) + + # sort them in descending order + vals.sort(reverse=True, key=default_sort_key) + + return vals diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/exceptions.py b/venv/lib/python3.10/site-packages/sympy/matrices/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..bfc7cfa0bdffd59ff2bc5a9cd85cf9b04ed1a63d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/exceptions.py @@ -0,0 +1,26 @@ +""" +Exceptions raised by the matrix module. +""" + + +class MatrixError(Exception): + pass + + +class ShapeError(ValueError, MatrixError): + """Wrong matrix shape""" + pass + + +class NonSquareMatrixError(ShapeError): + pass + + +class NonInvertibleMatrixError(ValueError, MatrixError): + """The matrix in not invertible (division by multidimensional zero error).""" + pass + + +class NonPositiveDefiniteMatrixError(ValueError, MatrixError): + """The matrix is not a positive-definite matrix.""" + pass diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__init__.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5f4ab203ab74165d1003cdedd83945ea3fcf8f47 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__init__.py @@ -0,0 +1,62 @@ +""" A module which handles Matrix Expressions """ + +from .slice import MatrixSlice +from .blockmatrix import BlockMatrix, BlockDiagMatrix, block_collapse, blockcut +from .companion import CompanionMatrix +from .funcmatrix import FunctionMatrix +from .inverse import Inverse +from .matadd import MatAdd +from .matexpr import MatrixExpr, MatrixSymbol, matrix_symbols +from .matmul import MatMul +from .matpow import MatPow +from .trace import Trace, trace +from .determinant import Determinant, det, Permanent, per +from .transpose import Transpose +from .adjoint import Adjoint +from .hadamard import hadamard_product, HadamardProduct, hadamard_power, HadamardPower +from .diagonal import DiagonalMatrix, DiagonalOf, DiagMatrix, diagonalize_vector +from .dotproduct import DotProduct +from .kronecker import kronecker_product, KroneckerProduct, combine_kronecker +from .permutation import PermutationMatrix, MatrixPermute +from .sets import MatrixSet +from .special import ZeroMatrix, Identity, OneMatrix + +__all__ = [ + 'MatrixSlice', + + 'BlockMatrix', 'BlockDiagMatrix', 'block_collapse', 'blockcut', + 'FunctionMatrix', + + 'CompanionMatrix', + + 'Inverse', + + 'MatAdd', + + 'Identity', 'MatrixExpr', 'MatrixSymbol', 'ZeroMatrix', 'OneMatrix', + 'matrix_symbols', 'MatrixSet', + + 'MatMul', + + 'MatPow', + + 'Trace', 'trace', + + 'Determinant', 'det', + + 'Transpose', + + 'Adjoint', + + 'hadamard_product', 'HadamardProduct', 'hadamard_power', 'HadamardPower', + + 'DiagonalMatrix', 'DiagonalOf', 'DiagMatrix', 'diagonalize_vector', + + 'DotProduct', + + 'kronecker_product', 'KroneckerProduct', 'combine_kronecker', + + 'PermutationMatrix', 'MatrixPermute', + + 'Permanent', 'per' +] diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aac2395db00b789a1ba04307c3c5c3468bb22f4b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/_shape.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/_shape.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..094b4f65d926d6f04bead3dfc8f781a54bacc121 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/_shape.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/adjoint.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/adjoint.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f20895001fc111c9565286e54177ff125c64c8e9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/adjoint.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/applyfunc.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/applyfunc.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11340a0e81d89cbf9482e24ec75d3d9f2e08ad06 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/applyfunc.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/blockmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/blockmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e779212baf9768a392fb97aec361c0ba518a178 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/blockmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/companion.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/companion.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1de4d2c39fd6be55c3ab005ab43d81f43add824f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/companion.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/determinant.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/determinant.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68d4fa3453d7534660160f857b36f5cbcf6e343f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/determinant.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/diagonal.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/diagonal.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ecd9b05fe9f3df39b09fe091446bffed30b58e7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/diagonal.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/dotproduct.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/dotproduct.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..303b46b26e00ed0ce19b8238b2066549f89c2a7a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/dotproduct.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/factorizations.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/factorizations.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c4545c39556c39213c3a38cc2a9eb5e5abd09bc Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/factorizations.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/fourier.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/fourier.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5da07b0cd400c0ed0e87366390eaf7e42ab660c6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/fourier.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/funcmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/funcmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..934a716d465cfd588d1b92c6d00be2cadd1db54d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/funcmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/hadamard.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/hadamard.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f82713064061b698e3d2f536c01f88d718324db Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/hadamard.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/inverse.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/inverse.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abcedc3d5c01cb510445b760da63c9c8d54d5150 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/inverse.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/kronecker.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/kronecker.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e673516d2ac1e476911a0fc058052c0d7cc0a98 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/kronecker.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matadd.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matadd.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9febe42d59087abefd6828038765330fec836b90 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matadd.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matexpr.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matexpr.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1ffce582ecd6623ebbd650b2b976699eb3f87af Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matexpr.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matmul.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matmul.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de6d140e014ffebc1183155ea86027f57a41d3e7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matmul.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matpow.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matpow.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8b90a56072bc174bb8027a8f65e6ccbf94955aa Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/matpow.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/permutation.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/permutation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..deed02c2cf1a7ea805d5d9c0f4670946e7093131 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/permutation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/sets.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/sets.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..918394a3822773e6f1a431d6e8889e42f34c35d0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/sets.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/slice.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/slice.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b89a15c82c64eb091584a31f473d5a77d31e45eb Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/slice.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/special.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/special.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db996f12ce404a166d55a8263241ef5d6148d118 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/special.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/trace.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/trace.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..327b5d6e8cef61d762923e1fea56edb544fa4f99 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/trace.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/transpose.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/transpose.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cd0e7a4e91bf521dff252cd4ea6c8f283847e78 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/__pycache__/transpose.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/_shape.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/_shape.py new file mode 100644 index 0000000000000000000000000000000000000000..a95d481bf8e1edf4c62992044cd50563b335caac --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/_shape.py @@ -0,0 +1,102 @@ +from sympy.core.relational import Eq +from sympy.core.expr import Expr +from sympy.core.numbers import Integer +from sympy.logic.boolalg import Boolean, And +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.matrices.exceptions import ShapeError +from typing import Union + + +def is_matadd_valid(*args: MatrixExpr) -> Boolean: + """Return the symbolic condition how ``MatAdd``, ``HadamardProduct`` + makes sense. + + Parameters + ========== + + args + The list of arguments of matrices to be tested for. + + Examples + ======== + + >>> from sympy import MatrixSymbol, symbols + >>> from sympy.matrices.expressions._shape import is_matadd_valid + + >>> m, n, p, q = symbols('m n p q') + >>> A = MatrixSymbol('A', m, n) + >>> B = MatrixSymbol('B', p, q) + >>> is_matadd_valid(A, B) + Eq(m, p) & Eq(n, q) + """ + rows, cols = zip(*(arg.shape for arg in args)) + return And( + *(Eq(i, j) for i, j in zip(rows[:-1], rows[1:])), + *(Eq(i, j) for i, j in zip(cols[:-1], cols[1:])), + ) + + +def is_matmul_valid(*args: Union[MatrixExpr, Expr]) -> Boolean: + """Return the symbolic condition how ``MatMul`` makes sense + + Parameters + ========== + + args + The list of arguments of matrices and scalar expressions to be tested + for. + + Examples + ======== + + >>> from sympy import MatrixSymbol, symbols + >>> from sympy.matrices.expressions._shape import is_matmul_valid + + >>> m, n, p, q = symbols('m n p q') + >>> A = MatrixSymbol('A', m, n) + >>> B = MatrixSymbol('B', p, q) + >>> is_matmul_valid(A, B) + Eq(n, p) + """ + rows, cols = zip(*(arg.shape for arg in args if isinstance(arg, MatrixExpr))) + return And(*(Eq(i, j) for i, j in zip(cols[:-1], rows[1:]))) + + +def is_square(arg: MatrixExpr, /) -> Boolean: + """Return the symbolic condition how the matrix is assumed to be square + + Parameters + ========== + + arg + The matrix to be tested for. + + Examples + ======== + + >>> from sympy import MatrixSymbol, symbols + >>> from sympy.matrices.expressions._shape import is_square + + >>> m, n = symbols('m n') + >>> A = MatrixSymbol('A', m, n) + >>> is_square(A) + Eq(m, n) + """ + return Eq(arg.rows, arg.cols) + + +def validate_matadd_integer(*args: MatrixExpr) -> None: + """Validate matrix shape for addition only for integer values""" + rows, cols = zip(*(x.shape for x in args)) + if len(set(filter(lambda x: isinstance(x, (int, Integer)), rows))) > 1: + raise ShapeError(f"Matrices have mismatching shape: {rows}") + if len(set(filter(lambda x: isinstance(x, (int, Integer)), cols))) > 1: + raise ShapeError(f"Matrices have mismatching shape: {cols}") + + +def validate_matmul_integer(*args: MatrixExpr) -> None: + """Validate matrix shape for multiplication only for integer values""" + for A, B in zip(args[:-1], args[1:]): + i, j = A.cols, B.rows + if isinstance(i, (int, Integer)) and isinstance(j, (int, Integer)) and i != j: + raise ShapeError("Matrices are not aligned", i, j) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/adjoint.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/adjoint.py new file mode 100644 index 0000000000000000000000000000000000000000..2039a7b2eb8eeacb02435979121c4133a11d8e02 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/adjoint.py @@ -0,0 +1,60 @@ +from sympy.core import Basic +from sympy.functions import adjoint, conjugate +from sympy.matrices.expressions.matexpr import MatrixExpr + + +class Adjoint(MatrixExpr): + """ + The Hermitian adjoint of a matrix expression. + + This is a symbolic object that simply stores its argument without + evaluating it. To actually compute the adjoint, use the ``adjoint()`` + function. + + Examples + ======== + + >>> from sympy import MatrixSymbol, Adjoint, adjoint + >>> A = MatrixSymbol('A', 3, 5) + >>> B = MatrixSymbol('B', 5, 3) + >>> Adjoint(A*B) + Adjoint(A*B) + >>> adjoint(A*B) + Adjoint(B)*Adjoint(A) + >>> adjoint(A*B) == Adjoint(A*B) + False + >>> adjoint(A*B) == Adjoint(A*B).doit() + True + """ + is_Adjoint = True + + def doit(self, **hints): + arg = self.arg + if hints.get('deep', True) and isinstance(arg, Basic): + return adjoint(arg.doit(**hints)) + else: + return adjoint(self.arg) + + @property + def arg(self): + return self.args[0] + + @property + def shape(self): + return self.arg.shape[::-1] + + def _entry(self, i, j, **kwargs): + return conjugate(self.arg._entry(j, i, **kwargs)) + + def _eval_adjoint(self): + return self.arg + + def _eval_transpose(self): + return self.arg.conjugate() + + def _eval_conjugate(self): + return self.arg.transpose() + + def _eval_trace(self): + from sympy.matrices.expressions.trace import Trace + return conjugate(Trace(self.arg)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/applyfunc.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/applyfunc.py new file mode 100644 index 0000000000000000000000000000000000000000..c0363658447a8dc37a152b30e45533bac582b10c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/applyfunc.py @@ -0,0 +1,204 @@ +from sympy.core.expr import ExprBuilder +from sympy.core.function import (Function, FunctionClass, Lambda) +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify, _sympify +from sympy.matrices.expressions import MatrixExpr +from sympy.matrices.matrixbase import MatrixBase + + +class ElementwiseApplyFunction(MatrixExpr): + r""" + Apply function to a matrix elementwise without evaluating. + + Examples + ======== + + It can be created by calling ``.applyfunc()`` on a matrix + expression: + + >>> from sympy import MatrixSymbol + >>> from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction + >>> from sympy import exp + >>> X = MatrixSymbol("X", 3, 3) + >>> X.applyfunc(exp) + Lambda(_d, exp(_d)).(X) + + Otherwise using the class constructor: + + >>> from sympy import eye + >>> expr = ElementwiseApplyFunction(exp, eye(3)) + >>> expr + Lambda(_d, exp(_d)).(Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]])) + >>> expr.doit() + Matrix([ + [E, 1, 1], + [1, E, 1], + [1, 1, E]]) + + Notice the difference with the real mathematical functions: + + >>> exp(eye(3)) + Matrix([ + [E, 0, 0], + [0, E, 0], + [0, 0, E]]) + """ + + def __new__(cls, function, expr): + expr = _sympify(expr) + if not expr.is_Matrix: + raise ValueError("{} must be a matrix instance.".format(expr)) + + if expr.shape == (1, 1): + # Check if the function returns a matrix, in that case, just apply + # the function instead of creating an ElementwiseApplyFunc object: + ret = function(expr) + if isinstance(ret, MatrixExpr): + return ret + + if not isinstance(function, (FunctionClass, Lambda)): + d = Dummy('d') + function = Lambda(d, function(d)) + + function = sympify(function) + if not isinstance(function, (FunctionClass, Lambda)): + raise ValueError( + "{} should be compatible with SymPy function classes." + .format(function)) + + if 1 not in function.nargs: + raise ValueError( + '{} should be able to accept 1 arguments.'.format(function)) + + if not isinstance(function, Lambda): + d = Dummy('d') + function = Lambda(d, function(d)) + + obj = MatrixExpr.__new__(cls, function, expr) + return obj + + @property + def function(self): + return self.args[0] + + @property + def expr(self): + return self.args[1] + + @property + def shape(self): + return self.expr.shape + + def doit(self, **hints): + deep = hints.get("deep", True) + expr = self.expr + if deep: + expr = expr.doit(**hints) + function = self.function + if isinstance(function, Lambda) and function.is_identity: + # This is a Lambda containing the identity function. + return expr + if isinstance(expr, MatrixBase): + return expr.applyfunc(self.function) + elif isinstance(expr, ElementwiseApplyFunction): + return ElementwiseApplyFunction( + lambda x: self.function(expr.function(x)), + expr.expr + ).doit(**hints) + else: + return self + + def _entry(self, i, j, **kwargs): + return self.function(self.expr._entry(i, j, **kwargs)) + + def _get_function_fdiff(self): + d = Dummy("d") + function = self.function(d) + fdiff = function.diff(d) + if isinstance(fdiff, Function): + fdiff = type(fdiff) + else: + fdiff = Lambda(d, fdiff) + return fdiff + + def _eval_derivative(self, x): + from sympy.matrices.expressions.hadamard import hadamard_product + dexpr = self.expr.diff(x) + fdiff = self._get_function_fdiff() + return hadamard_product( + dexpr, + ElementwiseApplyFunction(fdiff, self.expr) + ) + + def _eval_derivative_matrix_lines(self, x): + from sympy.matrices.expressions.special import Identity + from sympy.tensor.array.expressions.array_expressions import ArrayContraction + from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal + from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct + + fdiff = self._get_function_fdiff() + lr = self.expr._eval_derivative_matrix_lines(x) + ewdiff = ElementwiseApplyFunction(fdiff, self.expr) + if 1 in x.shape: + # Vector: + iscolumn = self.shape[1] == 1 + for i in lr: + if iscolumn: + ptr1 = i.first_pointer + ptr2 = Identity(self.shape[1]) + else: + ptr1 = Identity(self.shape[0]) + ptr2 = i.second_pointer + + subexpr = ExprBuilder( + ArrayDiagonal, + [ + ExprBuilder( + ArrayTensorProduct, + [ + ewdiff, + ptr1, + ptr2, + ] + ), + (0, 2) if iscolumn else (1, 4) + ], + validator=ArrayDiagonal._validate + ) + i._lines = [subexpr] + i._first_pointer_parent = subexpr.args[0].args + i._first_pointer_index = 1 + i._second_pointer_parent = subexpr.args[0].args + i._second_pointer_index = 2 + else: + # Matrix case: + for i in lr: + ptr1 = i.first_pointer + ptr2 = i.second_pointer + newptr1 = Identity(ptr1.shape[1]) + newptr2 = Identity(ptr2.shape[1]) + subexpr = ExprBuilder( + ArrayContraction, + [ + ExprBuilder( + ArrayTensorProduct, + [ptr1, newptr1, ewdiff, ptr2, newptr2] + ), + (1, 2, 4), + (5, 7, 8), + ], + validator=ArrayContraction._validate + ) + i._first_pointer_parent = subexpr.args[0].args + i._first_pointer_index = 1 + i._second_pointer_parent = subexpr.args[0].args + i._second_pointer_index = 4 + i._lines = [subexpr] + return lr + + def _eval_transpose(self): + from sympy.matrices.expressions.transpose import Transpose + return self.func(self.function, Transpose(self.expr).doit()) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/blockmatrix.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/blockmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..0125d6233ba7cf8c0b590fbb655d9c7c447e0bd4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/blockmatrix.py @@ -0,0 +1,975 @@ +from sympy.assumptions.ask import (Q, ask) +from sympy.core import Basic, Add, Mul, S +from sympy.core.sympify import _sympify +from sympy.functions.elementary.complexes import re, im +from sympy.strategies import typed, exhaust, condition, do_one, unpack +from sympy.strategies.traverse import bottom_up +from sympy.utilities.iterables import is_sequence, sift +from sympy.utilities.misc import filldedent + +from sympy.matrices import Matrix, ShapeError +from sympy.matrices.exceptions import NonInvertibleMatrixError +from sympy.matrices.expressions.determinant import det, Determinant +from sympy.matrices.expressions.inverse import Inverse +from sympy.matrices.expressions.matadd import MatAdd +from sympy.matrices.expressions.matexpr import MatrixExpr, MatrixElement +from sympy.matrices.expressions.matmul import MatMul +from sympy.matrices.expressions.matpow import MatPow +from sympy.matrices.expressions.slice import MatrixSlice +from sympy.matrices.expressions.special import ZeroMatrix, Identity +from sympy.matrices.expressions.trace import trace +from sympy.matrices.expressions.transpose import Transpose, transpose + + +class BlockMatrix(MatrixExpr): + """A BlockMatrix is a Matrix comprised of other matrices. + + The submatrices are stored in a SymPy Matrix object but accessed as part of + a Matrix Expression + + >>> from sympy import (MatrixSymbol, BlockMatrix, symbols, + ... Identity, ZeroMatrix, block_collapse) + >>> n,m,l = symbols('n m l') + >>> X = MatrixSymbol('X', n, n) + >>> Y = MatrixSymbol('Y', m, m) + >>> Z = MatrixSymbol('Z', n, m) + >>> B = BlockMatrix([[X, Z], [ZeroMatrix(m,n), Y]]) + >>> print(B) + Matrix([ + [X, Z], + [0, Y]]) + + >>> C = BlockMatrix([[Identity(n), Z]]) + >>> print(C) + Matrix([[I, Z]]) + + >>> print(block_collapse(C*B)) + Matrix([[X, Z + Z*Y]]) + + Some matrices might be comprised of rows of blocks with + the matrices in each row having the same height and the + rows all having the same total number of columns but + not having the same number of columns for each matrix + in each row. In this case, the matrix is not a block + matrix and should be instantiated by Matrix. + + >>> from sympy import ones, Matrix + >>> dat = [ + ... [ones(3,2), ones(3,3)*2], + ... [ones(2,3)*3, ones(2,2)*4]] + ... + >>> BlockMatrix(dat) + Traceback (most recent call last): + ... + ValueError: + Although this matrix is comprised of blocks, the blocks do not fill + the matrix in a size-symmetric fashion. To create a full matrix from + these arguments, pass them directly to Matrix. + >>> Matrix(dat) + Matrix([ + [1, 1, 2, 2, 2], + [1, 1, 2, 2, 2], + [1, 1, 2, 2, 2], + [3, 3, 3, 4, 4], + [3, 3, 3, 4, 4]]) + + See Also + ======== + sympy.matrices.matrixbase.MatrixBase.irregular + """ + def __new__(cls, *args, **kwargs): + from sympy.matrices.immutable import ImmutableDenseMatrix + isMat = lambda i: getattr(i, 'is_Matrix', False) + if len(args) != 1 or \ + not is_sequence(args[0]) or \ + len({isMat(r) for r in args[0]}) != 1: + raise ValueError(filldedent(''' + expecting a sequence of 1 or more rows + containing Matrices.''')) + rows = args[0] if args else [] + if not isMat(rows): + if rows and isMat(rows[0]): + rows = [rows] # rows is not list of lists or [] + # regularity check + # same number of matrices in each row + blocky = ok = len({len(r) for r in rows}) == 1 + if ok: + # same number of rows for each matrix in a row + for r in rows: + ok = len({i.rows for i in r}) == 1 + if not ok: + break + blocky = ok + if ok: + # same number of cols for each matrix in each col + for c in range(len(rows[0])): + ok = len({rows[i][c].cols + for i in range(len(rows))}) == 1 + if not ok: + break + if not ok: + # same total cols in each row + ok = len({ + sum(i.cols for i in r) for r in rows}) == 1 + if blocky and ok: + raise ValueError(filldedent(''' + Although this matrix is comprised of blocks, + the blocks do not fill the matrix in a + size-symmetric fashion. To create a full matrix + from these arguments, pass them directly to + Matrix.''')) + raise ValueError(filldedent(''' + When there are not the same number of rows in each + row's matrices or there are not the same number of + total columns in each row, the matrix is not a + block matrix. If this matrix is known to consist of + blocks fully filling a 2-D space then see + Matrix.irregular.''')) + mat = ImmutableDenseMatrix(rows, evaluate=False) + obj = Basic.__new__(cls, mat) + return obj + + @property + def shape(self): + numrows = numcols = 0 + M = self.blocks + for i in range(M.shape[0]): + numrows += M[i, 0].shape[0] + for i in range(M.shape[1]): + numcols += M[0, i].shape[1] + return (numrows, numcols) + + @property + def blockshape(self): + return self.blocks.shape + + @property + def blocks(self): + return self.args[0] + + @property + def rowblocksizes(self): + return [self.blocks[i, 0].rows for i in range(self.blockshape[0])] + + @property + def colblocksizes(self): + return [self.blocks[0, i].cols for i in range(self.blockshape[1])] + + def structurally_equal(self, other): + return (isinstance(other, BlockMatrix) + and self.shape == other.shape + and self.blockshape == other.blockshape + and self.rowblocksizes == other.rowblocksizes + and self.colblocksizes == other.colblocksizes) + + def _blockmul(self, other): + if (isinstance(other, BlockMatrix) and + self.colblocksizes == other.rowblocksizes): + return BlockMatrix(self.blocks*other.blocks) + + return self * other + + def _blockadd(self, other): + if (isinstance(other, BlockMatrix) + and self.structurally_equal(other)): + return BlockMatrix(self.blocks + other.blocks) + + return self + other + + def _eval_transpose(self): + # Flip all the individual matrices + matrices = [transpose(matrix) for matrix in self.blocks] + # Make a copy + M = Matrix(self.blockshape[0], self.blockshape[1], matrices) + # Transpose the block structure + M = M.transpose() + return BlockMatrix(M) + + def _eval_adjoint(self): + return BlockMatrix( + Matrix(self.blockshape[0], self.blockshape[1], self.blocks).adjoint() + ) + + def _eval_trace(self): + if self.rowblocksizes == self.colblocksizes: + blocks = [self.blocks[i, i] for i in range(self.blockshape[0])] + return Add(*[trace(block) for block in blocks]) + + def _eval_determinant(self): + if self.blockshape == (1, 1): + return det(self.blocks[0, 0]) + if self.blockshape == (2, 2): + [[A, B], + [C, D]] = self.blocks.tolist() + if ask(Q.invertible(A)): + return det(A)*det(D - C*A.I*B) + elif ask(Q.invertible(D)): + return det(D)*det(A - B*D.I*C) + return Determinant(self) + + def _eval_as_real_imag(self): + real_matrices = [re(matrix) for matrix in self.blocks] + real_matrices = Matrix(self.blockshape[0], self.blockshape[1], real_matrices) + + im_matrices = [im(matrix) for matrix in self.blocks] + im_matrices = Matrix(self.blockshape[0], self.blockshape[1], im_matrices) + + return (BlockMatrix(real_matrices), BlockMatrix(im_matrices)) + + def _eval_derivative(self, x): + return BlockMatrix(self.blocks.diff(x)) + + def transpose(self): + """Return transpose of matrix. + + Examples + ======== + + >>> from sympy import MatrixSymbol, BlockMatrix, ZeroMatrix + >>> from sympy.abc import m, n + >>> X = MatrixSymbol('X', n, n) + >>> Y = MatrixSymbol('Y', m, m) + >>> Z = MatrixSymbol('Z', n, m) + >>> B = BlockMatrix([[X, Z], [ZeroMatrix(m,n), Y]]) + >>> B.transpose() + Matrix([ + [X.T, 0], + [Z.T, Y.T]]) + >>> _.transpose() + Matrix([ + [X, Z], + [0, Y]]) + """ + return self._eval_transpose() + + def schur(self, mat = 'A', generalized = False): + """Return the Schur Complement of the 2x2 BlockMatrix + + Parameters + ========== + + mat : String, optional + The matrix with respect to which the + Schur Complement is calculated. 'A' is + used by default + + generalized : bool, optional + If True, returns the generalized Schur + Component which uses Moore-Penrose Inverse + + Examples + ======== + + >>> from sympy import symbols, MatrixSymbol, BlockMatrix + >>> m, n = symbols('m n') + >>> A = MatrixSymbol('A', n, n) + >>> B = MatrixSymbol('B', n, m) + >>> C = MatrixSymbol('C', m, n) + >>> D = MatrixSymbol('D', m, m) + >>> X = BlockMatrix([[A, B], [C, D]]) + + The default Schur Complement is evaluated with "A" + + >>> X.schur() + -C*A**(-1)*B + D + >>> X.schur('D') + A - B*D**(-1)*C + + Schur complement with non-invertible matrices is not + defined. Instead, the generalized Schur complement can + be calculated which uses the Moore-Penrose Inverse. To + achieve this, `generalized` must be set to `True` + + >>> X.schur('B', generalized=True) + C - D*(B.T*B)**(-1)*B.T*A + >>> X.schur('C', generalized=True) + -A*(C.T*C)**(-1)*C.T*D + B + + Returns + ======= + + M : Matrix + The Schur Complement Matrix + + Raises + ====== + + ShapeError + If the block matrix is not a 2x2 matrix + + NonInvertibleMatrixError + If given matrix is non-invertible + + References + ========== + + .. [1] Wikipedia Article on Schur Component : https://en.wikipedia.org/wiki/Schur_complement + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.pinv + """ + + if self.blockshape == (2, 2): + [[A, B], + [C, D]] = self.blocks.tolist() + d={'A' : A, 'B' : B, 'C' : C, 'D' : D} + try: + inv = (d[mat].T*d[mat]).inv()*d[mat].T if generalized else d[mat].inv() + if mat == 'A': + return D - C * inv * B + elif mat == 'B': + return C - D * inv * A + elif mat == 'C': + return B - A * inv * D + elif mat == 'D': + return A - B * inv * C + #For matrices where no sub-matrix is square + return self + except NonInvertibleMatrixError: + raise NonInvertibleMatrixError('The given matrix is not invertible. Please set generalized=True \ + to compute the generalized Schur Complement which uses Moore-Penrose Inverse') + else: + raise ShapeError('Schur Complement can only be calculated for 2x2 block matrices') + + def LDUdecomposition(self): + """Returns the Block LDU decomposition of + a 2x2 Block Matrix + + Returns + ======= + + (L, D, U) : Matrices + L : Lower Diagonal Matrix + D : Diagonal Matrix + U : Upper Diagonal Matrix + + Examples + ======== + + >>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse + >>> m, n = symbols('m n') + >>> A = MatrixSymbol('A', n, n) + >>> B = MatrixSymbol('B', n, m) + >>> C = MatrixSymbol('C', m, n) + >>> D = MatrixSymbol('D', m, m) + >>> X = BlockMatrix([[A, B], [C, D]]) + >>> L, D, U = X.LDUdecomposition() + >>> block_collapse(L*D*U) + Matrix([ + [A, B], + [C, D]]) + + Raises + ====== + + ShapeError + If the block matrix is not a 2x2 matrix + + NonInvertibleMatrixError + If the matrix "A" is non-invertible + + See Also + ======== + sympy.matrices.expressions.blockmatrix.BlockMatrix.UDLdecomposition + sympy.matrices.expressions.blockmatrix.BlockMatrix.LUdecomposition + """ + if self.blockshape == (2,2): + [[A, B], + [C, D]] = self.blocks.tolist() + try: + AI = A.I + except NonInvertibleMatrixError: + raise NonInvertibleMatrixError('Block LDU decomposition cannot be calculated when\ + "A" is singular') + Ip = Identity(B.shape[0]) + Iq = Identity(B.shape[1]) + Z = ZeroMatrix(*B.shape) + L = BlockMatrix([[Ip, Z], [C*AI, Iq]]) + D = BlockDiagMatrix(A, self.schur()) + U = BlockMatrix([[Ip, AI*B],[Z.T, Iq]]) + return L, D, U + else: + raise ShapeError("Block LDU decomposition is supported only for 2x2 block matrices") + + def UDLdecomposition(self): + """Returns the Block UDL decomposition of + a 2x2 Block Matrix + + Returns + ======= + + (U, D, L) : Matrices + U : Upper Diagonal Matrix + D : Diagonal Matrix + L : Lower Diagonal Matrix + + Examples + ======== + + >>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse + >>> m, n = symbols('m n') + >>> A = MatrixSymbol('A', n, n) + >>> B = MatrixSymbol('B', n, m) + >>> C = MatrixSymbol('C', m, n) + >>> D = MatrixSymbol('D', m, m) + >>> X = BlockMatrix([[A, B], [C, D]]) + >>> U, D, L = X.UDLdecomposition() + >>> block_collapse(U*D*L) + Matrix([ + [A, B], + [C, D]]) + + Raises + ====== + + ShapeError + If the block matrix is not a 2x2 matrix + + NonInvertibleMatrixError + If the matrix "D" is non-invertible + + See Also + ======== + sympy.matrices.expressions.blockmatrix.BlockMatrix.LDUdecomposition + sympy.matrices.expressions.blockmatrix.BlockMatrix.LUdecomposition + """ + if self.blockshape == (2,2): + [[A, B], + [C, D]] = self.blocks.tolist() + try: + DI = D.I + except NonInvertibleMatrixError: + raise NonInvertibleMatrixError('Block UDL decomposition cannot be calculated when\ + "D" is singular') + Ip = Identity(A.shape[0]) + Iq = Identity(B.shape[1]) + Z = ZeroMatrix(*B.shape) + U = BlockMatrix([[Ip, B*DI], [Z.T, Iq]]) + D = BlockDiagMatrix(self.schur('D'), D) + L = BlockMatrix([[Ip, Z],[DI*C, Iq]]) + return U, D, L + else: + raise ShapeError("Block UDL decomposition is supported only for 2x2 block matrices") + + def LUdecomposition(self): + """Returns the Block LU decomposition of + a 2x2 Block Matrix + + Returns + ======= + + (L, U) : Matrices + L : Lower Diagonal Matrix + U : Upper Diagonal Matrix + + Examples + ======== + + >>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse + >>> m, n = symbols('m n') + >>> A = MatrixSymbol('A', n, n) + >>> B = MatrixSymbol('B', n, m) + >>> C = MatrixSymbol('C', m, n) + >>> D = MatrixSymbol('D', m, m) + >>> X = BlockMatrix([[A, B], [C, D]]) + >>> L, U = X.LUdecomposition() + >>> block_collapse(L*U) + Matrix([ + [A, B], + [C, D]]) + + Raises + ====== + + ShapeError + If the block matrix is not a 2x2 matrix + + NonInvertibleMatrixError + If the matrix "A" is non-invertible + + See Also + ======== + sympy.matrices.expressions.blockmatrix.BlockMatrix.UDLdecomposition + sympy.matrices.expressions.blockmatrix.BlockMatrix.LDUdecomposition + """ + if self.blockshape == (2,2): + [[A, B], + [C, D]] = self.blocks.tolist() + try: + A = A**S.Half + AI = A.I + except NonInvertibleMatrixError: + raise NonInvertibleMatrixError('Block LU decomposition cannot be calculated when\ + "A" is singular') + Z = ZeroMatrix(*B.shape) + Q = self.schur()**S.Half + L = BlockMatrix([[A, Z], [C*AI, Q]]) + U = BlockMatrix([[A, AI*B],[Z.T, Q]]) + return L, U + else: + raise ShapeError("Block LU decomposition is supported only for 2x2 block matrices") + + def _entry(self, i, j, **kwargs): + # Find row entry + orig_i, orig_j = i, j + for row_block, numrows in enumerate(self.rowblocksizes): + cmp = i < numrows + if cmp == True: + break + elif cmp == False: + i -= numrows + elif row_block < self.blockshape[0] - 1: + # Can't tell which block and it's not the last one, return unevaluated + return MatrixElement(self, orig_i, orig_j) + for col_block, numcols in enumerate(self.colblocksizes): + cmp = j < numcols + if cmp == True: + break + elif cmp == False: + j -= numcols + elif col_block < self.blockshape[1] - 1: + return MatrixElement(self, orig_i, orig_j) + return self.blocks[row_block, col_block][i, j] + + @property + def is_Identity(self): + if self.blockshape[0] != self.blockshape[1]: + return False + for i in range(self.blockshape[0]): + for j in range(self.blockshape[1]): + if i==j and not self.blocks[i, j].is_Identity: + return False + if i!=j and not self.blocks[i, j].is_ZeroMatrix: + return False + return True + + @property + def is_structurally_symmetric(self): + return self.rowblocksizes == self.colblocksizes + + def equals(self, other): + if self == other: + return True + if (isinstance(other, BlockMatrix) and self.blocks == other.blocks): + return True + return super().equals(other) + + +class BlockDiagMatrix(BlockMatrix): + """A sparse matrix with block matrices along its diagonals + + Examples + ======== + + >>> from sympy import MatrixSymbol, BlockDiagMatrix, symbols + >>> n, m, l = symbols('n m l') + >>> X = MatrixSymbol('X', n, n) + >>> Y = MatrixSymbol('Y', m, m) + >>> BlockDiagMatrix(X, Y) + Matrix([ + [X, 0], + [0, Y]]) + + Notes + ===== + + If you want to get the individual diagonal blocks, use + :meth:`get_diag_blocks`. + + See Also + ======== + + sympy.matrices.dense.diag + """ + def __new__(cls, *mats): + return Basic.__new__(BlockDiagMatrix, *[_sympify(m) for m in mats]) + + @property + def diag(self): + return self.args + + @property + def blocks(self): + from sympy.matrices.immutable import ImmutableDenseMatrix + mats = self.args + data = [[mats[i] if i == j else ZeroMatrix(mats[i].rows, mats[j].cols) + for j in range(len(mats))] + for i in range(len(mats))] + return ImmutableDenseMatrix(data, evaluate=False) + + @property + def shape(self): + return (sum(block.rows for block in self.args), + sum(block.cols for block in self.args)) + + @property + def blockshape(self): + n = len(self.args) + return (n, n) + + @property + def rowblocksizes(self): + return [block.rows for block in self.args] + + @property + def colblocksizes(self): + return [block.cols for block in self.args] + + def _all_square_blocks(self): + """Returns true if all blocks are square""" + return all(mat.is_square for mat in self.args) + + def _eval_determinant(self): + if self._all_square_blocks(): + return Mul(*[det(mat) for mat in self.args]) + # At least one block is non-square. Since the entire matrix must be square we know there must + # be at least two blocks in this matrix, in which case the entire matrix is necessarily rank-deficient + return S.Zero + + def _eval_inverse(self, expand='ignored'): + if self._all_square_blocks(): + return BlockDiagMatrix(*[mat.inverse() for mat in self.args]) + # See comment in _eval_determinant() + raise NonInvertibleMatrixError('Matrix det == 0; not invertible.') + + def _eval_transpose(self): + return BlockDiagMatrix(*[mat.transpose() for mat in self.args]) + + def _blockmul(self, other): + if (isinstance(other, BlockDiagMatrix) and + self.colblocksizes == other.rowblocksizes): + return BlockDiagMatrix(*[a*b for a, b in zip(self.args, other.args)]) + else: + return BlockMatrix._blockmul(self, other) + + def _blockadd(self, other): + if (isinstance(other, BlockDiagMatrix) and + self.blockshape == other.blockshape and + self.rowblocksizes == other.rowblocksizes and + self.colblocksizes == other.colblocksizes): + return BlockDiagMatrix(*[a + b for a, b in zip(self.args, other.args)]) + else: + return BlockMatrix._blockadd(self, other) + + def get_diag_blocks(self): + """Return the list of diagonal blocks of the matrix. + + Examples + ======== + + >>> from sympy import BlockDiagMatrix, Matrix + + >>> A = Matrix([[1, 2], [3, 4]]) + >>> B = Matrix([[5, 6], [7, 8]]) + >>> M = BlockDiagMatrix(A, B) + + How to get diagonal blocks from the block diagonal matrix: + + >>> diag_blocks = M.get_diag_blocks() + >>> diag_blocks[0] + Matrix([ + [1, 2], + [3, 4]]) + >>> diag_blocks[1] + Matrix([ + [5, 6], + [7, 8]]) + """ + return self.args + + +def block_collapse(expr): + """Evaluates a block matrix expression + + >>> from sympy import MatrixSymbol, BlockMatrix, symbols, Identity, ZeroMatrix, block_collapse + >>> n,m,l = symbols('n m l') + >>> X = MatrixSymbol('X', n, n) + >>> Y = MatrixSymbol('Y', m, m) + >>> Z = MatrixSymbol('Z', n, m) + >>> B = BlockMatrix([[X, Z], [ZeroMatrix(m, n), Y]]) + >>> print(B) + Matrix([ + [X, Z], + [0, Y]]) + + >>> C = BlockMatrix([[Identity(n), Z]]) + >>> print(C) + Matrix([[I, Z]]) + + >>> print(block_collapse(C*B)) + Matrix([[X, Z + Z*Y]]) + """ + from sympy.strategies.util import expr_fns + + hasbm = lambda expr: isinstance(expr, MatrixExpr) and expr.has(BlockMatrix) + + conditioned_rl = condition( + hasbm, + typed( + {MatAdd: do_one(bc_matadd, bc_block_plus_ident), + MatMul: do_one(bc_matmul, bc_dist), + MatPow: bc_matmul, + Transpose: bc_transpose, + Inverse: bc_inverse, + BlockMatrix: do_one(bc_unpack, deblock)} + ) + ) + + rule = exhaust( + bottom_up( + exhaust(conditioned_rl), + fns=expr_fns + ) + ) + + result = rule(expr) + doit = getattr(result, 'doit', None) + if doit is not None: + return doit() + else: + return result + +def bc_unpack(expr): + if expr.blockshape == (1, 1): + return expr.blocks[0, 0] + return expr + +def bc_matadd(expr): + args = sift(expr.args, lambda M: isinstance(M, BlockMatrix)) + blocks = args[True] + if not blocks: + return expr + + nonblocks = args[False] + block = blocks[0] + for b in blocks[1:]: + block = block._blockadd(b) + if nonblocks: + return MatAdd(*nonblocks) + block + else: + return block + +def bc_block_plus_ident(expr): + idents = [arg for arg in expr.args if arg.is_Identity] + if not idents: + return expr + + blocks = [arg for arg in expr.args if isinstance(arg, BlockMatrix)] + if (blocks and all(b.structurally_equal(blocks[0]) for b in blocks) + and blocks[0].is_structurally_symmetric): + block_id = BlockDiagMatrix(*[Identity(k) + for k in blocks[0].rowblocksizes]) + rest = [arg for arg in expr.args if not arg.is_Identity and not isinstance(arg, BlockMatrix)] + return MatAdd(block_id * len(idents), *blocks, *rest).doit() + + return expr + +def bc_dist(expr): + """ Turn a*[X, Y] into [a*X, a*Y] """ + factor, mat = expr.as_coeff_mmul() + if factor == 1: + return expr + + unpacked = unpack(mat) + + if isinstance(unpacked, BlockDiagMatrix): + B = unpacked.diag + new_B = [factor * mat for mat in B] + return BlockDiagMatrix(*new_B) + elif isinstance(unpacked, BlockMatrix): + B = unpacked.blocks + new_B = [ + [factor * B[i, j] for j in range(B.cols)] for i in range(B.rows)] + return BlockMatrix(new_B) + return expr + + +def bc_matmul(expr): + if isinstance(expr, MatPow): + if expr.args[1].is_Integer and expr.args[1] > 0: + factor, matrices = 1, [expr.args[0]]*expr.args[1] + else: + return expr + else: + factor, matrices = expr.as_coeff_matrices() + + i = 0 + while (i+1 < len(matrices)): + A, B = matrices[i:i+2] + if isinstance(A, BlockMatrix) and isinstance(B, BlockMatrix): + matrices[i] = A._blockmul(B) + matrices.pop(i+1) + elif isinstance(A, BlockMatrix): + matrices[i] = A._blockmul(BlockMatrix([[B]])) + matrices.pop(i+1) + elif isinstance(B, BlockMatrix): + matrices[i] = BlockMatrix([[A]])._blockmul(B) + matrices.pop(i+1) + else: + i+=1 + return MatMul(factor, *matrices).doit() + +def bc_transpose(expr): + collapse = block_collapse(expr.arg) + return collapse._eval_transpose() + + +def bc_inverse(expr): + if isinstance(expr.arg, BlockDiagMatrix): + return expr.inverse() + + expr2 = blockinverse_1x1(expr) + if expr != expr2: + return expr2 + return blockinverse_2x2(Inverse(reblock_2x2(expr.arg))) + +def blockinverse_1x1(expr): + if isinstance(expr.arg, BlockMatrix) and expr.arg.blockshape == (1, 1): + mat = Matrix([[expr.arg.blocks[0].inverse()]]) + return BlockMatrix(mat) + return expr + + +def blockinverse_2x2(expr): + if isinstance(expr.arg, BlockMatrix) and expr.arg.blockshape == (2, 2): + # See: Inverses of 2x2 Block Matrices, Tzon-Tzer Lu and Sheng-Hua Shiou + [[A, B], + [C, D]] = expr.arg.blocks.tolist() + + formula = _choose_2x2_inversion_formula(A, B, C, D) + if formula != None: + MI = expr.arg.schur(formula).I + if formula == 'A': + AI = A.I + return BlockMatrix([[AI + AI * B * MI * C * AI, -AI * B * MI], [-MI * C * AI, MI]]) + if formula == 'B': + BI = B.I + return BlockMatrix([[-MI * D * BI, MI], [BI + BI * A * MI * D * BI, -BI * A * MI]]) + if formula == 'C': + CI = C.I + return BlockMatrix([[-CI * D * MI, CI + CI * D * MI * A * CI], [MI, -MI * A * CI]]) + if formula == 'D': + DI = D.I + return BlockMatrix([[MI, -MI * B * DI], [-DI * C * MI, DI + DI * C * MI * B * DI]]) + + return expr + + +def _choose_2x2_inversion_formula(A, B, C, D): + """ + Assuming [[A, B], [C, D]] would form a valid square block matrix, find + which of the classical 2x2 block matrix inversion formulas would be + best suited. + + Returns 'A', 'B', 'C', 'D' to represent the algorithm involving inversion + of the given argument or None if the matrix cannot be inverted using + any of those formulas. + """ + # Try to find a known invertible matrix. Note that the Schur complement + # is currently not being considered for this + A_inv = ask(Q.invertible(A)) + if A_inv == True: + return 'A' + B_inv = ask(Q.invertible(B)) + if B_inv == True: + return 'B' + C_inv = ask(Q.invertible(C)) + if C_inv == True: + return 'C' + D_inv = ask(Q.invertible(D)) + if D_inv == True: + return 'D' + # Otherwise try to find a matrix that isn't known to be non-invertible + if A_inv != False: + return 'A' + if B_inv != False: + return 'B' + if C_inv != False: + return 'C' + if D_inv != False: + return 'D' + return None + + +def deblock(B): + """ Flatten a BlockMatrix of BlockMatrices """ + if not isinstance(B, BlockMatrix) or not B.blocks.has(BlockMatrix): + return B + wrap = lambda x: x if isinstance(x, BlockMatrix) else BlockMatrix([[x]]) + bb = B.blocks.applyfunc(wrap) # everything is a block + + try: + MM = Matrix(0, sum(bb[0, i].blocks.shape[1] for i in range(bb.shape[1])), []) + for row in range(0, bb.shape[0]): + M = Matrix(bb[row, 0].blocks) + for col in range(1, bb.shape[1]): + M = M.row_join(bb[row, col].blocks) + MM = MM.col_join(M) + + return BlockMatrix(MM) + except ShapeError: + return B + + +def reblock_2x2(expr): + """ + Reblock a BlockMatrix so that it has 2x2 blocks of block matrices. If + possible in such a way that the matrix continues to be invertible using the + classical 2x2 block inversion formulas. + """ + if not isinstance(expr, BlockMatrix) or not all(d > 2 for d in expr.blockshape): + return expr + + BM = BlockMatrix # for brevity's sake + rowblocks, colblocks = expr.blockshape + blocks = expr.blocks + for i in range(1, rowblocks): + for j in range(1, colblocks): + # try to split rows at i and cols at j + A = bc_unpack(BM(blocks[:i, :j])) + B = bc_unpack(BM(blocks[:i, j:])) + C = bc_unpack(BM(blocks[i:, :j])) + D = bc_unpack(BM(blocks[i:, j:])) + + formula = _choose_2x2_inversion_formula(A, B, C, D) + if formula is not None: + return BlockMatrix([[A, B], [C, D]]) + + # else: nothing worked, just split upper left corner + return BM([[blocks[0, 0], BM(blocks[0, 1:])], + [BM(blocks[1:, 0]), BM(blocks[1:, 1:])]]) + + +def bounds(sizes): + """ Convert sequence of numbers into pairs of low-high pairs + + >>> from sympy.matrices.expressions.blockmatrix import bounds + >>> bounds((1, 10, 50)) + [(0, 1), (1, 11), (11, 61)] + """ + low = 0 + rv = [] + for size in sizes: + rv.append((low, low + size)) + low += size + return rv + +def blockcut(expr, rowsizes, colsizes): + """ Cut a matrix expression into Blocks + + >>> from sympy import ImmutableMatrix, blockcut + >>> M = ImmutableMatrix(4, 4, range(16)) + >>> B = blockcut(M, (1, 3), (1, 3)) + >>> type(B).__name__ + 'BlockMatrix' + >>> ImmutableMatrix(B.blocks[0, 1]) + Matrix([[1, 2, 3]]) + """ + + rowbounds = bounds(rowsizes) + colbounds = bounds(colsizes) + return BlockMatrix([[MatrixSlice(expr, rowbound, colbound) + for colbound in colbounds] + for rowbound in rowbounds]) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/companion.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/companion.py new file mode 100644 index 0000000000000000000000000000000000000000..6969c917f63806cb1f5417804e01ecc1350d1406 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/companion.py @@ -0,0 +1,56 @@ +from sympy.core.singleton import S +from sympy.core.sympify import _sympify +from sympy.polys.polytools import Poly + +from .matexpr import MatrixExpr + + +class CompanionMatrix(MatrixExpr): + """A symbolic companion matrix of a polynomial. + + Examples + ======== + + >>> from sympy import Poly, Symbol, symbols + >>> from sympy.matrices.expressions import CompanionMatrix + >>> x = Symbol('x') + >>> c0, c1, c2, c3, c4 = symbols('c0:5') + >>> p = Poly(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + x**5, x) + >>> CompanionMatrix(p) + CompanionMatrix(Poly(x**5 + c4*x**4 + c3*x**3 + c2*x**2 + c1*x + c0, + x, domain='ZZ[c0,c1,c2,c3,c4]')) + """ + def __new__(cls, poly): + poly = _sympify(poly) + if not isinstance(poly, Poly): + raise ValueError("{} must be a Poly instance.".format(poly)) + if not poly.is_monic: + raise ValueError("{} must be a monic polynomial.".format(poly)) + if not poly.is_univariate: + raise ValueError( + "{} must be a univariate polynomial.".format(poly)) + if not poly.degree() >= 1: + raise ValueError( + "{} must have degree not less than 1.".format(poly)) + + return super().__new__(cls, poly) + + + @property + def shape(self): + poly = self.args[0] + size = poly.degree() + return size, size + + + def _entry(self, i, j): + if j == self.cols - 1: + return -self.args[0].all_coeffs()[-1 - i] + elif i == j + 1: + return S.One + return S.Zero + + + def as_explicit(self): + from sympy.matrices.immutable import ImmutableDenseMatrix + return ImmutableDenseMatrix.companion(self.args[0]) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/determinant.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/determinant.py new file mode 100644 index 0000000000000000000000000000000000000000..b323b3f93a5a0404bf2205f39d25b931d173b6d9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/determinant.py @@ -0,0 +1,148 @@ +from sympy.core.basic import Basic +from sympy.core.expr import Expr +from sympy.core.singleton import S +from sympy.core.sympify import sympify +from sympy.matrices.exceptions import NonSquareMatrixError +from sympy.matrices.matrixbase import MatrixBase + + +class Determinant(Expr): + """Matrix Determinant + + Represents the determinant of a matrix expression. + + Examples + ======== + + >>> from sympy import MatrixSymbol, Determinant, eye + >>> A = MatrixSymbol('A', 3, 3) + >>> Determinant(A) + Determinant(A) + >>> Determinant(eye(3)).doit() + 1 + """ + is_commutative = True + + def __new__(cls, mat): + mat = sympify(mat) + if not mat.is_Matrix: + raise TypeError("Input to Determinant, %s, not a matrix" % str(mat)) + + if mat.is_square is False: + raise NonSquareMatrixError("Det of a non-square matrix") + + return Basic.__new__(cls, mat) + + @property + def arg(self): + return self.args[0] + + @property + def kind(self): + return self.arg.kind.element_kind + + def doit(self, **hints): + arg = self.arg + if hints.get('deep', True): + arg = arg.doit(**hints) + + result = arg._eval_determinant() + if result is not None: + return result + + return self + + +def det(matexpr): + """ Matrix Determinant + + Examples + ======== + + >>> from sympy import MatrixSymbol, det, eye + >>> A = MatrixSymbol('A', 3, 3) + >>> det(A) + Determinant(A) + >>> det(eye(3)) + 1 + """ + + return Determinant(matexpr).doit() + +class Permanent(Expr): + """Matrix Permanent + + Represents the permanent of a matrix expression. + + Examples + ======== + + >>> from sympy import MatrixSymbol, Permanent, ones + >>> A = MatrixSymbol('A', 3, 3) + >>> Permanent(A) + Permanent(A) + >>> Permanent(ones(3, 3)).doit() + 6 + """ + + def __new__(cls, mat): + mat = sympify(mat) + if not mat.is_Matrix: + raise TypeError("Input to Permanent, %s, not a matrix" % str(mat)) + + return Basic.__new__(cls, mat) + + @property + def arg(self): + return self.args[0] + + def doit(self, expand=False, **hints): + if isinstance(self.arg, MatrixBase): + return self.arg.per() + else: + return self + +def per(matexpr): + """ Matrix Permanent + + Examples + ======== + + >>> from sympy import MatrixSymbol, Matrix, per, ones + >>> A = MatrixSymbol('A', 3, 3) + >>> per(A) + Permanent(A) + >>> per(ones(5, 5)) + 120 + >>> M = Matrix([1, 2, 5]) + >>> per(M) + 8 + """ + + return Permanent(matexpr).doit() + +from sympy.assumptions.ask import ask, Q +from sympy.assumptions.refine import handlers_dict + + +def refine_Determinant(expr, assumptions): + """ + >>> from sympy import MatrixSymbol, Q, assuming, refine, det + >>> X = MatrixSymbol('X', 2, 2) + >>> det(X) + Determinant(X) + >>> with assuming(Q.orthogonal(X)): + ... print(refine(det(X))) + 1 + """ + if ask(Q.orthogonal(expr.arg), assumptions): + return S.One + elif ask(Q.singular(expr.arg), assumptions): + return S.Zero + elif ask(Q.unit_triangular(expr.arg), assumptions): + return S.One + + return expr + + +handlers_dict['Determinant'] = refine_Determinant diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/diagonal.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/diagonal.py new file mode 100644 index 0000000000000000000000000000000000000000..ba8a0216588143e3e251dab84c25f038fad550a4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/diagonal.py @@ -0,0 +1,220 @@ +from sympy.core.sympify import _sympify + +from sympy.matrices.expressions import MatrixExpr +from sympy.core import S, Eq, Ge +from sympy.core.mul import Mul +from sympy.functions.special.tensor_functions import KroneckerDelta + + +class DiagonalMatrix(MatrixExpr): + """DiagonalMatrix(M) will create a matrix expression that + behaves as though all off-diagonal elements, + `M[i, j]` where `i != j`, are zero. + + Examples + ======== + + >>> from sympy import MatrixSymbol, DiagonalMatrix, Symbol + >>> n = Symbol('n', integer=True) + >>> m = Symbol('m', integer=True) + >>> D = DiagonalMatrix(MatrixSymbol('x', 2, 3)) + >>> D[1, 2] + 0 + >>> D[1, 1] + x[1, 1] + + The length of the diagonal -- the lesser of the two dimensions of `M` -- + is accessed through the `diagonal_length` property: + + >>> D.diagonal_length + 2 + >>> DiagonalMatrix(MatrixSymbol('x', n + 1, n)).diagonal_length + n + + When one of the dimensions is symbolic the other will be treated as + though it is smaller: + + >>> tall = DiagonalMatrix(MatrixSymbol('x', n, 3)) + >>> tall.diagonal_length + 3 + >>> tall[10, 1] + 0 + + When the size of the diagonal is not known, a value of None will + be returned: + + >>> DiagonalMatrix(MatrixSymbol('x', n, m)).diagonal_length is None + True + + """ + arg = property(lambda self: self.args[0]) + + shape = property(lambda self: self.arg.shape) # type:ignore + + @property + def diagonal_length(self): + r, c = self.shape + if r.is_Integer and c.is_Integer: + m = min(r, c) + elif r.is_Integer and not c.is_Integer: + m = r + elif c.is_Integer and not r.is_Integer: + m = c + elif r == c: + m = r + else: + try: + m = min(r, c) + except TypeError: + m = None + return m + + def _entry(self, i, j, **kwargs): + if self.diagonal_length is not None: + if Ge(i, self.diagonal_length) is S.true: + return S.Zero + elif Ge(j, self.diagonal_length) is S.true: + return S.Zero + eq = Eq(i, j) + if eq is S.true: + return self.arg[i, i] + elif eq is S.false: + return S.Zero + return self.arg[i, j]*KroneckerDelta(i, j) + + +class DiagonalOf(MatrixExpr): + """DiagonalOf(M) will create a matrix expression that + is equivalent to the diagonal of `M`, represented as + a single column matrix. + + Examples + ======== + + >>> from sympy import MatrixSymbol, DiagonalOf, Symbol + >>> n = Symbol('n', integer=True) + >>> m = Symbol('m', integer=True) + >>> x = MatrixSymbol('x', 2, 3) + >>> diag = DiagonalOf(x) + >>> diag.shape + (2, 1) + + The diagonal can be addressed like a matrix or vector and will + return the corresponding element of the original matrix: + + >>> diag[1, 0] == diag[1] == x[1, 1] + True + + The length of the diagonal -- the lesser of the two dimensions of `M` -- + is accessed through the `diagonal_length` property: + + >>> diag.diagonal_length + 2 + >>> DiagonalOf(MatrixSymbol('x', n + 1, n)).diagonal_length + n + + When only one of the dimensions is symbolic the other will be + treated as though it is smaller: + + >>> dtall = DiagonalOf(MatrixSymbol('x', n, 3)) + >>> dtall.diagonal_length + 3 + + When the size of the diagonal is not known, a value of None will + be returned: + + >>> DiagonalOf(MatrixSymbol('x', n, m)).diagonal_length is None + True + + """ + arg = property(lambda self: self.args[0]) + @property + def shape(self): + r, c = self.arg.shape + if r.is_Integer and c.is_Integer: + m = min(r, c) + elif r.is_Integer and not c.is_Integer: + m = r + elif c.is_Integer and not r.is_Integer: + m = c + elif r == c: + m = r + else: + try: + m = min(r, c) + except TypeError: + m = None + return m, S.One + + @property + def diagonal_length(self): + return self.shape[0] + + def _entry(self, i, j, **kwargs): + return self.arg._entry(i, i, **kwargs) + + +class DiagMatrix(MatrixExpr): + """ + Turn a vector into a diagonal matrix. + """ + def __new__(cls, vector): + vector = _sympify(vector) + obj = MatrixExpr.__new__(cls, vector) + shape = vector.shape + dim = shape[1] if shape[0] == 1 else shape[0] + if vector.shape[0] != 1: + obj._iscolumn = True + else: + obj._iscolumn = False + obj._shape = (dim, dim) + obj._vector = vector + return obj + + @property + def shape(self): + return self._shape + + def _entry(self, i, j, **kwargs): + if self._iscolumn: + result = self._vector._entry(i, 0, **kwargs) + else: + result = self._vector._entry(0, j, **kwargs) + if i != j: + result *= KroneckerDelta(i, j) + return result + + def _eval_transpose(self): + return self + + def as_explicit(self): + from sympy.matrices.dense import diag + return diag(*list(self._vector.as_explicit())) + + def doit(self, **hints): + from sympy.assumptions import ask, Q + from sympy.matrices.expressions.matmul import MatMul + from sympy.matrices.expressions.transpose import Transpose + from sympy.matrices.dense import eye + from sympy.matrices.matrixbase import MatrixBase + vector = self._vector + # This accounts for shape (1, 1) and identity matrices, among others: + if ask(Q.diagonal(vector)): + return vector + if isinstance(vector, MatrixBase): + ret = eye(max(vector.shape)) + for i in range(ret.shape[0]): + ret[i, i] = vector[i] + return type(vector)(ret) + if vector.is_MatMul: + matrices = [arg for arg in vector.args if arg.is_Matrix] + scalars = [arg for arg in vector.args if arg not in matrices] + if scalars: + return Mul.fromiter(scalars)*DiagMatrix(MatMul.fromiter(matrices).doit()).doit() + if isinstance(vector, Transpose): + vector = vector.arg + return DiagMatrix(vector) + + +def diagonalize_vector(vector): + return DiagMatrix(vector).doit() diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/dotproduct.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/dotproduct.py new file mode 100644 index 0000000000000000000000000000000000000000..3a413f8c79a221505f0c082d7f19f78597a2befc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/dotproduct.py @@ -0,0 +1,55 @@ +from sympy.core import Basic, Expr +from sympy.core.sympify import _sympify +from sympy.matrices.expressions.transpose import transpose + + +class DotProduct(Expr): + """ + Dot product of vector matrices + + The input should be two 1 x n or n x 1 matrices. The output represents the + scalar dotproduct. + + This is similar to using MatrixElement and MatMul, except DotProduct does + not require that one vector to be a row vector and the other vector to be + a column vector. + + >>> from sympy import MatrixSymbol, DotProduct + >>> A = MatrixSymbol('A', 1, 3) + >>> B = MatrixSymbol('B', 1, 3) + >>> DotProduct(A, B) + DotProduct(A, B) + >>> DotProduct(A, B).doit() + A[0, 0]*B[0, 0] + A[0, 1]*B[0, 1] + A[0, 2]*B[0, 2] + """ + + def __new__(cls, arg1, arg2): + arg1, arg2 = _sympify((arg1, arg2)) + + if not arg1.is_Matrix: + raise TypeError("Argument 1 of DotProduct is not a matrix") + if not arg2.is_Matrix: + raise TypeError("Argument 2 of DotProduct is not a matrix") + if not (1 in arg1.shape): + raise TypeError("Argument 1 of DotProduct is not a vector") + if not (1 in arg2.shape): + raise TypeError("Argument 2 of DotProduct is not a vector") + + if set(arg1.shape) != set(arg2.shape): + raise TypeError("DotProduct arguments are not the same length") + + return Basic.__new__(cls, arg1, arg2) + + def doit(self, expand=False, **hints): + if self.args[0].shape == self.args[1].shape: + if self.args[0].shape[0] == 1: + mul = self.args[0]*transpose(self.args[1]) + else: + mul = transpose(self.args[0])*self.args[1] + else: + if self.args[0].shape[0] == 1: + mul = self.args[0]*self.args[1] + else: + mul = transpose(self.args[0])*transpose(self.args[1]) + + return mul[0] diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/factorizations.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/factorizations.py new file mode 100644 index 0000000000000000000000000000000000000000..aff2bb81ecff99d8e733f282ac2dd187d76ce895 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/factorizations.py @@ -0,0 +1,62 @@ +from sympy.matrices.expressions import MatrixExpr +from sympy.assumptions.ask import Q + +class Factorization(MatrixExpr): + arg = property(lambda self: self.args[0]) + shape = property(lambda self: self.arg.shape) # type: ignore + +class LofLU(Factorization): + @property + def predicates(self): + return (Q.lower_triangular,) +class UofLU(Factorization): + @property + def predicates(self): + return (Q.upper_triangular,) + +class LofCholesky(LofLU): pass +class UofCholesky(UofLU): pass + +class QofQR(Factorization): + @property + def predicates(self): + return (Q.orthogonal,) +class RofQR(Factorization): + @property + def predicates(self): + return (Q.upper_triangular,) + +class EigenVectors(Factorization): + @property + def predicates(self): + return (Q.orthogonal,) +class EigenValues(Factorization): + @property + def predicates(self): + return (Q.diagonal,) + +class UofSVD(Factorization): + @property + def predicates(self): + return (Q.orthogonal,) +class SofSVD(Factorization): + @property + def predicates(self): + return (Q.diagonal,) +class VofSVD(Factorization): + @property + def predicates(self): + return (Q.orthogonal,) + + +def lu(expr): + return LofLU(expr), UofLU(expr) + +def qr(expr): + return QofQR(expr), RofQR(expr) + +def eig(expr): + return EigenValues(expr), EigenVectors(expr) + +def svd(expr): + return UofSVD(expr), SofSVD(expr), VofSVD(expr) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/fourier.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/fourier.py new file mode 100644 index 0000000000000000000000000000000000000000..5fa9222c2a9b218f42636267235d5dd44c25f8bb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/fourier.py @@ -0,0 +1,91 @@ +from sympy.core.sympify import _sympify +from sympy.matrices.expressions import MatrixExpr +from sympy.core.numbers import I +from sympy.core.singleton import S +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt + + +class DFT(MatrixExpr): + r""" + Returns a discrete Fourier transform matrix. The matrix is scaled + with :math:`\frac{1}{\sqrt{n}}` so that it is unitary. + + Parameters + ========== + + n : integer or Symbol + Size of the transform. + + Examples + ======== + + >>> from sympy.abc import n + >>> from sympy.matrices.expressions.fourier import DFT + >>> DFT(3) + DFT(3) + >>> DFT(3).as_explicit() + Matrix([ + [sqrt(3)/3, sqrt(3)/3, sqrt(3)/3], + [sqrt(3)/3, sqrt(3)*exp(-2*I*pi/3)/3, sqrt(3)*exp(2*I*pi/3)/3], + [sqrt(3)/3, sqrt(3)*exp(2*I*pi/3)/3, sqrt(3)*exp(-2*I*pi/3)/3]]) + >>> DFT(n).shape + (n, n) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/DFT_matrix + + """ + + def __new__(cls, n): + n = _sympify(n) + cls._check_dim(n) + + obj = super().__new__(cls, n) + return obj + + n = property(lambda self: self.args[0]) # type: ignore + shape = property(lambda self: (self.n, self.n)) # type: ignore + + def _entry(self, i, j, **kwargs): + w = exp(-2*S.Pi*I/self.n) + return w**(i*j) / sqrt(self.n) + + def _eval_inverse(self): + return IDFT(self.n) + + +class IDFT(DFT): + r""" + Returns an inverse discrete Fourier transform matrix. The matrix is scaled + with :math:`\frac{1}{\sqrt{n}}` so that it is unitary. + + Parameters + ========== + + n : integer or Symbol + Size of the transform + + Examples + ======== + + >>> from sympy.matrices.expressions.fourier import DFT, IDFT + >>> IDFT(3) + IDFT(3) + >>> IDFT(4)*DFT(4) + I + + See Also + ======== + + DFT + + """ + def _entry(self, i, j, **kwargs): + w = exp(-2*S.Pi*I/self.n) + return w**(-i*j) / sqrt(self.n) + + def _eval_inverse(self): + return DFT(self.n) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/funcmatrix.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/funcmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..91106edb489b73ac9dd6cb94adc508c0db75d3a5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/funcmatrix.py @@ -0,0 +1,118 @@ +from .matexpr import MatrixExpr +from sympy.core.function import FunctionClass, Lambda +from sympy.core.symbol import Dummy +from sympy.core.sympify import _sympify, sympify +from sympy.matrices import Matrix +from sympy.functions.elementary.complexes import re, im + + +class FunctionMatrix(MatrixExpr): + """Represents a matrix using a function (``Lambda``) which gives + outputs according to the coordinates of each matrix entries. + + Parameters + ========== + + rows : nonnegative integer. Can be symbolic. + + cols : nonnegative integer. Can be symbolic. + + lamda : Function, Lambda or str + If it is a SymPy ``Function`` or ``Lambda`` instance, + it should be able to accept two arguments which represents the + matrix coordinates. + + If it is a pure string containing Python ``lambda`` semantics, + it is interpreted by the SymPy parser and casted into a SymPy + ``Lambda`` instance. + + Examples + ======== + + Creating a ``FunctionMatrix`` from ``Lambda``: + + >>> from sympy import FunctionMatrix, symbols, Lambda, MatPow + >>> i, j, n, m = symbols('i,j,n,m') + >>> FunctionMatrix(n, m, Lambda((i, j), i + j)) + FunctionMatrix(n, m, Lambda((i, j), i + j)) + + Creating a ``FunctionMatrix`` from a SymPy function: + + >>> from sympy import KroneckerDelta + >>> X = FunctionMatrix(3, 3, KroneckerDelta) + >>> X.as_explicit() + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + + Creating a ``FunctionMatrix`` from a SymPy undefined function: + + >>> from sympy import Function + >>> f = Function('f') + >>> X = FunctionMatrix(3, 3, f) + >>> X.as_explicit() + Matrix([ + [f(0, 0), f(0, 1), f(0, 2)], + [f(1, 0), f(1, 1), f(1, 2)], + [f(2, 0), f(2, 1), f(2, 2)]]) + + Creating a ``FunctionMatrix`` from Python ``lambda``: + + >>> FunctionMatrix(n, m, 'lambda i, j: i + j') + FunctionMatrix(n, m, Lambda((i, j), i + j)) + + Example of lazy evaluation of matrix product: + + >>> Y = FunctionMatrix(1000, 1000, Lambda((i, j), i + j)) + >>> isinstance(Y*Y, MatPow) # this is an expression object + True + >>> (Y**2)[10,10] # So this is evaluated lazily + 342923500 + + Notes + ===== + + This class provides an alternative way to represent an extremely + dense matrix with entries in some form of a sequence, in a most + sparse way. + """ + def __new__(cls, rows, cols, lamda): + rows, cols = _sympify(rows), _sympify(cols) + cls._check_dim(rows) + cls._check_dim(cols) + + lamda = sympify(lamda) + if not isinstance(lamda, (FunctionClass, Lambda)): + raise ValueError( + "{} should be compatible with SymPy function classes." + .format(lamda)) + + if 2 not in lamda.nargs: + raise ValueError( + '{} should be able to accept 2 arguments.'.format(lamda)) + + if not isinstance(lamda, Lambda): + i, j = Dummy('i'), Dummy('j') + lamda = Lambda((i, j), lamda(i, j)) + + return super().__new__(cls, rows, cols, lamda) + + @property + def shape(self): + return self.args[0:2] + + @property + def lamda(self): + return self.args[2] + + def _entry(self, i, j, **kwargs): + return self.lamda(i, j) + + def _eval_trace(self): + from sympy.matrices.expressions.trace import Trace + from sympy.concrete.summations import Sum + return Trace(self).rewrite(Sum).doit() + + def _eval_as_real_imag(self): + return (re(Matrix(self)), im(Matrix(self))) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/hadamard.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/hadamard.py new file mode 100644 index 0000000000000000000000000000000000000000..38c9033ebea3a7bfc569223978dc6ef3890206cf --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/hadamard.py @@ -0,0 +1,464 @@ +from collections import Counter + +from sympy.core import Mul, sympify +from sympy.core.add import Add +from sympy.core.expr import ExprBuilder +from sympy.core.sorting import default_sort_key +from sympy.functions.elementary.exponential import log +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.matrices.expressions._shape import validate_matadd_integer as validate +from sympy.matrices.expressions.special import ZeroMatrix, OneMatrix +from sympy.strategies import ( + unpack, flatten, condition, exhaust, rm_id, sort +) +from sympy.utilities.exceptions import sympy_deprecation_warning + + +def hadamard_product(*matrices): + """ + Return the elementwise (aka Hadamard) product of matrices. + + Examples + ======== + + >>> from sympy import hadamard_product, MatrixSymbol + >>> A = MatrixSymbol('A', 2, 3) + >>> B = MatrixSymbol('B', 2, 3) + >>> hadamard_product(A) + A + >>> hadamard_product(A, B) + HadamardProduct(A, B) + >>> hadamard_product(A, B)[0, 1] + A[0, 1]*B[0, 1] + """ + if not matrices: + raise TypeError("Empty Hadamard product is undefined") + if len(matrices) == 1: + return matrices[0] + return HadamardProduct(*matrices).doit() + + +class HadamardProduct(MatrixExpr): + """ + Elementwise product of matrix expressions + + Examples + ======== + + Hadamard product for matrix symbols: + + >>> from sympy import hadamard_product, HadamardProduct, MatrixSymbol + >>> A = MatrixSymbol('A', 5, 5) + >>> B = MatrixSymbol('B', 5, 5) + >>> isinstance(hadamard_product(A, B), HadamardProduct) + True + + Notes + ===== + + This is a symbolic object that simply stores its argument without + evaluating it. To actually compute the product, use the function + ``hadamard_product()`` or ``HadamardProduct.doit`` + """ + is_HadamardProduct = True + + def __new__(cls, *args, evaluate=False, check=None): + args = list(map(sympify, args)) + if len(args) == 0: + # We currently don't have a way to support one-matrices of generic dimensions: + raise ValueError("HadamardProduct needs at least one argument") + + if not all(isinstance(arg, MatrixExpr) for arg in args): + raise TypeError("Mix of Matrix and Scalar symbols") + + if check is not None: + sympy_deprecation_warning( + "Passing check to HadamardProduct is deprecated and the check argument will be removed in a future version.", + deprecated_since_version="1.11", + active_deprecations_target='remove-check-argument-from-matrix-operations') + + if check is not False: + validate(*args) + + obj = super().__new__(cls, *args) + if evaluate: + obj = obj.doit(deep=False) + return obj + + @property + def shape(self): + return self.args[0].shape + + def _entry(self, i, j, **kwargs): + return Mul(*[arg._entry(i, j, **kwargs) for arg in self.args]) + + def _eval_transpose(self): + from sympy.matrices.expressions.transpose import transpose + return HadamardProduct(*list(map(transpose, self.args))) + + def doit(self, **hints): + expr = self.func(*(i.doit(**hints) for i in self.args)) + # Check for explicit matrices: + from sympy.matrices.matrixbase import MatrixBase + from sympy.matrices.immutable import ImmutableMatrix + + explicit = [i for i in expr.args if isinstance(i, MatrixBase)] + if explicit: + remainder = [i for i in expr.args if i not in explicit] + expl_mat = ImmutableMatrix([ + Mul.fromiter(i) for i in zip(*explicit) + ]).reshape(*self.shape) + expr = HadamardProduct(*([expl_mat] + remainder)) + + return canonicalize(expr) + + def _eval_derivative(self, x): + terms = [] + args = list(self.args) + for i in range(len(args)): + factors = args[:i] + [args[i].diff(x)] + args[i+1:] + terms.append(hadamard_product(*factors)) + return Add.fromiter(terms) + + def _eval_derivative_matrix_lines(self, x): + from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal + from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct + from sympy.matrices.expressions.matexpr import _make_matrix + + with_x_ind = [i for i, arg in enumerate(self.args) if arg.has(x)] + lines = [] + for ind in with_x_ind: + left_args = self.args[:ind] + right_args = self.args[ind+1:] + + d = self.args[ind]._eval_derivative_matrix_lines(x) + hadam = hadamard_product(*(right_args + left_args)) + diagonal = [(0, 2), (3, 4)] + diagonal = [e for j, e in enumerate(diagonal) if self.shape[j] != 1] + for i in d: + l1 = i._lines[i._first_line_index] + l2 = i._lines[i._second_line_index] + subexpr = ExprBuilder( + ArrayDiagonal, + [ + ExprBuilder( + ArrayTensorProduct, + [ + ExprBuilder(_make_matrix, [l1]), + hadam, + ExprBuilder(_make_matrix, [l2]), + ] + ), + *diagonal], + + ) + i._first_pointer_parent = subexpr.args[0].args[0].args + i._first_pointer_index = 0 + i._second_pointer_parent = subexpr.args[0].args[2].args + i._second_pointer_index = 0 + i._lines = [subexpr] + lines.append(i) + + return lines + + +# TODO Implement algorithm for rewriting Hadamard product as diagonal matrix +# if matmul identy matrix is multiplied. +def canonicalize(x): + """Canonicalize the Hadamard product ``x`` with mathematical properties. + + Examples + ======== + + >>> from sympy import MatrixSymbol, HadamardProduct + >>> from sympy import OneMatrix, ZeroMatrix + >>> from sympy.matrices.expressions.hadamard import canonicalize + >>> from sympy import init_printing + >>> init_printing(use_unicode=False) + + >>> A = MatrixSymbol('A', 2, 2) + >>> B = MatrixSymbol('B', 2, 2) + >>> C = MatrixSymbol('C', 2, 2) + + Hadamard product associativity: + + >>> X = HadamardProduct(A, HadamardProduct(B, C)) + >>> X + A.*(B.*C) + >>> canonicalize(X) + A.*B.*C + + Hadamard product commutativity: + + >>> X = HadamardProduct(A, B) + >>> Y = HadamardProduct(B, A) + >>> X + A.*B + >>> Y + B.*A + >>> canonicalize(X) + A.*B + >>> canonicalize(Y) + A.*B + + Hadamard product identity: + + >>> X = HadamardProduct(A, OneMatrix(2, 2)) + >>> X + A.*1 + >>> canonicalize(X) + A + + Absorbing element of Hadamard product: + + >>> X = HadamardProduct(A, ZeroMatrix(2, 2)) + >>> X + A.*0 + >>> canonicalize(X) + 0 + + Rewriting to Hadamard Power + + >>> X = HadamardProduct(A, A, A) + >>> X + A.*A.*A + >>> canonicalize(X) + .3 + A + + Notes + ===== + + As the Hadamard product is associative, nested products can be flattened. + + The Hadamard product is commutative so that factors can be sorted for + canonical form. + + A matrix of only ones is an identity for Hadamard product, + so every matrices of only ones can be removed. + + Any zero matrix will make the whole product a zero matrix. + + Duplicate elements can be collected and rewritten as HadamardPower + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hadamard_product_(matrices) + """ + # Associativity + rule = condition( + lambda x: isinstance(x, HadamardProduct), + flatten + ) + fun = exhaust(rule) + x = fun(x) + + # Identity + fun = condition( + lambda x: isinstance(x, HadamardProduct), + rm_id(lambda x: isinstance(x, OneMatrix)) + ) + x = fun(x) + + # Absorbing by Zero Matrix + def absorb(x): + if any(isinstance(c, ZeroMatrix) for c in x.args): + return ZeroMatrix(*x.shape) + else: + return x + fun = condition( + lambda x: isinstance(x, HadamardProduct), + absorb + ) + x = fun(x) + + # Rewriting with HadamardPower + if isinstance(x, HadamardProduct): + tally = Counter(x.args) + + new_arg = [] + for base, exp in tally.items(): + if exp == 1: + new_arg.append(base) + else: + new_arg.append(HadamardPower(base, exp)) + + x = HadamardProduct(*new_arg) + + # Commutativity + fun = condition( + lambda x: isinstance(x, HadamardProduct), + sort(default_sort_key) + ) + x = fun(x) + + # Unpacking + x = unpack(x) + return x + + +def hadamard_power(base, exp): + base = sympify(base) + exp = sympify(exp) + if exp == 1: + return base + if not base.is_Matrix: + return base**exp + if exp.is_Matrix: + raise ValueError("cannot raise expression to a matrix") + return HadamardPower(base, exp) + + +class HadamardPower(MatrixExpr): + r""" + Elementwise power of matrix expressions + + Parameters + ========== + + base : scalar or matrix + + exp : scalar or matrix + + Notes + ===== + + There are four definitions for the hadamard power which can be used. + Let's consider `A, B` as `(m, n)` matrices, and `a, b` as scalars. + + Matrix raised to a scalar exponent: + + .. math:: + A^{\circ b} = \begin{bmatrix} + A_{0, 0}^b & A_{0, 1}^b & \cdots & A_{0, n-1}^b \\ + A_{1, 0}^b & A_{1, 1}^b & \cdots & A_{1, n-1}^b \\ + \vdots & \vdots & \ddots & \vdots \\ + A_{m-1, 0}^b & A_{m-1, 1}^b & \cdots & A_{m-1, n-1}^b + \end{bmatrix} + + Scalar raised to a matrix exponent: + + .. math:: + a^{\circ B} = \begin{bmatrix} + a^{B_{0, 0}} & a^{B_{0, 1}} & \cdots & a^{B_{0, n-1}} \\ + a^{B_{1, 0}} & a^{B_{1, 1}} & \cdots & a^{B_{1, n-1}} \\ + \vdots & \vdots & \ddots & \vdots \\ + a^{B_{m-1, 0}} & a^{B_{m-1, 1}} & \cdots & a^{B_{m-1, n-1}} + \end{bmatrix} + + Matrix raised to a matrix exponent: + + .. math:: + A^{\circ B} = \begin{bmatrix} + A_{0, 0}^{B_{0, 0}} & A_{0, 1}^{B_{0, 1}} & + \cdots & A_{0, n-1}^{B_{0, n-1}} \\ + A_{1, 0}^{B_{1, 0}} & A_{1, 1}^{B_{1, 1}} & + \cdots & A_{1, n-1}^{B_{1, n-1}} \\ + \vdots & \vdots & + \ddots & \vdots \\ + A_{m-1, 0}^{B_{m-1, 0}} & A_{m-1, 1}^{B_{m-1, 1}} & + \cdots & A_{m-1, n-1}^{B_{m-1, n-1}} + \end{bmatrix} + + Scalar raised to a scalar exponent: + + .. math:: + a^{\circ b} = a^b + """ + + def __new__(cls, base, exp): + base = sympify(base) + exp = sympify(exp) + + if base.is_scalar and exp.is_scalar: + return base ** exp + + if isinstance(base, MatrixExpr) and isinstance(exp, MatrixExpr): + validate(base, exp) + + obj = super().__new__(cls, base, exp) + return obj + + @property + def base(self): + return self._args[0] + + @property + def exp(self): + return self._args[1] + + @property + def shape(self): + if self.base.is_Matrix: + return self.base.shape + return self.exp.shape + + def _entry(self, i, j, **kwargs): + base = self.base + exp = self.exp + + if base.is_Matrix: + a = base._entry(i, j, **kwargs) + elif base.is_scalar: + a = base + else: + raise ValueError( + 'The base {} must be a scalar or a matrix.'.format(base)) + + if exp.is_Matrix: + b = exp._entry(i, j, **kwargs) + elif exp.is_scalar: + b = exp + else: + raise ValueError( + 'The exponent {} must be a scalar or a matrix.'.format(exp)) + + return a ** b + + def _eval_transpose(self): + from sympy.matrices.expressions.transpose import transpose + return HadamardPower(transpose(self.base), self.exp) + + def _eval_derivative(self, x): + dexp = self.exp.diff(x) + logbase = self.base.applyfunc(log) + dlbase = logbase.diff(x) + return hadamard_product( + dexp*logbase + self.exp*dlbase, + self + ) + + def _eval_derivative_matrix_lines(self, x): + from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct + from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal + from sympy.matrices.expressions.matexpr import _make_matrix + + lr = self.base._eval_derivative_matrix_lines(x) + for i in lr: + diagonal = [(1, 2), (3, 4)] + diagonal = [e for j, e in enumerate(diagonal) if self.base.shape[j] != 1] + l1 = i._lines[i._first_line_index] + l2 = i._lines[i._second_line_index] + subexpr = ExprBuilder( + ArrayDiagonal, + [ + ExprBuilder( + ArrayTensorProduct, + [ + ExprBuilder(_make_matrix, [l1]), + self.exp*hadamard_power(self.base, self.exp-1), + ExprBuilder(_make_matrix, [l2]), + ] + ), + *diagonal], + validator=ArrayDiagonal._validate + ) + i._first_pointer_parent = subexpr.args[0].args[0].args + i._first_pointer_index = 0 + i._first_line_index = 0 + i._second_pointer_parent = subexpr.args[0].args[2].args + i._second_pointer_index = 0 + i._second_line_index = 0 + i._lines = [subexpr] + return lr diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/inverse.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/inverse.py new file mode 100644 index 0000000000000000000000000000000000000000..cfc3feccd7126a761f18f23599eed9413c86a9e5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/inverse.py @@ -0,0 +1,112 @@ +from sympy.core.sympify import _sympify +from sympy.core import S, Basic + +from sympy.matrices.exceptions import NonSquareMatrixError +from sympy.matrices.expressions.matpow import MatPow + + +class Inverse(MatPow): + """ + The multiplicative inverse of a matrix expression + + This is a symbolic object that simply stores its argument without + evaluating it. To actually compute the inverse, use the ``.inverse()`` + method of matrices. + + Examples + ======== + + >>> from sympy import MatrixSymbol, Inverse + >>> A = MatrixSymbol('A', 3, 3) + >>> B = MatrixSymbol('B', 3, 3) + >>> Inverse(A) + A**(-1) + >>> A.inverse() == Inverse(A) + True + >>> (A*B).inverse() + B**(-1)*A**(-1) + >>> Inverse(A*B) + (A*B)**(-1) + + """ + is_Inverse = True + exp = S.NegativeOne + + def __new__(cls, mat, exp=S.NegativeOne): + # exp is there to make it consistent with + # inverse.func(*inverse.args) == inverse + mat = _sympify(mat) + exp = _sympify(exp) + if not mat.is_Matrix: + raise TypeError("mat should be a matrix") + if mat.is_square is False: + raise NonSquareMatrixError("Inverse of non-square matrix %s" % mat) + return Basic.__new__(cls, mat, exp) + + @property + def arg(self): + return self.args[0] + + @property + def shape(self): + return self.arg.shape + + def _eval_inverse(self): + return self.arg + + def _eval_transpose(self): + return Inverse(self.arg.transpose()) + + def _eval_adjoint(self): + return Inverse(self.arg.adjoint()) + + def _eval_conjugate(self): + return Inverse(self.arg.conjugate()) + + def _eval_determinant(self): + from sympy.matrices.expressions.determinant import det + return 1/det(self.arg) + + def doit(self, **hints): + if 'inv_expand' in hints and hints['inv_expand'] == False: + return self + + arg = self.arg + if hints.get('deep', True): + arg = arg.doit(**hints) + + return arg.inverse() + + def _eval_derivative_matrix_lines(self, x): + arg = self.args[0] + lines = arg._eval_derivative_matrix_lines(x) + for line in lines: + line.first_pointer *= -self.T + line.second_pointer *= self + return lines + + +from sympy.assumptions.ask import ask, Q +from sympy.assumptions.refine import handlers_dict + + +def refine_Inverse(expr, assumptions): + """ + >>> from sympy import MatrixSymbol, Q, assuming, refine + >>> X = MatrixSymbol('X', 2, 2) + >>> X.I + X**(-1) + >>> with assuming(Q.orthogonal(X)): + ... print(refine(X.I)) + X.T + """ + if ask(Q.orthogonal(expr), assumptions): + return expr.arg.T + elif ask(Q.unitary(expr), assumptions): + return expr.arg.conjugate() + elif ask(Q.singular(expr), assumptions): + raise ValueError("Inverse of singular matrix %s" % expr.arg) + + return expr + +handlers_dict['Inverse'] = refine_Inverse diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/kronecker.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/kronecker.py new file mode 100644 index 0000000000000000000000000000000000000000..1dd175cb0d500af3e786e2d0dbf6b010947840b4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/kronecker.py @@ -0,0 +1,434 @@ +"""Implementation of the Kronecker product""" +from functools import reduce +from math import prod + +from sympy.core import Mul, sympify +from sympy.functions import adjoint +from sympy.matrices.exceptions import ShapeError +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.matrices.expressions.transpose import transpose +from sympy.matrices.expressions.special import Identity +from sympy.matrices.matrixbase import MatrixBase +from sympy.strategies import ( + canon, condition, distribute, do_one, exhaust, flatten, typed, unpack) +from sympy.strategies.traverse import bottom_up +from sympy.utilities import sift + +from .matadd import MatAdd +from .matmul import MatMul +from .matpow import MatPow + + +def kronecker_product(*matrices): + """ + The Kronecker product of two or more arguments. + + This computes the explicit Kronecker product for subclasses of + ``MatrixBase`` i.e. explicit matrices. Otherwise, a symbolic + ``KroneckerProduct`` object is returned. + + + Examples + ======== + + For ``MatrixSymbol`` arguments a ``KroneckerProduct`` object is returned. + Elements of this matrix can be obtained by indexing, or for MatrixSymbols + with known dimension the explicit matrix can be obtained with + ``.as_explicit()`` + + >>> from sympy import kronecker_product, MatrixSymbol + >>> A = MatrixSymbol('A', 2, 2) + >>> B = MatrixSymbol('B', 2, 2) + >>> kronecker_product(A) + A + >>> kronecker_product(A, B) + KroneckerProduct(A, B) + >>> kronecker_product(A, B)[0, 1] + A[0, 0]*B[0, 1] + >>> kronecker_product(A, B).as_explicit() + Matrix([ + [A[0, 0]*B[0, 0], A[0, 0]*B[0, 1], A[0, 1]*B[0, 0], A[0, 1]*B[0, 1]], + [A[0, 0]*B[1, 0], A[0, 0]*B[1, 1], A[0, 1]*B[1, 0], A[0, 1]*B[1, 1]], + [A[1, 0]*B[0, 0], A[1, 0]*B[0, 1], A[1, 1]*B[0, 0], A[1, 1]*B[0, 1]], + [A[1, 0]*B[1, 0], A[1, 0]*B[1, 1], A[1, 1]*B[1, 0], A[1, 1]*B[1, 1]]]) + + For explicit matrices the Kronecker product is returned as a Matrix + + >>> from sympy import Matrix, kronecker_product + >>> sigma_x = Matrix([ + ... [0, 1], + ... [1, 0]]) + ... + >>> Isigma_y = Matrix([ + ... [0, 1], + ... [-1, 0]]) + ... + >>> kronecker_product(sigma_x, Isigma_y) + Matrix([ + [ 0, 0, 0, 1], + [ 0, 0, -1, 0], + [ 0, 1, 0, 0], + [-1, 0, 0, 0]]) + + See Also + ======== + KroneckerProduct + + """ + if not matrices: + raise TypeError("Empty Kronecker product is undefined") + if len(matrices) == 1: + return matrices[0] + else: + return KroneckerProduct(*matrices).doit() + + +class KroneckerProduct(MatrixExpr): + """ + The Kronecker product of two or more arguments. + + The Kronecker product is a non-commutative product of matrices. + Given two matrices of dimension (m, n) and (s, t) it produces a matrix + of dimension (m s, n t). + + This is a symbolic object that simply stores its argument without + evaluating it. To actually compute the product, use the function + ``kronecker_product()`` or call the ``.doit()`` or ``.as_explicit()`` + methods. + + >>> from sympy import KroneckerProduct, MatrixSymbol + >>> A = MatrixSymbol('A', 5, 5) + >>> B = MatrixSymbol('B', 5, 5) + >>> isinstance(KroneckerProduct(A, B), KroneckerProduct) + True + """ + is_KroneckerProduct = True + + def __new__(cls, *args, check=True): + args = list(map(sympify, args)) + if all(a.is_Identity for a in args): + ret = Identity(prod(a.rows for a in args)) + if all(isinstance(a, MatrixBase) for a in args): + return ret.as_explicit() + else: + return ret + + if check: + validate(*args) + return super().__new__(cls, *args) + + @property + def shape(self): + rows, cols = self.args[0].shape + for mat in self.args[1:]: + rows *= mat.rows + cols *= mat.cols + return (rows, cols) + + def _entry(self, i, j, **kwargs): + result = 1 + for mat in reversed(self.args): + i, m = divmod(i, mat.rows) + j, n = divmod(j, mat.cols) + result *= mat[m, n] + return result + + def _eval_adjoint(self): + return KroneckerProduct(*list(map(adjoint, self.args))).doit() + + def _eval_conjugate(self): + return KroneckerProduct(*[a.conjugate() for a in self.args]).doit() + + def _eval_transpose(self): + return KroneckerProduct(*list(map(transpose, self.args))).doit() + + def _eval_trace(self): + from .trace import trace + return Mul(*[trace(a) for a in self.args]) + + def _eval_determinant(self): + from .determinant import det, Determinant + if not all(a.is_square for a in self.args): + return Determinant(self) + + m = self.rows + return Mul(*[det(a)**(m/a.rows) for a in self.args]) + + def _eval_inverse(self): + try: + return KroneckerProduct(*[a.inverse() for a in self.args]) + except ShapeError: + from sympy.matrices.expressions.inverse import Inverse + return Inverse(self) + + def structurally_equal(self, other): + '''Determine whether two matrices have the same Kronecker product structure + + Examples + ======== + + >>> from sympy import KroneckerProduct, MatrixSymbol, symbols + >>> m, n = symbols(r'm, n', integer=True) + >>> A = MatrixSymbol('A', m, m) + >>> B = MatrixSymbol('B', n, n) + >>> C = MatrixSymbol('C', m, m) + >>> D = MatrixSymbol('D', n, n) + >>> KroneckerProduct(A, B).structurally_equal(KroneckerProduct(C, D)) + True + >>> KroneckerProduct(A, B).structurally_equal(KroneckerProduct(D, C)) + False + >>> KroneckerProduct(A, B).structurally_equal(C) + False + ''' + # Inspired by BlockMatrix + return (isinstance(other, KroneckerProduct) + and self.shape == other.shape + and len(self.args) == len(other.args) + and all(a.shape == b.shape for (a, b) in zip(self.args, other.args))) + + def has_matching_shape(self, other): + '''Determine whether two matrices have the appropriate structure to bring matrix + multiplication inside the KroneckerProdut + + Examples + ======== + >>> from sympy import KroneckerProduct, MatrixSymbol, symbols + >>> m, n = symbols(r'm, n', integer=True) + >>> A = MatrixSymbol('A', m, n) + >>> B = MatrixSymbol('B', n, m) + >>> KroneckerProduct(A, B).has_matching_shape(KroneckerProduct(B, A)) + True + >>> KroneckerProduct(A, B).has_matching_shape(KroneckerProduct(A, B)) + False + >>> KroneckerProduct(A, B).has_matching_shape(A) + False + ''' + return (isinstance(other, KroneckerProduct) + and self.cols == other.rows + and len(self.args) == len(other.args) + and all(a.cols == b.rows for (a, b) in zip(self.args, other.args))) + + def _eval_expand_kroneckerproduct(self, **hints): + return flatten(canon(typed({KroneckerProduct: distribute(KroneckerProduct, MatAdd)}))(self)) + + def _kronecker_add(self, other): + if self.structurally_equal(other): + return self.__class__(*[a + b for (a, b) in zip(self.args, other.args)]) + else: + return self + other + + def _kronecker_mul(self, other): + if self.has_matching_shape(other): + return self.__class__(*[a*b for (a, b) in zip(self.args, other.args)]) + else: + return self * other + + def doit(self, **hints): + deep = hints.get('deep', True) + if deep: + args = [arg.doit(**hints) for arg in self.args] + else: + args = self.args + return canonicalize(KroneckerProduct(*args)) + + +def validate(*args): + if not all(arg.is_Matrix for arg in args): + raise TypeError("Mix of Matrix and Scalar symbols") + + +# rules + +def extract_commutative(kron): + c_part = [] + nc_part = [] + for arg in kron.args: + c, nc = arg.args_cnc() + c_part.extend(c) + nc_part.append(Mul._from_args(nc)) + + c_part = Mul(*c_part) + if c_part != 1: + return c_part*KroneckerProduct(*nc_part) + return kron + + +def matrix_kronecker_product(*matrices): + """Compute the Kronecker product of a sequence of SymPy Matrices. + + This is the standard Kronecker product of matrices [1]. + + Parameters + ========== + + matrices : tuple of MatrixBase instances + The matrices to take the Kronecker product of. + + Returns + ======= + + matrix : MatrixBase + The Kronecker product matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.matrices.expressions.kronecker import ( + ... matrix_kronecker_product) + + >>> m1 = Matrix([[1,2],[3,4]]) + >>> m2 = Matrix([[1,0],[0,1]]) + >>> matrix_kronecker_product(m1, m2) + Matrix([ + [1, 0, 2, 0], + [0, 1, 0, 2], + [3, 0, 4, 0], + [0, 3, 0, 4]]) + >>> matrix_kronecker_product(m2, m1) + Matrix([ + [1, 2, 0, 0], + [3, 4, 0, 0], + [0, 0, 1, 2], + [0, 0, 3, 4]]) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Kronecker_product + """ + # Make sure we have a sequence of Matrices + if not all(isinstance(m, MatrixBase) for m in matrices): + raise TypeError( + 'Sequence of Matrices expected, got: %s' % repr(matrices) + ) + + # Pull out the first element in the product. + matrix_expansion = matrices[-1] + # Do the kronecker product working from right to left. + for mat in reversed(matrices[:-1]): + rows = mat.rows + cols = mat.cols + # Go through each row appending kronecker product to. + # running matrix_expansion. + for i in range(rows): + start = matrix_expansion*mat[i*cols] + # Go through each column joining each item + for j in range(cols - 1): + start = start.row_join( + matrix_expansion*mat[i*cols + j + 1] + ) + # If this is the first element, make it the start of the + # new row. + if i == 0: + next = start + else: + next = next.col_join(start) + matrix_expansion = next + + MatrixClass = max(matrices, key=lambda M: M._class_priority).__class__ + if isinstance(matrix_expansion, MatrixClass): + return matrix_expansion + else: + return MatrixClass(matrix_expansion) + + +def explicit_kronecker_product(kron): + # Make sure we have a sequence of Matrices + if not all(isinstance(m, MatrixBase) for m in kron.args): + return kron + + return matrix_kronecker_product(*kron.args) + + +rules = (unpack, + explicit_kronecker_product, + flatten, + extract_commutative) + +canonicalize = exhaust(condition(lambda x: isinstance(x, KroneckerProduct), + do_one(*rules))) + + +def _kronecker_dims_key(expr): + if isinstance(expr, KroneckerProduct): + return tuple(a.shape for a in expr.args) + else: + return (0,) + + +def kronecker_mat_add(expr): + args = sift(expr.args, _kronecker_dims_key) + nonkrons = args.pop((0,), None) + if not args: + return expr + + krons = [reduce(lambda x, y: x._kronecker_add(y), group) + for group in args.values()] + + if not nonkrons: + return MatAdd(*krons) + else: + return MatAdd(*krons) + nonkrons + + +def kronecker_mat_mul(expr): + # modified from block matrix code + factor, matrices = expr.as_coeff_matrices() + + i = 0 + while i < len(matrices) - 1: + A, B = matrices[i:i+2] + if isinstance(A, KroneckerProduct) and isinstance(B, KroneckerProduct): + matrices[i] = A._kronecker_mul(B) + matrices.pop(i+1) + else: + i += 1 + + return factor*MatMul(*matrices) + + +def kronecker_mat_pow(expr): + if isinstance(expr.base, KroneckerProduct) and all(a.is_square for a in expr.base.args): + return KroneckerProduct(*[MatPow(a, expr.exp) for a in expr.base.args]) + else: + return expr + + +def combine_kronecker(expr): + """Combine KronekeckerProduct with expression. + + If possible write operations on KroneckerProducts of compatible shapes + as a single KroneckerProduct. + + Examples + ======== + + >>> from sympy.matrices.expressions import combine_kronecker + >>> from sympy import MatrixSymbol, KroneckerProduct, symbols + >>> m, n = symbols(r'm, n', integer=True) + >>> A = MatrixSymbol('A', m, n) + >>> B = MatrixSymbol('B', n, m) + >>> combine_kronecker(KroneckerProduct(A, B)*KroneckerProduct(B, A)) + KroneckerProduct(A*B, B*A) + >>> combine_kronecker(KroneckerProduct(A, B)+KroneckerProduct(B.T, A.T)) + KroneckerProduct(A + B.T, B + A.T) + >>> C = MatrixSymbol('C', n, n) + >>> D = MatrixSymbol('D', m, m) + >>> combine_kronecker(KroneckerProduct(C, D)**m) + KroneckerProduct(C**m, D**m) + """ + def haskron(expr): + return isinstance(expr, MatrixExpr) and expr.has(KroneckerProduct) + + rule = exhaust( + bottom_up(exhaust(condition(haskron, typed( + {MatAdd: kronecker_mat_add, + MatMul: kronecker_mat_mul, + MatPow: kronecker_mat_pow}))))) + result = rule(expr) + doit = getattr(result, 'doit', None) + if doit is not None: + return doit() + else: + return result diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matadd.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matadd.py new file mode 100644 index 0000000000000000000000000000000000000000..cfae1e5010e4077c7210c85c4315ed2404f245d7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matadd.py @@ -0,0 +1,155 @@ +from functools import reduce +import operator + +from sympy.core import Basic, sympify +from sympy.core.add import add, Add, _could_extract_minus_sign +from sympy.core.sorting import default_sort_key +from sympy.functions import adjoint +from sympy.matrices.matrixbase import MatrixBase +from sympy.matrices.expressions.transpose import transpose +from sympy.strategies import (rm_id, unpack, flatten, sort, condition, + exhaust, do_one, glom) +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.matrices.expressions.special import ZeroMatrix, GenericZeroMatrix +from sympy.matrices.expressions._shape import validate_matadd_integer as validate +from sympy.utilities.iterables import sift +from sympy.utilities.exceptions import sympy_deprecation_warning + +# XXX: MatAdd should perhaps not subclass directly from Add +class MatAdd(MatrixExpr, Add): + """A Sum of Matrix Expressions + + MatAdd inherits from and operates like SymPy Add + + Examples + ======== + + >>> from sympy import MatAdd, MatrixSymbol + >>> A = MatrixSymbol('A', 5, 5) + >>> B = MatrixSymbol('B', 5, 5) + >>> C = MatrixSymbol('C', 5, 5) + >>> MatAdd(A, B, C) + A + B + C + """ + is_MatAdd = True + + identity = GenericZeroMatrix() + + def __new__(cls, *args, evaluate=False, check=None, _sympify=True): + if not args: + return cls.identity + + # This must be removed aggressively in the constructor to avoid + # TypeErrors from GenericZeroMatrix().shape + args = list(filter(lambda i: cls.identity != i, args)) + if _sympify: + args = list(map(sympify, args)) + + if not all(isinstance(arg, MatrixExpr) for arg in args): + raise TypeError("Mix of Matrix and Scalar symbols") + + obj = Basic.__new__(cls, *args) + + if check is not None: + sympy_deprecation_warning( + "Passing check to MatAdd is deprecated and the check argument will be removed in a future version.", + deprecated_since_version="1.11", + active_deprecations_target='remove-check-argument-from-matrix-operations') + + if check is not False: + validate(*args) + + if evaluate: + obj = cls._evaluate(obj) + + return obj + + @classmethod + def _evaluate(cls, expr): + return canonicalize(expr) + + @property + def shape(self): + return self.args[0].shape + + def could_extract_minus_sign(self): + return _could_extract_minus_sign(self) + + def expand(self, **kwargs): + expanded = super(MatAdd, self).expand(**kwargs) + return self._evaluate(expanded) + + def _entry(self, i, j, **kwargs): + return Add(*[arg._entry(i, j, **kwargs) for arg in self.args]) + + def _eval_transpose(self): + return MatAdd(*[transpose(arg) for arg in self.args]).doit() + + def _eval_adjoint(self): + return MatAdd(*[adjoint(arg) for arg in self.args]).doit() + + def _eval_trace(self): + from .trace import trace + return Add(*[trace(arg) for arg in self.args]).doit() + + def doit(self, **hints): + deep = hints.get('deep', True) + if deep: + args = [arg.doit(**hints) for arg in self.args] + else: + args = self.args + return canonicalize(MatAdd(*args)) + + def _eval_derivative_matrix_lines(self, x): + add_lines = [arg._eval_derivative_matrix_lines(x) for arg in self.args] + return [j for i in add_lines for j in i] + +add.register_handlerclass((Add, MatAdd), MatAdd) + + +factor_of = lambda arg: arg.as_coeff_mmul()[0] +matrix_of = lambda arg: unpack(arg.as_coeff_mmul()[1]) +def combine(cnt, mat): + if cnt == 1: + return mat + else: + return cnt * mat + + +def merge_explicit(matadd): + """ Merge explicit MatrixBase arguments + + Examples + ======== + + >>> from sympy import MatrixSymbol, eye, Matrix, MatAdd, pprint + >>> from sympy.matrices.expressions.matadd import merge_explicit + >>> A = MatrixSymbol('A', 2, 2) + >>> B = eye(2) + >>> C = Matrix([[1, 2], [3, 4]]) + >>> X = MatAdd(A, B, C) + >>> pprint(X) + [1 0] [1 2] + A + [ ] + [ ] + [0 1] [3 4] + >>> pprint(merge_explicit(X)) + [2 2] + A + [ ] + [3 5] + """ + groups = sift(matadd.args, lambda arg: isinstance(arg, MatrixBase)) + if len(groups[True]) > 1: + return MatAdd(*(groups[False] + [reduce(operator.add, groups[True])])) + else: + return matadd + + +rules = (rm_id(lambda x: x == 0 or isinstance(x, ZeroMatrix)), + unpack, + flatten, + glom(matrix_of, factor_of, combine), + merge_explicit, + sort(default_sort_key)) + +canonicalize = exhaust(condition(lambda x: isinstance(x, MatAdd), + do_one(*rules))) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matexpr.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matexpr.py new file mode 100644 index 0000000000000000000000000000000000000000..a4e99296ccfcbdac5e09a86ecee020adf9831c73 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matexpr.py @@ -0,0 +1,888 @@ +from __future__ import annotations +from functools import wraps + +from sympy.core import S, Integer, Basic, Mul, Add +from sympy.core.assumptions import check_assumptions +from sympy.core.decorators import call_highest_priority +from sympy.core.expr import Expr, ExprBuilder +from sympy.core.logic import FuzzyBool +from sympy.core.symbol import Str, Dummy, symbols, Symbol +from sympy.core.sympify import SympifyError, _sympify +from sympy.external.gmpy import SYMPY_INTS +from sympy.functions import conjugate, adjoint +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.matrices.exceptions import NonSquareMatrixError +from sympy.matrices.kind import MatrixKind +from sympy.matrices.matrixbase import MatrixBase +from sympy.multipledispatch import dispatch +from sympy.utilities.misc import filldedent + + +def _sympifyit(arg, retval=None): + # This version of _sympifyit sympifies MutableMatrix objects + def deco(func): + @wraps(func) + def __sympifyit_wrapper(a, b): + try: + b = _sympify(b) + return func(a, b) + except SympifyError: + return retval + + return __sympifyit_wrapper + + return deco + + +class MatrixExpr(Expr): + """Superclass for Matrix Expressions + + MatrixExprs represent abstract matrices, linear transformations represented + within a particular basis. + + Examples + ======== + + >>> from sympy import MatrixSymbol + >>> A = MatrixSymbol('A', 3, 3) + >>> y = MatrixSymbol('y', 3, 1) + >>> x = (A.T*A).I * A * y + + See Also + ======== + + MatrixSymbol, MatAdd, MatMul, Transpose, Inverse + """ + __slots__: tuple[str, ...] = () + + # Should not be considered iterable by the + # sympy.utilities.iterables.iterable function. Subclass that actually are + # iterable (i.e., explicit matrices) should set this to True. + _iterable = False + + _op_priority = 11.0 + + is_Matrix: bool = True + is_MatrixExpr: bool = True + is_Identity: FuzzyBool = None + is_Inverse = False + is_Transpose = False + is_ZeroMatrix = False + is_MatAdd = False + is_MatMul = False + + is_commutative = False + is_number = False + is_symbol = False + is_scalar = False + + kind: MatrixKind = MatrixKind() + + def __new__(cls, *args, **kwargs): + args = map(_sympify, args) + return Basic.__new__(cls, *args, **kwargs) + + # The following is adapted from the core Expr object + + @property + def shape(self) -> tuple[Expr, Expr]: + raise NotImplementedError + + @property + def _add_handler(self): + return MatAdd + + @property + def _mul_handler(self): + return MatMul + + def __neg__(self): + return MatMul(S.NegativeOne, self).doit() + + def __abs__(self): + raise NotImplementedError + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__radd__') + def __add__(self, other): + return MatAdd(self, other).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__add__') + def __radd__(self, other): + return MatAdd(other, self).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__rsub__') + def __sub__(self, other): + return MatAdd(self, -other).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__sub__') + def __rsub__(self, other): + return MatAdd(other, -self).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__rmul__') + def __mul__(self, other): + return MatMul(self, other).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__rmul__') + def __matmul__(self, other): + return MatMul(self, other).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__mul__') + def __rmul__(self, other): + return MatMul(other, self).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__mul__') + def __rmatmul__(self, other): + return MatMul(other, self).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__rpow__') + def __pow__(self, other): + return MatPow(self, other).doit() + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__pow__') + def __rpow__(self, other): + raise NotImplementedError("Matrix Power not defined") + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__rtruediv__') + def __truediv__(self, other): + return self * other**S.NegativeOne + + @_sympifyit('other', NotImplemented) + @call_highest_priority('__truediv__') + def __rtruediv__(self, other): + raise NotImplementedError() + #return MatMul(other, Pow(self, S.NegativeOne)) + + @property + def rows(self): + return self.shape[0] + + @property + def cols(self): + return self.shape[1] + + @property + def is_square(self) -> bool | None: + rows, cols = self.shape + if isinstance(rows, Integer) and isinstance(cols, Integer): + return rows == cols + if rows == cols: + return True + return None + + def _eval_conjugate(self): + from sympy.matrices.expressions.adjoint import Adjoint + return Adjoint(Transpose(self)) + + def as_real_imag(self, deep=True, **hints): + return self._eval_as_real_imag() + + def _eval_as_real_imag(self): + real = S.Half * (self + self._eval_conjugate()) + im = (self - self._eval_conjugate())/(2*S.ImaginaryUnit) + return (real, im) + + def _eval_inverse(self): + return Inverse(self) + + def _eval_determinant(self): + return Determinant(self) + + def _eval_transpose(self): + return Transpose(self) + + def _eval_trace(self): + return None + + def _eval_power(self, exp): + """ + Override this in sub-classes to implement simplification of powers. The cases where the exponent + is -1, 0, 1 are already covered in MatPow.doit(), so implementations can exclude these cases. + """ + return MatPow(self, exp) + + def _eval_simplify(self, **kwargs): + if self.is_Atom: + return self + else: + from sympy.simplify import simplify + return self.func(*[simplify(x, **kwargs) for x in self.args]) + + def _eval_adjoint(self): + from sympy.matrices.expressions.adjoint import Adjoint + return Adjoint(self) + + def _eval_derivative_n_times(self, x, n): + return Basic._eval_derivative_n_times(self, x, n) + + def _eval_derivative(self, x): + # `x` is a scalar: + if self.has(x): + # See if there are other methods using it: + return super()._eval_derivative(x) + else: + return ZeroMatrix(*self.shape) + + @classmethod + def _check_dim(cls, dim): + """Helper function to check invalid matrix dimensions""" + ok = not dim.is_Float and check_assumptions( + dim, integer=True, nonnegative=True) + if ok is False: + raise ValueError( + "The dimension specification {} should be " + "a nonnegative integer.".format(dim)) + + + def _entry(self, i, j, **kwargs): + raise NotImplementedError( + "Indexing not implemented for %s" % self.__class__.__name__) + + def adjoint(self): + return adjoint(self) + + def as_coeff_Mul(self, rational=False): + """Efficiently extract the coefficient of a product.""" + return S.One, self + + def conjugate(self): + return conjugate(self) + + def transpose(self): + from sympy.matrices.expressions.transpose import transpose + return transpose(self) + + @property + def T(self): + '''Matrix transposition''' + return self.transpose() + + def inverse(self): + if self.is_square is False: + raise NonSquareMatrixError('Inverse of non-square matrix') + return self._eval_inverse() + + def inv(self): + return self.inverse() + + def det(self): + from sympy.matrices.expressions.determinant import det + return det(self) + + @property + def I(self): + return self.inverse() + + def valid_index(self, i, j): + def is_valid(idx): + return isinstance(idx, (int, Integer, Symbol, Expr)) + return (is_valid(i) and is_valid(j) and + (self.rows is None or + (i >= -self.rows) != False and (i < self.rows) != False) and + (j >= -self.cols) != False and (j < self.cols) != False) + + def __getitem__(self, key): + if not isinstance(key, tuple) and isinstance(key, slice): + from sympy.matrices.expressions.slice import MatrixSlice + return MatrixSlice(self, key, (0, None, 1)) + if isinstance(key, tuple) and len(key) == 2: + i, j = key + if isinstance(i, slice) or isinstance(j, slice): + from sympy.matrices.expressions.slice import MatrixSlice + return MatrixSlice(self, i, j) + i, j = _sympify(i), _sympify(j) + if self.valid_index(i, j) != False: + return self._entry(i, j) + else: + raise IndexError("Invalid indices (%s, %s)" % (i, j)) + elif isinstance(key, (SYMPY_INTS, Integer)): + # row-wise decomposition of matrix + rows, cols = self.shape + # allow single indexing if number of columns is known + if not isinstance(cols, Integer): + raise IndexError(filldedent(''' + Single indexing is only supported when the number + of columns is known.''')) + key = _sympify(key) + i = key // cols + j = key % cols + if self.valid_index(i, j) != False: + return self._entry(i, j) + else: + raise IndexError("Invalid index %s" % key) + elif isinstance(key, (Symbol, Expr)): + raise IndexError(filldedent(''' + Only integers may be used when addressing the matrix + with a single index.''')) + raise IndexError("Invalid index, wanted %s[i,j]" % self) + + def _is_shape_symbolic(self) -> bool: + return (not isinstance(self.rows, (SYMPY_INTS, Integer)) + or not isinstance(self.cols, (SYMPY_INTS, Integer))) + + def as_explicit(self): + """ + Returns a dense Matrix with elements represented explicitly + + Returns an object of type ImmutableDenseMatrix. + + Examples + ======== + + >>> from sympy import Identity + >>> I = Identity(3) + >>> I + I + >>> I.as_explicit() + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + + See Also + ======== + as_mutable: returns mutable Matrix type + + """ + if self._is_shape_symbolic(): + raise ValueError( + 'Matrix with symbolic shape ' + 'cannot be represented explicitly.') + from sympy.matrices.immutable import ImmutableDenseMatrix + return ImmutableDenseMatrix([[self[i, j] + for j in range(self.cols)] + for i in range(self.rows)]) + + def as_mutable(self): + """ + Returns a dense, mutable matrix with elements represented explicitly + + Examples + ======== + + >>> from sympy import Identity + >>> I = Identity(3) + >>> I + I + >>> I.shape + (3, 3) + >>> I.as_mutable() + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + + See Also + ======== + as_explicit: returns ImmutableDenseMatrix + """ + return self.as_explicit().as_mutable() + + def __array__(self, dtype=object, copy=None): + if copy is not None and not copy: + raise TypeError("Cannot implement copy=False when converting Matrix to ndarray") + from numpy import empty + a = empty(self.shape, dtype=object) + for i in range(self.rows): + for j in range(self.cols): + a[i, j] = self[i, j] + return a + + def equals(self, other): + """ + Test elementwise equality between matrices, potentially of different + types + + >>> from sympy import Identity, eye + >>> Identity(3).equals(eye(3)) + True + """ + return self.as_explicit().equals(other) + + def canonicalize(self): + return self + + def as_coeff_mmul(self): + return S.One, MatMul(self) + + @staticmethod + def from_index_summation(expr, first_index=None, last_index=None, dimensions=None): + r""" + Parse expression of matrices with explicitly summed indices into a + matrix expression without indices, if possible. + + This transformation expressed in mathematical notation: + + `\sum_{j=0}^{N-1} A_{i,j} B_{j,k} \Longrightarrow \mathbf{A}\cdot \mathbf{B}` + + Optional parameter ``first_index``: specify which free index to use as + the index starting the expression. + + Examples + ======== + + >>> from sympy import MatrixSymbol, MatrixExpr, Sum + >>> from sympy.abc import i, j, k, l, N + >>> A = MatrixSymbol("A", N, N) + >>> B = MatrixSymbol("B", N, N) + >>> expr = Sum(A[i, j]*B[j, k], (j, 0, N-1)) + >>> MatrixExpr.from_index_summation(expr) + A*B + + Transposition is detected: + + >>> expr = Sum(A[j, i]*B[j, k], (j, 0, N-1)) + >>> MatrixExpr.from_index_summation(expr) + A.T*B + + Detect the trace: + + >>> expr = Sum(A[i, i], (i, 0, N-1)) + >>> MatrixExpr.from_index_summation(expr) + Trace(A) + + More complicated expressions: + + >>> expr = Sum(A[i, j]*B[k, j]*A[l, k], (j, 0, N-1), (k, 0, N-1)) + >>> MatrixExpr.from_index_summation(expr) + A*B.T*A.T + """ + from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array + from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix + first_indices = [] + if first_index is not None: + first_indices.append(first_index) + if last_index is not None: + first_indices.append(last_index) + arr = convert_indexed_to_array(expr, first_indices=first_indices) + return convert_array_to_matrix(arr) + + def applyfunc(self, func): + from .applyfunc import ElementwiseApplyFunction + return ElementwiseApplyFunction(func, self) + + +@dispatch(MatrixExpr, Expr) +def _eval_is_eq(lhs, rhs): # noqa:F811 + return False + +@dispatch(MatrixExpr, MatrixExpr) # type: ignore +def _eval_is_eq(lhs, rhs): # noqa:F811 + if lhs.shape != rhs.shape: + return False + if (lhs - rhs).is_ZeroMatrix: + return True + +def get_postprocessor(cls): + def _postprocessor(expr): + # To avoid circular imports, we can't have MatMul/MatAdd on the top level + mat_class = {Mul: MatMul, Add: MatAdd}[cls] + nonmatrices = [] + matrices = [] + for term in expr.args: + if isinstance(term, MatrixExpr): + matrices.append(term) + else: + nonmatrices.append(term) + + if not matrices: + return cls._from_args(nonmatrices) + + if nonmatrices: + if cls == Mul: + for i in range(len(matrices)): + if not matrices[i].is_MatrixExpr: + # If one of the matrices explicit, absorb the scalar into it + # (doit will combine all explicit matrices into one, so it + # doesn't matter which) + matrices[i] = matrices[i].__mul__(cls._from_args(nonmatrices)) + nonmatrices = [] + break + + else: + # Maintain the ability to create Add(scalar, matrix) without + # raising an exception. That way different algorithms can + # replace matrix expressions with non-commutative symbols to + # manipulate them like non-commutative scalars. + return cls._from_args(nonmatrices + [mat_class(*matrices).doit(deep=False)]) + + if mat_class == MatAdd: + return mat_class(*matrices).doit(deep=False) + return mat_class(cls._from_args(nonmatrices), *matrices).doit(deep=False) + return _postprocessor + + +Basic._constructor_postprocessor_mapping[MatrixExpr] = { + "Mul": [get_postprocessor(Mul)], + "Add": [get_postprocessor(Add)], +} + + +def _matrix_derivative(expr, x, old_algorithm=False): + + if isinstance(expr, MatrixBase) or isinstance(x, MatrixBase): + # Do not use array expressions for explicit matrices: + old_algorithm = True + + if old_algorithm: + return _matrix_derivative_old_algorithm(expr, x) + + from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array + from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive + from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix + + array_expr = convert_matrix_to_array(expr) + diff_array_expr = array_derive(array_expr, x) + diff_matrix_expr = convert_array_to_matrix(diff_array_expr) + return diff_matrix_expr + + +def _matrix_derivative_old_algorithm(expr, x): + from sympy.tensor.array.array_derivatives import ArrayDerivative + lines = expr._eval_derivative_matrix_lines(x) + + parts = [i.build() for i in lines] + + from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix + + parts = [[convert_array_to_matrix(j) for j in i] for i in parts] + + def _get_shape(elem): + if isinstance(elem, MatrixExpr): + return elem.shape + return 1, 1 + + def get_rank(parts): + return sum(j not in (1, None) for i in parts for j in _get_shape(i)) + + ranks = [get_rank(i) for i in parts] + rank = ranks[0] + + def contract_one_dims(parts): + if len(parts) == 1: + return parts[0] + else: + p1, p2 = parts[:2] + if p2.is_Matrix: + p2 = p2.T + if p1 == Identity(1): + pbase = p2 + elif p2 == Identity(1): + pbase = p1 + else: + pbase = p1*p2 + if len(parts) == 2: + return pbase + else: # len(parts) > 2 + if pbase.is_Matrix: + raise ValueError("") + return pbase*Mul.fromiter(parts[2:]) + + if rank <= 2: + return Add.fromiter([contract_one_dims(i) for i in parts]) + + return ArrayDerivative(expr, x) + + +class MatrixElement(Expr): + parent = property(lambda self: self.args[0]) + i = property(lambda self: self.args[1]) + j = property(lambda self: self.args[2]) + _diff_wrt = True + is_symbol = True + is_commutative = True + + def __new__(cls, name, n, m): + n, m = map(_sympify, (n, m)) + if isinstance(name, str): + name = Symbol(name) + else: + if isinstance(name, MatrixBase): + if n.is_Integer and m.is_Integer: + return name[n, m] + name = _sympify(name) # change mutable into immutable + else: + name = _sympify(name) + if not isinstance(name.kind, MatrixKind): + raise TypeError("First argument of MatrixElement should be a matrix") + if not getattr(name, 'valid_index', lambda n, m: True)(n, m): + raise IndexError('indices out of range') + obj = Expr.__new__(cls, name, n, m) + return obj + + @property + def symbol(self): + return self.args[0] + + def doit(self, **hints): + deep = hints.get('deep', True) + if deep: + args = [arg.doit(**hints) for arg in self.args] + else: + args = self.args + return args[0][args[1], args[2]] + + @property + def indices(self): + return self.args[1:] + + def _eval_derivative(self, v): + + if not isinstance(v, MatrixElement): + return self.parent.diff(v)[self.i, self.j] + + M = self.args[0] + + m, n = self.parent.shape + + if M == v.args[0]: + return KroneckerDelta(self.args[1], v.args[1], (0, m-1)) * \ + KroneckerDelta(self.args[2], v.args[2], (0, n-1)) + + if isinstance(M, Inverse): + from sympy.concrete.summations import Sum + i, j = self.args[1:] + i1, i2 = symbols("z1, z2", cls=Dummy) + Y = M.args[0] + r1, r2 = Y.shape + return -Sum(M[i, i1]*Y[i1, i2].diff(v)*M[i2, j], (i1, 0, r1-1), (i2, 0, r2-1)) + + if self.has(v.args[0]): + return None + + return S.Zero + + +class MatrixSymbol(MatrixExpr): + """Symbolic representation of a Matrix object + + Creates a SymPy Symbol to represent a Matrix. This matrix has a shape and + can be included in Matrix Expressions + + Examples + ======== + + >>> from sympy import MatrixSymbol, Identity + >>> A = MatrixSymbol('A', 3, 4) # A 3 by 4 Matrix + >>> B = MatrixSymbol('B', 4, 3) # A 4 by 3 Matrix + >>> A.shape + (3, 4) + >>> 2*A*B + Identity(3) + I + 2*A*B + """ + is_commutative = False + is_symbol = True + _diff_wrt = True + + def __new__(cls, name, n, m): + n, m = _sympify(n), _sympify(m) + + cls._check_dim(m) + cls._check_dim(n) + + if isinstance(name, str): + name = Str(name) + obj = Basic.__new__(cls, name, n, m) + return obj + + @property + def shape(self): + return self.args[1], self.args[2] + + @property + def name(self): + return self.args[0].name + + def _entry(self, i, j, **kwargs): + return MatrixElement(self, i, j) + + @property + def free_symbols(self): + return {self} + + def _eval_simplify(self, **kwargs): + return self + + def _eval_derivative(self, x): + # x is a scalar: + return ZeroMatrix(self.shape[0], self.shape[1]) + + def _eval_derivative_matrix_lines(self, x): + if self != x: + first = ZeroMatrix(x.shape[0], self.shape[0]) if self.shape[0] != 1 else S.Zero + second = ZeroMatrix(x.shape[1], self.shape[1]) if self.shape[1] != 1 else S.Zero + return [_LeftRightArgs( + [first, second], + )] + else: + first = Identity(self.shape[0]) if self.shape[0] != 1 else S.One + second = Identity(self.shape[1]) if self.shape[1] != 1 else S.One + return [_LeftRightArgs( + [first, second], + )] + + +def matrix_symbols(expr): + return [sym for sym in expr.free_symbols if sym.is_Matrix] + + +class _LeftRightArgs: + r""" + Helper class to compute matrix derivatives. + + The logic: when an expression is derived by a matrix `X_{mn}`, two lines of + matrix multiplications are created: the one contracted to `m` (first line), + and the one contracted to `n` (second line). + + Transposition flips the side by which new matrices are connected to the + lines. + + The trace connects the end of the two lines. + """ + + def __init__(self, lines, higher=S.One): + self._lines = list(lines) + self._first_pointer_parent = self._lines + self._first_pointer_index = 0 + self._first_line_index = 0 + self._second_pointer_parent = self._lines + self._second_pointer_index = 1 + self._second_line_index = 1 + self.higher = higher + + @property + def first_pointer(self): + return self._first_pointer_parent[self._first_pointer_index] + + @first_pointer.setter + def first_pointer(self, value): + self._first_pointer_parent[self._first_pointer_index] = value + + @property + def second_pointer(self): + return self._second_pointer_parent[self._second_pointer_index] + + @second_pointer.setter + def second_pointer(self, value): + self._second_pointer_parent[self._second_pointer_index] = value + + def __repr__(self): + built = [self._build(i) for i in self._lines] + return "_LeftRightArgs(lines=%s, higher=%s)" % ( + built, + self.higher, + ) + + def transpose(self): + self._first_pointer_parent, self._second_pointer_parent = self._second_pointer_parent, self._first_pointer_parent + self._first_pointer_index, self._second_pointer_index = self._second_pointer_index, self._first_pointer_index + self._first_line_index, self._second_line_index = self._second_line_index, self._first_line_index + return self + + @staticmethod + def _build(expr): + if isinstance(expr, ExprBuilder): + return expr.build() + if isinstance(expr, list): + if len(expr) == 1: + return expr[0] + else: + return expr[0](*[_LeftRightArgs._build(i) for i in expr[1]]) + else: + return expr + + def build(self): + data = [self._build(i) for i in self._lines] + if self.higher != 1: + data += [self._build(self.higher)] + data = list(data) + return data + + def matrix_form(self): + if self.first != 1 and self.higher != 1: + raise ValueError("higher dimensional array cannot be represented") + + def _get_shape(elem): + if isinstance(elem, MatrixExpr): + return elem.shape + return (None, None) + + if _get_shape(self.first)[1] != _get_shape(self.second)[1]: + # Remove one-dimensional identity matrices: + # (this is needed by `a.diff(a)` where `a` is a vector) + if _get_shape(self.second) == (1, 1): + return self.first*self.second[0, 0] + if _get_shape(self.first) == (1, 1): + return self.first[1, 1]*self.second.T + raise ValueError("incompatible shapes") + if self.first != 1: + return self.first*self.second.T + else: + return self.higher + + def rank(self): + """ + Number of dimensions different from trivial (warning: not related to + matrix rank). + """ + rank = 0 + if self.first != 1: + rank += sum(i != 1 for i in self.first.shape) + if self.second != 1: + rank += sum(i != 1 for i in self.second.shape) + if self.higher != 1: + rank += 2 + return rank + + def _multiply_pointer(self, pointer, other): + from ...tensor.array.expressions.array_expressions import ArrayTensorProduct + from ...tensor.array.expressions.array_expressions import ArrayContraction + + subexpr = ExprBuilder( + ArrayContraction, + [ + ExprBuilder( + ArrayTensorProduct, + [ + pointer, + other + ] + ), + (1, 2) + ], + validator=ArrayContraction._validate + ) + + return subexpr + + def append_first(self, other): + self.first_pointer *= other + + def append_second(self, other): + self.second_pointer *= other + + +def _make_matrix(x): + from sympy.matrices.immutable import ImmutableDenseMatrix + if isinstance(x, MatrixExpr): + return x + return ImmutableDenseMatrix([[x]]) + + +from .matmul import MatMul +from .matadd import MatAdd +from .matpow import MatPow +from .transpose import Transpose +from .inverse import Inverse +from .special import ZeroMatrix, Identity +from .determinant import Determinant diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matmul.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matmul.py new file mode 100644 index 0000000000000000000000000000000000000000..1c46f7ff5251d89793423f92ea02d7243601de3f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matmul.py @@ -0,0 +1,496 @@ +from sympy.assumptions.ask import ask, Q +from sympy.assumptions.refine import handlers_dict +from sympy.core import Basic, sympify, S +from sympy.core.mul import mul, Mul +from sympy.core.numbers import Number, Integer +from sympy.core.symbol import Dummy +from sympy.functions import adjoint +from sympy.strategies import (rm_id, unpack, typed, flatten, exhaust, + do_one, new) +from sympy.matrices.exceptions import NonInvertibleMatrixError +from sympy.matrices.matrixbase import MatrixBase +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.matrices.expressions._shape import validate_matmul_integer as validate + +from .inverse import Inverse +from .matexpr import MatrixExpr +from .matpow import MatPow +from .transpose import transpose +from .permutation import PermutationMatrix +from .special import ZeroMatrix, Identity, GenericIdentity, OneMatrix + + +# XXX: MatMul should perhaps not subclass directly from Mul +class MatMul(MatrixExpr, Mul): + """ + A product of matrix expressions + + Examples + ======== + + >>> from sympy import MatMul, MatrixSymbol + >>> A = MatrixSymbol('A', 5, 4) + >>> B = MatrixSymbol('B', 4, 3) + >>> C = MatrixSymbol('C', 3, 6) + >>> MatMul(A, B, C) + A*B*C + """ + is_MatMul = True + + identity = GenericIdentity() + + def __new__(cls, *args, evaluate=False, check=None, _sympify=True): + if not args: + return cls.identity + + # This must be removed aggressively in the constructor to avoid + # TypeErrors from GenericIdentity().shape + args = list(filter(lambda i: cls.identity != i, args)) + if _sympify: + args = list(map(sympify, args)) + obj = Basic.__new__(cls, *args) + factor, matrices = obj.as_coeff_matrices() + + if check is not None: + sympy_deprecation_warning( + "Passing check to MatMul is deprecated and the check argument will be removed in a future version.", + deprecated_since_version="1.11", + active_deprecations_target='remove-check-argument-from-matrix-operations') + + if check is not False: + validate(*matrices) + + if not matrices: + # Should it be + # + # return Basic.__neq__(cls, factor, GenericIdentity()) ? + return factor + + if evaluate: + return cls._evaluate(obj) + + return obj + + @classmethod + def _evaluate(cls, expr): + return canonicalize(expr) + + @property + def shape(self): + matrices = [arg for arg in self.args if arg.is_Matrix] + return (matrices[0].rows, matrices[-1].cols) + + def _entry(self, i, j, expand=True, **kwargs): + # Avoid cyclic imports + from sympy.concrete.summations import Sum + from sympy.matrices.immutable import ImmutableMatrix + + coeff, matrices = self.as_coeff_matrices() + + if len(matrices) == 1: # situation like 2*X, matmul is just X + return coeff * matrices[0][i, j] + + indices = [None]*(len(matrices) + 1) + ind_ranges = [None]*(len(matrices) - 1) + indices[0] = i + indices[-1] = j + + def f(): + counter = 1 + while True: + yield Dummy("i_%i" % counter) + counter += 1 + + dummy_generator = kwargs.get("dummy_generator", f()) + + for i in range(1, len(matrices)): + indices[i] = next(dummy_generator) + + for i, arg in enumerate(matrices[:-1]): + ind_ranges[i] = arg.shape[1] - 1 + matrices = [arg._entry(indices[i], indices[i+1], dummy_generator=dummy_generator) for i, arg in enumerate(matrices)] + expr_in_sum = Mul.fromiter(matrices) + if any(v.has(ImmutableMatrix) for v in matrices): + expand = True + result = coeff*Sum( + expr_in_sum, + *zip(indices[1:-1], [0]*len(ind_ranges), ind_ranges) + ) + + # Don't waste time in result.doit() if the sum bounds are symbolic + if not any(isinstance(v, (Integer, int)) for v in ind_ranges): + expand = False + return result.doit() if expand else result + + def as_coeff_matrices(self): + scalars = [x for x in self.args if not x.is_Matrix] + matrices = [x for x in self.args if x.is_Matrix] + coeff = Mul(*scalars) + if coeff.is_commutative is False: + raise NotImplementedError("noncommutative scalars in MatMul are not supported.") + + return coeff, matrices + + def as_coeff_mmul(self): + coeff, matrices = self.as_coeff_matrices() + return coeff, MatMul(*matrices) + + def expand(self, **kwargs): + expanded = super(MatMul, self).expand(**kwargs) + return self._evaluate(expanded) + + def _eval_transpose(self): + """Transposition of matrix multiplication. + + Notes + ===== + + The following rules are applied. + + Transposition for matrix multiplied with another matrix: + `\\left(A B\\right)^{T} = B^{T} A^{T}` + + Transposition for matrix multiplied with scalar: + `\\left(c A\\right)^{T} = c A^{T}` + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Transpose + """ + coeff, matrices = self.as_coeff_matrices() + return MatMul( + coeff, *[transpose(arg) for arg in matrices[::-1]]).doit() + + def _eval_adjoint(self): + return MatMul(*[adjoint(arg) for arg in self.args[::-1]]).doit() + + def _eval_trace(self): + factor, mmul = self.as_coeff_mmul() + if factor != 1: + from .trace import trace + return factor * trace(mmul.doit()) + + def _eval_determinant(self): + from sympy.matrices.expressions.determinant import Determinant + factor, matrices = self.as_coeff_matrices() + square_matrices = only_squares(*matrices) + return factor**self.rows * Mul(*list(map(Determinant, square_matrices))) + + def _eval_inverse(self): + if all(arg.is_square for arg in self.args if isinstance(arg, MatrixExpr)): + return MatMul(*( + arg.inverse() if isinstance(arg, MatrixExpr) else arg**-1 + for arg in self.args[::-1] + ) + ).doit() + return Inverse(self) + + def doit(self, **hints): + deep = hints.get('deep', True) + if deep: + args = tuple(arg.doit(**hints) for arg in self.args) + else: + args = self.args + + # treat scalar*MatrixSymbol or scalar*MatPow separately + expr = canonicalize(MatMul(*args)) + return expr + + # Needed for partial compatibility with Mul + def args_cnc(self, cset=False, warn=True, **kwargs): + coeff_c = [x for x in self.args if x.is_commutative] + coeff_nc = [x for x in self.args if not x.is_commutative] + if cset: + clen = len(coeff_c) + coeff_c = set(coeff_c) + if clen and warn and len(coeff_c) != clen: + raise ValueError('repeated commutative arguments: %s' % + [ci for ci in coeff_c if list(self.args).count(ci) > 1]) + return [coeff_c, coeff_nc] + + def _eval_derivative_matrix_lines(self, x): + from .transpose import Transpose + with_x_ind = [i for i, arg in enumerate(self.args) if arg.has(x)] + lines = [] + for ind in with_x_ind: + left_args = self.args[:ind] + right_args = self.args[ind+1:] + + if right_args: + right_mat = MatMul.fromiter(right_args) + else: + right_mat = Identity(self.shape[1]) + if left_args: + left_rev = MatMul.fromiter([Transpose(i).doit() if i.is_Matrix else i for i in reversed(left_args)]) + else: + left_rev = Identity(self.shape[0]) + + d = self.args[ind]._eval_derivative_matrix_lines(x) + for i in d: + i.append_first(left_rev) + i.append_second(right_mat) + lines.append(i) + + return lines + +mul.register_handlerclass((Mul, MatMul), MatMul) + + +# Rules +def newmul(*args): + if args[0] == 1: + args = args[1:] + return new(MatMul, *args) + +def any_zeros(mul): + if any(arg.is_zero or (arg.is_Matrix and arg.is_ZeroMatrix) + for arg in mul.args): + matrices = [arg for arg in mul.args if arg.is_Matrix] + return ZeroMatrix(matrices[0].rows, matrices[-1].cols) + return mul + +def merge_explicit(matmul): + """ Merge explicit MatrixBase arguments + + >>> from sympy import MatrixSymbol, Matrix, MatMul, pprint + >>> from sympy.matrices.expressions.matmul import merge_explicit + >>> A = MatrixSymbol('A', 2, 2) + >>> B = Matrix([[1, 1], [1, 1]]) + >>> C = Matrix([[1, 2], [3, 4]]) + >>> X = MatMul(A, B, C) + >>> pprint(X) + [1 1] [1 2] + A*[ ]*[ ] + [1 1] [3 4] + >>> pprint(merge_explicit(X)) + [4 6] + A*[ ] + [4 6] + + >>> X = MatMul(B, A, C) + >>> pprint(X) + [1 1] [1 2] + [ ]*A*[ ] + [1 1] [3 4] + >>> pprint(merge_explicit(X)) + [1 1] [1 2] + [ ]*A*[ ] + [1 1] [3 4] + """ + if not any(isinstance(arg, MatrixBase) for arg in matmul.args): + return matmul + newargs = [] + last = matmul.args[0] + for arg in matmul.args[1:]: + if isinstance(arg, (MatrixBase, Number)) and isinstance(last, (MatrixBase, Number)): + last = last * arg + else: + newargs.append(last) + last = arg + newargs.append(last) + + return MatMul(*newargs) + +def remove_ids(mul): + """ Remove Identities from a MatMul + + This is a modified version of sympy.strategies.rm_id. + This is necessary because MatMul may contain both MatrixExprs and Exprs + as args. + + See Also + ======== + + sympy.strategies.rm_id + """ + # Separate Exprs from MatrixExprs in args + factor, mmul = mul.as_coeff_mmul() + # Apply standard rm_id for MatMuls + result = rm_id(lambda x: x.is_Identity is True)(mmul) + if result != mmul: + return newmul(factor, *result.args) # Recombine and return + else: + return mul + +def factor_in_front(mul): + factor, matrices = mul.as_coeff_matrices() + if factor != 1: + return newmul(factor, *matrices) + return mul + +def combine_powers(mul): + r"""Combine consecutive powers with the same base into one, e.g. + $$A \times A^2 \Rightarrow A^3$$ + + This also cancels out the possible matrix inverses using the + knowledgebase of :class:`~.Inverse`, e.g., + $$ Y \times X \times X^{-1} \Rightarrow Y $$ + """ + factor, args = mul.as_coeff_matrices() + new_args = [args[0]] + + for i in range(1, len(args)): + A = new_args[-1] + B = args[i] + + if isinstance(B, Inverse) and isinstance(B.arg, MatMul): + Bargs = B.arg.args + l = len(Bargs) + if list(Bargs) == new_args[-l:]: + new_args = new_args[:-l] + [Identity(B.shape[0])] + continue + + if isinstance(A, Inverse) and isinstance(A.arg, MatMul): + Aargs = A.arg.args + l = len(Aargs) + if list(Aargs) == args[i:i+l]: + identity = Identity(A.shape[0]) + new_args[-1] = identity + for j in range(i, i+l): + args[j] = identity + continue + + if A.is_square == False or B.is_square == False: + new_args.append(B) + continue + + if isinstance(A, MatPow): + A_base, A_exp = A.args + else: + A_base, A_exp = A, S.One + + if isinstance(B, MatPow): + B_base, B_exp = B.args + else: + B_base, B_exp = B, S.One + + if A_base == B_base: + new_exp = A_exp + B_exp + new_args[-1] = MatPow(A_base, new_exp).doit(deep=False) + continue + elif not isinstance(B_base, MatrixBase): + try: + B_base_inv = B_base.inverse() + except NonInvertibleMatrixError: + B_base_inv = None + if B_base_inv is not None and A_base == B_base_inv: + new_exp = A_exp - B_exp + new_args[-1] = MatPow(A_base, new_exp).doit(deep=False) + continue + new_args.append(B) + + return newmul(factor, *new_args) + +def combine_permutations(mul): + """Refine products of permutation matrices as the products of cycles. + """ + args = mul.args + l = len(args) + if l < 2: + return mul + + result = [args[0]] + for i in range(1, l): + A = result[-1] + B = args[i] + if isinstance(A, PermutationMatrix) and \ + isinstance(B, PermutationMatrix): + cycle_1 = A.args[0] + cycle_2 = B.args[0] + result[-1] = PermutationMatrix(cycle_1 * cycle_2) + else: + result.append(B) + + return MatMul(*result) + +def combine_one_matrices(mul): + """ + Combine products of OneMatrix + + e.g. OneMatrix(2, 3) * OneMatrix(3, 4) -> 3 * OneMatrix(2, 4) + """ + factor, args = mul.as_coeff_matrices() + new_args = [args[0]] + + for B in args[1:]: + A = new_args[-1] + if not isinstance(A, OneMatrix) or not isinstance(B, OneMatrix): + new_args.append(B) + continue + new_args.pop() + new_args.append(OneMatrix(A.shape[0], B.shape[1])) + factor *= A.shape[1] + + return newmul(factor, *new_args) + +def distribute_monom(mul): + """ + Simplify MatMul expressions but distributing + rational term to MatMul. + + e.g. 2*(A+B) -> 2*A + 2*B + """ + args = mul.args + if len(args) == 2: + from .matadd import MatAdd + if args[0].is_MatAdd and args[1].is_Rational: + return MatAdd(*[MatMul(mat, args[1]).doit() for mat in args[0].args]) + if args[1].is_MatAdd and args[0].is_Rational: + return MatAdd(*[MatMul(args[0], mat).doit() for mat in args[1].args]) + return mul + +rules = ( + distribute_monom, any_zeros, remove_ids, combine_one_matrices, combine_powers, unpack, rm_id(lambda x: x == 1), + merge_explicit, factor_in_front, flatten, combine_permutations) + +canonicalize = exhaust(typed({MatMul: do_one(*rules)})) + +def only_squares(*matrices): + """factor matrices only if they are square""" + if matrices[0].rows != matrices[-1].cols: + raise RuntimeError("Invalid matrices being multiplied") + out = [] + start = 0 + for i, M in enumerate(matrices): + if M.cols == matrices[start].rows: + out.append(MatMul(*matrices[start:i+1]).doit()) + start = i+1 + return out + + +def refine_MatMul(expr, assumptions): + """ + >>> from sympy import MatrixSymbol, Q, assuming, refine + >>> X = MatrixSymbol('X', 2, 2) + >>> expr = X * X.T + >>> print(expr) + X*X.T + >>> with assuming(Q.orthogonal(X)): + ... print(refine(expr)) + I + """ + newargs = [] + exprargs = [] + + for args in expr.args: + if args.is_Matrix: + exprargs.append(args) + else: + newargs.append(args) + + last = exprargs[0] + for arg in exprargs[1:]: + if arg == last.T and ask(Q.orthogonal(arg), assumptions): + last = Identity(arg.shape[0]) + elif arg == last.conjugate() and ask(Q.unitary(arg), assumptions): + last = Identity(arg.shape[0]) + else: + newargs.append(last) + last = arg + newargs.append(last) + + return MatMul(*newargs) + + +handlers_dict['MatMul'] = refine_MatMul diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matpow.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matpow.py new file mode 100644 index 0000000000000000000000000000000000000000..b6472995e134e9e5ebfd28a901480665d1531275 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/matpow.py @@ -0,0 +1,150 @@ +from .matexpr import MatrixExpr +from .special import Identity +from sympy.core import S +from sympy.core.expr import ExprBuilder +from sympy.core.cache import cacheit +from sympy.core.power import Pow +from sympy.core.sympify import _sympify +from sympy.matrices import MatrixBase +from sympy.matrices.exceptions import NonSquareMatrixError + + +class MatPow(MatrixExpr): + def __new__(cls, base, exp, evaluate=False, **options): + base = _sympify(base) + if not base.is_Matrix: + raise TypeError("MatPow base should be a matrix") + + if base.is_square is False: + raise NonSquareMatrixError("Power of non-square matrix %s" % base) + + exp = _sympify(exp) + obj = super().__new__(cls, base, exp) + + if evaluate: + obj = obj.doit(deep=False) + + return obj + + @property + def base(self): + return self.args[0] + + @property + def exp(self): + return self.args[1] + + @property + def shape(self): + return self.base.shape + + @cacheit + def _get_explicit_matrix(self): + return self.base.as_explicit()**self.exp + + def _entry(self, i, j, **kwargs): + from sympy.matrices.expressions import MatMul + A = self.doit() + if isinstance(A, MatPow): + # We still have a MatPow, make an explicit MatMul out of it. + if A.exp.is_Integer and A.exp.is_positive: + A = MatMul(*[A.base for k in range(A.exp)]) + elif not self._is_shape_symbolic(): + return A._get_explicit_matrix()[i, j] + else: + # Leave the expression unevaluated: + from sympy.matrices.expressions.matexpr import MatrixElement + return MatrixElement(self, i, j) + return A[i, j] + + def doit(self, **hints): + if hints.get('deep', True): + base, exp = (arg.doit(**hints) for arg in self.args) + else: + base, exp = self.args + + # combine all powers, e.g. (A ** 2) ** 3 -> A ** 6 + while isinstance(base, MatPow): + exp *= base.args[1] + base = base.args[0] + + if isinstance(base, MatrixBase): + # Delegate + return base ** exp + + # Handle simple cases so that _eval_power() in MatrixExpr sub-classes can ignore them + if exp == S.One: + return base + if exp == S.Zero: + return Identity(base.rows) + if exp == S.NegativeOne: + from sympy.matrices.expressions import Inverse + return Inverse(base).doit(**hints) + + eval_power = getattr(base, '_eval_power', None) + if eval_power is not None: + return eval_power(exp) + + return MatPow(base, exp) + + def _eval_transpose(self): + base, exp = self.args + return MatPow(base.transpose(), exp) + + def _eval_adjoint(self): + base, exp = self.args + return MatPow(base.adjoint(), exp) + + def _eval_conjugate(self): + base, exp = self.args + return MatPow(base.conjugate(), exp) + + def _eval_derivative(self, x): + return Pow._eval_derivative(self, x) + + def _eval_derivative_matrix_lines(self, x): + from sympy.tensor.array.expressions.array_expressions import ArrayContraction + from ...tensor.array.expressions.array_expressions import ArrayTensorProduct + from .matmul import MatMul + from .inverse import Inverse + exp = self.exp + if self.base.shape == (1, 1) and not exp.has(x): + lr = self.base._eval_derivative_matrix_lines(x) + for i in lr: + subexpr = ExprBuilder( + ArrayContraction, + [ + ExprBuilder( + ArrayTensorProduct, + [ + Identity(1), + i._lines[0], + exp*self.base**(exp-1), + i._lines[1], + Identity(1), + ] + ), + (0, 3, 4), (5, 7, 8) + ], + validator=ArrayContraction._validate + ) + i._first_pointer_parent = subexpr.args[0].args + i._first_pointer_index = 0 + i._second_pointer_parent = subexpr.args[0].args + i._second_pointer_index = 4 + i._lines = [subexpr] + return lr + if (exp > 0) == True: + newexpr = MatMul.fromiter([self.base for i in range(exp)]) + elif (exp == -1) == True: + return Inverse(self.base)._eval_derivative_matrix_lines(x) + elif (exp < 0) == True: + newexpr = MatMul.fromiter([Inverse(self.base) for i in range(-exp)]) + elif (exp == 0) == True: + return self.doit()._eval_derivative_matrix_lines(x) + else: + raise NotImplementedError("cannot evaluate %s derived by %s" % (self, x)) + return newexpr._eval_derivative_matrix_lines(x) + + def _eval_inverse(self): + return MatPow(self.base, -self.exp) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/permutation.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/permutation.py new file mode 100644 index 0000000000000000000000000000000000000000..5634fa941a53d8890583fe61bb29bc34f4e6000d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/permutation.py @@ -0,0 +1,303 @@ +from sympy.core import S +from sympy.core.sympify import _sympify +from sympy.functions import KroneckerDelta + +from .matexpr import MatrixExpr +from .special import ZeroMatrix, Identity, OneMatrix + + +class PermutationMatrix(MatrixExpr): + """A Permutation Matrix + + Parameters + ========== + + perm : Permutation + The permutation the matrix uses. + + The size of the permutation determines the matrix size. + + See the documentation of + :class:`sympy.combinatorics.permutations.Permutation` for + the further information of how to create a permutation object. + + Examples + ======== + + >>> from sympy import Matrix, PermutationMatrix + >>> from sympy.combinatorics import Permutation + + Creating a permutation matrix: + + >>> p = Permutation(1, 2, 0) + >>> P = PermutationMatrix(p) + >>> P = P.as_explicit() + >>> P + Matrix([ + [0, 1, 0], + [0, 0, 1], + [1, 0, 0]]) + + Permuting a matrix row and column: + + >>> M = Matrix([0, 1, 2]) + >>> Matrix(P*M) + Matrix([ + [1], + [2], + [0]]) + + >>> Matrix(M.T*P) + Matrix([[2, 0, 1]]) + + See Also + ======== + + sympy.combinatorics.permutations.Permutation + """ + + def __new__(cls, perm): + from sympy.combinatorics.permutations import Permutation + + perm = _sympify(perm) + if not isinstance(perm, Permutation): + raise ValueError( + "{} must be a SymPy Permutation instance.".format(perm)) + + return super().__new__(cls, perm) + + @property + def shape(self): + size = self.args[0].size + return (size, size) + + @property + def is_Identity(self): + return self.args[0].is_Identity + + def doit(self, **hints): + if self.is_Identity: + return Identity(self.rows) + return self + + def _entry(self, i, j, **kwargs): + perm = self.args[0] + return KroneckerDelta(perm.apply(i), j) + + def _eval_power(self, exp): + return PermutationMatrix(self.args[0] ** exp).doit() + + def _eval_inverse(self): + return PermutationMatrix(self.args[0] ** -1) + + _eval_transpose = _eval_adjoint = _eval_inverse + + def _eval_determinant(self): + sign = self.args[0].signature() + if sign == 1: + return S.One + elif sign == -1: + return S.NegativeOne + raise NotImplementedError + + def _eval_rewrite_as_BlockDiagMatrix(self, *args, **kwargs): + from sympy.combinatorics.permutations import Permutation + from .blockmatrix import BlockDiagMatrix + + perm = self.args[0] + full_cyclic_form = perm.full_cyclic_form + + cycles_picks = [] + + # Stage 1. Decompose the cycles into the blockable form. + a, b, c = 0, 0, 0 + flag = False + for cycle in full_cyclic_form: + l = len(cycle) + m = max(cycle) + + if not flag: + if m + 1 > a + l: + flag = True + temp = [cycle] + b = m + c = l + else: + cycles_picks.append([cycle]) + a += l + + else: + if m > b: + if m + 1 == a + c + l: + temp.append(cycle) + cycles_picks.append(temp) + flag = False + a = m+1 + else: + b = m + temp.append(cycle) + c += l + else: + if b + 1 == a + c + l: + temp.append(cycle) + cycles_picks.append(temp) + flag = False + a = b+1 + else: + temp.append(cycle) + c += l + + # Stage 2. Normalize each decomposed cycles and build matrix. + p = 0 + args = [] + for pick in cycles_picks: + new_cycles = [] + l = 0 + for cycle in pick: + new_cycle = [i - p for i in cycle] + new_cycles.append(new_cycle) + l += len(cycle) + p += l + perm = Permutation(new_cycles) + mat = PermutationMatrix(perm) + args.append(mat) + + return BlockDiagMatrix(*args) + + +class MatrixPermute(MatrixExpr): + r"""Symbolic representation for permuting matrix rows or columns. + + Parameters + ========== + + perm : Permutation, PermutationMatrix + The permutation to use for permuting the matrix. + The permutation can be resized to the suitable one, + + axis : 0 or 1 + The axis to permute alongside. + If `0`, it will permute the matrix rows. + If `1`, it will permute the matrix columns. + + Notes + ===== + + This follows the same notation used in + :meth:`sympy.matrices.matrixbase.MatrixBase.permute`. + + Examples + ======== + + >>> from sympy import Matrix, MatrixPermute + >>> from sympy.combinatorics import Permutation + + Permuting the matrix rows: + + >>> p = Permutation(1, 2, 0) + >>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> B = MatrixPermute(A, p, axis=0) + >>> B.as_explicit() + Matrix([ + [4, 5, 6], + [7, 8, 9], + [1, 2, 3]]) + + Permuting the matrix columns: + + >>> B = MatrixPermute(A, p, axis=1) + >>> B.as_explicit() + Matrix([ + [2, 3, 1], + [5, 6, 4], + [8, 9, 7]]) + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.permute + """ + def __new__(cls, mat, perm, axis=S.Zero): + from sympy.combinatorics.permutations import Permutation + + mat = _sympify(mat) + if not mat.is_Matrix: + raise ValueError( + "{} must be a SymPy matrix instance.".format(perm)) + + perm = _sympify(perm) + if isinstance(perm, PermutationMatrix): + perm = perm.args[0] + + if not isinstance(perm, Permutation): + raise ValueError( + "{} must be a SymPy Permutation or a PermutationMatrix " \ + "instance".format(perm)) + + axis = _sympify(axis) + if axis not in (0, 1): + raise ValueError("The axis must be 0 or 1.") + + mat_size = mat.shape[axis] + if mat_size != perm.size: + try: + perm = perm.resize(mat_size) + except ValueError: + raise ValueError( + "Size does not match between the permutation {} " + "and the matrix {} threaded over the axis {} " + "and cannot be converted." + .format(perm, mat, axis)) + + return super().__new__(cls, mat, perm, axis) + + def doit(self, deep=True, **hints): + mat, perm, axis = self.args + + if deep: + mat = mat.doit(deep=deep, **hints) + perm = perm.doit(deep=deep, **hints) + + if perm.is_Identity: + return mat + + if mat.is_Identity: + if axis is S.Zero: + return PermutationMatrix(perm) + elif axis is S.One: + return PermutationMatrix(perm**-1) + + if isinstance(mat, (ZeroMatrix, OneMatrix)): + return mat + + if isinstance(mat, MatrixPermute) and mat.args[2] == axis: + return MatrixPermute(mat.args[0], perm * mat.args[1], axis) + + return self + + @property + def shape(self): + return self.args[0].shape + + def _entry(self, i, j, **kwargs): + mat, perm, axis = self.args + + if axis == 0: + return mat[perm.apply(i), j] + elif axis == 1: + return mat[i, perm.apply(j)] + + def _eval_rewrite_as_MatMul(self, *args, **kwargs): + from .matmul import MatMul + + mat, perm, axis = self.args + + deep = kwargs.get("deep", True) + + if deep: + mat = mat.rewrite(MatMul) + + if axis == 0: + return MatMul(PermutationMatrix(perm), mat) + elif axis == 1: + return MatMul(mat, PermutationMatrix(perm**-1)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/sets.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/sets.py new file mode 100644 index 0000000000000000000000000000000000000000..ab4930ea8f1b058977a8dd1abdc62f1f5e2195c1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/sets.py @@ -0,0 +1,68 @@ +from sympy.core.assumptions import check_assumptions +from sympy.core.logic import fuzzy_and +from sympy.core.sympify import _sympify +from sympy.matrices.kind import MatrixKind +from sympy.sets.sets import Set, SetKind +from sympy.core.kind import NumberKind +from .matexpr import MatrixExpr + + +class MatrixSet(Set): + """ + MatrixSet represents the set of matrices with ``shape = (n, m)`` over the + given set. + + Examples + ======== + + >>> from sympy.matrices import MatrixSet + >>> from sympy import S, I, Matrix + >>> M = MatrixSet(2, 2, set=S.Reals) + >>> X = Matrix([[1, 2], [3, 4]]) + >>> X in M + True + >>> X = Matrix([[1, 2], [I, 4]]) + >>> X in M + False + + """ + is_empty = False + + def __new__(cls, n, m, set): + n, m, set = _sympify(n), _sympify(m), _sympify(set) + cls._check_dim(n) + cls._check_dim(m) + if not isinstance(set, Set): + raise TypeError("{} should be an instance of Set.".format(set)) + return Set.__new__(cls, n, m, set) + + @property + def shape(self): + return self.args[:2] + + @property + def set(self): + return self.args[2] + + def _contains(self, other): + if not isinstance(other, MatrixExpr): + raise TypeError("{} should be an instance of MatrixExpr.".format(other)) + if other.shape != self.shape: + are_symbolic = any(_sympify(x).is_Symbol for x in other.shape + self.shape) + if are_symbolic: + return None + return False + return fuzzy_and(self.set.contains(x) for x in other) + + @classmethod + def _check_dim(cls, dim): + """Helper function to check invalid matrix dimensions""" + ok = not dim.is_Float and check_assumptions( + dim, integer=True, nonnegative=True) + if ok is False: + raise ValueError( + "The dimension specification {} should be " + "a nonnegative integer.".format(dim)) + + def _kind(self): + return SetKind(MatrixKind(NumberKind)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/slice.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/slice.py new file mode 100644 index 0000000000000000000000000000000000000000..1904b49f29c503fb4c0c909532f8342fb0f4b135 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/slice.py @@ -0,0 +1,114 @@ +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.functions.elementary.integers import floor + +def normalize(i, parentsize): + if isinstance(i, slice): + i = (i.start, i.stop, i.step) + if not isinstance(i, (tuple, list, Tuple)): + if (i < 0) == True: + i += parentsize + i = (i, i+1, 1) + i = list(i) + if len(i) == 2: + i.append(1) + start, stop, step = i + start = start or 0 + if stop is None: + stop = parentsize + if (start < 0) == True: + start += parentsize + if (stop < 0) == True: + stop += parentsize + step = step or 1 + + if ((stop - start) * step < 1) == True: + raise IndexError() + + return (start, stop, step) + +class MatrixSlice(MatrixExpr): + """ A MatrixSlice of a Matrix Expression + + Examples + ======== + + >>> from sympy import MatrixSlice, ImmutableMatrix + >>> M = ImmutableMatrix(4, 4, range(16)) + >>> M + Matrix([ + [ 0, 1, 2, 3], + [ 4, 5, 6, 7], + [ 8, 9, 10, 11], + [12, 13, 14, 15]]) + + >>> B = MatrixSlice(M, (0, 2), (2, 4)) + >>> ImmutableMatrix(B) + Matrix([ + [2, 3], + [6, 7]]) + """ + parent = property(lambda self: self.args[0]) + rowslice = property(lambda self: self.args[1]) + colslice = property(lambda self: self.args[2]) + + def __new__(cls, parent, rowslice, colslice): + rowslice = normalize(rowslice, parent.shape[0]) + colslice = normalize(colslice, parent.shape[1]) + if not (len(rowslice) == len(colslice) == 3): + raise IndexError() + if ((0 > rowslice[0]) == True or + (parent.shape[0] < rowslice[1]) == True or + (0 > colslice[0]) == True or + (parent.shape[1] < colslice[1]) == True): + raise IndexError() + if isinstance(parent, MatrixSlice): + return mat_slice_of_slice(parent, rowslice, colslice) + return Basic.__new__(cls, parent, Tuple(*rowslice), Tuple(*colslice)) + + @property + def shape(self): + rows = self.rowslice[1] - self.rowslice[0] + rows = rows if self.rowslice[2] == 1 else floor(rows/self.rowslice[2]) + cols = self.colslice[1] - self.colslice[0] + cols = cols if self.colslice[2] == 1 else floor(cols/self.colslice[2]) + return rows, cols + + def _entry(self, i, j, **kwargs): + return self.parent._entry(i*self.rowslice[2] + self.rowslice[0], + j*self.colslice[2] + self.colslice[0], + **kwargs) + + @property + def on_diag(self): + return self.rowslice == self.colslice + + +def slice_of_slice(s, t): + start1, stop1, step1 = s + start2, stop2, step2 = t + + start = start1 + start2*step1 + step = step1 * step2 + stop = start1 + step1*stop2 + + if stop > stop1: + raise IndexError() + + return start, stop, step + + +def mat_slice_of_slice(parent, rowslice, colslice): + """ Collapse nested matrix slices + + >>> from sympy import MatrixSymbol + >>> X = MatrixSymbol('X', 10, 10) + >>> X[:, 1:5][5:8, :] + X[5:8, 1:5] + >>> X[1:9:2, 2:6][1:3, 2] + X[3:7:2, 4:5] + """ + row = slice_of_slice(parent.rowslice, rowslice) + col = slice_of_slice(parent.colslice, colslice) + return MatrixSlice(parent.parent, row, col) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/special.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/special.py new file mode 100644 index 0000000000000000000000000000000000000000..d1e426f16ada0e4245b644867974b41b6f86b5cc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/special.py @@ -0,0 +1,299 @@ +from sympy.assumptions.ask import ask, Q +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.sympify import _sympify +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.matrices.exceptions import NonInvertibleMatrixError +from .matexpr import MatrixExpr + + +class ZeroMatrix(MatrixExpr): + """The Matrix Zero 0 - additive identity + + Examples + ======== + + >>> from sympy import MatrixSymbol, ZeroMatrix + >>> A = MatrixSymbol('A', 3, 5) + >>> Z = ZeroMatrix(3, 5) + >>> A + Z + A + >>> Z*A.T + 0 + """ + is_ZeroMatrix = True + + def __new__(cls, m, n): + m, n = _sympify(m), _sympify(n) + cls._check_dim(m) + cls._check_dim(n) + + return super().__new__(cls, m, n) + + @property + def shape(self): + return (self.args[0], self.args[1]) + + def _eval_power(self, exp): + # exp = -1, 0, 1 are already handled at this stage + if (exp < 0) == True: + raise NonInvertibleMatrixError("Matrix det == 0; not invertible") + return self + + def _eval_transpose(self): + return ZeroMatrix(self.cols, self.rows) + + def _eval_adjoint(self): + return ZeroMatrix(self.cols, self.rows) + + def _eval_trace(self): + return S.Zero + + def _eval_determinant(self): + return S.Zero + + def _eval_inverse(self): + raise NonInvertibleMatrixError("Matrix det == 0; not invertible.") + + def _eval_as_real_imag(self): + return (self, self) + + def _eval_conjugate(self): + return self + + def _entry(self, i, j, **kwargs): + return S.Zero + + +class GenericZeroMatrix(ZeroMatrix): + """ + A zero matrix without a specified shape + + This exists primarily so MatAdd() with no arguments can return something + meaningful. + """ + def __new__(cls): + # super(ZeroMatrix, cls) instead of super(GenericZeroMatrix, cls) + # because ZeroMatrix.__new__ doesn't have the same signature + return super(ZeroMatrix, cls).__new__(cls) + + @property + def rows(self): + raise TypeError("GenericZeroMatrix does not have a specified shape") + + @property + def cols(self): + raise TypeError("GenericZeroMatrix does not have a specified shape") + + @property + def shape(self): + raise TypeError("GenericZeroMatrix does not have a specified shape") + + # Avoid Matrix.__eq__ which might call .shape + def __eq__(self, other): + return isinstance(other, GenericZeroMatrix) + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return super().__hash__() + + + +class Identity(MatrixExpr): + """The Matrix Identity I - multiplicative identity + + Examples + ======== + + >>> from sympy import Identity, MatrixSymbol + >>> A = MatrixSymbol('A', 3, 5) + >>> I = Identity(3) + >>> I*A + A + """ + + is_Identity = True + + def __new__(cls, n): + n = _sympify(n) + cls._check_dim(n) + + return super().__new__(cls, n) + + @property + def rows(self): + return self.args[0] + + @property + def cols(self): + return self.args[0] + + @property + def shape(self): + return (self.args[0], self.args[0]) + + @property + def is_square(self): + return True + + def _eval_transpose(self): + return self + + def _eval_trace(self): + return self.rows + + def _eval_inverse(self): + return self + + def _eval_as_real_imag(self): + return (self, ZeroMatrix(*self.shape)) + + def _eval_conjugate(self): + return self + + def _eval_adjoint(self): + return self + + def _entry(self, i, j, **kwargs): + eq = Eq(i, j) + if eq is S.true: + return S.One + elif eq is S.false: + return S.Zero + return KroneckerDelta(i, j, (0, self.cols-1)) + + def _eval_determinant(self): + return S.One + + def _eval_power(self, exp): + return self + + +class GenericIdentity(Identity): + """ + An identity matrix without a specified shape + + This exists primarily so MatMul() with no arguments can return something + meaningful. + """ + def __new__(cls): + # super(Identity, cls) instead of super(GenericIdentity, cls) because + # Identity.__new__ doesn't have the same signature + return super(Identity, cls).__new__(cls) + + @property + def rows(self): + raise TypeError("GenericIdentity does not have a specified shape") + + @property + def cols(self): + raise TypeError("GenericIdentity does not have a specified shape") + + @property + def shape(self): + raise TypeError("GenericIdentity does not have a specified shape") + + @property + def is_square(self): + return True + + # Avoid Matrix.__eq__ which might call .shape + def __eq__(self, other): + return isinstance(other, GenericIdentity) + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return super().__hash__() + + +class OneMatrix(MatrixExpr): + """ + Matrix whose all entries are ones. + """ + def __new__(cls, m, n, evaluate=False): + m, n = _sympify(m), _sympify(n) + cls._check_dim(m) + cls._check_dim(n) + + if evaluate: + condition = Eq(m, 1) & Eq(n, 1) + if condition == True: + return Identity(1) + + obj = super().__new__(cls, m, n) + return obj + + @property + def shape(self): + return self._args + + @property + def is_Identity(self): + return self._is_1x1() == True + + def as_explicit(self): + from sympy.matrices.immutable import ImmutableDenseMatrix + return ImmutableDenseMatrix.ones(*self.shape) + + def doit(self, **hints): + args = self.args + if hints.get('deep', True): + args = [a.doit(**hints) for a in args] + return self.func(*args, evaluate=True) + + def _eval_power(self, exp): + # exp = -1, 0, 1 are already handled at this stage + if self._is_1x1() == True: + return Identity(1) + if (exp < 0) == True: + raise NonInvertibleMatrixError("Matrix det == 0; not invertible") + if ask(Q.integer(exp)): + return self.shape[0] ** (exp - 1) * OneMatrix(*self.shape) + return super()._eval_power(exp) + + def _eval_transpose(self): + return OneMatrix(self.cols, self.rows) + + def _eval_adjoint(self): + return OneMatrix(self.cols, self.rows) + + def _eval_trace(self): + return S.One*self.rows + + def _is_1x1(self): + """Returns true if the matrix is known to be 1x1""" + shape = self.shape + return Eq(shape[0], 1) & Eq(shape[1], 1) + + def _eval_determinant(self): + condition = self._is_1x1() + if condition == True: + return S.One + elif condition == False: + return S.Zero + else: + from sympy.matrices.expressions.determinant import Determinant + return Determinant(self) + + def _eval_inverse(self): + condition = self._is_1x1() + if condition == True: + return Identity(1) + elif condition == False: + raise NonInvertibleMatrixError("Matrix det == 0; not invertible.") + else: + from .inverse import Inverse + return Inverse(self) + + def _eval_as_real_imag(self): + return (self, ZeroMatrix(*self.shape)) + + def _eval_conjugate(self): + return self + + def _entry(self, i, j, **kwargs): + return S.One diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd3446a5b4d7c621a09322fe70ce8f845e21a9c2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_adjoint.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_adjoint.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25099554cbf257c3eb481439c67ea0051e7c0ca2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_adjoint.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_applyfunc.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_applyfunc.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1075f72e0aa99cb2303c0f4d9f35eec234ef8ba0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_applyfunc.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_blockmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_blockmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..973647bb1ff4d4da3acbe5786c85401792a091af Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_blockmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_companion.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_companion.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10969a5d80e152c7fb66c93bf786f435d6fc341c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_companion.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_derivatives.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_derivatives.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a660c6d5292d86f9b108c45f3eef81c653de40a8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_derivatives.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_determinant.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_determinant.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..536cc22470edcf6fce673a14d308c903614df389 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_determinant.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_diagonal.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_diagonal.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e87288be95a69412da78568435d4fffcb29bbd4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_diagonal.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_dotproduct.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_dotproduct.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abc1ca4d0ed7bb263f93cd8b396294384b3c9af3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_dotproduct.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_factorizations.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_factorizations.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ec2b0edcf1117956e4cfbdf7ba23b224c570057 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_factorizations.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_fourier.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_fourier.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ba6ca99fb5f1e9ab8c33ce2bb39ce0c4f69f945 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_fourier.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_funcmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_funcmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..021290e6d5bb423167d884320d9374471ec1d42c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_funcmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_hadamard.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_hadamard.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..661181d1dc1b84272cc238077e09b2901dcb8776 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_hadamard.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_indexing.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_indexing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25ffc732537a4601e8e0cf44892767f993b3cd4a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_indexing.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_inverse.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_inverse.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ae58eea850d9a4ddf4d7a64ae9444273d652e07 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_inverse.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_kronecker.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_kronecker.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a58786574054d07e4ee04106f32d5de68414f10 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_kronecker.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matadd.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matadd.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab4480a0c6fef87a01f035dbf28e1d7cfaa7b2cd Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matadd.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matexpr.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matexpr.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc421531fb8eefd2b382a5dd8fc1b898aa0df513 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matexpr.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matmul.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matmul.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9f25c461072af1a66ef3222275ad904a4e144ac Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matmul.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matpow.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matpow.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54b3cc82f77190e46ff0a3de16cb8eacedfe016b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_matpow.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_permutation.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_permutation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55a123630552e9b69d8fed6f4cec2227c6a2af4f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_permutation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_sets.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_sets.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec2c38f641e2239fcf1fc6adfc63f0edb5a1b825 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_sets.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_slice.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_slice.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68ed77733bd285495612e820d8561bca817466b0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_slice.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_special.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_special.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ef849488db858f0a0dcfe5fe978c4b746bcaab2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_special.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_trace.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_trace.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7616948321d7b400c7054892650fa89ad89bf62 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_trace.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_transpose.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_transpose.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9a03bf871ad7511b22558a040135e65784a6b3b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/__pycache__/test_transpose.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_adjoint.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_adjoint.py new file mode 100644 index 0000000000000000000000000000000000000000..7106b5740b1dc7c32f2c6f5ecb9d286b5e1dd222 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_adjoint.py @@ -0,0 +1,34 @@ +from sympy.core import symbols, S +from sympy.functions import adjoint, conjugate, transpose +from sympy.matrices.expressions import MatrixSymbol, Adjoint, trace, Transpose +from sympy.matrices import eye, Matrix + +n, m, l, k, p = symbols('n m l k p', integer=True) +A = MatrixSymbol('A', n, m) +B = MatrixSymbol('B', m, l) +C = MatrixSymbol('C', n, n) + + +def test_adjoint(): + Sq = MatrixSymbol('Sq', n, n) + + assert Adjoint(A).shape == (m, n) + assert Adjoint(A*B).shape == (l, n) + assert adjoint(Adjoint(A)) == A + assert isinstance(Adjoint(Adjoint(A)), Adjoint) + + assert conjugate(Adjoint(A)) == Transpose(A) + assert transpose(Adjoint(A)) == Adjoint(Transpose(A)) + + assert Adjoint(eye(3)).doit() == eye(3) + + assert Adjoint(S(5)).doit() == S(5) + + assert Adjoint(Matrix([[1, 2], [3, 4]])).doit() == Matrix([[1, 3], [2, 4]]) + + assert adjoint(trace(Sq)) == conjugate(trace(Sq)) + assert trace(adjoint(Sq)) == conjugate(trace(Sq)) + + assert Adjoint(Sq)[0, 1] == conjugate(Sq[1, 0]) + + assert Adjoint(A*B).doit() == Adjoint(B) * Adjoint(A) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_applyfunc.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_applyfunc.py new file mode 100644 index 0000000000000000000000000000000000000000..d98732e2751e53938d96d7ea56c916e6fee4578e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_applyfunc.py @@ -0,0 +1,118 @@ +from sympy.core.symbol import symbols, Dummy +from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction +from sympy.core.function import Lambda +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.trigonometric import sin +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.matmul import MatMul +from sympy.simplify.simplify import simplify + + +X = MatrixSymbol("X", 3, 3) +Y = MatrixSymbol("Y", 3, 3) + +k = symbols("k") +Xk = MatrixSymbol("X", k, k) + +Xd = X.as_explicit() + +x, y, z, t = symbols("x y z t") + + +def test_applyfunc_matrix(): + x = Dummy('x') + double = Lambda(x, x**2) + + expr = ElementwiseApplyFunction(double, Xd) + assert isinstance(expr, ElementwiseApplyFunction) + assert expr.doit() == Xd.applyfunc(lambda x: x**2) + assert expr.shape == (3, 3) + assert expr.func(*expr.args) == expr + assert simplify(expr) == expr + assert expr[0, 0] == double(Xd[0, 0]) + + expr = ElementwiseApplyFunction(double, X) + assert isinstance(expr, ElementwiseApplyFunction) + assert isinstance(expr.doit(), ElementwiseApplyFunction) + assert expr == X.applyfunc(double) + assert expr.func(*expr.args) == expr + + expr = ElementwiseApplyFunction(exp, X*Y) + assert expr.expr == X*Y + assert expr.function.dummy_eq(Lambda(x, exp(x))) + assert expr.dummy_eq((X*Y).applyfunc(exp)) + assert expr.func(*expr.args) == expr + + assert isinstance(X*expr, MatMul) + assert (X*expr).shape == (3, 3) + Z = MatrixSymbol("Z", 2, 3) + assert (Z*expr).shape == (2, 3) + + expr = ElementwiseApplyFunction(exp, Z.T)*ElementwiseApplyFunction(exp, Z) + assert expr.shape == (3, 3) + expr = ElementwiseApplyFunction(exp, Z)*ElementwiseApplyFunction(exp, Z.T) + assert expr.shape == (2, 2) + + M = Matrix([[x, y], [z, t]]) + expr = ElementwiseApplyFunction(sin, M) + assert isinstance(expr, ElementwiseApplyFunction) + assert expr.function.dummy_eq(Lambda(x, sin(x))) + assert expr.expr == M + assert expr.doit() == M.applyfunc(sin) + assert expr.doit() == Matrix([[sin(x), sin(y)], [sin(z), sin(t)]]) + assert expr.func(*expr.args) == expr + + expr = ElementwiseApplyFunction(double, Xk) + assert expr.doit() == expr + assert expr.subs(k, 2).shape == (2, 2) + assert (expr*expr).shape == (k, k) + M = MatrixSymbol("M", k, t) + expr2 = M.T*expr*M + assert isinstance(expr2, MatMul) + assert expr2.args[1] == expr + assert expr2.shape == (t, t) + expr3 = expr*M + assert expr3.shape == (k, t) + + expr1 = ElementwiseApplyFunction(lambda x: x+1, Xk) + expr2 = ElementwiseApplyFunction(lambda x: x, Xk) + assert expr1 != expr2 + + +def test_applyfunc_entry(): + + af = X.applyfunc(sin) + assert af[0, 0] == sin(X[0, 0]) + + af = Xd.applyfunc(sin) + assert af[0, 0] == sin(X[0, 0]) + + +def test_applyfunc_as_explicit(): + + af = X.applyfunc(sin) + assert af.as_explicit() == Matrix([ + [sin(X[0, 0]), sin(X[0, 1]), sin(X[0, 2])], + [sin(X[1, 0]), sin(X[1, 1]), sin(X[1, 2])], + [sin(X[2, 0]), sin(X[2, 1]), sin(X[2, 2])], + ]) + + +def test_applyfunc_transpose(): + + af = Xk.applyfunc(sin) + assert af.T.dummy_eq(Xk.T.applyfunc(sin)) + + +def test_applyfunc_shape_11_matrices(): + M = MatrixSymbol("M", 1, 1) + + double = Lambda(x, x*2) + + expr = M.applyfunc(sin) + assert isinstance(expr, ElementwiseApplyFunction) + + expr = M.applyfunc(double) + assert isinstance(expr, MatMul) + assert expr == 2*M diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_blockmatrix.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_blockmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..1d4893cd9a4b3e47dd8e84db33031f7f6f3201fd --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_blockmatrix.py @@ -0,0 +1,469 @@ +from sympy.matrices.expressions.trace import Trace +from sympy.testing.pytest import raises, slow +from sympy.matrices.expressions.blockmatrix import ( + block_collapse, bc_matmul, bc_block_plus_ident, BlockDiagMatrix, + BlockMatrix, bc_dist, bc_matadd, bc_transpose, bc_inverse, + blockcut, reblock_2x2, deblock) +from sympy.matrices.expressions import ( + MatrixSymbol, Identity, trace, det, ZeroMatrix, OneMatrix) +from sympy.matrices.expressions.inverse import Inverse +from sympy.matrices.expressions.matpow import MatPow +from sympy.matrices.expressions.transpose import Transpose +from sympy.matrices.exceptions import NonInvertibleMatrixError +from sympy.matrices import ( + Matrix, ImmutableMatrix, ImmutableSparseMatrix, zeros) +from sympy.core import Tuple, Expr, S, Function +from sympy.core.symbol import Symbol, symbols +from sympy.functions import transpose, im, re + +i, j, k, l, m, n, p = symbols('i:n, p', integer=True) +A = MatrixSymbol('A', n, n) +B = MatrixSymbol('B', n, n) +C = MatrixSymbol('C', n, n) +D = MatrixSymbol('D', n, n) +G = MatrixSymbol('G', n, n) +H = MatrixSymbol('H', n, n) +b1 = BlockMatrix([[G, H]]) +b2 = BlockMatrix([[G], [H]]) + +def test_bc_matmul(): + assert bc_matmul(H*b1*b2*G) == BlockMatrix([[(H*G*G + H*H*H)*G]]) + +def test_bc_matadd(): + assert bc_matadd(BlockMatrix([[G, H]]) + BlockMatrix([[H, H]])) == \ + BlockMatrix([[G+H, H+H]]) + +def test_bc_transpose(): + assert bc_transpose(Transpose(BlockMatrix([[A, B], [C, D]]))) == \ + BlockMatrix([[A.T, C.T], [B.T, D.T]]) + +def test_bc_dist_diag(): + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', m, m) + C = MatrixSymbol('C', l, l) + X = BlockDiagMatrix(A, B, C) + + assert bc_dist(X+X).equals(BlockDiagMatrix(2*A, 2*B, 2*C)) + +def test_block_plus_ident(): + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', n, m) + C = MatrixSymbol('C', m, n) + D = MatrixSymbol('D', m, m) + X = BlockMatrix([[A, B], [C, D]]) + Z = MatrixSymbol('Z', n + m, n + m) + assert bc_block_plus_ident(X + Identity(m + n) + Z) == \ + BlockDiagMatrix(Identity(n), Identity(m)) + X + Z + +def test_BlockMatrix(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', n, k) + C = MatrixSymbol('C', l, m) + D = MatrixSymbol('D', l, k) + M = MatrixSymbol('M', m + k, p) + N = MatrixSymbol('N', l + n, k + m) + X = BlockMatrix(Matrix([[A, B], [C, D]])) + + assert X.__class__(*X.args) == X + + # block_collapse does nothing on normal inputs + E = MatrixSymbol('E', n, m) + assert block_collapse(A + 2*E) == A + 2*E + F = MatrixSymbol('F', m, m) + assert block_collapse(E.T*A*F) == E.T*A*F + + assert X.shape == (l + n, k + m) + assert X.blockshape == (2, 2) + assert transpose(X) == BlockMatrix(Matrix([[A.T, C.T], [B.T, D.T]])) + assert transpose(X).shape == X.shape[::-1] + + # Test that BlockMatrices and MatrixSymbols can still mix + assert (X*M).is_MatMul + assert X._blockmul(M).is_MatMul + assert (X*M).shape == (n + l, p) + assert (X + N).is_MatAdd + assert X._blockadd(N).is_MatAdd + assert (X + N).shape == X.shape + + E = MatrixSymbol('E', m, 1) + F = MatrixSymbol('F', k, 1) + + Y = BlockMatrix(Matrix([[E], [F]])) + + assert (X*Y).shape == (l + n, 1) + assert block_collapse(X*Y).blocks[0, 0] == A*E + B*F + assert block_collapse(X*Y).blocks[1, 0] == C*E + D*F + + # block_collapse passes down into container objects, transposes, and inverse + assert block_collapse(transpose(X*Y)) == transpose(block_collapse(X*Y)) + assert block_collapse(Tuple(X*Y, 2*X)) == ( + block_collapse(X*Y), block_collapse(2*X)) + + # Make sure that MatrixSymbols will enter 1x1 BlockMatrix if it simplifies + Ab = BlockMatrix([[A]]) + Z = MatrixSymbol('Z', *A.shape) + assert block_collapse(Ab + Z) == A + Z + +def test_block_collapse_explicit_matrices(): + A = Matrix([[1, 2], [3, 4]]) + assert block_collapse(BlockMatrix([[A]])) == A + + A = ImmutableSparseMatrix([[1, 2], [3, 4]]) + assert block_collapse(BlockMatrix([[A]])) == A + +def test_issue_17624(): + a = MatrixSymbol("a", 2, 2) + z = ZeroMatrix(2, 2) + b = BlockMatrix([[a, z], [z, z]]) + assert block_collapse(b * b) == BlockMatrix([[a**2, z], [z, z]]) + assert block_collapse(b * b * b) == BlockMatrix([[a**3, z], [z, z]]) + +def test_issue_18618(): + A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert A == Matrix(BlockDiagMatrix(A)) + +def test_BlockMatrix_trace(): + A, B, C, D = [MatrixSymbol(s, 3, 3) for s in 'ABCD'] + X = BlockMatrix([[A, B], [C, D]]) + assert trace(X) == trace(A) + trace(D) + assert trace(BlockMatrix([ZeroMatrix(n, n)])) == 0 + +def test_BlockMatrix_Determinant(): + A, B, C, D = [MatrixSymbol(s, 3, 3) for s in 'ABCD'] + X = BlockMatrix([[A, B], [C, D]]) + from sympy.assumptions.ask import Q + from sympy.assumptions.assume import assuming + with assuming(Q.invertible(A)): + assert det(X) == det(A) * det(X.schur('A')) + + assert isinstance(det(X), Expr) + assert det(BlockMatrix([A])) == det(A) + assert det(BlockMatrix([ZeroMatrix(n, n)])) == 0 + +def test_squareBlockMatrix(): + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', n, m) + C = MatrixSymbol('C', m, n) + D = MatrixSymbol('D', m, m) + X = BlockMatrix([[A, B], [C, D]]) + Y = BlockMatrix([[A]]) + + assert X.is_square + + Q = X + Identity(m + n) + assert (block_collapse(Q) == + BlockMatrix([[A + Identity(n), B], [C, D + Identity(m)]])) + + assert (X + MatrixSymbol('Q', n + m, n + m)).is_MatAdd + assert (X * MatrixSymbol('Q', n + m, n + m)).is_MatMul + + assert block_collapse(Y.I) == A.I + + assert isinstance(X.inverse(), Inverse) + + assert not X.is_Identity + + Z = BlockMatrix([[Identity(n), B], [C, D]]) + assert not Z.is_Identity + + +def test_BlockMatrix_2x2_inverse_symbolic(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', n, k - m) + C = MatrixSymbol('C', k - n, m) + D = MatrixSymbol('D', k - n, k - m) + X = BlockMatrix([[A, B], [C, D]]) + assert X.is_square and X.shape == (k, k) + assert isinstance(block_collapse(X.I), Inverse) # Can't invert when none of the blocks is square + + # test code path where only A is invertible + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', n, m) + C = MatrixSymbol('C', m, n) + D = ZeroMatrix(m, m) + X = BlockMatrix([[A, B], [C, D]]) + assert block_collapse(X.inverse()) == BlockMatrix([ + [A.I + A.I * B * X.schur('A').I * C * A.I, -A.I * B * X.schur('A').I], + [-X.schur('A').I * C * A.I, X.schur('A').I], + ]) + + # test code path where only B is invertible + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', n, n) + C = ZeroMatrix(m, m) + D = MatrixSymbol('D', m, n) + X = BlockMatrix([[A, B], [C, D]]) + assert block_collapse(X.inverse()) == BlockMatrix([ + [-X.schur('B').I * D * B.I, X.schur('B').I], + [B.I + B.I * A * X.schur('B').I * D * B.I, -B.I * A * X.schur('B').I], + ]) + + # test code path where only C is invertible + A = MatrixSymbol('A', n, m) + B = ZeroMatrix(n, n) + C = MatrixSymbol('C', m, m) + D = MatrixSymbol('D', m, n) + X = BlockMatrix([[A, B], [C, D]]) + assert block_collapse(X.inverse()) == BlockMatrix([ + [-C.I * D * X.schur('C').I, C.I + C.I * D * X.schur('C').I * A * C.I], + [X.schur('C').I, -X.schur('C').I * A * C.I], + ]) + + # test code path where only D is invertible + A = ZeroMatrix(n, n) + B = MatrixSymbol('B', n, m) + C = MatrixSymbol('C', m, n) + D = MatrixSymbol('D', m, m) + X = BlockMatrix([[A, B], [C, D]]) + assert block_collapse(X.inverse()) == BlockMatrix([ + [X.schur('D').I, -X.schur('D').I * B * D.I], + [-D.I * C * X.schur('D').I, D.I + D.I * C * X.schur('D').I * B * D.I], + ]) + + +def test_BlockMatrix_2x2_inverse_numeric(): + """Test 2x2 block matrix inversion numerically for all 4 formulas""" + M = Matrix([[1, 2], [3, 4]]) + # rank deficient matrices that have full rank when two of them combined + D1 = Matrix([[1, 2], [2, 4]]) + D2 = Matrix([[1, 3], [3, 9]]) + D3 = Matrix([[1, 4], [4, 16]]) + assert D1.rank() == D2.rank() == D3.rank() == 1 + assert (D1 + D2).rank() == (D2 + D3).rank() == (D3 + D1).rank() == 2 + + # Only A is invertible + K = BlockMatrix([[M, D1], [D2, D3]]) + assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv() + # Only B is invertible + K = BlockMatrix([[D1, M], [D2, D3]]) + assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv() + # Only C is invertible + K = BlockMatrix([[D1, D2], [M, D3]]) + assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv() + # Only D is invertible + K = BlockMatrix([[D1, D2], [D3, M]]) + assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv() + + +@slow +def test_BlockMatrix_3x3_symbolic(): + # Only test one of these, instead of all permutations, because it's slow + rowblocksizes = (n, m, k) + colblocksizes = (m, k, n) + K = BlockMatrix([ + [MatrixSymbol('M%s%s' % (rows, cols), rows, cols) for cols in colblocksizes] + for rows in rowblocksizes + ]) + collapse = block_collapse(K.I) + assert isinstance(collapse, BlockMatrix) + + +def test_BlockDiagMatrix(): + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', m, m) + C = MatrixSymbol('C', l, l) + M = MatrixSymbol('M', n + m + l, n + m + l) + + X = BlockDiagMatrix(A, B, C) + Y = BlockDiagMatrix(A, 2*B, 3*C) + + assert X.blocks[1, 1] == B + assert X.shape == (n + m + l, n + m + l) + assert all(X.blocks[i, j].is_ZeroMatrix if i != j else X.blocks[i, j] in [A, B, C] + for i in range(3) for j in range(3)) + assert X.__class__(*X.args) == X + assert X.get_diag_blocks() == (A, B, C) + + assert isinstance(block_collapse(X.I * X), Identity) + + assert bc_matmul(X*X) == BlockDiagMatrix(A*A, B*B, C*C) + assert block_collapse(X*X) == BlockDiagMatrix(A*A, B*B, C*C) + #XXX: should be == ?? + assert block_collapse(X + X).equals(BlockDiagMatrix(2*A, 2*B, 2*C)) + assert block_collapse(X*Y) == BlockDiagMatrix(A*A, 2*B*B, 3*C*C) + assert block_collapse(X + Y) == BlockDiagMatrix(2*A, 3*B, 4*C) + + # Ensure that BlockDiagMatrices can still interact with normal MatrixExprs + assert (X*(2*M)).is_MatMul + assert (X + (2*M)).is_MatAdd + + assert (X._blockmul(M)).is_MatMul + assert (X._blockadd(M)).is_MatAdd + +def test_BlockDiagMatrix_nonsquare(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', k, l) + X = BlockDiagMatrix(A, B) + assert X.shape == (n + k, m + l) + assert X.shape == (n + k, m + l) + assert X.rowblocksizes == [n, k] + assert X.colblocksizes == [m, l] + C = MatrixSymbol('C', n, m) + D = MatrixSymbol('D', k, l) + Y = BlockDiagMatrix(C, D) + assert block_collapse(X + Y) == BlockDiagMatrix(A + C, B + D) + assert block_collapse(X * Y.T) == BlockDiagMatrix(A * C.T, B * D.T) + raises(NonInvertibleMatrixError, lambda: BlockDiagMatrix(A, C.T).inverse()) + +def test_BlockDiagMatrix_determinant(): + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', m, m) + assert det(BlockDiagMatrix()) == 1 + assert det(BlockDiagMatrix(A)) == det(A) + assert det(BlockDiagMatrix(A, B)) == det(A) * det(B) + + # non-square blocks + C = MatrixSymbol('C', m, n) + D = MatrixSymbol('D', n, m) + assert det(BlockDiagMatrix(C, D)) == 0 + +def test_BlockDiagMatrix_trace(): + assert trace(BlockDiagMatrix()) == 0 + assert trace(BlockDiagMatrix(ZeroMatrix(n, n))) == 0 + A = MatrixSymbol('A', n, n) + assert trace(BlockDiagMatrix(A)) == trace(A) + B = MatrixSymbol('B', m, m) + assert trace(BlockDiagMatrix(A, B)) == trace(A) + trace(B) + + # non-square blocks + C = MatrixSymbol('C', m, n) + D = MatrixSymbol('D', n, m) + assert isinstance(trace(BlockDiagMatrix(C, D)), Trace) + +def test_BlockDiagMatrix_transpose(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', k, l) + assert transpose(BlockDiagMatrix()) == BlockDiagMatrix() + assert transpose(BlockDiagMatrix(A)) == BlockDiagMatrix(A.T) + assert transpose(BlockDiagMatrix(A, B)) == BlockDiagMatrix(A.T, B.T) + +def test_issue_2460(): + bdm1 = BlockDiagMatrix(Matrix([i]), Matrix([j])) + bdm2 = BlockDiagMatrix(Matrix([k]), Matrix([l])) + assert block_collapse(bdm1 + bdm2) == BlockDiagMatrix(Matrix([i + k]), Matrix([j + l])) + +def test_blockcut(): + A = MatrixSymbol('A', n, m) + B = blockcut(A, (n/2, n/2), (m/2, m/2)) + assert B == BlockMatrix([[A[:n/2, :m/2], A[:n/2, m/2:]], + [A[n/2:, :m/2], A[n/2:, m/2:]]]) + + M = ImmutableMatrix(4, 4, range(16)) + B = blockcut(M, (2, 2), (2, 2)) + assert M == ImmutableMatrix(B) + + B = blockcut(M, (1, 3), (2, 2)) + assert ImmutableMatrix(B.blocks[0, 1]) == ImmutableMatrix([[2, 3]]) + +def test_reblock_2x2(): + B = BlockMatrix([[MatrixSymbol('A_%d%d'%(i,j), 2, 2) + for j in range(3)] + for i in range(3)]) + assert B.blocks.shape == (3, 3) + + BB = reblock_2x2(B) + assert BB.blocks.shape == (2, 2) + + assert B.shape == BB.shape + assert B.as_explicit() == BB.as_explicit() + +def test_deblock(): + B = BlockMatrix([[MatrixSymbol('A_%d%d'%(i,j), n, n) + for j in range(4)] + for i in range(4)]) + + assert deblock(reblock_2x2(B)) == B + +def test_block_collapse_type(): + bm1 = BlockDiagMatrix(ImmutableMatrix([1]), ImmutableMatrix([2])) + bm2 = BlockDiagMatrix(ImmutableMatrix([3]), ImmutableMatrix([4])) + + assert bm1.T.__class__ == BlockDiagMatrix + assert block_collapse(bm1 - bm2).__class__ == BlockDiagMatrix + assert block_collapse(Inverse(bm1)).__class__ == BlockDiagMatrix + assert block_collapse(Transpose(bm1)).__class__ == BlockDiagMatrix + assert bc_transpose(Transpose(bm1)).__class__ == BlockDiagMatrix + assert bc_inverse(Inverse(bm1)).__class__ == BlockDiagMatrix + +def test_invalid_block_matrix(): + raises(ValueError, lambda: BlockMatrix([ + [Identity(2), Identity(5)], + ])) + raises(ValueError, lambda: BlockMatrix([ + [Identity(n), Identity(m)], + ])) + raises(ValueError, lambda: BlockMatrix([ + [ZeroMatrix(n, n), ZeroMatrix(n, n)], + [ZeroMatrix(n, n - 1), ZeroMatrix(n, n + 1)], + ])) + raises(ValueError, lambda: BlockMatrix([ + [ZeroMatrix(n - 1, n), ZeroMatrix(n, n)], + [ZeroMatrix(n + 1, n), ZeroMatrix(n, n)], + ])) + +def test_block_lu_decomposition(): + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', n, m) + C = MatrixSymbol('C', m, n) + D = MatrixSymbol('D', m, m) + X = BlockMatrix([[A, B], [C, D]]) + + #LDU decomposition + L, D, U = X.LDUdecomposition() + assert block_collapse(L*D*U) == X + + #UDL decomposition + U, D, L = X.UDLdecomposition() + assert block_collapse(U*D*L) == X + + #LU decomposition + L, U = X.LUdecomposition() + assert block_collapse(L*U) == X + +def test_issue_21866(): + n = 10 + I = Identity(n) + O = ZeroMatrix(n, n) + A = BlockMatrix([[ I, O, O, O ], + [ O, I, O, O ], + [ O, O, I, O ], + [ I, O, O, I ]]) + Ainv = block_collapse(A.inv()) + AinvT = BlockMatrix([[ I, O, O, O ], + [ O, I, O, O ], + [ O, O, I, O ], + [ -I, O, O, I ]]) + assert Ainv == AinvT + + +def test_adjoint_and_special_matrices(): + A = Identity(3) + B = OneMatrix(3, 2) + C = ZeroMatrix(2, 3) + D = Identity(2) + X = BlockMatrix([[A, B], [C, D]]) + X2 = BlockMatrix([[A, S.ImaginaryUnit*B], [C, D]]) + assert X.adjoint() == BlockMatrix([[A, ZeroMatrix(3, 2)], [OneMatrix(2, 3), D]]) + assert re(X) == X + assert X2.adjoint() == BlockMatrix([[A, ZeroMatrix(3, 2)], [-S.ImaginaryUnit*OneMatrix(2, 3), D]]) + assert im(X2) == BlockMatrix([[ZeroMatrix(3, 3), OneMatrix(3, 2)], [ZeroMatrix(2, 3), ZeroMatrix(2, 2)]]) + + +def test_block_matrix_derivative(): + x = symbols('x') + A = Matrix(3, 3, [Function(f'a{i}')(x) for i in range(9)]) + bc = BlockMatrix([[A[:2, :2], A[:2, 2]], [A[2, :2], A[2:, 2]]]) + assert Matrix(bc.diff(x)) - A.diff(x) == zeros(3, 3) + + +def test_transpose_inverse_commute(): + n = Symbol('n') + I = Identity(n) + Z = ZeroMatrix(n, n) + A = BlockMatrix([[I, Z], [Z, I]]) + + assert block_collapse(A.transpose().inverse()) == A + assert block_collapse(A.inverse().transpose()) == A + + assert block_collapse(MatPow(A.transpose(), -2)) == MatPow(A, -2) + assert block_collapse(MatPow(A, -2).transpose()) == MatPow(A, -2) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_companion.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_companion.py new file mode 100644 index 0000000000000000000000000000000000000000..edc592c29098eddce0c6352806aa73d5d889e999 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_companion.py @@ -0,0 +1,48 @@ +from sympy.core.expr import unchanged +from sympy.core.symbol import Symbol, symbols +from sympy.matrices.immutable import ImmutableDenseMatrix +from sympy.matrices.expressions.companion import CompanionMatrix +from sympy.polys.polytools import Poly +from sympy.testing.pytest import raises + + +def test_creation(): + x = Symbol('x') + y = Symbol('y') + raises(ValueError, lambda: CompanionMatrix(1)) + raises(ValueError, lambda: CompanionMatrix(Poly([1], x))) + raises(ValueError, lambda: CompanionMatrix(Poly([2, 1], x))) + raises(ValueError, lambda: CompanionMatrix(Poly(x*y, [x, y]))) + assert unchanged(CompanionMatrix, Poly([1, 2, 3], x)) + + +def test_shape(): + c0, c1, c2 = symbols('c0:3') + x = Symbol('x') + assert CompanionMatrix(Poly([1, c0], x)).shape == (1, 1) + assert CompanionMatrix(Poly([1, c1, c0], x)).shape == (2, 2) + assert CompanionMatrix(Poly([1, c2, c1, c0], x)).shape == (3, 3) + + +def test_entry(): + c0, c1, c2 = symbols('c0:3') + x = Symbol('x') + A = CompanionMatrix(Poly([1, c2, c1, c0], x)) + assert A[0, 0] == 0 + assert A[1, 0] == 1 + assert A[1, 1] == 0 + assert A[2, 1] == 1 + assert A[0, 2] == -c0 + assert A[1, 2] == -c1 + assert A[2, 2] == -c2 + + +def test_as_explicit(): + c0, c1, c2 = symbols('c0:3') + x = Symbol('x') + assert CompanionMatrix(Poly([1, c0], x)).as_explicit() == \ + ImmutableDenseMatrix([-c0]) + assert CompanionMatrix(Poly([1, c1, c0], x)).as_explicit() == \ + ImmutableDenseMatrix([[0, -c0], [1, -c1]]) + assert CompanionMatrix(Poly([1, c2, c1, c0], x)).as_explicit() == \ + ImmutableDenseMatrix([[0, 0, -c0], [1, 0, -c1], [0, 1, -c2]]) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_derivatives.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_derivatives.py new file mode 100644 index 0000000000000000000000000000000000000000..77484c994dda62eea9771a76afd8b3caeadacb93 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_derivatives.py @@ -0,0 +1,477 @@ +""" +Some examples have been taken from: + +http://www.math.uwaterloo.ca/~hwolkowi//matrixcookbook.pdf +""" +from sympy import KroneckerProduct +from sympy.combinatorics import Permutation +from sympy.concrete.summations import Sum +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin, tan) +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.matrices.expressions.determinant import Determinant +from sympy.matrices.expressions.diagonal import DiagMatrix +from sympy.matrices.expressions.hadamard import (HadamardPower, HadamardProduct, hadamard_product) +from sympy.matrices.expressions.inverse import Inverse +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import OneMatrix +from sympy.matrices.expressions.trace import Trace +from sympy.matrices.expressions.matadd import MatAdd +from sympy.matrices.expressions.matmul import MatMul +from sympy.matrices.expressions.special import (Identity, ZeroMatrix) +from sympy.tensor.array.array_derivatives import ArrayDerivative +from sympy.matrices.expressions import hadamard_power +from sympy.tensor.array.expressions.array_expressions import ArrayAdd, ArrayTensorProduct, PermuteDims + +i, j, k = symbols("i j k") +m, n = symbols("m n") + +X = MatrixSymbol("X", k, k) +x = MatrixSymbol("x", k, 1) +y = MatrixSymbol("y", k, 1) + +A = MatrixSymbol("A", k, k) +B = MatrixSymbol("B", k, k) +C = MatrixSymbol("C", k, k) +D = MatrixSymbol("D", k, k) + +a = MatrixSymbol("a", k, 1) +b = MatrixSymbol("b", k, 1) +c = MatrixSymbol("c", k, 1) +d = MatrixSymbol("d", k, 1) + + +KDelta = lambda i, j: KroneckerDelta(i, j, (0, k-1)) + + +def _check_derivative_with_explicit_matrix(expr, x, diffexpr, dim=2): + # TODO: this is commented because it slows down the tests. + return + + expr = expr.xreplace({k: dim}) + x = x.xreplace({k: dim}) + diffexpr = diffexpr.xreplace({k: dim}) + + expr = expr.as_explicit() + x = x.as_explicit() + diffexpr = diffexpr.as_explicit() + + assert expr.diff(x).reshape(*diffexpr.shape).tomatrix() == diffexpr + + +def test_matrix_derivative_by_scalar(): + assert A.diff(i) == ZeroMatrix(k, k) + assert (A*(X + B)*c).diff(i) == ZeroMatrix(k, 1) + assert x.diff(i) == ZeroMatrix(k, 1) + assert (x.T*y).diff(i) == ZeroMatrix(1, 1) + assert (x*x.T).diff(i) == ZeroMatrix(k, k) + assert (x + y).diff(i) == ZeroMatrix(k, 1) + assert hadamard_power(x, 2).diff(i) == ZeroMatrix(k, 1) + assert hadamard_power(x, i).diff(i).dummy_eq( + HadamardProduct(x.applyfunc(log), HadamardPower(x, i))) + assert hadamard_product(x, y).diff(i) == ZeroMatrix(k, 1) + assert hadamard_product(i*OneMatrix(k, 1), x, y).diff(i) == hadamard_product(x, y) + assert (i*x).diff(i) == x + assert (sin(i)*A*B*x).diff(i) == cos(i)*A*B*x + assert x.applyfunc(sin).diff(i) == ZeroMatrix(k, 1) + assert Trace(i**2*X).diff(i) == 2*i*Trace(X) + + mu = symbols("mu") + expr = (2*mu*x) + assert expr.diff(x) == 2*mu*Identity(k) + + +def test_one_matrix(): + assert MatMul(x.T, OneMatrix(k, 1)).diff(x) == OneMatrix(k, 1) + + +def test_matrix_derivative_non_matrix_result(): + # This is a 4-dimensional array: + I = Identity(k) + AdA = PermuteDims(ArrayTensorProduct(I, I), Permutation(3)(1, 2)) + assert A.diff(A) == AdA + assert A.T.diff(A) == PermuteDims(ArrayTensorProduct(I, I), Permutation(3)(1, 2, 3)) + assert (2*A).diff(A) == PermuteDims(ArrayTensorProduct(2*I, I), Permutation(3)(1, 2)) + assert MatAdd(A, A).diff(A) == ArrayAdd(AdA, AdA) + assert (A + B).diff(A) == AdA + + +def test_matrix_derivative_trivial_cases(): + # Cookbook example 33: + # TODO: find a way to represent a four-dimensional zero-array: + assert X.diff(A) == ArrayDerivative(X, A) + + +def test_matrix_derivative_with_inverse(): + + # Cookbook example 61: + expr = a.T*Inverse(X)*b + assert expr.diff(X) == -Inverse(X).T*a*b.T*Inverse(X).T + + # Cookbook example 62: + expr = Determinant(Inverse(X)) + # Not implemented yet: + # assert expr.diff(X) == -Determinant(X.inv())*(X.inv()).T + + # Cookbook example 63: + expr = Trace(A*Inverse(X)*B) + assert expr.diff(X) == -(X**(-1)*B*A*X**(-1)).T + + # Cookbook example 64: + expr = Trace(Inverse(X + A)) + assert expr.diff(X) == -(Inverse(X + A)).T**2 + + +def test_matrix_derivative_vectors_and_scalars(): + + assert x.diff(x) == Identity(k) + assert x[i, 0].diff(x[m, 0]).doit() == KDelta(m, i) + + assert x.T.diff(x) == Identity(k) + + # Cookbook example 69: + expr = x.T*a + assert expr.diff(x) == a + assert expr[0, 0].diff(x[m, 0]).doit() == a[m, 0] + expr = a.T*x + assert expr.diff(x) == a + + # Cookbook example 70: + expr = a.T*X*b + assert expr.diff(X) == a*b.T + + # Cookbook example 71: + expr = a.T*X.T*b + assert expr.diff(X) == b*a.T + + # Cookbook example 72: + expr = a.T*X*a + assert expr.diff(X) == a*a.T + expr = a.T*X.T*a + assert expr.diff(X) == a*a.T + + # Cookbook example 77: + expr = b.T*X.T*X*c + assert expr.diff(X) == X*b*c.T + X*c*b.T + + # Cookbook example 78: + expr = (B*x + b).T*C*(D*x + d) + assert expr.diff(x) == B.T*C*(D*x + d) + D.T*C.T*(B*x + b) + + # Cookbook example 81: + expr = x.T*B*x + assert expr.diff(x) == B*x + B.T*x + + # Cookbook example 82: + expr = b.T*X.T*D*X*c + assert expr.diff(X) == D.T*X*b*c.T + D*X*c*b.T + + # Cookbook example 83: + expr = (X*b + c).T*D*(X*b + c) + assert expr.diff(X) == D*(X*b + c)*b.T + D.T*(X*b + c)*b.T + assert str(expr[0, 0].diff(X[m, n]).doit()) == \ + 'b[n, 0]*Sum((c[_i_1, 0] + Sum(X[_i_1, _i_3]*b[_i_3, 0], (_i_3, 0, k - 1)))*D[_i_1, m], (_i_1, 0, k - 1)) + Sum((c[_i_2, 0] + Sum(X[_i_2, _i_4]*b[_i_4, 0], (_i_4, 0, k - 1)))*D[m, _i_2]*b[n, 0], (_i_2, 0, k - 1))' + + # See https://github.com/sympy/sympy/issues/16504#issuecomment-1018339957 + expr = x*x.T*x + I = Identity(k) + assert expr.diff(x) == KroneckerProduct(I, x.T*x) + 2*x*x.T + + +def test_matrix_derivatives_of_traces(): + + expr = Trace(A)*A + I = Identity(k) + assert expr.diff(A) == ArrayAdd(ArrayTensorProduct(I, A), PermuteDims(ArrayTensorProduct(Trace(A)*I, I), Permutation(3)(1, 2))) + assert expr[i, j].diff(A[m, n]).doit() == ( + KDelta(i, m)*KDelta(j, n)*Trace(A) + + KDelta(m, n)*A[i, j] + ) + + ## First order: + + # Cookbook example 99: + expr = Trace(X) + assert expr.diff(X) == Identity(k) + assert expr.rewrite(Sum).diff(X[m, n]).doit() == KDelta(m, n) + + # Cookbook example 100: + expr = Trace(X*A) + assert expr.diff(X) == A.T + assert expr.rewrite(Sum).diff(X[m, n]).doit() == A[n, m] + + # Cookbook example 101: + expr = Trace(A*X*B) + assert expr.diff(X) == A.T*B.T + assert expr.rewrite(Sum).diff(X[m, n]).doit().dummy_eq((A.T*B.T)[m, n]) + + # Cookbook example 102: + expr = Trace(A*X.T*B) + assert expr.diff(X) == B*A + + # Cookbook example 103: + expr = Trace(X.T*A) + assert expr.diff(X) == A + + # Cookbook example 104: + expr = Trace(A*X.T) + assert expr.diff(X) == A + + # Cookbook example 105: + # TODO: TensorProduct is not supported + #expr = Trace(TensorProduct(A, X)) + #assert expr.diff(X) == Trace(A)*Identity(k) + + ## Second order: + + # Cookbook example 106: + expr = Trace(X**2) + assert expr.diff(X) == 2*X.T + + # Cookbook example 107: + expr = Trace(X**2*B) + assert expr.diff(X) == (X*B + B*X).T + expr = Trace(MatMul(X, X, B)) + assert expr.diff(X) == (X*B + B*X).T + + # Cookbook example 108: + expr = Trace(X.T*B*X) + assert expr.diff(X) == B*X + B.T*X + + # Cookbook example 109: + expr = Trace(B*X*X.T) + assert expr.diff(X) == B*X + B.T*X + + # Cookbook example 110: + expr = Trace(X*X.T*B) + assert expr.diff(X) == B*X + B.T*X + + # Cookbook example 111: + expr = Trace(X*B*X.T) + assert expr.diff(X) == X*B.T + X*B + + # Cookbook example 112: + expr = Trace(B*X.T*X) + assert expr.diff(X) == X*B.T + X*B + + # Cookbook example 113: + expr = Trace(X.T*X*B) + assert expr.diff(X) == X*B.T + X*B + + # Cookbook example 114: + expr = Trace(A*X*B*X) + assert expr.diff(X) == A.T*X.T*B.T + B.T*X.T*A.T + + # Cookbook example 115: + expr = Trace(X.T*X) + assert expr.diff(X) == 2*X + expr = Trace(X*X.T) + assert expr.diff(X) == 2*X + + # Cookbook example 116: + expr = Trace(B.T*X.T*C*X*B) + assert expr.diff(X) == C.T*X*B*B.T + C*X*B*B.T + + # Cookbook example 117: + expr = Trace(X.T*B*X*C) + assert expr.diff(X) == B*X*C + B.T*X*C.T + + # Cookbook example 118: + expr = Trace(A*X*B*X.T*C) + assert expr.diff(X) == A.T*C.T*X*B.T + C*A*X*B + + # Cookbook example 119: + expr = Trace((A*X*B + C)*(A*X*B + C).T) + assert expr.diff(X) == 2*A.T*(A*X*B + C)*B.T + + # Cookbook example 120: + # TODO: no support for TensorProduct. + # expr = Trace(TensorProduct(X, X)) + # expr = Trace(X)*Trace(X) + # expr.diff(X) == 2*Trace(X)*Identity(k) + + # Higher Order + + # Cookbook example 121: + expr = Trace(X**k) + #assert expr.diff(X) == k*(X**(k-1)).T + + # Cookbook example 122: + expr = Trace(A*X**k) + #assert expr.diff(X) == # Needs indices + + # Cookbook example 123: + expr = Trace(B.T*X.T*C*X*X.T*C*X*B) + assert expr.diff(X) == C*X*X.T*C*X*B*B.T + C.T*X*B*B.T*X.T*C.T*X + C*X*B*B.T*X.T*C*X + C.T*X*X.T*C.T*X*B*B.T + + # Other + + # Cookbook example 124: + expr = Trace(A*X**(-1)*B) + assert expr.diff(X) == -Inverse(X).T*A.T*B.T*Inverse(X).T + + # Cookbook example 125: + expr = Trace(Inverse(X.T*C*X)*A) + # Warning: result in the cookbook is equivalent if B and C are symmetric: + assert expr.diff(X) == - X.inv().T*A.T*X.inv()*C.inv().T*X.inv().T - X.inv().T*A*X.inv()*C.inv()*X.inv().T + + # Cookbook example 126: + expr = Trace((X.T*C*X).inv()*(X.T*B*X)) + assert expr.diff(X) == -2*C*X*(X.T*C*X).inv()*X.T*B*X*(X.T*C*X).inv() + 2*B*X*(X.T*C*X).inv() + + # Cookbook example 127: + expr = Trace((A + X.T*C*X).inv()*(X.T*B*X)) + # Warning: result in the cookbook is equivalent if B and C are symmetric: + assert expr.diff(X) == B*X*Inverse(A + X.T*C*X) - C*X*Inverse(A + X.T*C*X)*X.T*B*X*Inverse(A + X.T*C*X) - C.T*X*Inverse(A.T + (C*X).T*X)*X.T*B.T*X*Inverse(A.T + (C*X).T*X) + B.T*X*Inverse(A.T + (C*X).T*X) + + +def test_derivatives_of_complicated_matrix_expr(): + expr = a.T*(A*X*(X.T*B + X*A) + B.T*X.T*(a*b.T*(X*D*X.T + X*(X.T*B + A*X)*D*B - X.T*C.T*A)*B + B*(X*D.T + B*A*X*A.T - 3*X*D))*B + 42*X*B*X.T*A.T*(X + X.T))*b + result = (B*(B*A*X*A.T - 3*X*D + X*D.T) + a*b.T*(X*(A*X + X.T*B)*D*B + X*D*X.T - X.T*C.T*A)*B)*B*b*a.T*B.T + B**2*b*a.T*B.T*X.T*a*b.T*X*D + 42*A*X*B.T*X.T*a*b.T + B*D*B**3*b*a.T*B.T*X.T*a*b.T*X + B*b*a.T*A*X + a*b.T*(42*X + 42*X.T)*A*X*B.T + b*a.T*X*B*a*b.T*B.T**2*X*D.T + b*a.T*X*B*a*b.T*B.T**3*D.T*(B.T*X + X.T*A.T) + 42*b*a.T*X*B*X.T*A.T + A.T*(42*X + 42*X.T)*b*a.T*X*B + A.T*B.T**2*X*B*a*b.T*B.T*A + A.T*a*b.T*(A.T*X.T + B.T*X) + A.T*X.T*b*a.T*X*B*a*b.T*B.T**3*D.T + B.T*X*B*a*b.T*B.T*D - 3*B.T*X*B*a*b.T*B.T*D.T - C.T*A*B**2*b*a.T*B.T*X.T*a*b.T + X.T*A.T*a*b.T*A.T + assert expr.diff(X) == result + + +def test_mixed_deriv_mixed_expressions(): + + expr = 3*Trace(A) + assert expr.diff(A) == 3*Identity(k) + + expr = k + deriv = expr.diff(A) + assert isinstance(deriv, ZeroMatrix) + assert deriv == ZeroMatrix(k, k) + + expr = Trace(A)**2 + assert expr.diff(A) == (2*Trace(A))*Identity(k) + + expr = Trace(A)*A + I = Identity(k) + assert expr.diff(A) == ArrayAdd(ArrayTensorProduct(I, A), PermuteDims(ArrayTensorProduct(Trace(A)*I, I), Permutation(3)(1, 2))) + + expr = Trace(Trace(A)*A) + assert expr.diff(A) == (2*Trace(A))*Identity(k) + + expr = Trace(Trace(Trace(A)*A)*A) + assert expr.diff(A) == (3*Trace(A)**2)*Identity(k) + + +def test_derivatives_matrix_norms(): + + expr = x.T*y + assert expr.diff(x) == y + assert expr[0, 0].diff(x[m, 0]).doit() == y[m, 0] + + expr = (x.T*y)**S.Half + assert expr.diff(x) == y/(2*sqrt(x.T*y)) + + expr = (x.T*x)**S.Half + assert expr.diff(x) == x*(x.T*x)**Rational(-1, 2) + + expr = (c.T*a*x.T*b)**S.Half + assert expr.diff(x) == b*a.T*c/sqrt(c.T*a*x.T*b)/2 + + expr = (c.T*a*x.T*b)**Rational(1, 3) + assert expr.diff(x) == b*a.T*c*(c.T*a*x.T*b)**Rational(-2, 3)/3 + + expr = (a.T*X*b)**S.Half + assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*b.T + + expr = d.T*x*(a.T*X*b)**S.Half*y.T*c + assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*x.T*d*y.T*c*b.T + + +def test_derivatives_elementwise_applyfunc(): + + expr = x.applyfunc(tan) + assert expr.diff(x).dummy_eq( + DiagMatrix(x.applyfunc(lambda x: tan(x)**2 + 1))) + assert expr[i, 0].diff(x[m, 0]).doit() == (tan(x[i, 0])**2 + 1)*KDelta(i, m) + _check_derivative_with_explicit_matrix(expr, x, expr.diff(x)) + + expr = (i**2*x).applyfunc(sin) + assert expr.diff(i).dummy_eq( + HadamardProduct((2*i)*x, (i**2*x).applyfunc(cos))) + assert expr[i, 0].diff(i).doit() == 2*i*x[i, 0]*cos(i**2*x[i, 0]) + _check_derivative_with_explicit_matrix(expr, i, expr.diff(i)) + + expr = (log(i)*A*B).applyfunc(sin) + assert expr.diff(i).dummy_eq( + HadamardProduct(A*B/i, (log(i)*A*B).applyfunc(cos))) + _check_derivative_with_explicit_matrix(expr, i, expr.diff(i)) + + expr = A*x.applyfunc(exp) + # TODO: restore this result (currently returning the transpose): + # assert expr.diff(x).dummy_eq(DiagMatrix(x.applyfunc(exp))*A.T) + _check_derivative_with_explicit_matrix(expr, x, expr.diff(x)) + + expr = x.T*A*x + k*y.applyfunc(sin).T*x + assert expr.diff(x).dummy_eq(A.T*x + A*x + k*y.applyfunc(sin)) + _check_derivative_with_explicit_matrix(expr, x, expr.diff(x)) + + expr = x.applyfunc(sin).T*y + # TODO: restore (currently returning the transpose): + # assert expr.diff(x).dummy_eq(DiagMatrix(x.applyfunc(cos))*y) + _check_derivative_with_explicit_matrix(expr, x, expr.diff(x)) + + expr = (a.T * X * b).applyfunc(sin) + assert expr.diff(X).dummy_eq(a*(a.T*X*b).applyfunc(cos)*b.T) + _check_derivative_with_explicit_matrix(expr, X, expr.diff(X)) + + expr = a.T * X.applyfunc(sin) * b + assert expr.diff(X).dummy_eq( + DiagMatrix(a)*X.applyfunc(cos)*DiagMatrix(b)) + _check_derivative_with_explicit_matrix(expr, X, expr.diff(X)) + + expr = a.T * (A*X*B).applyfunc(sin) * b + assert expr.diff(X).dummy_eq( + A.T*DiagMatrix(a)*(A*X*B).applyfunc(cos)*DiagMatrix(b)*B.T) + _check_derivative_with_explicit_matrix(expr, X, expr.diff(X)) + + expr = a.T * (A*X*b).applyfunc(sin) * b.T + # TODO: not implemented + #assert expr.diff(X) == ... + #_check_derivative_with_explicit_matrix(expr, X, expr.diff(X)) + + expr = a.T*A*X.applyfunc(sin)*B*b + assert expr.diff(X).dummy_eq( + HadamardProduct(A.T * a * b.T * B.T, X.applyfunc(cos))) + + expr = a.T * (A*X.applyfunc(sin)*B).applyfunc(log) * b + # TODO: wrong + # assert expr.diff(X) == A.T*DiagMatrix(a)*(A*X.applyfunc(sin)*B).applyfunc(Lambda(k, 1/k))*DiagMatrix(b)*B.T + + expr = a.T * (X.applyfunc(sin)).applyfunc(log) * b + # TODO: wrong + # assert expr.diff(X) == DiagMatrix(a)*X.applyfunc(sin).applyfunc(Lambda(k, 1/k))*DiagMatrix(b) + + +def test_derivatives_of_hadamard_expressions(): + + # Hadamard Product + + expr = hadamard_product(a, x, b) + assert expr.diff(x) == DiagMatrix(hadamard_product(b, a)) + + expr = a.T*hadamard_product(A, X, B)*b + assert expr.diff(X) == HadamardProduct(a*b.T, A, B) + + # Hadamard Power + + expr = hadamard_power(x, 2) + assert expr.diff(x).doit() == 2*DiagMatrix(x) + + expr = hadamard_power(x.T, 2) + assert expr.diff(x).doit() == 2*DiagMatrix(x) + + expr = hadamard_power(x, S.Half) + assert expr.diff(x) == S.Half*DiagMatrix(hadamard_power(x, Rational(-1, 2))) + + expr = hadamard_power(a.T*X*b, 2) + assert expr.diff(X) == 2*a*a.T*X*b*b.T + + expr = hadamard_power(a.T*X*b, S.Half) + assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*b.T diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_determinant.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_determinant.py new file mode 100644 index 0000000000000000000000000000000000000000..d1a66c728f076f8c769d2519ee47c8a9cc90a90e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_determinant.py @@ -0,0 +1,65 @@ +from sympy.core import S, symbols +from sympy.matrices import eye, ones, Matrix, ShapeError +from sympy.matrices.expressions import ( + Identity, MatrixExpr, MatrixSymbol, Determinant, + det, per, ZeroMatrix, Transpose, + Permanent, MatMul +) +from sympy.matrices.expressions.special import OneMatrix +from sympy.testing.pytest import raises +from sympy.assumptions.ask import Q +from sympy.assumptions.refine import refine + +n = symbols('n', integer=True) +A = MatrixSymbol('A', n, n) +B = MatrixSymbol('B', n, n) +C = MatrixSymbol('C', 3, 4) + + +def test_det(): + assert isinstance(Determinant(A), Determinant) + assert not isinstance(Determinant(A), MatrixExpr) + raises(ShapeError, lambda: Determinant(C)) + assert det(eye(3)) == 1 + assert det(Matrix(3, 3, [1, 3, 2, 4, 1, 3, 2, 5, 2])) == 17 + _ = A / det(A) # Make sure this is possible + + raises(TypeError, lambda: Determinant(S.One)) + + assert Determinant(A).arg is A + + +def test_eval_determinant(): + assert det(Identity(n)) == 1 + assert det(ZeroMatrix(n, n)) == 0 + assert det(OneMatrix(n, n)) == Determinant(OneMatrix(n, n)) + assert det(OneMatrix(1, 1)) == 1 + assert det(OneMatrix(2, 2)) == 0 + assert det(Transpose(A)) == det(A) + assert Determinant(MatMul(eye(2), eye(2))).doit(deep=True) == 1 + + +def test_refine(): + assert refine(det(A), Q.orthogonal(A)) == 1 + assert refine(det(A), Q.singular(A)) == 0 + assert refine(det(A), Q.unit_triangular(A)) == 1 + assert refine(det(A), Q.normal(A)) == det(A) + + +def test_commutative(): + det_a = Determinant(A) + det_b = Determinant(B) + assert det_a.is_commutative + assert det_b.is_commutative + assert det_a * det_b == det_b * det_a + + +def test_permanent(): + assert isinstance(Permanent(A), Permanent) + assert not isinstance(Permanent(A), MatrixExpr) + assert isinstance(Permanent(C), Permanent) + assert Permanent(ones(3, 3)).doit() == 6 + _ = C / per(C) + assert per(Matrix(3, 3, [1, 3, 2, 4, 1, 3, 2, 5, 2])) == 103 + raises(TypeError, lambda: Permanent(S.One)) + assert Permanent(A).arg is A diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_diagonal.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_diagonal.py new file mode 100644 index 0000000000000000000000000000000000000000..3e4f7ea4c178121c33eeb26c09675403d274c1e8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_diagonal.py @@ -0,0 +1,156 @@ +from sympy.matrices.expressions import MatrixSymbol +from sympy.matrices.expressions.diagonal import DiagonalMatrix, DiagonalOf, DiagMatrix, diagonalize_vector +from sympy.assumptions.ask import (Q, ask) +from sympy.core.symbol import Symbol +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.matmul import MatMul +from sympy.matrices.expressions.special import Identity +from sympy.testing.pytest import raises + + +n = Symbol('n') +m = Symbol('m') + + +def test_DiagonalMatrix(): + x = MatrixSymbol('x', n, m) + D = DiagonalMatrix(x) + assert D.diagonal_length is None + assert D.shape == (n, m) + + x = MatrixSymbol('x', n, n) + D = DiagonalMatrix(x) + assert D.diagonal_length == n + assert D.shape == (n, n) + assert D[1, 2] == 0 + assert D[1, 1] == x[1, 1] + i = Symbol('i') + j = Symbol('j') + x = MatrixSymbol('x', 3, 3) + ij = DiagonalMatrix(x)[i, j] + assert ij != 0 + assert ij.subs({i:0, j:0}) == x[0, 0] + assert ij.subs({i:0, j:1}) == 0 + assert ij.subs({i:1, j:1}) == x[1, 1] + assert ask(Q.diagonal(D)) # affirm that D is diagonal + + x = MatrixSymbol('x', n, 3) + D = DiagonalMatrix(x) + assert D.diagonal_length == 3 + assert D.shape == (n, 3) + assert D[2, m] == KroneckerDelta(2, m)*x[2, m] + assert D[3, m] == 0 + raises(IndexError, lambda: D[m, 3]) + + x = MatrixSymbol('x', 3, n) + D = DiagonalMatrix(x) + assert D.diagonal_length == 3 + assert D.shape == (3, n) + assert D[m, 2] == KroneckerDelta(m, 2)*x[m, 2] + assert D[m, 3] == 0 + raises(IndexError, lambda: D[3, m]) + + x = MatrixSymbol('x', n, m) + D = DiagonalMatrix(x) + assert D.diagonal_length is None + assert D.shape == (n, m) + assert D[m, 4] != 0 + + x = MatrixSymbol('x', 3, 4) + assert [DiagonalMatrix(x)[i] for i in range(12)] == [ + x[0, 0], 0, 0, 0, 0, x[1, 1], 0, 0, 0, 0, x[2, 2], 0] + + # shape is retained, issue 12427 + assert ( + DiagonalMatrix(MatrixSymbol('x', 3, 4))* + DiagonalMatrix(MatrixSymbol('x', 4, 2))).shape == (3, 2) + + +def test_DiagonalOf(): + x = MatrixSymbol('x', n, n) + d = DiagonalOf(x) + assert d.shape == (n, 1) + assert d.diagonal_length == n + assert d[2, 0] == d[2] == x[2, 2] + + x = MatrixSymbol('x', n, m) + d = DiagonalOf(x) + assert d.shape == (None, 1) + assert d.diagonal_length is None + assert d[2, 0] == d[2] == x[2, 2] + + d = DiagonalOf(MatrixSymbol('x', 4, 3)) + assert d.shape == (3, 1) + d = DiagonalOf(MatrixSymbol('x', n, 3)) + assert d.shape == (3, 1) + d = DiagonalOf(MatrixSymbol('x', 3, n)) + assert d.shape == (3, 1) + x = MatrixSymbol('x', n, m) + assert [DiagonalOf(x)[i] for i in range(4)] ==[ + x[0, 0], x[1, 1], x[2, 2], x[3, 3]] + + +def test_DiagMatrix(): + x = MatrixSymbol('x', n, 1) + d = DiagMatrix(x) + assert d.shape == (n, n) + assert d[0, 1] == 0 + assert d[0, 0] == x[0, 0] + + a = MatrixSymbol('a', 1, 1) + d = diagonalize_vector(a) + assert isinstance(d, MatrixSymbol) + assert a == d + assert diagonalize_vector(Identity(3)) == Identity(3) + assert DiagMatrix(Identity(3)).doit() == Identity(3) + assert isinstance(DiagMatrix(Identity(3)), DiagMatrix) + + # A diagonal matrix is equal to its transpose: + assert DiagMatrix(x).T == DiagMatrix(x) + assert diagonalize_vector(x.T) == DiagMatrix(x) + + dx = DiagMatrix(x) + assert dx[0, 0] == x[0, 0] + assert dx[1, 1] == x[1, 0] + assert dx[0, 1] == 0 + assert dx[0, m] == x[0, 0]*KroneckerDelta(0, m) + + z = MatrixSymbol('z', 1, n) + dz = DiagMatrix(z) + assert dz[0, 0] == z[0, 0] + assert dz[1, 1] == z[0, 1] + assert dz[0, 1] == 0 + assert dz[0, m] == z[0, m]*KroneckerDelta(0, m) + + v = MatrixSymbol('v', 3, 1) + dv = DiagMatrix(v) + assert dv.as_explicit() == Matrix([ + [v[0, 0], 0, 0], + [0, v[1, 0], 0], + [0, 0, v[2, 0]], + ]) + + v = MatrixSymbol('v', 1, 3) + dv = DiagMatrix(v) + assert dv.as_explicit() == Matrix([ + [v[0, 0], 0, 0], + [0, v[0, 1], 0], + [0, 0, v[0, 2]], + ]) + + dv = DiagMatrix(3*v) + assert dv.args == (3*v,) + assert dv.doit() == 3*DiagMatrix(v) + assert isinstance(dv.doit(), MatMul) + + a = MatrixSymbol("a", 3, 1).as_explicit() + expr = DiagMatrix(a) + result = Matrix([ + [a[0, 0], 0, 0], + [0, a[1, 0], 0], + [0, 0, a[2, 0]], + ]) + assert expr.doit() == result + expr = DiagMatrix(a.T) + assert expr.doit() == result diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_dotproduct.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_dotproduct.py new file mode 100644 index 0000000000000000000000000000000000000000..abf8ab8e935cbd3039f25f83d3603ac444e5a7bb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_dotproduct.py @@ -0,0 +1,35 @@ +from sympy.core.expr import unchanged +from sympy.core.mul import Mul +from sympy.matrices import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.dotproduct import DotProduct +from sympy.testing.pytest import raises + + +A = Matrix(3, 1, [1, 2, 3]) +B = Matrix(3, 1, [1, 3, 5]) +C = Matrix(4, 1, [1, 2, 4, 5]) +D = Matrix(2, 2, [1, 2, 3, 4]) + +def test_docproduct(): + assert DotProduct(A, B).doit() == 22 + assert DotProduct(A.T, B).doit() == 22 + assert DotProduct(A, B.T).doit() == 22 + assert DotProduct(A.T, B.T).doit() == 22 + + raises(TypeError, lambda: DotProduct(1, A)) + raises(TypeError, lambda: DotProduct(A, 1)) + raises(TypeError, lambda: DotProduct(A, D)) + raises(TypeError, lambda: DotProduct(D, A)) + + raises(TypeError, lambda: DotProduct(B, C).doit()) + +def test_dotproduct_symbolic(): + A = MatrixSymbol('A', 3, 1) + B = MatrixSymbol('B', 3, 1) + + dot = DotProduct(A, B) + assert dot.is_scalar == True + assert unchanged(Mul, 2, dot) + # XXX Fix forced evaluation for arithmetics with matrix expressions + assert dot * A == (A[0, 0]*B[0, 0] + A[1, 0]*B[1, 0] + A[2, 0]*B[2, 0])*A diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_factorizations.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_factorizations.py new file mode 100644 index 0000000000000000000000000000000000000000..a0319acabbb7409dfa5c24ceca39e25ff0240618 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_factorizations.py @@ -0,0 +1,29 @@ +from sympy.matrices.expressions.factorizations import lu, LofCholesky, qr, svd +from sympy.assumptions.ask import (Q, ask) +from sympy.core.symbol import Symbol +from sympy.matrices.expressions.matexpr import MatrixSymbol + +n = Symbol('n') +X = MatrixSymbol('X', n, n) + +def test_LU(): + L, U = lu(X) + assert L.shape == U.shape == X.shape + assert ask(Q.lower_triangular(L)) + assert ask(Q.upper_triangular(U)) + +def test_Cholesky(): + LofCholesky(X) + +def test_QR(): + Q_, R = qr(X) + assert Q_.shape == R.shape == X.shape + assert ask(Q.orthogonal(Q_)) + assert ask(Q.upper_triangular(R)) + +def test_svd(): + U, S, V = svd(X) + assert U.shape == S.shape == V.shape == X.shape + assert ask(Q.orthogonal(U)) + assert ask(Q.orthogonal(V)) + assert ask(Q.diagonal(S)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_fourier.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_fourier.py new file mode 100644 index 0000000000000000000000000000000000000000..0230c8a0957ed28fb0a5cc1e9ee77ecae797265b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_fourier.py @@ -0,0 +1,44 @@ +from sympy.assumptions.ask import (Q, ask) +from sympy.core.numbers import (I, Rational) +from sympy.core.singleton import S +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.simplify.simplify import simplify +from sympy.core.symbol import symbols +from sympy.matrices.expressions.fourier import DFT, IDFT +from sympy.matrices import det, Matrix, Identity +from sympy.testing.pytest import raises + + +def test_dft_creation(): + assert DFT(2) + assert DFT(0) + raises(ValueError, lambda: DFT(-1)) + raises(ValueError, lambda: DFT(2.0)) + raises(ValueError, lambda: DFT(2 + 1j)) + + n = symbols('n') + assert DFT(n) + n = symbols('n', integer=False) + raises(ValueError, lambda: DFT(n)) + n = symbols('n', negative=True) + raises(ValueError, lambda: DFT(n)) + + +def test_dft(): + n, i, j = symbols('n i j') + assert DFT(4).shape == (4, 4) + assert ask(Q.unitary(DFT(4))) + assert Abs(simplify(det(Matrix(DFT(4))))) == 1 + assert DFT(n)*IDFT(n) == Identity(n) + assert DFT(n)[i, j] == exp(-2*S.Pi*I/n)**(i*j) / sqrt(n) + + +def test_dft2(): + assert DFT(1).as_explicit() == Matrix([[1]]) + assert DFT(2).as_explicit() == 1/sqrt(2)*Matrix([[1,1],[1,-1]]) + assert DFT(4).as_explicit() == Matrix([[S.Half, S.Half, S.Half, S.Half], + [S.Half, -I/2, Rational(-1,2), I/2], + [S.Half, Rational(-1,2), S.Half, Rational(-1,2)], + [S.Half, I/2, Rational(-1,2), -I/2]]) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_funcmatrix.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_funcmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..e4850fe5c739b9390fac6afa10757b5babf821c6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_funcmatrix.py @@ -0,0 +1,54 @@ +from sympy.core import symbols, Lambda +from sympy.core.sympify import SympifyError +from sympy.functions import KroneckerDelta +from sympy.matrices import Matrix +from sympy.matrices.expressions import FunctionMatrix, MatrixExpr, Identity +from sympy.testing.pytest import raises + + +def test_funcmatrix_creation(): + i, j, k = symbols('i j k') + assert FunctionMatrix(2, 2, Lambda((i, j), 0)) + assert FunctionMatrix(0, 0, Lambda((i, j), 0)) + + raises(ValueError, lambda: FunctionMatrix(-1, 0, Lambda((i, j), 0))) + raises(ValueError, lambda: FunctionMatrix(2.0, 0, Lambda((i, j), 0))) + raises(ValueError, lambda: FunctionMatrix(2j, 0, Lambda((i, j), 0))) + raises(ValueError, lambda: FunctionMatrix(0, -1, Lambda((i, j), 0))) + raises(ValueError, lambda: FunctionMatrix(0, 2.0, Lambda((i, j), 0))) + raises(ValueError, lambda: FunctionMatrix(0, 2j, Lambda((i, j), 0))) + + raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda(i, 0))) + raises(SympifyError, lambda: FunctionMatrix(2, 2, lambda i, j: 0)) + raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda((i,), 0))) + raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda((i, j, k), 0))) + raises(ValueError, lambda: FunctionMatrix(2, 2, i+j)) + assert FunctionMatrix(2, 2, "lambda i, j: 0") == \ + FunctionMatrix(2, 2, Lambda((i, j), 0)) + + m = FunctionMatrix(2, 2, KroneckerDelta) + assert m.as_explicit() == Identity(2).as_explicit() + assert m.args[2].dummy_eq(Lambda((i, j), KroneckerDelta(i, j))) + + n = symbols('n') + assert FunctionMatrix(n, n, Lambda((i, j), 0)) + n = symbols('n', integer=False) + raises(ValueError, lambda: FunctionMatrix(n, n, Lambda((i, j), 0))) + n = symbols('n', negative=True) + raises(ValueError, lambda: FunctionMatrix(n, n, Lambda((i, j), 0))) + + +def test_funcmatrix(): + i, j = symbols('i,j') + X = FunctionMatrix(3, 3, Lambda((i, j), i - j)) + assert X[1, 1] == 0 + assert X[1, 2] == -1 + assert X.shape == (3, 3) + assert X.rows == X.cols == 3 + assert Matrix(X) == Matrix(3, 3, lambda i, j: i - j) + assert isinstance(X*X + X, MatrixExpr) + + +def test_replace_issue(): + X = FunctionMatrix(3, 3, KroneckerDelta) + assert X.replace(lambda x: True, lambda x: x) == X diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_hadamard.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_hadamard.py new file mode 100644 index 0000000000000000000000000000000000000000..800fa830a9b089103d69b372db93ebcea541d02b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_hadamard.py @@ -0,0 +1,141 @@ +from sympy.matrices.dense import Matrix, eye +from sympy.matrices.exceptions import ShapeError +from sympy.matrices.expressions.matadd import MatAdd +from sympy.matrices.expressions.special import Identity, OneMatrix, ZeroMatrix +from sympy.core import symbols +from sympy.testing.pytest import raises, warns_deprecated_sympy + +from sympy.matrices import MatrixSymbol +from sympy.matrices.expressions import (HadamardProduct, hadamard_product, HadamardPower, hadamard_power) + +n, m, k = symbols('n,m,k') +Z = MatrixSymbol('Z', n, n) +A = MatrixSymbol('A', n, m) +B = MatrixSymbol('B', n, m) +C = MatrixSymbol('C', m, k) + + +def test_HadamardProduct(): + assert HadamardProduct(A, B, A).shape == A.shape + + raises(TypeError, lambda: HadamardProduct(A, n)) + raises(TypeError, lambda: HadamardProduct(A, 1)) + + assert HadamardProduct(A, 2*B, -A)[1, 1] == \ + -2 * A[1, 1] * B[1, 1] * A[1, 1] + + mix = HadamardProduct(Z*A, B)*C + assert mix.shape == (n, k) + + assert set(HadamardProduct(A, B, A).T.args) == {A.T, A.T, B.T} + + +def test_HadamardProduct_isnt_commutative(): + assert HadamardProduct(A, B) != HadamardProduct(B, A) + + +def test_mixed_indexing(): + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + Z = MatrixSymbol('Z', 2, 2) + + assert (X*HadamardProduct(Y, Z))[0, 0] == \ + X[0, 0]*Y[0, 0]*Z[0, 0] + X[0, 1]*Y[1, 0]*Z[1, 0] + + +def test_canonicalize(): + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + with warns_deprecated_sympy(): + expr = HadamardProduct(X, check=False) + assert isinstance(expr, HadamardProduct) + expr2 = expr.doit() # unpack is called + assert isinstance(expr2, MatrixSymbol) + Z = ZeroMatrix(2, 2) + U = OneMatrix(2, 2) + assert HadamardProduct(Z, X).doit() == Z + assert HadamardProduct(U, X, X, U).doit() == HadamardPower(X, 2) + assert HadamardProduct(X, U, Y).doit() == HadamardProduct(X, Y) + assert HadamardProduct(X, Z, U, Y).doit() == Z + + +def test_hadamard(): + m, n, p = symbols('m, n, p', integer=True) + A = MatrixSymbol('A', m, n) + B = MatrixSymbol('B', m, n) + X = MatrixSymbol('X', m, m) + I = Identity(m) + + raises(TypeError, lambda: hadamard_product()) + assert hadamard_product(A) == A + assert isinstance(hadamard_product(A, B), HadamardProduct) + assert hadamard_product(A, B).doit() == hadamard_product(A, B) + assert hadamard_product(X, I) == HadamardProduct(I, X) + assert isinstance(hadamard_product(X, I), HadamardProduct) + + a = MatrixSymbol("a", k, 1) + expr = MatAdd(ZeroMatrix(k, 1), OneMatrix(k, 1)) + expr = HadamardProduct(expr, a) + assert expr.doit() == a + + raises(ValueError, lambda: HadamardProduct()) + + +def test_hadamard_product_with_explicit_mat(): + A = MatrixSymbol("A", 3, 3).as_explicit() + B = MatrixSymbol("B", 3, 3).as_explicit() + X = MatrixSymbol("X", 3, 3) + expr = hadamard_product(A, B) + ret = Matrix([i*j for i, j in zip(A, B)]).reshape(3, 3) + assert expr == ret + expr = hadamard_product(A, X, B) + assert expr == HadamardProduct(ret, X) + expr = hadamard_product(eye(3), A) + assert expr == Matrix([[A[0, 0], 0, 0], [0, A[1, 1], 0], [0, 0, A[2, 2]]]) + expr = hadamard_product(eye(3), eye(3)) + assert expr == eye(3) + + +def test_hadamard_power(): + m, n, p = symbols('m, n, p', integer=True) + A = MatrixSymbol('A', m, n) + + assert hadamard_power(A, 1) == A + assert isinstance(hadamard_power(A, 2), HadamardPower) + assert hadamard_power(A, n).T == hadamard_power(A.T, n) + assert hadamard_power(A, n)[0, 0] == A[0, 0]**n + assert hadamard_power(m, n) == m**n + raises(ValueError, lambda: hadamard_power(A, A)) + + +def test_hadamard_power_explicit(): + A = MatrixSymbol('A', 2, 2) + B = MatrixSymbol('B', 2, 2) + a, b = symbols('a b') + + assert HadamardPower(a, b) == a**b + + assert HadamardPower(a, B).as_explicit() == \ + Matrix([ + [a**B[0, 0], a**B[0, 1]], + [a**B[1, 0], a**B[1, 1]]]) + + assert HadamardPower(A, b).as_explicit() == \ + Matrix([ + [A[0, 0]**b, A[0, 1]**b], + [A[1, 0]**b, A[1, 1]**b]]) + + assert HadamardPower(A, B).as_explicit() == \ + Matrix([ + [A[0, 0]**B[0, 0], A[0, 1]**B[0, 1]], + [A[1, 0]**B[1, 0], A[1, 1]**B[1, 1]]]) + + +def test_shape_error(): + A = MatrixSymbol('A', 2, 3) + B = MatrixSymbol('B', 3, 3) + raises(ShapeError, lambda: HadamardProduct(A, B)) + raises(ShapeError, lambda: HadamardPower(A, B)) + A = MatrixSymbol('A', 3, 2) + raises(ShapeError, lambda: HadamardProduct(A, B)) + raises(ShapeError, lambda: HadamardPower(A, B)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_indexing.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_indexing.py new file mode 100644 index 0000000000000000000000000000000000000000..500761f248eef5f627c2a7344a6817aca0b8a802 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_indexing.py @@ -0,0 +1,299 @@ +from sympy.concrete.summations import Sum +from sympy.core.symbol import symbols, Symbol, Dummy +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.matrices.dense import eye +from sympy.matrices.expressions.blockmatrix import BlockMatrix +from sympy.matrices.expressions.hadamard import HadamardPower +from sympy.matrices.expressions.matexpr import (MatrixSymbol, + MatrixExpr, MatrixElement) +from sympy.matrices.expressions.matpow import MatPow +from sympy.matrices.expressions.special import (ZeroMatrix, Identity, + OneMatrix) +from sympy.matrices.expressions.trace import Trace, trace +from sympy.matrices.immutable import ImmutableMatrix +from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct +from sympy.testing.pytest import XFAIL, raises + +k, l, m, n = symbols('k l m n', integer=True) +i, j = symbols('i j', integer=True) + +W = MatrixSymbol('W', k, l) +X = MatrixSymbol('X', l, m) +Y = MatrixSymbol('Y', l, m) +Z = MatrixSymbol('Z', m, n) + +X1 = MatrixSymbol('X1', m, m) +X2 = MatrixSymbol('X2', m, m) +X3 = MatrixSymbol('X3', m, m) +X4 = MatrixSymbol('X4', m, m) + +A = MatrixSymbol('A', 2, 2) +B = MatrixSymbol('B', 2, 2) +x = MatrixSymbol('x', 1, 2) +y = MatrixSymbol('x', 2, 1) + + +def test_symbolic_indexing(): + x12 = X[1, 2] + assert all(s in str(x12) for s in ['1', '2', X.name]) + # We don't care about the exact form of this. We do want to make sure + # that all of these features are present + + +def test_add_index(): + assert (X + Y)[i, j] == X[i, j] + Y[i, j] + + +def test_mul_index(): + assert (A*y)[0, 0] == A[0, 0]*y[0, 0] + A[0, 1]*y[1, 0] + assert (A*B).as_mutable() == (A.as_mutable() * B.as_mutable()) + X = MatrixSymbol('X', n, m) + Y = MatrixSymbol('Y', m, k) + + result = (X*Y)[4,2] + expected = Sum(X[4, i]*Y[i, 2], (i, 0, m - 1)) + assert result.args[0].dummy_eq(expected.args[0], i) + assert result.args[1][1:] == expected.args[1][1:] + + +def test_pow_index(): + Q = MatPow(A, 2) + assert Q[0, 0] == A[0, 0]**2 + A[0, 1]*A[1, 0] + n = symbols("n") + Q2 = A**n + assert Q2[0, 0] == 2*( + -sqrt((A[0, 0] + A[1, 1])**2 - 4*A[0, 0]*A[1, 1] + + 4*A[0, 1]*A[1, 0])/2 + A[0, 0]/2 + A[1, 1]/2 + )**n * \ + A[0, 1]*A[1, 0]/( + (sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + + A[1, 1]**2) + A[0, 0] - A[1, 1])* + sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2) + ) - 2*( + sqrt((A[0, 0] + A[1, 1])**2 - 4*A[0, 0]*A[1, 1] + + 4*A[0, 1]*A[1, 0])/2 + A[0, 0]/2 + A[1, 1]/2 + )**n * A[0, 1]*A[1, 0]/( + (-sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + + A[1, 1]**2) + A[0, 0] - A[1, 1])* + sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2) + ) + + +def test_transpose_index(): + assert X.T[i, j] == X[j, i] + + +def test_Identity_index(): + I = Identity(3) + assert I[0, 0] == I[1, 1] == I[2, 2] == 1 + assert I[1, 0] == I[0, 1] == I[2, 1] == 0 + assert I[i, 0].delta_range == (0, 2) + raises(IndexError, lambda: I[3, 3]) + + +def test_block_index(): + I = Identity(3) + Z = ZeroMatrix(3, 3) + B = BlockMatrix([[I, I], [I, I]]) + e3 = ImmutableMatrix(eye(3)) + BB = BlockMatrix([[e3, e3], [e3, e3]]) + assert B[0, 0] == B[3, 0] == B[0, 3] == B[3, 3] == 1 + assert B[4, 3] == B[5, 1] == 0 + + BB = BlockMatrix([[e3, e3], [e3, e3]]) + assert B.as_explicit() == BB.as_explicit() + + BI = BlockMatrix([[I, Z], [Z, I]]) + + assert BI.as_explicit().equals(eye(6)) + + +def test_block_index_symbolic(): + # Note that these matrices may be zero-sized and indices may be negative, which causes + # all naive simplifications given in the comments to be invalid + A1 = MatrixSymbol('A1', n, k) + A2 = MatrixSymbol('A2', n, l) + A3 = MatrixSymbol('A3', m, k) + A4 = MatrixSymbol('A4', m, l) + A = BlockMatrix([[A1, A2], [A3, A4]]) + assert A[0, 0] == MatrixElement(A, 0, 0) # Cannot be A1[0, 0] + assert A[n - 1, k - 1] == A1[n - 1, k - 1] + assert A[n, k] == A4[0, 0] + assert A[n + m - 1, 0] == MatrixElement(A, n + m - 1, 0) # Cannot be A3[m - 1, 0] + assert A[0, k + l - 1] == MatrixElement(A, 0, k + l - 1) # Cannot be A2[0, l - 1] + assert A[n + m - 1, k + l - 1] == MatrixElement(A, n + m - 1, k + l - 1) # Cannot be A4[m - 1, l - 1] + assert A[i, j] == MatrixElement(A, i, j) + assert A[n + i, k + j] == MatrixElement(A, n + i, k + j) # Cannot be A4[i, j] + assert A[n - i - 1, k - j - 1] == MatrixElement(A, n - i - 1, k - j - 1) # Cannot be A1[n - i - 1, k - j - 1] + + +def test_block_index_symbolic_nonzero(): + # All invalid simplifications from test_block_index_symbolic() that become valid if all + # matrices have nonzero size and all indices are nonnegative + k, l, m, n = symbols('k l m n', integer=True, positive=True) + i, j = symbols('i j', integer=True, nonnegative=True) + A1 = MatrixSymbol('A1', n, k) + A2 = MatrixSymbol('A2', n, l) + A3 = MatrixSymbol('A3', m, k) + A4 = MatrixSymbol('A4', m, l) + A = BlockMatrix([[A1, A2], [A3, A4]]) + assert A[0, 0] == A1[0, 0] + assert A[n + m - 1, 0] == A3[m - 1, 0] + assert A[0, k + l - 1] == A2[0, l - 1] + assert A[n + m - 1, k + l - 1] == A4[m - 1, l - 1] + assert A[i, j] == MatrixElement(A, i, j) + assert A[n + i, k + j] == A4[i, j] + assert A[n - i - 1, k - j - 1] == A1[n - i - 1, k - j - 1] + assert A[2 * n, 2 * k] == A4[n, k] + + +def test_block_index_large(): + n, m, k = symbols('n m k', integer=True, positive=True) + i = symbols('i', integer=True, nonnegative=True) + A1 = MatrixSymbol('A1', n, n) + A2 = MatrixSymbol('A2', n, m) + A3 = MatrixSymbol('A3', n, k) + A4 = MatrixSymbol('A4', m, n) + A5 = MatrixSymbol('A5', m, m) + A6 = MatrixSymbol('A6', m, k) + A7 = MatrixSymbol('A7', k, n) + A8 = MatrixSymbol('A8', k, m) + A9 = MatrixSymbol('A9', k, k) + A = BlockMatrix([[A1, A2, A3], [A4, A5, A6], [A7, A8, A9]]) + assert A[n + i, n + i] == MatrixElement(A, n + i, n + i) + + +@XFAIL +def test_block_index_symbolic_fail(): + # To make this work, symbolic matrix dimensions would need to be somehow assumed nonnegative + # even if the symbols aren't specified as such. Then 2 * n < n would correctly evaluate to + # False in BlockMatrix._entry() + A1 = MatrixSymbol('A1', n, 1) + A2 = MatrixSymbol('A2', m, 1) + A = BlockMatrix([[A1], [A2]]) + assert A[2 * n, 0] == A2[n, 0] + + +def test_slicing(): + A.as_explicit()[0, :] # does not raise an error + + +def test_errors(): + raises(IndexError, lambda: Identity(2)[1, 2, 3, 4, 5]) + raises(IndexError, lambda: Identity(2)[[1, 2, 3, 4, 5]]) + + +def test_matrix_expression_to_indices(): + i, j = symbols("i, j") + i1, i2, i3 = symbols("i_1:4") + + def replace_dummies(expr): + repl = {i: Symbol(i.name) for i in expr.atoms(Dummy)} + return expr.xreplace(repl) + + expr = W*X*Z + assert replace_dummies(expr._entry(i, j)) == \ + Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) + assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr + + expr = Z.T*X.T*W.T + assert replace_dummies(expr._entry(i, j)) == \ + Sum(W[j, i2]*X[i2, i1]*Z[i1, i], (i1, 0, m-1), (i2, 0, l-1)) + assert MatrixExpr.from_index_summation(expr._entry(i, j), i) == expr + + expr = W*X*Z + W*Y*Z + assert replace_dummies(expr._entry(i, j)) == \ + Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) +\ + Sum(W[i, i1]*Y[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) + assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr + + expr = 2*W*X*Z + 3*W*Y*Z + assert replace_dummies(expr._entry(i, j)) == \ + 2*Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) +\ + 3*Sum(W[i, i1]*Y[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) + assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr + + expr = W*(X + Y)*Z + assert replace_dummies(expr._entry(i, j)) == \ + Sum(W[i, i1]*(X[i1, i2] + Y[i1, i2])*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) + assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr + + expr = A*B**2*A + #assert replace_dummies(expr._entry(i, j)) == \ + # Sum(A[i, i1]*B[i1, i2]*B[i2, i3]*A[i3, j], (i1, 0, 1), (i2, 0, 1), (i3, 0, 1)) + + # Check that different dummies are used in sub-multiplications: + expr = (X1*X2 + X2*X1)*X3 + assert replace_dummies(expr._entry(i, j)) == \ + Sum((Sum(X1[i, i2] * X2[i2, i1], (i2, 0, m - 1)) + Sum(X1[i3, i1] * X2[i, i3], (i3, 0, m - 1))) * X3[ + i1, j], (i1, 0, m - 1)) + + +def test_matrix_expression_from_index_summation(): + from sympy.abc import a,b,c,d + A = MatrixSymbol("A", k, k) + B = MatrixSymbol("B", k, k) + C = MatrixSymbol("C", k, k) + w1 = MatrixSymbol("w1", k, 1) + + i0, i1, i2, i3, i4 = symbols("i0:5", cls=Dummy) + + expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m-1)) + assert MatrixExpr.from_index_summation(expr, a) == W*X*Z + expr = Sum(W.T[b,a]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m-1)) + assert MatrixExpr.from_index_summation(expr, a) == W*X*Z + expr = Sum(A[b, a]*B[b, c]*C[c, d], (b, 0, k-1), (c, 0, k-1)) + assert MatrixSymbol.from_index_summation(expr, a) == A.T*B*C + expr = Sum(A[b, a]*B[c, b]*C[c, d], (b, 0, k-1), (c, 0, k-1)) + assert MatrixSymbol.from_index_summation(expr, a) == A.T*B.T*C + expr = Sum(C[c, d]*A[b, a]*B[c, b], (b, 0, k-1), (c, 0, k-1)) + assert MatrixSymbol.from_index_summation(expr, a) == A.T*B.T*C + expr = Sum(A[a, b] + B[a, b], (a, 0, k-1), (b, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, a) == OneMatrix(1, k)*A*OneMatrix(k, 1) + OneMatrix(1, k)*B*OneMatrix(k, 1) + expr = Sum(A[a, b]**2, (a, 0, k - 1), (b, 0, k - 1)) + assert MatrixExpr.from_index_summation(expr, a) == Trace(A * A.T) + expr = Sum(A[a, b]**3, (a, 0, k - 1), (b, 0, k - 1)) + assert MatrixExpr.from_index_summation(expr, a) == Trace(HadamardPower(A.T, 2) * A) + expr = Sum((A[a, b] + B[a, b])*C[b, c], (b, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, a) == (A+B)*C + expr = Sum((A[a, b] + B[b, a])*C[b, c], (b, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, a) == (A+B.T)*C + expr = Sum(A[a, b]*A[b, c]*A[c, d], (b, 0, k-1), (c, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, a) == A**3 + expr = Sum(A[a, b]*A[b, c]*B[c, d], (b, 0, k-1), (c, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, a) == A**2*B + + # Parse the trace of a matrix: + + expr = Sum(A[a, a], (a, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, None) == trace(A) + expr = Sum(A[a, a]*B[b, c]*C[c, d], (a, 0, k-1), (c, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, b) == trace(A)*B*C + + # Check wrong sum ranges (should raise an exception): + + ## Case 1: 0 to m instead of 0 to m-1 + expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m)) + raises(ValueError, lambda: MatrixExpr.from_index_summation(expr, a)) + ## Case 2: 1 to m-1 instead of 0 to m-1 + expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 1, m-1)) + raises(ValueError, lambda: MatrixExpr.from_index_summation(expr, a)) + + # Parse nested sums: + expr = Sum(A[a, b]*Sum(B[b, c]*C[c, d], (c, 0, k-1)), (b, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, a) == A*B*C + + # Test Kronecker delta: + expr = Sum(A[a, b]*KroneckerDelta(b, c)*B[c, d], (b, 0, k-1), (c, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, a) == A*B + + expr = Sum(KroneckerDelta(i1, m)*KroneckerDelta(i2, n)*A[i, i1]*A[j, i2], (i1, 0, k-1), (i2, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, m) == ArrayTensorProduct(A.T, A) + + # Test numbered indices: + expr = Sum(A[i1, i2]*w1[i2, 0], (i2, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, i1) == MatrixElement(A*w1, i1, 0) + + expr = Sum(A[i1, i2]*B[i2, 0], (i2, 0, k-1)) + assert MatrixExpr.from_index_summation(expr, i1) == MatrixElement(A*B, i1, 0) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_inverse.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_inverse.py new file mode 100644 index 0000000000000000000000000000000000000000..4bcc7d4de2b2bee4c4922bda8bc48a52aa205961 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_inverse.py @@ -0,0 +1,69 @@ +from sympy.core import symbols, S +from sympy.matrices.expressions import MatrixSymbol, Inverse, MatPow, ZeroMatrix, OneMatrix +from sympy.matrices.exceptions import NonInvertibleMatrixError, NonSquareMatrixError +from sympy.matrices import eye, Identity +from sympy.testing.pytest import raises +from sympy.assumptions.ask import Q +from sympy.assumptions.refine import refine + +n, m, l = symbols('n m l', integer=True) +A = MatrixSymbol('A', n, m) +B = MatrixSymbol('B', m, l) +C = MatrixSymbol('C', n, n) +D = MatrixSymbol('D', n, n) +E = MatrixSymbol('E', m, n) + + +def test_inverse(): + assert Inverse(C).args == (C, S.NegativeOne) + assert Inverse(C).shape == (n, n) + assert Inverse(A*E).shape == (n, n) + assert Inverse(E*A).shape == (m, m) + assert Inverse(C).inverse() == C + assert Inverse(Inverse(C)).doit() == C + assert isinstance(Inverse(Inverse(C)), Inverse) + + assert Inverse(*Inverse(E*A).args) == Inverse(E*A) + + assert C.inverse().inverse() == C + + assert C.inverse()*C == Identity(C.rows) + + assert Identity(n).inverse() == Identity(n) + assert (3*Identity(n)).inverse() == Identity(n)/3 + + # Simplifies Muls if possible (i.e. submatrices are square) + assert (C*D).inverse() == D.I*C.I + # But still works when not possible + assert isinstance((A*E).inverse(), Inverse) + assert Inverse(C*D).doit(inv_expand=False) == Inverse(C*D) + + assert Inverse(eye(3)).doit() == eye(3) + assert Inverse(eye(3)).doit(deep=False) == eye(3) + + assert OneMatrix(1, 1).I == Identity(1) + assert isinstance(OneMatrix(n, n).I, Inverse) + +def test_inverse_non_invertible(): + raises(NonInvertibleMatrixError, lambda: ZeroMatrix(n, n).I) + raises(NonInvertibleMatrixError, lambda: OneMatrix(2, 2).I) + +def test_refine(): + assert refine(C.I, Q.orthogonal(C)) == C.T + + +def test_inverse_matpow_canonicalization(): + A = MatrixSymbol('A', 3, 3) + assert Inverse(MatPow(A, 3)).doit() == MatPow(Inverse(A), 3).doit() + + +def test_nonsquare_error(): + A = MatrixSymbol('A', 3, 4) + raises(NonSquareMatrixError, lambda: Inverse(A)) + + +def test_adjoint_trnaspose_conjugate(): + A = MatrixSymbol('A', n, n) + assert A.transpose().inverse() == A.inverse().transpose() + assert A.conjugate().inverse() == A.inverse().conjugate() + assert A.adjoint().inverse() == A.inverse().adjoint() diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_kronecker.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_kronecker.py new file mode 100644 index 0000000000000000000000000000000000000000..b4444716a76a52e3638dd7a36238a9f459179083 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_kronecker.py @@ -0,0 +1,150 @@ +from sympy.core.mod import Mod +from sympy.core.numbers import I +from sympy.core.symbol import symbols +from sympy.functions.elementary.integers import floor +from sympy.matrices.dense import (Matrix, eye) +from sympy.matrices import MatrixSymbol, Identity +from sympy.matrices.expressions import det, trace + +from sympy.matrices.expressions.kronecker import (KroneckerProduct, + kronecker_product, + combine_kronecker) + + +mat1 = Matrix([[1, 2 * I], [1 + I, 3]]) +mat2 = Matrix([[2 * I, 3], [4 * I, 2]]) + +i, j, k, n, m, o, p, x = symbols('i,j,k,n,m,o,p,x') +Z = MatrixSymbol('Z', n, n) +W = MatrixSymbol('W', m, m) +A = MatrixSymbol('A', n, m) +B = MatrixSymbol('B', n, m) +C = MatrixSymbol('C', m, k) + + +def test_KroneckerProduct(): + assert isinstance(KroneckerProduct(A, B), KroneckerProduct) + assert KroneckerProduct(A, B).subs(A, C) == KroneckerProduct(C, B) + assert KroneckerProduct(A, C).shape == (n*m, m*k) + assert (KroneckerProduct(A, C) + KroneckerProduct(-A, C)).is_ZeroMatrix + assert (KroneckerProduct(W, Z) * KroneckerProduct(W.I, Z.I)).is_Identity + + +def test_KroneckerProduct_identity(): + assert KroneckerProduct(Identity(m), Identity(n)) == Identity(m*n) + assert KroneckerProduct(eye(2), eye(3)) == eye(6) + + +def test_KroneckerProduct_explicit(): + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + kp = KroneckerProduct(X, Y) + assert kp.shape == (4, 4) + assert kp.as_explicit() == Matrix( + [ + [X[0, 0]*Y[0, 0], X[0, 0]*Y[0, 1], X[0, 1]*Y[0, 0], X[0, 1]*Y[0, 1]], + [X[0, 0]*Y[1, 0], X[0, 0]*Y[1, 1], X[0, 1]*Y[1, 0], X[0, 1]*Y[1, 1]], + [X[1, 0]*Y[0, 0], X[1, 0]*Y[0, 1], X[1, 1]*Y[0, 0], X[1, 1]*Y[0, 1]], + [X[1, 0]*Y[1, 0], X[1, 0]*Y[1, 1], X[1, 1]*Y[1, 0], X[1, 1]*Y[1, 1]] + ] + ) + + +def test_tensor_product_adjoint(): + assert KroneckerProduct(I*A, B).adjoint() == \ + -I*KroneckerProduct(A.adjoint(), B.adjoint()) + assert KroneckerProduct(mat1, mat2).adjoint() == \ + kronecker_product(mat1.adjoint(), mat2.adjoint()) + + +def test_tensor_product_conjugate(): + assert KroneckerProduct(I*A, B).conjugate() == \ + -I*KroneckerProduct(A.conjugate(), B.conjugate()) + assert KroneckerProduct(mat1, mat2).conjugate() == \ + kronecker_product(mat1.conjugate(), mat2.conjugate()) + + +def test_tensor_product_transpose(): + assert KroneckerProduct(I*A, B).transpose() == \ + I*KroneckerProduct(A.transpose(), B.transpose()) + assert KroneckerProduct(mat1, mat2).transpose() == \ + kronecker_product(mat1.transpose(), mat2.transpose()) + + +def test_KroneckerProduct_is_associative(): + assert kronecker_product(A, kronecker_product( + B, C)) == kronecker_product(kronecker_product(A, B), C) + assert kronecker_product(A, kronecker_product( + B, C)) == KroneckerProduct(A, B, C) + + +def test_KroneckerProduct_is_bilinear(): + assert kronecker_product(x*A, B) == x*kronecker_product(A, B) + assert kronecker_product(A, x*B) == x*kronecker_product(A, B) + + +def test_KroneckerProduct_determinant(): + kp = kronecker_product(W, Z) + assert det(kp) == det(W)**n * det(Z)**m + + +def test_KroneckerProduct_trace(): + kp = kronecker_product(W, Z) + assert trace(kp) == trace(W)*trace(Z) + + +def test_KroneckerProduct_isnt_commutative(): + assert KroneckerProduct(A, B) != KroneckerProduct(B, A) + assert KroneckerProduct(A, B).is_commutative is False + + +def test_KroneckerProduct_extracts_commutative_part(): + assert kronecker_product(x * A, 2 * B) == x * \ + 2 * KroneckerProduct(A, B) + + +def test_KroneckerProduct_inverse(): + kp = kronecker_product(W, Z) + assert kp.inverse() == kronecker_product(W.inverse(), Z.inverse()) + + +def test_KroneckerProduct_combine_add(): + kp1 = kronecker_product(A, B) + kp2 = kronecker_product(C, W) + assert combine_kronecker(kp1*kp2) == kronecker_product(A*C, B*W) + + +def test_KroneckerProduct_combine_mul(): + X = MatrixSymbol('X', m, n) + Y = MatrixSymbol('Y', m, n) + kp1 = kronecker_product(A, X) + kp2 = kronecker_product(B, Y) + assert combine_kronecker(kp1+kp2) == kronecker_product(A+B, X+Y) + + +def test_KroneckerProduct_combine_pow(): + X = MatrixSymbol('X', n, n) + Y = MatrixSymbol('Y', n, n) + assert combine_kronecker(KroneckerProduct( + X, Y)**x) == KroneckerProduct(X**x, Y**x) + assert combine_kronecker(x * KroneckerProduct(X, Y) + ** 2) == x * KroneckerProduct(X**2, Y**2) + assert combine_kronecker( + x * (KroneckerProduct(X, Y)**2) * KroneckerProduct(A, B)) == x * KroneckerProduct(X**2 * A, Y**2 * B) + # cannot simplify because of non-square arguments to kronecker product: + assert combine_kronecker(KroneckerProduct(A, B.T) ** m) == KroneckerProduct(A, B.T) ** m + + +def test_KroneckerProduct_expand(): + X = MatrixSymbol('X', n, n) + Y = MatrixSymbol('Y', n, n) + + assert KroneckerProduct(X + Y, Y + Z).expand(kroneckerproduct=True) == \ + KroneckerProduct(X, Y) + KroneckerProduct(X, Z) + \ + KroneckerProduct(Y, Y) + KroneckerProduct(Y, Z) + +def test_KroneckerProduct_entry(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', o, p) + + assert KroneckerProduct(A, B)._entry(i, j) == A[Mod(floor(i/o), n), Mod(floor(j/p), m)]*B[Mod(i, o), Mod(j, p)] diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matadd.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matadd.py new file mode 100644 index 0000000000000000000000000000000000000000..43229ae8c2e42f0253a5f3eceefa5fffe7a99f29 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matadd.py @@ -0,0 +1,58 @@ +from sympy.matrices.expressions import MatrixSymbol, MatAdd, MatPow, MatMul +from sympy.matrices.expressions.special import GenericZeroMatrix, ZeroMatrix +from sympy.matrices.exceptions import ShapeError +from sympy.matrices import eye, ImmutableMatrix +from sympy.core import Add, Basic, S +from sympy.core.add import add +from sympy.testing.pytest import XFAIL, raises + +X = MatrixSymbol('X', 2, 2) +Y = MatrixSymbol('Y', 2, 2) + +def test_evaluate(): + assert MatAdd(X, X, evaluate=True) == add(X, X, evaluate=True) == MatAdd(X, X).doit() + +def test_sort_key(): + assert MatAdd(Y, X).doit().args == add(Y, X).doit().args == (X, Y) + + +def test_matadd_sympify(): + assert isinstance(MatAdd(eye(1), eye(1)).args[0], Basic) + assert isinstance(add(eye(1), eye(1)).args[0], Basic) + + +def test_matadd_of_matrices(): + assert MatAdd(eye(2), 4*eye(2), eye(2)).doit() == ImmutableMatrix(6*eye(2)) + assert add(eye(2), 4*eye(2), eye(2)).doit() == ImmutableMatrix(6*eye(2)) + + +def test_doit_args(): + A = ImmutableMatrix([[1, 2], [3, 4]]) + B = ImmutableMatrix([[2, 3], [4, 5]]) + assert MatAdd(A, MatPow(B, 2)).doit() == A + B**2 + assert MatAdd(A, MatMul(A, B)).doit() == A + A*B + assert (MatAdd(A, X, MatMul(A, B), Y, MatAdd(2*A, B)).doit() == + add(A, X, MatMul(A, B), Y, add(2*A, B)).doit() == + MatAdd(3*A + A*B + B, X, Y)) + + +def test_generic_identity(): + assert MatAdd.identity == GenericZeroMatrix() + assert MatAdd.identity != S.Zero + + +def test_zero_matrix_add(): + assert Add(ZeroMatrix(2, 2), ZeroMatrix(2, 2)) == ZeroMatrix(2, 2) + +@XFAIL +def test_matrix_Add_with_scalar(): + raises(TypeError, lambda: Add(0, ZeroMatrix(2, 2))) + + +def test_shape_error(): + A = MatrixSymbol('A', 2, 3) + B = MatrixSymbol('B', 3, 3) + raises(ShapeError, lambda: MatAdd(A, B)) + + A = MatrixSymbol('A', 3, 2) + raises(ShapeError, lambda: MatAdd(A, B)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matexpr.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matexpr.py new file mode 100644 index 0000000000000000000000000000000000000000..f2319e8d8097c2ad3519eab783c4665623c55b80 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matexpr.py @@ -0,0 +1,592 @@ +from sympy.concrete.summations import Sum +from sympy.core.exprtools import gcd_terms +from sympy.core.function import (diff, expand) +from sympy.core.relational import Eq +from sympy.core.symbol import (Dummy, Symbol, Str) +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.matrices.dense import zeros +from sympy.polys.polytools import factor + +from sympy.core import (S, symbols, Add, Mul, SympifyError, Rational, + Function) +from sympy.functions import sin, cos, tan, sqrt, cbrt, exp +from sympy.simplify import simplify +from sympy.matrices import (ImmutableMatrix, Inverse, MatAdd, MatMul, + MatPow, Matrix, MatrixExpr, MatrixSymbol, + SparseMatrix, Transpose, Adjoint, MatrixSet) +from sympy.matrices.exceptions import NonSquareMatrixError +from sympy.matrices.expressions.determinant import Determinant, det +from sympy.matrices.expressions.matexpr import MatrixElement +from sympy.matrices.expressions.special import ZeroMatrix, Identity +from sympy.testing.pytest import raises, XFAIL, skip +from importlib.metadata import version + +n, m, l, k, p = symbols('n m l k p', integer=True) +x = symbols('x') +A = MatrixSymbol('A', n, m) +B = MatrixSymbol('B', m, l) +C = MatrixSymbol('C', n, n) +D = MatrixSymbol('D', n, n) +E = MatrixSymbol('E', m, n) +w = MatrixSymbol('w', n, 1) + + +def test_matrix_symbol_creation(): + assert MatrixSymbol('A', 2, 2) + assert MatrixSymbol('A', 0, 0) + raises(ValueError, lambda: MatrixSymbol('A', -1, 2)) + raises(ValueError, lambda: MatrixSymbol('A', 2.0, 2)) + raises(ValueError, lambda: MatrixSymbol('A', 2j, 2)) + raises(ValueError, lambda: MatrixSymbol('A', 2, -1)) + raises(ValueError, lambda: MatrixSymbol('A', 2, 2.0)) + raises(ValueError, lambda: MatrixSymbol('A', 2, 2j)) + + n = symbols('n') + assert MatrixSymbol('A', n, n) + n = symbols('n', integer=False) + raises(ValueError, lambda: MatrixSymbol('A', n, n)) + n = symbols('n', negative=True) + raises(ValueError, lambda: MatrixSymbol('A', n, n)) + + +def test_matexpr_properties(): + assert A.shape == (n, m) + assert (A * B).shape == (n, l) + assert A[0, 1].indices == (0, 1) + assert A[0, 0].symbol == A + assert A[0, 0].symbol.name == 'A' + + +def test_matexpr(): + assert (x*A).shape == A.shape + assert (x*A).__class__ == MatMul + assert 2*A - A - A == ZeroMatrix(*A.shape) + assert (A*B).shape == (n, l) + + +def test_matexpr_subs(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', m, l) + C = MatrixSymbol('C', m, l) + + assert A.subs(n, m).shape == (m, m) + assert (A*B).subs(B, C) == A*C + assert (A*B).subs(l, n).is_square + + W = MatrixSymbol("W", 3, 3) + X = MatrixSymbol("X", 2, 2) + Y = MatrixSymbol("Y", 1, 2) + Z = MatrixSymbol("Z", n, 2) + # no restrictions on Symbol replacement + assert X.subs(X, Y) == Y + # it might be better to just change the name + y = Str('y') + assert X.subs(Str("X"), y).args == (y, 2, 2) + # it's ok to introduce a wider matrix + assert X[1, 1].subs(X, W) == W[1, 1] + # but for a given MatrixExpression, only change + # name if indexing on the new shape is valid. + # Here, X is 2,2; Y is 1,2 and Y[1, 1] is out + # of range so an error is raised + raises(IndexError, lambda: X[1, 1].subs(X, Y)) + # here, [0, 1] is in range so the subs succeeds + assert X[0, 1].subs(X, Y) == Y[0, 1] + # and here the size of n will accept any index + # in the first position + assert W[2, 1].subs(W, Z) == Z[2, 1] + # but not in the second position + raises(IndexError, lambda: W[2, 2].subs(W, Z)) + # any matrix should raise if invalid + raises(IndexError, lambda: W[2, 2].subs(W, zeros(2))) + + A = SparseMatrix([[1, 2], [3, 4]]) + B = Matrix([[1, 2], [3, 4]]) + C, D = MatrixSymbol('C', 2, 2), MatrixSymbol('D', 2, 2) + + assert (C*D).subs({C: A, D: B}) == MatMul(A, B) + + +def test_addition(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', n, m) + + assert isinstance(A + B, MatAdd) + assert (A + B).shape == A.shape + assert isinstance(A - A + 2*B, MatMul) + + raises(TypeError, lambda: A + 1) + raises(TypeError, lambda: 5 + A) + raises(TypeError, lambda: 5 - A) + + assert A + ZeroMatrix(n, m) - A == ZeroMatrix(n, m) + raises(TypeError, lambda: ZeroMatrix(n, m) + S.Zero) + + +def test_multiplication(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', m, l) + C = MatrixSymbol('C', n, n) + + assert (2*A*B).shape == (n, l) + assert (A*0*B) == ZeroMatrix(n, l) + assert (2*A).shape == A.shape + + assert A * ZeroMatrix(m, m) * B == ZeroMatrix(n, l) + + assert C * Identity(n) * C.I == Identity(n) + + assert B/2 == S.Half*B + raises(NotImplementedError, lambda: 2/B) + + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', n, n) + assert Identity(n) * (A + B) == A + B + + assert A**2*A == A**3 + assert A**2*(A.I)**3 == A.I + assert A**3*(A.I)**2 == A + + +def test_MatPow(): + A = MatrixSymbol('A', n, n) + + AA = MatPow(A, 2) + assert AA.exp == 2 + assert AA.base == A + assert (A**n).exp == n + + assert A**0 == Identity(n) + assert A**1 == A + assert A**2 == AA + assert A**-1 == Inverse(A) + assert (A**-1)**-1 == A + assert (A**2)**3 == A**6 + assert A**S.Half == sqrt(A) + assert A**Rational(1, 3) == cbrt(A) + raises(NonSquareMatrixError, lambda: MatrixSymbol('B', 3, 2)**2) + + +def test_MatrixSymbol(): + n, m, t = symbols('n,m,t') + X = MatrixSymbol('X', n, m) + assert X.shape == (n, m) + raises(TypeError, lambda: MatrixSymbol('X', n, m)(t)) # issue 5855 + assert X.doit() == X + + +def test_dense_conversion(): + X = MatrixSymbol('X', 2, 2) + assert ImmutableMatrix(X) == ImmutableMatrix(2, 2, lambda i, j: X[i, j]) + assert Matrix(X) == Matrix(2, 2, lambda i, j: X[i, j]) + + +def test_free_symbols(): + assert (C*D).free_symbols == {C, D} + + +def test_zero_matmul(): + assert isinstance(S.Zero * MatrixSymbol('X', 2, 2), MatrixExpr) + + +def test_matadd_simplify(): + A = MatrixSymbol('A', 1, 1) + assert simplify(MatAdd(A, ImmutableMatrix([[sin(x)**2 + cos(x)**2]]))) == \ + MatAdd(A, Matrix([[1]])) + + +def test_matmul_simplify(): + A = MatrixSymbol('A', 1, 1) + assert simplify(MatMul(A, ImmutableMatrix([[sin(x)**2 + cos(x)**2]]))) == \ + MatMul(A, Matrix([[1]])) + + +def test_invariants(): + A = MatrixSymbol('A', n, m) + B = MatrixSymbol('B', m, l) + X = MatrixSymbol('X', n, n) + objs = [Identity(n), ZeroMatrix(m, n), A, MatMul(A, B), MatAdd(A, A), + Transpose(A), Adjoint(A), Inverse(X), MatPow(X, 2), MatPow(X, -1), + MatPow(X, 0)] + for obj in objs: + assert obj == obj.__class__(*obj.args) + + +def test_matexpr_indexing(): + A = MatrixSymbol('A', n, m) + A[1, 2] + A[l, k] + A[l + 1, k + 1] + A = MatrixSymbol('A', 2, 1) + for i in range(-2, 2): + for j in range(-1, 1): + A[i, j] + + +def test_single_indexing(): + A = MatrixSymbol('A', 2, 3) + assert A[1] == A[0, 1] + assert A[int(1)] == A[0, 1] + assert A[3] == A[1, 0] + assert list(A[:2, :2]) == [A[0, 0], A[0, 1], A[1, 0], A[1, 1]] + raises(IndexError, lambda: A[6]) + raises(IndexError, lambda: A[n]) + B = MatrixSymbol('B', n, m) + raises(IndexError, lambda: B[1]) + B = MatrixSymbol('B', n, 3) + assert B[3] == B[1, 0] + + +def test_MatrixElement_commutative(): + assert A[0, 1]*A[1, 0] == A[1, 0]*A[0, 1] + + +def test_MatrixSymbol_determinant(): + A = MatrixSymbol('A', 4, 4) + assert A.as_explicit().det() == A[0, 0]*A[1, 1]*A[2, 2]*A[3, 3] - \ + A[0, 0]*A[1, 1]*A[2, 3]*A[3, 2] - A[0, 0]*A[1, 2]*A[2, 1]*A[3, 3] + \ + A[0, 0]*A[1, 2]*A[2, 3]*A[3, 1] + A[0, 0]*A[1, 3]*A[2, 1]*A[3, 2] - \ + A[0, 0]*A[1, 3]*A[2, 2]*A[3, 1] - A[0, 1]*A[1, 0]*A[2, 2]*A[3, 3] + \ + A[0, 1]*A[1, 0]*A[2, 3]*A[3, 2] + A[0, 1]*A[1, 2]*A[2, 0]*A[3, 3] - \ + A[0, 1]*A[1, 2]*A[2, 3]*A[3, 0] - A[0, 1]*A[1, 3]*A[2, 0]*A[3, 2] + \ + A[0, 1]*A[1, 3]*A[2, 2]*A[3, 0] + A[0, 2]*A[1, 0]*A[2, 1]*A[3, 3] - \ + A[0, 2]*A[1, 0]*A[2, 3]*A[3, 1] - A[0, 2]*A[1, 1]*A[2, 0]*A[3, 3] + \ + A[0, 2]*A[1, 1]*A[2, 3]*A[3, 0] + A[0, 2]*A[1, 3]*A[2, 0]*A[3, 1] - \ + A[0, 2]*A[1, 3]*A[2, 1]*A[3, 0] - A[0, 3]*A[1, 0]*A[2, 1]*A[3, 2] + \ + A[0, 3]*A[1, 0]*A[2, 2]*A[3, 1] + A[0, 3]*A[1, 1]*A[2, 0]*A[3, 2] - \ + A[0, 3]*A[1, 1]*A[2, 2]*A[3, 0] - A[0, 3]*A[1, 2]*A[2, 0]*A[3, 1] + \ + A[0, 3]*A[1, 2]*A[2, 1]*A[3, 0] + + B = MatrixSymbol('B', 4, 4) + assert Determinant(A + B).doit() == det(A + B) == (A + B).det() + + +def test_MatrixElement_diff(): + assert (A[3, 0]*A[0, 0]).diff(A[0, 0]) == A[3, 0] + + +def test_MatrixElement_doit(): + u = MatrixSymbol('u', 2, 1) + v = ImmutableMatrix([3, 5]) + assert u[0, 0].subs(u, v).doit() == v[0, 0] + + +def test_identity_powers(): + M = Identity(n) + assert MatPow(M, 3).doit() == M**3 + assert M**n == M + assert MatPow(M, 0).doit() == M**2 + assert M**-2 == M + assert MatPow(M, -2).doit() == M**0 + N = Identity(3) + assert MatPow(N, 2).doit() == N**n + assert MatPow(N, 3).doit() == N + assert MatPow(N, -2).doit() == N**4 + assert MatPow(N, 2).doit() == N**0 + + +def test_Zero_power(): + z1 = ZeroMatrix(n, n) + assert z1**4 == z1 + raises(ValueError, lambda:z1**-2) + assert z1**0 == Identity(n) + assert MatPow(z1, 2).doit() == z1**2 + raises(ValueError, lambda:MatPow(z1, -2).doit()) + z2 = ZeroMatrix(3, 3) + assert MatPow(z2, 4).doit() == z2**4 + raises(ValueError, lambda:z2**-3) + assert z2**3 == MatPow(z2, 3).doit() + assert z2**0 == Identity(3) + raises(ValueError, lambda:MatPow(z2, -1).doit()) + + +def test_matrixelement_diff(): + dexpr = diff((D*w)[k,0], w[p,0]) + + assert w[k, p].diff(w[k, p]) == 1 + assert w[k, p].diff(w[0, 0]) == KroneckerDelta(0, k, (0, n-1))*KroneckerDelta(0, p, (0, 0)) + _i_1 = Dummy("_i_1") + assert dexpr.dummy_eq(Sum(KroneckerDelta(_i_1, p, (0, n-1))*D[k, _i_1], (_i_1, 0, n - 1))) + assert dexpr.doit() == D[k, p] + + +def test_MatrixElement_with_values(): + x, y, z, w = symbols("x y z w") + M = Matrix([[x, y], [z, w]]) + i, j = symbols("i, j") + Mij = M[i, j] + assert isinstance(Mij, MatrixElement) + Ms = SparseMatrix([[2, 3], [4, 5]]) + msij = Ms[i, j] + assert isinstance(msij, MatrixElement) + for oi, oj in [(0, 0), (0, 1), (1, 0), (1, 1)]: + assert Mij.subs({i: oi, j: oj}) == M[oi, oj] + assert msij.subs({i: oi, j: oj}) == Ms[oi, oj] + A = MatrixSymbol("A", 2, 2) + assert A[0, 0].subs(A, M) == x + assert A[i, j].subs(A, M) == M[i, j] + assert M[i, j].subs(M, A) == A[i, j] + + assert isinstance(M[3*i - 2, j], MatrixElement) + assert M[3*i - 2, j].subs({i: 1, j: 0}) == M[1, 0] + assert isinstance(M[i, 0], MatrixElement) + assert M[i, 0].subs(i, 0) == M[0, 0] + assert M[0, i].subs(i, 1) == M[0, 1] + + assert M[i, j].diff(x) == Matrix([[1, 0], [0, 0]])[i, j] + + raises(ValueError, lambda: M[i, 2]) + raises(ValueError, lambda: M[i, -1]) + raises(ValueError, lambda: M[2, i]) + raises(ValueError, lambda: M[-1, i]) + + +def test_inv(): + B = MatrixSymbol('B', 3, 3) + assert B.inv() == B**-1 + + # https://github.com/sympy/sympy/issues/19162 + X = MatrixSymbol('X', 1, 1).as_explicit() + assert X.inv() == Matrix([[1/X[0, 0]]]) + + X = MatrixSymbol('X', 2, 2).as_explicit() + detX = X[0, 0]*X[1, 1] - X[0, 1]*X[1, 0] + invX = Matrix([[ X[1, 1], -X[0, 1]], + [-X[1, 0], X[0, 0]]]) / detX + assert X.inv() == invX + + +@XFAIL +def test_factor_expand(): + A = MatrixSymbol("A", n, n) + B = MatrixSymbol("B", n, n) + expr1 = (A + B)*(C + D) + expr2 = A*C + B*C + A*D + B*D + assert expr1 != expr2 + assert expand(expr1) == expr2 + assert factor(expr2) == expr1 + + expr = B**(-1)*(A**(-1)*B**(-1) - A**(-1)*C*B**(-1))**(-1)*A**(-1) + I = Identity(n) + # Ideally we get the first, but we at least don't want a wrong answer + assert factor(expr) in [I - C, B**-1*(A**-1*(I - C)*B**-1)**-1*A**-1] + +def test_numpy_conversion(): + try: + from numpy import array, array_equal + except ImportError: + skip('NumPy must be available to test creating matrices from ndarrays') + A = MatrixSymbol('A', 2, 2) + np_array = array([[MatrixElement(A, 0, 0), MatrixElement(A, 0, 1)], + [MatrixElement(A, 1, 0), MatrixElement(A, 1, 1)]]) + assert array_equal(array(A), np_array) + assert array_equal(array(A, copy=True), np_array) + if(int(version('numpy').split('.')[0]) >= 2): #run this test only if numpy is new enough that copy variable is passed properly. + raises(TypeError, lambda: array(A, copy=False)) + +def test_issue_2749(): + A = MatrixSymbol("A", 5, 2) + assert (A.T * A).I.as_explicit() == Matrix([[(A.T * A).I[0, 0], (A.T * A).I[0, 1]], \ + [(A.T * A).I[1, 0], (A.T * A).I[1, 1]]]) + + +def test_issue_2750(): + x = MatrixSymbol('x', 1, 1) + assert (x.T*x).as_explicit()**-1 == Matrix([[x[0, 0]**(-2)]]) + + +def test_issue_7842(): + A = MatrixSymbol('A', 3, 1) + B = MatrixSymbol('B', 2, 1) + assert Eq(A, B) == False + assert Eq(A[1,0], B[1, 0]).func is Eq + A = ZeroMatrix(2, 3) + B = ZeroMatrix(2, 3) + assert Eq(A, B) == True + + +def test_issue_21195(): + t = symbols('t') + x = Function('x')(t) + dx = x.diff(t) + exp1 = cos(x) + cos(x)*dx + exp2 = sin(x) + tan(x)*(dx.diff(t)) + exp3 = sin(x)*sin(t)*(dx.diff(t)).diff(t) + A = Matrix([[exp1], [exp2], [exp3]]) + B = Matrix([[exp1.diff(x)], [exp2.diff(x)], [exp3.diff(x)]]) + assert A.diff(x) == B + + +def test_issue_24859(): + A = MatrixSymbol('A', 2, 3) + B = MatrixSymbol('B', 3, 2) + J = A*B + Jinv = Matrix(J).adjugate() + u = MatrixSymbol('u', 2, 3) + Jk = Jinv.subs(A, A + x*u) + + expected = B[0, 1]*u[1, 0] + B[1, 1]*u[1, 1] + B[2, 1]*u[1, 2] + assert Jk[0, 0].diff(x) == expected + assert diff(Jk[0, 0], x).doit() == expected + + +def test_MatMul_postprocessor(): + z = zeros(2) + z1 = ZeroMatrix(2, 2) + assert Mul(0, z) == Mul(z, 0) in [z, z1] + + M = Matrix([[1, 2], [3, 4]]) + Mx = Matrix([[x, 2*x], [3*x, 4*x]]) + assert Mul(x, M) == Mul(M, x) == Mx + + A = MatrixSymbol("A", 2, 2) + assert Mul(A, M) == MatMul(A, M) + assert Mul(M, A) == MatMul(M, A) + # Scalars should be absorbed into constant matrices + a = Mul(x, M, A) + b = Mul(M, x, A) + c = Mul(M, A, x) + assert a == b == c == MatMul(Mx, A) + a = Mul(x, A, M) + b = Mul(A, x, M) + c = Mul(A, M, x) + assert a == b == c == MatMul(A, Mx) + assert Mul(M, M) == M**2 + assert Mul(A, M, M) == MatMul(A, M**2) + assert Mul(M, M, A) == MatMul(M**2, A) + assert Mul(M, A, M) == MatMul(M, A, M) + + assert Mul(A, x, M, M, x) == MatMul(A, Mx**2) + + +@XFAIL +def test_MatAdd_postprocessor_xfail(): + # This is difficult to get working because of the way that Add processes + # its args. + z = zeros(2) + assert Add(z, S.NaN) == Add(S.NaN, z) + + +def test_MatAdd_postprocessor(): + # Some of these are nonsensical, but we do not raise errors for Add + # because that breaks algorithms that want to replace matrices with dummy + # symbols. + + z = zeros(2) + + assert Add(0, z) == Add(z, 0) == z + + a = Add(S.Infinity, z) + assert a == Add(z, S.Infinity) + assert isinstance(a, Add) + assert a.args == (S.Infinity, z) + + a = Add(S.ComplexInfinity, z) + assert a == Add(z, S.ComplexInfinity) + assert isinstance(a, Add) + assert a.args == (S.ComplexInfinity, z) + + a = Add(z, S.NaN) + # assert a == Add(S.NaN, z) # See the XFAIL above + assert isinstance(a, Add) + assert a.args == (S.NaN, z) + + M = Matrix([[1, 2], [3, 4]]) + a = Add(x, M) + assert a == Add(M, x) + assert isinstance(a, Add) + assert a.args == (x, M) + + A = MatrixSymbol("A", 2, 2) + assert Add(A, M) == Add(M, A) == A + M + + # Scalars should be absorbed into constant matrices (producing an error) + a = Add(x, M, A) + assert a == Add(M, x, A) == Add(M, A, x) == Add(x, A, M) == Add(A, x, M) == Add(A, M, x) + assert isinstance(a, Add) + assert a.args == (x, A + M) + + assert Add(M, M) == 2*M + assert Add(M, A, M) == Add(M, M, A) == Add(A, M, M) == A + 2*M + + a = Add(A, x, M, M, x) + assert isinstance(a, Add) + assert a.args == (2*x, A + 2*M) + + +def test_simplify_matrix_expressions(): + # Various simplification functions + assert type(gcd_terms(C*D + D*C)) == MatAdd + a = gcd_terms(2*C*D + 4*D*C) + assert type(a) == MatAdd + assert a.args == (2*C*D, 4*D*C) + + +def test_exp(): + A = MatrixSymbol('A', 2, 2) + B = MatrixSymbol('B', 2, 2) + expr1 = exp(A)*exp(B) + expr2 = exp(B)*exp(A) + assert expr1 != expr2 + assert expr1 - expr2 != 0 + assert not isinstance(expr1, exp) + assert not isinstance(expr2, exp) + + +def test_invalid_args(): + raises(SympifyError, lambda: MatrixSymbol(1, 2, 'A')) + + +def test_matrixsymbol_from_symbol(): + # The label should be preserved during doit and subs + A_label = Symbol('A', complex=True) + A = MatrixSymbol(A_label, 2, 2) + + A_1 = A.doit() + A_2 = A.subs(2, 3) + assert A_1.args == A.args + assert A_2.args[0] == A.args[0] + + +def test_as_explicit(): + Z = MatrixSymbol('Z', 2, 3) + assert Z.as_explicit() == ImmutableMatrix([ + [Z[0, 0], Z[0, 1], Z[0, 2]], + [Z[1, 0], Z[1, 1], Z[1, 2]], + ]) + raises(ValueError, lambda: A.as_explicit()) + + +def test_MatrixSet(): + M = MatrixSet(2, 2, set=S.Reals) + assert M.shape == (2, 2) + assert M.set == S.Reals + X = Matrix([[1, 2], [3, 4]]) + assert X in M + X = ZeroMatrix(2, 2) + assert X in M + raises(TypeError, lambda: A in M) + raises(TypeError, lambda: 1 in M) + M = MatrixSet(n, m, set=S.Reals) + assert A in M + raises(TypeError, lambda: C in M) + raises(TypeError, lambda: X in M) + M = MatrixSet(2, 2, set={1, 2, 3}) + X = Matrix([[1, 2], [3, 4]]) + Y = Matrix([[1, 2]]) + assert (X in M) == S.false + assert (Y in M) == S.false + raises(ValueError, lambda: MatrixSet(2, -2, S.Reals)) + raises(ValueError, lambda: MatrixSet(2.4, -1, S.Reals)) + raises(TypeError, lambda: MatrixSet(2, 2, (1, 2, 3))) + + +def test_matrixsymbol_solving(): + A = MatrixSymbol('A', 2, 2) + B = MatrixSymbol('B', 2, 2) + Z = ZeroMatrix(2, 2) + assert -(-A + B) - A + B == Z + assert (-(-A + B) - A + B).simplify() == Z + assert (-(-A + B) - A + B).expand() == Z + assert (-(-A + B) - A + B - Z).simplify() == Z + assert (-(-A + B) - A + B - Z).expand() == Z + assert (A*(A + B) + B*(A.T + B.T)).expand() == A**2 + A*B + B*A.T + B*B.T diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matmul.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matmul.py new file mode 100644 index 0000000000000000000000000000000000000000..813926e2c83e27716f4f894ebebd09b2a576f046 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matmul.py @@ -0,0 +1,193 @@ +from sympy.core import I, symbols, Basic, Mul, S +from sympy.core.mul import mul +from sympy.functions import adjoint, transpose +from sympy.matrices.exceptions import ShapeError +from sympy.matrices import (Identity, Inverse, Matrix, MatrixSymbol, ZeroMatrix, + eye, ImmutableMatrix) +from sympy.matrices.expressions import Adjoint, Transpose, det, MatPow +from sympy.matrices.expressions.special import GenericIdentity +from sympy.matrices.expressions.matmul import (factor_in_front, remove_ids, + MatMul, combine_powers, any_zeros, unpack, only_squares) +from sympy.strategies import null_safe +from sympy.assumptions.ask import Q +from sympy.assumptions.refine import refine +from sympy.core.symbol import Symbol + +from sympy.testing.pytest import XFAIL, raises + +n, m, l, k = symbols('n m l k', integer=True) +x = symbols('x') +A = MatrixSymbol('A', n, m) +B = MatrixSymbol('B', m, l) +C = MatrixSymbol('C', n, n) +D = MatrixSymbol('D', n, n) +E = MatrixSymbol('E', m, n) + +def test_evaluate(): + assert MatMul(C, C, evaluate=True) == MatMul(C, C).doit() + +def test_adjoint(): + assert adjoint(A*B) == Adjoint(B)*Adjoint(A) + assert adjoint(2*A*B) == 2*Adjoint(B)*Adjoint(A) + assert adjoint(2*I*C) == -2*I*Adjoint(C) + + M = Matrix(2, 2, [1, 2 + I, 3, 4]) + MA = Matrix(2, 2, [1, 3, 2 - I, 4]) + assert adjoint(M) == MA + assert adjoint(2*M) == 2*MA + assert adjoint(MatMul(2, M)) == MatMul(2, MA).doit() + + +def test_transpose(): + assert transpose(A*B) == Transpose(B)*Transpose(A) + assert transpose(2*A*B) == 2*Transpose(B)*Transpose(A) + assert transpose(2*I*C) == 2*I*Transpose(C) + + M = Matrix(2, 2, [1, 2 + I, 3, 4]) + MT = Matrix(2, 2, [1, 3, 2 + I, 4]) + assert transpose(M) == MT + assert transpose(2*M) == 2*MT + assert transpose(x*M) == x*MT + assert transpose(MatMul(2, M)) == MatMul(2, MT).doit() + + +def test_factor_in_front(): + assert factor_in_front(MatMul(A, 2, B, evaluate=False)) ==\ + MatMul(2, A, B, evaluate=False) + + +def test_remove_ids(): + assert remove_ids(MatMul(A, Identity(m), B, evaluate=False)) == \ + MatMul(A, B, evaluate=False) + assert null_safe(remove_ids)(MatMul(Identity(n), evaluate=False)) == \ + MatMul(Identity(n), evaluate=False) + + +def test_combine_powers(): + assert combine_powers(MatMul(D, Inverse(D), D, evaluate=False)) == \ + MatMul(Identity(n), D, evaluate=False) + assert combine_powers(MatMul(B.T, Inverse(E*A), E, A, B, evaluate=False)) == \ + MatMul(B.T, Identity(m), B, evaluate=False) + assert combine_powers(MatMul(A, E, Inverse(A*E), D, evaluate=False)) == \ + MatMul(Identity(n), D, evaluate=False) + + +def test_any_zeros(): + assert any_zeros(MatMul(A, ZeroMatrix(m, k), evaluate=False)) == \ + ZeroMatrix(n, k) + + +def test_unpack(): + assert unpack(MatMul(A, evaluate=False)) == A + x = MatMul(A, B) + assert unpack(x) == x + + +def test_only_squares(): + assert only_squares(C) == [C] + assert only_squares(C, D) == [C, D] + assert only_squares(C, A, A.T, D) == [C, A*A.T, D] + + +def test_determinant(): + assert det(2*C) == 2**n*det(C) + assert det(2*C*D) == 2**n*det(C)*det(D) + assert det(3*C*A*A.T*D) == 3**n*det(C)*det(A*A.T)*det(D) + + +def test_doit(): + assert MatMul(C, 2, D).args == (C, 2, D) + assert MatMul(C, 2, D).doit().args == (2, C, D) + assert MatMul(C, Transpose(D*C)).args == (C, Transpose(D*C)) + assert MatMul(C, Transpose(D*C)).doit(deep=True).args == (C, C.T, D.T) + + +def test_doit_drills_down(): + X = ImmutableMatrix([[1, 2], [3, 4]]) + Y = ImmutableMatrix([[2, 3], [4, 5]]) + assert MatMul(X, MatPow(Y, 2)).doit() == X*Y**2 + assert MatMul(C, Transpose(D*C)).doit().args == (C, C.T, D.T) + + +def test_doit_deep_false_still_canonical(): + assert (MatMul(C, Transpose(D*C), 2).doit(deep=False).args == + (2, C, Transpose(D*C))) + + +def test_matmul_scalar_Matrix_doit(): + # Issue 9053 + X = Matrix([[1, 2], [3, 4]]) + assert MatMul(2, X).doit() == 2*X + + +def test_matmul_sympify(): + assert isinstance(MatMul(eye(1), eye(1)).args[0], Basic) + + +def test_collapse_MatrixBase(): + A = Matrix([[1, 1], [1, 1]]) + B = Matrix([[1, 2], [3, 4]]) + assert MatMul(A, B).doit() == ImmutableMatrix([[4, 6], [4, 6]]) + + +def test_refine(): + assert refine(C*C.T*D, Q.orthogonal(C)).doit() == D + + kC = k*C + assert refine(kC*C.T, Q.orthogonal(C)).doit() == k*Identity(n) + assert refine(kC* kC.T, Q.orthogonal(C)).doit() == (k**2)*Identity(n) + +def test_matmul_no_matrices(): + assert MatMul(1) == 1 + assert MatMul(n, m) == n*m + assert not isinstance(MatMul(n, m), MatMul) + +def test_matmul_args_cnc(): + assert MatMul(n, A, A.T).args_cnc() == [[n], [A, A.T]] + assert MatMul(A, A.T).args_cnc() == [[], [A, A.T]] + +@XFAIL +def test_matmul_args_cnc_symbols(): + # Not currently supported + a, b = symbols('a b', commutative=False) + assert MatMul(n, a, b, A, A.T).args_cnc() == [[n], [a, b, A, A.T]] + assert MatMul(n, a, A, b, A.T).args_cnc() == [[n], [a, A, b, A.T]] + +def test_issue_12950(): + M = Matrix([[Symbol("x")]]) * MatrixSymbol("A", 1, 1) + assert MatrixSymbol("A", 1, 1).as_explicit()[0]*Symbol('x') == M.as_explicit()[0] + +def test_construction_with_Mul(): + assert Mul(C, D) == MatMul(C, D) + assert Mul(D, C) == MatMul(D, C) + +def test_construction_with_mul(): + assert mul(C, D) == MatMul(C, D) + assert mul(D, C) == MatMul(D, C) + assert mul(C, D) != MatMul(D, C) + +def test_generic_identity(): + assert MatMul.identity == GenericIdentity() + assert MatMul.identity != S.One + + +def test_issue_23519(): + N = Symbol("N", integer=True) + M1 = MatrixSymbol("M1", N, N) + M2 = MatrixSymbol("M2", N, N) + I = Identity(N) + z = (M2 + 2 * (M2 + I) * M1 + I) + assert z.coeff(M1) == 2*I + 2*M2 + + +def test_shape_error(): + A = MatrixSymbol('A', 2, 2) + B = MatrixSymbol('B', 3, 3) + raises(ShapeError, lambda: MatMul(A, B)) + + +def test_matmul_transpose(): + # https://github.com/sympy/sympy/issues/9503 + M = Matrix(2, 2, [1, 2 + I, 3, 4]) + a = Symbol('a') + assert (MatMul(a, M).T).expand() == (a*Matrix([[1, 3],[2 + I, 4]])).expand() diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matpow.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matpow.py new file mode 100644 index 0000000000000000000000000000000000000000..2afb5fdc2aa652c321de52aba43db63da60941fd --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_matpow.py @@ -0,0 +1,217 @@ +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.simplify.powsimp import powsimp +from sympy.testing.pytest import raises +from sympy.core.expr import unchanged +from sympy.core import symbols, S +from sympy.matrices import Identity, MatrixSymbol, ImmutableMatrix, ZeroMatrix, OneMatrix, Matrix +from sympy.matrices.exceptions import NonSquareMatrixError +from sympy.matrices.expressions import MatPow, MatAdd, MatMul +from sympy.matrices.expressions.inverse import Inverse +from sympy.matrices.expressions.matexpr import MatrixElement + +n, m, l, k = symbols('n m l k', integer=True) +A = MatrixSymbol('A', n, m) +B = MatrixSymbol('B', m, l) +C = MatrixSymbol('C', n, n) +D = MatrixSymbol('D', n, n) +E = MatrixSymbol('E', m, n) + + +def test_entry_matrix(): + X = ImmutableMatrix([[1, 2], [3, 4]]) + assert MatPow(X, 0)[0, 0] == 1 + assert MatPow(X, 0)[0, 1] == 0 + assert MatPow(X, 1)[0, 0] == 1 + assert MatPow(X, 1)[0, 1] == 2 + assert MatPow(X, 2)[0, 0] == 7 + + +def test_entry_symbol(): + from sympy.concrete import Sum + assert MatPow(C, 0)[0, 0] == 1 + assert MatPow(C, 0)[0, 1] == 0 + assert MatPow(C, 1)[0, 0] == C[0, 0] + assert isinstance(MatPow(C, 2)[0, 0], Sum) + assert isinstance(MatPow(C, n)[0, 0], MatrixElement) + + +def test_as_explicit_symbol(): + X = MatrixSymbol('X', 2, 2) + assert MatPow(X, 0).as_explicit() == ImmutableMatrix(Identity(2)) + assert MatPow(X, 1).as_explicit() == X.as_explicit() + assert MatPow(X, 2).as_explicit() == (X.as_explicit())**2 + assert MatPow(X, n).as_explicit() == ImmutableMatrix([ + [(X ** n)[0, 0], (X ** n)[0, 1]], + [(X ** n)[1, 0], (X ** n)[1, 1]], + ]) + + a = MatrixSymbol("a", 3, 1) + b = MatrixSymbol("b", 3, 1) + c = MatrixSymbol("c", 3, 1) + + expr = (a.T*b)**S.Half + assert expr.as_explicit() == Matrix([[sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0])]]) + + expr = c*(a.T*b)**S.Half + m = sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0]) + assert expr.as_explicit() == Matrix([[c[0, 0]*m], [c[1, 0]*m], [c[2, 0]*m]]) + + expr = (a*b.T)**S.Half + denom = sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0]) + expected = (a*b.T).as_explicit()/denom + assert expr.as_explicit() == expected + + expr = X**-1 + det = X[0, 0]*X[1, 1] - X[1, 0]*X[0, 1] + expected = Matrix([[X[1, 1], -X[0, 1]], [-X[1, 0], X[0, 0]]])/det + assert expr.as_explicit() == expected + + expr = X**m + assert expr.as_explicit() == X.as_explicit()**m + + +def test_as_explicit_matrix(): + A = ImmutableMatrix([[1, 2], [3, 4]]) + assert MatPow(A, 0).as_explicit() == ImmutableMatrix(Identity(2)) + assert MatPow(A, 1).as_explicit() == A + assert MatPow(A, 2).as_explicit() == A**2 + assert MatPow(A, -1).as_explicit() == A.inv() + assert MatPow(A, -2).as_explicit() == (A.inv())**2 + # less expensive than testing on a 2x2 + A = ImmutableMatrix([4]) + assert MatPow(A, S.Half).as_explicit() == A**S.Half + + +def test_doit_symbol(): + assert MatPow(C, 0).doit() == Identity(n) + assert MatPow(C, 1).doit() == C + assert MatPow(C, -1).doit() == C.I + for r in [2, S.Half, S.Pi, n]: + assert MatPow(C, r).doit() == MatPow(C, r) + + +def test_doit_matrix(): + X = ImmutableMatrix([[1, 2], [3, 4]]) + assert MatPow(X, 0).doit() == ImmutableMatrix(Identity(2)) + assert MatPow(X, 1).doit() == X + assert MatPow(X, 2).doit() == X**2 + assert MatPow(X, -1).doit() == X.inv() + assert MatPow(X, -2).doit() == (X.inv())**2 + # less expensive than testing on a 2x2 + assert MatPow(ImmutableMatrix([4]), S.Half).doit() == ImmutableMatrix([2]) + X = ImmutableMatrix([[0, 2], [0, 4]]) # det() == 0 + raises(ValueError, lambda: MatPow(X,-1).doit()) + raises(ValueError, lambda: MatPow(X,-2).doit()) + + +def test_nonsquare(): + A = MatrixSymbol('A', 2, 3) + B = ImmutableMatrix([[1, 2, 3], [4, 5, 6]]) + for r in [-1, 0, 1, 2, S.Half, S.Pi, n]: + raises(NonSquareMatrixError, lambda: MatPow(A, r)) + raises(NonSquareMatrixError, lambda: MatPow(B, r)) + + +def test_doit_equals_pow(): #17179 + X = ImmutableMatrix ([[1,0],[0,1]]) + assert MatPow(X, n).doit() == X**n == X + + +def test_doit_nested_MatrixExpr(): + X = ImmutableMatrix([[1, 2], [3, 4]]) + Y = ImmutableMatrix([[2, 3], [4, 5]]) + assert MatPow(MatMul(X, Y), 2).doit() == (X*Y)**2 + assert MatPow(MatAdd(X, Y), 2).doit() == (X + Y)**2 + + +def test_identity_power(): + k = Identity(n) + assert MatPow(k, 4).doit() == k + assert MatPow(k, n).doit() == k + assert MatPow(k, -3).doit() == k + assert MatPow(k, 0).doit() == k + l = Identity(3) + assert MatPow(l, n).doit() == l + assert MatPow(l, -1).doit() == l + assert MatPow(l, 0).doit() == l + + +def test_zero_power(): + z1 = ZeroMatrix(n, n) + assert MatPow(z1, 3).doit() == z1 + raises(ValueError, lambda:MatPow(z1, -1).doit()) + assert MatPow(z1, 0).doit() == Identity(n) + assert MatPow(z1, n).doit() == z1 + raises(ValueError, lambda:MatPow(z1, -2).doit()) + z2 = ZeroMatrix(4, 4) + assert MatPow(z2, n).doit() == z2 + raises(ValueError, lambda:MatPow(z2, -3).doit()) + assert MatPow(z2, 2).doit() == z2 + assert MatPow(z2, 0).doit() == Identity(4) + raises(ValueError, lambda:MatPow(z2, -1).doit()) + + +def test_OneMatrix_power(): + o = OneMatrix(3, 3) + assert o ** 0 == Identity(3) + assert o ** 1 == o + assert o * o == o ** 2 == 3 * o + assert o * o * o == o ** 3 == 9 * o + + o = OneMatrix(n, n) + assert o * o == o ** 2 == n * o + # powsimp necessary as n ** (n - 2) * n does not produce n ** (n - 1) + assert powsimp(o ** (n - 1) * o) == o ** n == n ** (n - 1) * o + + +def test_transpose_power(): + from sympy.matrices.expressions.transpose import Transpose as TP + + assert (C*D).T**5 == ((C*D)**5).T == (D.T * C.T)**5 + assert ((C*D).T**5).T == (C*D)**5 + + assert (C.T.I.T)**7 == C**-7 + assert (C.T**l).T**k == C**(l*k) + + assert ((E.T * A.T)**5).T == (A*E)**5 + assert ((A*E).T**5).T**7 == (A*E)**35 + assert TP(TP(C**2 * D**3)**5).doit() == (C**2 * D**3)**5 + + assert ((D*C)**-5).T**-5 == ((D*C)**25).T + assert (((D*C)**l).T**k).T == (D*C)**(l*k) + + +def test_Inverse(): + assert Inverse(MatPow(C, 0)).doit() == Identity(n) + assert Inverse(MatPow(C, 1)).doit() == Inverse(C) + assert Inverse(MatPow(C, 2)).doit() == MatPow(C, -2) + assert Inverse(MatPow(C, -1)).doit() == C + + assert MatPow(Inverse(C), 0).doit() == Identity(n) + assert MatPow(Inverse(C), 1).doit() == Inverse(C) + assert MatPow(Inverse(C), 2).doit() == MatPow(C, -2) + assert MatPow(Inverse(C), -1).doit() == C + + +def test_combine_powers(): + assert (C ** 1) ** 1 == C + assert (C ** 2) ** 3 == MatPow(C, 6) + assert (C ** -2) ** -3 == MatPow(C, 6) + assert (C ** -1) ** -1 == C + assert (((C ** 2) ** 3) ** 4) ** 5 == MatPow(C, 120) + assert (C ** n) ** n == C ** (n ** 2) + + +def test_unchanged(): + assert unchanged(MatPow, C, 0) + assert unchanged(MatPow, C, 1) + assert unchanged(MatPow, Inverse(C), -1) + assert unchanged(Inverse, MatPow(C, -1), -1) + assert unchanged(MatPow, MatPow(C, -1), -1) + assert unchanged(MatPow, MatPow(C, 1), 1) + + +def test_no_exponentiation(): + # if this passes, Pow.as_numer_denom should recognize + # MatAdd as exponent + raises(NotImplementedError, lambda: 3**(-2*C)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_permutation.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_permutation.py new file mode 100644 index 0000000000000000000000000000000000000000..41a924f6636afb2e5b6560987e38a0fa0c861f1e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_permutation.py @@ -0,0 +1,166 @@ +from sympy.combinatorics import Permutation +from sympy.core.expr import unchanged +from sympy.matrices import Matrix +from sympy.matrices.expressions import \ + MatMul, BlockDiagMatrix, Determinant, Inverse +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import ZeroMatrix, OneMatrix, Identity +from sympy.matrices.expressions.permutation import \ + MatrixPermute, PermutationMatrix +from sympy.testing.pytest import raises +from sympy.core.symbol import Symbol + + +def test_PermutationMatrix_basic(): + p = Permutation([1, 0]) + assert unchanged(PermutationMatrix, p) + raises(ValueError, lambda: PermutationMatrix((0, 1, 2))) + assert PermutationMatrix(p).as_explicit() == Matrix([[0, 1], [1, 0]]) + assert isinstance(PermutationMatrix(p)*MatrixSymbol('A', 2, 2), MatMul) + + +def test_PermutationMatrix_matmul(): + p = Permutation([1, 2, 0]) + P = PermutationMatrix(p) + M = Matrix([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) + assert (P*M).as_explicit() == P.as_explicit()*M + assert (M*P).as_explicit() == M*P.as_explicit() + + P1 = PermutationMatrix(Permutation([1, 2, 0])) + P2 = PermutationMatrix(Permutation([2, 1, 0])) + P3 = PermutationMatrix(Permutation([1, 0, 2])) + assert P1*P2 == P3 + + +def test_PermutationMatrix_matpow(): + p1 = Permutation([1, 2, 0]) + P1 = PermutationMatrix(p1) + p2 = Permutation([2, 0, 1]) + P2 = PermutationMatrix(p2) + assert P1**2 == P2 + assert P1**3 == Identity(3) + + +def test_PermutationMatrix_identity(): + p = Permutation([0, 1]) + assert PermutationMatrix(p).is_Identity + + p = Permutation([1, 0]) + assert not PermutationMatrix(p).is_Identity + + +def test_PermutationMatrix_determinant(): + P = PermutationMatrix(Permutation([0, 1, 2])) + assert Determinant(P).doit() == 1 + P = PermutationMatrix(Permutation([0, 2, 1])) + assert Determinant(P).doit() == -1 + P = PermutationMatrix(Permutation([2, 0, 1])) + assert Determinant(P).doit() == 1 + + +def test_PermutationMatrix_inverse(): + P = PermutationMatrix(Permutation(0, 1, 2)) + assert Inverse(P).doit() == PermutationMatrix(Permutation(0, 2, 1)) + + +def test_PermutationMatrix_rewrite_BlockDiagMatrix(): + P = PermutationMatrix(Permutation([0, 1, 2, 3, 4, 5])) + P0 = PermutationMatrix(Permutation([0])) + assert P.rewrite(BlockDiagMatrix) == \ + BlockDiagMatrix(P0, P0, P0, P0, P0, P0) + + P = PermutationMatrix(Permutation([0, 1, 3, 2, 4, 5])) + P10 = PermutationMatrix(Permutation(0, 1)) + assert P.rewrite(BlockDiagMatrix) == \ + BlockDiagMatrix(P0, P0, P10, P0, P0) + + P = PermutationMatrix(Permutation([1, 0, 3, 2, 5, 4])) + assert P.rewrite(BlockDiagMatrix) == \ + BlockDiagMatrix(P10, P10, P10) + + P = PermutationMatrix(Permutation([0, 4, 3, 2, 1, 5])) + P3210 = PermutationMatrix(Permutation([3, 2, 1, 0])) + assert P.rewrite(BlockDiagMatrix) == \ + BlockDiagMatrix(P0, P3210, P0) + + P = PermutationMatrix(Permutation([0, 4, 2, 3, 1, 5])) + P3120 = PermutationMatrix(Permutation([3, 1, 2, 0])) + assert P.rewrite(BlockDiagMatrix) == \ + BlockDiagMatrix(P0, P3120, P0) + + P = PermutationMatrix(Permutation(0, 3)(1, 4)(2, 5)) + assert P.rewrite(BlockDiagMatrix) == BlockDiagMatrix(P) + + +def test_MartrixPermute_basic(): + p = Permutation(0, 1) + P = PermutationMatrix(p) + A = MatrixSymbol('A', 2, 2) + + raises(ValueError, lambda: MatrixPermute(Symbol('x'), p)) + raises(ValueError, lambda: MatrixPermute(A, Symbol('x'))) + + assert MatrixPermute(A, P) == MatrixPermute(A, p) + raises(ValueError, lambda: MatrixPermute(A, p, 2)) + + pp = Permutation(0, 1, size=3) + assert MatrixPermute(A, pp) == MatrixPermute(A, p) + pp = Permutation(0, 1, 2) + raises(ValueError, lambda: MatrixPermute(A, pp)) + + +def test_MatrixPermute_shape(): + p = Permutation(0, 1) + A = MatrixSymbol('A', 2, 3) + assert MatrixPermute(A, p).shape == (2, 3) + + +def test_MatrixPermute_explicit(): + p = Permutation(0, 1, 2) + A = MatrixSymbol('A', 3, 3) + AA = A.as_explicit() + assert MatrixPermute(A, p, 0).as_explicit() == \ + AA.permute(p, orientation='rows') + assert MatrixPermute(A, p, 1).as_explicit() == \ + AA.permute(p, orientation='cols') + + +def test_MatrixPermute_rewrite_MatMul(): + p = Permutation(0, 1, 2) + A = MatrixSymbol('A', 3, 3) + + assert MatrixPermute(A, p, 0).rewrite(MatMul).as_explicit() == \ + MatrixPermute(A, p, 0).as_explicit() + assert MatrixPermute(A, p, 1).rewrite(MatMul).as_explicit() == \ + MatrixPermute(A, p, 1).as_explicit() + + +def test_MatrixPermute_doit(): + p = Permutation(0, 1, 2) + A = MatrixSymbol('A', 3, 3) + assert MatrixPermute(A, p).doit() == MatrixPermute(A, p) + + p = Permutation(0, size=3) + A = MatrixSymbol('A', 3, 3) + assert MatrixPermute(A, p).doit().as_explicit() == \ + MatrixPermute(A, p).as_explicit() + + p = Permutation(0, 1, 2) + A = Identity(3) + assert MatrixPermute(A, p, 0).doit().as_explicit() == \ + MatrixPermute(A, p, 0).as_explicit() + assert MatrixPermute(A, p, 1).doit().as_explicit() == \ + MatrixPermute(A, p, 1).as_explicit() + + A = ZeroMatrix(3, 3) + assert MatrixPermute(A, p).doit() == A + A = OneMatrix(3, 3) + assert MatrixPermute(A, p).doit() == A + + A = MatrixSymbol('A', 4, 4) + p1 = Permutation(0, 1, 2, 3) + p2 = Permutation(0, 2, 3, 1) + expr = MatrixPermute(MatrixPermute(A, p1, 0), p2, 0) + assert expr.as_explicit() == expr.doit().as_explicit() + expr = MatrixPermute(MatrixPermute(A, p1, 1), p2, 1) + assert expr.as_explicit() == expr.doit().as_explicit() diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_sets.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_sets.py new file mode 100644 index 0000000000000000000000000000000000000000..e811c7968c5a22d65f1c99e995aaa7e5e59d15c4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_sets.py @@ -0,0 +1,42 @@ +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.matrices import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.sets import MatrixSet +from sympy.matrices.expressions.special import ZeroMatrix +from sympy.testing.pytest import raises +from sympy.sets.sets import SetKind +from sympy.matrices.kind import MatrixKind +from sympy.core.kind import NumberKind + + +def test_MatrixSet(): + n, m = symbols('n m', integer=True) + A = MatrixSymbol('A', n, m) + C = MatrixSymbol('C', n, n) + + M = MatrixSet(2, 2, set=S.Reals) + assert M.shape == (2, 2) + assert M.set == S.Reals + X = Matrix([[1, 2], [3, 4]]) + assert X in M + X = ZeroMatrix(2, 2) + assert X in M + raises(TypeError, lambda: A in M) + raises(TypeError, lambda: 1 in M) + M = MatrixSet(n, m, set=S.Reals) + assert A in M + raises(TypeError, lambda: C in M) + raises(TypeError, lambda: X in M) + M = MatrixSet(2, 2, set={1, 2, 3}) + X = Matrix([[1, 2], [3, 4]]) + Y = Matrix([[1, 2]]) + assert (X in M) == S.false + assert (Y in M) == S.false + raises(ValueError, lambda: MatrixSet(2, -2, S.Reals)) + raises(ValueError, lambda: MatrixSet(2.4, -1, S.Reals)) + raises(TypeError, lambda: MatrixSet(2, 2, (1, 2, 3))) + + +def test_SetKind_MatrixSet(): + assert MatrixSet(2, 2, set=S.Reals).kind is SetKind(MatrixKind(NumberKind)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_slice.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_slice.py new file mode 100644 index 0000000000000000000000000000000000000000..36490719e26908b9e913ed99b7673d602647c492 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_slice.py @@ -0,0 +1,65 @@ +from sympy.matrices.expressions.slice import MatrixSlice +from sympy.matrices.expressions import MatrixSymbol +from sympy.abc import a, b, c, d, k, l, m, n +from sympy.testing.pytest import raises, XFAIL +from sympy.functions.elementary.integers import floor +from sympy.assumptions import assuming, Q + + +X = MatrixSymbol('X', n, m) +Y = MatrixSymbol('Y', m, k) + +def test_shape(): + B = MatrixSlice(X, (a, b), (c, d)) + assert B.shape == (b - a, d - c) + +def test_entry(): + B = MatrixSlice(X, (a, b), (c, d)) + assert B[0,0] == X[a, c] + assert B[k,l] == X[a+k, c+l] + raises(IndexError, lambda : MatrixSlice(X, 1, (2, 5))[1, 0]) + + assert X[1::2, :][1, 3] == X[1+2, 3] + assert X[:, 1::2][3, 1] == X[3, 1+2] + +def test_on_diag(): + assert not MatrixSlice(X, (a, b), (c, d)).on_diag + assert MatrixSlice(X, (a, b), (a, b)).on_diag + +def test_inputs(): + assert MatrixSlice(X, 1, (2, 5)) == MatrixSlice(X, (1, 2), (2, 5)) + assert MatrixSlice(X, 1, (2, 5)).shape == (1, 3) + +def test_slicing(): + assert X[1:5, 2:4] == MatrixSlice(X, (1, 5), (2, 4)) + assert X[1, 2:4] == MatrixSlice(X, 1, (2, 4)) + assert X[1:5, :].shape == (4, X.shape[1]) + assert X[:, 1:5].shape == (X.shape[0], 4) + + assert X[::2, ::2].shape == (floor(n/2), floor(m/2)) + assert X[2, :] == MatrixSlice(X, 2, (0, m)) + assert X[k, :] == MatrixSlice(X, k, (0, m)) + +def test_exceptions(): + X = MatrixSymbol('x', 10, 20) + raises(IndexError, lambda: X[0:12, 2]) + raises(IndexError, lambda: X[0:9, 22]) + raises(IndexError, lambda: X[-1:5, 2]) + +@XFAIL +def test_symmetry(): + X = MatrixSymbol('x', 10, 10) + Y = X[:5, 5:] + with assuming(Q.symmetric(X)): + assert Y.T == X[5:, :5] + +def test_slice_of_slice(): + X = MatrixSymbol('x', 10, 10) + assert X[2, :][:, 3][0, 0] == X[2, 3] + assert X[:5, :5][:4, :4] == X[:4, :4] + assert X[1:5, 2:6][1:3, 2] == X[2:4, 4] + assert X[1:9:2, 2:6][1:3, 2] == X[3:7:2, 4] + +def test_negative_index(): + X = MatrixSymbol('x', 10, 10) + assert X[-1, :] == X[9, :] diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_special.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_special.py new file mode 100644 index 0000000000000000000000000000000000000000..beeaf1d76a63673b6622709cda598dfcb295bba4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_special.py @@ -0,0 +1,228 @@ +from sympy.core.add import Add +from sympy.core.expr import unchanged +from sympy.core.mul import Mul +from sympy.core.symbol import symbols +from sympy.core.relational import Eq +from sympy.concrete.summations import Sum +from sympy.functions.elementary.complexes import im, re +from sympy.functions.elementary.piecewise import Piecewise +from sympy.matrices.immutable import ImmutableDenseMatrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.matadd import MatAdd +from sympy.matrices.expressions.special import ( + ZeroMatrix, GenericZeroMatrix, Identity, GenericIdentity, OneMatrix) +from sympy.matrices.expressions.matmul import MatMul +from sympy.testing.pytest import raises + + +def test_zero_matrix_creation(): + assert unchanged(ZeroMatrix, 2, 2) + assert unchanged(ZeroMatrix, 0, 0) + raises(ValueError, lambda: ZeroMatrix(-1, 2)) + raises(ValueError, lambda: ZeroMatrix(2.0, 2)) + raises(ValueError, lambda: ZeroMatrix(2j, 2)) + raises(ValueError, lambda: ZeroMatrix(2, -1)) + raises(ValueError, lambda: ZeroMatrix(2, 2.0)) + raises(ValueError, lambda: ZeroMatrix(2, 2j)) + + n = symbols('n') + assert unchanged(ZeroMatrix, n, n) + n = symbols('n', integer=False) + raises(ValueError, lambda: ZeroMatrix(n, n)) + n = symbols('n', negative=True) + raises(ValueError, lambda: ZeroMatrix(n, n)) + + +def test_generic_zero_matrix(): + z = GenericZeroMatrix() + n = symbols('n', integer=True) + A = MatrixSymbol("A", n, n) + + assert z == z + assert z != A + assert A != z + + assert z.is_ZeroMatrix + + raises(TypeError, lambda: z.shape) + raises(TypeError, lambda: z.rows) + raises(TypeError, lambda: z.cols) + + assert MatAdd() == z + assert MatAdd(z, A) == MatAdd(A) + # Make sure it is hashable + hash(z) + + +def test_identity_matrix_creation(): + assert Identity(2) + assert Identity(0) + raises(ValueError, lambda: Identity(-1)) + raises(ValueError, lambda: Identity(2.0)) + raises(ValueError, lambda: Identity(2j)) + + n = symbols('n') + assert Identity(n) + n = symbols('n', integer=False) + raises(ValueError, lambda: Identity(n)) + n = symbols('n', negative=True) + raises(ValueError, lambda: Identity(n)) + + +def test_generic_identity(): + I = GenericIdentity() + n = symbols('n', integer=True) + A = MatrixSymbol("A", n, n) + + assert I == I + assert I != A + assert A != I + + assert I.is_Identity + assert I**-1 == I + + raises(TypeError, lambda: I.shape) + raises(TypeError, lambda: I.rows) + raises(TypeError, lambda: I.cols) + + assert MatMul() == I + assert MatMul(I, A) == MatMul(A) + # Make sure it is hashable + hash(I) + + +def test_one_matrix_creation(): + assert OneMatrix(2, 2) + assert OneMatrix(0, 0) + assert Eq(OneMatrix(1, 1), Identity(1)) + raises(ValueError, lambda: OneMatrix(-1, 2)) + raises(ValueError, lambda: OneMatrix(2.0, 2)) + raises(ValueError, lambda: OneMatrix(2j, 2)) + raises(ValueError, lambda: OneMatrix(2, -1)) + raises(ValueError, lambda: OneMatrix(2, 2.0)) + raises(ValueError, lambda: OneMatrix(2, 2j)) + + n = symbols('n') + assert OneMatrix(n, n) + n = symbols('n', integer=False) + raises(ValueError, lambda: OneMatrix(n, n)) + n = symbols('n', negative=True) + raises(ValueError, lambda: OneMatrix(n, n)) + + +def test_ZeroMatrix(): + n, m = symbols('n m', integer=True) + A = MatrixSymbol('A', n, m) + Z = ZeroMatrix(n, m) + + assert A + Z == A + assert A*Z.T == ZeroMatrix(n, n) + assert Z*A.T == ZeroMatrix(n, n) + assert A - A == ZeroMatrix(*A.shape) + + assert Z + + assert Z.transpose() == ZeroMatrix(m, n) + assert Z.conjugate() == Z + assert Z.adjoint() == ZeroMatrix(m, n) + assert re(Z) == Z + assert im(Z) == Z + + assert ZeroMatrix(n, n)**0 == Identity(n) + assert ZeroMatrix(3, 3).as_explicit() == ImmutableDenseMatrix.zeros(3, 3) + + +def test_ZeroMatrix_doit(): + n = symbols('n', integer=True) + Znn = ZeroMatrix(Add(n, n, evaluate=False), n) + assert isinstance(Znn.rows, Add) + assert Znn.doit() == ZeroMatrix(2*n, n) + assert isinstance(Znn.doit().rows, Mul) + + +def test_OneMatrix(): + n, m = symbols('n m', integer=True) + A = MatrixSymbol('A', n, m) + U = OneMatrix(n, m) + + assert U.shape == (n, m) + assert isinstance(A + U, Add) + assert U.transpose() == OneMatrix(m, n) + assert U.conjugate() == U + assert U.adjoint() == OneMatrix(m, n) + assert re(U) == U + assert im(U) == ZeroMatrix(n, m) + + assert OneMatrix(n, n) ** 0 == Identity(n) + + U = OneMatrix(n, n) + assert U[1, 2] == 1 + + U = OneMatrix(2, 3) + assert U.as_explicit() == ImmutableDenseMatrix.ones(2, 3) + + +def test_OneMatrix_doit(): + n = symbols('n', integer=True) + Unn = OneMatrix(Add(n, n, evaluate=False), n) + assert isinstance(Unn.rows, Add) + assert Unn.doit() == OneMatrix(2 * n, n) + assert isinstance(Unn.doit().rows, Mul) + + +def test_OneMatrix_mul(): + n, m, k = symbols('n m k', integer=True) + w = MatrixSymbol('w', n, 1) + assert OneMatrix(n, m) * OneMatrix(m, k) == OneMatrix(n, k) * m + assert w * OneMatrix(1, 1) == w + assert OneMatrix(1, 1) * w.T == w.T + + +def test_Identity(): + n, m = symbols('n m', integer=True) + A = MatrixSymbol('A', n, m) + i, j = symbols('i j') + + In = Identity(n) + Im = Identity(m) + + assert A*Im == A + assert In*A == A + + assert In.transpose() == In + assert In.inverse() == In + assert In.conjugate() == In + assert In.adjoint() == In + assert re(In) == In + assert im(In) == ZeroMatrix(n, n) + + assert In[i, j] != 0 + assert Sum(In[i, j], (i, 0, n-1), (j, 0, n-1)).subs(n,3).doit() == 3 + assert Sum(Sum(In[i, j], (i, 0, n-1)), (j, 0, n-1)).subs(n,3).doit() == 3 + + # If range exceeds the limit `(0, n-1)`, do not remove `Piecewise`: + expr = Sum(In[i, j], (i, 0, n-1)) + assert expr.doit() == 1 + expr = Sum(In[i, j], (i, 0, n-2)) + assert expr.doit().dummy_eq( + Piecewise( + (1, (j >= 0) & (j <= n-2)), + (0, True) + ) + ) + expr = Sum(In[i, j], (i, 1, n-1)) + assert expr.doit().dummy_eq( + Piecewise( + (1, (j >= 1) & (j <= n-1)), + (0, True) + ) + ) + assert Identity(3).as_explicit() == ImmutableDenseMatrix.eye(3) + + +def test_Identity_doit(): + n = symbols('n', integer=True) + Inn = Identity(Add(n, n, evaluate=False)) + assert isinstance(Inn.rows, Add) + assert Inn.doit() == Identity(2*n) + assert isinstance(Inn.doit().rows, Mul) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_trace.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_trace.py new file mode 100644 index 0000000000000000000000000000000000000000..3bd66bec2377dae634ff486f42cc474eda7b23b1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_trace.py @@ -0,0 +1,116 @@ +from sympy.core import Lambda, S, symbols +from sympy.concrete import Sum +from sympy.functions import adjoint, conjugate, transpose +from sympy.matrices import eye, Matrix, ShapeError, ImmutableMatrix +from sympy.matrices.expressions import ( + Adjoint, Identity, FunctionMatrix, MatrixExpr, MatrixSymbol, Trace, + ZeroMatrix, trace, MatPow, MatAdd, MatMul +) +from sympy.matrices.expressions.special import OneMatrix +from sympy.testing.pytest import raises +from sympy.abc import i + + +n = symbols('n', integer=True) +A = MatrixSymbol('A', n, n) +B = MatrixSymbol('B', n, n) +C = MatrixSymbol('C', 3, 4) + + +def test_Trace(): + assert isinstance(Trace(A), Trace) + assert not isinstance(Trace(A), MatrixExpr) + raises(ShapeError, lambda: Trace(C)) + assert trace(eye(3)) == 3 + assert trace(Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9])) == 15 + + assert adjoint(Trace(A)) == trace(Adjoint(A)) + assert conjugate(Trace(A)) == trace(Adjoint(A)) + assert transpose(Trace(A)) == Trace(A) + + _ = A / Trace(A) # Make sure this is possible + + # Some easy simplifications + assert trace(Identity(5)) == 5 + assert trace(ZeroMatrix(5, 5)) == 0 + assert trace(OneMatrix(1, 1)) == 1 + assert trace(OneMatrix(2, 2)) == 2 + assert trace(OneMatrix(n, n)) == n + assert trace(2*A*B) == 2*Trace(A*B) + assert trace(A.T) == trace(A) + + i, j = symbols('i j') + F = FunctionMatrix(3, 3, Lambda((i, j), i + j)) + assert trace(F) == (0 + 0) + (1 + 1) + (2 + 2) + + raises(TypeError, lambda: Trace(S.One)) + + assert Trace(A).arg is A + + assert str(trace(A)) == str(Trace(A).doit()) + + assert Trace(A).is_commutative is True + +def test_Trace_A_plus_B(): + assert trace(A + B) == Trace(A) + Trace(B) + assert Trace(A + B).arg == MatAdd(A, B) + assert Trace(A + B).doit() == Trace(A) + Trace(B) + + +def test_Trace_MatAdd_doit(): + # See issue #9028 + X = ImmutableMatrix([[1, 2, 3]]*3) + Y = MatrixSymbol('Y', 3, 3) + q = MatAdd(X, 2*X, Y, -3*Y) + assert Trace(q).arg == q + assert Trace(q).doit() == 18 - 2*Trace(Y) + + +def test_Trace_MatPow_doit(): + X = Matrix([[1, 2], [3, 4]]) + assert Trace(X).doit() == 5 + q = MatPow(X, 2) + assert Trace(q).arg == q + assert Trace(q).doit() == 29 + + +def test_Trace_MutableMatrix_plus(): + # See issue #9043 + X = Matrix([[1, 2], [3, 4]]) + assert Trace(X) + Trace(X) == 2*Trace(X) + + +def test_Trace_doit_deep_False(): + X = Matrix([[1, 2], [3, 4]]) + q = MatPow(X, 2) + assert Trace(q).doit(deep=False).arg == q + q = MatAdd(X, 2*X) + assert Trace(q).doit(deep=False).arg == q + q = MatMul(X, 2*X) + assert Trace(q).doit(deep=False).arg == q + + +def test_trace_constant_factor(): + # Issue 9052: gave 2*Trace(MatMul(A)) instead of 2*Trace(A) + assert trace(2*A) == 2*Trace(A) + X = ImmutableMatrix([[1, 2], [3, 4]]) + assert trace(MatMul(2, X)) == 10 + + +def test_trace_rewrite(): + assert trace(A).rewrite(Sum) == Sum(A[i, i], (i, 0, n - 1)) + assert trace(eye(3)).rewrite(Sum) == 3 + + +def test_trace_normalize(): + assert Trace(B*A) != Trace(A*B) + assert Trace(B*A)._normalize() == Trace(A*B) + assert Trace(B*A.T)._normalize() == Trace(A*B.T) + + +def test_trace_as_explicit(): + raises(ValueError, lambda: Trace(A).as_explicit()) + + X = MatrixSymbol("X", 3, 3) + assert Trace(X).as_explicit() == X[0, 0] + X[1, 1] + X[2, 2] + assert Trace(eye(3)).as_explicit() == 3 diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_transpose.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..a1a6113873426d99bacf85484d3b66781f300af7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/tests/test_transpose.py @@ -0,0 +1,69 @@ +from sympy.functions import adjoint, conjugate, transpose +from sympy.matrices.expressions import MatrixSymbol, Adjoint, trace, Transpose +from sympy.matrices import eye, Matrix +from sympy.assumptions.ask import Q +from sympy.assumptions.refine import refine +from sympy.core.singleton import S +from sympy.core.symbol import symbols + +n, m, l, k, p = symbols('n m l k p', integer=True) +A = MatrixSymbol('A', n, m) +B = MatrixSymbol('B', m, l) +C = MatrixSymbol('C', n, n) + + +def test_transpose(): + Sq = MatrixSymbol('Sq', n, n) + + assert transpose(A) == Transpose(A) + assert Transpose(A).shape == (m, n) + assert Transpose(A*B).shape == (l, n) + assert transpose(Transpose(A)) == A + assert isinstance(Transpose(Transpose(A)), Transpose) + + assert adjoint(Transpose(A)) == Adjoint(Transpose(A)) + assert conjugate(Transpose(A)) == Adjoint(A) + + assert Transpose(eye(3)).doit() == eye(3) + + assert Transpose(S(5)).doit() == S(5) + + assert Transpose(Matrix([[1, 2], [3, 4]])).doit() == Matrix([[1, 3], [2, 4]]) + + assert transpose(trace(Sq)) == trace(Sq) + assert trace(Transpose(Sq)) == trace(Sq) + + assert Transpose(Sq)[0, 1] == Sq[1, 0] + + assert Transpose(A*B).doit() == Transpose(B) * Transpose(A) + + +def test_transpose_MatAdd_MatMul(): + # Issue 16807 + from sympy.functions.elementary.trigonometric import cos + + x = symbols('x') + M = MatrixSymbol('M', 3, 3) + N = MatrixSymbol('N', 3, 3) + + assert (N + (cos(x) * M)).T == cos(x)*M.T + N.T + + +def test_refine(): + assert refine(C.T, Q.symmetric(C)) == C + + +def test_transpose1x1(): + m = MatrixSymbol('m', 1, 1) + assert m == refine(m.T) + assert m == refine(m.T.T) + +def test_issue_9817(): + from sympy.matrices.expressions import Identity + v = MatrixSymbol('v', 3, 1) + A = MatrixSymbol('A', 3, 3) + x = Matrix([i + 1 for i in range(3)]) + X = Identity(3) + quadratic = v.T * A * v + subbed = quadratic.xreplace({v:x, A:X}) + assert subbed.as_explicit() == Matrix([[14]]) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/trace.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/trace.py new file mode 100644 index 0000000000000000000000000000000000000000..b5f9f94ea7486dc21b47c2e2e783a93280b180e0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/trace.py @@ -0,0 +1,167 @@ +from sympy.core.basic import Basic +from sympy.core.expr import Expr, ExprBuilder +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import uniquely_named_symbol +from sympy.core.sympify import sympify +from sympy.matrices.matrixbase import MatrixBase +from sympy.matrices.exceptions import NonSquareMatrixError + + +class Trace(Expr): + """Matrix Trace + + Represents the trace of a matrix expression. + + Examples + ======== + + >>> from sympy import MatrixSymbol, Trace, eye + >>> A = MatrixSymbol('A', 3, 3) + >>> Trace(A) + Trace(A) + >>> Trace(eye(3)) + Trace(Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]])) + >>> Trace(eye(3)).simplify() + 3 + """ + is_Trace = True + is_commutative = True + + def __new__(cls, mat): + mat = sympify(mat) + + if not mat.is_Matrix: + raise TypeError("input to Trace, %s, is not a matrix" % str(mat)) + + if mat.is_square is False: + raise NonSquareMatrixError("Trace of a non-square matrix") + + return Basic.__new__(cls, mat) + + def _eval_transpose(self): + return self + + def _eval_derivative(self, v): + from sympy.concrete.summations import Sum + from .matexpr import MatrixElement + if isinstance(v, MatrixElement): + return self.rewrite(Sum).diff(v) + expr = self.doit() + if isinstance(expr, Trace): + # Avoid looping infinitely: + raise NotImplementedError + return expr._eval_derivative(v) + + def _eval_derivative_matrix_lines(self, x): + from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayContraction + r = self.args[0]._eval_derivative_matrix_lines(x) + for lr in r: + if lr.higher == 1: + lr.higher = ExprBuilder( + ArrayContraction, + [ + ExprBuilder( + ArrayTensorProduct, + [ + lr._lines[0], + lr._lines[1], + ] + ), + (1, 3), + ], + validator=ArrayContraction._validate + ) + else: + # This is not a matrix line: + lr.higher = ExprBuilder( + ArrayContraction, + [ + ExprBuilder( + ArrayTensorProduct, + [ + lr._lines[0], + lr._lines[1], + lr.higher, + ] + ), + (1, 3), (0, 2) + ] + ) + lr._lines = [S.One, S.One] + lr._first_pointer_parent = lr._lines + lr._second_pointer_parent = lr._lines + lr._first_pointer_index = 0 + lr._second_pointer_index = 1 + return r + + @property + def arg(self): + return self.args[0] + + def doit(self, **hints): + if hints.get('deep', True): + arg = self.arg.doit(**hints) + result = arg._eval_trace() + if result is not None: + return result + else: + return Trace(arg) + else: + # _eval_trace would go too deep here + if isinstance(self.arg, MatrixBase): + return trace(self.arg) + else: + return Trace(self.arg) + + def as_explicit(self): + return Trace(self.arg.as_explicit()).doit() + + def _normalize(self): + # Normalization of trace of matrix products. Use transposition and + # cyclic properties of traces to make sure the arguments of the matrix + # product are sorted and the first argument is not a transposition. + from sympy.matrices.expressions.matmul import MatMul + from sympy.matrices.expressions.transpose import Transpose + trace_arg = self.arg + if isinstance(trace_arg, MatMul): + + def get_arg_key(x): + a = trace_arg.args[x] + if isinstance(a, Transpose): + a = a.arg + return default_sort_key(a) + + indmin = min(range(len(trace_arg.args)), key=get_arg_key) + if isinstance(trace_arg.args[indmin], Transpose): + trace_arg = Transpose(trace_arg).doit() + indmin = min(range(len(trace_arg.args)), key=lambda x: default_sort_key(trace_arg.args[x])) + trace_arg = MatMul.fromiter(trace_arg.args[indmin:] + trace_arg.args[:indmin]) + return Trace(trace_arg) + return self + + def _eval_rewrite_as_Sum(self, expr, **kwargs): + from sympy.concrete.summations import Sum + i = uniquely_named_symbol('i', [expr]) + s = Sum(self.arg[i, i], (i, 0, self.arg.rows - 1)) + return s.doit() + + +def trace(expr): + """Trace of a Matrix. Sum of the diagonal elements. + + Examples + ======== + + >>> from sympy import trace, Symbol, MatrixSymbol, eye + >>> n = Symbol('n') + >>> X = MatrixSymbol('X', n, n) # A square matrix + >>> trace(2*X) + 2*Trace(X) + >>> trace(eye(3)) + 3 + """ + return Trace(expr).doit() diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/expressions/transpose.py b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..b11f7fc21490aab219420610ca529d81d6995d40 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/expressions/transpose.py @@ -0,0 +1,103 @@ +from sympy.core.basic import Basic +from sympy.matrices.expressions.matexpr import MatrixExpr + + +class Transpose(MatrixExpr): + """ + The transpose of a matrix expression. + + This is a symbolic object that simply stores its argument without + evaluating it. To actually compute the transpose, use the ``transpose()`` + function, or the ``.T`` attribute of matrices. + + Examples + ======== + + >>> from sympy import MatrixSymbol, Transpose, transpose + >>> A = MatrixSymbol('A', 3, 5) + >>> B = MatrixSymbol('B', 5, 3) + >>> Transpose(A) + A.T + >>> A.T == transpose(A) == Transpose(A) + True + >>> Transpose(A*B) + (A*B).T + >>> transpose(A*B) + B.T*A.T + + """ + is_Transpose = True + + def doit(self, **hints): + arg = self.arg + if hints.get('deep', True) and isinstance(arg, Basic): + arg = arg.doit(**hints) + _eval_transpose = getattr(arg, '_eval_transpose', None) + if _eval_transpose is not None: + result = _eval_transpose() + return result if result is not None else Transpose(arg) + else: + return Transpose(arg) + + @property + def arg(self): + return self.args[0] + + @property + def shape(self): + return self.arg.shape[::-1] + + def _entry(self, i, j, expand=False, **kwargs): + return self.arg._entry(j, i, expand=expand, **kwargs) + + def _eval_adjoint(self): + return self.arg.conjugate() + + def _eval_conjugate(self): + return self.arg.adjoint() + + def _eval_transpose(self): + return self.arg + + def _eval_trace(self): + from .trace import Trace + return Trace(self.arg) # Trace(X.T) => Trace(X) + + def _eval_determinant(self): + from sympy.matrices.expressions.determinant import det + return det(self.arg) + + def _eval_derivative(self, x): + # x is a scalar: + return self.arg._eval_derivative(x) + + def _eval_derivative_matrix_lines(self, x): + lines = self.args[0]._eval_derivative_matrix_lines(x) + return [i.transpose() for i in lines] + + +def transpose(expr): + """Matrix transpose""" + return Transpose(expr).doit(deep=False) + + +from sympy.assumptions.ask import ask, Q +from sympy.assumptions.refine import handlers_dict + + +def refine_Transpose(expr, assumptions): + """ + >>> from sympy import MatrixSymbol, Q, assuming, refine + >>> X = MatrixSymbol('X', 2, 2) + >>> X.T + X.T + >>> with assuming(Q.symmetric(X)): + ... print(refine(X.T)) + X + """ + if ask(Q.symmetric(expr), assumptions): + return expr.arg + + return expr + +handlers_dict['Transpose'] = refine_Transpose diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/graph.py b/venv/lib/python3.10/site-packages/sympy/matrices/graph.py new file mode 100644 index 0000000000000000000000000000000000000000..4c6356db884cfcd3c759ada07ac559f43dbcbbcb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/graph.py @@ -0,0 +1,279 @@ +from sympy.utilities.iterables import \ + flatten, connected_components, strongly_connected_components +from .exceptions import NonSquareMatrixError + + +def _connected_components(M): + """Returns the list of connected vertices of the graph when + a square matrix is viewed as a weighted graph. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([ + ... [66, 0, 0, 68, 0, 0, 0, 0, 67], + ... [0, 55, 0, 0, 0, 0, 54, 53, 0], + ... [0, 0, 0, 0, 1, 2, 0, 0, 0], + ... [86, 0, 0, 88, 0, 0, 0, 0, 87], + ... [0, 0, 10, 0, 11, 12, 0, 0, 0], + ... [0, 0, 20, 0, 21, 22, 0, 0, 0], + ... [0, 45, 0, 0, 0, 0, 44, 43, 0], + ... [0, 35, 0, 0, 0, 0, 34, 33, 0], + ... [76, 0, 0, 78, 0, 0, 0, 0, 77]]) + >>> A.connected_components() + [[0, 3, 8], [1, 6, 7], [2, 4, 5]] + + Notes + ===== + + Even if any symbolic elements of the matrix can be indeterminate + to be zero mathematically, this only takes the account of the + structural aspect of the matrix, so they will considered to be + nonzero. + """ + if not M.is_square: + raise NonSquareMatrixError + + V = range(M.rows) + E = sorted(M.todok().keys()) + return connected_components((V, E)) + + +def _strongly_connected_components(M): + """Returns the list of strongly connected vertices of the graph when + a square matrix is viewed as a weighted graph. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([ + ... [44, 0, 0, 0, 43, 0, 45, 0, 0], + ... [0, 66, 62, 61, 0, 68, 0, 60, 67], + ... [0, 0, 22, 21, 0, 0, 0, 20, 0], + ... [0, 0, 12, 11, 0, 0, 0, 10, 0], + ... [34, 0, 0, 0, 33, 0, 35, 0, 0], + ... [0, 86, 82, 81, 0, 88, 0, 80, 87], + ... [54, 0, 0, 0, 53, 0, 55, 0, 0], + ... [0, 0, 2, 1, 0, 0, 0, 0, 0], + ... [0, 76, 72, 71, 0, 78, 0, 70, 77]]) + >>> A.strongly_connected_components() + [[0, 4, 6], [2, 3, 7], [1, 5, 8]] + """ + if not M.is_square: + raise NonSquareMatrixError + + # RepMatrix uses the more efficient DomainMatrix.scc() method + rep = getattr(M, '_rep', None) + if rep is not None: + return rep.scc() + + V = range(M.rows) + E = sorted(M.todok().keys()) + return strongly_connected_components((V, E)) + + +def _connected_components_decomposition(M): + """Decomposes a square matrix into block diagonal form only + using the permutations. + + Explanation + =========== + + The decomposition is in a form of $A = P^{-1} B P$ where $P$ is a + permutation matrix and $B$ is a block diagonal matrix. + + Returns + ======= + + P, B : PermutationMatrix, BlockDiagMatrix + *P* is a permutation matrix for the similarity transform + as in the explanation. And *B* is the block diagonal matrix of + the result of the permutation. + + If you would like to get the diagonal blocks from the + BlockDiagMatrix, see + :meth:`~sympy.matrices.expressions.blockmatrix.BlockDiagMatrix.get_diag_blocks`. + + Examples + ======== + + >>> from sympy import Matrix, pprint + >>> A = Matrix([ + ... [66, 0, 0, 68, 0, 0, 0, 0, 67], + ... [0, 55, 0, 0, 0, 0, 54, 53, 0], + ... [0, 0, 0, 0, 1, 2, 0, 0, 0], + ... [86, 0, 0, 88, 0, 0, 0, 0, 87], + ... [0, 0, 10, 0, 11, 12, 0, 0, 0], + ... [0, 0, 20, 0, 21, 22, 0, 0, 0], + ... [0, 45, 0, 0, 0, 0, 44, 43, 0], + ... [0, 35, 0, 0, 0, 0, 34, 33, 0], + ... [76, 0, 0, 78, 0, 0, 0, 0, 77]]) + + >>> P, B = A.connected_components_decomposition() + >>> pprint(P) + PermutationMatrix((1 3)(2 8 5 7 4 6)) + >>> pprint(B) + [[66 68 67] ] + [[ ] ] + [[86 88 87] 0 0 ] + [[ ] ] + [[76 78 77] ] + [ ] + [ [55 54 53] ] + [ [ ] ] + [ 0 [45 44 43] 0 ] + [ [ ] ] + [ [35 34 33] ] + [ ] + [ [0 1 2 ]] + [ [ ]] + [ 0 0 [10 11 12]] + [ [ ]] + [ [20 21 22]] + + >>> P = P.as_explicit() + >>> B = B.as_explicit() + >>> P.T*B*P == A + True + + Notes + ===== + + This problem corresponds to the finding of the connected components + of a graph, when a matrix is viewed as a weighted graph. + """ + from sympy.combinatorics.permutations import Permutation + from sympy.matrices.expressions.blockmatrix import BlockDiagMatrix + from sympy.matrices.expressions.permutation import PermutationMatrix + + iblocks = M.connected_components() + + p = Permutation(flatten(iblocks)) + P = PermutationMatrix(p) + + blocks = [] + for b in iblocks: + blocks.append(M[b, b]) + B = BlockDiagMatrix(*blocks) + return P, B + + +def _strongly_connected_components_decomposition(M, lower=True): + """Decomposes a square matrix into block triangular form only + using the permutations. + + Explanation + =========== + + The decomposition is in a form of $A = P^{-1} B P$ where $P$ is a + permutation matrix and $B$ is a block diagonal matrix. + + Parameters + ========== + + lower : bool + Makes $B$ lower block triangular when ``True``. + Otherwise, makes $B$ upper block triangular. + + Returns + ======= + + P, B : PermutationMatrix, BlockMatrix + *P* is a permutation matrix for the similarity transform + as in the explanation. And *B* is the block triangular matrix of + the result of the permutation. + + Examples + ======== + + >>> from sympy import Matrix, pprint + >>> A = Matrix([ + ... [44, 0, 0, 0, 43, 0, 45, 0, 0], + ... [0, 66, 62, 61, 0, 68, 0, 60, 67], + ... [0, 0, 22, 21, 0, 0, 0, 20, 0], + ... [0, 0, 12, 11, 0, 0, 0, 10, 0], + ... [34, 0, 0, 0, 33, 0, 35, 0, 0], + ... [0, 86, 82, 81, 0, 88, 0, 80, 87], + ... [54, 0, 0, 0, 53, 0, 55, 0, 0], + ... [0, 0, 2, 1, 0, 0, 0, 0, 0], + ... [0, 76, 72, 71, 0, 78, 0, 70, 77]]) + + A lower block triangular decomposition: + + >>> P, B = A.strongly_connected_components_decomposition() + >>> pprint(P) + PermutationMatrix((8)(1 4 3 2 6)(5 7)) + >>> pprint(B) + [[44 43 45] [0 0 0] [0 0 0] ] + [[ ] [ ] [ ] ] + [[34 33 35] [0 0 0] [0 0 0] ] + [[ ] [ ] [ ] ] + [[54 53 55] [0 0 0] [0 0 0] ] + [ ] + [ [0 0 0] [22 21 20] [0 0 0] ] + [ [ ] [ ] [ ] ] + [ [0 0 0] [12 11 10] [0 0 0] ] + [ [ ] [ ] [ ] ] + [ [0 0 0] [2 1 0 ] [0 0 0] ] + [ ] + [ [0 0 0] [62 61 60] [66 68 67]] + [ [ ] [ ] [ ]] + [ [0 0 0] [82 81 80] [86 88 87]] + [ [ ] [ ] [ ]] + [ [0 0 0] [72 71 70] [76 78 77]] + + >>> P = P.as_explicit() + >>> B = B.as_explicit() + >>> P.T * B * P == A + True + + An upper block triangular decomposition: + + >>> P, B = A.strongly_connected_components_decomposition(lower=False) + >>> pprint(P) + PermutationMatrix((0 1 5 7 4 3 2 8 6)) + >>> pprint(B) + [[66 68 67] [62 61 60] [0 0 0] ] + [[ ] [ ] [ ] ] + [[86 88 87] [82 81 80] [0 0 0] ] + [[ ] [ ] [ ] ] + [[76 78 77] [72 71 70] [0 0 0] ] + [ ] + [ [0 0 0] [22 21 20] [0 0 0] ] + [ [ ] [ ] [ ] ] + [ [0 0 0] [12 11 10] [0 0 0] ] + [ [ ] [ ] [ ] ] + [ [0 0 0] [2 1 0 ] [0 0 0] ] + [ ] + [ [0 0 0] [0 0 0] [44 43 45]] + [ [ ] [ ] [ ]] + [ [0 0 0] [0 0 0] [34 33 35]] + [ [ ] [ ] [ ]] + [ [0 0 0] [0 0 0] [54 53 55]] + + >>> P = P.as_explicit() + >>> B = B.as_explicit() + >>> P.T * B * P == A + True + """ + from sympy.combinatorics.permutations import Permutation + from sympy.matrices.expressions.blockmatrix import BlockMatrix + from sympy.matrices.expressions.permutation import PermutationMatrix + + iblocks = M.strongly_connected_components() + if not lower: + iblocks = list(reversed(iblocks)) + + p = Permutation(flatten(iblocks)) + P = PermutationMatrix(p) + + rows = [] + for a in iblocks: + cols = [] + for b in iblocks: + cols.append(M[a, b]) + rows.append(cols) + B = BlockMatrix(rows) + return P, B diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/immutable.py b/venv/lib/python3.10/site-packages/sympy/matrices/immutable.py new file mode 100644 index 0000000000000000000000000000000000000000..7ec2174bf1c785e1a4698e1b55078d300e62dafe --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/immutable.py @@ -0,0 +1,196 @@ +from mpmath.matrices.matrices import _matrix + +from sympy.core import Basic, Dict, Tuple +from sympy.core.numbers import Integer +from sympy.core.cache import cacheit +from sympy.core.sympify import _sympy_converter as sympify_converter, _sympify +from sympy.matrices.dense import DenseMatrix +from sympy.matrices.expressions import MatrixExpr +from sympy.matrices.matrixbase import MatrixBase +from sympy.matrices.repmatrix import RepMatrix +from sympy.matrices.sparse import SparseRepMatrix +from sympy.multipledispatch import dispatch + + +def sympify_matrix(arg): + return arg.as_immutable() + + +sympify_converter[MatrixBase] = sympify_matrix + + +def sympify_mpmath_matrix(arg): + mat = [_sympify(x) for x in arg] + return ImmutableDenseMatrix(arg.rows, arg.cols, mat) + + +sympify_converter[_matrix] = sympify_mpmath_matrix + + +class ImmutableRepMatrix(RepMatrix, MatrixExpr): # type: ignore + """Immutable matrix based on RepMatrix + + Uses DomainMAtrix as the internal representation. + """ + + # + # This is a subclass of RepMatrix that adds/overrides some methods to make + # the instances Basic and immutable. ImmutableRepMatrix is a superclass for + # both ImmutableDenseMatrix and ImmutableSparseMatrix. + # + + def __new__(cls, *args, **kwargs): + return cls._new(*args, **kwargs) + + __hash__ = MatrixExpr.__hash__ + + def copy(self): + return self + + @property + def cols(self): + return self._cols + + @property + def rows(self): + return self._rows + + @property + def shape(self): + return self._rows, self._cols + + def as_immutable(self): + return self + + def _entry(self, i, j, **kwargs): + return self[i, j] + + def __setitem__(self, *args): + raise TypeError("Cannot set values of {}".format(self.__class__)) + + def is_diagonalizable(self, reals_only=False, **kwargs): + return super().is_diagonalizable( + reals_only=reals_only, **kwargs) + + is_diagonalizable.__doc__ = SparseRepMatrix.is_diagonalizable.__doc__ + is_diagonalizable = cacheit(is_diagonalizable) + + def analytic_func(self, f, x): + return self.as_mutable().analytic_func(f, x).as_immutable() + + +class ImmutableDenseMatrix(DenseMatrix, ImmutableRepMatrix): # type: ignore + """Create an immutable version of a matrix. + + Examples + ======== + + >>> from sympy import eye, ImmutableMatrix + >>> ImmutableMatrix(eye(3)) + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> _[0, 0] = 42 + Traceback (most recent call last): + ... + TypeError: Cannot set values of ImmutableDenseMatrix + """ + + # MatrixExpr is set as NotIterable, but we want explicit matrices to be + # iterable + _iterable = True + _class_priority = 8 + _op_priority = 10.001 + + @classmethod + def _new(cls, *args, **kwargs): + if len(args) == 1 and isinstance(args[0], ImmutableDenseMatrix): + return args[0] + if kwargs.get('copy', True) is False: + if len(args) != 3: + raise TypeError("'copy=False' requires a matrix be initialized as rows,cols,[list]") + rows, cols, flat_list = args + else: + rows, cols, flat_list = cls._handle_creation_inputs(*args, **kwargs) + flat_list = list(flat_list) # create a shallow copy + + rep = cls._flat_list_to_DomainMatrix(rows, cols, flat_list) + + return cls._fromrep(rep) + + @classmethod + def _fromrep(cls, rep): + rows, cols = rep.shape + flat_list = rep.to_sympy().to_list_flat() + obj = Basic.__new__(cls, + Integer(rows), + Integer(cols), + Tuple(*flat_list, sympify=False)) + obj._rows = rows + obj._cols = cols + obj._rep = rep + return obj + + +# make sure ImmutableDenseMatrix is aliased as ImmutableMatrix +ImmutableMatrix = ImmutableDenseMatrix + + +class ImmutableSparseMatrix(SparseRepMatrix, ImmutableRepMatrix): # type:ignore + """Create an immutable version of a sparse matrix. + + Examples + ======== + + >>> from sympy import eye, ImmutableSparseMatrix + >>> ImmutableSparseMatrix(1, 1, {}) + Matrix([[0]]) + >>> ImmutableSparseMatrix(eye(3)) + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> _[0, 0] = 42 + Traceback (most recent call last): + ... + TypeError: Cannot set values of ImmutableSparseMatrix + >>> _.shape + (3, 3) + """ + is_Matrix = True + _class_priority = 9 + + @classmethod + def _new(cls, *args, **kwargs): + rows, cols, smat = cls._handle_creation_inputs(*args, **kwargs) + + rep = cls._smat_to_DomainMatrix(rows, cols, smat) + + return cls._fromrep(rep) + + @classmethod + def _fromrep(cls, rep): + rows, cols = rep.shape + smat = rep.to_sympy().to_dok() + obj = Basic.__new__(cls, Integer(rows), Integer(cols), Dict(smat)) + obj._rows = rows + obj._cols = cols + obj._rep = rep + return obj + + +@dispatch(ImmutableDenseMatrix, ImmutableDenseMatrix) +def _eval_is_eq(lhs, rhs): # noqa:F811 + """Helper method for Equality with matrices.sympy. + + Relational automatically converts matrices to ImmutableDenseMatrix + instances, so this method only applies here. Returns True if the + matrices are definitively the same, False if they are definitively + different, and None if undetermined (e.g. if they contain Symbols). + Returning None triggers default handling of Equalities. + + """ + if lhs.shape != rhs.shape: + return False + return (lhs - rhs).is_zero_matrix diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/inverse.py b/venv/lib/python3.10/site-packages/sympy/matrices/inverse.py new file mode 100644 index 0000000000000000000000000000000000000000..61d9e12edf013d2f5555d61786343aa3840edfd3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/inverse.py @@ -0,0 +1,524 @@ +from sympy.polys.matrices.exceptions import DMNonInvertibleMatrixError +from sympy.polys.domains import EX + +from .exceptions import MatrixError, NonSquareMatrixError, NonInvertibleMatrixError +from .utilities import _iszero + + +def _pinv_full_rank(M): + """Subroutine for full row or column rank matrices. + + For full row rank matrices, inverse of ``A * A.H`` Exists. + For full column rank matrices, inverse of ``A.H * A`` Exists. + + This routine can apply for both cases by checking the shape + and have small decision. + """ + + if M.is_zero_matrix: + return M.H + + if M.rows >= M.cols: + return M.H.multiply(M).inv().multiply(M.H) + else: + return M.H.multiply(M.multiply(M.H).inv()) + +def _pinv_rank_decomposition(M): + """Subroutine for rank decomposition + + With rank decompositions, `A` can be decomposed into two full- + rank matrices, and each matrix can take pseudoinverse + individually. + """ + + if M.is_zero_matrix: + return M.H + + B, C = M.rank_decomposition() + + Bp = _pinv_full_rank(B) + Cp = _pinv_full_rank(C) + + return Cp.multiply(Bp) + +def _pinv_diagonalization(M): + """Subroutine using diagonalization + + This routine can sometimes fail if SymPy's eigenvalue + computation is not reliable. + """ + + if M.is_zero_matrix: + return M.H + + A = M + AH = M.H + + try: + if M.rows >= M.cols: + P, D = AH.multiply(A).diagonalize(normalize=True) + D_pinv = D.applyfunc(lambda x: 0 if _iszero(x) else 1 / x) + + return P.multiply(D_pinv).multiply(P.H).multiply(AH) + + else: + P, D = A.multiply(AH).diagonalize( + normalize=True) + D_pinv = D.applyfunc(lambda x: 0 if _iszero(x) else 1 / x) + + return AH.multiply(P).multiply(D_pinv).multiply(P.H) + + except MatrixError: + raise NotImplementedError( + 'pinv for rank-deficient matrices where ' + 'diagonalization of A.H*A fails is not supported yet.') + +def _pinv(M, method='RD'): + """Calculate the Moore-Penrose pseudoinverse of the matrix. + + The Moore-Penrose pseudoinverse exists and is unique for any matrix. + If the matrix is invertible, the pseudoinverse is the same as the + inverse. + + Parameters + ========== + + method : String, optional + Specifies the method for computing the pseudoinverse. + + If ``'RD'``, Rank-Decomposition will be used. + + If ``'ED'``, Diagonalization will be used. + + Examples + ======== + + Computing pseudoinverse by rank decomposition : + + >>> from sympy import Matrix + >>> A = Matrix([[1, 2, 3], [4, 5, 6]]) + >>> A.pinv() + Matrix([ + [-17/18, 4/9], + [ -1/9, 1/9], + [ 13/18, -2/9]]) + + Computing pseudoinverse by diagonalization : + + >>> B = A.pinv(method='ED') + >>> B.simplify() + >>> B + Matrix([ + [-17/18, 4/9], + [ -1/9, 1/9], + [ 13/18, -2/9]]) + + See Also + ======== + + inv + pinv_solve + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Moore-Penrose_pseudoinverse + + """ + + # Trivial case: pseudoinverse of all-zero matrix is its transpose. + if M.is_zero_matrix: + return M.H + + if method == 'RD': + return _pinv_rank_decomposition(M) + elif method == 'ED': + return _pinv_diagonalization(M) + else: + raise ValueError('invalid pinv method %s' % repr(method)) + + +def _verify_invertible(M, iszerofunc=_iszero): + """Initial check to see if a matrix is invertible. Raises or returns + determinant for use in _inv_ADJ.""" + + if not M.is_square: + raise NonSquareMatrixError("A Matrix must be square to invert.") + + d = M.det(method='berkowitz') + zero = d.equals(0) + + if zero is None: # if equals() can't decide, will rref be able to? + ok = M.rref(simplify=True)[0] + zero = any(iszerofunc(ok[j, j]) for j in range(ok.rows)) + + if zero: + raise NonInvertibleMatrixError("Matrix det == 0; not invertible.") + + return d + +def _inv_ADJ(M, iszerofunc=_iszero): + """Calculates the inverse using the adjugate matrix and a determinant. + + See Also + ======== + + inv + inverse_GE + inverse_LU + inverse_CH + inverse_LDL + """ + + d = _verify_invertible(M, iszerofunc=iszerofunc) + + return M.adjugate() / d + +def _inv_GE(M, iszerofunc=_iszero): + """Calculates the inverse using Gaussian elimination. + + See Also + ======== + + inv + inverse_ADJ + inverse_LU + inverse_CH + inverse_LDL + """ + + from .dense import Matrix + + if not M.is_square: + raise NonSquareMatrixError("A Matrix must be square to invert.") + + big = Matrix.hstack(M.as_mutable(), Matrix.eye(M.rows)) + red = big.rref(iszerofunc=iszerofunc, simplify=True)[0] + + if any(iszerofunc(red[j, j]) for j in range(red.rows)): + raise NonInvertibleMatrixError("Matrix det == 0; not invertible.") + + return M._new(red[:, big.rows:]) + +def _inv_LU(M, iszerofunc=_iszero): + """Calculates the inverse using LU decomposition. + + See Also + ======== + + inv + inverse_ADJ + inverse_GE + inverse_CH + inverse_LDL + """ + + if not M.is_square: + raise NonSquareMatrixError("A Matrix must be square to invert.") + if M.free_symbols: + _verify_invertible(M, iszerofunc=iszerofunc) + + return M.LUsolve(M.eye(M.rows), iszerofunc=_iszero) + +def _inv_CH(M, iszerofunc=_iszero): + """Calculates the inverse using cholesky decomposition. + + See Also + ======== + + inv + inverse_ADJ + inverse_GE + inverse_LU + inverse_LDL + """ + + _verify_invertible(M, iszerofunc=iszerofunc) + + return M.cholesky_solve(M.eye(M.rows)) + +def _inv_LDL(M, iszerofunc=_iszero): + """Calculates the inverse using LDL decomposition. + + See Also + ======== + + inv + inverse_ADJ + inverse_GE + inverse_LU + inverse_CH + """ + + _verify_invertible(M, iszerofunc=iszerofunc) + + return M.LDLsolve(M.eye(M.rows)) + +def _inv_QR(M, iszerofunc=_iszero): + """Calculates the inverse using QR decomposition. + + See Also + ======== + + inv + inverse_ADJ + inverse_GE + inverse_CH + inverse_LDL + """ + + _verify_invertible(M, iszerofunc=iszerofunc) + + return M.QRsolve(M.eye(M.rows)) + +def _try_DM(M, use_EX=False): + """Try to convert a matrix to a ``DomainMatrix``.""" + dM = M.to_DM() + K = dM.domain + + # Return DomainMatrix if a domain is found. Only use EX if use_EX=True. + if not use_EX and K.is_EXRAW: + return None + elif K.is_EXRAW: + return dM.convert_to(EX) + else: + return dM + + +def _use_exact_domain(dom): + """Check whether to convert to an exact domain.""" + # DomainMatrix can handle RR and CC with partial pivoting. Other inexact + # domains like RR[a,b,...] can only be handled by converting to an exact + # domain like QQ[a,b,...] + if dom.is_RR or dom.is_CC: + return False + else: + return not dom.is_Exact + + +def _inv_DM(dM, cancel=True): + """Calculates the inverse using ``DomainMatrix``. + + See Also + ======== + + inv + inverse_ADJ + inverse_GE + inverse_CH + inverse_LDL + sympy.polys.matrices.domainmatrix.DomainMatrix.inv + """ + m, n = dM.shape + dom = dM.domain + + if m != n: + raise NonSquareMatrixError("A Matrix must be square to invert.") + + # Convert RR[a,b,...] to QQ[a,b,...] + use_exact = _use_exact_domain(dom) + + if use_exact: + dom_exact = dom.get_exact() + dM = dM.convert_to(dom_exact) + + try: + dMi, den = dM.inv_den() + except DMNonInvertibleMatrixError: + raise NonInvertibleMatrixError("Matrix det == 0; not invertible.") + + if use_exact: + dMi = dMi.convert_to(dom) + den = dom.convert_from(den, dom_exact) + + if cancel: + # Convert to field and cancel with the denominator. + if not dMi.domain.is_Field: + dMi = dMi.to_field() + Mi = (dMi / den).to_Matrix() + else: + # Convert to Matrix and divide without cancelling + Mi = dMi.to_Matrix() / dMi.domain.to_sympy(den) + + return Mi + +def _inv_block(M, iszerofunc=_iszero): + """Calculates the inverse using BLOCKWISE inversion. + + See Also + ======== + + inv + inverse_ADJ + inverse_GE + inverse_CH + inverse_LDL + """ + from sympy.matrices.expressions.blockmatrix import BlockMatrix + i = M.shape[0] + if i <= 20 : + return M.inv(method="LU", iszerofunc=_iszero) + A = M[:i // 2, :i //2] + B = M[:i // 2, i // 2:] + C = M[i // 2:, :i // 2] + D = M[i // 2:, i // 2:] + try: + D_inv = _inv_block(D) + except NonInvertibleMatrixError: + return M.inv(method="LU", iszerofunc=_iszero) + B_D_i = B*D_inv + BDC = B_D_i*C + A_n = A - BDC + try: + A_n = _inv_block(A_n) + except NonInvertibleMatrixError: + return M.inv(method="LU", iszerofunc=_iszero) + B_n = -A_n*B_D_i + dc = D_inv*C + C_n = -dc*A_n + D_n = D_inv + dc*-B_n + nn = BlockMatrix([[A_n, B_n], [C_n, D_n]]).as_explicit() + return nn + +def _inv(M, method=None, iszerofunc=_iszero, try_block_diag=False): + """ + Return the inverse of a matrix using the method indicated. The default + is DM if a suitable domain is found or otherwise GE for dense matrices + LDL for sparse matrices. + + Parameters + ========== + + method : ('DM', 'DMNC', 'GE', 'LU', 'ADJ', 'CH', 'LDL', 'QR') + + iszerofunc : function, optional + Zero-testing function to use. + + try_block_diag : bool, optional + If True then will try to form block diagonal matrices using the + method get_diag_blocks(), invert these individually, and then + reconstruct the full inverse matrix. + + Examples + ======== + + >>> from sympy import SparseMatrix, Matrix + >>> A = SparseMatrix([ + ... [ 2, -1, 0], + ... [-1, 2, -1], + ... [ 0, 0, 2]]) + >>> A.inv('CH') + Matrix([ + [2/3, 1/3, 1/6], + [1/3, 2/3, 1/3], + [ 0, 0, 1/2]]) + >>> A.inv(method='LDL') # use of 'method=' is optional + Matrix([ + [2/3, 1/3, 1/6], + [1/3, 2/3, 1/3], + [ 0, 0, 1/2]]) + >>> A * _ + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + >>> A = Matrix(A) + >>> A.inv('CH') + Matrix([ + [2/3, 1/3, 1/6], + [1/3, 2/3, 1/3], + [ 0, 0, 1/2]]) + >>> A.inv('ADJ') == A.inv('GE') == A.inv('LU') == A.inv('CH') == A.inv('LDL') == A.inv('QR') + True + + Notes + ===== + + According to the ``method`` keyword, it calls the appropriate method: + + DM .... Use DomainMatrix ``inv_den`` method + DMNC .... Use DomainMatrix ``inv_den`` method without cancellation + GE .... inverse_GE(); default for dense matrices + LU .... inverse_LU() + ADJ ... inverse_ADJ() + CH ... inverse_CH() + LDL ... inverse_LDL(); default for sparse matrices + QR ... inverse_QR() + + Note, the GE and LU methods may require the matrix to be simplified + before it is inverted in order to properly detect zeros during + pivoting. In difficult cases a custom zero detection function can + be provided by setting the ``iszerofunc`` argument to a function that + should return True if its argument is zero. The ADJ routine computes + the determinant and uses that to detect singular matrices in addition + to testing for zeros on the diagonal. + + See Also + ======== + + inverse_ADJ + inverse_GE + inverse_LU + inverse_CH + inverse_LDL + + Raises + ====== + + ValueError + If the determinant of the matrix is zero. + """ + + from sympy.matrices import diag, SparseMatrix + + if not M.is_square: + raise NonSquareMatrixError("A Matrix must be square to invert.") + + if try_block_diag: + blocks = M.get_diag_blocks() + r = [] + + for block in blocks: + r.append(block.inv(method=method, iszerofunc=iszerofunc)) + + return diag(*r) + + # Default: Use DomainMatrix if the domain is not EX. + # If DM is requested explicitly then use it even if the domain is EX. + if method is None and iszerofunc is _iszero: + dM = _try_DM(M, use_EX=False) + if dM is not None: + method = 'DM' + elif method in ("DM", "DMNC"): + dM = _try_DM(M, use_EX=True) + + # A suitable domain was not found, fall back to GE for dense matrices + # and LDL for sparse matrices. + if method is None: + if isinstance(M, SparseMatrix): + method = 'LDL' + else: + method = 'GE' + + if method == "DM": + rv = _inv_DM(dM) + elif method == "DMNC": + rv = _inv_DM(dM, cancel=False) + elif method == "GE": + rv = M.inverse_GE(iszerofunc=iszerofunc) + elif method == "LU": + rv = M.inverse_LU(iszerofunc=iszerofunc) + elif method == "ADJ": + rv = M.inverse_ADJ(iszerofunc=iszerofunc) + elif method == "CH": + rv = M.inverse_CH(iszerofunc=iszerofunc) + elif method == "LDL": + rv = M.inverse_LDL(iszerofunc=iszerofunc) + elif method == "QR": + rv = M.inverse_QR(iszerofunc=iszerofunc) + elif method == "BLOCK": + rv = M.inverse_BLOCK(iszerofunc=iszerofunc) + else: + raise ValueError("Inversion method unrecognized") + + return M._new(rv) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/kind.py b/venv/lib/python3.10/site-packages/sympy/matrices/kind.py new file mode 100644 index 0000000000000000000000000000000000000000..f9f53ffe16f7cbde60213e49071a2a74e80e5c6c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/kind.py @@ -0,0 +1,97 @@ +# sympy.matrices.kind + +from sympy.core.kind import Kind, _NumberKind, NumberKind +from sympy.core.mul import Mul + + +class MatrixKind(Kind): + """ + Kind for all matrices in SymPy. + + Basic class for this kind is ``MatrixBase`` and ``MatrixExpr``, + but any expression representing the matrix can have this. + + Parameters + ========== + + element_kind : Kind + Kind of the element. Default is + :class:`sympy.core.kind.NumberKind`, + which means that the matrix contains only numbers. + + Examples + ======== + + Any instance of matrix class has kind ``MatrixKind``: + + >>> from sympy import MatrixSymbol + >>> A = MatrixSymbol('A', 2, 2) + >>> A.kind + MatrixKind(NumberKind) + + An expression representing a matrix may not be an instance of + the Matrix class, but it will have kind ``MatrixKind``: + + >>> from sympy import MatrixExpr, Integral + >>> from sympy.abc import x + >>> intM = Integral(A, x) + >>> isinstance(intM, MatrixExpr) + False + >>> intM.kind + MatrixKind(NumberKind) + + Use ``isinstance()`` to check for ``MatrixKind`` without specifying the + element kind. Use ``is`` to check the kind including the element kind: + + >>> from sympy import Matrix + >>> from sympy.core import NumberKind + >>> from sympy.matrices import MatrixKind + >>> M = Matrix([1, 2]) + >>> isinstance(M.kind, MatrixKind) + True + >>> M.kind is MatrixKind(NumberKind) + True + + See Also + ======== + + sympy.core.kind.NumberKind + sympy.core.kind.UndefinedKind + sympy.core.containers.TupleKind + sympy.sets.sets.SetKind + + """ + def __new__(cls, element_kind=NumberKind): + obj = super().__new__(cls, element_kind) + obj.element_kind = element_kind + return obj + + def __repr__(self): + return "MatrixKind(%s)" % self.element_kind + + +@Mul._kind_dispatcher.register(_NumberKind, MatrixKind) +def num_mat_mul(k1, k2): + """ + Return MatrixKind. The element kind is selected by recursive dispatching. + Do not need to dispatch in reversed order because KindDispatcher + searches for this automatically. + """ + # Deal with Mul._kind_dispatcher's commutativity + # XXX: this function is called with either k1 or k2 as MatrixKind because + # the Mul kind dispatcher is commutative. Maybe it shouldn't be. Need to + # swap the args here because NumberKind does not have an element_kind + # attribute. + if not isinstance(k2, MatrixKind): + k1, k2 = k2, k1 + elemk = Mul._kind_dispatcher(k1, k2.element_kind) + return MatrixKind(elemk) + + +@Mul._kind_dispatcher.register(MatrixKind, MatrixKind) +def mat_mat_mul(k1, k2): + """ + Return MatrixKind. The element kind is selected by recursive dispatching. + """ + elemk = Mul._kind_dispatcher(k1.element_kind, k2.element_kind) + return MatrixKind(elemk) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/matrices.py b/venv/lib/python3.10/site-packages/sympy/matrices/matrices.py new file mode 100644 index 0000000000000000000000000000000000000000..fed41a626cb395ac0529071317630d853e0d3a96 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/matrices.py @@ -0,0 +1,687 @@ +# +# A module consisting of deprecated matrix classes. New code should not be +# added here. +# +from sympy.core.basic import Basic +from sympy.core.symbol import Dummy + +from .common import MatrixCommon + +from .exceptions import NonSquareMatrixError + +from .utilities import _iszero, _is_zero_after_expand_mul, _simplify + +from .determinant import ( + _find_reasonable_pivot, _find_reasonable_pivot_naive, + _adjugate, _charpoly, _cofactor, _cofactor_matrix, _per, + _det, _det_bareiss, _det_berkowitz, _det_bird, _det_laplace, _det_LU, + _minor, _minor_submatrix) + +from .reductions import _is_echelon, _echelon_form, _rank, _rref +from .subspaces import _columnspace, _nullspace, _rowspace, _orthogonalize + +from .eigen import ( + _eigenvals, _eigenvects, + _bidiagonalize, _bidiagonal_decomposition, + _is_diagonalizable, _diagonalize, + _is_positive_definite, _is_positive_semidefinite, + _is_negative_definite, _is_negative_semidefinite, _is_indefinite, + _jordan_form, _left_eigenvects, _singular_values) + + +# This class was previously defined in this module, but was moved to +# sympy.matrices.matrixbase. We import it here for backwards compatibility in +# case someone was importing it from here. +from .matrixbase import MatrixBase + + +__doctest_requires__ = { + ('MatrixEigen.is_indefinite', + 'MatrixEigen.is_negative_definite', + 'MatrixEigen.is_negative_semidefinite', + 'MatrixEigen.is_positive_definite', + 'MatrixEigen.is_positive_semidefinite'): ['matplotlib'], +} + + +class MatrixDeterminant(MatrixCommon): + """Provides basic matrix determinant operations. Should not be instantiated + directly. See ``determinant.py`` for their implementations.""" + + def _eval_det_bareiss(self, iszerofunc=_is_zero_after_expand_mul): + return _det_bareiss(self, iszerofunc=iszerofunc) + + def _eval_det_berkowitz(self): + return _det_berkowitz(self) + + def _eval_det_lu(self, iszerofunc=_iszero, simpfunc=None): + return _det_LU(self, iszerofunc=iszerofunc, simpfunc=simpfunc) + + def _eval_det_bird(self): + return _det_bird(self) + + def _eval_det_laplace(self): + return _det_laplace(self) + + def _eval_determinant(self): # for expressions.determinant.Determinant + return _det(self) + + def adjugate(self, method="berkowitz"): + return _adjugate(self, method=method) + + def charpoly(self, x='lambda', simplify=_simplify): + return _charpoly(self, x=x, simplify=simplify) + + def cofactor(self, i, j, method="berkowitz"): + return _cofactor(self, i, j, method=method) + + def cofactor_matrix(self, method="berkowitz"): + return _cofactor_matrix(self, method=method) + + def det(self, method="bareiss", iszerofunc=None): + return _det(self, method=method, iszerofunc=iszerofunc) + + def per(self): + return _per(self) + + def minor(self, i, j, method="berkowitz"): + return _minor(self, i, j, method=method) + + def minor_submatrix(self, i, j): + return _minor_submatrix(self, i, j) + + _find_reasonable_pivot.__doc__ = _find_reasonable_pivot.__doc__ + _find_reasonable_pivot_naive.__doc__ = _find_reasonable_pivot_naive.__doc__ + _eval_det_bareiss.__doc__ = _det_bareiss.__doc__ + _eval_det_berkowitz.__doc__ = _det_berkowitz.__doc__ + _eval_det_bird.__doc__ = _det_bird.__doc__ + _eval_det_laplace.__doc__ = _det_laplace.__doc__ + _eval_det_lu.__doc__ = _det_LU.__doc__ + _eval_determinant.__doc__ = _det.__doc__ + adjugate.__doc__ = _adjugate.__doc__ + charpoly.__doc__ = _charpoly.__doc__ + cofactor.__doc__ = _cofactor.__doc__ + cofactor_matrix.__doc__ = _cofactor_matrix.__doc__ + det.__doc__ = _det.__doc__ + per.__doc__ = _per.__doc__ + minor.__doc__ = _minor.__doc__ + minor_submatrix.__doc__ = _minor_submatrix.__doc__ + + +class MatrixReductions(MatrixDeterminant): + """Provides basic matrix row/column operations. Should not be instantiated + directly. See ``reductions.py`` for some of their implementations.""" + + def echelon_form(self, iszerofunc=_iszero, simplify=False, with_pivots=False): + return _echelon_form(self, iszerofunc=iszerofunc, simplify=simplify, + with_pivots=with_pivots) + + @property + def is_echelon(self): + return _is_echelon(self) + + def rank(self, iszerofunc=_iszero, simplify=False): + return _rank(self, iszerofunc=iszerofunc, simplify=simplify) + + def rref_rhs(self, rhs): + """Return reduced row-echelon form of matrix, matrix showing + rhs after reduction steps. ``rhs`` must have the same number + of rows as ``self``. + + Examples + ======== + + >>> from sympy import Matrix, symbols + >>> r1, r2 = symbols('r1 r2') + >>> Matrix([[1, 1], [2, 1]]).rref_rhs(Matrix([r1, r2])) + (Matrix([ + [1, 0], + [0, 1]]), Matrix([ + [ -r1 + r2], + [2*r1 - r2]])) + """ + r, _ = _rref(self.hstack(self, self.eye(self.rows), rhs)) + return r[:, :self.cols], r[:, -rhs.cols:] + + def rref(self, iszerofunc=_iszero, simplify=False, pivots=True, + normalize_last=True): + return _rref(self, iszerofunc=iszerofunc, simplify=simplify, + pivots=pivots, normalize_last=normalize_last) + + echelon_form.__doc__ = _echelon_form.__doc__ + is_echelon.__doc__ = _is_echelon.__doc__ + rank.__doc__ = _rank.__doc__ + rref.__doc__ = _rref.__doc__ + + def _normalize_op_args(self, op, col, k, col1, col2, error_str="col"): + """Validate the arguments for a row/column operation. ``error_str`` + can be one of "row" or "col" depending on the arguments being parsed.""" + if op not in ["n->kn", "n<->m", "n->n+km"]: + raise ValueError("Unknown {} operation '{}'. Valid col operations " + "are 'n->kn', 'n<->m', 'n->n+km'".format(error_str, op)) + + # define self_col according to error_str + self_cols = self.cols if error_str == 'col' else self.rows + + # normalize and validate the arguments + if op == "n->kn": + col = col if col is not None else col1 + if col is None or k is None: + raise ValueError("For a {0} operation 'n->kn' you must provide the " + "kwargs `{0}` and `k`".format(error_str)) + if not 0 <= col < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col)) + + elif op == "n<->m": + # we need two cols to swap. It does not matter + # how they were specified, so gather them together and + # remove `None` + cols = {col, k, col1, col2}.difference([None]) + if len(cols) > 2: + # maybe the user left `k` by mistake? + cols = {col, col1, col2}.difference([None]) + if len(cols) != 2: + raise ValueError("For a {0} operation 'n<->m' you must provide the " + "kwargs `{0}1` and `{0}2`".format(error_str)) + col1, col2 = cols + if not 0 <= col1 < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col1)) + if not 0 <= col2 < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col2)) + + elif op == "n->n+km": + col = col1 if col is None else col + col2 = col1 if col2 is None else col2 + if col is None or col2 is None or k is None: + raise ValueError("For a {0} operation 'n->n+km' you must provide the " + "kwargs `{0}`, `k`, and `{0}2`".format(error_str)) + if col == col2: + raise ValueError("For a {0} operation 'n->n+km' `{0}` and `{0}2` must " + "be different.".format(error_str)) + if not 0 <= col < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col)) + if not 0 <= col2 < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col2)) + + else: + raise ValueError('invalid operation %s' % repr(op)) + + return op, col, k, col1, col2 + + def _eval_col_op_multiply_col_by_const(self, col, k): + def entry(i, j): + if j == col: + return k * self[i, j] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_col_op_swap(self, col1, col2): + def entry(i, j): + if j == col1: + return self[i, col2] + elif j == col2: + return self[i, col1] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_col_op_add_multiple_to_other_col(self, col, k, col2): + def entry(i, j): + if j == col: + return self[i, j] + k * self[i, col2] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_row_op_swap(self, row1, row2): + def entry(i, j): + if i == row1: + return self[row2, j] + elif i == row2: + return self[row1, j] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_row_op_multiply_row_by_const(self, row, k): + def entry(i, j): + if i == row: + return k * self[i, j] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_row_op_add_multiple_to_other_row(self, row, k, row2): + def entry(i, j): + if i == row: + return self[i, j] + k * self[row2, j] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def elementary_col_op(self, op="n->kn", col=None, k=None, col1=None, col2=None): + """Performs the elementary column operation `op`. + + `op` may be one of + + * ``"n->kn"`` (column n goes to k*n) + * ``"n<->m"`` (swap column n and column m) + * ``"n->n+km"`` (column n goes to column n + k*column m) + + Parameters + ========== + + op : string; the elementary row operation + col : the column to apply the column operation + k : the multiple to apply in the column operation + col1 : one column of a column swap + col2 : second column of a column swap or column "m" in the column operation + "n->n+km" + """ + + op, col, k, col1, col2 = self._normalize_op_args(op, col, k, col1, col2, "col") + + # now that we've validated, we're all good to dispatch + if op == "n->kn": + return self._eval_col_op_multiply_col_by_const(col, k) + if op == "n<->m": + return self._eval_col_op_swap(col1, col2) + if op == "n->n+km": + return self._eval_col_op_add_multiple_to_other_col(col, k, col2) + + def elementary_row_op(self, op="n->kn", row=None, k=None, row1=None, row2=None): + """Performs the elementary row operation `op`. + + `op` may be one of + + * ``"n->kn"`` (row n goes to k*n) + * ``"n<->m"`` (swap row n and row m) + * ``"n->n+km"`` (row n goes to row n + k*row m) + + Parameters + ========== + + op : string; the elementary row operation + row : the row to apply the row operation + k : the multiple to apply in the row operation + row1 : one row of a row swap + row2 : second row of a row swap or row "m" in the row operation + "n->n+km" + """ + + op, row, k, row1, row2 = self._normalize_op_args(op, row, k, row1, row2, "row") + + # now that we've validated, we're all good to dispatch + if op == "n->kn": + return self._eval_row_op_multiply_row_by_const(row, k) + if op == "n<->m": + return self._eval_row_op_swap(row1, row2) + if op == "n->n+km": + return self._eval_row_op_add_multiple_to_other_row(row, k, row2) + + +class MatrixSubspaces(MatrixReductions): + """Provides methods relating to the fundamental subspaces of a matrix. + Should not be instantiated directly. See ``subspaces.py`` for their + implementations.""" + + def columnspace(self, simplify=False): + return _columnspace(self, simplify=simplify) + + def nullspace(self, simplify=False, iszerofunc=_iszero): + return _nullspace(self, simplify=simplify, iszerofunc=iszerofunc) + + def rowspace(self, simplify=False): + return _rowspace(self, simplify=simplify) + + # This is a classmethod but is converted to such later in order to allow + # assignment of __doc__ since that does not work for already wrapped + # classmethods in Python 3.6. + def orthogonalize(cls, *vecs, **kwargs): + return _orthogonalize(cls, *vecs, **kwargs) + + columnspace.__doc__ = _columnspace.__doc__ + nullspace.__doc__ = _nullspace.__doc__ + rowspace.__doc__ = _rowspace.__doc__ + orthogonalize.__doc__ = _orthogonalize.__doc__ + + orthogonalize = classmethod(orthogonalize) # type:ignore + + +class MatrixEigen(MatrixSubspaces): + """Provides basic matrix eigenvalue/vector operations. + Should not be instantiated directly. See ``eigen.py`` for their + implementations.""" + + def eigenvals(self, error_when_incomplete=True, **flags): + return _eigenvals(self, error_when_incomplete=error_when_incomplete, **flags) + + def eigenvects(self, error_when_incomplete=True, iszerofunc=_iszero, **flags): + return _eigenvects(self, error_when_incomplete=error_when_incomplete, + iszerofunc=iszerofunc, **flags) + + def is_diagonalizable(self, reals_only=False, **kwargs): + return _is_diagonalizable(self, reals_only=reals_only, **kwargs) + + def diagonalize(self, reals_only=False, sort=False, normalize=False): + return _diagonalize(self, reals_only=reals_only, sort=sort, + normalize=normalize) + + def bidiagonalize(self, upper=True): + return _bidiagonalize(self, upper=upper) + + def bidiagonal_decomposition(self, upper=True): + return _bidiagonal_decomposition(self, upper=upper) + + @property + def is_positive_definite(self): + return _is_positive_definite(self) + + @property + def is_positive_semidefinite(self): + return _is_positive_semidefinite(self) + + @property + def is_negative_definite(self): + return _is_negative_definite(self) + + @property + def is_negative_semidefinite(self): + return _is_negative_semidefinite(self) + + @property + def is_indefinite(self): + return _is_indefinite(self) + + def jordan_form(self, calc_transform=True, **kwargs): + return _jordan_form(self, calc_transform=calc_transform, **kwargs) + + def left_eigenvects(self, **flags): + return _left_eigenvects(self, **flags) + + def singular_values(self): + return _singular_values(self) + + eigenvals.__doc__ = _eigenvals.__doc__ + eigenvects.__doc__ = _eigenvects.__doc__ + is_diagonalizable.__doc__ = _is_diagonalizable.__doc__ + diagonalize.__doc__ = _diagonalize.__doc__ + is_positive_definite.__doc__ = _is_positive_definite.__doc__ + is_positive_semidefinite.__doc__ = _is_positive_semidefinite.__doc__ + is_negative_definite.__doc__ = _is_negative_definite.__doc__ + is_negative_semidefinite.__doc__ = _is_negative_semidefinite.__doc__ + is_indefinite.__doc__ = _is_indefinite.__doc__ + jordan_form.__doc__ = _jordan_form.__doc__ + left_eigenvects.__doc__ = _left_eigenvects.__doc__ + singular_values.__doc__ = _singular_values.__doc__ + bidiagonalize.__doc__ = _bidiagonalize.__doc__ + bidiagonal_decomposition.__doc__ = _bidiagonal_decomposition.__doc__ + + +class MatrixCalculus(MatrixCommon): + """Provides calculus-related matrix operations.""" + + def diff(self, *args, evaluate=True, **kwargs): + """Calculate the derivative of each element in the matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[x, y], [1, 0]]) + >>> M.diff(x) + Matrix([ + [1, 0], + [0, 0]]) + + See Also + ======== + + integrate + limit + """ + # XXX this should be handled here rather than in Derivative + from sympy.tensor.array.array_derivatives import ArrayDerivative + deriv = ArrayDerivative(self, *args, evaluate=evaluate) + # XXX This can rather changed to always return immutable matrix + if not isinstance(self, Basic) and evaluate: + return deriv.as_mutable() + return deriv + + def _eval_derivative(self, arg): + return self.applyfunc(lambda x: x.diff(arg)) + + def integrate(self, *args, **kwargs): + """Integrate each element of the matrix. ``args`` will + be passed to the ``integrate`` function. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[x, y], [1, 0]]) + >>> M.integrate((x, )) + Matrix([ + [x**2/2, x*y], + [ x, 0]]) + >>> M.integrate((x, 0, 2)) + Matrix([ + [2, 2*y], + [2, 0]]) + + See Also + ======== + + limit + diff + """ + return self.applyfunc(lambda x: x.integrate(*args, **kwargs)) + + def jacobian(self, X): + """Calculates the Jacobian matrix (derivative of a vector-valued function). + + Parameters + ========== + + ``self`` : vector of expressions representing functions f_i(x_1, ..., x_n). + X : set of x_i's in order, it can be a list or a Matrix + + Both ``self`` and X can be a row or a column matrix in any order + (i.e., jacobian() should always work). + + Examples + ======== + + >>> from sympy import sin, cos, Matrix + >>> from sympy.abc import rho, phi + >>> X = Matrix([rho*cos(phi), rho*sin(phi), rho**2]) + >>> Y = Matrix([rho, phi]) + >>> X.jacobian(Y) + Matrix([ + [cos(phi), -rho*sin(phi)], + [sin(phi), rho*cos(phi)], + [ 2*rho, 0]]) + >>> X = Matrix([rho*cos(phi), rho*sin(phi)]) + >>> X.jacobian(Y) + Matrix([ + [cos(phi), -rho*sin(phi)], + [sin(phi), rho*cos(phi)]]) + + See Also + ======== + + hessian + wronskian + """ + if not isinstance(X, MatrixBase): + X = self._new(X) + # Both X and ``self`` can be a row or a column matrix, so we need to make + # sure all valid combinations work, but everything else fails: + if self.shape[0] == 1: + m = self.shape[1] + elif self.shape[1] == 1: + m = self.shape[0] + else: + raise TypeError("``self`` must be a row or a column matrix") + if X.shape[0] == 1: + n = X.shape[1] + elif X.shape[1] == 1: + n = X.shape[0] + else: + raise TypeError("X must be a row or a column matrix") + + # m is the number of functions and n is the number of variables + # computing the Jacobian is now easy: + return self._new(m, n, lambda j, i: self[j].diff(X[i])) + + def limit(self, *args): + """Calculate the limit of each element in the matrix. + ``args`` will be passed to the ``limit`` function. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[x, y], [1, 0]]) + >>> M.limit(x, 2) + Matrix([ + [2, y], + [1, 0]]) + + See Also + ======== + + integrate + diff + """ + return self.applyfunc(lambda x: x.limit(*args)) + + +# https://github.com/sympy/sympy/pull/12854 +class MatrixDeprecated(MatrixCommon): + """A class to house deprecated matrix methods.""" + def berkowitz_charpoly(self, x=Dummy('lambda'), simplify=_simplify): + return self.charpoly(x=x) + + def berkowitz_det(self): + """Computes determinant using Berkowitz method. + + See Also + ======== + + det + berkowitz + """ + return self.det(method='berkowitz') + + def berkowitz_eigenvals(self, **flags): + """Computes eigenvalues of a Matrix using Berkowitz method. + + See Also + ======== + + berkowitz + """ + return self.eigenvals(**flags) + + def berkowitz_minors(self): + """Computes principal minors using Berkowitz method. + + See Also + ======== + + berkowitz + """ + sign, minors = self.one, [] + + for poly in self.berkowitz(): + minors.append(sign * poly[-1]) + sign = -sign + + return tuple(minors) + + def berkowitz(self): + from sympy.matrices import zeros + berk = ((1,),) + if not self: + return berk + + if not self.is_square: + raise NonSquareMatrixError() + + A, N = self, self.rows + transforms = [0] * (N - 1) + + for n in range(N, 1, -1): + T, k = zeros(n + 1, n), n - 1 + + R, C = -A[k, :k], A[:k, k] + A, a = A[:k, :k], -A[k, k] + + items = [C] + + for i in range(0, n - 2): + items.append(A * items[i]) + + for i, B in enumerate(items): + items[i] = (R * B)[0, 0] + + items = [self.one, a] + items + + for i in range(n): + T[i:, i] = items[:n - i + 1] + + transforms[k - 1] = T + + polys = [self._new([self.one, -A[0, 0]])] + + for i, T in enumerate(transforms): + polys.append(T * polys[i]) + + return berk + tuple(map(tuple, polys)) + + def cofactorMatrix(self, method="berkowitz"): + return self.cofactor_matrix(method=method) + + def det_bareis(self): + return _det_bareiss(self) + + def det_LU_decomposition(self): + """Compute matrix determinant using LU decomposition. + + + Note that this method fails if the LU decomposition itself + fails. In particular, if the matrix has no inverse this method + will fail. + + TODO: Implement algorithm for sparse matrices (SFF), + https://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps + + See Also + ======== + + + det + det_bareiss + berkowitz_det + """ + return self.det(method='lu') + + def jordan_cell(self, eigenval, n): + return self.jordan_block(size=n, eigenvalue=eigenval) + + def jordan_cells(self, calc_transformation=True): + P, J = self.jordan_form() + return P, J.get_diag_blocks() + + def minorEntry(self, i, j, method="berkowitz"): + return self.minor(i, j, method=method) + + def minorMatrix(self, i, j): + return self.minor_submatrix(i, j) + + def permuteBkwd(self, perm): + """Permute the rows of the matrix with the given permutation in reverse.""" + return self.permute_rows(perm, direction='backward') + + def permuteFwd(self, perm): + """Permute the rows of the matrix with the given permutation.""" + return self.permute_rows(perm, direction='forward') diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/matrixbase.py b/venv/lib/python3.10/site-packages/sympy/matrices/matrixbase.py new file mode 100644 index 0000000000000000000000000000000000000000..49acc04043b30e003f7eed256f2e06e6a6556401 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/matrixbase.py @@ -0,0 +1,5428 @@ +from __future__ import annotations +from collections import defaultdict +from collections.abc import Iterable +from inspect import isfunction +from functools import reduce + +from sympy.assumptions.refine import refine +from sympy.core import SympifyError, Add +from sympy.core.basic import Atom, Basic +from sympy.core.kind import UndefinedKind +from sympy.core.numbers import Integer +from sympy.core.mod import Mod +from sympy.core.symbol import Symbol, Dummy +from sympy.core.sympify import sympify, _sympify +from sympy.core.function import diff +from sympy.polys import cancel +from sympy.functions.elementary.complexes import Abs, re, im +from sympy.printing import sstr +from sympy.functions.elementary.miscellaneous import Max, Min, sqrt +from sympy.functions.special.tensor_functions import KroneckerDelta, LeviCivita +from sympy.core.singleton import S +from sympy.printing.defaults import Printable +from sympy.printing.str import StrPrinter +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.combinatorial.factorials import binomial, factorial + +import mpmath as mp +from collections.abc import Callable +from sympy.utilities.iterables import reshape +from sympy.core.expr import Expr +from sympy.core.power import Pow +from sympy.core.symbol import uniquely_named_symbol + +from .utilities import _dotprodsimp, _simplify as _utilities_simplify +from sympy.polys.polytools import Poly +from sympy.utilities.iterables import flatten, is_sequence +from sympy.utilities.misc import as_int, filldedent +from sympy.core.decorators import call_highest_priority +from sympy.core.logic import fuzzy_and, FuzzyBool +from sympy.tensor.array import NDimArray +from sympy.utilities.iterables import NotIterable + +from .utilities import _get_intermediate_simp_bool + +from .kind import MatrixKind + +from .exceptions import ( + MatrixError, ShapeError, NonSquareMatrixError, NonInvertibleMatrixError, +) + +from .utilities import _iszero, _is_zero_after_expand_mul + +from .determinant import ( + _find_reasonable_pivot, _find_reasonable_pivot_naive, + _adjugate, _charpoly, _cofactor, _cofactor_matrix, _per, + _det, _det_bareiss, _det_berkowitz, _det_bird, _det_laplace, _det_LU, + _minor, _minor_submatrix) + +from .reductions import _is_echelon, _echelon_form, _rank, _rref + +from .solvers import ( + _diagonal_solve, _lower_triangular_solve, _upper_triangular_solve, + _cholesky_solve, _LDLsolve, _LUsolve, _QRsolve, _gauss_jordan_solve, + _pinv_solve, _cramer_solve, _solve, _solve_least_squares) + +from .inverse import ( + _pinv, _inv_ADJ, _inv_GE, _inv_LU, _inv_CH, _inv_LDL, _inv_QR, + _inv, _inv_block) + +from .subspaces import _columnspace, _nullspace, _rowspace, _orthogonalize + +from .eigen import ( + _eigenvals, _eigenvects, + _bidiagonalize, _bidiagonal_decomposition, + _is_diagonalizable, _diagonalize, + _is_positive_definite, _is_positive_semidefinite, + _is_negative_definite, _is_negative_semidefinite, _is_indefinite, + _jordan_form, _left_eigenvects, _singular_values) + +from .decompositions import ( + _rank_decomposition, _cholesky, _LDLdecomposition, + _LUdecomposition, _LUdecomposition_Simple, _LUdecompositionFF, + _singular_value_decomposition, _QRdecomposition, _upper_hessenberg_decomposition) + +from .graph import ( + _connected_components, _connected_components_decomposition, + _strongly_connected_components, _strongly_connected_components_decomposition) + + +__doctest_requires__ = { + ('MatrixBase.is_indefinite', + 'MatrixBase.is_positive_definite', + 'MatrixBase.is_positive_semidefinite', + 'MatrixBase.is_negative_definite', + 'MatrixBase.is_negative_semidefinite'): ['matplotlib'], +} + + +class MatrixBase(Printable): + """All common matrix operations including basic arithmetic, shaping, + and special matrices like `zeros`, and `eye`.""" + + _op_priority = 10.01 + + # Added just for numpy compatibility + __array_priority__ = 11 + + is_Matrix = True + _class_priority = 3 + _sympify = staticmethod(sympify) + zero = S.Zero + one = S.One + + _diff_wrt: bool = True + rows: int + cols: int + _simplify = None + + @classmethod + def _new(cls, *args, **kwargs): + """`_new` must, at minimum, be callable as + `_new(rows, cols, mat) where mat is a flat list of the + elements of the matrix.""" + raise NotImplementedError("Subclasses must implement this.") + + def __eq__(self, other): + raise NotImplementedError("Subclasses must implement this.") + + def __getitem__(self, key): + """Implementations of __getitem__ should accept ints, in which + case the matrix is indexed as a flat list, tuples (i,j) in which + case the (i,j) entry is returned, slices, or mixed tuples (a,b) + where a and b are any combination of slices and integers.""" + raise NotImplementedError("Subclasses must implement this.") + + @property + def shape(self): + """The shape (dimensions) of the matrix as the 2-tuple (rows, cols). + + Examples + ======== + + >>> from sympy import zeros + >>> M = zeros(2, 3) + >>> M.shape + (2, 3) + >>> M.rows + 2 + >>> M.cols + 3 + """ + return (self.rows, self.cols) + + def _eval_col_del(self, col): + def entry(i, j): + return self[i, j] if j < col else self[i, j + 1] + return self._new(self.rows, self.cols - 1, entry) + + def _eval_col_insert(self, pos, other): + + def entry(i, j): + if j < pos: + return self[i, j] + elif pos <= j < pos + other.cols: + return other[i, j - pos] + return self[i, j - other.cols] + + return self._new(self.rows, self.cols + other.cols, entry) + + def _eval_col_join(self, other): + rows = self.rows + + def entry(i, j): + if i < rows: + return self[i, j] + return other[i - rows, j] + + return classof(self, other)._new(self.rows + other.rows, self.cols, + entry) + + def _eval_extract(self, rowsList, colsList): + mat = list(self) + cols = self.cols + indices = (i * cols + j for i in rowsList for j in colsList) + return self._new(len(rowsList), len(colsList), + [mat[i] for i in indices]) + + def _eval_get_diag_blocks(self): + sub_blocks = [] + + def recurse_sub_blocks(M): + for i in range(1, M.shape[0] + 1): + if i == 1: + to_the_right = M[0, i:] + to_the_bottom = M[i:, 0] + else: + to_the_right = M[:i, i:] + to_the_bottom = M[i:, :i] + if any(to_the_right) or any(to_the_bottom): + continue + sub_blocks.append(M[:i, :i]) + if M.shape != M[:i, :i].shape: + recurse_sub_blocks(M[i:, i:]) + return + + recurse_sub_blocks(self) + return sub_blocks + + def _eval_row_del(self, row): + def entry(i, j): + return self[i, j] if i < row else self[i + 1, j] + return self._new(self.rows - 1, self.cols, entry) + + def _eval_row_insert(self, pos, other): + entries = list(self) + insert_pos = pos * self.cols + entries[insert_pos:insert_pos] = list(other) + return self._new(self.rows + other.rows, self.cols, entries) + + def _eval_row_join(self, other): + cols = self.cols + + def entry(i, j): + if j < cols: + return self[i, j] + return other[i, j - cols] + + return classof(self, other)._new(self.rows, self.cols + other.cols, + entry) + + def _eval_tolist(self): + return [list(self[i,:]) for i in range(self.rows)] + + def _eval_todok(self): + dok = {} + rows, cols = self.shape + for i in range(rows): + for j in range(cols): + val = self[i, j] + if val != self.zero: + dok[i, j] = val + return dok + + @classmethod + def _eval_from_dok(cls, rows, cols, dok): + out_flat = [cls.zero] * (rows * cols) + for (i, j), val in dok.items(): + out_flat[i * cols + j] = val + return cls._new(rows, cols, out_flat) + + def _eval_vec(self): + rows = self.rows + + def entry(n, _): + # we want to read off the columns first + j = n // rows + i = n - j * rows + return self[i, j] + + return self._new(len(self), 1, entry) + + def _eval_vech(self, diagonal): + c = self.cols + v = [] + if diagonal: + for j in range(c): + for i in range(j, c): + v.append(self[i, j]) + else: + for j in range(c): + for i in range(j + 1, c): + v.append(self[i, j]) + return self._new(len(v), 1, v) + + def col_del(self, col): + """Delete the specified column.""" + if col < 0: + col += self.cols + if not 0 <= col < self.cols: + raise IndexError("Column {} is out of range.".format(col)) + return self._eval_col_del(col) + + def col_insert(self, pos, other): + """Insert one or more columns at the given column position. + + Examples + ======== + + >>> from sympy import zeros, ones + >>> M = zeros(3) + >>> V = ones(3, 1) + >>> M.col_insert(1, V) + Matrix([ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0]]) + + See Also + ======== + + col + row_insert + """ + # Allows you to build a matrix even if it is null matrix + if not self: + return type(self)(other) + + pos = as_int(pos) + + if pos < 0: + pos = self.cols + pos + if pos < 0: + pos = 0 + elif pos > self.cols: + pos = self.cols + + if self.rows != other.rows: + raise ShapeError( + "The matrices have incompatible number of rows ({} and {})" + .format(self.rows, other.rows)) + + return self._eval_col_insert(pos, other) + + def col_join(self, other): + """Concatenates two matrices along self's last and other's first row. + + Examples + ======== + + >>> from sympy import zeros, ones + >>> M = zeros(3) + >>> V = ones(1, 3) + >>> M.col_join(V) + Matrix([ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [1, 1, 1]]) + + See Also + ======== + + col + row_join + """ + # A null matrix can always be stacked (see #10770) + if self.rows == 0 and self.cols != other.cols: + return self._new(0, other.cols, []).col_join(other) + + if self.cols != other.cols: + raise ShapeError( + "The matrices have incompatible number of columns ({} and {})" + .format(self.cols, other.cols)) + return self._eval_col_join(other) + + def col(self, j): + """Elementary column selector. + + Examples + ======== + + >>> from sympy import eye + >>> eye(2).col(0) + Matrix([ + [1], + [0]]) + + See Also + ======== + + row + col_del + col_join + col_insert + """ + return self[:, j] + + def extract(self, rowsList, colsList): + r"""Return a submatrix by specifying a list of rows and columns. + Negative indices can be given. All indices must be in the range + $-n \le i < n$ where $n$ is the number of rows or columns. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(4, 3, range(12)) + >>> m + Matrix([ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11]]) + >>> m.extract([0, 1, 3], [0, 1]) + Matrix([ + [0, 1], + [3, 4], + [9, 10]]) + + Rows or columns can be repeated: + + >>> m.extract([0, 0, 1], [-1]) + Matrix([ + [2], + [2], + [5]]) + + Every other row can be taken by using range to provide the indices: + + >>> m.extract(range(0, m.rows, 2), [-1]) + Matrix([ + [2], + [8]]) + + RowsList or colsList can also be a list of booleans, in which case + the rows or columns corresponding to the True values will be selected: + + >>> m.extract([0, 1, 2, 3], [True, False, True]) + Matrix([ + [0, 2], + [3, 5], + [6, 8], + [9, 11]]) + """ + + if not is_sequence(rowsList) or not is_sequence(colsList): + raise TypeError("rowsList and colsList must be iterable") + # ensure rowsList and colsList are lists of integers + if rowsList and all(isinstance(i, bool) for i in rowsList): + rowsList = [index for index, item in enumerate(rowsList) if item] + if colsList and all(isinstance(i, bool) for i in colsList): + colsList = [index for index, item in enumerate(colsList) if item] + + # ensure everything is in range + rowsList = [a2idx(k, self.rows) for k in rowsList] + colsList = [a2idx(k, self.cols) for k in colsList] + + return self._eval_extract(rowsList, colsList) + + def get_diag_blocks(self): + """Obtains the square sub-matrices on the main diagonal of a square matrix. + + Useful for inverting symbolic matrices or solving systems of + linear equations which may be decoupled by having a block diagonal + structure. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y, z + >>> A = Matrix([[1, 3, 0, 0], [y, z*z, 0, 0], [0, 0, x, 0], [0, 0, 0, 0]]) + >>> a1, a2, a3 = A.get_diag_blocks() + >>> a1 + Matrix([ + [1, 3], + [y, z**2]]) + >>> a2 + Matrix([[x]]) + >>> a3 + Matrix([[0]]) + + """ + return self._eval_get_diag_blocks() + + @classmethod + def hstack(cls, *args): + """Return a matrix formed by joining args horizontally (i.e. + by repeated application of row_join). + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> Matrix.hstack(eye(2), 2*eye(2)) + Matrix([ + [1, 0, 2, 0], + [0, 1, 0, 2]]) + """ + if len(args) == 0: + return cls._new() + + kls = type(args[0]) + return reduce(kls.row_join, args) + + def reshape(self, rows, cols): + """Reshape the matrix. Total number of elements must remain the same. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 3, lambda i, j: 1) + >>> m + Matrix([ + [1, 1, 1], + [1, 1, 1]]) + >>> m.reshape(1, 6) + Matrix([[1, 1, 1, 1, 1, 1]]) + >>> m.reshape(3, 2) + Matrix([ + [1, 1], + [1, 1], + [1, 1]]) + + """ + if self.rows * self.cols != rows * cols: + raise ValueError("Invalid reshape parameters %d %d" % (rows, cols)) + dok = {divmod(i*self.cols + j, cols): + v for (i, j), v in self.todok().items()} + return self._eval_from_dok(rows, cols, dok) + + def row_del(self, row): + """Delete the specified row.""" + if row < 0: + row += self.rows + if not 0 <= row < self.rows: + raise IndexError("Row {} is out of range.".format(row)) + + return self._eval_row_del(row) + + def row_insert(self, pos, other): + """Insert one or more rows at the given row position. + + Examples + ======== + + >>> from sympy import zeros, ones + >>> M = zeros(3) + >>> V = ones(1, 3) + >>> M.row_insert(1, V) + Matrix([ + [0, 0, 0], + [1, 1, 1], + [0, 0, 0], + [0, 0, 0]]) + + See Also + ======== + + row + col_insert + """ + # Allows you to build a matrix even if it is null matrix + if not self: + return self._new(other) + + pos = as_int(pos) + + if pos < 0: + pos = self.rows + pos + if pos < 0: + pos = 0 + elif pos > self.rows: + pos = self.rows + + if self.cols != other.cols: + raise ShapeError( + "The matrices have incompatible number of columns ({} and {})" + .format(self.cols, other.cols)) + + return self._eval_row_insert(pos, other) + + def row_join(self, other): + """Concatenates two matrices along self's last and rhs's first column + + Examples + ======== + + >>> from sympy import zeros, ones + >>> M = zeros(3) + >>> V = ones(3, 1) + >>> M.row_join(V) + Matrix([ + [0, 0, 0, 1], + [0, 0, 0, 1], + [0, 0, 0, 1]]) + + See Also + ======== + + row + col_join + """ + # A null matrix can always be stacked (see #10770) + if self.cols == 0 and self.rows != other.rows: + return self._new(other.rows, 0, []).row_join(other) + + if self.rows != other.rows: + raise ShapeError( + "The matrices have incompatible number of rows ({} and {})" + .format(self.rows, other.rows)) + return self._eval_row_join(other) + + def diagonal(self, k=0): + """Returns the kth diagonal of self. The main diagonal + corresponds to `k=0`; diagonals above and below correspond to + `k > 0` and `k < 0`, respectively. The values of `self[i, j]` + for which `j - i = k`, are returned in order of increasing + `i + j`, starting with `i + j = |k|`. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(3, 3, lambda i, j: j - i); m + Matrix([ + [ 0, 1, 2], + [-1, 0, 1], + [-2, -1, 0]]) + >>> _.diagonal() + Matrix([[0, 0, 0]]) + >>> m.diagonal(1) + Matrix([[1, 1]]) + >>> m.diagonal(-2) + Matrix([[-2]]) + + Even though the diagonal is returned as a Matrix, the element + retrieval can be done with a single index: + + >>> Matrix.diag(1, 2, 3).diagonal()[1] # instead of [0, 1] + 2 + + See Also + ======== + + diag + """ + rv = [] + k = as_int(k) + r = 0 if k > 0 else -k + c = 0 if r else k + while True: + if r == self.rows or c == self.cols: + break + rv.append(self[r, c]) + r += 1 + c += 1 + if not rv: + raise ValueError(filldedent(''' + The %s diagonal is out of range [%s, %s]''' % ( + k, 1 - self.rows, self.cols - 1))) + return self._new(1, len(rv), rv) + + def row(self, i): + """Elementary row selector. + + Examples + ======== + + >>> from sympy import eye + >>> eye(2).row(0) + Matrix([[1, 0]]) + + See Also + ======== + + col + row_del + row_join + row_insert + """ + return self[i, :] + + def todok(self): + """Return the matrix as dictionary of keys. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix.eye(3) + >>> M.todok() + {(0, 0): 1, (1, 1): 1, (2, 2): 1} + """ + return self._eval_todok() + + @classmethod + def from_dok(cls, rows, cols, dok): + """Create a matrix from a dictionary of keys. + + Examples + ======== + + >>> from sympy import Matrix + >>> d = {(0, 0): 1, (1, 2): 3, (2, 1): 4} + >>> Matrix.from_dok(3, 3, d) + Matrix([ + [1, 0, 0], + [0, 0, 3], + [0, 4, 0]]) + """ + dok = {ij: cls._sympify(val) for ij, val in dok.items()} + return cls._eval_from_dok(rows, cols, dok) + + def tolist(self): + """Return the Matrix as a nested Python list. + + Examples + ======== + + >>> from sympy import Matrix, ones + >>> m = Matrix(3, 3, range(9)) + >>> m + Matrix([ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8]]) + >>> m.tolist() + [[0, 1, 2], [3, 4, 5], [6, 7, 8]] + >>> ones(3, 0).tolist() + [[], [], []] + + When there are no rows then it will not be possible to tell how + many columns were in the original matrix: + + >>> ones(0, 3).tolist() + [] + + """ + if not self.rows: + return [] + if not self.cols: + return [[] for i in range(self.rows)] + return self._eval_tolist() + + def todod(M): + """Returns matrix as dict of dicts containing non-zero elements of the Matrix + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[0, 1],[0, 3]]) + >>> A + Matrix([ + [0, 1], + [0, 3]]) + >>> A.todod() + {0: {1: 1}, 1: {1: 3}} + + + """ + rowsdict = {} + Mlol = M.tolist() + for i, Mi in enumerate(Mlol): + row = {j: Mij for j, Mij in enumerate(Mi) if Mij} + if row: + rowsdict[i] = row + return rowsdict + + def vec(self): + """Return the Matrix converted into a one column matrix by stacking columns + + Examples + ======== + + >>> from sympy import Matrix + >>> m=Matrix([[1, 3], [2, 4]]) + >>> m + Matrix([ + [1, 3], + [2, 4]]) + >>> m.vec() + Matrix([ + [1], + [2], + [3], + [4]]) + + See Also + ======== + + vech + """ + return self._eval_vec() + + def vech(self, diagonal=True, check_symmetry=True): + """Reshapes the matrix into a column vector by stacking the + elements in the lower triangle. + + Parameters + ========== + + diagonal : bool, optional + If ``True``, it includes the diagonal elements. + + check_symmetry : bool, optional + If ``True``, it checks whether the matrix is symmetric. + + Examples + ======== + + >>> from sympy import Matrix + >>> m=Matrix([[1, 2], [2, 3]]) + >>> m + Matrix([ + [1, 2], + [2, 3]]) + >>> m.vech() + Matrix([ + [1], + [2], + [3]]) + >>> m.vech(diagonal=False) + Matrix([[2]]) + + Notes + ===== + + This should work for symmetric matrices and ``vech`` can + represent symmetric matrices in vector form with less size than + ``vec``. + + See Also + ======== + + vec + """ + if not self.is_square: + raise NonSquareMatrixError + + if check_symmetry and not self.is_symmetric(): + raise ValueError("The matrix is not symmetric.") + + return self._eval_vech(diagonal) + + @classmethod + def vstack(cls, *args): + """Return a matrix formed by joining args vertically (i.e. + by repeated application of col_join). + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> Matrix.vstack(eye(2), 2*eye(2)) + Matrix([ + [1, 0], + [0, 1], + [2, 0], + [0, 2]]) + """ + if len(args) == 0: + return cls._new() + + kls = type(args[0]) + return reduce(kls.col_join, args) + + @classmethod + def _eval_diag(cls, rows, cols, diag_dict): + """diag_dict is a defaultdict containing + all the entries of the diagonal matrix.""" + def entry(i, j): + return diag_dict[(i, j)] + return cls._new(rows, cols, entry) + + @classmethod + def _eval_eye(cls, rows, cols): + vals = [cls.zero]*(rows*cols) + vals[::cols+1] = [cls.one]*min(rows, cols) + return cls._new(rows, cols, vals, copy=False) + + @classmethod + def _eval_jordan_block(cls, size: int, eigenvalue, band='upper'): + if band == 'lower': + def entry(i, j): + if i == j: + return eigenvalue + elif j + 1 == i: + return cls.one + return cls.zero + else: + def entry(i, j): + if i == j: + return eigenvalue + elif i + 1 == j: + return cls.one + return cls.zero + return cls._new(size, size, entry) + + @classmethod + def _eval_ones(cls, rows, cols): + def entry(i, j): + return cls.one + return cls._new(rows, cols, entry) + + @classmethod + def _eval_zeros(cls, rows, cols): + return cls._new(rows, cols, [cls.zero]*(rows*cols), copy=False) + + @classmethod + def _eval_wilkinson(cls, n): + def entry(i, j): + return cls.one if i + 1 == j else cls.zero + + D = cls._new(2*n + 1, 2*n + 1, entry) + + wminus = cls.diag(list(range(-n, n + 1)), unpack=True) + D + D.T + wplus = abs(cls.diag(list(range(-n, n + 1)), unpack=True)) + D + D.T + + return wminus, wplus + + @classmethod + def diag(kls, *args, strict=False, unpack=True, rows=None, cols=None, **kwargs): + """Returns a matrix with the specified diagonal. + If matrices are passed, a block-diagonal matrix + is created (i.e. the "direct sum" of the matrices). + + kwargs + ====== + + rows : rows of the resulting matrix; computed if + not given. + + cols : columns of the resulting matrix; computed if + not given. + + cls : class for the resulting matrix + + unpack : bool which, when True (default), unpacks a single + sequence rather than interpreting it as a Matrix. + + strict : bool which, when False (default), allows Matrices to + have variable-length rows. + + Examples + ======== + + >>> from sympy import Matrix + >>> Matrix.diag(1, 2, 3) + Matrix([ + [1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + + The current default is to unpack a single sequence. If this is + not desired, set `unpack=False` and it will be interpreted as + a matrix. + + >>> Matrix.diag([1, 2, 3]) == Matrix.diag(1, 2, 3) + True + + When more than one element is passed, each is interpreted as + something to put on the diagonal. Lists are converted to + matrices. Filling of the diagonal always continues from + the bottom right hand corner of the previous item: this + will create a block-diagonal matrix whether the matrices + are square or not. + + >>> col = [1, 2, 3] + >>> row = [[4, 5]] + >>> Matrix.diag(col, row) + Matrix([ + [1, 0, 0], + [2, 0, 0], + [3, 0, 0], + [0, 4, 5]]) + + When `unpack` is False, elements within a list need not all be + of the same length. Setting `strict` to True would raise a + ValueError for the following: + + >>> Matrix.diag([[1, 2, 3], [4, 5], [6]], unpack=False) + Matrix([ + [1, 2, 3], + [4, 5, 0], + [6, 0, 0]]) + + The type of the returned matrix can be set with the ``cls`` + keyword. + + >>> from sympy import ImmutableMatrix + >>> from sympy.utilities.misc import func_name + >>> func_name(Matrix.diag(1, cls=ImmutableMatrix)) + 'ImmutableDenseMatrix' + + A zero dimension matrix can be used to position the start of + the filling at the start of an arbitrary row or column: + + >>> from sympy import ones + >>> r2 = ones(0, 2) + >>> Matrix.diag(r2, 1, 2) + Matrix([ + [0, 0, 1, 0], + [0, 0, 0, 2]]) + + See Also + ======== + eye + diagonal + .dense.diag + .expressions.blockmatrix.BlockMatrix + .sparsetools.banded + """ + from sympy.matrices.matrixbase import MatrixBase + from sympy.matrices.dense import Matrix + from sympy.matrices import SparseMatrix + klass = kwargs.get('cls', kls) + if unpack and len(args) == 1 and is_sequence(args[0]) and \ + not isinstance(args[0], MatrixBase): + args = args[0] + + # fill a default dict with the diagonal entries + diag_entries = defaultdict(int) + rmax = cmax = 0 # keep track of the biggest index seen + for m in args: + if isinstance(m, list): + if strict: + # if malformed, Matrix will raise an error + _ = Matrix(m) + r, c = _.shape + m = _.tolist() + else: + r, c, smat = SparseMatrix._handle_creation_inputs(m) + for (i, j), _ in smat.items(): + diag_entries[(i + rmax, j + cmax)] = _ + m = [] # to skip process below + elif hasattr(m, 'shape'): # a Matrix + # convert to list of lists + r, c = m.shape + m = m.tolist() + else: # in this case, we're a single value + diag_entries[(rmax, cmax)] = m + rmax += 1 + cmax += 1 + continue + # process list of lists + for i, mi in enumerate(m): + for j, _ in enumerate(mi): + diag_entries[(i + rmax, j + cmax)] = _ + rmax += r + cmax += c + if rows is None: + rows, cols = cols, rows + if rows is None: + rows, cols = rmax, cmax + else: + cols = rows if cols is None else cols + if rows < rmax or cols < cmax: + raise ValueError(filldedent(''' + The constructed matrix is {} x {} but a size of {} x {} + was specified.'''.format(rmax, cmax, rows, cols))) + return klass._eval_diag(rows, cols, diag_entries) + + @classmethod + def eye(kls, rows, cols=None, **kwargs): + """Returns an identity matrix. + + Parameters + ========== + + rows : rows of the matrix + cols : cols of the matrix (if None, cols=rows) + + kwargs + ====== + cls : class of the returned matrix + """ + if cols is None: + cols = rows + if rows < 0 or cols < 0: + raise ValueError("Cannot create a {} x {} matrix. " + "Both dimensions must be positive".format(rows, cols)) + klass = kwargs.get('cls', kls) + rows, cols = as_int(rows), as_int(cols) + + return klass._eval_eye(rows, cols) + + @classmethod + def jordan_block(kls, size=None, eigenvalue=None, *, band='upper', **kwargs): + """Returns a Jordan block + + Parameters + ========== + + size : Integer, optional + Specifies the shape of the Jordan block matrix. + + eigenvalue : Number or Symbol + Specifies the value for the main diagonal of the matrix. + + .. note:: + The keyword ``eigenval`` is also specified as an alias + of this keyword, but it is not recommended to use. + + We may deprecate the alias in later release. + + band : 'upper' or 'lower', optional + Specifies the position of the off-diagonal to put `1` s on. + + cls : Matrix, optional + Specifies the matrix class of the output form. + + If it is not specified, the class type where the method is + being executed on will be returned. + + Returns + ======= + + Matrix + A Jordan block matrix. + + Raises + ====== + + ValueError + If insufficient arguments are given for matrix size + specification, or no eigenvalue is given. + + Examples + ======== + + Creating a default Jordan block: + + >>> from sympy import Matrix + >>> from sympy.abc import x + >>> Matrix.jordan_block(4, x) + Matrix([ + [x, 1, 0, 0], + [0, x, 1, 0], + [0, 0, x, 1], + [0, 0, 0, x]]) + + Creating an alternative Jordan block matrix where `1` is on + lower off-diagonal: + + >>> Matrix.jordan_block(4, x, band='lower') + Matrix([ + [x, 0, 0, 0], + [1, x, 0, 0], + [0, 1, x, 0], + [0, 0, 1, x]]) + + Creating a Jordan block with keyword arguments + + >>> Matrix.jordan_block(size=4, eigenvalue=x) + Matrix([ + [x, 1, 0, 0], + [0, x, 1, 0], + [0, 0, x, 1], + [0, 0, 0, x]]) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Jordan_matrix + """ + klass = kwargs.pop('cls', kls) + + eigenval = kwargs.get('eigenval', None) + if eigenvalue is None and eigenval is None: + raise ValueError("Must supply an eigenvalue") + elif eigenvalue != eigenval and None not in (eigenval, eigenvalue): + raise ValueError( + "Inconsistent values are given: 'eigenval'={}, " + "'eigenvalue'={}".format(eigenval, eigenvalue)) + else: + if eigenval is not None: + eigenvalue = eigenval + + if size is None: + raise ValueError("Must supply a matrix size") + + size = as_int(size) + return klass._eval_jordan_block(size, eigenvalue, band) + + @classmethod + def ones(kls, rows, cols=None, **kwargs): + """Returns a matrix of ones. + + Parameters + ========== + + rows : rows of the matrix + cols : cols of the matrix (if None, cols=rows) + + kwargs + ====== + cls : class of the returned matrix + """ + if cols is None: + cols = rows + klass = kwargs.get('cls', kls) + rows, cols = as_int(rows), as_int(cols) + + return klass._eval_ones(rows, cols) + + @classmethod + def zeros(kls, rows, cols=None, **kwargs): + """Returns a matrix of zeros. + + Parameters + ========== + + rows : rows of the matrix + cols : cols of the matrix (if None, cols=rows) + + kwargs + ====== + cls : class of the returned matrix + """ + if cols is None: + cols = rows + if rows < 0 or cols < 0: + raise ValueError("Cannot create a {} x {} matrix. " + "Both dimensions must be positive".format(rows, cols)) + klass = kwargs.get('cls', kls) + rows, cols = as_int(rows), as_int(cols) + + return klass._eval_zeros(rows, cols) + + @classmethod + def companion(kls, poly): + """Returns a companion matrix of a polynomial. + + Examples + ======== + + >>> from sympy import Matrix, Poly, Symbol, symbols + >>> x = Symbol('x') + >>> c0, c1, c2, c3, c4 = symbols('c0:5') + >>> p = Poly(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + x**5, x) + >>> Matrix.companion(p) + Matrix([ + [0, 0, 0, 0, -c0], + [1, 0, 0, 0, -c1], + [0, 1, 0, 0, -c2], + [0, 0, 1, 0, -c3], + [0, 0, 0, 1, -c4]]) + """ + poly = kls._sympify(poly) + if not isinstance(poly, Poly): + raise ValueError("{} must be a Poly instance.".format(poly)) + if not poly.is_monic: + raise ValueError("{} must be a monic polynomial.".format(poly)) + if not poly.is_univariate: + raise ValueError( + "{} must be a univariate polynomial.".format(poly)) + + size = poly.degree() + if not size >= 1: + raise ValueError( + "{} must have degree not less than 1.".format(poly)) + + coeffs = poly.all_coeffs() + def entry(i, j): + if j == size - 1: + return -coeffs[-1 - i] + elif i == j + 1: + return kls.one + return kls.zero + return kls._new(size, size, entry) + + + @classmethod + def wilkinson(kls, n, **kwargs): + """Returns two square Wilkinson Matrix of size 2*n + 1 + $W_{2n + 1}^-, W_{2n + 1}^+ =$ Wilkinson(n) + + Examples + ======== + + >>> from sympy import Matrix + >>> wminus, wplus = Matrix.wilkinson(3) + >>> wminus + Matrix([ + [-3, 1, 0, 0, 0, 0, 0], + [ 1, -2, 1, 0, 0, 0, 0], + [ 0, 1, -1, 1, 0, 0, 0], + [ 0, 0, 1, 0, 1, 0, 0], + [ 0, 0, 0, 1, 1, 1, 0], + [ 0, 0, 0, 0, 1, 2, 1], + [ 0, 0, 0, 0, 0, 1, 3]]) + >>> wplus + Matrix([ + [3, 1, 0, 0, 0, 0, 0], + [1, 2, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0], + [0, 0, 0, 0, 1, 2, 1], + [0, 0, 0, 0, 0, 1, 3]]) + + References + ========== + + .. [1] https://blogs.mathworks.com/cleve/2013/04/15/wilkinsons-matrices-2/ + .. [2] J. H. Wilkinson, The Algebraic Eigenvalue Problem, Claredon Press, Oxford, 1965, 662 pp. + + """ + klass = kwargs.get('cls', kls) + n = as_int(n) + return klass._eval_wilkinson(n) + + # The RepMatrix subclass uses more efficient sparse implementations of + # _eval_iter_values and other things. + + def _eval_iter_values(self): + return (i for i in self if i is not S.Zero) + + def _eval_values(self): + return list(self.iter_values()) + + def _eval_iter_items(self): + for i in range(self.rows): + for j in range(self.cols): + if self[i, j]: + yield (i, j), self[i, j] + + def _eval_atoms(self, *types): + values = self.values() + if len(values) < self.rows * self.cols and isinstance(S.Zero, types): + s = {S.Zero} + else: + s = set() + return s.union(*[v.atoms(*types) for v in values]) + + def _eval_free_symbols(self): + return set().union(*(i.free_symbols for i in set(self.values()))) + + def _eval_has(self, *patterns): + return any(a.has(*patterns) for a in self.iter_values()) + + def _eval_is_symbolic(self): + return self.has(Symbol) + + # _eval_is_hermitian is called by some general SymPy + # routines and has a different *args signature. Make + # sure the names don't clash by adding `_matrix_` in name. + def _eval_is_matrix_hermitian(self, simpfunc): + herm = lambda i, j: simpfunc(self[i, j] - self[j, i].adjoint()).is_zero + return fuzzy_and(herm(i, j) for (i, j), v in self.iter_items()) + + def _eval_is_zero_matrix(self): + return fuzzy_and(v.is_zero for v in self.iter_values()) + + def _eval_is_Identity(self) -> FuzzyBool: + one = self.one + zero = self.zero + ident = lambda i, j, v: v is one if i == j else v is zero + return all(ident(i, j, v) for (i, j), v in self.iter_items()) + + def _eval_is_diagonal(self): + return fuzzy_and(v.is_zero for (i, j), v in self.iter_items() if i != j) + + def _eval_is_lower(self): + return all(v.is_zero for (i, j), v in self.iter_items() if i < j) + + def _eval_is_upper(self): + return all(v.is_zero for (i, j), v in self.iter_items() if i > j) + + def _eval_is_lower_hessenberg(self): + return all(v.is_zero for (i, j), v in self.iter_items() if i + 1 < j) + + def _eval_is_upper_hessenberg(self): + return all(v.is_zero for (i, j), v in self.iter_items() if i > j + 1) + + def _eval_is_symmetric(self, simpfunc): + sym = lambda i, j: simpfunc(self[i, j] - self[j, i]).is_zero + return fuzzy_and(sym(i, j) for (i, j), v in self.iter_items()) + + def _eval_is_anti_symmetric(self, simpfunc): + anti = lambda i, j: simpfunc(self[i, j] + self[j, i]).is_zero + return fuzzy_and(anti(i, j) for (i, j), v in self.iter_items()) + + def _has_positive_diagonals(self): + diagonal_entries = (self[i, i] for i in range(self.rows)) + return fuzzy_and(x.is_positive for x in diagonal_entries) + + def _has_nonnegative_diagonals(self): + diagonal_entries = (self[i, i] for i in range(self.rows)) + return fuzzy_and(x.is_nonnegative for x in diagonal_entries) + + def atoms(self, *types): + """Returns the atoms that form the current object. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import Matrix + >>> Matrix([[x]]) + Matrix([[x]]) + >>> _.atoms() + {x} + >>> Matrix([[x, y], [y, x]]) + Matrix([ + [x, y], + [y, x]]) + >>> _.atoms() + {x, y} + """ + + types = tuple(t if isinstance(t, type) else type(t) for t in types) + if not types: + types = (Atom,) + return self._eval_atoms(*types) + + @property + def free_symbols(self): + """Returns the free symbols within the matrix. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import Matrix + >>> Matrix([[x], [1]]).free_symbols + {x} + """ + return self._eval_free_symbols() + + def has(self, *patterns): + """Test whether any subexpression matches any of the patterns. + + Examples + ======== + + >>> from sympy import Matrix, SparseMatrix, Float + >>> from sympy.abc import x, y + >>> A = Matrix(((1, x), (0.2, 3))) + >>> B = SparseMatrix(((1, x), (0.2, 3))) + >>> A.has(x) + True + >>> A.has(y) + False + >>> A.has(Float) + True + >>> B.has(x) + True + >>> B.has(y) + False + >>> B.has(Float) + True + """ + return self._eval_has(*patterns) + + def is_anti_symmetric(self, simplify=True): + """Check if matrix M is an antisymmetric matrix, + that is, M is a square matrix with all M[i, j] == -M[j, i]. + + When ``simplify=True`` (default), the sum M[i, j] + M[j, i] is + simplified before testing to see if it is zero. By default, + the SymPy simplify function is used. To use a custom function + set simplify to a function that accepts a single argument which + returns a simplified expression. To skip simplification, set + simplify to False but note that although this will be faster, + it may induce false negatives. + + Examples + ======== + + >>> from sympy import Matrix, symbols + >>> m = Matrix(2, 2, [0, 1, -1, 0]) + >>> m + Matrix([ + [ 0, 1], + [-1, 0]]) + >>> m.is_anti_symmetric() + True + >>> x, y = symbols('x y') + >>> m = Matrix(2, 3, [0, 0, x, -y, 0, 0]) + >>> m + Matrix([ + [ 0, 0, x], + [-y, 0, 0]]) + >>> m.is_anti_symmetric() + False + + >>> from sympy.abc import x, y + >>> m = Matrix(3, 3, [0, x**2 + 2*x + 1, y, + ... -(x + 1)**2, 0, x*y, + ... -y, -x*y, 0]) + + Simplification of matrix elements is done by default so even + though two elements which should be equal and opposite would not + pass an equality test, the matrix is still reported as + anti-symmetric: + + >>> m[0, 1] == -m[1, 0] + False + >>> m.is_anti_symmetric() + True + + If ``simplify=False`` is used for the case when a Matrix is already + simplified, this will speed things up. Here, we see that without + simplification the matrix does not appear anti-symmetric: + + >>> print(m.is_anti_symmetric(simplify=False)) + None + + But if the matrix were already expanded, then it would appear + anti-symmetric and simplification in the is_anti_symmetric routine + is not needed: + + >>> m = m.expand() + >>> m.is_anti_symmetric(simplify=False) + True + """ + # accept custom simplification + simpfunc = simplify + if not isfunction(simplify): + simpfunc = _utilities_simplify if simplify else lambda x: x + + if not self.is_square: + return False + return self._eval_is_anti_symmetric(simpfunc) + + def is_diagonal(self): + """Check if matrix is diagonal, + that is matrix in which the entries outside the main diagonal are all zero. + + Examples + ======== + + >>> from sympy import Matrix, diag + >>> m = Matrix(2, 2, [1, 0, 0, 2]) + >>> m + Matrix([ + [1, 0], + [0, 2]]) + >>> m.is_diagonal() + True + + >>> m = Matrix(2, 2, [1, 1, 0, 2]) + >>> m + Matrix([ + [1, 1], + [0, 2]]) + >>> m.is_diagonal() + False + + >>> m = diag(1, 2, 3) + >>> m + Matrix([ + [1, 0, 0], + [0, 2, 0], + [0, 0, 3]]) + >>> m.is_diagonal() + True + + See Also + ======== + + is_lower + is_upper + sympy.matrices.matrixbase.MatrixBase.is_diagonalizable + diagonalize + """ + return self._eval_is_diagonal() + + @property + def is_weakly_diagonally_dominant(self): + r"""Tests if the matrix is row weakly diagonally dominant. + + Explanation + =========== + + A $n, n$ matrix $A$ is row weakly diagonally dominant if + + .. math:: + \left|A_{i, i}\right| \ge \sum_{j = 0, j \neq i}^{n-1} + \left|A_{i, j}\right| \quad {\text{for all }} + i \in \{ 0, ..., n-1 \} + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[3, -2, 1], [1, -3, 2], [-1, 2, 4]]) + >>> A.is_weakly_diagonally_dominant + True + + >>> A = Matrix([[-2, 2, 1], [1, 3, 2], [1, -2, 0]]) + >>> A.is_weakly_diagonally_dominant + False + + >>> A = Matrix([[-4, 2, 1], [1, 6, 2], [1, -2, 5]]) + >>> A.is_weakly_diagonally_dominant + True + + Notes + ===== + + If you want to test whether a matrix is column diagonally + dominant, you can apply the test after transposing the matrix. + """ + if not self.is_square: + return False + + rows, cols = self.shape + + def test_row(i): + summation = self.zero + for j in range(cols): + if i != j: + summation += Abs(self[i, j]) + return (Abs(self[i, i]) - summation).is_nonnegative + + return fuzzy_and(test_row(i) for i in range(rows)) + + @property + def is_strongly_diagonally_dominant(self): + r"""Tests if the matrix is row strongly diagonally dominant. + + Explanation + =========== + + A $n, n$ matrix $A$ is row strongly diagonally dominant if + + .. math:: + \left|A_{i, i}\right| > \sum_{j = 0, j \neq i}^{n-1} + \left|A_{i, j}\right| \quad {\text{for all }} + i \in \{ 0, ..., n-1 \} + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[3, -2, 1], [1, -3, 2], [-1, 2, 4]]) + >>> A.is_strongly_diagonally_dominant + False + + >>> A = Matrix([[-2, 2, 1], [1, 3, 2], [1, -2, 0]]) + >>> A.is_strongly_diagonally_dominant + False + + >>> A = Matrix([[-4, 2, 1], [1, 6, 2], [1, -2, 5]]) + >>> A.is_strongly_diagonally_dominant + True + + Notes + ===== + + If you want to test whether a matrix is column diagonally + dominant, you can apply the test after transposing the matrix. + """ + if not self.is_square: + return False + + rows, cols = self.shape + + def test_row(i): + summation = self.zero + for j in range(cols): + if i != j: + summation += Abs(self[i, j]) + return (Abs(self[i, i]) - summation).is_positive + + return fuzzy_and(test_row(i) for i in range(rows)) + + @property + def is_hermitian(self): + """Checks if the matrix is Hermitian. + + In a Hermitian matrix element i,j is the complex conjugate of + element j,i. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy import I + >>> from sympy.abc import x + >>> a = Matrix([[1, I], [-I, 1]]) + >>> a + Matrix([ + [ 1, I], + [-I, 1]]) + >>> a.is_hermitian + True + >>> a[0, 0] = 2*I + >>> a.is_hermitian + False + >>> a[0, 0] = x + >>> a.is_hermitian + >>> a[0, 1] = a[1, 0]*I + >>> a.is_hermitian + False + """ + if not self.is_square: + return False + + return self._eval_is_matrix_hermitian(_utilities_simplify) + + @property + def is_Identity(self) -> FuzzyBool: + if not self.is_square: + return False + return self._eval_is_Identity() + + @property + def is_lower_hessenberg(self): + r"""Checks if the matrix is in the lower-Hessenberg form. + + The lower hessenberg matrix has zero entries + above the first superdiagonal. + + Examples + ======== + + >>> from sympy import Matrix + >>> a = Matrix([[1, 2, 0, 0], [5, 2, 3, 0], [3, 4, 3, 7], [5, 6, 1, 1]]) + >>> a + Matrix([ + [1, 2, 0, 0], + [5, 2, 3, 0], + [3, 4, 3, 7], + [5, 6, 1, 1]]) + >>> a.is_lower_hessenberg + True + + See Also + ======== + + is_upper_hessenberg + is_lower + """ + return self._eval_is_lower_hessenberg() + + @property + def is_lower(self): + """Check if matrix is a lower triangular matrix. True can be returned + even if the matrix is not square. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 2, [1, 0, 0, 1]) + >>> m + Matrix([ + [1, 0], + [0, 1]]) + >>> m.is_lower + True + + >>> m = Matrix(4, 3, [0, 0, 0, 2, 0, 0, 1, 4, 0, 6, 6, 5]) + >>> m + Matrix([ + [0, 0, 0], + [2, 0, 0], + [1, 4, 0], + [6, 6, 5]]) + >>> m.is_lower + True + + >>> from sympy.abc import x, y + >>> m = Matrix(2, 2, [x**2 + y, y**2 + x, 0, x + y]) + >>> m + Matrix([ + [x**2 + y, x + y**2], + [ 0, x + y]]) + >>> m.is_lower + False + + See Also + ======== + + is_upper + is_diagonal + is_lower_hessenberg + """ + return self._eval_is_lower() + + @property + def is_square(self): + """Checks if a matrix is square. + + A matrix is square if the number of rows equals the number of columns. + The empty matrix is square by definition, since the number of rows and + the number of columns are both zero. + + Examples + ======== + + >>> from sympy import Matrix + >>> a = Matrix([[1, 2, 3], [4, 5, 6]]) + >>> b = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> c = Matrix([]) + >>> a.is_square + False + >>> b.is_square + True + >>> c.is_square + True + """ + return self.rows == self.cols + + def is_symbolic(self): + """Checks if any elements contain Symbols. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[x, y], [1, 0]]) + >>> M.is_symbolic() + True + + """ + return self._eval_is_symbolic() + + def is_symmetric(self, simplify=True): + """Check if matrix is symmetric matrix, + that is square matrix and is equal to its transpose. + + By default, simplifications occur before testing symmetry. + They can be skipped using 'simplify=False'; while speeding things a bit, + this may however induce false negatives. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 2, [0, 1, 1, 2]) + >>> m + Matrix([ + [0, 1], + [1, 2]]) + >>> m.is_symmetric() + True + + >>> m = Matrix(2, 2, [0, 1, 2, 0]) + >>> m + Matrix([ + [0, 1], + [2, 0]]) + >>> m.is_symmetric() + False + + >>> m = Matrix(2, 3, [0, 0, 0, 0, 0, 0]) + >>> m + Matrix([ + [0, 0, 0], + [0, 0, 0]]) + >>> m.is_symmetric() + False + + >>> from sympy.abc import x, y + >>> m = Matrix(3, 3, [1, x**2 + 2*x + 1, y, (x + 1)**2, 2, 0, y, 0, 3]) + >>> m + Matrix([ + [ 1, x**2 + 2*x + 1, y], + [(x + 1)**2, 2, 0], + [ y, 0, 3]]) + >>> m.is_symmetric() + True + + If the matrix is already simplified, you may speed-up is_symmetric() + test by using 'simplify=False'. + + >>> bool(m.is_symmetric(simplify=False)) + False + >>> m1 = m.expand() + >>> m1.is_symmetric(simplify=False) + True + """ + simpfunc = simplify + if not isfunction(simplify): + simpfunc = _utilities_simplify if simplify else lambda x: x + + if not self.is_square: + return False + + return self._eval_is_symmetric(simpfunc) + + @property + def is_upper_hessenberg(self): + """Checks if the matrix is the upper-Hessenberg form. + + The upper hessenberg matrix has zero entries + below the first subdiagonal. + + Examples + ======== + + >>> from sympy import Matrix + >>> a = Matrix([[1, 4, 2, 3], [3, 4, 1, 7], [0, 2, 3, 4], [0, 0, 1, 3]]) + >>> a + Matrix([ + [1, 4, 2, 3], + [3, 4, 1, 7], + [0, 2, 3, 4], + [0, 0, 1, 3]]) + >>> a.is_upper_hessenberg + True + + See Also + ======== + + is_lower_hessenberg + is_upper + """ + return self._eval_is_upper_hessenberg() + + @property + def is_upper(self): + """Check if matrix is an upper triangular matrix. True can be returned + even if the matrix is not square. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 2, [1, 0, 0, 1]) + >>> m + Matrix([ + [1, 0], + [0, 1]]) + >>> m.is_upper + True + + >>> m = Matrix(4, 3, [5, 1, 9, 0, 4, 6, 0, 0, 5, 0, 0, 0]) + >>> m + Matrix([ + [5, 1, 9], + [0, 4, 6], + [0, 0, 5], + [0, 0, 0]]) + >>> m.is_upper + True + + >>> m = Matrix(2, 3, [4, 2, 5, 6, 1, 1]) + >>> m + Matrix([ + [4, 2, 5], + [6, 1, 1]]) + >>> m.is_upper + False + + See Also + ======== + + is_lower + is_diagonal + is_upper_hessenberg + """ + return self._eval_is_upper() + + @property + def is_zero_matrix(self): + """Checks if a matrix is a zero matrix. + + A matrix is zero if every element is zero. A matrix need not be square + to be considered zero. The empty matrix is zero by the principle of + vacuous truth. For a matrix that may or may not be zero (e.g. + contains a symbol), this will be None + + Examples + ======== + + >>> from sympy import Matrix, zeros + >>> from sympy.abc import x + >>> a = Matrix([[0, 0], [0, 0]]) + >>> b = zeros(3, 4) + >>> c = Matrix([[0, 1], [0, 0]]) + >>> d = Matrix([]) + >>> e = Matrix([[x, 0], [0, 0]]) + >>> a.is_zero_matrix + True + >>> b.is_zero_matrix + True + >>> c.is_zero_matrix + False + >>> d.is_zero_matrix + True + >>> e.is_zero_matrix + """ + return self._eval_is_zero_matrix() + + def values(self): + """Return non-zero values of self. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix([[0, 1], [2, 3]]) + >>> m.values() + [1, 2, 3] + + See Also + ======== + + iter_values + tolist + flat + """ + return self._eval_values() + + def iter_values(self): + """ + Iterate over non-zero values of self. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix([[0, 1], [2, 3]]) + >>> list(m.iter_values()) + [1, 2, 3] + + See Also + ======== + + values + """ + return self._eval_iter_values() + + def iter_items(self): + """Iterate over indices and values of nonzero items. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix([[0, 1], [2, 3]]) + >>> list(m.iter_items()) + [((0, 1), 1), ((1, 0), 2), ((1, 1), 3)] + + See Also + ======== + + iter_values + todok + """ + return self._eval_iter_items() + + def _eval_adjoint(self): + return self.transpose().applyfunc(lambda x: x.adjoint()) + + def _eval_applyfunc(self, f): + cols = self.cols + size = self.rows*self.cols + + dok = self.todok() + valmap = {v: f(v) for v in dok.values()} + + if len(dok) < size and ((fzero := f(S.Zero)) is not S.Zero): + out_flat = [fzero]*size + for (i, j), v in dok.items(): + out_flat[i*cols + j] = valmap[v] + out = self._new(self.rows, self.cols, out_flat) + else: + fdok = {ij: valmap[v] for ij, v in dok.items()} + out = self.from_dok(self.rows, self.cols, fdok) + + return out + + def _eval_as_real_imag(self): # type: ignore + return (self.applyfunc(re), self.applyfunc(im)) + + def _eval_conjugate(self): + return self.applyfunc(lambda x: x.conjugate()) + + def _eval_permute_cols(self, perm): + # apply the permutation to a list + mapping = list(perm) + + def entry(i, j): + return self[i, mapping[j]] + + return self._new(self.rows, self.cols, entry) + + def _eval_permute_rows(self, perm): + # apply the permutation to a list + mapping = list(perm) + + def entry(i, j): + return self[mapping[i], j] + + return self._new(self.rows, self.cols, entry) + + def _eval_trace(self): + return sum(self[i, i] for i in range(self.rows)) + + def _eval_transpose(self): + return self._new(self.cols, self.rows, lambda i, j: self[j, i]) + + def adjoint(self): + """Conjugate transpose or Hermitian conjugation.""" + return self._eval_adjoint() + + def applyfunc(self, f): + """Apply a function to each element of the matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix(2, 2, lambda i, j: i*2+j) + >>> m + Matrix([ + [0, 1], + [2, 3]]) + >>> m.applyfunc(lambda i: 2*i) + Matrix([ + [0, 2], + [4, 6]]) + + """ + if not callable(f): + raise TypeError("`f` must be callable.") + + return self._eval_applyfunc(f) + + def as_real_imag(self, deep=True, **hints): + """Returns a tuple containing the (real, imaginary) part of matrix.""" + # XXX: Ignoring deep and hints... + return self._eval_as_real_imag() + + def conjugate(self): + """Return the by-element conjugation. + + Examples + ======== + + >>> from sympy import SparseMatrix, I + >>> a = SparseMatrix(((1, 2 + I), (3, 4), (I, -I))) + >>> a + Matrix([ + [1, 2 + I], + [3, 4], + [I, -I]]) + >>> a.C + Matrix([ + [ 1, 2 - I], + [ 3, 4], + [-I, I]]) + + See Also + ======== + + transpose: Matrix transposition + H: Hermite conjugation + sympy.matrices.matrixbase.MatrixBase.D: Dirac conjugation + """ + return self._eval_conjugate() + + def doit(self, **hints): + return self.applyfunc(lambda x: x.doit(**hints)) + + def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None, verbose=False): + """Apply evalf() to each element of self.""" + options = {'subs':subs, 'maxn':maxn, 'chop':chop, 'strict':strict, + 'quad':quad, 'verbose':verbose} + return self.applyfunc(lambda i: i.evalf(n, **options)) + + def expand(self, deep=True, modulus=None, power_base=True, power_exp=True, + mul=True, log=True, multinomial=True, basic=True, **hints): + """Apply core.function.expand to each entry of the matrix. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import Matrix + >>> Matrix(1, 1, [x*(x+1)]) + Matrix([[x*(x + 1)]]) + >>> _.expand() + Matrix([[x**2 + x]]) + + """ + return self.applyfunc(lambda x: x.expand( + deep, modulus, power_base, power_exp, mul, log, multinomial, basic, + **hints)) + + @property + def H(self): + """Return Hermite conjugate. + + Examples + ======== + + >>> from sympy import Matrix, I + >>> m = Matrix((0, 1 + I, 2, 3)) + >>> m + Matrix([ + [ 0], + [1 + I], + [ 2], + [ 3]]) + >>> m.H + Matrix([[0, 1 - I, 2, 3]]) + + See Also + ======== + + conjugate: By-element conjugation + sympy.matrices.matrixbase.MatrixBase.D: Dirac conjugation + """ + return self.adjoint() + + def permute(self, perm, orientation='rows', direction='forward'): + r"""Permute the rows or columns of a matrix by the given list of + swaps. + + Parameters + ========== + + perm : Permutation, list, or list of lists + A representation for the permutation. + + If it is ``Permutation``, it is used directly with some + resizing with respect to the matrix size. + + If it is specified as list of lists, + (e.g., ``[[0, 1], [0, 2]]``), then the permutation is formed + from applying the product of cycles. The direction how the + cyclic product is applied is described in below. + + If it is specified as a list, the list should represent + an array form of a permutation. (e.g., ``[1, 2, 0]``) which + would would form the swapping function + `0 \mapsto 1, 1 \mapsto 2, 2\mapsto 0`. + + orientation : 'rows', 'cols' + A flag to control whether to permute the rows or the columns + + direction : 'forward', 'backward' + A flag to control whether to apply the permutations from + the start of the list first, or from the back of the list + first. + + For example, if the permutation specification is + ``[[0, 1], [0, 2]]``, + + If the flag is set to ``'forward'``, the cycle would be + formed as `0 \mapsto 2, 2 \mapsto 1, 1 \mapsto 0`. + + If the flag is set to ``'backward'``, the cycle would be + formed as `0 \mapsto 1, 1 \mapsto 2, 2 \mapsto 0`. + + If the argument ``perm`` is not in a form of list of lists, + this flag takes no effect. + + Examples + ======== + + >>> from sympy import eye + >>> M = eye(3) + >>> M.permute([[0, 1], [0, 2]], orientation='rows', direction='forward') + Matrix([ + [0, 0, 1], + [1, 0, 0], + [0, 1, 0]]) + + >>> from sympy import eye + >>> M = eye(3) + >>> M.permute([[0, 1], [0, 2]], orientation='rows', direction='backward') + Matrix([ + [0, 1, 0], + [0, 0, 1], + [1, 0, 0]]) + + Notes + ===== + + If a bijective function + `\sigma : \mathbb{N}_0 \rightarrow \mathbb{N}_0` denotes the + permutation. + + If the matrix `A` is the matrix to permute, represented as + a horizontal or a vertical stack of vectors: + + .. math:: + A = + \begin{bmatrix} + a_0 \\ a_1 \\ \vdots \\ a_{n-1} + \end{bmatrix} = + \begin{bmatrix} + \alpha_0 & \alpha_1 & \cdots & \alpha_{n-1} + \end{bmatrix} + + If the matrix `B` is the result, the permutation of matrix rows + is defined as: + + .. math:: + B := \begin{bmatrix} + a_{\sigma(0)} \\ a_{\sigma(1)} \\ \vdots \\ a_{\sigma(n-1)} + \end{bmatrix} + + And the permutation of matrix columns is defined as: + + .. math:: + B := \begin{bmatrix} + \alpha_{\sigma(0)} & \alpha_{\sigma(1)} & + \cdots & \alpha_{\sigma(n-1)} + \end{bmatrix} + """ + from sympy.combinatorics import Permutation + + # allow british variants and `columns` + if direction == 'forwards': + direction = 'forward' + if direction == 'backwards': + direction = 'backward' + if orientation == 'columns': + orientation = 'cols' + + if direction not in ('forward', 'backward'): + raise TypeError("direction='{}' is an invalid kwarg. " + "Try 'forward' or 'backward'".format(direction)) + if orientation not in ('rows', 'cols'): + raise TypeError("orientation='{}' is an invalid kwarg. " + "Try 'rows' or 'cols'".format(orientation)) + + if not isinstance(perm, (Permutation, Iterable)): + raise ValueError( + "{} must be a list, a list of lists, " + "or a SymPy permutation object.".format(perm)) + + # ensure all swaps are in range + max_index = self.rows if orientation == 'rows' else self.cols + if not all(0 <= t <= max_index for t in flatten(list(perm))): + raise IndexError("`swap` indices out of range.") + + if perm and not isinstance(perm, Permutation) and \ + isinstance(perm[0], Iterable): + if direction == 'forward': + perm = list(reversed(perm)) + perm = Permutation(perm, size=max_index+1) + else: + perm = Permutation(perm, size=max_index+1) + + if orientation == 'rows': + return self._eval_permute_rows(perm) + if orientation == 'cols': + return self._eval_permute_cols(perm) + + def permute_cols(self, swaps, direction='forward'): + """Alias for + ``self.permute(swaps, orientation='cols', direction=direction)`` + + See Also + ======== + + permute + """ + return self.permute(swaps, orientation='cols', direction=direction) + + def permute_rows(self, swaps, direction='forward'): + """Alias for + ``self.permute(swaps, orientation='rows', direction=direction)`` + + See Also + ======== + + permute + """ + return self.permute(swaps, orientation='rows', direction=direction) + + def refine(self, assumptions=True): + """Apply refine to each element of the matrix. + + Examples + ======== + + >>> from sympy import Symbol, Matrix, Abs, sqrt, Q + >>> x = Symbol('x') + >>> Matrix([[Abs(x)**2, sqrt(x**2)],[sqrt(x**2), Abs(x)**2]]) + Matrix([ + [ Abs(x)**2, sqrt(x**2)], + [sqrt(x**2), Abs(x)**2]]) + >>> _.refine(Q.real(x)) + Matrix([ + [ x**2, Abs(x)], + [Abs(x), x**2]]) + + """ + return self.applyfunc(lambda x: refine(x, assumptions)) + + def replace(self, F, G, map=False, simultaneous=True, exact=None): + """Replaces Function F in Matrix entries with Function G. + + Examples + ======== + + >>> from sympy import symbols, Function, Matrix + >>> F, G = symbols('F, G', cls=Function) + >>> M = Matrix(2, 2, lambda i, j: F(i+j)) ; M + Matrix([ + [F(0), F(1)], + [F(1), F(2)]]) + >>> N = M.replace(F,G) + >>> N + Matrix([ + [G(0), G(1)], + [G(1), G(2)]]) + """ + kwargs = {'map': map, 'simultaneous': simultaneous, 'exact': exact} + + if map: + + d = {} + def func(eij): + eij, dij = eij.replace(F, G, **kwargs) + d.update(dij) + return eij + + M = self.applyfunc(func) + return M, d + + else: + return self.applyfunc(lambda i: i.replace(F, G, **kwargs)) + + def rot90(self, k=1): + """Rotates Matrix by 90 degrees + + Parameters + ========== + + k : int + Specifies how many times the matrix is rotated by 90 degrees + (clockwise when positive, counter-clockwise when negative). + + Examples + ======== + + >>> from sympy import Matrix, symbols + >>> A = Matrix(2, 2, symbols('a:d')) + >>> A + Matrix([ + [a, b], + [c, d]]) + + Rotating the matrix clockwise one time: + + >>> A.rot90(1) + Matrix([ + [c, a], + [d, b]]) + + Rotating the matrix anticlockwise two times: + + >>> A.rot90(-2) + Matrix([ + [d, c], + [b, a]]) + """ + + mod = k%4 + if mod == 0: + return self + if mod == 1: + return self[::-1, ::].T + if mod == 2: + return self[::-1, ::-1] + if mod == 3: + return self[::, ::-1].T + + def simplify(self, **kwargs): + """Apply simplify to each element of the matrix. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import SparseMatrix, sin, cos + >>> SparseMatrix(1, 1, [x*sin(y)**2 + x*cos(y)**2]) + Matrix([[x*sin(y)**2 + x*cos(y)**2]]) + >>> _.simplify() + Matrix([[x]]) + """ + return self.applyfunc(lambda x: x.simplify(**kwargs)) + + def subs(self, *args, **kwargs): # should mirror core.basic.subs + """Return a new matrix with subs applied to each entry. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import SparseMatrix, Matrix + >>> SparseMatrix(1, 1, [x]) + Matrix([[x]]) + >>> _.subs(x, y) + Matrix([[y]]) + >>> Matrix(_).subs(y, x) + Matrix([[x]]) + """ + + if len(args) == 1 and not isinstance(args[0], (dict, set)) and iter(args[0]) and not is_sequence(args[0]): + args = (list(args[0]),) + + return self.applyfunc(lambda x: x.subs(*args, **kwargs)) + + def trace(self): + """ + Returns the trace of a square matrix i.e. the sum of the + diagonal elements. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix(2, 2, [1, 2, 3, 4]) + >>> A.trace() + 5 + + """ + if self.rows != self.cols: + raise NonSquareMatrixError() + return self._eval_trace() + + def transpose(self): + """ + Returns the transpose of the matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix(2, 2, [1, 2, 3, 4]) + >>> A.transpose() + Matrix([ + [1, 3], + [2, 4]]) + + >>> from sympy import Matrix, I + >>> m=Matrix(((1, 2+I), (3, 4))) + >>> m + Matrix([ + [1, 2 + I], + [3, 4]]) + >>> m.transpose() + Matrix([ + [ 1, 3], + [2 + I, 4]]) + >>> m.T == m.transpose() + True + + See Also + ======== + + conjugate: By-element conjugation + + """ + return self._eval_transpose() + + @property + def T(self): + '''Matrix transposition''' + return self.transpose() + + @property + def C(self): + '''By-element conjugation''' + return self.conjugate() + + def n(self, *args, **kwargs): + """Apply evalf() to each element of self.""" + return self.evalf(*args, **kwargs) + + def xreplace(self, rule): # should mirror core.basic.xreplace + """Return a new matrix with xreplace applied to each entry. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import SparseMatrix, Matrix + >>> SparseMatrix(1, 1, [x]) + Matrix([[x]]) + >>> _.xreplace({x: y}) + Matrix([[y]]) + >>> Matrix(_).xreplace({y: x}) + Matrix([[x]]) + """ + return self.applyfunc(lambda x: x.xreplace(rule)) + + def _eval_simplify(self, **kwargs): + # XXX: We can't use self.simplify here as mutable subclasses will + # override simplify and have it return None + return self.applyfunc(lambda x: x.simplify(**kwargs)) + + def _eval_trigsimp(self, **opts): + from sympy.simplify.trigsimp import trigsimp + return self.applyfunc(lambda x: trigsimp(x, **opts)) + + def upper_triangular(self, k=0): + """Return the elements on and above the kth diagonal of a matrix. + If k is not specified then simply returns upper-triangular portion + of a matrix + + Examples + ======== + + >>> from sympy import ones + >>> A = ones(4) + >>> A.upper_triangular() + Matrix([ + [1, 1, 1, 1], + [0, 1, 1, 1], + [0, 0, 1, 1], + [0, 0, 0, 1]]) + + >>> A.upper_triangular(2) + Matrix([ + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0], + [0, 0, 0, 0]]) + + >>> A.upper_triangular(-1) + Matrix([ + [1, 1, 1, 1], + [1, 1, 1, 1], + [0, 1, 1, 1], + [0, 0, 1, 1]]) + + """ + + def entry(i, j): + return self[i, j] if i + k <= j else self.zero + + return self._new(self.rows, self.cols, entry) + + def lower_triangular(self, k=0): + """Return the elements on and below the kth diagonal of a matrix. + If k is not specified then simply returns lower-triangular portion + of a matrix + + Examples + ======== + + >>> from sympy import ones + >>> A = ones(4) + >>> A.lower_triangular() + Matrix([ + [1, 0, 0, 0], + [1, 1, 0, 0], + [1, 1, 1, 0], + [1, 1, 1, 1]]) + + >>> A.lower_triangular(-2) + Matrix([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 0, 0, 0], + [1, 1, 0, 0]]) + + >>> A.lower_triangular(1) + Matrix([ + [1, 1, 0, 0], + [1, 1, 1, 0], + [1, 1, 1, 1], + [1, 1, 1, 1]]) + + """ + + def entry(i, j): + return self[i, j] if i + k >= j else self.zero + + return self._new(self.rows, self.cols, entry) + + def _eval_Abs(self): + return self._new(self.rows, self.cols, lambda i, j: Abs(self[i, j])) + + def _eval_add(self, other): + return self._new(self.rows, self.cols, + lambda i, j: self[i, j] + other[i, j]) + + def _eval_matrix_mul(self, other): + def entry(i, j): + vec = [self[i,k]*other[k,j] for k in range(self.cols)] + try: + return Add(*vec) + except (TypeError, SympifyError): + # Some matrices don't work with `sum` or `Add` + # They don't work with `sum` because `sum` tries to add `0` + # Fall back to a safe way to multiply if the `Add` fails. + return reduce(lambda a, b: a + b, vec) + + return self._new(self.rows, other.cols, entry) + + def _eval_matrix_mul_elementwise(self, other): + return self._new(self.rows, self.cols, lambda i, j: self[i,j]*other[i,j]) + + def _eval_matrix_rmul(self, other): + def entry(i, j): + return sum(other[i,k]*self[k,j] for k in range(other.cols)) + return self._new(other.rows, self.cols, entry) + + def _eval_pow_by_recursion(self, num): + if num == 1: + return self + + if num % 2 == 1: + a, b = self, self._eval_pow_by_recursion(num - 1) + else: + a = b = self._eval_pow_by_recursion(num // 2) + + return a.multiply(b) + + def _eval_pow_by_cayley(self, exp): + from sympy.discrete.recurrences import linrec_coeffs + row = self.shape[0] + p = self.charpoly() + + coeffs = (-p).all_coeffs()[1:] + coeffs = linrec_coeffs(coeffs, exp) + new_mat = self.eye(row) + ans = self.zeros(row) + + for i in range(row): + ans += coeffs[i]*new_mat + new_mat *= self + + return ans + + def _eval_pow_by_recursion_dotprodsimp(self, num, prevsimp=None): + if prevsimp is None: + prevsimp = [True]*len(self) + + if num == 1: + return self + + if num % 2 == 1: + a, b = self, self._eval_pow_by_recursion_dotprodsimp(num - 1, + prevsimp=prevsimp) + else: + a = b = self._eval_pow_by_recursion_dotprodsimp(num // 2, + prevsimp=prevsimp) + + m = a.multiply(b, dotprodsimp=False) + lenm = len(m) + elems = [None]*lenm + + for i in range(lenm): + if prevsimp[i]: + elems[i], prevsimp[i] = _dotprodsimp(m[i], withsimp=True) + else: + elems[i] = m[i] + + return m._new(m.rows, m.cols, elems) + + def _eval_scalar_mul(self, other): + return self._new(self.rows, self.cols, lambda i, j: self[i,j]*other) + + def _eval_scalar_rmul(self, other): + return self._new(self.rows, self.cols, lambda i, j: other*self[i,j]) + + def _eval_Mod(self, other): + return self._new(self.rows, self.cols, lambda i, j: Mod(self[i, j], other)) + + # Python arithmetic functions + def __abs__(self): + """Returns a new matrix with entry-wise absolute values.""" + return self._eval_Abs() + + @call_highest_priority('__radd__') + def __add__(self, other): + """Return self + other, raising ShapeError if shapes do not match.""" + + other, T = _coerce_operand(self, other) + + if T != "is_matrix": + return NotImplemented + + if self.shape != other.shape: + raise ShapeError(f"Matrix size mismatch: {self.shape} + {other.shape}.") + + # Unify matrix types + a, b = self, other + if a.__class__ != classof(a, b): + b, a = a, b + + return a._eval_add(b) + + @call_highest_priority('__rtruediv__') + def __truediv__(self, other): + return self * (self.one / other) + + @call_highest_priority('__rmatmul__') + def __matmul__(self, other): + self, other, T = _unify_with_other(self, other) + + if T != "is_matrix": + return NotImplemented + + return self.__mul__(other) + + def __mod__(self, other): + return self.applyfunc(lambda x: x % other) + + @call_highest_priority('__rmul__') + def __mul__(self, other): + """Return self*other where other is either a scalar or a matrix + of compatible dimensions. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[1, 2, 3], [4, 5, 6]]) + >>> 2*A == A*2 == Matrix([[2, 4, 6], [8, 10, 12]]) + True + >>> B = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> A*B + Matrix([ + [30, 36, 42], + [66, 81, 96]]) + >>> B*A + Traceback (most recent call last): + ... + ShapeError: Matrices size mismatch. + >>> + + See Also + ======== + + matrix_multiply_elementwise + """ + + return self.multiply(other) + + def multiply(self, other, dotprodsimp=None): + """Same as __mul__() but with optional simplification. + + Parameters + ========== + + dotprodsimp : bool, optional + Specifies whether intermediate term algebraic simplification is used + during matrix multiplications to control expression blowup and thus + speed up calculation. Default is off. + """ + + isimpbool = _get_intermediate_simp_bool(False, dotprodsimp) + + self, other, T = _unify_with_other(self, other) + + if T == "possible_scalar": + try: + return self._eval_scalar_mul(other) + except TypeError: + return NotImplemented + + elif T == "is_matrix": + + if self.shape[1] != other.shape[0]: + raise ShapeError(f"Matrix size mismatch: {self.shape} * {other.shape}.") + + m = self._eval_matrix_mul(other) + + if isimpbool: + m = m._new(m.rows, m.cols, [_dotprodsimp(e) for e in m]) + + return m + + else: + return NotImplemented + + def multiply_elementwise(self, other): + """Return the Hadamard product (elementwise product) of A and B + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[0, 1, 2], [3, 4, 5]]) + >>> B = Matrix([[1, 10, 100], [100, 10, 1]]) + >>> A.multiply_elementwise(B) + Matrix([ + [ 0, 10, 200], + [300, 40, 5]]) + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.cross + sympy.matrices.matrixbase.MatrixBase.dot + multiply + """ + if self.shape != other.shape: + raise ShapeError("Matrix shapes must agree {} != {}".format(self.shape, other.shape)) + + return self._eval_matrix_mul_elementwise(other) + + def __neg__(self): + return self._eval_scalar_mul(-1) + + @call_highest_priority('__rpow__') + def __pow__(self, exp): + """Return self**exp a scalar or symbol.""" + + return self.pow(exp) + + + def pow(self, exp, method=None): + r"""Return self**exp a scalar or symbol. + + Parameters + ========== + + method : multiply, mulsimp, jordan, cayley + If multiply then it returns exponentiation using recursion. + If jordan then Jordan form exponentiation will be used. + If cayley then the exponentiation is done using Cayley-Hamilton + theorem. + If mulsimp then the exponentiation is done using recursion + with dotprodsimp. This specifies whether intermediate term + algebraic simplification is used during naive matrix power to + control expression blowup and thus speed up calculation. + If None, then it heuristically decides which method to use. + + """ + + if method is not None and method not in ['multiply', 'mulsimp', 'jordan', 'cayley']: + raise TypeError('No such method') + if self.rows != self.cols: + raise NonSquareMatrixError() + a = self + jordan_pow = getattr(a, '_matrix_pow_by_jordan_blocks', None) + exp = sympify(exp) + + if exp.is_zero: + return a._new(a.rows, a.cols, lambda i, j: int(i == j)) + if exp == 1: + return a + + diagonal = getattr(a, 'is_diagonal', None) + if diagonal is not None and diagonal(): + return a._new(a.rows, a.cols, lambda i, j: a[i,j]**exp if i == j else 0) + + if exp.is_Number and exp % 1 == 0: + if a.rows == 1: + return a._new([[a[0]**exp]]) + if exp < 0: + exp = -exp + a = a.inv() + # When certain conditions are met, + # Jordan block algorithm is faster than + # computation by recursion. + if method == 'jordan': + try: + return jordan_pow(exp) + except MatrixError: + if method == 'jordan': + raise + + elif method == 'cayley': + if not exp.is_Number or exp % 1 != 0: + raise ValueError("cayley method is only valid for integer powers") + return a._eval_pow_by_cayley(exp) + + elif method == "mulsimp": + if not exp.is_Number or exp % 1 != 0: + raise ValueError("mulsimp method is only valid for integer powers") + return a._eval_pow_by_recursion_dotprodsimp(exp) + + elif method == "multiply": + if not exp.is_Number or exp % 1 != 0: + raise ValueError("multiply method is only valid for integer powers") + return a._eval_pow_by_recursion(exp) + + elif method is None and exp.is_Number and exp % 1 == 0: + if exp.is_Float: + exp = Integer(exp) + # Decide heuristically which method to apply + if a.rows == 2 and exp > 100000: + return jordan_pow(exp) + elif _get_intermediate_simp_bool(True, None): + return a._eval_pow_by_recursion_dotprodsimp(exp) + elif exp > 10000: + return a._eval_pow_by_cayley(exp) + else: + return a._eval_pow_by_recursion(exp) + + if jordan_pow: + try: + return jordan_pow(exp) + except NonInvertibleMatrixError: + # Raised by jordan_pow on zero determinant matrix unless exp is + # definitely known to be a non-negative integer. + # Here we raise if n is definitely not a non-negative integer + # but otherwise we can leave this as an unevaluated MatPow. + if exp.is_integer is False or exp.is_nonnegative is False: + raise + + from sympy.matrices.expressions import MatPow + return MatPow(a, exp) + + @call_highest_priority('__add__') + def __radd__(self, other): + return self.__add__(other) + + @call_highest_priority('__matmul__') + def __rmatmul__(self, other): + self, other, T = _unify_with_other(self, other) + + if T != "is_matrix": + return NotImplemented + + return self.__rmul__(other) + + @call_highest_priority('__mul__') + def __rmul__(self, other): + return self.rmultiply(other) + + def rmultiply(self, other, dotprodsimp=None): + """Same as __rmul__() but with optional simplification. + + Parameters + ========== + + dotprodsimp : bool, optional + Specifies whether intermediate term algebraic simplification is used + during matrix multiplications to control expression blowup and thus + speed up calculation. Default is off. + """ + isimpbool = _get_intermediate_simp_bool(False, dotprodsimp) + self, other, T = _unify_with_other(self, other) + + if T == "possible_scalar": + try: + return self._eval_scalar_rmul(other) + except TypeError: + return NotImplemented + + elif T == "is_matrix": + if self.shape[0] != other.shape[1]: + raise ShapeError("Matrix size mismatch.") + + m = self._eval_matrix_rmul(other) + + if isimpbool: + return m._new(m.rows, m.cols, [_dotprodsimp(e) for e in m]) + + return m + + else: + return NotImplemented + + @call_highest_priority('__sub__') + def __rsub__(self, a): + return (-self) + a + + @call_highest_priority('__rsub__') + def __sub__(self, a): + return self + (-a) + + def _eval_det_bareiss(self, iszerofunc=_is_zero_after_expand_mul): + return _det_bareiss(self, iszerofunc=iszerofunc) + + def _eval_det_berkowitz(self): + return _det_berkowitz(self) + + def _eval_det_lu(self, iszerofunc=_iszero, simpfunc=None): + return _det_LU(self, iszerofunc=iszerofunc, simpfunc=simpfunc) + + def _eval_det_bird(self): + return _det_bird(self) + + def _eval_det_laplace(self): + return _det_laplace(self) + + def _eval_determinant(self): # for expressions.determinant.Determinant + return _det(self) + + def adjugate(self, method="berkowitz"): + return _adjugate(self, method=method) + + def charpoly(self, x='lambda', simplify=_utilities_simplify): + return _charpoly(self, x=x, simplify=simplify) + + def cofactor(self, i, j, method="berkowitz"): + return _cofactor(self, i, j, method=method) + + def cofactor_matrix(self, method="berkowitz"): + return _cofactor_matrix(self, method=method) + + def det(self, method="bareiss", iszerofunc=None): + return _det(self, method=method, iszerofunc=iszerofunc) + + def per(self): + return _per(self) + + def minor(self, i, j, method="berkowitz"): + return _minor(self, i, j, method=method) + + def minor_submatrix(self, i, j): + return _minor_submatrix(self, i, j) + + _find_reasonable_pivot.__doc__ = _find_reasonable_pivot.__doc__ + _find_reasonable_pivot_naive.__doc__ = _find_reasonable_pivot_naive.__doc__ + _eval_det_bareiss.__doc__ = _det_bareiss.__doc__ + _eval_det_berkowitz.__doc__ = _det_berkowitz.__doc__ + _eval_det_bird.__doc__ = _det_bird.__doc__ + _eval_det_laplace.__doc__ = _det_laplace.__doc__ + _eval_det_lu.__doc__ = _det_LU.__doc__ + _eval_determinant.__doc__ = _det.__doc__ + adjugate.__doc__ = _adjugate.__doc__ + charpoly.__doc__ = _charpoly.__doc__ + cofactor.__doc__ = _cofactor.__doc__ + cofactor_matrix.__doc__ = _cofactor_matrix.__doc__ + det.__doc__ = _det.__doc__ + per.__doc__ = _per.__doc__ + minor.__doc__ = _minor.__doc__ + minor_submatrix.__doc__ = _minor_submatrix.__doc__ + + def echelon_form(self, iszerofunc=_iszero, simplify=False, with_pivots=False): + return _echelon_form(self, iszerofunc=iszerofunc, simplify=simplify, + with_pivots=with_pivots) + + @property + def is_echelon(self): + return _is_echelon(self) + + def rank(self, iszerofunc=_iszero, simplify=False): + return _rank(self, iszerofunc=iszerofunc, simplify=simplify) + + def rref_rhs(self, rhs): + """Return reduced row-echelon form of matrix, matrix showing + rhs after reduction steps. ``rhs`` must have the same number + of rows as ``self``. + + Examples + ======== + + >>> from sympy import Matrix, symbols + >>> r1, r2 = symbols('r1 r2') + >>> Matrix([[1, 1], [2, 1]]).rref_rhs(Matrix([r1, r2])) + (Matrix([ + [1, 0], + [0, 1]]), Matrix([ + [ -r1 + r2], + [2*r1 - r2]])) + """ + r, _ = _rref(self.hstack(self, self.eye(self.rows), rhs)) + return r[:, :self.cols], r[:, -rhs.cols:] + + def rref(self, iszerofunc=_iszero, simplify=False, pivots=True, + normalize_last=True): + return _rref(self, iszerofunc=iszerofunc, simplify=simplify, + pivots=pivots, normalize_last=normalize_last) + + echelon_form.__doc__ = _echelon_form.__doc__ + is_echelon.__doc__ = _is_echelon.__doc__ + rank.__doc__ = _rank.__doc__ + rref.__doc__ = _rref.__doc__ + + def _normalize_op_args(self, op, col, k, col1, col2, error_str="col"): + """Validate the arguments for a row/column operation. ``error_str`` + can be one of "row" or "col" depending on the arguments being parsed.""" + if op not in ["n->kn", "n<->m", "n->n+km"]: + raise ValueError("Unknown {} operation '{}'. Valid col operations " + "are 'n->kn', 'n<->m', 'n->n+km'".format(error_str, op)) + + # define self_col according to error_str + self_cols = self.cols if error_str == 'col' else self.rows + + # normalize and validate the arguments + if op == "n->kn": + col = col if col is not None else col1 + if col is None or k is None: + raise ValueError("For a {0} operation 'n->kn' you must provide the " + "kwargs `{0}` and `k`".format(error_str)) + if not 0 <= col < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col)) + + elif op == "n<->m": + # we need two cols to swap. It does not matter + # how they were specified, so gather them together and + # remove `None` + cols = {col, k, col1, col2}.difference([None]) + if len(cols) > 2: + # maybe the user left `k` by mistake? + cols = {col, col1, col2}.difference([None]) + if len(cols) != 2: + raise ValueError("For a {0} operation 'n<->m' you must provide the " + "kwargs `{0}1` and `{0}2`".format(error_str)) + col1, col2 = cols + if not 0 <= col1 < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col1)) + if not 0 <= col2 < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col2)) + + elif op == "n->n+km": + col = col1 if col is None else col + col2 = col1 if col2 is None else col2 + if col is None or col2 is None or k is None: + raise ValueError("For a {0} operation 'n->n+km' you must provide the " + "kwargs `{0}`, `k`, and `{0}2`".format(error_str)) + if col == col2: + raise ValueError("For a {0} operation 'n->n+km' `{0}` and `{0}2` must " + "be different.".format(error_str)) + if not 0 <= col < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col)) + if not 0 <= col2 < self_cols: + raise ValueError("This matrix does not have a {} '{}'".format(error_str, col2)) + + else: + raise ValueError('invalid operation %s' % repr(op)) + + return op, col, k, col1, col2 + + def _eval_col_op_multiply_col_by_const(self, col, k): + def entry(i, j): + if j == col: + return k * self[i, j] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_col_op_swap(self, col1, col2): + def entry(i, j): + if j == col1: + return self[i, col2] + elif j == col2: + return self[i, col1] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_col_op_add_multiple_to_other_col(self, col, k, col2): + def entry(i, j): + if j == col: + return self[i, j] + k * self[i, col2] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_row_op_swap(self, row1, row2): + def entry(i, j): + if i == row1: + return self[row2, j] + elif i == row2: + return self[row1, j] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_row_op_multiply_row_by_const(self, row, k): + def entry(i, j): + if i == row: + return k * self[i, j] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def _eval_row_op_add_multiple_to_other_row(self, row, k, row2): + def entry(i, j): + if i == row: + return self[i, j] + k * self[row2, j] + return self[i, j] + return self._new(self.rows, self.cols, entry) + + def elementary_col_op(self, op="n->kn", col=None, k=None, col1=None, col2=None): + """Performs the elementary column operation `op`. + + `op` may be one of + + * ``"n->kn"`` (column n goes to k*n) + * ``"n<->m"`` (swap column n and column m) + * ``"n->n+km"`` (column n goes to column n + k*column m) + + Parameters + ========== + + op : string; the elementary row operation + col : the column to apply the column operation + k : the multiple to apply in the column operation + col1 : one column of a column swap + col2 : second column of a column swap or column "m" in the column operation + "n->n+km" + """ + + op, col, k, col1, col2 = self._normalize_op_args(op, col, k, col1, col2, "col") + + # now that we've validated, we're all good to dispatch + if op == "n->kn": + return self._eval_col_op_multiply_col_by_const(col, k) + if op == "n<->m": + return self._eval_col_op_swap(col1, col2) + if op == "n->n+km": + return self._eval_col_op_add_multiple_to_other_col(col, k, col2) + + def elementary_row_op(self, op="n->kn", row=None, k=None, row1=None, row2=None): + """Performs the elementary row operation `op`. + + `op` may be one of + + * ``"n->kn"`` (row n goes to k*n) + * ``"n<->m"`` (swap row n and row m) + * ``"n->n+km"`` (row n goes to row n + k*row m) + + Parameters + ========== + + op : string; the elementary row operation + row : the row to apply the row operation + k : the multiple to apply in the row operation + row1 : one row of a row swap + row2 : second row of a row swap or row "m" in the row operation + "n->n+km" + """ + + op, row, k, row1, row2 = self._normalize_op_args(op, row, k, row1, row2, "row") + + # now that we've validated, we're all good to dispatch + if op == "n->kn": + return self._eval_row_op_multiply_row_by_const(row, k) + if op == "n<->m": + return self._eval_row_op_swap(row1, row2) + if op == "n->n+km": + return self._eval_row_op_add_multiple_to_other_row(row, k, row2) + + def columnspace(self, simplify=False): + return _columnspace(self, simplify=simplify) + + def nullspace(self, simplify=False, iszerofunc=_iszero): + return _nullspace(self, simplify=simplify, iszerofunc=iszerofunc) + + def rowspace(self, simplify=False): + return _rowspace(self, simplify=simplify) + + # This is a classmethod but is converted to such later in order to allow + # assignment of __doc__ since that does not work for already wrapped + # classmethods in Python 3.6. + def orthogonalize(cls, *vecs, **kwargs): + return _orthogonalize(cls, *vecs, **kwargs) + + columnspace.__doc__ = _columnspace.__doc__ + nullspace.__doc__ = _nullspace.__doc__ + rowspace.__doc__ = _rowspace.__doc__ + orthogonalize.__doc__ = _orthogonalize.__doc__ + + orthogonalize = classmethod(orthogonalize) # type:ignore + + def eigenvals(self, error_when_incomplete=True, **flags): + return _eigenvals(self, error_when_incomplete=error_when_incomplete, **flags) + + def eigenvects(self, error_when_incomplete=True, iszerofunc=_iszero, **flags): + return _eigenvects(self, error_when_incomplete=error_when_incomplete, + iszerofunc=iszerofunc, **flags) + + def is_diagonalizable(self, reals_only=False, **kwargs): + return _is_diagonalizable(self, reals_only=reals_only, **kwargs) + + def diagonalize(self, reals_only=False, sort=False, normalize=False): + return _diagonalize(self, reals_only=reals_only, sort=sort, + normalize=normalize) + + def bidiagonalize(self, upper=True): + return _bidiagonalize(self, upper=upper) + + def bidiagonal_decomposition(self, upper=True): + return _bidiagonal_decomposition(self, upper=upper) + + @property + def is_positive_definite(self): + return _is_positive_definite(self) + + @property + def is_positive_semidefinite(self): + return _is_positive_semidefinite(self) + + @property + def is_negative_definite(self): + return _is_negative_definite(self) + + @property + def is_negative_semidefinite(self): + return _is_negative_semidefinite(self) + + @property + def is_indefinite(self): + return _is_indefinite(self) + + def jordan_form(self, calc_transform=True, **kwargs): + return _jordan_form(self, calc_transform=calc_transform, **kwargs) + + def left_eigenvects(self, **flags): + return _left_eigenvects(self, **flags) + + def singular_values(self): + return _singular_values(self) + + eigenvals.__doc__ = _eigenvals.__doc__ + eigenvects.__doc__ = _eigenvects.__doc__ + is_diagonalizable.__doc__ = _is_diagonalizable.__doc__ + diagonalize.__doc__ = _diagonalize.__doc__ + is_positive_definite.__doc__ = _is_positive_definite.__doc__ + is_positive_semidefinite.__doc__ = _is_positive_semidefinite.__doc__ + is_negative_definite.__doc__ = _is_negative_definite.__doc__ + is_negative_semidefinite.__doc__ = _is_negative_semidefinite.__doc__ + is_indefinite.__doc__ = _is_indefinite.__doc__ + jordan_form.__doc__ = _jordan_form.__doc__ + left_eigenvects.__doc__ = _left_eigenvects.__doc__ + singular_values.__doc__ = _singular_values.__doc__ + bidiagonalize.__doc__ = _bidiagonalize.__doc__ + bidiagonal_decomposition.__doc__ = _bidiagonal_decomposition.__doc__ + + def diff(self, *args, evaluate=True, **kwargs): + """Calculate the derivative of each element in the matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[x, y], [1, 0]]) + >>> M.diff(x) + Matrix([ + [1, 0], + [0, 0]]) + + See Also + ======== + + integrate + limit + """ + # XXX this should be handled here rather than in Derivative + from sympy.tensor.array.array_derivatives import ArrayDerivative + deriv = ArrayDerivative(self, *args, evaluate=evaluate) + # XXX This can rather changed to always return immutable matrix + if not isinstance(self, Basic) and evaluate: + return deriv.as_mutable() + return deriv + + def _eval_derivative(self, arg): + return self.applyfunc(lambda x: x.diff(arg)) + + def integrate(self, *args, **kwargs): + """Integrate each element of the matrix. ``args`` will + be passed to the ``integrate`` function. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[x, y], [1, 0]]) + >>> M.integrate((x, )) + Matrix([ + [x**2/2, x*y], + [ x, 0]]) + >>> M.integrate((x, 0, 2)) + Matrix([ + [2, 2*y], + [2, 0]]) + + See Also + ======== + + limit + diff + """ + return self.applyfunc(lambda x: x.integrate(*args, **kwargs)) + + def jacobian(self, X): + """Calculates the Jacobian matrix (derivative of a vector-valued function). + + Parameters + ========== + + ``self`` : vector of expressions representing functions f_i(x_1, ..., x_n). + X : set of x_i's in order, it can be a list or a Matrix + + Both ``self`` and X can be a row or a column matrix in any order + (i.e., jacobian() should always work). + + Examples + ======== + + >>> from sympy import sin, cos, Matrix + >>> from sympy.abc import rho, phi + >>> X = Matrix([rho*cos(phi), rho*sin(phi), rho**2]) + >>> Y = Matrix([rho, phi]) + >>> X.jacobian(Y) + Matrix([ + [cos(phi), -rho*sin(phi)], + [sin(phi), rho*cos(phi)], + [ 2*rho, 0]]) + >>> X = Matrix([rho*cos(phi), rho*sin(phi)]) + >>> X.jacobian(Y) + Matrix([ + [cos(phi), -rho*sin(phi)], + [sin(phi), rho*cos(phi)]]) + + See Also + ======== + + hessian + wronskian + """ + from sympy.matrices.matrixbase import MatrixBase + if not isinstance(X, MatrixBase): + X = self._new(X) + # Both X and ``self`` can be a row or a column matrix, so we need to make + # sure all valid combinations work, but everything else fails: + if self.shape[0] == 1: + m = self.shape[1] + elif self.shape[1] == 1: + m = self.shape[0] + else: + raise TypeError("``self`` must be a row or a column matrix") + if X.shape[0] == 1: + n = X.shape[1] + elif X.shape[1] == 1: + n = X.shape[0] + else: + raise TypeError("X must be a row or a column matrix") + + # m is the number of functions and n is the number of variables + # computing the Jacobian is now easy: + return self._new(m, n, lambda j, i: self[j].diff(X[i])) + + def limit(self, *args): + """Calculate the limit of each element in the matrix. + ``args`` will be passed to the ``limit`` function. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x, y + >>> M = Matrix([[x, y], [1, 0]]) + >>> M.limit(x, 2) + Matrix([ + [2, y], + [1, 0]]) + + See Also + ======== + + integrate + diff + """ + return self.applyfunc(lambda x: x.limit(*args)) + + def berkowitz_charpoly(self, x=Dummy('lambda'), simplify=_utilities_simplify): + return self.charpoly(x=x) + + def berkowitz_det(self): + """Computes determinant using Berkowitz method. + + See Also + ======== + + det + """ + return self.det(method='berkowitz') + + def berkowitz_eigenvals(self, **flags): + """Computes eigenvalues of a Matrix using Berkowitz method.""" + return self.eigenvals(**flags) + + def berkowitz_minors(self): + """Computes principal minors using Berkowitz method.""" + sign, minors = self.one, [] + + for poly in self.berkowitz(): + minors.append(sign * poly[-1]) + sign = -sign + + return tuple(minors) + + def berkowitz(self): + from sympy.matrices import zeros + berk = ((1,),) + if not self: + return berk + + if not self.is_square: + raise NonSquareMatrixError() + + A, N = self, self.rows + transforms = [0] * (N - 1) + + for n in range(N, 1, -1): + T, k = zeros(n + 1, n), n - 1 + + R, C = -A[k, :k], A[:k, k] + A, a = A[:k, :k], -A[k, k] + + items = [C] + + for i in range(0, n - 2): + items.append(A * items[i]) + + for i, B in enumerate(items): + items[i] = (R * B)[0, 0] + + items = [self.one, a] + items + + for i in range(n): + T[i:, i] = items[:n - i + 1] + + transforms[k - 1] = T + + polys = [self._new([self.one, -A[0, 0]])] + + for i, T in enumerate(transforms): + polys.append(T * polys[i]) + + return berk + tuple(map(tuple, polys)) + + def cofactorMatrix(self, method="berkowitz"): + return self.cofactor_matrix(method=method) + + def det_bareis(self): + return _det_bareiss(self) + + def det_LU_decomposition(self): + """Compute matrix determinant using LU decomposition. + + + Note that this method fails if the LU decomposition itself + fails. In particular, if the matrix has no inverse this method + will fail. + + TODO: Implement algorithm for sparse matrices (SFF), + http://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps. + + See Also + ======== + + + det + berkowitz_det + """ + return self.det(method='lu') + + def jordan_cell(self, eigenval, n): + return self.jordan_block(size=n, eigenvalue=eigenval) + + def jordan_cells(self, calc_transformation=True): + P, J = self.jordan_form() + return P, J.get_diag_blocks() + + def minorEntry(self, i, j, method="berkowitz"): + return self.minor(i, j, method=method) + + def minorMatrix(self, i, j): + return self.minor_submatrix(i, j) + + def permuteBkwd(self, perm): + """Permute the rows of the matrix with the given permutation in reverse.""" + return self.permute_rows(perm, direction='backward') + + def permuteFwd(self, perm): + """Permute the rows of the matrix with the given permutation.""" + return self.permute_rows(perm, direction='forward') + + @property + def kind(self) -> MatrixKind: + elem_kinds = {e.kind for e in self.flat()} + if len(elem_kinds) == 1: + elemkind, = elem_kinds + else: + elemkind = UndefinedKind + return MatrixKind(elemkind) + + def flat(self): + """ + Returns a flat list of all elements in the matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> m = Matrix([[0, 2], [3, 4]]) + >>> m.flat() + [0, 2, 3, 4] + + See Also + ======== + + tolist + values + """ + return [self[i, j] for i in range(self.rows) for j in range(self.cols)] + + def __array__(self, dtype=object, copy=None): + if copy is not None and not copy: + raise TypeError("Cannot implement copy=False when converting Matrix to ndarray") + from .dense import matrix2numpy + return matrix2numpy(self, dtype=dtype) + + def __len__(self): + """Return the number of elements of ``self``. + + Implemented mainly so bool(Matrix()) == False. + """ + return self.rows * self.cols + + def _matrix_pow_by_jordan_blocks(self, num): + from sympy.matrices import diag, MutableMatrix + + def jordan_cell_power(jc, n): + N = jc.shape[0] + l = jc[0,0] + if l.is_zero: + if N == 1 and n.is_nonnegative: + jc[0,0] = l**n + elif not (n.is_integer and n.is_nonnegative): + raise NonInvertibleMatrixError("Non-invertible matrix can only be raised to a nonnegative integer") + else: + for i in range(N): + jc[0,i] = KroneckerDelta(i, n) + else: + for i in range(N): + bn = binomial(n, i) + if isinstance(bn, binomial): + bn = bn._eval_expand_func() + jc[0,i] = l**(n-i)*bn + for i in range(N): + for j in range(1, N-i): + jc[j,i+j] = jc [j-1,i+j-1] + + P, J = self.jordan_form() + jordan_cells = J.get_diag_blocks() + # Make sure jordan_cells matrices are mutable: + jordan_cells = [MutableMatrix(j) for j in jordan_cells] + for j in jordan_cells: + jordan_cell_power(j, num) + return self._new(P.multiply(diag(*jordan_cells)) + .multiply(P.inv())) + + def __str__(self): + if S.Zero in self.shape: + return 'Matrix(%s, %s, [])' % (self.rows, self.cols) + return "Matrix(%s)" % str(self.tolist()) + + def _format_str(self, printer=None): + if not printer: + printer = StrPrinter() + # Handle zero dimensions: + if S.Zero in self.shape: + return 'Matrix(%s, %s, [])' % (self.rows, self.cols) + if self.rows == 1: + return "Matrix([%s])" % self.table(printer, rowsep=',\n') + return "Matrix([\n%s])" % self.table(printer, rowsep=',\n') + + @classmethod + def irregular(cls, ntop, *matrices, **kwargs): + """Return a matrix filled by the given matrices which + are listed in order of appearance from left to right, top to + bottom as they first appear in the matrix. They must fill the + matrix completely. + + Examples + ======== + + >>> from sympy import ones, Matrix + >>> Matrix.irregular(3, ones(2,1), ones(3,3)*2, ones(2,2)*3, + ... ones(1,1)*4, ones(2,2)*5, ones(1,2)*6, ones(1,2)*7) + Matrix([ + [1, 2, 2, 2, 3, 3], + [1, 2, 2, 2, 3, 3], + [4, 2, 2, 2, 5, 5], + [6, 6, 7, 7, 5, 5]]) + """ + ntop = as_int(ntop) + # make sure we are working with explicit matrices + b = [i.as_explicit() if hasattr(i, 'as_explicit') else i + for i in matrices] + q = list(range(len(b))) + dat = [i.rows for i in b] + active = [q.pop(0) for _ in range(ntop)] + cols = sum(b[i].cols for i in active) + rows = [] + while any(dat): + r = [] + for a, j in enumerate(active): + r.extend(b[j][-dat[j], :]) + dat[j] -= 1 + if dat[j] == 0 and q: + active[a] = q.pop(0) + if len(r) != cols: + raise ValueError(filldedent(''' + Matrices provided do not appear to fill + the space completely.''')) + rows.append(r) + return cls._new(rows) + + @classmethod + def _handle_ndarray(cls, arg): + # NumPy array or matrix or some other object that implements + # __array__. So let's first use this method to get a + # numpy.array() and then make a Python list out of it. + arr = arg.__array__() + if len(arr.shape) == 2: + rows, cols = arr.shape[0], arr.shape[1] + flat_list = [cls._sympify(i) for i in arr.ravel()] + return rows, cols, flat_list + elif len(arr.shape) == 1: + flat_list = [cls._sympify(i) for i in arr] + return arr.shape[0], 1, flat_list + else: + raise NotImplementedError( + "SymPy supports just 1D and 2D matrices") + + @classmethod + def _handle_creation_inputs(cls, *args, **kwargs): + """Return the number of rows, cols and flat matrix elements. + + Examples + ======== + + >>> from sympy import Matrix, I + + Matrix can be constructed as follows: + + * from a nested list of iterables + + >>> Matrix( ((1, 2+I), (3, 4)) ) + Matrix([ + [1, 2 + I], + [3, 4]]) + + * from un-nested iterable (interpreted as a column) + + >>> Matrix( [1, 2] ) + Matrix([ + [1], + [2]]) + + * from un-nested iterable with dimensions + + >>> Matrix(1, 2, [1, 2] ) + Matrix([[1, 2]]) + + * from no arguments (a 0 x 0 matrix) + + >>> Matrix() + Matrix(0, 0, []) + + * from a rule + + >>> Matrix(2, 2, lambda i, j: i/(j + 1) ) + Matrix([ + [0, 0], + [1, 1/2]]) + + See Also + ======== + irregular - filling a matrix with irregular blocks + """ + from sympy.matrices import SparseMatrix + from sympy.matrices.expressions.matexpr import MatrixSymbol + from sympy.matrices.expressions.blockmatrix import BlockMatrix + + flat_list = None + + if len(args) == 1: + # Matrix(SparseMatrix(...)) + if isinstance(args[0], SparseMatrix): + return args[0].rows, args[0].cols, flatten(args[0].tolist()) + + # Matrix(Matrix(...)) + elif isinstance(args[0], MatrixBase): + return args[0].rows, args[0].cols, args[0].flat() + + # Matrix(MatrixSymbol('X', 2, 2)) + elif isinstance(args[0], Basic) and args[0].is_Matrix: + return args[0].rows, args[0].cols, args[0].as_explicit().flat() + + elif isinstance(args[0], mp.matrix): + M = args[0] + flat_list = [cls._sympify(x) for x in M] + return M.rows, M.cols, flat_list + + # Matrix(numpy.ones((2, 2))) + elif hasattr(args[0], "__array__"): + return cls._handle_ndarray(args[0]) + + # Matrix([1, 2, 3]) or Matrix([[1, 2], [3, 4]]) + elif is_sequence(args[0]) \ + and not isinstance(args[0], DeferredVector): + dat = list(args[0]) + ismat = lambda i: isinstance(i, MatrixBase) and ( + evaluate or isinstance(i, (BlockMatrix, MatrixSymbol))) + raw = lambda i: is_sequence(i) and not ismat(i) + evaluate = kwargs.get('evaluate', True) + + + if evaluate: + + def make_explicit(x): + """make Block and Symbol explicit""" + if isinstance(x, BlockMatrix): + return x.as_explicit() + elif isinstance(x, MatrixSymbol) and all(_.is_Integer for _ in x.shape): + return x.as_explicit() + else: + return x + + def make_explicit_row(row): + # Could be list or could be list of lists + if isinstance(row, (list, tuple)): + return [make_explicit(x) for x in row] + else: + return make_explicit(row) + + if isinstance(dat, (list, tuple)): + dat = [make_explicit_row(row) for row in dat] + + if len(dat) == 0: + rows = cols = 0 + flat_list = [] + elif all(raw(i) for i in dat) and len(dat[0]) == 0: + if not all(len(i) == 0 for i in dat): + raise ValueError('mismatched dimensions') + rows = len(dat) + cols = 0 + flat_list = [] + elif not any(raw(i) or ismat(i) for i in dat): + # a column as a list of values + flat_list = [cls._sympify(i) for i in dat] + rows = len(flat_list) + cols = 1 if rows else 0 + elif evaluate and all(ismat(i) for i in dat): + # a column as a list of matrices + ncol = {i.cols for i in dat if any(i.shape)} + if ncol: + if len(ncol) != 1: + raise ValueError('mismatched dimensions') + flat_list = [_ for i in dat for r in i.tolist() for _ in r] + cols = ncol.pop() + rows = len(flat_list)//cols + else: + rows = cols = 0 + flat_list = [] + elif evaluate and any(ismat(i) for i in dat): + ncol = set() + flat_list = [] + for i in dat: + if ismat(i): + flat_list.extend( + [k for j in i.tolist() for k in j]) + if any(i.shape): + ncol.add(i.cols) + elif raw(i): + if i: + ncol.add(len(i)) + flat_list.extend([cls._sympify(ij) for ij in i]) + else: + ncol.add(1) + flat_list.append(i) + if len(ncol) > 1: + raise ValueError('mismatched dimensions') + cols = ncol.pop() + rows = len(flat_list)//cols + else: + # list of lists; each sublist is a logical row + # which might consist of many rows if the values in + # the row are matrices + flat_list = [] + ncol = set() + rows = cols = 0 + for row in dat: + if not is_sequence(row) and \ + not getattr(row, 'is_Matrix', False): + raise ValueError('expecting list of lists') + + if hasattr(row, '__array__'): + if 0 in row.shape: + continue + + if evaluate and all(ismat(i) for i in row): + r, c, flatT = cls._handle_creation_inputs( + [i.T for i in row]) + T = reshape(flatT, [c]) + flat = \ + [T[i][j] for j in range(c) for i in range(r)] + r, c = c, r + else: + r = 1 + if getattr(row, 'is_Matrix', False): + c = 1 + flat = [row] + else: + c = len(row) + flat = [cls._sympify(i) for i in row] + ncol.add(c) + if len(ncol) > 1: + raise ValueError('mismatched dimensions') + flat_list.extend(flat) + rows += r + cols = ncol.pop() if ncol else 0 + + elif len(args) == 3: + rows = as_int(args[0]) + cols = as_int(args[1]) + + if rows < 0 or cols < 0: + raise ValueError("Cannot create a {} x {} matrix. " + "Both dimensions must be positive".format(rows, cols)) + + # Matrix(2, 2, lambda i, j: i+j) + if len(args) == 3 and isinstance(args[2], Callable): + op = args[2] + flat_list = [] + for i in range(rows): + flat_list.extend( + [cls._sympify(op(cls._sympify(i), cls._sympify(j))) + for j in range(cols)]) + + # Matrix(2, 2, [1, 2, 3, 4]) + elif len(args) == 3 and is_sequence(args[2]): + flat_list = args[2] + if len(flat_list) != rows * cols: + raise ValueError( + 'List length should be equal to rows*columns') + flat_list = [cls._sympify(i) for i in flat_list] + + + # Matrix() + elif len(args) == 0: + # Empty Matrix + rows = cols = 0 + flat_list = [] + + if flat_list is None: + raise TypeError(filldedent(''' + Data type not understood; expecting list of lists + or lists of values.''')) + + return rows, cols, flat_list + + def _setitem(self, key, value): + """Helper to set value at location given by key. + + Examples + ======== + + >>> from sympy import Matrix, I, zeros, ones + >>> m = Matrix(((1, 2+I), (3, 4))) + >>> m + Matrix([ + [1, 2 + I], + [3, 4]]) + >>> m[1, 0] = 9 + >>> m + Matrix([ + [1, 2 + I], + [9, 4]]) + >>> m[1, 0] = [[0, 1]] + + To replace row r you assign to position r*m where m + is the number of columns: + + >>> M = zeros(4) + >>> m = M.cols + >>> M[3*m] = ones(1, m)*2; M + Matrix([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [2, 2, 2, 2]]) + + And to replace column c you can assign to position c: + + >>> M[2] = ones(m, 1)*4; M + Matrix([ + [0, 0, 4, 0], + [0, 0, 4, 0], + [0, 0, 4, 0], + [2, 2, 4, 2]]) + """ + from .dense import Matrix + + is_slice = isinstance(key, slice) + i, j = key = self.key2ij(key) + is_mat = isinstance(value, MatrixBase) + if isinstance(i, slice) or isinstance(j, slice): + if is_mat: + self.copyin_matrix(key, value) + return + if not isinstance(value, Expr) and is_sequence(value): + self.copyin_list(key, value) + return + raise ValueError('unexpected value: %s' % value) + else: + if (not is_mat and + not isinstance(value, Basic) and is_sequence(value)): + value = Matrix(value) + is_mat = True + if is_mat: + if is_slice: + key = (slice(*divmod(i, self.cols)), + slice(*divmod(j, self.cols))) + else: + key = (slice(i, i + value.rows), + slice(j, j + value.cols)) + self.copyin_matrix(key, value) + else: + return i, j, self._sympify(value) + return + + def add(self, b): + """Return self + b.""" + return self + b + + def condition_number(self): + """Returns the condition number of a matrix. + + This is the maximum singular value divided by the minimum singular value + + Examples + ======== + + >>> from sympy import Matrix, S + >>> A = Matrix([[1, 0, 0], [0, 10, 0], [0, 0, S.One/10]]) + >>> A.condition_number() + 100 + + See Also + ======== + + singular_values + """ + + if not self: + return self.zero + singularvalues = self.singular_values() + return Max(*singularvalues) / Min(*singularvalues) + + def copy(self): + """ + Returns the copy of a matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix(2, 2, [1, 2, 3, 4]) + >>> A.copy() + Matrix([ + [1, 2], + [3, 4]]) + + """ + return self._new(self.rows, self.cols, self.flat()) + + def cross(self, b): + r""" + Return the cross product of ``self`` and ``b`` relaxing the condition + of compatible dimensions: if each has 3 elements, a matrix of the + same type and shape as ``self`` will be returned. If ``b`` has the same + shape as ``self`` then common identities for the cross product (like + `a \times b = - b \times a`) will hold. + + Parameters + ========== + b : 3x1 or 1x3 Matrix + + See Also + ======== + + dot + hat + vee + multiply + multiply_elementwise + """ + from sympy.matrices.expressions.matexpr import MatrixExpr + + if not isinstance(b, (MatrixBase, MatrixExpr)): + raise TypeError( + "{} must be a Matrix, not {}.".format(b, type(b))) + + if not (self.rows * self.cols == b.rows * b.cols == 3): + raise ShapeError("Dimensions incorrect for cross product: %s x %s" % + ((self.rows, self.cols), (b.rows, b.cols))) + else: + return self._new(self.rows, self.cols, ( + (self[1] * b[2] - self[2] * b[1]), + (self[2] * b[0] - self[0] * b[2]), + (self[0] * b[1] - self[1] * b[0]))) + + def hat(self): + r""" + Return the skew-symmetric matrix representing the cross product, + so that ``self.hat() * b`` is equivalent to ``self.cross(b)``. + + Examples + ======== + + Calling ``hat`` creates a skew-symmetric 3x3 Matrix from a 3x1 Matrix: + + >>> from sympy import Matrix + >>> a = Matrix([1, 2, 3]) + >>> a.hat() + Matrix([ + [ 0, -3, 2], + [ 3, 0, -1], + [-2, 1, 0]]) + + Multiplying it with another 3x1 Matrix calculates the cross product: + + >>> b = Matrix([3, 2, 1]) + >>> a.hat() * b + Matrix([ + [-4], + [ 8], + [-4]]) + + Which is equivalent to calling the ``cross`` method: + + >>> a.cross(b) + Matrix([ + [-4], + [ 8], + [-4]]) + + See Also + ======== + + dot + cross + vee + multiply + multiply_elementwise + """ + + if self.shape != (3, 1): + raise ShapeError("Dimensions incorrect, expected (3, 1), got " + + str(self.shape)) + else: + x, y, z = self + return self._new(3, 3, ( + 0, -z, y, + z, 0, -x, + -y, x, 0)) + + def vee(self): + r""" + Return a 3x1 vector from a skew-symmetric matrix representing the cross product, + so that ``self * b`` is equivalent to ``self.vee().cross(b)``. + + Examples + ======== + + Calling ``vee`` creates a vector from a skew-symmetric Matrix: + + >>> from sympy import Matrix + >>> A = Matrix([[0, -3, 2], [3, 0, -1], [-2, 1, 0]]) + >>> a = A.vee() + >>> a + Matrix([ + [1], + [2], + [3]]) + + Calculating the matrix product of the original matrix with a vector + is equivalent to a cross product: + + >>> b = Matrix([3, 2, 1]) + >>> A * b + Matrix([ + [-4], + [ 8], + [-4]]) + + >>> a.cross(b) + Matrix([ + [-4], + [ 8], + [-4]]) + + ``vee`` can also be used to retrieve angular velocity expressions. + Defining a rotation matrix: + + >>> from sympy import rot_ccw_axis3, trigsimp + >>> from sympy.physics.mechanics import dynamicsymbols + >>> theta = dynamicsymbols('theta') + >>> R = rot_ccw_axis3(theta) + >>> R + Matrix([ + [cos(theta(t)), -sin(theta(t)), 0], + [sin(theta(t)), cos(theta(t)), 0], + [ 0, 0, 1]]) + + We can retrieve the angular velocity: + + >>> Omega = R.T * R.diff() + >>> Omega = trigsimp(Omega) + >>> Omega.vee() + Matrix([ + [ 0], + [ 0], + [Derivative(theta(t), t)]]) + + See Also + ======== + + dot + cross + hat + multiply + multiply_elementwise + """ + + if self.shape != (3, 3): + raise ShapeError("Dimensions incorrect, expected (3, 3), got " + + str(self.shape)) + elif not self.is_anti_symmetric(): + raise ValueError("Matrix is not skew-symmetric") + else: + return self._new(3, 1, ( + self[2, 1], + self[0, 2], + self[1, 0])) + + @property + def D(self): + """Return Dirac conjugate (if ``self.rows == 4``). + + Examples + ======== + + >>> from sympy import Matrix, I, eye + >>> m = Matrix((0, 1 + I, 2, 3)) + >>> m.D + Matrix([[0, 1 - I, -2, -3]]) + >>> m = (eye(4) + I*eye(4)) + >>> m[0, 3] = 2 + >>> m.D + Matrix([ + [1 - I, 0, 0, 0], + [ 0, 1 - I, 0, 0], + [ 0, 0, -1 + I, 0], + [ 2, 0, 0, -1 + I]]) + + If the matrix does not have 4 rows an AttributeError will be raised + because this property is only defined for matrices with 4 rows. + + >>> Matrix(eye(2)).D + Traceback (most recent call last): + ... + AttributeError: Matrix has no attribute D. + + See Also + ======== + + sympy.matrices.matrixbase.MatrixBase.conjugate: By-element conjugation + sympy.matrices.matrixbase.MatrixBase.H: Hermite conjugation + """ + from sympy.physics.matrices import mgamma + if self.rows != 4: + # In Python 3.2, properties can only return an AttributeError + # so we can't raise a ShapeError -- see commit which added the + # first line of this inline comment. Also, there is no need + # for a message since MatrixBase will raise the AttributeError + raise AttributeError + return self.H * mgamma(0) + + def dot(self, b, hermitian=None, conjugate_convention=None): + """Return the dot or inner product of two vectors of equal length. + Here ``self`` must be a ``Matrix`` of size 1 x n or n x 1, and ``b`` + must be either a matrix of size 1 x n, n x 1, or a list/tuple of length n. + A scalar is returned. + + By default, ``dot`` does not conjugate ``self`` or ``b``, even if there are + complex entries. Set ``hermitian=True`` (and optionally a ``conjugate_convention``) + to compute the hermitian inner product. + + Possible kwargs are ``hermitian`` and ``conjugate_convention``. + + If ``conjugate_convention`` is ``"left"``, ``"math"`` or ``"maths"``, + the conjugate of the first vector (``self``) is used. If ``"right"`` + or ``"physics"`` is specified, the conjugate of the second vector ``b`` is used. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> v = Matrix([1, 1, 1]) + >>> M.row(0).dot(v) + 6 + >>> M.col(0).dot(v) + 12 + >>> v = [3, 2, 1] + >>> M.row(0).dot(v) + 10 + + >>> from sympy import I + >>> q = Matrix([1*I, 1*I, 1*I]) + >>> q.dot(q, hermitian=False) + -3 + + >>> q.dot(q, hermitian=True) + 3 + + >>> q1 = Matrix([1, 1, 1*I]) + >>> q.dot(q1, hermitian=True, conjugate_convention="maths") + 1 - 2*I + >>> q.dot(q1, hermitian=True, conjugate_convention="physics") + 1 + 2*I + + + See Also + ======== + + cross + multiply + multiply_elementwise + """ + from .dense import Matrix + + if not isinstance(b, MatrixBase): + if is_sequence(b): + if len(b) != self.cols and len(b) != self.rows: + raise ShapeError( + "Dimensions incorrect for dot product: %s, %s" % ( + self.shape, len(b))) + return self.dot(Matrix(b)) + else: + raise TypeError( + "`b` must be an ordered iterable or Matrix, not %s." % + type(b)) + + if (1 not in self.shape) or (1 not in b.shape): + raise ShapeError + if len(self) != len(b): + raise ShapeError( + "Dimensions incorrect for dot product: %s, %s" % (self.shape, b.shape)) + + mat = self + n = len(mat) + if mat.shape != (1, n): + mat = mat.reshape(1, n) + if b.shape != (n, 1): + b = b.reshape(n, 1) + + # Now ``mat`` is a row vector and ``b`` is a column vector. + + # If it so happens that only conjugate_convention is passed + # then automatically set hermitian to True. If only hermitian + # is true but no conjugate_convention is not passed then + # automatically set it to ``"maths"`` + + if conjugate_convention is not None and hermitian is None: + hermitian = True + if hermitian and conjugate_convention is None: + conjugate_convention = "maths" + + if hermitian == True: + if conjugate_convention in ("maths", "left", "math"): + mat = mat.conjugate() + elif conjugate_convention in ("physics", "right"): + b = b.conjugate() + else: + raise ValueError("Unknown conjugate_convention was entered." + " conjugate_convention must be one of the" + " following: math, maths, left, physics or right.") + return (mat * b)[0] + + def dual(self): + """Returns the dual of a matrix. + + A dual of a matrix is: + + ``(1/2)*levicivita(i, j, k, l)*M(k, l)`` summed over indices `k` and `l` + + Since the levicivita method is anti_symmetric for any pairwise + exchange of indices, the dual of a symmetric matrix is the zero + matrix. Strictly speaking the dual defined here assumes that the + 'matrix' `M` is a contravariant anti_symmetric second rank tensor, + so that the dual is a covariant second rank tensor. + + """ + from sympy.matrices import zeros + + M, n = self[:, :], self.rows + work = zeros(n) + if self.is_symmetric(): + return work + + for i in range(1, n): + for j in range(1, n): + acum = 0 + for k in range(1, n): + acum += LeviCivita(i, j, 0, k) * M[0, k] + work[i, j] = acum + work[j, i] = -acum + + for l in range(1, n): + acum = 0 + for a in range(1, n): + for b in range(1, n): + acum += LeviCivita(0, l, a, b) * M[a, b] + acum /= 2 + work[0, l] = -acum + work[l, 0] = acum + + return work + + def _eval_matrix_exp_jblock(self): + """A helper function to compute an exponential of a Jordan block + matrix + + Examples + ======== + + >>> from sympy import Symbol, Matrix + >>> l = Symbol('lamda') + + A trivial example of 1*1 Jordan block: + + >>> m = Matrix.jordan_block(1, l) + >>> m._eval_matrix_exp_jblock() + Matrix([[exp(lamda)]]) + + An example of 3*3 Jordan block: + + >>> m = Matrix.jordan_block(3, l) + >>> m._eval_matrix_exp_jblock() + Matrix([ + [exp(lamda), exp(lamda), exp(lamda)/2], + [ 0, exp(lamda), exp(lamda)], + [ 0, 0, exp(lamda)]]) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Matrix_function#Jordan_decomposition + """ + size = self.rows + l = self[0, 0] + exp_l = exp(l) + + bands = {i: exp_l / factorial(i) for i in range(size)} + + from .sparsetools import banded + return self.__class__(banded(size, bands)) + + + def analytic_func(self, f, x): + """ + Computes f(A) where A is a Square Matrix + and f is an analytic function. + + Examples + ======== + + >>> from sympy import Symbol, Matrix, S, log + + >>> x = Symbol('x') + >>> m = Matrix([[S(5)/4, S(3)/4], [S(3)/4, S(5)/4]]) + >>> f = log(x) + >>> m.analytic_func(f, x) + Matrix([ + [ 0, log(2)], + [log(2), 0]]) + + Parameters + ========== + + f : Expr + Analytic Function + x : Symbol + parameter of f + + """ + + f, x = _sympify(f), _sympify(x) + if not self.is_square: + raise NonSquareMatrixError + if not x.is_symbol: + raise ValueError("{} must be a symbol.".format(x)) + if x not in f.free_symbols: + raise ValueError( + "{} must be a parameter of {}.".format(x, f)) + if x in self.free_symbols: + raise ValueError( + "{} must not be a parameter of {}.".format(x, self)) + + eigen = self.eigenvals() + max_mul = max(eigen.values()) + derivative = {} + dd = f + for i in range(max_mul - 1): + dd = diff(dd, x) + derivative[i + 1] = dd + n = self.shape[0] + r = self.zeros(n) + f_val = self.zeros(n, 1) + row = 0 + + for i in eigen: + mul = eigen[i] + f_val[row] = f.subs(x, i) + if f_val[row].is_number and not f_val[row].is_complex: + raise ValueError( + "Cannot evaluate the function because the " + "function {} is not analytic at the given " + "eigenvalue {}".format(f, f_val[row])) + val = 1 + for a in range(n): + r[row, a] = val + val *= i + if mul > 1: + coe = [1 for ii in range(n)] + deri = 1 + while mul > 1: + row = row + 1 + mul -= 1 + d_i = derivative[deri].subs(x, i) + if d_i.is_number and not d_i.is_complex: + raise ValueError( + "Cannot evaluate the function because the " + "derivative {} is not analytic at the given " + "eigenvalue {}".format(derivative[deri], d_i)) + f_val[row] = d_i + for a in range(n): + if a - deri + 1 <= 0: + r[row, a] = 0 + coe[a] = 0 + continue + coe[a] = coe[a]*(a - deri + 1) + r[row, a] = coe[a]*pow(i, a - deri) + deri += 1 + row += 1 + c = r.solve(f_val) + ans = self.zeros(n) + pre = self.eye(n) + for i in range(n): + ans = ans + c[i]*pre + pre *= self + return ans + + + def exp(self): + """Return the exponential of a square matrix. + + Examples + ======== + + >>> from sympy import Symbol, Matrix + + >>> t = Symbol('t') + >>> m = Matrix([[0, 1], [-1, 0]]) * t + >>> m.exp() + Matrix([ + [ exp(I*t)/2 + exp(-I*t)/2, -I*exp(I*t)/2 + I*exp(-I*t)/2], + [I*exp(I*t)/2 - I*exp(-I*t)/2, exp(I*t)/2 + exp(-I*t)/2]]) + """ + if not self.is_square: + raise NonSquareMatrixError( + "Exponentiation is valid only for square matrices") + try: + P, J = self.jordan_form() + cells = J.get_diag_blocks() + except MatrixError: + raise NotImplementedError( + "Exponentiation is implemented only for matrices for which the Jordan normal form can be computed") + + blocks = [cell._eval_matrix_exp_jblock() for cell in cells] + from sympy.matrices import diag + eJ = diag(*blocks) + # n = self.rows + ret = P.multiply(eJ, dotprodsimp=None).multiply(P.inv(), dotprodsimp=None) + if all(value.is_real for value in self.values()): + return type(self)(re(ret)) + else: + return type(self)(ret) + + def _eval_matrix_log_jblock(self): + """Helper function to compute logarithm of a jordan block. + + Examples + ======== + + >>> from sympy import Symbol, Matrix + >>> l = Symbol('lamda') + + A trivial example of 1*1 Jordan block: + + >>> m = Matrix.jordan_block(1, l) + >>> m._eval_matrix_log_jblock() + Matrix([[log(lamda)]]) + + An example of 3*3 Jordan block: + + >>> m = Matrix.jordan_block(3, l) + >>> m._eval_matrix_log_jblock() + Matrix([ + [log(lamda), 1/lamda, -1/(2*lamda**2)], + [ 0, log(lamda), 1/lamda], + [ 0, 0, log(lamda)]]) + """ + size = self.rows + l = self[0, 0] + + if l.is_zero: + raise MatrixError( + 'Could not take logarithm or reciprocal for the given ' + 'eigenvalue {}'.format(l)) + + bands = {0: log(l)} + for i in range(1, size): + bands[i] = -((-l) ** -i) / i + + from .sparsetools import banded + return self.__class__(banded(size, bands)) + + def log(self, simplify=cancel): + """Return the logarithm of a square matrix. + + Parameters + ========== + + simplify : function, bool + The function to simplify the result with. + + Default is ``cancel``, which is effective to reduce the + expression growing for taking reciprocals and inverses for + symbolic matrices. + + Examples + ======== + + >>> from sympy import S, Matrix + + Examples for positive-definite matrices: + + >>> m = Matrix([[1, 1], [0, 1]]) + >>> m.log() + Matrix([ + [0, 1], + [0, 0]]) + + >>> m = Matrix([[S(5)/4, S(3)/4], [S(3)/4, S(5)/4]]) + >>> m.log() + Matrix([ + [ 0, log(2)], + [log(2), 0]]) + + Examples for non positive-definite matrices: + + >>> m = Matrix([[S(3)/4, S(5)/4], [S(5)/4, S(3)/4]]) + >>> m.log() + Matrix([ + [ I*pi/2, log(2) - I*pi/2], + [log(2) - I*pi/2, I*pi/2]]) + + >>> m = Matrix( + ... [[0, 0, 0, 1], + ... [0, 0, 1, 0], + ... [0, 1, 0, 0], + ... [1, 0, 0, 0]]) + >>> m.log() + Matrix([ + [ I*pi/2, 0, 0, -I*pi/2], + [ 0, I*pi/2, -I*pi/2, 0], + [ 0, -I*pi/2, I*pi/2, 0], + [-I*pi/2, 0, 0, I*pi/2]]) + """ + if not self.is_square: + raise NonSquareMatrixError( + "Logarithm is valid only for square matrices") + + try: + if simplify: + P, J = simplify(self).jordan_form() + else: + P, J = self.jordan_form() + + cells = J.get_diag_blocks() + except MatrixError: + raise NotImplementedError( + "Logarithm is implemented only for matrices for which " + "the Jordan normal form can be computed") + + blocks = [ + cell._eval_matrix_log_jblock() + for cell in cells] + from sympy.matrices import diag + eJ = diag(*blocks) + + if simplify: + ret = simplify(P * eJ * simplify(P.inv())) + ret = self.__class__(ret) + else: + ret = P * eJ * P.inv() + + return ret + + def is_nilpotent(self): + """Checks if a matrix is nilpotent. + + A matrix B is nilpotent if for some integer k, B**k is + a zero matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> a = Matrix([[0, 0, 0], [1, 0, 0], [1, 1, 0]]) + >>> a.is_nilpotent() + True + + >>> a = Matrix([[1, 0, 1], [1, 0, 0], [1, 1, 0]]) + >>> a.is_nilpotent() + False + """ + if not self: + return True + if not self.is_square: + raise NonSquareMatrixError( + "Nilpotency is valid only for square matrices") + x = uniquely_named_symbol('x', self, modify=lambda s: '_' + s) + p = self.charpoly(x) + if p.args[0] == x ** self.rows: + return True + return False + + def key2bounds(self, keys): + """Converts a key with potentially mixed types of keys (integer and slice) + into a tuple of ranges and raises an error if any index is out of ``self``'s + range. + + See Also + ======== + + key2ij + """ + islice, jslice = [isinstance(k, slice) for k in keys] + if islice: + if not self.rows: + rlo = rhi = 0 + else: + rlo, rhi = keys[0].indices(self.rows)[:2] + else: + rlo = a2idx(keys[0], self.rows) + rhi = rlo + 1 + if jslice: + if not self.cols: + clo = chi = 0 + else: + clo, chi = keys[1].indices(self.cols)[:2] + else: + clo = a2idx(keys[1], self.cols) + chi = clo + 1 + return rlo, rhi, clo, chi + + def key2ij(self, key): + """Converts key into canonical form, converting integers or indexable + items into valid integers for ``self``'s range or returning slices + unchanged. + + See Also + ======== + + key2bounds + """ + if is_sequence(key): + if not len(key) == 2: + raise TypeError('key must be a sequence of length 2') + return [a2idx(i, n) if not isinstance(i, slice) else i + for i, n in zip(key, self.shape)] + elif isinstance(key, slice): + return key.indices(len(self))[:2] + else: + return divmod(a2idx(key, len(self)), self.cols) + + def normalized(self, iszerofunc=_iszero): + """Return the normalized version of ``self``. + + Parameters + ========== + + iszerofunc : Function, optional + A function to determine whether ``self`` is a zero vector. + The default ``_iszero`` tests to see if each element is + exactly zero. + + Returns + ======= + + Matrix + Normalized vector form of ``self``. + It has the same length as a unit vector. However, a zero vector + will be returned for a vector with norm 0. + + Raises + ====== + + ShapeError + If the matrix is not in a vector form. + + See Also + ======== + + norm + """ + if self.rows != 1 and self.cols != 1: + raise ShapeError("A Matrix must be a vector to normalize.") + norm = self.norm() + if iszerofunc(norm): + out = self.zeros(self.rows, self.cols) + else: + out = self.applyfunc(lambda i: i / norm) + return out + + def norm(self, ord=None): + """Return the Norm of a Matrix or Vector. + + In the simplest case this is the geometric size of the vector + Other norms can be specified by the ord parameter + + + ===== ============================ ========================== + ord norm for matrices norm for vectors + ===== ============================ ========================== + None Frobenius norm 2-norm + 'fro' Frobenius norm - does not exist + inf maximum row sum max(abs(x)) + -inf -- min(abs(x)) + 1 maximum column sum as below + -1 -- as below + 2 2-norm (largest sing. value) as below + -2 smallest singular value as below + other - does not exist sum(abs(x)**ord)**(1./ord) + ===== ============================ ========================== + + Examples + ======== + + >>> from sympy import Matrix, Symbol, trigsimp, cos, sin, oo + >>> x = Symbol('x', real=True) + >>> v = Matrix([cos(x), sin(x)]) + >>> trigsimp( v.norm() ) + 1 + >>> v.norm(10) + (sin(x)**10 + cos(x)**10)**(1/10) + >>> A = Matrix([[1, 1], [1, 1]]) + >>> A.norm(1) # maximum sum of absolute values of A is 2 + 2 + >>> A.norm(2) # Spectral norm (max of |Ax|/|x| under 2-vector-norm) + 2 + >>> A.norm(-2) # Inverse spectral norm (smallest singular value) + 0 + >>> A.norm() # Frobenius Norm + 2 + >>> A.norm(oo) # Infinity Norm + 2 + >>> Matrix([1, -2]).norm(oo) + 2 + >>> Matrix([-1, 2]).norm(-oo) + 1 + + See Also + ======== + + normalized + """ + # Row or Column Vector Norms + vals = list(self.values()) or [0] + if S.One in self.shape: + if ord in (2, None): # Common case sqrt() + return sqrt(Add(*(abs(i) ** 2 for i in vals))) + + elif ord == 1: # sum(abs(x)) + return Add(*(abs(i) for i in vals)) + + elif ord is S.Infinity: # max(abs(x)) + return Max(*[abs(i) for i in vals]) + + elif ord is S.NegativeInfinity: # min(abs(x)) + return Min(*[abs(i) for i in vals]) + + # Otherwise generalize the 2-norm, Sum(x_i**ord)**(1/ord) + # Note that while useful this is not mathematically a norm + try: + return Pow(Add(*(abs(i) ** ord for i in vals)), S.One / ord) + except (NotImplementedError, TypeError): + raise ValueError("Expected order to be Number, Symbol, oo") + + # Matrix Norms + else: + if ord == 1: # Maximum column sum + m = self.applyfunc(abs) + return Max(*[sum(m.col(i)) for i in range(m.cols)]) + + elif ord == 2: # Spectral Norm + # Maximum singular value + return Max(*self.singular_values()) + + elif ord == -2: + # Minimum singular value + return Min(*self.singular_values()) + + elif ord is S.Infinity: # Infinity Norm - Maximum row sum + m = self.applyfunc(abs) + return Max(*[sum(m.row(i)) for i in range(m.rows)]) + + elif (ord is None or isinstance(ord, + str) and ord.lower() in + ['f', 'fro', 'frobenius', 'vector']): + # Reshape as vector and send back to norm function + return self.vec().norm(ord=2) + + else: + raise NotImplementedError("Matrix Norms under development") + + def print_nonzero(self, symb="X"): + """Shows location of non-zero entries for fast shape lookup. + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> m = Matrix(2, 3, lambda i, j: i*3+j) + >>> m + Matrix([ + [0, 1, 2], + [3, 4, 5]]) + >>> m.print_nonzero() + [ XX] + [XXX] + >>> m = eye(4) + >>> m.print_nonzero("x") + [x ] + [ x ] + [ x ] + [ x] + + """ + s = [] + for i in range(self.rows): + line = [] + for j in range(self.cols): + if self[i, j] == 0: + line.append(" ") + else: + line.append(str(symb)) + s.append("[%s]" % ''.join(line)) + print('\n'.join(s)) + + def project(self, v): + """Return the projection of ``self`` onto the line containing ``v``. + + Examples + ======== + + >>> from sympy import Matrix, S, sqrt + >>> V = Matrix([sqrt(3)/2, S.Half]) + >>> x = Matrix([[1, 0]]) + >>> V.project(x) + Matrix([[sqrt(3)/2, 0]]) + >>> V.project(-x) + Matrix([[sqrt(3)/2, 0]]) + """ + return v * (self.dot(v) / v.dot(v)) + + def table(self, printer, rowstart='[', rowend=']', rowsep='\n', + colsep=', ', align='right'): + r""" + String form of Matrix as a table. + + ``printer`` is the printer to use for on the elements (generally + something like StrPrinter()) + + ``rowstart`` is the string used to start each row (by default '['). + + ``rowend`` is the string used to end each row (by default ']'). + + ``rowsep`` is the string used to separate rows (by default a newline). + + ``colsep`` is the string used to separate columns (by default ', '). + + ``align`` defines how the elements are aligned. Must be one of 'left', + 'right', or 'center'. You can also use '<', '>', and '^' to mean the + same thing, respectively. + + This is used by the string printer for Matrix. + + Examples + ======== + + >>> from sympy import Matrix, StrPrinter + >>> M = Matrix([[1, 2], [-33, 4]]) + >>> printer = StrPrinter() + >>> M.table(printer) + '[ 1, 2]\n[-33, 4]' + >>> print(M.table(printer)) + [ 1, 2] + [-33, 4] + >>> print(M.table(printer, rowsep=',\n')) + [ 1, 2], + [-33, 4] + >>> print('[%s]' % M.table(printer, rowsep=',\n')) + [[ 1, 2], + [-33, 4]] + >>> print(M.table(printer, colsep=' ')) + [ 1 2] + [-33 4] + >>> print(M.table(printer, align='center')) + [ 1 , 2] + [-33, 4] + >>> print(M.table(printer, rowstart='{', rowend='}')) + { 1, 2} + {-33, 4} + """ + # Handle zero dimensions: + if S.Zero in self.shape: + return '[]' + # 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): + s = printer._print(self[i, j]) + res[-1].append(s) + maxlen[j] = max(len(s), maxlen[j]) + # Patch strings together + align = { + 'left': 'ljust', + 'right': 'rjust', + 'center': 'center', + '<': 'ljust', + '>': 'rjust', + '^': 'center', + }[align] + for i, row in enumerate(res): + for j, elem in enumerate(row): + row[j] = getattr(elem, align)(maxlen[j]) + res[i] = rowstart + colsep.join(row) + rowend + return rowsep.join(res) + + def rank_decomposition(self, iszerofunc=_iszero, simplify=False): + return _rank_decomposition(self, iszerofunc=iszerofunc, + simplify=simplify) + + def cholesky(self, hermitian=True): + raise NotImplementedError('This function is implemented in DenseMatrix or SparseMatrix') + + def LDLdecomposition(self, hermitian=True): + raise NotImplementedError('This function is implemented in DenseMatrix or SparseMatrix') + + def LUdecomposition(self, iszerofunc=_iszero, simpfunc=None, + rankcheck=False): + return _LUdecomposition(self, iszerofunc=iszerofunc, simpfunc=simpfunc, + rankcheck=rankcheck) + + def LUdecomposition_Simple(self, iszerofunc=_iszero, simpfunc=None, + rankcheck=False): + return _LUdecomposition_Simple(self, iszerofunc=iszerofunc, + simpfunc=simpfunc, rankcheck=rankcheck) + + def LUdecompositionFF(self): + return _LUdecompositionFF(self) + + def singular_value_decomposition(self): + return _singular_value_decomposition(self) + + def QRdecomposition(self): + return _QRdecomposition(self) + + def upper_hessenberg_decomposition(self): + return _upper_hessenberg_decomposition(self) + + def diagonal_solve(self, rhs): + return _diagonal_solve(self, rhs) + + def lower_triangular_solve(self, rhs): + raise NotImplementedError('This function is implemented in DenseMatrix or SparseMatrix') + + def upper_triangular_solve(self, rhs): + raise NotImplementedError('This function is implemented in DenseMatrix or SparseMatrix') + + def cholesky_solve(self, rhs): + return _cholesky_solve(self, rhs) + + def LDLsolve(self, rhs): + return _LDLsolve(self, rhs) + + def LUsolve(self, rhs, iszerofunc=_iszero): + return _LUsolve(self, rhs, iszerofunc=iszerofunc) + + def QRsolve(self, b): + return _QRsolve(self, b) + + def gauss_jordan_solve(self, B, freevar=False): + return _gauss_jordan_solve(self, B, freevar=freevar) + + def pinv_solve(self, B, arbitrary_matrix=None): + return _pinv_solve(self, B, arbitrary_matrix=arbitrary_matrix) + + def cramer_solve(self, rhs, det_method="laplace"): + return _cramer_solve(self, rhs, det_method=det_method) + + def solve(self, rhs, method='GJ'): + return _solve(self, rhs, method=method) + + def solve_least_squares(self, rhs, method='CH'): + return _solve_least_squares(self, rhs, method=method) + + def pinv(self, method='RD'): + return _pinv(self, method=method) + + def inverse_ADJ(self, iszerofunc=_iszero): + return _inv_ADJ(self, iszerofunc=iszerofunc) + + def inverse_BLOCK(self, iszerofunc=_iszero): + return _inv_block(self, iszerofunc=iszerofunc) + + def inverse_GE(self, iszerofunc=_iszero): + return _inv_GE(self, iszerofunc=iszerofunc) + + def inverse_LU(self, iszerofunc=_iszero): + return _inv_LU(self, iszerofunc=iszerofunc) + + def inverse_CH(self, iszerofunc=_iszero): + return _inv_CH(self, iszerofunc=iszerofunc) + + def inverse_LDL(self, iszerofunc=_iszero): + return _inv_LDL(self, iszerofunc=iszerofunc) + + def inverse_QR(self, iszerofunc=_iszero): + return _inv_QR(self, iszerofunc=iszerofunc) + + def inv(self, method=None, iszerofunc=_iszero, try_block_diag=False): + return _inv(self, method=method, iszerofunc=iszerofunc, + try_block_diag=try_block_diag) + + def connected_components(self): + return _connected_components(self) + + def connected_components_decomposition(self): + return _connected_components_decomposition(self) + + def strongly_connected_components(self): + return _strongly_connected_components(self) + + def strongly_connected_components_decomposition(self, lower=True): + return _strongly_connected_components_decomposition(self, lower=lower) + + _sage_ = Basic._sage_ + + rank_decomposition.__doc__ = _rank_decomposition.__doc__ + cholesky.__doc__ = _cholesky.__doc__ + LDLdecomposition.__doc__ = _LDLdecomposition.__doc__ + LUdecomposition.__doc__ = _LUdecomposition.__doc__ + LUdecomposition_Simple.__doc__ = _LUdecomposition_Simple.__doc__ + LUdecompositionFF.__doc__ = _LUdecompositionFF.__doc__ + singular_value_decomposition.__doc__ = _singular_value_decomposition.__doc__ + QRdecomposition.__doc__ = _QRdecomposition.__doc__ + upper_hessenberg_decomposition.__doc__ = _upper_hessenberg_decomposition.__doc__ + + diagonal_solve.__doc__ = _diagonal_solve.__doc__ + lower_triangular_solve.__doc__ = _lower_triangular_solve.__doc__ + upper_triangular_solve.__doc__ = _upper_triangular_solve.__doc__ + cholesky_solve.__doc__ = _cholesky_solve.__doc__ + LDLsolve.__doc__ = _LDLsolve.__doc__ + LUsolve.__doc__ = _LUsolve.__doc__ + QRsolve.__doc__ = _QRsolve.__doc__ + gauss_jordan_solve.__doc__ = _gauss_jordan_solve.__doc__ + pinv_solve.__doc__ = _pinv_solve.__doc__ + cramer_solve.__doc__ = _cramer_solve.__doc__ + solve.__doc__ = _solve.__doc__ + solve_least_squares.__doc__ = _solve_least_squares.__doc__ + + pinv.__doc__ = _pinv.__doc__ + inverse_ADJ.__doc__ = _inv_ADJ.__doc__ + inverse_GE.__doc__ = _inv_GE.__doc__ + inverse_LU.__doc__ = _inv_LU.__doc__ + inverse_CH.__doc__ = _inv_CH.__doc__ + inverse_LDL.__doc__ = _inv_LDL.__doc__ + inverse_QR.__doc__ = _inv_QR.__doc__ + inverse_BLOCK.__doc__ = _inv_block.__doc__ + inv.__doc__ = _inv.__doc__ + + connected_components.__doc__ = _connected_components.__doc__ + connected_components_decomposition.__doc__ = \ + _connected_components_decomposition.__doc__ + strongly_connected_components.__doc__ = \ + _strongly_connected_components.__doc__ + strongly_connected_components_decomposition.__doc__ = \ + _strongly_connected_components_decomposition.__doc__ + + +def _convert_matrix(typ, mat): + """Convert mat to a Matrix of type typ.""" + from sympy.matrices.matrixbase import MatrixBase + if getattr(mat, "is_Matrix", False) and not isinstance(mat, MatrixBase): + # This is needed for interop between Matrix and the redundant matrix + # mixin types like _MinimalMatrix etc. If anyone should happen to be + # using those then this keeps them working. Really _MinimalMatrix etc + # should be deprecated and removed though. + return typ(*mat.shape, list(mat)) + else: + return typ(mat) + + +def _has_matrix_shape(other): + shape = getattr(other, 'shape', None) + if shape is None: + return False + return isinstance(shape, tuple) and len(shape) == 2 + + +def _has_rows_cols(other): + return hasattr(other, 'rows') and hasattr(other, 'cols') + + +def _coerce_operand(self, other): + """Convert other to a Matrix, or check for possible scalar.""" + + INVALID = None, 'invalid_type' + + # Disallow mixing Matrix and Array + if isinstance(other, NDimArray): + return INVALID + + is_Matrix = getattr(other, 'is_Matrix', None) + + # Return a Matrix as-is + if is_Matrix: + return other, 'is_matrix' + + # Try to convert numpy array, mpmath matrix etc. + if is_Matrix is None: + if _has_matrix_shape(other) or _has_rows_cols(other): + return _convert_matrix(type(self), other), 'is_matrix' + + # Could be a scalar but only if not iterable... + if not isinstance(other, Iterable): + return other, 'possible_scalar' + + return INVALID + + +def classof(A, B): + """ + Get the type of the result when combining matrices of different types. + + Currently the strategy is that immutability is contagious. + + Examples + ======== + + >>> from sympy import Matrix, ImmutableMatrix + >>> from sympy.matrices.matrixbase import classof + >>> M = Matrix([[1, 2], [3, 4]]) # a Mutable Matrix + >>> IM = ImmutableMatrix([[1, 2], [3, 4]]) + >>> classof(M, IM) + + """ + priority_A = getattr(A, '_class_priority', None) + priority_B = getattr(B, '_class_priority', None) + if None not in (priority_A, priority_B): + if A._class_priority > B._class_priority: + return A.__class__ + else: + return B.__class__ + + try: + import numpy + except ImportError: + pass + else: + if isinstance(A, numpy.ndarray): + return B.__class__ + if isinstance(B, numpy.ndarray): + return A.__class__ + + raise TypeError("Incompatible classes %s, %s" % (A.__class__, B.__class__)) + + +def _unify_with_other(self, other): + """Unify self and other into a single matrix type, or check for scalar.""" + other, T = _coerce_operand(self, other) + + if T == "is_matrix": + typ = classof(self, other) + if typ != self.__class__: + self = _convert_matrix(typ, self) + if typ != other.__class__: + other = _convert_matrix(typ, other) + + return self, other, T + + +def a2idx(j, n=None): + """Return integer after making positive and validating against n.""" + if not isinstance(j, int): + jindex = getattr(j, '__index__', None) + if jindex is not None: + j = jindex() + else: + raise IndexError("Invalid index a[%r]" % (j,)) + if n is not None: + if j < 0: + j += n + if not (j >= 0 and j < n): + raise IndexError("Index out of range: a[%s]" % (j,)) + return int(j) + + +class DeferredVector(Symbol, NotIterable): # type: ignore + """A vector whose components are deferred (e.g. for use with lambdify). + + Examples + ======== + + >>> from sympy import DeferredVector, lambdify + >>> X = DeferredVector( 'X' ) + >>> X + X + >>> expr = (X[0] + 2, X[2] + 3) + >>> func = lambdify( X, expr) + >>> func( [1, 2, 3] ) + (3, 6) + """ + + def __getitem__(self, i): + if i == -0: + i = 0 + if i < 0: + raise IndexError('DeferredVector index out of range') + component_name = '%s[%d]' % (self.name, i) + return Symbol(component_name) + + def __str__(self): + return sstr(self) + + def __repr__(self): + return "DeferredVector('%s')" % self.name diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/normalforms.py b/venv/lib/python3.10/site-packages/sympy/matrices/normalforms.py new file mode 100644 index 0000000000000000000000000000000000000000..61a7d26bbdb8c8a3e8e3044d39b2403b2e14b7d5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/normalforms.py @@ -0,0 +1,156 @@ +'''Functions returning normal forms of matrices''' + +from sympy.polys.domains.integerring import ZZ +from sympy.polys.polytools import Poly +from sympy.polys.matrices import DomainMatrix +from sympy.polys.matrices.normalforms import ( + smith_normal_form as _snf, + is_smith_normal_form as _is_snf, + smith_normal_decomp as _snd, + invariant_factors as _invf, + hermite_normal_form as _hnf, + ) + + +def _to_domain(m, domain=None): + """Convert Matrix to DomainMatrix""" + # XXX: deprecated support for RawMatrix: + ring = getattr(m, "ring", None) + m = m.applyfunc(lambda e: e.as_expr() if isinstance(e, Poly) else e) + + dM = DomainMatrix.from_Matrix(m) + + domain = domain or ring + if domain is not None: + dM = dM.convert_to(domain) + return dM + + +def smith_normal_form(m, domain=None): + ''' + Return the Smith Normal Form of a matrix `m` over the ring `domain`. + This will only work if the ring is a principal ideal domain. + + Examples + ======== + + >>> from sympy import Matrix, ZZ + >>> from sympy.matrices.normalforms import smith_normal_form + >>> m = Matrix([[12, 6, 4], [3, 9, 6], [2, 16, 14]]) + >>> print(smith_normal_form(m, domain=ZZ)) + Matrix([[1, 0, 0], [0, 10, 0], [0, 0, 30]]) + + ''' + dM = _to_domain(m, domain) + return _snf(dM).to_Matrix() + + +def is_smith_normal_form(m, domain=None): + ''' + Checks that the matrix is in Smith Normal Form + ''' + dM = _to_domain(m, domain) + return _is_snf(dM) + + +def smith_normal_decomp(m, domain=None): + ''' + Return the Smith Normal Decomposition of a matrix `m` over the ring + `domain`. This will only work if the ring is a principal ideal domain. + + Examples + ======== + + >>> from sympy import Matrix, ZZ + >>> from sympy.matrices.normalforms import smith_normal_decomp + >>> m = Matrix([[12, 6, 4], [3, 9, 6], [2, 16, 14]]) + >>> a, s, t = smith_normal_decomp(m, domain=ZZ) + >>> assert a == s * m * t + ''' + dM = _to_domain(m, domain) + a, s, t = _snd(dM) + return a.to_Matrix(), s.to_Matrix(), t.to_Matrix() + + +def invariant_factors(m, domain=None): + ''' + Return the tuple of abelian invariants for a matrix `m` + (as in the Smith-Normal form) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Smith_normal_form#Algorithm + .. [2] https://web.archive.org/web/20200331143852/https://sierra.nmsu.edu/morandi/notes/SmithNormalForm.pdf + + ''' + dM = _to_domain(m, domain) + factors = _invf(dM) + factors = tuple(dM.domain.to_sympy(f) for f in factors) + # XXX: deprecated. + if hasattr(m, "ring"): + if m.ring.is_PolynomialRing: + K = m.ring + to_poly = lambda f: Poly(f, K.symbols, domain=K.domain) + factors = tuple(to_poly(f) for f in factors) + return factors + + +def hermite_normal_form(A, *, D=None, check_rank=False): + r""" + Compute the Hermite Normal Form of a Matrix *A* of integers. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.matrices.normalforms import hermite_normal_form + >>> m = Matrix([[12, 6, 4], [3, 9, 6], [2, 16, 14]]) + >>> print(hermite_normal_form(m)) + Matrix([[10, 0, 2], [0, 15, 3], [0, 0, 2]]) + + Parameters + ========== + + A : $m \times n$ ``Matrix`` of integers. + + D : int, optional + Let $W$ be the HNF of *A*. If known in advance, a positive integer *D* + being any multiple of $\det(W)$ may be provided. In this case, if *A* + also has rank $m$, then we may use an alternative algorithm that works + mod *D* in order to prevent coefficient explosion. + + check_rank : boolean, optional (default=False) + The basic assumption is that, if you pass a value for *D*, then + you already believe that *A* has rank $m$, so we do not waste time + checking it for you. If you do want this to be checked (and the + ordinary, non-modulo *D* algorithm to be used if the check fails), then + set *check_rank* to ``True``. + + Returns + ======= + + ``Matrix`` + The HNF of matrix *A*. + + Raises + ====== + + DMDomainError + If the domain of the matrix is not :ref:`ZZ`. + + DMShapeError + If the mod *D* algorithm is used but the matrix has more rows than + columns. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Algorithms 2.4.5 and 2.4.8.) + + """ + # Accept any of Python int, SymPy Integer, and ZZ itself: + if D is not None and not ZZ.of_type(D): + D = ZZ(int(D)) + return _hnf(A._rep, D=D, check_rank=check_rank).to_Matrix() diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/reductions.py b/venv/lib/python3.10/site-packages/sympy/matrices/reductions.py new file mode 100644 index 0000000000000000000000000000000000000000..aace8c0336358e1869a34d99f79390cc0c0163fe --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/reductions.py @@ -0,0 +1,387 @@ +from types import FunctionType + +from sympy.polys.polyerrors import CoercionFailed +from sympy.polys.domains import ZZ, QQ + +from .utilities import _get_intermediate_simp, _iszero, _dotprodsimp, _simplify +from .determinant import _find_reasonable_pivot + + +def _row_reduce_list(mat, rows, cols, one, iszerofunc, simpfunc, + normalize_last=True, normalize=True, zero_above=True): + """Row reduce a flat list representation of a matrix and return a tuple + (rref_matrix, pivot_cols, swaps) where ``rref_matrix`` is a flat list, + ``pivot_cols`` are the pivot columns and ``swaps`` are any row swaps that + were used in the process of row reduction. + + Parameters + ========== + + mat : list + list of matrix elements, must be ``rows`` * ``cols`` in length + + rows, cols : integer + number of rows and columns in flat list representation + + one : SymPy object + represents the value one, from ``Matrix.one`` + + iszerofunc : determines if an entry can be used as a pivot + + simpfunc : used to simplify elements and test if they are + zero if ``iszerofunc`` returns `None` + + normalize_last : indicates where all row reduction should + happen in a fraction-free manner and then the rows are + normalized (so that the pivots are 1), or whether + rows should be normalized along the way (like the naive + row reduction algorithm) + + normalize : whether pivot rows should be normalized so that + the pivot value is 1 + + zero_above : whether entries above the pivot should be zeroed. + If ``zero_above=False``, an echelon matrix will be returned. + """ + + def get_col(i): + return mat[i::cols] + + def row_swap(i, j): + mat[i*cols:(i + 1)*cols], mat[j*cols:(j + 1)*cols] = \ + mat[j*cols:(j + 1)*cols], mat[i*cols:(i + 1)*cols] + + def cross_cancel(a, i, b, j): + """Does the row op row[i] = a*row[i] - b*row[j]""" + q = (j - i)*cols + for p in range(i*cols, (i + 1)*cols): + mat[p] = isimp(a*mat[p] - b*mat[p + q]) + + isimp = _get_intermediate_simp(_dotprodsimp) + piv_row, piv_col = 0, 0 + pivot_cols = [] + swaps = [] + + # use a fraction free method to zero above and below each pivot + while piv_col < cols and piv_row < rows: + pivot_offset, pivot_val, \ + assumed_nonzero, newly_determined = _find_reasonable_pivot( + get_col(piv_col)[piv_row:], iszerofunc, simpfunc) + + # _find_reasonable_pivot may have simplified some things + # in the process. Let's not let them go to waste + for (offset, val) in newly_determined: + offset += piv_row + mat[offset*cols + piv_col] = val + + if pivot_offset is None: + piv_col += 1 + continue + + pivot_cols.append(piv_col) + if pivot_offset != 0: + row_swap(piv_row, pivot_offset + piv_row) + swaps.append((piv_row, pivot_offset + piv_row)) + + # if we aren't normalizing last, we normalize + # before we zero the other rows + if normalize_last is False: + i, j = piv_row, piv_col + mat[i*cols + j] = one + for p in range(i*cols + j + 1, (i + 1)*cols): + mat[p] = isimp(mat[p] / pivot_val) + # after normalizing, the pivot value is 1 + pivot_val = one + + # zero above and below the pivot + for row in range(rows): + # don't zero our current row + if row == piv_row: + continue + # don't zero above the pivot unless we're told. + if zero_above is False and row < piv_row: + continue + # if we're already a zero, don't do anything + val = mat[row*cols + piv_col] + if iszerofunc(val): + continue + + cross_cancel(pivot_val, row, val, piv_row) + piv_row += 1 + + # normalize each row + if normalize_last is True and normalize is True: + for piv_i, piv_j in enumerate(pivot_cols): + pivot_val = mat[piv_i*cols + piv_j] + mat[piv_i*cols + piv_j] = one + for p in range(piv_i*cols + piv_j + 1, (piv_i + 1)*cols): + mat[p] = isimp(mat[p] / pivot_val) + + return mat, tuple(pivot_cols), tuple(swaps) + + +# This functions is a candidate for caching if it gets implemented for matrices. +def _row_reduce(M, iszerofunc, simpfunc, normalize_last=True, + normalize=True, zero_above=True): + + mat, pivot_cols, swaps = _row_reduce_list(list(M), M.rows, M.cols, M.one, + iszerofunc, simpfunc, normalize_last=normalize_last, + normalize=normalize, zero_above=zero_above) + + return M._new(M.rows, M.cols, mat), pivot_cols, swaps + + +def _is_echelon(M, iszerofunc=_iszero): + """Returns `True` if the matrix is in echelon form. That is, all rows of + zeros are at the bottom, and below each leading non-zero in a row are + exclusively zeros.""" + + if M.rows <= 0 or M.cols <= 0: + return True + + zeros_below = all(iszerofunc(t) for t in M[1:, 0]) + + if iszerofunc(M[0, 0]): + return zeros_below and _is_echelon(M[:, 1:], iszerofunc) + + return zeros_below and _is_echelon(M[1:, 1:], iszerofunc) + + +def _echelon_form(M, iszerofunc=_iszero, simplify=False, with_pivots=False): + """Returns a matrix row-equivalent to ``M`` that is in echelon form. Note + that echelon form of a matrix is *not* unique, however, properties like the + row space and the null space are preserved. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2], [3, 4]]) + >>> M.echelon_form() + Matrix([ + [1, 2], + [0, -2]]) + """ + + simpfunc = simplify if isinstance(simplify, FunctionType) else _simplify + + mat, pivots, _ = _row_reduce(M, iszerofunc, simpfunc, + normalize_last=True, normalize=False, zero_above=False) + + if with_pivots: + return mat, pivots + + return mat + + +# This functions is a candidate for caching if it gets implemented for matrices. +def _rank(M, iszerofunc=_iszero, simplify=False): + """Returns the rank of a matrix. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x + >>> m = Matrix([[1, 2], [x, 1 - 1/x]]) + >>> m.rank() + 2 + >>> n = Matrix(3, 3, range(1, 10)) + >>> n.rank() + 2 + """ + + def _permute_complexity_right(M, iszerofunc): + """Permute columns with complicated elements as + far right as they can go. Since the ``sympy`` row reduction + algorithms start on the left, having complexity right-shifted + speeds things up. + + Returns a tuple (mat, perm) where perm is a permutation + of the columns to perform to shift the complex columns right, and mat + is the permuted matrix.""" + + def complexity(i): + # the complexity of a column will be judged by how many + # element's zero-ness cannot be determined + return sum(1 if iszerofunc(e) is None else 0 for e in M[:, i]) + + complex = [(complexity(i), i) for i in range(M.cols)] + perm = [j for (i, j) in sorted(complex)] + + return (M.permute(perm, orientation='cols'), perm) + + simpfunc = simplify if isinstance(simplify, FunctionType) else _simplify + + # for small matrices, we compute the rank explicitly + # if is_zero on elements doesn't answer the question + # for small matrices, we fall back to the full routine. + if M.rows <= 0 or M.cols <= 0: + return 0 + + if M.rows <= 1 or M.cols <= 1: + zeros = [iszerofunc(x) for x in M] + + if False in zeros: + return 1 + + if M.rows == 2 and M.cols == 2: + zeros = [iszerofunc(x) for x in M] + + if False not in zeros and None not in zeros: + return 0 + + d = M.det() + + if iszerofunc(d) and False in zeros: + return 1 + if iszerofunc(d) is False: + return 2 + + mat, _ = _permute_complexity_right(M, iszerofunc=iszerofunc) + _, pivots, _ = _row_reduce(mat, iszerofunc, simpfunc, normalize_last=True, + normalize=False, zero_above=False) + + return len(pivots) + + +def _to_DM_ZZ_QQ(M): + # We have to test for _rep here because there are tests that otherwise fail + # with e.g. "AttributeError: 'SubspaceOnlyMatrix' object has no attribute + # '_rep'." There is almost certainly no value in such tests. The + # presumption seems to be that someone could create a new class by + # inheriting some of the Matrix classes and not the full set that is used + # by the standard Matrix class but if anyone tried that it would fail in + # many ways. + if not hasattr(M, '_rep'): + return None + + rep = M._rep + K = rep.domain + + if K.is_ZZ: + return rep + elif K.is_QQ: + try: + return rep.convert_to(ZZ) + except CoercionFailed: + return rep + else: + if not all(e.is_Rational for e in M): + return None + try: + return rep.convert_to(ZZ) + except CoercionFailed: + return rep.convert_to(QQ) + + +def _rref_dm(dM): + """Compute the reduced row echelon form of a DomainMatrix.""" + K = dM.domain + + if K.is_ZZ: + dM_rref, den, pivots = dM.rref_den(keep_domain=False) + dM_rref = dM_rref.to_field() / den + elif K.is_QQ: + dM_rref, pivots = dM.rref() + else: + assert False # pragma: no cover + + M_rref = dM_rref.to_Matrix() + + return M_rref, pivots + + +def _rref(M, iszerofunc=_iszero, simplify=False, pivots=True, + normalize_last=True): + """Return reduced row-echelon form of matrix and indices + of pivot vars. + + Parameters + ========== + + iszerofunc : Function + A function used for detecting whether an element can + act as a pivot. ``lambda x: x.is_zero`` is used by default. + + simplify : Function + A function used to simplify elements when looking for a pivot. + By default SymPy's ``simplify`` is used. + + pivots : True or False + If ``True``, a tuple containing the row-reduced matrix and a tuple + of pivot columns is returned. If ``False`` just the row-reduced + matrix is returned. + + normalize_last : True or False + If ``True``, no pivots are normalized to `1` until after all + entries above and below each pivot are zeroed. This means the row + reduction algorithm is fraction free until the very last step. + If ``False``, the naive row reduction procedure is used where + each pivot is normalized to be `1` before row operations are + used to zero above and below the pivot. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x + >>> m = Matrix([[1, 2], [x, 1 - 1/x]]) + >>> m.rref() + (Matrix([ + [1, 0], + [0, 1]]), (0, 1)) + >>> rref_matrix, rref_pivots = m.rref() + >>> rref_matrix + Matrix([ + [1, 0], + [0, 1]]) + >>> rref_pivots + (0, 1) + + ``iszerofunc`` can correct rounding errors in matrices with float + values. In the following example, calling ``rref()`` leads to + floating point errors, incorrectly row reducing the matrix. + ``iszerofunc= lambda x: abs(x) < 1e-9`` sets sufficiently small numbers + to zero, avoiding this error. + + >>> m = Matrix([[0.9, -0.1, -0.2, 0], [-0.8, 0.9, -0.4, 0], [-0.1, -0.8, 0.6, 0]]) + >>> m.rref() + (Matrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0]]), (0, 1, 2)) + >>> m.rref(iszerofunc=lambda x:abs(x)<1e-9) + (Matrix([ + [1, 0, -0.301369863013699, 0], + [0, 1, -0.712328767123288, 0], + [0, 0, 0, 0]]), (0, 1)) + + Notes + ===== + + The default value of ``normalize_last=True`` can provide significant + speedup to row reduction, especially on matrices with symbols. However, + if you depend on the form row reduction algorithm leaves entries + of the matrix, set ``normalize_last=False`` + """ + # Try to use DomainMatrix for ZZ or QQ + dM = _to_DM_ZZ_QQ(M) + + if dM is not None: + # Use DomainMatrix for ZZ or QQ + mat, pivot_cols = _rref_dm(dM) + else: + # Use the generic Matrix routine. + if isinstance(simplify, FunctionType): + simpfunc = simplify + else: + simpfunc = _simplify + + mat, pivot_cols, _ = _row_reduce(M, iszerofunc, simpfunc, + normalize_last, normalize=True, zero_above=True) + + if pivots: + return mat, pivot_cols + else: + return mat diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/repmatrix.py b/venv/lib/python3.10/site-packages/sympy/matrices/repmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..57f32fae34786f68f579fad7de38c9e3cf43e131 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/repmatrix.py @@ -0,0 +1,1034 @@ +from collections import defaultdict + +from operator import index as index_ + +from sympy.core.expr import Expr +from sympy.core.kind import Kind, NumberKind, UndefinedKind +from sympy.core.numbers import Integer, Rational +from sympy.core.sympify import _sympify, SympifyError +from sympy.core.singleton import S +from sympy.polys.domains import ZZ, QQ, GF, EXRAW +from sympy.polys.matrices import DomainMatrix +from sympy.polys.matrices.exceptions import DMNonInvertibleMatrixError +from sympy.polys.polyerrors import CoercionFailed, NotInvertible +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import is_sequence +from sympy.utilities.misc import filldedent, as_int + +from .exceptions import ShapeError, NonSquareMatrixError, NonInvertibleMatrixError +from .matrixbase import classof, MatrixBase +from .kind import MatrixKind + + +class RepMatrix(MatrixBase): + """Matrix implementation based on DomainMatrix as an internal representation. + + The RepMatrix class is a superclass for Matrix, ImmutableMatrix, + SparseMatrix and ImmutableSparseMatrix which are the main usable matrix + classes in SymPy. Most methods on this class are simply forwarded to + DomainMatrix. + """ + + # + # MatrixBase is the common superclass for all of the usable explicit matrix + # classes in SymPy. The idea is that MatrixBase is an abstract class though + # and that subclasses will implement the lower-level methods. + # + # RepMatrix is a subclass of MatrixBase that uses DomainMatrix as an + # internal representation and delegates lower-level methods to + # DomainMatrix. All of SymPy's standard explicit matrix classes subclass + # RepMatrix and so use DomainMatrix internally. + # + # A RepMatrix uses an internal DomainMatrix with the domain set to ZZ, QQ + # or EXRAW. The EXRAW domain is equivalent to the previous implementation + # of Matrix that used Expr for the elements. The ZZ and QQ domains are used + # when applicable just because they are compatible with the previous + # implementation but are much more efficient. Other domains such as QQ[x] + # are not used because they differ from Expr in some way (e.g. automatic + # expansion of powers and products). + # + + _rep: DomainMatrix + + def __eq__(self, other): + # Skip sympify for mutable matrices... + if not isinstance(other, RepMatrix): + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if not isinstance(other, RepMatrix): + return NotImplemented + + return self._rep.unify_eq(other._rep) + + def to_DM(self, domain=None, **kwargs): + """Convert to a :class:`~.DomainMatrix`. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2], [3, 4]]) + >>> M.to_DM() + DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ) + + The :meth:`DomainMatrix.to_Matrix` method can be used to convert back: + + >>> M.to_DM().to_Matrix() == M + True + + The domain can be given explicitly or otherwise it will be chosen by + :func:`construct_domain`. Any keyword arguments (besides ``domain``) + are passed to :func:`construct_domain`: + + >>> from sympy import QQ, symbols + >>> x = symbols('x') + >>> M = Matrix([[x, 1], [1, x]]) + >>> M + Matrix([ + [x, 1], + [1, x]]) + >>> M.to_DM().domain + ZZ[x] + >>> M.to_DM(field=True).domain + ZZ(x) + >>> M.to_DM(domain=QQ[x]).domain + QQ[x] + + See Also + ======== + + DomainMatrix + DomainMatrix.to_Matrix + DomainMatrix.convert_to + DomainMatrix.choose_domain + construct_domain + """ + if domain is not None: + if kwargs: + raise TypeError("Options cannot be used with domain parameter") + return self._rep.convert_to(domain) + + rep = self._rep + dom = rep.domain + + # If the internal DomainMatrix is already ZZ or QQ then we can maybe + # bypass calling construct_domain or performing any conversions. Some + # kwargs might affect this though e.g. field=True (not sure if there + # are others). + if not kwargs: + if dom.is_ZZ: + return rep.copy() + elif dom.is_QQ: + # All elements might be integers + try: + return rep.convert_to(ZZ) + except CoercionFailed: + pass + return rep.copy() + + # Let construct_domain choose a domain + rep_dom = rep.choose_domain(**kwargs) + + # XXX: There should be an option to construct_domain to choose EXRAW + # instead of EX. At least converting to EX does not initially trigger + # EX.simplify which is what we want here but should probably be + # considered a bug in EX. Perhaps also this could be handled in + # DomainMatrix.choose_domain rather than here... + if rep_dom.domain.is_EX: + rep_dom = rep_dom.convert_to(EXRAW) + + return rep_dom + + @classmethod + def _unify_element_sympy(cls, rep, element): + domain = rep.domain + element = _sympify(element) + + if domain != EXRAW: + # The domain can only be ZZ, QQ or EXRAW + if element.is_Integer: + new_domain = domain + elif element.is_Rational: + new_domain = QQ + else: + new_domain = EXRAW + + # XXX: This converts the domain for all elements in the matrix + # which can be slow. This happens e.g. if __setitem__ changes one + # element to something that does not fit in the domain + if new_domain != domain: + rep = rep.convert_to(new_domain) + domain = new_domain + + if domain != EXRAW: + element = new_domain.from_sympy(element) + + if domain == EXRAW and not isinstance(element, Expr): + sympy_deprecation_warning( + """ + non-Expr objects in a Matrix is deprecated. Matrix represents + a mathematical matrix. To represent a container of non-numeric + entities, Use a list of lists, TableForm, NumPy array, or some + other data structure instead. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-non-expr-in-matrix", + stacklevel=4, + ) + + return rep, element + + @classmethod + def _dod_to_DomainMatrix(cls, rows, cols, dod, types): + + if not all(issubclass(typ, Expr) for typ in types): + sympy_deprecation_warning( + """ + non-Expr objects in a Matrix is deprecated. Matrix represents + a mathematical matrix. To represent a container of non-numeric + entities, Use a list of lists, TableForm, NumPy array, or some + other data structure instead. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-non-expr-in-matrix", + stacklevel=6, + ) + + rep = DomainMatrix(dod, (rows, cols), EXRAW) + + if all(issubclass(typ, Rational) for typ in types): + if all(issubclass(typ, Integer) for typ in types): + rep = rep.convert_to(ZZ) + else: + rep = rep.convert_to(QQ) + + return rep + + @classmethod + def _flat_list_to_DomainMatrix(cls, rows, cols, flat_list): + + elements_dod = defaultdict(dict) + for n, element in enumerate(flat_list): + if element != 0: + i, j = divmod(n, cols) + elements_dod[i][j] = element + + types = set(map(type, flat_list)) + + rep = cls._dod_to_DomainMatrix(rows, cols, elements_dod, types) + return rep + + @classmethod + def _smat_to_DomainMatrix(cls, rows, cols, smat): + + elements_dod = defaultdict(dict) + for (i, j), element in smat.items(): + if element != 0: + elements_dod[i][j] = element + + types = set(map(type, smat.values())) + + rep = cls._dod_to_DomainMatrix(rows, cols, elements_dod, types) + return rep + + def flat(self): + return self._rep.to_sympy().to_list_flat() + + def _eval_tolist(self): + return self._rep.to_sympy().to_list() + + def _eval_todok(self): + return self._rep.to_sympy().to_dok() + + @classmethod + def _eval_from_dok(cls, rows, cols, dok): + return cls._fromrep(cls._smat_to_DomainMatrix(rows, cols, dok)) + + def _eval_values(self): + return list(self._eval_iter_values()) + + def _eval_iter_values(self): + rep = self._rep + K = rep.domain + values = rep.iter_values() + if not K.is_EXRAW: + values = map(K.to_sympy, values) + return values + + def _eval_iter_items(self): + rep = self._rep + K = rep.domain + to_sympy = K.to_sympy + items = rep.iter_items() + if not K.is_EXRAW: + items = ((i, to_sympy(v)) for i, v in items) + return items + + def copy(self): + return self._fromrep(self._rep.copy()) + + @property + def kind(self) -> MatrixKind: + domain = self._rep.domain + element_kind: Kind + if domain in (ZZ, QQ): + element_kind = NumberKind + elif domain == EXRAW: + kinds = {e.kind for e in self.values()} + if len(kinds) == 1: + [element_kind] = kinds + else: + element_kind = UndefinedKind + else: # pragma: no cover + raise RuntimeError("Domain should only be ZZ, QQ or EXRAW") + return MatrixKind(element_kind) + + def _eval_has(self, *patterns): + # if the matrix has any zeros, see if S.Zero + # has the pattern. If _smat is full length, + # the matrix has no zeros. + zhas = False + dok = self.todok() + if len(dok) != self.rows*self.cols: + zhas = S.Zero.has(*patterns) + return zhas or any(value.has(*patterns) for value in dok.values()) + + def _eval_is_Identity(self): + if not all(self[i, i] == 1 for i in range(self.rows)): + return False + return len(self.todok()) == self.rows + + def _eval_is_symmetric(self, simpfunc): + diff = (self - self.T).applyfunc(simpfunc) + return len(diff.values()) == 0 + + def _eval_transpose(self): + """Returns the transposed SparseMatrix of this SparseMatrix. + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> a = SparseMatrix(((1, 2), (3, 4))) + >>> a + Matrix([ + [1, 2], + [3, 4]]) + >>> a.T + Matrix([ + [1, 3], + [2, 4]]) + """ + return self._fromrep(self._rep.transpose()) + + def _eval_col_join(self, other): + return self._fromrep(self._rep.vstack(other._rep)) + + def _eval_row_join(self, other): + return self._fromrep(self._rep.hstack(other._rep)) + + def _eval_extract(self, rowsList, colsList): + return self._fromrep(self._rep.extract(rowsList, colsList)) + + def __getitem__(self, key): + return _getitem_RepMatrix(self, key) + + @classmethod + def _eval_zeros(cls, rows, cols): + rep = DomainMatrix.zeros((rows, cols), ZZ) + return cls._fromrep(rep) + + @classmethod + def _eval_eye(cls, rows, cols): + rep = DomainMatrix.eye((rows, cols), ZZ) + return cls._fromrep(rep) + + def _eval_add(self, other): + return classof(self, other)._fromrep(self._rep + other._rep) + + def _eval_matrix_mul(self, other): + return classof(self, other)._fromrep(self._rep * other._rep) + + def _eval_matrix_mul_elementwise(self, other): + selfrep, otherrep = self._rep.unify(other._rep) + newrep = selfrep.mul_elementwise(otherrep) + return classof(self, other)._fromrep(newrep) + + def _eval_scalar_mul(self, other): + rep, other = self._unify_element_sympy(self._rep, other) + return self._fromrep(rep.scalarmul(other)) + + def _eval_scalar_rmul(self, other): + rep, other = self._unify_element_sympy(self._rep, other) + return self._fromrep(rep.rscalarmul(other)) + + def _eval_Abs(self): + return self._fromrep(self._rep.applyfunc(abs)) + + def _eval_conjugate(self): + rep = self._rep + domain = rep.domain + if domain in (ZZ, QQ): + return self.copy() + else: + return self._fromrep(rep.applyfunc(lambda e: e.conjugate())) + + def equals(self, other, failing_expression=False): + """Applies ``equals`` to corresponding elements of the matrices, + trying to prove that the elements are equivalent, returning True + if they are, False if any pair is not, and None (or the first + failing expression if failing_expression is True) if it cannot + be decided if the expressions are equivalent or not. This is, in + general, an expensive operation. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.abc import x + >>> A = Matrix([x*(x - 1), 0]) + >>> B = Matrix([x**2 - x, 0]) + >>> A == B + False + >>> A.simplify() == B.simplify() + True + >>> A.equals(B) + True + >>> A.equals(2) + False + + See Also + ======== + sympy.core.expr.Expr.equals + """ + if self.shape != getattr(other, 'shape', None): + return False + + rv = True + for i in range(self.rows): + for j in range(self.cols): + ans = self[i, j].equals(other[i, j], failing_expression) + if ans is False: + return False + elif ans is not True and rv is True: + rv = ans + return rv + + def inv_mod(M, m): + r""" + Returns the inverse of the integer matrix ``M`` modulo ``m``. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix(2, 2, [1, 2, 3, 4]) + >>> A.inv_mod(5) + Matrix([ + [3, 1], + [4, 2]]) + >>> A.inv_mod(3) + Matrix([ + [1, 1], + [0, 1]]) + + """ + + if not M.is_square: + raise NonSquareMatrixError() + + try: + m = as_int(m) + except ValueError: + raise TypeError("inv_mod: modulus m must be an integer") + + K = GF(m, symmetric=False) + + try: + dM = M.to_DM(K) + except CoercionFailed: + raise ValueError("inv_mod: matrix entries must be integers") + + if K.is_Field: + try: + dMi = dM.inv() + except DMNonInvertibleMatrixError as exc: + msg = f'Matrix is not invertible (mod {m})' + raise NonInvertibleMatrixError(msg) from exc + else: + dMadj, det = dM.adj_det() + try: + detinv = 1 / det + except NotInvertible: + msg = f'Matrix is not invertible (mod {m})' + raise NonInvertibleMatrixError(msg) + dMi = dMadj * detinv + + return dMi.to_Matrix() + + def lll(self, delta=0.75): + """LLL-reduced basis for the rowspace of a matrix of integers. + + Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm. + + The implementation is provided by :class:`~DomainMatrix`. See + :meth:`~DomainMatrix.lll` for more details. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 0, 0, 0, -20160], + ... [0, 1, 0, 0, 33768], + ... [0, 0, 1, 0, 39578], + ... [0, 0, 0, 1, 47757]]) + >>> M.lll() + Matrix([ + [ 10, -3, -2, 8, -4], + [ 3, -9, 8, 1, -11], + [ -3, 13, -9, -3, -9], + [-12, -7, -11, 9, -1]]) + + See Also + ======== + + lll_transform + sympy.polys.matrices.domainmatrix.DomainMatrix.lll + """ + delta = QQ.from_sympy(_sympify(delta)) + dM = self._rep.convert_to(ZZ) + basis = dM.lll(delta=delta) + return self._fromrep(basis) + + def lll_transform(self, delta=0.75): + """LLL-reduced basis and transformation matrix. + + Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm. + + The implementation is provided by :class:`~DomainMatrix`. See + :meth:`~DomainMatrix.lll_transform` for more details. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 0, 0, 0, -20160], + ... [0, 1, 0, 0, 33768], + ... [0, 0, 1, 0, 39578], + ... [0, 0, 0, 1, 47757]]) + >>> B, T = M.lll_transform() + >>> B + Matrix([ + [ 10, -3, -2, 8, -4], + [ 3, -9, 8, 1, -11], + [ -3, 13, -9, -3, -9], + [-12, -7, -11, 9, -1]]) + >>> T + Matrix([ + [ 10, -3, -2, 8], + [ 3, -9, 8, 1], + [ -3, 13, -9, -3], + [-12, -7, -11, 9]]) + + The transformation matrix maps the original basis to the LLL-reduced + basis: + + >>> T * M == B + True + + See Also + ======== + + lll + sympy.polys.matrices.domainmatrix.DomainMatrix.lll_transform + """ + delta = QQ.from_sympy(_sympify(delta)) + dM = self._rep.convert_to(ZZ) + basis, transform = dM.lll_transform(delta=delta) + B = self._fromrep(basis) + T = self._fromrep(transform) + return B, T + + +class MutableRepMatrix(RepMatrix): + """Mutable matrix based on DomainMatrix as the internal representation""" + + # + # MutableRepMatrix is a subclass of RepMatrix that adds/overrides methods + # to make the instances mutable. MutableRepMatrix is a superclass for both + # MutableDenseMatrix and MutableSparseMatrix. + # + + is_zero = False + + def __new__(cls, *args, **kwargs): + return cls._new(*args, **kwargs) + + @classmethod + def _new(cls, *args, copy=True, **kwargs): + if copy is False: + # The input was rows, cols, [list]. + # It should be used directly without creating a copy. + if len(args) != 3: + raise TypeError("'copy=False' requires a matrix be initialized as rows,cols,[list]") + rows, cols, flat_list = args + else: + rows, cols, flat_list = cls._handle_creation_inputs(*args, **kwargs) + flat_list = list(flat_list) # create a shallow copy + + rep = cls._flat_list_to_DomainMatrix(rows, cols, flat_list) + + return cls._fromrep(rep) + + @classmethod + def _fromrep(cls, rep): + obj = super().__new__(cls) + obj.rows, obj.cols = rep.shape + obj._rep = rep + return obj + + def copy(self): + return self._fromrep(self._rep.copy()) + + def as_mutable(self): + return self.copy() + + def __setitem__(self, key, value): + """ + + Examples + ======== + + >>> from sympy import Matrix, I, zeros, ones + >>> m = Matrix(((1, 2+I), (3, 4))) + >>> m + Matrix([ + [1, 2 + I], + [3, 4]]) + >>> m[1, 0] = 9 + >>> m + Matrix([ + [1, 2 + I], + [9, 4]]) + >>> m[1, 0] = [[0, 1]] + + To replace row r you assign to position r*m where m + is the number of columns: + + >>> M = zeros(4) + >>> m = M.cols + >>> M[3*m] = ones(1, m)*2; M + Matrix([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [2, 2, 2, 2]]) + + And to replace column c you can assign to position c: + + >>> M[2] = ones(m, 1)*4; M + Matrix([ + [0, 0, 4, 0], + [0, 0, 4, 0], + [0, 0, 4, 0], + [2, 2, 4, 2]]) + """ + rv = self._setitem(key, value) + if rv is not None: + i, j, value = rv + self._rep, value = self._unify_element_sympy(self._rep, value) + self._rep.rep.setitem(i, j, value) + + def _eval_col_del(self, col): + self._rep = DomainMatrix.hstack(self._rep[:,:col], self._rep[:,col+1:]) + self.cols -= 1 + + def _eval_row_del(self, row): + self._rep = DomainMatrix.vstack(self._rep[:row,:], self._rep[row+1:, :]) + self.rows -= 1 + + def _eval_col_insert(self, col, other): + other = self._new(other) + return self.hstack(self[:,:col], other, self[:,col:]) + + def _eval_row_insert(self, row, other): + other = self._new(other) + return self.vstack(self[:row,:], other, self[row:,:]) + + def col_op(self, j, f): + """In-place operation on col j using two-arg functor whose args are + interpreted as (self[i, j], i). + + Examples + ======== + + >>> from sympy import eye + >>> M = eye(3) + >>> M.col_op(1, lambda v, i: v + 2*M[i, 0]); M + Matrix([ + [1, 2, 0], + [0, 1, 0], + [0, 0, 1]]) + + See Also + ======== + col + row_op + """ + for i in range(self.rows): + self[i, j] = f(self[i, j], i) + + def col_swap(self, i, j): + """Swap the two given columns of the matrix in-place. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 0], [1, 0]]) + >>> M + Matrix([ + [1, 0], + [1, 0]]) + >>> M.col_swap(0, 1) + >>> M + Matrix([ + [0, 1], + [0, 1]]) + + See Also + ======== + + col + row_swap + """ + for k in range(0, self.rows): + self[k, i], self[k, j] = self[k, j], self[k, i] + + def row_op(self, i, f): + """In-place operation on row ``i`` using two-arg functor whose args are + interpreted as ``(self[i, j], j)``. + + Examples + ======== + + >>> from sympy import eye + >>> M = eye(3) + >>> M.row_op(1, lambda v, j: v + 2*M[0, j]); M + Matrix([ + [1, 0, 0], + [2, 1, 0], + [0, 0, 1]]) + + See Also + ======== + row + zip_row_op + col_op + + """ + for j in range(self.cols): + self[i, j] = f(self[i, j], j) + + #The next three methods give direct support for the most common row operations inplace. + def row_mult(self,i,factor): + """Multiply the given row by the given factor in-place. + + Examples + ======== + + >>> from sympy import eye + >>> M = eye(3) + >>> M.row_mult(1,7); M + Matrix([ + [1, 0, 0], + [0, 7, 0], + [0, 0, 1]]) + + """ + for j in range(self.cols): + self[i,j] *= factor + + def row_add(self,s,t,k): + """Add k times row s (source) to row t (target) in place. + + Examples + ======== + + >>> from sympy import eye + >>> M = eye(3) + >>> M.row_add(0, 2,3); M + Matrix([ + [1, 0, 0], + [0, 1, 0], + [3, 0, 1]]) + """ + + for j in range(self.cols): + self[t,j] += k*self[s,j] + + def row_swap(self, i, j): + """Swap the two given rows of the matrix in-place. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[0, 1], [1, 0]]) + >>> M + Matrix([ + [0, 1], + [1, 0]]) + >>> M.row_swap(0, 1) + >>> M + Matrix([ + [1, 0], + [0, 1]]) + + See Also + ======== + + row + col_swap + """ + for k in range(0, self.cols): + self[i, k], self[j, k] = self[j, k], self[i, k] + + def zip_row_op(self, i, k, f): + """In-place operation on row ``i`` using two-arg functor whose args are + interpreted as ``(self[i, j], self[k, j])``. + + Examples + ======== + + >>> from sympy import eye + >>> M = eye(3) + >>> M.zip_row_op(1, 0, lambda v, u: v + 2*u); M + Matrix([ + [1, 0, 0], + [2, 1, 0], + [0, 0, 1]]) + + See Also + ======== + row + row_op + col_op + + """ + for j in range(self.cols): + self[i, j] = f(self[i, j], self[k, j]) + + def copyin_list(self, key, value): + """Copy in elements from a list. + + Parameters + ========== + + key : slice + The section of this matrix to replace. + value : iterable + The iterable to copy values from. + + Examples + ======== + + >>> from sympy import eye + >>> I = eye(3) + >>> I[:2, 0] = [1, 2] # col + >>> I + Matrix([ + [1, 0, 0], + [2, 1, 0], + [0, 0, 1]]) + >>> I[1, :2] = [[3, 4]] + >>> I + Matrix([ + [1, 0, 0], + [3, 4, 0], + [0, 0, 1]]) + + See Also + ======== + + copyin_matrix + """ + if not is_sequence(value): + raise TypeError("`value` must be an ordered iterable, not %s." % type(value)) + return self.copyin_matrix(key, type(self)(value)) + + def copyin_matrix(self, key, value): + """Copy in values from a matrix into the given bounds. + + Parameters + ========== + + key : slice + The section of this matrix to replace. + value : Matrix + The matrix to copy values from. + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> M = Matrix([[0, 1], [2, 3], [4, 5]]) + >>> I = eye(3) + >>> I[:3, :2] = M + >>> I + Matrix([ + [0, 1, 0], + [2, 3, 0], + [4, 5, 1]]) + >>> I[0, 1] = M + >>> I + Matrix([ + [0, 0, 1], + [2, 2, 3], + [4, 4, 5]]) + + See Also + ======== + + copyin_list + """ + rlo, rhi, clo, chi = self.key2bounds(key) + shape = value.shape + dr, dc = rhi - rlo, chi - clo + if shape != (dr, dc): + raise ShapeError(filldedent("The Matrix `value` doesn't have the " + "same dimensions " + "as the in sub-Matrix given by `key`.")) + + for i in range(value.rows): + for j in range(value.cols): + self[i + rlo, j + clo] = value[i, j] + + def fill(self, value): + """Fill self with the given value. + + Notes + ===== + + Unless many values are going to be deleted (i.e. set to zero) + this will create a matrix that is slower than a dense matrix in + operations. + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> M = SparseMatrix.zeros(3); M + Matrix([ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0]]) + >>> M.fill(1); M + Matrix([ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1]]) + + See Also + ======== + + zeros + ones + """ + value = _sympify(value) + if not value: + self._rep = DomainMatrix.zeros(self.shape, EXRAW) + else: + elements_dod = {i: dict.fromkeys(range(self.cols), value) for i in range(self.rows)} + self._rep = DomainMatrix(elements_dod, self.shape, EXRAW) + + +def _getitem_RepMatrix(self, key): + """Return portion of self defined by key. If the key involves a slice + then a list will be returned (if key is a single slice) or a matrix + (if key was a tuple involving a slice). + + Examples + ======== + + >>> from sympy import Matrix, I + >>> m = Matrix([ + ... [1, 2 + I], + ... [3, 4 ]]) + + If the key is a tuple that does not involve a slice then that element + is returned: + + >>> m[1, 0] + 3 + + When a tuple key involves a slice, a matrix is returned. Here, the + first column is selected (all rows, column 0): + + >>> m[:, 0] + Matrix([ + [1], + [3]]) + + If the slice is not a tuple then it selects from the underlying + list of elements that are arranged in row order and a list is + returned if a slice is involved: + + >>> m[0] + 1 + >>> m[::2] + [1, 3] + """ + if isinstance(key, tuple): + i, j = key + try: + return self._rep.getitem_sympy(index_(i), index_(j)) + except (TypeError, IndexError): + if (isinstance(i, Expr) and not i.is_number) or (isinstance(j, Expr) and not j.is_number): + if ((j < 0) is True) or ((j >= self.shape[1]) is True) or\ + ((i < 0) is True) or ((i >= self.shape[0]) is True): + raise ValueError("index out of boundary") + from sympy.matrices.expressions.matexpr import MatrixElement + return MatrixElement(self, i, j) + + if isinstance(i, slice): + i = range(self.rows)[i] + elif is_sequence(i): + pass + else: + i = [i] + if isinstance(j, slice): + j = range(self.cols)[j] + elif is_sequence(j): + pass + else: + j = [j] + return self.extract(i, j) + + else: + # Index/slice like a flattened list + rows, cols = self.shape + + # Raise the appropriate exception: + if not rows * cols: + return [][key] + + rep = self._rep.rep + domain = rep.domain + is_slice = isinstance(key, slice) + + if is_slice: + values = [rep.getitem(*divmod(n, cols)) for n in range(rows * cols)[key]] + else: + values = [rep.getitem(*divmod(index_(key), cols))] + + if domain != EXRAW: + to_sympy = domain.to_sympy + values = [to_sympy(val) for val in values] + + if is_slice: + return values + else: + return values[0] diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/solvers.py b/venv/lib/python3.10/site-packages/sympy/matrices/solvers.py new file mode 100644 index 0000000000000000000000000000000000000000..1fba990df80dcf46304ecb1412f5382f60948c51 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/solvers.py @@ -0,0 +1,942 @@ +from sympy.core.function import expand_mul +from sympy.core.symbol import Dummy, uniquely_named_symbol, symbols +from sympy.utilities.iterables import numbered_symbols + +from .exceptions import ShapeError, NonSquareMatrixError, NonInvertibleMatrixError +from .eigen import _fuzzy_positive_definite +from .utilities import _get_intermediate_simp, _iszero + + +def _diagonal_solve(M, rhs): + """Solves ``Ax = B`` efficiently, where A is a diagonal Matrix, + with non-zero diagonal entries. + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> A = eye(2)*2 + >>> B = Matrix([[1, 2], [3, 4]]) + >>> A.diagonal_solve(B) == B/2 + True + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.lower_triangular_solve + sympy.matrices.dense.DenseMatrix.upper_triangular_solve + gauss_jordan_solve + cholesky_solve + LDLsolve + LUsolve + QRsolve + pinv_solve + cramer_solve + """ + + if not M.is_diagonal(): + raise TypeError("Matrix should be diagonal") + if rhs.rows != M.rows: + raise TypeError("Size mismatch") + + return M._new( + rhs.rows, rhs.cols, lambda i, j: rhs[i, j] / M[i, i]) + + +def _lower_triangular_solve(M, rhs): + """Solves ``Ax = B``, where A is a lower triangular matrix. + + See Also + ======== + + upper_triangular_solve + gauss_jordan_solve + cholesky_solve + diagonal_solve + LDLsolve + LUsolve + QRsolve + pinv_solve + cramer_solve + """ + + from .dense import MutableDenseMatrix + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + if rhs.rows != M.rows: + raise ShapeError("Matrices size mismatch.") + if not M.is_lower: + raise ValueError("Matrix must be lower triangular.") + + dps = _get_intermediate_simp() + X = MutableDenseMatrix.zeros(M.rows, rhs.cols) + + for j in range(rhs.cols): + for i in range(M.rows): + if M[i, i] == 0: + raise TypeError("Matrix must be non-singular.") + + X[i, j] = dps((rhs[i, j] - sum(M[i, k]*X[k, j] + for k in range(i))) / M[i, i]) + + return M._new(X) + +def _lower_triangular_solve_sparse(M, rhs): + """Solves ``Ax = B``, where A is a lower triangular matrix. + + See Also + ======== + + upper_triangular_solve + gauss_jordan_solve + cholesky_solve + diagonal_solve + LDLsolve + LUsolve + QRsolve + pinv_solve + cramer_solve + """ + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + if rhs.rows != M.rows: + raise ShapeError("Matrices size mismatch.") + if not M.is_lower: + raise ValueError("Matrix must be lower triangular.") + + dps = _get_intermediate_simp() + rows = [[] for i in range(M.rows)] + + for i, j, v in M.row_list(): + if i > j: + rows[i].append((j, v)) + + X = rhs.as_mutable() + + for j in range(rhs.cols): + for i in range(rhs.rows): + for u, v in rows[i]: + X[i, j] -= v*X[u, j] + + X[i, j] = dps(X[i, j] / M[i, i]) + + return M._new(X) + + +def _upper_triangular_solve(M, rhs): + """Solves ``Ax = B``, where A is an upper triangular matrix. + + See Also + ======== + + lower_triangular_solve + gauss_jordan_solve + cholesky_solve + diagonal_solve + LDLsolve + LUsolve + QRsolve + pinv_solve + cramer_solve + """ + + from .dense import MutableDenseMatrix + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + if rhs.rows != M.rows: + raise ShapeError("Matrix size mismatch.") + if not M.is_upper: + raise TypeError("Matrix is not upper triangular.") + + dps = _get_intermediate_simp() + X = MutableDenseMatrix.zeros(M.rows, rhs.cols) + + for j in range(rhs.cols): + for i in reversed(range(M.rows)): + if M[i, i] == 0: + raise ValueError("Matrix must be non-singular.") + + X[i, j] = dps((rhs[i, j] - sum(M[i, k]*X[k, j] + for k in range(i + 1, M.rows))) / M[i, i]) + + return M._new(X) + +def _upper_triangular_solve_sparse(M, rhs): + """Solves ``Ax = B``, where A is an upper triangular matrix. + + See Also + ======== + + lower_triangular_solve + gauss_jordan_solve + cholesky_solve + diagonal_solve + LDLsolve + LUsolve + QRsolve + pinv_solve + cramer_solve + """ + + if not M.is_square: + raise NonSquareMatrixError("Matrix must be square.") + if rhs.rows != M.rows: + raise ShapeError("Matrix size mismatch.") + if not M.is_upper: + raise TypeError("Matrix is not upper triangular.") + + dps = _get_intermediate_simp() + rows = [[] for i in range(M.rows)] + + for i, j, v in M.row_list(): + if i < j: + rows[i].append((j, v)) + + X = rhs.as_mutable() + + for j in range(rhs.cols): + for i in reversed(range(rhs.rows)): + for u, v in reversed(rows[i]): + X[i, j] -= v*X[u, j] + + X[i, j] = dps(X[i, j] / M[i, i]) + + return M._new(X) + + +def _cholesky_solve(M, rhs): + """Solves ``Ax = B`` using Cholesky decomposition, + for a general square non-singular matrix. + For a non-square matrix with rows > cols, + the least squares solution is returned. + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.lower_triangular_solve + sympy.matrices.dense.DenseMatrix.upper_triangular_solve + gauss_jordan_solve + diagonal_solve + LDLsolve + LUsolve + QRsolve + pinv_solve + cramer_solve + """ + + if M.rows < M.cols: + raise NotImplementedError( + 'Under-determined System. Try M.gauss_jordan_solve(rhs)') + + hermitian = True + reform = False + + if M.is_symmetric(): + hermitian = False + elif not M.is_hermitian: + reform = True + + if reform or _fuzzy_positive_definite(M) is False: + H = M.H + M = H.multiply(M) + rhs = H.multiply(rhs) + hermitian = not M.is_symmetric() + + L = M.cholesky(hermitian=hermitian) + Y = L.lower_triangular_solve(rhs) + + if hermitian: + return (L.H).upper_triangular_solve(Y) + else: + return (L.T).upper_triangular_solve(Y) + + +def _LDLsolve(M, rhs): + """Solves ``Ax = B`` using LDL decomposition, + for a general square and non-singular matrix. + + For a non-square matrix with rows > cols, + the least squares solution is returned. + + Examples + ======== + + >>> from sympy import Matrix, eye + >>> A = eye(2)*2 + >>> B = Matrix([[1, 2], [3, 4]]) + >>> A.LDLsolve(B) == B/2 + True + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.LDLdecomposition + sympy.matrices.dense.DenseMatrix.lower_triangular_solve + sympy.matrices.dense.DenseMatrix.upper_triangular_solve + gauss_jordan_solve + cholesky_solve + diagonal_solve + LUsolve + QRsolve + pinv_solve + cramer_solve + """ + + if M.rows < M.cols: + raise NotImplementedError( + 'Under-determined System. Try M.gauss_jordan_solve(rhs)') + + hermitian = True + reform = False + + if M.is_symmetric(): + hermitian = False + elif not M.is_hermitian: + reform = True + + if reform or _fuzzy_positive_definite(M) is False: + H = M.H + M = H.multiply(M) + rhs = H.multiply(rhs) + hermitian = not M.is_symmetric() + + L, D = M.LDLdecomposition(hermitian=hermitian) + Y = L.lower_triangular_solve(rhs) + Z = D.diagonal_solve(Y) + + if hermitian: + return (L.H).upper_triangular_solve(Z) + else: + return (L.T).upper_triangular_solve(Z) + + +def _LUsolve(M, rhs, iszerofunc=_iszero): + """Solve the linear system ``Ax = rhs`` for ``x`` where ``A = M``. + + This is for symbolic matrices, for real or complex ones use + mpmath.lu_solve or mpmath.qr_solve. + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.lower_triangular_solve + sympy.matrices.dense.DenseMatrix.upper_triangular_solve + gauss_jordan_solve + cholesky_solve + diagonal_solve + LDLsolve + QRsolve + pinv_solve + LUdecomposition + cramer_solve + """ + + if rhs.rows != M.rows: + raise ShapeError( + "``M`` and ``rhs`` must have the same number of rows.") + + m = M.rows + n = M.cols + + if m < n: + raise NotImplementedError("Underdetermined systems not supported.") + + try: + A, perm = M.LUdecomposition_Simple( + iszerofunc=iszerofunc, rankcheck=True) + except ValueError: + raise NonInvertibleMatrixError("Matrix det == 0; not invertible.") + + dps = _get_intermediate_simp() + b = rhs.permute_rows(perm).as_mutable() + + # forward substitution, all diag entries are scaled to 1 + for i in range(m): + for j in range(min(i, n)): + scale = A[i, j] + b.zip_row_op(i, j, lambda x, y: dps(x - scale * y)) + + # consistency check for overdetermined systems + if m > n: + for i in range(n, m): + for j in range(b.cols): + if not iszerofunc(b[i, j]): + raise ValueError("The system is inconsistent.") + + b = b[0:n, :] # truncate zero rows if consistent + + # backward substitution + for i in range(n - 1, -1, -1): + for j in range(i + 1, n): + scale = A[i, j] + b.zip_row_op(i, j, lambda x, y: dps(x - scale * y)) + + scale = A[i, i] + b.row_op(i, lambda x, _: dps(scale**-1 * x)) + + return rhs.__class__(b) + + +def _QRsolve(M, b): + """Solve the linear system ``Ax = b``. + + ``M`` is the matrix ``A``, the method argument is the vector + ``b``. The method returns the solution vector ``x``. If ``b`` is a + matrix, the system is solved for each column of ``b`` and the + return value is a matrix of the same shape as ``b``. + + This method is slower (approximately by a factor of 2) but + more stable for floating-point arithmetic than the LUsolve method. + However, LUsolve usually uses an exact arithmetic, so you do not need + to use QRsolve. + + This is mainly for educational purposes and symbolic matrices, for real + (or complex) matrices use mpmath.qr_solve. + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.lower_triangular_solve + sympy.matrices.dense.DenseMatrix.upper_triangular_solve + gauss_jordan_solve + cholesky_solve + diagonal_solve + LDLsolve + LUsolve + pinv_solve + QRdecomposition + cramer_solve + """ + + dps = _get_intermediate_simp(expand_mul, expand_mul) + Q, R = M.QRdecomposition() + y = Q.T * b + + # back substitution to solve R*x = y: + # We build up the result "backwards" in the vector 'x' and reverse it + # only in the end. + x = [] + n = R.rows + + for j in range(n - 1, -1, -1): + tmp = y[j, :] + + for k in range(j + 1, n): + tmp -= R[j, k] * x[n - 1 - k] + + tmp = dps(tmp) + + x.append(tmp / R[j, j]) + + return M.vstack(*x[::-1]) + + +def _gauss_jordan_solve(M, B, freevar=False): + """ + Solves ``Ax = B`` using Gauss Jordan elimination. + + There may be zero, one, or infinite solutions. If one solution + exists, it will be returned. If infinite solutions exist, it will + be returned parametrically. If no solutions exist, It will throw + ValueError. + + Parameters + ========== + + B : Matrix + The right hand side of the equation to be solved for. Must have + the same number of rows as matrix A. + + freevar : boolean, optional + Flag, when set to `True` will return the indices of the free + variables in the solutions (column Matrix), for a system that is + undetermined (e.g. A has more columns than rows), for which + infinite solutions are possible, in terms of arbitrary + values of free variables. Default `False`. + + Returns + ======= + + x : Matrix + The matrix that will satisfy ``Ax = B``. Will have as many rows as + matrix A has columns, and as many columns as matrix B. + + params : Matrix + If the system is underdetermined (e.g. A has more columns than + rows), infinite solutions are possible, in terms of arbitrary + parameters. These arbitrary parameters are returned as params + Matrix. + + free_var_index : List, optional + If the system is underdetermined (e.g. A has more columns than + rows), infinite solutions are possible, in terms of arbitrary + values of free variables. Then the indices of the free variables + in the solutions (column Matrix) are returned by free_var_index, + if the flag `freevar` is set to `True`. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[1, 2, 1, 1], [1, 2, 2, -1], [2, 4, 0, 6]]) + >>> B = Matrix([7, 12, 4]) + >>> sol, params = A.gauss_jordan_solve(B) + >>> sol + Matrix([ + [-2*tau0 - 3*tau1 + 2], + [ tau0], + [ 2*tau1 + 5], + [ tau1]]) + >>> params + Matrix([ + [tau0], + [tau1]]) + >>> taus_zeroes = { tau:0 for tau in params } + >>> sol_unique = sol.xreplace(taus_zeroes) + >>> sol_unique + Matrix([ + [2], + [0], + [5], + [0]]) + + + >>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) + >>> B = Matrix([3, 6, 9]) + >>> sol, params = A.gauss_jordan_solve(B) + >>> sol + Matrix([ + [-1], + [ 2], + [ 0]]) + >>> params + Matrix(0, 1, []) + + >>> A = Matrix([[2, -7], [-1, 4]]) + >>> B = Matrix([[-21, 3], [12, -2]]) + >>> sol, params = A.gauss_jordan_solve(B) + >>> sol + Matrix([ + [0, -2], + [3, -1]]) + >>> params + Matrix(0, 2, []) + + + >>> from sympy import Matrix + >>> A = Matrix([[1, 2, 1, 1], [1, 2, 2, -1], [2, 4, 0, 6]]) + >>> B = Matrix([7, 12, 4]) + >>> sol, params, freevars = A.gauss_jordan_solve(B, freevar=True) + >>> sol + Matrix([ + [-2*tau0 - 3*tau1 + 2], + [ tau0], + [ 2*tau1 + 5], + [ tau1]]) + >>> params + Matrix([ + [tau0], + [tau1]]) + >>> freevars + [1, 3] + + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.lower_triangular_solve + sympy.matrices.dense.DenseMatrix.upper_triangular_solve + cholesky_solve + diagonal_solve + LDLsolve + LUsolve + QRsolve + pinv + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gaussian_elimination + + """ + + from sympy.matrices import Matrix, zeros + + cls = M.__class__ + aug = M.hstack(M.copy(), B.copy()) + B_cols = B.cols + row, col = aug[:, :-B_cols].shape + + # solve by reduced row echelon form + A, pivots = aug.rref(simplify=True) + A, v = A[:, :-B_cols], A[:, -B_cols:] + pivots = list(filter(lambda p: p < col, pivots)) + rank = len(pivots) + + # Get index of free symbols (free parameters) + # non-pivots columns are free variables + free_var_index = [c for c in range(A.cols) if c not in pivots] + + # Bring to block form + permutation = Matrix(pivots + free_var_index).T + + # check for existence of solutions + # rank of aug Matrix should be equal to rank of coefficient matrix + if not v[rank:, :].is_zero_matrix: + raise ValueError("Linear system has no solution") + + # Free parameters + # what are current unnumbered free symbol names? + name = uniquely_named_symbol('tau', [aug], + compare=lambda i: str(i).rstrip('1234567890'), + modify=lambda s: '_' + s).name + gen = numbered_symbols(name) + tau = Matrix([next(gen) for k in range((col - rank)*B_cols)]).reshape( + col - rank, B_cols) + + # Full parametric solution + V = A[:rank, free_var_index] + vt = v[:rank, :] + free_sol = tau.vstack(vt - V * tau, tau) + + # Undo permutation + sol = zeros(col, B_cols) + + for k in range(col): + sol[permutation[k], :] = free_sol[k,:] + + sol, tau = cls(sol), cls(tau) + + if freevar: + return sol, tau, free_var_index + else: + return sol, tau + + +def _pinv_solve(M, B, arbitrary_matrix=None): + """Solve ``Ax = B`` using the Moore-Penrose pseudoinverse. + + There may be zero, one, or infinite solutions. If one solution + exists, it will be returned. If infinite solutions exist, one will + be returned based on the value of arbitrary_matrix. If no solutions + exist, the least-squares solution is returned. + + Parameters + ========== + + B : Matrix + The right hand side of the equation to be solved for. Must have + the same number of rows as matrix A. + arbitrary_matrix : Matrix + If the system is underdetermined (e.g. A has more columns than + rows), infinite solutions are possible, in terms of an arbitrary + matrix. This parameter may be set to a specific matrix to use + for that purpose; if so, it must be the same shape as x, with as + many rows as matrix A has columns, and as many columns as matrix + B. If left as None, an appropriate matrix containing dummy + symbols in the form of ``wn_m`` will be used, with n and m being + row and column position of each symbol. + + Returns + ======= + + x : Matrix + The matrix that will satisfy ``Ax = B``. Will have as many rows as + matrix A has columns, and as many columns as matrix B. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[1, 2, 3], [4, 5, 6]]) + >>> B = Matrix([7, 8]) + >>> A.pinv_solve(B) + Matrix([ + [ _w0_0/6 - _w1_0/3 + _w2_0/6 - 55/18], + [-_w0_0/3 + 2*_w1_0/3 - _w2_0/3 + 1/9], + [ _w0_0/6 - _w1_0/3 + _w2_0/6 + 59/18]]) + >>> A.pinv_solve(B, arbitrary_matrix=Matrix([0, 0, 0])) + Matrix([ + [-55/18], + [ 1/9], + [ 59/18]]) + + See Also + ======== + + sympy.matrices.dense.DenseMatrix.lower_triangular_solve + sympy.matrices.dense.DenseMatrix.upper_triangular_solve + gauss_jordan_solve + cholesky_solve + diagonal_solve + LDLsolve + LUsolve + QRsolve + pinv + + Notes + ===== + + This may return either exact solutions or least squares solutions. + To determine which, check ``A * A.pinv() * B == B``. It will be + True if exact solutions exist, and False if only a least-squares + solution exists. Be aware that the left hand side of that equation + may need to be simplified to correctly compare to the right hand + side. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Moore-Penrose_pseudoinverse#Obtaining_all_solutions_of_a_linear_system + + """ + + from sympy.matrices import eye + + A = M + A_pinv = M.pinv() + + if arbitrary_matrix is None: + rows, cols = A.cols, B.cols + w = symbols('w:{}_:{}'.format(rows, cols), cls=Dummy) + arbitrary_matrix = M.__class__(cols, rows, w).T + + return A_pinv.multiply(B) + (eye(A.cols) - + A_pinv.multiply(A)).multiply(arbitrary_matrix) + + +def _cramer_solve(M, rhs, det_method="laplace"): + """Solves system of linear equations using Cramer's rule. + + This method is relatively inefficient compared to other methods. + However it only uses a single division, assuming a division-free determinant + method is provided. This is helpful to minimize the chance of divide-by-zero + cases in symbolic solutions to linear systems. + + Parameters + ========== + M : Matrix + The matrix representing the left hand side of the equation. + rhs : Matrix + The matrix representing the right hand side of the equation. + det_method : str or callable + The method to use to calculate the determinant of the matrix. + The default is ``'laplace'``. If a callable is passed, it should take a + single argument, the matrix, and return the determinant of the matrix. + + Returns + ======= + x : Matrix + The matrix that will satisfy ``Ax = B``. Will have as many rows as + matrix A has columns, and as many columns as matrix B. + + Examples + ======== + + >>> from sympy import Matrix + >>> A = Matrix([[0, -6, 1], [0, -6, -1], [-5, -2, 3]]) + >>> B = Matrix([[-30, -9], [-18, -27], [-26, 46]]) + >>> x = A.cramer_solve(B) + >>> x + Matrix([ + [ 0, -5], + [ 4, 3], + [-6, 9]]) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Cramer%27s_rule#Explicit_formulas_for_small_systems + + """ + from .dense import zeros + + def entry(i, j): + return rhs[i, sol] if j == col else M[i, j] + + if det_method == "bird": + from .determinant import _det_bird + det = _det_bird + elif det_method == "laplace": + from .determinant import _det_laplace + det = _det_laplace + elif isinstance(det_method, str): + det = lambda matrix: matrix.det(method=det_method) + else: + det = det_method + det_M = det(M) + x = zeros(*rhs.shape) + for sol in range(rhs.shape[1]): + for col in range(rhs.shape[0]): + x[col, sol] = det(M.__class__(*M.shape, entry)) / det_M + return M.__class__(x) + + +def _solve(M, rhs, method='GJ'): + """Solves linear equation where the unique solution exists. + + Parameters + ========== + + rhs : Matrix + Vector representing the right hand side of the linear equation. + + method : string, optional + If set to ``'GJ'`` or ``'GE'``, the Gauss-Jordan elimination will be + used, which is implemented in the routine ``gauss_jordan_solve``. + + If set to ``'LU'``, ``LUsolve`` routine will be used. + + If set to ``'QR'``, ``QRsolve`` routine will be used. + + If set to ``'PINV'``, ``pinv_solve`` routine will be used. + + If set to ``'CRAMER'``, ``cramer_solve`` routine will be used. + + It also supports the methods available for special linear systems + + For positive definite systems: + + If set to ``'CH'``, ``cholesky_solve`` routine will be used. + + If set to ``'LDL'``, ``LDLsolve`` routine will be used. + + To use a different method and to compute the solution via the + inverse, use a method defined in the .inv() docstring. + + Returns + ======= + + solutions : Matrix + Vector representing the solution. + + Raises + ====== + + ValueError + If there is not a unique solution then a ``ValueError`` will be + raised. + + If ``M`` is not square, a ``ValueError`` and a different routine + for solving the system will be suggested. + """ + + if method in ('GJ', 'GE'): + try: + soln, param = M.gauss_jordan_solve(rhs) + + if param: + raise NonInvertibleMatrixError("Matrix det == 0; not invertible. " + "Try ``M.gauss_jordan_solve(rhs)`` to obtain a parametric solution.") + + except ValueError: + raise NonInvertibleMatrixError("Matrix det == 0; not invertible.") + + return soln + + elif method == 'LU': + return M.LUsolve(rhs) + elif method == 'CH': + return M.cholesky_solve(rhs) + elif method == 'QR': + return M.QRsolve(rhs) + elif method == 'LDL': + return M.LDLsolve(rhs) + elif method == 'PINV': + return M.pinv_solve(rhs) + elif method == 'CRAMER': + return M.cramer_solve(rhs) + else: + return M.inv(method=method).multiply(rhs) + + +def _solve_least_squares(M, rhs, method='CH'): + """Return the least-square fit to the data. + + Parameters + ========== + + rhs : Matrix + Vector representing the right hand side of the linear equation. + + method : string or boolean, optional + If set to ``'CH'``, ``cholesky_solve`` routine will be used. + + If set to ``'LDL'``, ``LDLsolve`` routine will be used. + + If set to ``'QR'``, ``QRsolve`` routine will be used. + + If set to ``'PINV'``, ``pinv_solve`` routine will be used. + + Otherwise, the conjugate of ``M`` will be used to create a system + of equations that is passed to ``solve`` along with the hint + defined by ``method``. + + Returns + ======= + + solutions : Matrix + Vector representing the solution. + + Examples + ======== + + >>> from sympy import Matrix, ones + >>> A = Matrix([1, 2, 3]) + >>> B = Matrix([2, 3, 4]) + >>> S = Matrix(A.row_join(B)) + >>> S + Matrix([ + [1, 2], + [2, 3], + [3, 4]]) + + If each line of S represent coefficients of Ax + By + and x and y are [2, 3] then S*xy is: + + >>> r = S*Matrix([2, 3]); r + Matrix([ + [ 8], + [13], + [18]]) + + But let's add 1 to the middle value and then solve for the + least-squares value of xy: + + >>> xy = S.solve_least_squares(Matrix([8, 14, 18])); xy + Matrix([ + [ 5/3], + [10/3]]) + + The error is given by S*xy - r: + + >>> S*xy - r + Matrix([ + [1/3], + [1/3], + [1/3]]) + >>> _.norm().n(2) + 0.58 + + If a different xy is used, the norm will be higher: + + >>> xy += ones(2, 1)/10 + >>> (S*xy - r).norm().n(2) + 1.5 + + """ + + if method == 'CH': + return M.cholesky_solve(rhs) + elif method == 'QR': + return M.QRsolve(rhs) + elif method == 'LDL': + return M.LDLsolve(rhs) + elif method == 'PINV': + return M.pinv_solve(rhs) + else: + t = M.H + return (t * M).solve(t * rhs, method=method) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/sparse.py b/venv/lib/python3.10/site-packages/sympy/matrices/sparse.py new file mode 100644 index 0000000000000000000000000000000000000000..95a7b3ca0ac29cf4409ec1eeecd059f9643e9bbc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/sparse.py @@ -0,0 +1,473 @@ +from collections.abc import Callable + +from sympy.core.containers import Dict +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import is_sequence +from sympy.utilities.misc import as_int + +from .matrixbase import MatrixBase +from .repmatrix import MutableRepMatrix, RepMatrix + +from .utilities import _iszero + +from .decompositions import ( + _liupc, _row_structure_symbolic_cholesky, _cholesky_sparse, + _LDLdecomposition_sparse) + +from .solvers import ( + _lower_triangular_solve_sparse, _upper_triangular_solve_sparse) + + +class SparseRepMatrix(RepMatrix): + """ + A sparse matrix (a matrix with a large number of zero elements). + + Examples + ======== + + >>> from sympy import SparseMatrix, ones + >>> SparseMatrix(2, 2, range(4)) + Matrix([ + [0, 1], + [2, 3]]) + >>> SparseMatrix(2, 2, {(1, 1): 2}) + Matrix([ + [0, 0], + [0, 2]]) + + A SparseMatrix can be instantiated from a ragged list of lists: + + >>> SparseMatrix([[1, 2, 3], [1, 2], [1]]) + Matrix([ + [1, 2, 3], + [1, 2, 0], + [1, 0, 0]]) + + For safety, one may include the expected size and then an error + will be raised if the indices of any element are out of range or + (for a flat list) if the total number of elements does not match + the expected shape: + + >>> SparseMatrix(2, 2, [1, 2]) + Traceback (most recent call last): + ... + ValueError: List length (2) != rows*columns (4) + + Here, an error is not raised because the list is not flat and no + element is out of range: + + >>> SparseMatrix(2, 2, [[1, 2]]) + Matrix([ + [1, 2], + [0, 0]]) + + But adding another element to the first (and only) row will cause + an error to be raised: + + >>> SparseMatrix(2, 2, [[1, 2, 3]]) + Traceback (most recent call last): + ... + ValueError: The location (0, 2) is out of designated range: (1, 1) + + To autosize the matrix, pass None for rows: + + >>> SparseMatrix(None, [[1, 2, 3]]) + Matrix([[1, 2, 3]]) + >>> SparseMatrix(None, {(1, 1): 1, (3, 3): 3}) + Matrix([ + [0, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 3]]) + + Values that are themselves a Matrix are automatically expanded: + + >>> SparseMatrix(4, 4, {(1, 1): ones(2)}) + Matrix([ + [0, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 0], + [0, 0, 0, 0]]) + + A ValueError is raised if the expanding matrix tries to overwrite + a different element already present: + + >>> SparseMatrix(3, 3, {(0, 0): ones(2), (1, 1): 2}) + Traceback (most recent call last): + ... + ValueError: collision at (1, 1) + + See Also + ======== + DenseMatrix + MutableSparseMatrix + ImmutableSparseMatrix + """ + + @classmethod + def _handle_creation_inputs(cls, *args, **kwargs): + if len(args) == 1 and isinstance(args[0], MatrixBase): + rows = args[0].rows + cols = args[0].cols + smat = args[0].todok() + return rows, cols, smat + + smat = {} + # autosizing + if len(args) == 2 and args[0] is None: + args = [None, None, args[1]] + + if len(args) == 3: + r, c = args[:2] + if r is c is None: + rows = cols = None + elif None in (r, c): + raise ValueError( + 'Pass rows=None and no cols for autosizing.') + else: + rows, cols = as_int(args[0]), as_int(args[1]) + + if isinstance(args[2], Callable): + op = args[2] + + if None in (rows, cols): + raise ValueError( + "{} and {} must be integers for this " + "specification.".format(rows, cols)) + + row_indices = [cls._sympify(i) for i in range(rows)] + col_indices = [cls._sympify(j) for j in range(cols)] + + for i in row_indices: + for j in col_indices: + value = cls._sympify(op(i, j)) + if value != cls.zero: + smat[i, j] = value + + return rows, cols, smat + + elif isinstance(args[2], (dict, Dict)): + def update(i, j, v): + # update smat and make sure there are no collisions + if v: + if (i, j) in smat and v != smat[i, j]: + raise ValueError( + "There is a collision at {} for {} and {}." + .format((i, j), v, smat[i, j]) + ) + smat[i, j] = v + + # manual copy, copy.deepcopy() doesn't work + for (r, c), v in args[2].items(): + if isinstance(v, MatrixBase): + for (i, j), vv in v.todok().items(): + update(r + i, c + j, vv) + elif isinstance(v, (list, tuple)): + _, _, smat = cls._handle_creation_inputs(v, **kwargs) + for i, j in smat: + update(r + i, c + j, smat[i, j]) + else: + v = cls._sympify(v) + update(r, c, cls._sympify(v)) + + elif is_sequence(args[2]): + flat = not any(is_sequence(i) for i in args[2]) + if not flat: + _, _, smat = \ + cls._handle_creation_inputs(args[2], **kwargs) + else: + flat_list = args[2] + if len(flat_list) != rows * cols: + raise ValueError( + "The length of the flat list ({}) does not " + "match the specified size ({} * {})." + .format(len(flat_list), rows, cols) + ) + + for i in range(rows): + for j in range(cols): + value = flat_list[i*cols + j] + value = cls._sympify(value) + if value != cls.zero: + smat[i, j] = value + + if rows is None: # autosizing + keys = smat.keys() + rows = max(r for r, _ in keys) + 1 if keys else 0 + cols = max(c for _, c in keys) + 1 if keys else 0 + + else: + for i, j in smat.keys(): + if i and i >= rows or j and j >= cols: + raise ValueError( + "The location {} is out of the designated range" + "[{}, {}]x[{}, {}]" + .format((i, j), 0, rows - 1, 0, cols - 1) + ) + + return rows, cols, smat + + elif len(args) == 1 and isinstance(args[0], (list, tuple)): + # list of values or lists + v = args[0] + c = 0 + for i, row in enumerate(v): + if not isinstance(row, (list, tuple)): + row = [row] + for j, vv in enumerate(row): + if vv != cls.zero: + smat[i, j] = cls._sympify(vv) + c = max(c, len(row)) + rows = len(v) if c else 0 + cols = c + return rows, cols, smat + + else: + # handle full matrix forms with _handle_creation_inputs + rows, cols, mat = super()._handle_creation_inputs(*args) + for i in range(rows): + for j in range(cols): + value = mat[cols*i + j] + if value != cls.zero: + smat[i, j] = value + + return rows, cols, smat + + @property + def _smat(self): + + sympy_deprecation_warning( + """ + The private _smat attribute of SparseMatrix is deprecated. Use the + .todok() method instead. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-private-matrix-attributes" + ) + + return self.todok() + + def _eval_inverse(self, **kwargs): + return self.inv(method=kwargs.get('method', 'LDL'), + iszerofunc=kwargs.get('iszerofunc', _iszero), + try_block_diag=kwargs.get('try_block_diag', False)) + + def applyfunc(self, f): + """Apply a function to each element of the matrix. + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> m = SparseMatrix(2, 2, lambda i, j: i*2+j) + >>> m + Matrix([ + [0, 1], + [2, 3]]) + >>> m.applyfunc(lambda i: 2*i) + Matrix([ + [0, 2], + [4, 6]]) + + """ + if not callable(f): + raise TypeError("`f` must be callable.") + + # XXX: This only applies the function to the nonzero elements of the + # matrix so is inconsistent with DenseMatrix.applyfunc e.g. + # zeros(2, 2).applyfunc(lambda x: x + 1) + dok = {} + for k, v in self.todok().items(): + fv = f(v) + if fv != 0: + dok[k] = fv + + return self._new(self.rows, self.cols, dok) + + def as_immutable(self): + """Returns an Immutable version of this Matrix.""" + from .immutable import ImmutableSparseMatrix + return ImmutableSparseMatrix(self) + + def as_mutable(self): + """Returns a mutable version of this matrix. + + Examples + ======== + + >>> from sympy import ImmutableMatrix + >>> X = ImmutableMatrix([[1, 2], [3, 4]]) + >>> Y = X.as_mutable() + >>> Y[1, 1] = 5 # Can set values in Y + >>> Y + Matrix([ + [1, 2], + [3, 5]]) + """ + return MutableSparseMatrix(self) + + def col_list(self): + """Returns a column-sorted list of non-zero elements of the matrix. + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> a=SparseMatrix(((1, 2), (3, 4))) + >>> a + Matrix([ + [1, 2], + [3, 4]]) + >>> a.CL + [(0, 0, 1), (1, 0, 3), (0, 1, 2), (1, 1, 4)] + + See Also + ======== + + sympy.matrices.sparse.SparseMatrix.row_list + """ + return [tuple(k + (self[k],)) for k in sorted(self.todok().keys(), key=lambda k: list(reversed(k)))] + + def nnz(self): + """Returns the number of non-zero elements in Matrix.""" + return len(self.todok()) + + def row_list(self): + """Returns a row-sorted list of non-zero elements of the matrix. + + Examples + ======== + + >>> from sympy import SparseMatrix + >>> a = SparseMatrix(((1, 2), (3, 4))) + >>> a + Matrix([ + [1, 2], + [3, 4]]) + >>> a.RL + [(0, 0, 1), (0, 1, 2), (1, 0, 3), (1, 1, 4)] + + See Also + ======== + + sympy.matrices.sparse.SparseMatrix.col_list + """ + return [tuple(k + (self[k],)) for k in + sorted(self.todok().keys(), key=list)] + + def scalar_multiply(self, scalar): + "Scalar element-wise multiplication" + return scalar * self + + def solve_least_squares(self, rhs, method='LDL'): + """Return the least-square fit to the data. + + By default the cholesky_solve routine is used (method='CH'); other + methods of matrix inversion can be used. To find out which are + available, see the docstring of the .inv() method. + + Examples + ======== + + >>> from sympy import SparseMatrix, Matrix, ones + >>> A = Matrix([1, 2, 3]) + >>> B = Matrix([2, 3, 4]) + >>> S = SparseMatrix(A.row_join(B)) + >>> S + Matrix([ + [1, 2], + [2, 3], + [3, 4]]) + + If each line of S represent coefficients of Ax + By + and x and y are [2, 3] then S*xy is: + + >>> r = S*Matrix([2, 3]); r + Matrix([ + [ 8], + [13], + [18]]) + + But let's add 1 to the middle value and then solve for the + least-squares value of xy: + + >>> xy = S.solve_least_squares(Matrix([8, 14, 18])); xy + Matrix([ + [ 5/3], + [10/3]]) + + The error is given by S*xy - r: + + >>> S*xy - r + Matrix([ + [1/3], + [1/3], + [1/3]]) + >>> _.norm().n(2) + 0.58 + + If a different xy is used, the norm will be higher: + + >>> xy += ones(2, 1)/10 + >>> (S*xy - r).norm().n(2) + 1.5 + + """ + t = self.T + return (t*self).inv(method=method)*t*rhs + + def solve(self, rhs, method='LDL'): + """Return solution to self*soln = rhs using given inversion method. + + For a list of possible inversion methods, see the .inv() docstring. + """ + if not self.is_square: + if self.rows < self.cols: + raise ValueError('Under-determined system.') + elif self.rows > self.cols: + raise ValueError('For over-determined system, M, having ' + 'more rows than columns, try M.solve_least_squares(rhs).') + else: + return self.inv(method=method).multiply(rhs) + + RL = property(row_list, None, None, "Alternate faster representation") + CL = property(col_list, None, None, "Alternate faster representation") + + def liupc(self): + return _liupc(self) + + def row_structure_symbolic_cholesky(self): + return _row_structure_symbolic_cholesky(self) + + def cholesky(self, hermitian=True): + return _cholesky_sparse(self, hermitian=hermitian) + + def LDLdecomposition(self, hermitian=True): + return _LDLdecomposition_sparse(self, hermitian=hermitian) + + def lower_triangular_solve(self, rhs): + return _lower_triangular_solve_sparse(self, rhs) + + def upper_triangular_solve(self, rhs): + return _upper_triangular_solve_sparse(self, rhs) + + liupc.__doc__ = _liupc.__doc__ + row_structure_symbolic_cholesky.__doc__ = _row_structure_symbolic_cholesky.__doc__ + cholesky.__doc__ = _cholesky_sparse.__doc__ + LDLdecomposition.__doc__ = _LDLdecomposition_sparse.__doc__ + lower_triangular_solve.__doc__ = lower_triangular_solve.__doc__ + upper_triangular_solve.__doc__ = upper_triangular_solve.__doc__ + + +class MutableSparseMatrix(SparseRepMatrix, MutableRepMatrix): + + @classmethod + def _new(cls, *args, **kwargs): + rows, cols, smat = cls._handle_creation_inputs(*args, **kwargs) + + rep = cls._smat_to_DomainMatrix(rows, cols, smat) + + return cls._fromrep(rep) + + +SparseMatrix = MutableSparseMatrix diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/sparsetools.py b/venv/lib/python3.10/site-packages/sympy/matrices/sparsetools.py new file mode 100644 index 0000000000000000000000000000000000000000..50048f6dc7e5cf160366963d16427987616ddce7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/sparsetools.py @@ -0,0 +1,300 @@ +from sympy.core.containers import Dict +from sympy.core.symbol import Dummy +from sympy.utilities.iterables import is_sequence +from sympy.utilities.misc import as_int, filldedent + +from .sparse import MutableSparseMatrix as SparseMatrix + + +def _doktocsr(dok): + """Converts a sparse matrix to Compressed Sparse Row (CSR) format. + + Parameters + ========== + + A : contains non-zero elements sorted by key (row, column) + JA : JA[i] is the column corresponding to A[i] + IA : IA[i] contains the index in A for the first non-zero element + of row[i]. Thus IA[i+1] - IA[i] gives number of non-zero + elements row[i]. The length of IA is always 1 more than the + number of rows in the matrix. + + Examples + ======== + + >>> from sympy.matrices.sparsetools import _doktocsr + >>> from sympy import SparseMatrix, diag + >>> m = SparseMatrix(diag(1, 2, 3)) + >>> m[2, 0] = -1 + >>> _doktocsr(m) + [[1, 2, -1, 3], [0, 1, 0, 2], [0, 1, 2, 4], [3, 3]] + + """ + row, JA, A = [list(i) for i in zip(*dok.row_list())] + IA = [0]*((row[0] if row else 0) + 1) + for i, r in enumerate(row): + IA.extend([i]*(r - row[i - 1])) # if i = 0 nothing is extended + IA.extend([len(A)]*(dok.rows - len(IA) + 1)) + shape = [dok.rows, dok.cols] + return [A, JA, IA, shape] + + +def _csrtodok(csr): + """Converts a CSR representation to DOK representation. + + Examples + ======== + + >>> from sympy.matrices.sparsetools import _csrtodok + >>> _csrtodok([[5, 8, 3, 6], [0, 1, 2, 1], [0, 0, 2, 3, 4], [4, 3]]) + Matrix([ + [0, 0, 0], + [5, 8, 0], + [0, 0, 3], + [0, 6, 0]]) + + """ + smat = {} + A, JA, IA, shape = csr + for i in range(len(IA) - 1): + indices = slice(IA[i], IA[i + 1]) + for l, m in zip(A[indices], JA[indices]): + smat[i, m] = l + return SparseMatrix(*shape, smat) + + +def banded(*args, **kwargs): + """Returns a SparseMatrix from the given dictionary describing + the diagonals of the matrix. The keys are positive for upper + diagonals and negative for those below the main diagonal. The + values may be: + + * expressions or single-argument functions, + + * lists or tuples of values, + + * matrices + + Unless dimensions are given, the size of the returned matrix will + be large enough to contain the largest non-zero value provided. + + kwargs + ====== + + rows : rows of the resulting matrix; computed if + not given. + + cols : columns of the resulting matrix; computed if + not given. + + Examples + ======== + + >>> from sympy import banded, ones, Matrix + >>> from sympy.abc import x + + If explicit values are given in tuples, + the matrix will autosize to contain all values, otherwise + a single value is filled onto the entire diagonal: + + >>> banded({1: (1, 2, 3), -1: (4, 5, 6), 0: x}) + Matrix([ + [x, 1, 0, 0], + [4, x, 2, 0], + [0, 5, x, 3], + [0, 0, 6, x]]) + + A function accepting a single argument can be used to fill the + diagonal as a function of diagonal index (which starts at 0). + The size (or shape) of the matrix must be given to obtain more + than a 1x1 matrix: + + >>> s = lambda d: (1 + d)**2 + >>> banded(5, {0: s, 2: s, -2: 2}) + Matrix([ + [1, 0, 1, 0, 0], + [0, 4, 0, 4, 0], + [2, 0, 9, 0, 9], + [0, 2, 0, 16, 0], + [0, 0, 2, 0, 25]]) + + The diagonal of matrices placed on a diagonal will coincide + with the indicated diagonal: + + >>> vert = Matrix([1, 2, 3]) + >>> banded({0: vert}, cols=3) + Matrix([ + [1, 0, 0], + [2, 1, 0], + [3, 2, 1], + [0, 3, 2], + [0, 0, 3]]) + + >>> banded(4, {0: ones(2)}) + Matrix([ + [1, 1, 0, 0], + [1, 1, 0, 0], + [0, 0, 1, 1], + [0, 0, 1, 1]]) + + Errors are raised if the designated size will not hold + all values an integral number of times. Here, the rows + are designated as odd (but an even number is required to + hold the off-diagonal 2x2 ones): + + >>> banded({0: 2, 1: ones(2)}, rows=5) + Traceback (most recent call last): + ... + ValueError: + sequence does not fit an integral number of times in the matrix + + And here, an even number of rows is given...but the square + matrix has an even number of columns, too. As we saw + in the previous example, an odd number is required: + + >>> banded(4, {0: 2, 1: ones(2)}) # trying to make 4x4 and cols must be odd + Traceback (most recent call last): + ... + ValueError: + sequence does not fit an integral number of times in the matrix + + A way around having to count rows is to enclosing matrix elements + in a tuple and indicate the desired number of them to the right: + + >>> banded({0: 2, 2: (ones(2),)*3}) + Matrix([ + [2, 0, 1, 1, 0, 0, 0, 0], + [0, 2, 1, 1, 0, 0, 0, 0], + [0, 0, 2, 0, 1, 1, 0, 0], + [0, 0, 0, 2, 1, 1, 0, 0], + [0, 0, 0, 0, 2, 0, 1, 1], + [0, 0, 0, 0, 0, 2, 1, 1]]) + + An error will be raised if more than one value + is written to a given entry. Here, the ones overlap + with the main diagonal if they are placed on the + first diagonal: + + >>> banded({0: (2,)*5, 1: (ones(2),)*3}) + Traceback (most recent call last): + ... + ValueError: collision at (1, 1) + + By placing a 0 at the bottom left of the 2x2 matrix of + ones, the collision is avoided: + + >>> u2 = Matrix([ + ... [1, 1], + ... [0, 1]]) + >>> banded({0: [2]*5, 1: [u2]*3}) + Matrix([ + [2, 1, 1, 0, 0, 0, 0], + [0, 2, 1, 0, 0, 0, 0], + [0, 0, 2, 1, 1, 0, 0], + [0, 0, 0, 2, 1, 0, 0], + [0, 0, 0, 0, 2, 1, 1], + [0, 0, 0, 0, 0, 0, 1]]) + """ + try: + if len(args) not in (1, 2, 3): + raise TypeError + if not isinstance(args[-1], (dict, Dict)): + raise TypeError + if len(args) == 1: + rows = kwargs.get('rows', None) + cols = kwargs.get('cols', None) + if rows is not None: + rows = as_int(rows) + if cols is not None: + cols = as_int(cols) + elif len(args) == 2: + rows = cols = as_int(args[0]) + else: + rows, cols = map(as_int, args[:2]) + # fails with ValueError if any keys are not ints + _ = all(as_int(k) for k in args[-1]) + except (ValueError, TypeError): + raise TypeError(filldedent( + '''unrecognized input to banded: + expecting [[row,] col,] {int: value}''')) + def rc(d): + # return row,col coord of diagonal start + r = -d if d < 0 else 0 + c = 0 if r else d + return r, c + smat = {} + undone = [] + tba = Dummy() + # first handle objects with size + for d, v in args[-1].items(): + r, c = rc(d) + # note: only list and tuple are recognized since this + # will allow other Basic objects like Tuple + # into the matrix if so desired + if isinstance(v, (list, tuple)): + extra = 0 + for i, vi in enumerate(v): + i += extra + if is_sequence(vi): + vi = SparseMatrix(vi) + smat[r + i, c + i] = vi + extra += min(vi.shape) - 1 + else: + smat[r + i, c + i] = vi + elif is_sequence(v): + v = SparseMatrix(v) + rv, cv = v.shape + if rows and cols: + nr, xr = divmod(rows - r, rv) + nc, xc = divmod(cols - c, cv) + x = xr or xc + do = min(nr, nc) + elif rows: + do, x = divmod(rows - r, rv) + elif cols: + do, x = divmod(cols - c, cv) + else: + do = 1 + x = 0 + if x: + raise ValueError(filldedent(''' + sequence does not fit an integral number of times + in the matrix''')) + j = min(v.shape) + for i in range(do): + smat[r, c] = v + r += j + c += j + elif v: + smat[r, c] = tba + undone.append((d, v)) + s = SparseMatrix(None, smat) # to expand matrices + smat = s.todok() + # check for dim errors here + if rows is not None and rows < s.rows: + raise ValueError('Designated rows %s < needed %s' % (rows, s.rows)) + if cols is not None and cols < s.cols: + raise ValueError('Designated cols %s < needed %s' % (cols, s.cols)) + if rows is cols is None: + rows = s.rows + cols = s.cols + elif rows is not None and cols is None: + cols = max(rows, s.cols) + elif cols is not None and rows is None: + rows = max(cols, s.rows) + def update(i, j, v): + # update smat and make sure there are + # no collisions + if v: + if (i, j) in smat and smat[i, j] not in (tba, v): + raise ValueError('collision at %s' % ((i, j),)) + smat[i, j] = v + if undone: + for d, vi in undone: + r, c = rc(d) + v = vi if callable(vi) else lambda _: vi + i = 0 + while r + i < rows and c + i < cols: + update(r + i, c + i, v(i)) + i += 1 + return SparseMatrix(rows, cols, smat) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/subspaces.py b/venv/lib/python3.10/site-packages/sympy/matrices/subspaces.py new file mode 100644 index 0000000000000000000000000000000000000000..1ab0b71b4289ebaeb6394059c6a7cd49d3a148a1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/subspaces.py @@ -0,0 +1,174 @@ +from .utilities import _iszero + + +def _columnspace(M, simplify=False): + """Returns a list of vectors (Matrix objects) that span columnspace of ``M`` + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix(3, 3, [1, 3, 0, -2, -6, 0, 3, 9, 6]) + >>> M + Matrix([ + [ 1, 3, 0], + [-2, -6, 0], + [ 3, 9, 6]]) + >>> M.columnspace() + [Matrix([ + [ 1], + [-2], + [ 3]]), Matrix([ + [0], + [0], + [6]])] + + See Also + ======== + + nullspace + rowspace + """ + + reduced, pivots = M.echelon_form(simplify=simplify, with_pivots=True) + + return [M.col(i) for i in pivots] + + +def _nullspace(M, simplify=False, iszerofunc=_iszero): + """Returns list of vectors (Matrix objects) that span nullspace of ``M`` + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix(3, 3, [1, 3, 0, -2, -6, 0, 3, 9, 6]) + >>> M + Matrix([ + [ 1, 3, 0], + [-2, -6, 0], + [ 3, 9, 6]]) + >>> M.nullspace() + [Matrix([ + [-3], + [ 1], + [ 0]])] + + See Also + ======== + + columnspace + rowspace + """ + + reduced, pivots = M.rref(iszerofunc=iszerofunc, simplify=simplify) + + free_vars = [i for i in range(M.cols) if i not in pivots] + basis = [] + + for free_var in free_vars: + # for each free variable, we will set it to 1 and all others + # to 0. Then, we will use back substitution to solve the system + vec = [M.zero] * M.cols + vec[free_var] = M.one + + for piv_row, piv_col in enumerate(pivots): + vec[piv_col] -= reduced[piv_row, free_var] + + basis.append(vec) + + return [M._new(M.cols, 1, b) for b in basis] + + +def _rowspace(M, simplify=False): + """Returns a list of vectors that span the row space of ``M``. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix(3, 3, [1, 3, 0, -2, -6, 0, 3, 9, 6]) + >>> M + Matrix([ + [ 1, 3, 0], + [-2, -6, 0], + [ 3, 9, 6]]) + >>> M.rowspace() + [Matrix([[1, 3, 0]]), Matrix([[0, 0, 6]])] + """ + + reduced, pivots = M.echelon_form(simplify=simplify, with_pivots=True) + + return [reduced.row(i) for i in range(len(pivots))] + + +def _orthogonalize(cls, *vecs, normalize=False, rankcheck=False): + """Apply the Gram-Schmidt orthogonalization procedure + to vectors supplied in ``vecs``. + + Parameters + ========== + + vecs + vectors to be made orthogonal + + normalize : bool + If ``True``, return an orthonormal basis. + + rankcheck : bool + If ``True``, the computation does not stop when encountering + linearly dependent vectors. + + If ``False``, it will raise ``ValueError`` when any zero + or linearly dependent vectors are found. + + Returns + ======= + + list + List of orthogonal (or orthonormal) basis vectors. + + Examples + ======== + + >>> from sympy import I, Matrix + >>> v = [Matrix([1, I]), Matrix([1, -I])] + >>> Matrix.orthogonalize(*v) + [Matrix([ + [1], + [I]]), Matrix([ + [ 1], + [-I]])] + + See Also + ======== + + MatrixBase.QRdecomposition + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process + """ + from .decompositions import _QRdecomposition_optional + + if not vecs: + return [] + + all_row_vecs = (vecs[0].rows == 1) + + vecs = [x.vec() for x in vecs] + M = cls.hstack(*vecs) + Q, R = _QRdecomposition_optional(M, normalize=normalize) + + if rankcheck and Q.cols < len(vecs): + raise ValueError("GramSchmidt: vector set not linearly independent") + + ret = [] + for i in range(Q.cols): + if all_row_vecs: + col = cls(Q[:, i].T) + else: + col = cls(Q[:, i]) + ret.append(col) + return ret diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27d17e6544c4cbe1e850ac2aeda7f459699ef532 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_commonmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_commonmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb0eea727bed45127170eefaca3c9070100e2bda Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_commonmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_decompositions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_decompositions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5cd9af69df220957cb083f6fa7e32747b55cf4e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_decompositions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_determinant.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_determinant.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5dc496e487e93f0e131cd5445313754c04001c88 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_determinant.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_domains.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_domains.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1738a31a2e64a84afead0f74568a230bcd4ca7f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_domains.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_eigen.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_eigen.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..485205a4f16588a44a5ff8d5fb9313857f302b75 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_eigen.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_graph.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_graph.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0710d590bf9c174c7854d79feeb54433f102fa2d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_graph.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_immutable.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_immutable.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7ce2a66d666ae2f0aca6d7cfcc4782b51d11642 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_immutable.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_interactions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_interactions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93288a2264e81a05e3dff1da197405cdda95b1ac Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_interactions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_normalforms.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_normalforms.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a70de0e076bb656b4dd6cfda9c414ce0ca3c4a2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_normalforms.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_reductions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_reductions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8992dc5b309f7af2abaaa61ab81053e8a571caf Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_reductions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_repmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_repmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc179d77a5f05060500d650097e5b1e713b8c762 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_repmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_solvers.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_solvers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea5b4cbb89f79d93cedb8c5114a332336feb5700 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_solvers.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_sparse.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_sparse.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06415edac9ba0c2cc949f0b877ac026e12473f54 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_sparse.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_sparsetools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_sparsetools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bb1034bb22c2b59f86a321b3eb22d9f549b0ca9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_sparsetools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_subspaces.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_subspaces.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd33419162c76b19ea5495be411bbd3008ec5653 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/matrices/tests/__pycache__/test_subspaces.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_commonmatrix.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_commonmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..6735adc1a9d4f9934a55c7ee70b087a19d3a48b4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_commonmatrix.py @@ -0,0 +1,1266 @@ +# +# Code for testing deprecated matrix classes. New test code should not be added +# here. Instead, add it to test_matrixbase.py. +# +# This entire test module and the corresponding sympy/matrices/common.py +# module will be removed in a future release. +# +from sympy.testing.pytest import raises, XFAIL, warns_deprecated_sympy + +from sympy.assumptions import Q +from sympy.core.expr import Expr +from sympy.core.add import Add +from sympy.core.function import Function +from sympy.core.kind import NumberKind, UndefinedKind +from sympy.core.numbers import I, Integer, oo, pi, Rational +from sympy.core.singleton import S +from sympy.core.symbol import Symbol, symbols +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.matrices.exceptions import ShapeError, NonSquareMatrixError +from sympy.matrices.kind import MatrixKind +from sympy.matrices.common import ( + _MinimalMatrix, _CastableMatrix, MatrixShaping, MatrixProperties, + MatrixOperations, MatrixArithmetic, MatrixSpecial) +from sympy.matrices.matrices import MatrixCalculus +from sympy.matrices import (Matrix, diag, eye, + matrix_multiply_elementwise, ones, zeros, SparseMatrix, banded, + MutableDenseMatrix, MutableSparseMatrix, ImmutableDenseMatrix, + ImmutableSparseMatrix) +from sympy.polys.polytools import Poly +from sympy.utilities.iterables import flatten +from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray as Array + +from sympy.abc import x, y, z + + +def test_matrix_deprecated_isinstance(): + + # Test that e.g. isinstance(M, MatrixCommon) still gives True when M is a + # Matrix for each of the deprecated matrix classes. + + from sympy.matrices.common import ( + MatrixRequired, + MatrixShaping, + MatrixSpecial, + MatrixProperties, + MatrixOperations, + MatrixArithmetic, + MatrixCommon + ) + from sympy.matrices.matrices import ( + MatrixDeterminant, + MatrixReductions, + MatrixSubspaces, + MatrixEigen, + MatrixCalculus, + MatrixDeprecated + ) + from sympy import ( + Matrix, + ImmutableMatrix, + SparseMatrix, + ImmutableSparseMatrix + ) + all_mixins = ( + MatrixRequired, + MatrixShaping, + MatrixSpecial, + MatrixProperties, + MatrixOperations, + MatrixArithmetic, + MatrixCommon, + MatrixDeterminant, + MatrixReductions, + MatrixSubspaces, + MatrixEigen, + MatrixCalculus, + MatrixDeprecated + ) + all_matrices = ( + Matrix, + ImmutableMatrix, + SparseMatrix, + ImmutableSparseMatrix + ) + + Ms = [M([[1, 2], [3, 4]]) for M in all_matrices] + t = () + + for mixin in all_mixins: + for M in Ms: + with warns_deprecated_sympy(): + assert isinstance(M, mixin) is True + with warns_deprecated_sympy(): + assert isinstance(t, mixin) is False + + +# classes to test the deprecated matrix classes. We use warns_deprecated_sympy +# to suppress the deprecation warnings because subclassing the deprecated +# classes causes a warning to be raised. + +with warns_deprecated_sympy(): + class ShapingOnlyMatrix(_MinimalMatrix, _CastableMatrix, MatrixShaping): + pass + + +def eye_Shaping(n): + return ShapingOnlyMatrix(n, n, lambda i, j: int(i == j)) + + +def zeros_Shaping(n): + return ShapingOnlyMatrix(n, n, lambda i, j: 0) + + +with warns_deprecated_sympy(): + class PropertiesOnlyMatrix(_MinimalMatrix, _CastableMatrix, MatrixProperties): + pass + + +def eye_Properties(n): + return PropertiesOnlyMatrix(n, n, lambda i, j: int(i == j)) + + +def zeros_Properties(n): + return PropertiesOnlyMatrix(n, n, lambda i, j: 0) + + +with warns_deprecated_sympy(): + class OperationsOnlyMatrix(_MinimalMatrix, _CastableMatrix, MatrixOperations): + pass + + +def eye_Operations(n): + return OperationsOnlyMatrix(n, n, lambda i, j: int(i == j)) + + +def zeros_Operations(n): + return OperationsOnlyMatrix(n, n, lambda i, j: 0) + + +with warns_deprecated_sympy(): + class ArithmeticOnlyMatrix(_MinimalMatrix, _CastableMatrix, MatrixArithmetic): + pass + + +def eye_Arithmetic(n): + return ArithmeticOnlyMatrix(n, n, lambda i, j: int(i == j)) + + +def zeros_Arithmetic(n): + return ArithmeticOnlyMatrix(n, n, lambda i, j: 0) + + +with warns_deprecated_sympy(): + class SpecialOnlyMatrix(_MinimalMatrix, _CastableMatrix, MatrixSpecial): + pass + + +with warns_deprecated_sympy(): + class CalculusOnlyMatrix(_MinimalMatrix, _CastableMatrix, MatrixCalculus): + pass + + +def test__MinimalMatrix(): + x = _MinimalMatrix(2, 3, [1, 2, 3, 4, 5, 6]) + assert x.rows == 2 + assert x.cols == 3 + assert x[2] == 3 + assert x[1, 1] == 5 + assert list(x) == [1, 2, 3, 4, 5, 6] + assert list(x[1, :]) == [4, 5, 6] + assert list(x[:, 1]) == [2, 5] + assert list(x[:, :]) == list(x) + assert x[:, :] == x + assert _MinimalMatrix(x) == x + assert _MinimalMatrix([[1, 2, 3], [4, 5, 6]]) == x + assert _MinimalMatrix(([1, 2, 3], [4, 5, 6])) == x + assert _MinimalMatrix([(1, 2, 3), (4, 5, 6)]) == x + assert _MinimalMatrix(((1, 2, 3), (4, 5, 6))) == x + assert not (_MinimalMatrix([[1, 2], [3, 4], [5, 6]]) == x) + + +def test_kind(): + assert Matrix([[1, 2], [3, 4]]).kind == MatrixKind(NumberKind) + assert Matrix([[0, 0], [0, 0]]).kind == MatrixKind(NumberKind) + assert Matrix(0, 0, []).kind == MatrixKind(NumberKind) + assert Matrix([[x]]).kind == MatrixKind(NumberKind) + assert Matrix([[1, Matrix([[1]])]]).kind == MatrixKind(UndefinedKind) + assert SparseMatrix([[1]]).kind == MatrixKind(NumberKind) + assert SparseMatrix([[1, Matrix([[1]])]]).kind == MatrixKind(UndefinedKind) + + +# ShapingOnlyMatrix tests +def test_vec(): + m = ShapingOnlyMatrix(2, 2, [1, 3, 2, 4]) + m_vec = m.vec() + assert m_vec.cols == 1 + for i in range(4): + assert m_vec[i] == i + 1 + + +def test_todok(): + a, b, c, d = symbols('a:d') + m1 = MutableDenseMatrix([[a, b], [c, d]]) + m2 = ImmutableDenseMatrix([[a, b], [c, d]]) + m3 = MutableSparseMatrix([[a, b], [c, d]]) + m4 = ImmutableSparseMatrix([[a, b], [c, d]]) + assert m1.todok() == m2.todok() == m3.todok() == m4.todok() == \ + {(0, 0): a, (0, 1): b, (1, 0): c, (1, 1): d} + + +def test_tolist(): + lst = [[S.One, S.Half, x*y, S.Zero], [x, y, z, x**2], [y, -S.One, z*x, 3]] + flat_lst = [S.One, S.Half, x*y, S.Zero, x, y, z, x**2, y, -S.One, z*x, 3] + m = ShapingOnlyMatrix(3, 4, flat_lst) + assert m.tolist() == lst + +def test_todod(): + m = ShapingOnlyMatrix(3, 2, [[S.One, 0], [0, S.Half], [x, 0]]) + dict = {0: {0: S.One}, 1: {1: S.Half}, 2: {0: x}} + assert m.todod() == dict + +def test_row_col_del(): + e = ShapingOnlyMatrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + raises(IndexError, lambda: e.row_del(5)) + raises(IndexError, lambda: e.row_del(-5)) + raises(IndexError, lambda: e.col_del(5)) + raises(IndexError, lambda: e.col_del(-5)) + + assert e.row_del(2) == e.row_del(-1) == Matrix([[1, 2, 3], [4, 5, 6]]) + assert e.col_del(2) == e.col_del(-1) == Matrix([[1, 2], [4, 5], [7, 8]]) + + assert e.row_del(1) == e.row_del(-2) == Matrix([[1, 2, 3], [7, 8, 9]]) + assert e.col_del(1) == e.col_del(-2) == Matrix([[1, 3], [4, 6], [7, 9]]) + + +def test_get_diag_blocks1(): + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + assert a.get_diag_blocks() == [a] + assert b.get_diag_blocks() == [b] + assert c.get_diag_blocks() == [c] + + +def test_get_diag_blocks2(): + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + A, B, C, D = diag(a, b, b), diag(a, b, c), diag(a, c, b), diag(c, c, b) + A = ShapingOnlyMatrix(A.rows, A.cols, A) + B = ShapingOnlyMatrix(B.rows, B.cols, B) + C = ShapingOnlyMatrix(C.rows, C.cols, C) + D = ShapingOnlyMatrix(D.rows, D.cols, D) + + assert A.get_diag_blocks() == [a, b, b] + assert B.get_diag_blocks() == [a, b, c] + assert C.get_diag_blocks() == [a, c, b] + assert D.get_diag_blocks() == [c, c, b] + + +def test_shape(): + m = ShapingOnlyMatrix(1, 2, [0, 0]) + assert m.shape == (1, 2) + + +def test_reshape(): + m0 = eye_Shaping(3) + assert m0.reshape(1, 9) == Matrix(1, 9, (1, 0, 0, 0, 1, 0, 0, 0, 1)) + m1 = ShapingOnlyMatrix(3, 4, lambda i, j: i + j) + assert m1.reshape( + 4, 3) == Matrix(((0, 1, 2), (3, 1, 2), (3, 4, 2), (3, 4, 5))) + assert m1.reshape(2, 6) == Matrix(((0, 1, 2, 3, 1, 2), (3, 4, 2, 3, 4, 5))) + + +def test_row_col(): + m = ShapingOnlyMatrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + assert m.row(0) == Matrix(1, 3, [1, 2, 3]) + assert m.col(0) == Matrix(3, 1, [1, 4, 7]) + + +def test_row_join(): + assert eye_Shaping(3).row_join(Matrix([7, 7, 7])) == \ + Matrix([[1, 0, 0, 7], + [0, 1, 0, 7], + [0, 0, 1, 7]]) + + +def test_col_join(): + assert eye_Shaping(3).col_join(Matrix([[7, 7, 7]])) == \ + Matrix([[1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [7, 7, 7]]) + + +def test_row_insert(): + r4 = Matrix([[4, 4, 4]]) + for i in range(-4, 5): + l = [1, 0, 0] + l.insert(i, 4) + assert flatten(eye_Shaping(3).row_insert(i, r4).col(0).tolist()) == l + + +def test_col_insert(): + c4 = Matrix([4, 4, 4]) + for i in range(-4, 5): + l = [0, 0, 0] + l.insert(i, 4) + assert flatten(zeros_Shaping(3).col_insert(i, c4).row(0).tolist()) == l + # issue 13643 + assert eye_Shaping(6).col_insert(3, Matrix([[2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]])) == \ + Matrix([[1, 0, 0, 2, 2, 0, 0, 0], + [0, 1, 0, 2, 2, 0, 0, 0], + [0, 0, 1, 2, 2, 0, 0, 0], + [0, 0, 0, 2, 2, 1, 0, 0], + [0, 0, 0, 2, 2, 0, 1, 0], + [0, 0, 0, 2, 2, 0, 0, 1]]) + + +def test_extract(): + m = ShapingOnlyMatrix(4, 3, lambda i, j: i*3 + j) + assert m.extract([0, 1, 3], [0, 1]) == Matrix(3, 2, [0, 1, 3, 4, 9, 10]) + assert m.extract([0, 3], [0, 0, 2]) == Matrix(2, 3, [0, 0, 2, 9, 9, 11]) + assert m.extract(range(4), range(3)) == m + raises(IndexError, lambda: m.extract([4], [0])) + raises(IndexError, lambda: m.extract([0], [3])) + + +def test_hstack(): + m = ShapingOnlyMatrix(4, 3, lambda i, j: i*3 + j) + m2 = ShapingOnlyMatrix(3, 4, lambda i, j: i*3 + j) + assert m == m.hstack(m) + assert m.hstack(m, m, m) == ShapingOnlyMatrix.hstack(m, m, m) == Matrix([ + [0, 1, 2, 0, 1, 2, 0, 1, 2], + [3, 4, 5, 3, 4, 5, 3, 4, 5], + [6, 7, 8, 6, 7, 8, 6, 7, 8], + [9, 10, 11, 9, 10, 11, 9, 10, 11]]) + raises(ShapeError, lambda: m.hstack(m, m2)) + assert Matrix.hstack() == Matrix() + + # test regression #12938 + M1 = Matrix.zeros(0, 0) + M2 = Matrix.zeros(0, 1) + M3 = Matrix.zeros(0, 2) + M4 = Matrix.zeros(0, 3) + m = ShapingOnlyMatrix.hstack(M1, M2, M3, M4) + assert m.rows == 0 and m.cols == 6 + + +def test_vstack(): + m = ShapingOnlyMatrix(4, 3, lambda i, j: i*3 + j) + m2 = ShapingOnlyMatrix(3, 4, lambda i, j: i*3 + j) + assert m == m.vstack(m) + assert m.vstack(m, m, m) == ShapingOnlyMatrix.vstack(m, m, m) == Matrix([ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11], + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11], + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11]]) + raises(ShapeError, lambda: m.vstack(m, m2)) + assert Matrix.vstack() == Matrix() + + +# PropertiesOnlyMatrix tests +def test_atoms(): + m = PropertiesOnlyMatrix(2, 2, [1, 2, x, 1 - 1/x]) + assert m.atoms() == {S.One, S(2), S.NegativeOne, x} + assert m.atoms(Symbol) == {x} + + +def test_free_symbols(): + assert PropertiesOnlyMatrix([[x], [0]]).free_symbols == {x} + + +def test_has(): + A = PropertiesOnlyMatrix(((x, y), (2, 3))) + assert A.has(x) + assert not A.has(z) + assert A.has(Symbol) + + A = PropertiesOnlyMatrix(((2, y), (2, 3))) + assert not A.has(x) + + +def test_is_anti_symmetric(): + x = symbols('x') + assert PropertiesOnlyMatrix(2, 1, [1, 2]).is_anti_symmetric() is False + m = PropertiesOnlyMatrix(3, 3, [0, x**2 + 2*x + 1, y, -(x + 1)**2, 0, x*y, -y, -x*y, 0]) + assert m.is_anti_symmetric() is True + assert m.is_anti_symmetric(simplify=False) is False + assert m.is_anti_symmetric(simplify=lambda x: x) is False + + m = PropertiesOnlyMatrix(3, 3, [x.expand() for x in m]) + assert m.is_anti_symmetric(simplify=False) is True + m = PropertiesOnlyMatrix(3, 3, [x.expand() for x in [S.One] + list(m)[1:]]) + assert m.is_anti_symmetric() is False + + +def test_diagonal_symmetrical(): + m = PropertiesOnlyMatrix(2, 2, [0, 1, 1, 0]) + assert not m.is_diagonal() + assert m.is_symmetric() + assert m.is_symmetric(simplify=False) + + m = PropertiesOnlyMatrix(2, 2, [1, 0, 0, 1]) + assert m.is_diagonal() + + m = PropertiesOnlyMatrix(3, 3, diag(1, 2, 3)) + assert m.is_diagonal() + assert m.is_symmetric() + + m = PropertiesOnlyMatrix(3, 3, [1, 0, 0, 0, 2, 0, 0, 0, 3]) + assert m == diag(1, 2, 3) + + m = PropertiesOnlyMatrix(2, 3, zeros(2, 3)) + assert not m.is_symmetric() + assert m.is_diagonal() + + m = PropertiesOnlyMatrix(((5, 0), (0, 6), (0, 0))) + assert m.is_diagonal() + + m = PropertiesOnlyMatrix(((5, 0, 0), (0, 6, 0))) + assert m.is_diagonal() + + m = Matrix(3, 3, [1, x**2 + 2*x + 1, y, (x + 1)**2, 2, 0, y, 0, 3]) + assert m.is_symmetric() + assert not m.is_symmetric(simplify=False) + assert m.expand().is_symmetric(simplify=False) + + +def test_is_hermitian(): + a = PropertiesOnlyMatrix([[1, I], [-I, 1]]) + assert a.is_hermitian + a = PropertiesOnlyMatrix([[2*I, I], [-I, 1]]) + assert a.is_hermitian is False + a = PropertiesOnlyMatrix([[x, I], [-I, 1]]) + assert a.is_hermitian is None + a = PropertiesOnlyMatrix([[x, 1], [-I, 1]]) + assert a.is_hermitian is False + + +def test_is_Identity(): + assert eye_Properties(3).is_Identity + assert not PropertiesOnlyMatrix(zeros(3)).is_Identity + assert not PropertiesOnlyMatrix(ones(3)).is_Identity + # issue 6242 + assert not PropertiesOnlyMatrix([[1, 0, 0]]).is_Identity + + +def test_is_symbolic(): + a = PropertiesOnlyMatrix([[x, x], [x, x]]) + assert a.is_symbolic() is True + a = PropertiesOnlyMatrix([[1, 2, 3, 4], [5, 6, 7, 8]]) + assert a.is_symbolic() is False + a = PropertiesOnlyMatrix([[1, 2, 3, 4], [5, 6, x, 8]]) + assert a.is_symbolic() is True + a = PropertiesOnlyMatrix([[1, x, 3]]) + assert a.is_symbolic() is True + a = PropertiesOnlyMatrix([[1, 2, 3]]) + assert a.is_symbolic() is False + a = PropertiesOnlyMatrix([[1], [x], [3]]) + assert a.is_symbolic() is True + a = PropertiesOnlyMatrix([[1], [2], [3]]) + assert a.is_symbolic() is False + + +def test_is_upper(): + a = PropertiesOnlyMatrix([[1, 2, 3]]) + assert a.is_upper is True + a = PropertiesOnlyMatrix([[1], [2], [3]]) + assert a.is_upper is False + + +def test_is_lower(): + a = PropertiesOnlyMatrix([[1, 2, 3]]) + assert a.is_lower is False + a = PropertiesOnlyMatrix([[1], [2], [3]]) + assert a.is_lower is True + + +def test_is_square(): + m = PropertiesOnlyMatrix([[1], [1]]) + m2 = PropertiesOnlyMatrix([[2, 2], [2, 2]]) + assert not m.is_square + assert m2.is_square + + +def test_is_symmetric(): + m = PropertiesOnlyMatrix(2, 2, [0, 1, 1, 0]) + assert m.is_symmetric() + m = PropertiesOnlyMatrix(2, 2, [0, 1, 0, 1]) + assert not m.is_symmetric() + + +def test_is_hessenberg(): + A = PropertiesOnlyMatrix([[3, 4, 1], [2, 4, 5], [0, 1, 2]]) + assert A.is_upper_hessenberg + A = PropertiesOnlyMatrix(3, 3, [3, 2, 0, 4, 4, 1, 1, 5, 2]) + assert A.is_lower_hessenberg + A = PropertiesOnlyMatrix(3, 3, [3, 2, -1, 4, 4, 1, 1, 5, 2]) + assert A.is_lower_hessenberg is False + assert A.is_upper_hessenberg is False + + A = PropertiesOnlyMatrix([[3, 4, 1], [2, 4, 5], [3, 1, 2]]) + assert not A.is_upper_hessenberg + + +def test_is_zero(): + assert PropertiesOnlyMatrix(0, 0, []).is_zero_matrix + assert PropertiesOnlyMatrix([[0, 0], [0, 0]]).is_zero_matrix + assert PropertiesOnlyMatrix(zeros(3, 4)).is_zero_matrix + assert not PropertiesOnlyMatrix(eye(3)).is_zero_matrix + assert PropertiesOnlyMatrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert PropertiesOnlyMatrix([[x, 1], [0, 0]]).is_zero_matrix == False + a = Symbol('a', nonzero=True) + assert PropertiesOnlyMatrix([[a, 0], [0, 0]]).is_zero_matrix == False + + +def test_values(): + assert set(PropertiesOnlyMatrix(2, 2, [0, 1, 2, 3] + ).values()) == {1, 2, 3} + x = Symbol('x', real=True) + assert set(PropertiesOnlyMatrix(2, 2, [x, 0, 0, 1] + ).values()) == {x, 1} + + +# OperationsOnlyMatrix tests +def test_applyfunc(): + m0 = OperationsOnlyMatrix(eye(3)) + assert m0.applyfunc(lambda x: 2*x) == eye(3)*2 + assert m0.applyfunc(lambda x: 0) == zeros(3) + assert m0.applyfunc(lambda x: 1) == ones(3) + + +def test_adjoint(): + dat = [[0, I], [1, 0]] + ans = OperationsOnlyMatrix([[0, 1], [-I, 0]]) + assert ans.adjoint() == Matrix(dat) + + +def test_as_real_imag(): + m1 = OperationsOnlyMatrix(2, 2, [1, 2, 3, 4]) + m3 = OperationsOnlyMatrix(2, 2, + [1 + S.ImaginaryUnit, 2 + 2*S.ImaginaryUnit, + 3 + 3*S.ImaginaryUnit, 4 + 4*S.ImaginaryUnit]) + + a, b = m3.as_real_imag() + assert a == m1 + assert b == m1 + + +def test_conjugate(): + M = OperationsOnlyMatrix([[0, I, 5], + [1, 2, 0]]) + + assert M.T == Matrix([[0, 1], + [I, 2], + [5, 0]]) + + assert M.C == Matrix([[0, -I, 5], + [1, 2, 0]]) + assert M.C == M.conjugate() + + assert M.H == M.T.C + assert M.H == Matrix([[ 0, 1], + [-I, 2], + [ 5, 0]]) + + +def test_doit(): + a = OperationsOnlyMatrix([[Add(x, x, evaluate=False)]]) + assert a[0] != 2*x + assert a.doit() == Matrix([[2*x]]) + + +def test_evalf(): + a = OperationsOnlyMatrix(2, 1, [sqrt(5), 6]) + assert all(a.evalf()[i] == a[i].evalf() for i in range(2)) + assert all(a.evalf(2)[i] == a[i].evalf(2) for i in range(2)) + assert all(a.n(2)[i] == a[i].n(2) for i in range(2)) + + +def test_expand(): + m0 = OperationsOnlyMatrix([[x*(x + y), 2], [((x + y)*y)*x, x*(y + x*(x + y))]]) + # Test if expand() returns a matrix + m1 = m0.expand() + assert m1 == Matrix( + [[x*y + x**2, 2], [x*y**2 + y*x**2, x*y + y*x**2 + x**3]]) + + a = Symbol('a', real=True) + + assert OperationsOnlyMatrix(1, 1, [exp(I*a)]).expand(complex=True) == \ + Matrix([cos(a) + I*sin(a)]) + + +def test_refine(): + m0 = OperationsOnlyMatrix([[Abs(x)**2, sqrt(x**2)], + [sqrt(x**2)*Abs(y)**2, sqrt(y**2)*Abs(x)**2]]) + m1 = m0.refine(Q.real(x) & Q.real(y)) + assert m1 == Matrix([[x**2, Abs(x)], [y**2*Abs(x), x**2*Abs(y)]]) + + m1 = m0.refine(Q.positive(x) & Q.positive(y)) + assert m1 == Matrix([[x**2, x], [x*y**2, x**2*y]]) + + m1 = m0.refine(Q.negative(x) & Q.negative(y)) + assert m1 == Matrix([[x**2, -x], [-x*y**2, -x**2*y]]) + + +def test_replace(): + F, G = symbols('F, G', cls=Function) + K = OperationsOnlyMatrix(2, 2, lambda i, j: G(i+j)) + M = OperationsOnlyMatrix(2, 2, lambda i, j: F(i+j)) + N = M.replace(F, G) + assert N == K + + +def test_replace_map(): + F, G = symbols('F, G', cls=Function) + K = OperationsOnlyMatrix(2, 2, [(G(0), {F(0): G(0)}), (G(1), {F(1): G(1)}), (G(1), {F(1) \ + : G(1)}), (G(2), {F(2): G(2)})]) + M = OperationsOnlyMatrix(2, 2, lambda i, j: F(i+j)) + N = M.replace(F, G, True) + assert N == K + + +def test_rot90(): + A = Matrix([[1, 2], [3, 4]]) + assert A == A.rot90(0) == A.rot90(4) + assert A.rot90(2) == A.rot90(-2) == A.rot90(6) == Matrix(((4, 3), (2, 1))) + assert A.rot90(3) == A.rot90(-1) == A.rot90(7) == Matrix(((2, 4), (1, 3))) + assert A.rot90() == A.rot90(-7) == A.rot90(-3) == Matrix(((3, 1), (4, 2))) + +def test_simplify(): + n = Symbol('n') + f = Function('f') + + M = OperationsOnlyMatrix([[ 1/x + 1/y, (x + x*y) / x ], + [ (f(x) + y*f(x))/f(x), 2 * (1/n - cos(n * pi)/n) / pi ]]) + assert M.simplify() == Matrix([[ (x + y)/(x * y), 1 + y ], + [ 1 + y, 2*((1 - 1*cos(pi*n))/(pi*n)) ]]) + eq = (1 + x)**2 + M = OperationsOnlyMatrix([[eq]]) + assert M.simplify() == Matrix([[eq]]) + assert M.simplify(ratio=oo) == Matrix([[eq.simplify(ratio=oo)]]) + + # https://github.com/sympy/sympy/issues/19353 + m = Matrix([[30, 2], [3, 4]]) + assert (1/(m.trace())).simplify() == Rational(1, 34) + + +def test_subs(): + assert OperationsOnlyMatrix([[1, x], [x, 4]]).subs(x, 5) == Matrix([[1, 5], [5, 4]]) + assert OperationsOnlyMatrix([[x, 2], [x + y, 4]]).subs([[x, -1], [y, -2]]) == \ + Matrix([[-1, 2], [-3, 4]]) + assert OperationsOnlyMatrix([[x, 2], [x + y, 4]]).subs([(x, -1), (y, -2)]) == \ + Matrix([[-1, 2], [-3, 4]]) + assert OperationsOnlyMatrix([[x, 2], [x + y, 4]]).subs({x: -1, y: -2}) == \ + Matrix([[-1, 2], [-3, 4]]) + assert OperationsOnlyMatrix([[x*y]]).subs({x: y - 1, y: x - 1}, simultaneous=True) == \ + Matrix([[(x - 1)*(y - 1)]]) + + +def test_trace(): + M = OperationsOnlyMatrix([[1, 0, 0], + [0, 5, 0], + [0, 0, 8]]) + assert M.trace() == 14 + + +def test_xreplace(): + assert OperationsOnlyMatrix([[1, x], [x, 4]]).xreplace({x: 5}) == \ + Matrix([[1, 5], [5, 4]]) + assert OperationsOnlyMatrix([[x, 2], [x + y, 4]]).xreplace({x: -1, y: -2}) == \ + Matrix([[-1, 2], [-3, 4]]) + + +def test_permute(): + a = OperationsOnlyMatrix(3, 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + + raises(IndexError, lambda: a.permute([[0, 5]])) + raises(ValueError, lambda: a.permute(Symbol('x'))) + b = a.permute_rows([[0, 2], [0, 1]]) + assert a.permute([[0, 2], [0, 1]]) == b == Matrix([ + [5, 6, 7, 8], + [9, 10, 11, 12], + [1, 2, 3, 4]]) + + b = a.permute_cols([[0, 2], [0, 1]]) + assert a.permute([[0, 2], [0, 1]], orientation='cols') == b ==\ + Matrix([ + [ 2, 3, 1, 4], + [ 6, 7, 5, 8], + [10, 11, 9, 12]]) + + b = a.permute_cols([[0, 2], [0, 1]], direction='backward') + assert a.permute([[0, 2], [0, 1]], orientation='cols', direction='backward') == b ==\ + Matrix([ + [ 3, 1, 2, 4], + [ 7, 5, 6, 8], + [11, 9, 10, 12]]) + + assert a.permute([1, 2, 0, 3]) == Matrix([ + [5, 6, 7, 8], + [9, 10, 11, 12], + [1, 2, 3, 4]]) + + from sympy.combinatorics import Permutation + assert a.permute(Permutation([1, 2, 0, 3])) == Matrix([ + [5, 6, 7, 8], + [9, 10, 11, 12], + [1, 2, 3, 4]]) + +def test_upper_triangular(): + + A = OperationsOnlyMatrix([ + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1] + ]) + + R = A.upper_triangular(2) + assert R == OperationsOnlyMatrix([ + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0], + [0, 0, 0, 0] + ]) + + R = A.upper_triangular(-2) + assert R == OperationsOnlyMatrix([ + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [0, 1, 1, 1] + ]) + + R = A.upper_triangular() + assert R == OperationsOnlyMatrix([ + [1, 1, 1, 1], + [0, 1, 1, 1], + [0, 0, 1, 1], + [0, 0, 0, 1] + ]) + +def test_lower_triangular(): + A = OperationsOnlyMatrix([ + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1] + ]) + + L = A.lower_triangular() + assert L == ArithmeticOnlyMatrix([ + [1, 0, 0, 0], + [1, 1, 0, 0], + [1, 1, 1, 0], + [1, 1, 1, 1]]) + + L = A.lower_triangular(2) + assert L == ArithmeticOnlyMatrix([ + [1, 1, 1, 0], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1] + ]) + + L = A.lower_triangular(-2) + assert L == ArithmeticOnlyMatrix([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 0, 0, 0], + [1, 1, 0, 0] + ]) + + +# ArithmeticOnlyMatrix tests +def test_abs(): + m = ArithmeticOnlyMatrix([[1, -2], [x, y]]) + assert abs(m) == ArithmeticOnlyMatrix([[1, 2], [Abs(x), Abs(y)]]) + + +def test_add(): + m = ArithmeticOnlyMatrix([[1, 2, 3], [x, y, x], [2*y, -50, z*x]]) + assert m + m == ArithmeticOnlyMatrix([[2, 4, 6], [2*x, 2*y, 2*x], [4*y, -100, 2*z*x]]) + n = ArithmeticOnlyMatrix(1, 2, [1, 2]) + raises(ShapeError, lambda: m + n) + + +def test_multiplication(): + a = ArithmeticOnlyMatrix(( + (1, 2), + (3, 1), + (0, 6), + )) + + b = ArithmeticOnlyMatrix(( + (1, 2), + (3, 0), + )) + + raises(ShapeError, lambda: b*a) + raises(TypeError, lambda: a*{}) + + c = a*b + assert c[0, 0] == 7 + assert c[0, 1] == 2 + assert c[1, 0] == 6 + assert c[1, 1] == 6 + assert c[2, 0] == 18 + assert c[2, 1] == 0 + + try: + eval('c = a @ b') + except SyntaxError: + pass + else: + assert c[0, 0] == 7 + assert c[0, 1] == 2 + assert c[1, 0] == 6 + assert c[1, 1] == 6 + assert c[2, 0] == 18 + assert c[2, 1] == 0 + + h = a.multiply_elementwise(c) + assert h == matrix_multiply_elementwise(a, c) + assert h[0, 0] == 7 + assert h[0, 1] == 4 + assert h[1, 0] == 18 + assert h[1, 1] == 6 + assert h[2, 0] == 0 + assert h[2, 1] == 0 + raises(ShapeError, lambda: a.multiply_elementwise(b)) + + c = b * Symbol("x") + assert isinstance(c, ArithmeticOnlyMatrix) + assert c[0, 0] == x + assert c[0, 1] == 2*x + assert c[1, 0] == 3*x + assert c[1, 1] == 0 + + c2 = x * b + assert c == c2 + + c = 5 * b + assert isinstance(c, ArithmeticOnlyMatrix) + assert c[0, 0] == 5 + assert c[0, 1] == 2*5 + assert c[1, 0] == 3*5 + assert c[1, 1] == 0 + + try: + eval('c = 5 @ b') + except SyntaxError: + pass + else: + assert isinstance(c, ArithmeticOnlyMatrix) + assert c[0, 0] == 5 + assert c[0, 1] == 2*5 + assert c[1, 0] == 3*5 + assert c[1, 1] == 0 + + # https://github.com/sympy/sympy/issues/22353 + A = Matrix(ones(3, 1)) + _h = -Rational(1, 2) + B = Matrix([_h, _h, _h]) + assert A.multiply_elementwise(B) == Matrix([ + [_h], + [_h], + [_h]]) + + +def test_matmul(): + a = Matrix([[1, 2], [3, 4]]) + + assert a.__matmul__(2) == NotImplemented + + assert a.__rmatmul__(2) == NotImplemented + + #This is done this way because @ is only supported in Python 3.5+ + #To check 2@a case + try: + eval('2 @ a') + except SyntaxError: + pass + except TypeError: #TypeError is raised in case of NotImplemented is returned + pass + + #Check a@2 case + try: + eval('a @ 2') + except SyntaxError: + pass + except TypeError: #TypeError is raised in case of NotImplemented is returned + pass + + +def test_non_matmul(): + """ + Test that if explicitly specified as non-matrix, mul reverts + to scalar multiplication. + """ + class foo(Expr): + is_Matrix=False + is_MatrixLike=False + shape = (1, 1) + + A = Matrix([[1, 2], [3, 4]]) + b = foo() + assert b*A == Matrix([[b, 2*b], [3*b, 4*b]]) + assert A*b == Matrix([[b, 2*b], [3*b, 4*b]]) + + +def test_power(): + raises(NonSquareMatrixError, lambda: Matrix((1, 2))**2) + + A = ArithmeticOnlyMatrix([[2, 3], [4, 5]]) + assert (A**5)[:] == (6140, 8097, 10796, 14237) + A = ArithmeticOnlyMatrix([[2, 1, 3], [4, 2, 4], [6, 12, 1]]) + assert (A**3)[:] == (290, 262, 251, 448, 440, 368, 702, 954, 433) + assert A**0 == eye(3) + assert A**1 == A + assert (ArithmeticOnlyMatrix([[2]]) ** 100)[0, 0] == 2**100 + assert ArithmeticOnlyMatrix([[1, 2], [3, 4]])**Integer(2) == ArithmeticOnlyMatrix([[7, 10], [15, 22]]) + A = Matrix([[1,2],[4,5]]) + assert A.pow(20, method='cayley') == A.pow(20, method='multiply') + +def test_neg(): + n = ArithmeticOnlyMatrix(1, 2, [1, 2]) + assert -n == ArithmeticOnlyMatrix(1, 2, [-1, -2]) + + +def test_sub(): + n = ArithmeticOnlyMatrix(1, 2, [1, 2]) + assert n - n == ArithmeticOnlyMatrix(1, 2, [0, 0]) + + +def test_div(): + n = ArithmeticOnlyMatrix(1, 2, [1, 2]) + assert n/2 == ArithmeticOnlyMatrix(1, 2, [S.Half, S(2)/2]) + +# SpecialOnlyMatrix tests +def test_eye(): + assert list(SpecialOnlyMatrix.eye(2, 2)) == [1, 0, 0, 1] + assert list(SpecialOnlyMatrix.eye(2)) == [1, 0, 0, 1] + assert type(SpecialOnlyMatrix.eye(2)) == SpecialOnlyMatrix + assert type(SpecialOnlyMatrix.eye(2, cls=Matrix)) == Matrix + + +def test_ones(): + assert list(SpecialOnlyMatrix.ones(2, 2)) == [1, 1, 1, 1] + assert list(SpecialOnlyMatrix.ones(2)) == [1, 1, 1, 1] + assert SpecialOnlyMatrix.ones(2, 3) == Matrix([[1, 1, 1], [1, 1, 1]]) + assert type(SpecialOnlyMatrix.ones(2)) == SpecialOnlyMatrix + assert type(SpecialOnlyMatrix.ones(2, cls=Matrix)) == Matrix + + +def test_zeros(): + assert list(SpecialOnlyMatrix.zeros(2, 2)) == [0, 0, 0, 0] + assert list(SpecialOnlyMatrix.zeros(2)) == [0, 0, 0, 0] + assert SpecialOnlyMatrix.zeros(2, 3) == Matrix([[0, 0, 0], [0, 0, 0]]) + assert type(SpecialOnlyMatrix.zeros(2)) == SpecialOnlyMatrix + assert type(SpecialOnlyMatrix.zeros(2, cls=Matrix)) == Matrix + + +def test_diag_make(): + diag = SpecialOnlyMatrix.diag + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + assert diag(a, b, b) == Matrix([ + [1, 2, 0, 0, 0, 0], + [2, 3, 0, 0, 0, 0], + [0, 0, 3, x, 0, 0], + [0, 0, y, 3, 0, 0], + [0, 0, 0, 0, 3, x], + [0, 0, 0, 0, y, 3], + ]) + assert diag(a, b, c) == Matrix([ + [1, 2, 0, 0, 0, 0, 0], + [2, 3, 0, 0, 0, 0, 0], + [0, 0, 3, x, 0, 0, 0], + [0, 0, y, 3, 0, 0, 0], + [0, 0, 0, 0, 3, x, 3], + [0, 0, 0, 0, y, 3, z], + [0, 0, 0, 0, x, y, z], + ]) + assert diag(a, c, b) == Matrix([ + [1, 2, 0, 0, 0, 0, 0], + [2, 3, 0, 0, 0, 0, 0], + [0, 0, 3, x, 3, 0, 0], + [0, 0, y, 3, z, 0, 0], + [0, 0, x, y, z, 0, 0], + [0, 0, 0, 0, 0, 3, x], + [0, 0, 0, 0, 0, y, 3], + ]) + a = Matrix([x, y, z]) + b = Matrix([[1, 2], [3, 4]]) + c = Matrix([[5, 6]]) + # this "wandering diagonal" is what makes this + # a block diagonal where each block is independent + # of the others + assert diag(a, 7, b, c) == Matrix([ + [x, 0, 0, 0, 0, 0], + [y, 0, 0, 0, 0, 0], + [z, 0, 0, 0, 0, 0], + [0, 7, 0, 0, 0, 0], + [0, 0, 1, 2, 0, 0], + [0, 0, 3, 4, 0, 0], + [0, 0, 0, 0, 5, 6]]) + raises(ValueError, lambda: diag(a, 7, b, c, rows=5)) + assert diag(1) == Matrix([[1]]) + assert diag(1, rows=2) == Matrix([[1, 0], [0, 0]]) + assert diag(1, cols=2) == Matrix([[1, 0], [0, 0]]) + assert diag(1, rows=3, cols=2) == Matrix([[1, 0], [0, 0], [0, 0]]) + assert diag(*[2, 3]) == Matrix([ + [2, 0], + [0, 3]]) + assert diag(Matrix([2, 3])) == Matrix([ + [2], + [3]]) + assert diag([1, [2, 3], 4], unpack=False) == \ + diag([[1], [2, 3], [4]], unpack=False) == Matrix([ + [1, 0], + [2, 3], + [4, 0]]) + assert type(diag(1)) == SpecialOnlyMatrix + assert type(diag(1, cls=Matrix)) == Matrix + assert Matrix.diag([1, 2, 3]) == Matrix.diag(1, 2, 3) + assert Matrix.diag([1, 2, 3], unpack=False).shape == (3, 1) + assert Matrix.diag([[1, 2, 3]]).shape == (3, 1) + assert Matrix.diag([[1, 2, 3]], unpack=False).shape == (1, 3) + assert Matrix.diag([[[1, 2, 3]]]).shape == (1, 3) + # kerning can be used to move the starting point + assert Matrix.diag(ones(0, 2), 1, 2) == Matrix([ + [0, 0, 1, 0], + [0, 0, 0, 2]]) + assert Matrix.diag(ones(2, 0), 1, 2) == Matrix([ + [0, 0], + [0, 0], + [1, 0], + [0, 2]]) + + +def test_diagonal(): + m = Matrix(3, 3, range(9)) + d = m.diagonal() + assert d == m.diagonal(0) + assert tuple(d) == (0, 4, 8) + assert tuple(m.diagonal(1)) == (1, 5) + assert tuple(m.diagonal(-1)) == (3, 7) + assert tuple(m.diagonal(2)) == (2,) + assert type(m.diagonal()) == type(m) + s = SparseMatrix(3, 3, {(1, 1): 1}) + assert type(s.diagonal()) == type(s) + assert type(m) != type(s) + raises(ValueError, lambda: m.diagonal(3)) + raises(ValueError, lambda: m.diagonal(-3)) + raises(ValueError, lambda: m.diagonal(pi)) + M = ones(2, 3) + assert banded({i: list(M.diagonal(i)) + for i in range(1-M.rows, M.cols)}) == M + + +def test_jordan_block(): + assert SpecialOnlyMatrix.jordan_block(3, 2) == SpecialOnlyMatrix.jordan_block(3, eigenvalue=2) \ + == SpecialOnlyMatrix.jordan_block(size=3, eigenvalue=2) \ + == SpecialOnlyMatrix.jordan_block(3, 2, band='upper') \ + == SpecialOnlyMatrix.jordan_block( + size=3, eigenval=2, eigenvalue=2) \ + == Matrix([ + [2, 1, 0], + [0, 2, 1], + [0, 0, 2]]) + + assert SpecialOnlyMatrix.jordan_block(3, 2, band='lower') == Matrix([ + [2, 0, 0], + [1, 2, 0], + [0, 1, 2]]) + # missing eigenvalue + raises(ValueError, lambda: SpecialOnlyMatrix.jordan_block(2)) + # non-integral size + raises(ValueError, lambda: SpecialOnlyMatrix.jordan_block(3.5, 2)) + # size not specified + raises(ValueError, lambda: SpecialOnlyMatrix.jordan_block(eigenvalue=2)) + # inconsistent eigenvalue + raises(ValueError, + lambda: SpecialOnlyMatrix.jordan_block( + eigenvalue=2, eigenval=4)) + + # Using alias keyword + assert SpecialOnlyMatrix.jordan_block(size=3, eigenvalue=2) == \ + SpecialOnlyMatrix.jordan_block(size=3, eigenval=2) + + +def test_orthogonalize(): + m = Matrix([[1, 2], [3, 4]]) + assert m.orthogonalize(Matrix([[2], [1]])) == [Matrix([[2], [1]])] + assert m.orthogonalize(Matrix([[2], [1]]), normalize=True) == \ + [Matrix([[2*sqrt(5)/5], [sqrt(5)/5]])] + assert m.orthogonalize(Matrix([[1], [2]]), Matrix([[-1], [4]])) == \ + [Matrix([[1], [2]]), Matrix([[Rational(-12, 5)], [Rational(6, 5)]])] + assert m.orthogonalize(Matrix([[0], [0]]), Matrix([[-1], [4]])) == \ + [Matrix([[-1], [4]])] + assert m.orthogonalize(Matrix([[0], [0]])) == [] + + n = Matrix([[9, 1, 9], [3, 6, 10], [8, 5, 2]]) + vecs = [Matrix([[-5], [1]]), Matrix([[-5], [2]]), Matrix([[-5], [-2]])] + assert n.orthogonalize(*vecs) == \ + [Matrix([[-5], [1]]), Matrix([[Rational(5, 26)], [Rational(25, 26)]])] + + vecs = [Matrix([0, 0, 0]), Matrix([1, 2, 3]), Matrix([1, 4, 5])] + raises(ValueError, lambda: Matrix.orthogonalize(*vecs, rankcheck=True)) + + vecs = [Matrix([1, 2, 3]), Matrix([4, 5, 6]), Matrix([7, 8, 9])] + raises(ValueError, lambda: Matrix.orthogonalize(*vecs, rankcheck=True)) + +def test_wilkinson(): + + wminus, wplus = Matrix.wilkinson(1) + assert wminus == Matrix([ + [-1, 1, 0], + [1, 0, 1], + [0, 1, 1]]) + assert wplus == Matrix([ + [1, 1, 0], + [1, 0, 1], + [0, 1, 1]]) + + wminus, wplus = Matrix.wilkinson(3) + assert wminus == Matrix([ + [-3, 1, 0, 0, 0, 0, 0], + [1, -2, 1, 0, 0, 0, 0], + [0, 1, -1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0], + [0, 0, 0, 0, 1, 2, 1], + + [0, 0, 0, 0, 0, 1, 3]]) + + assert wplus == Matrix([ + [3, 1, 0, 0, 0, 0, 0], + [1, 2, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0], + [0, 0, 0, 0, 1, 2, 1], + [0, 0, 0, 0, 0, 1, 3]]) + + +# CalculusOnlyMatrix tests +@XFAIL +def test_diff(): + x, y = symbols('x y') + m = CalculusOnlyMatrix(2, 1, [x, y]) + # TODO: currently not working as ``_MinimalMatrix`` cannot be sympified: + assert m.diff(x) == Matrix(2, 1, [1, 0]) + + +def test_integrate(): + x, y = symbols('x y') + m = CalculusOnlyMatrix(2, 1, [x, y]) + assert m.integrate(x) == Matrix(2, 1, [x**2/2, y*x]) + + +def test_jacobian2(): + rho, phi = symbols("rho,phi") + X = CalculusOnlyMatrix(3, 1, [rho*cos(phi), rho*sin(phi), rho**2]) + Y = CalculusOnlyMatrix(2, 1, [rho, phi]) + J = Matrix([ + [cos(phi), -rho*sin(phi)], + [sin(phi), rho*cos(phi)], + [ 2*rho, 0], + ]) + assert X.jacobian(Y) == J + + m = CalculusOnlyMatrix(2, 2, [1, 2, 3, 4]) + m2 = CalculusOnlyMatrix(4, 1, [1, 2, 3, 4]) + raises(TypeError, lambda: m.jacobian(Matrix([1, 2]))) + raises(TypeError, lambda: m2.jacobian(m)) + + +def test_limit(): + x, y = symbols('x y') + m = CalculusOnlyMatrix(2, 1, [1/x, y]) + assert m.limit(x, 5) == Matrix(2, 1, [Rational(1, 5), y]) + + +def test_issue_13774(): + M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + v = [1, 1, 1] + raises(TypeError, lambda: M*v) + raises(TypeError, lambda: v*M) + +def test_companion(): + x = Symbol('x') + y = Symbol('y') + raises(ValueError, lambda: Matrix.companion(1)) + raises(ValueError, lambda: Matrix.companion(Poly([1], x))) + raises(ValueError, lambda: Matrix.companion(Poly([2, 1], x))) + raises(ValueError, lambda: Matrix.companion(Poly(x*y, [x, y]))) + + c0, c1, c2 = symbols('c0:3') + assert Matrix.companion(Poly([1, c0], x)) == Matrix([-c0]) + assert Matrix.companion(Poly([1, c1, c0], x)) == \ + Matrix([[0, -c0], [1, -c1]]) + assert Matrix.companion(Poly([1, c2, c1, c0], x)) == \ + Matrix([[0, 0, -c0], [1, 0, -c1], [0, 1, -c2]]) + +def test_issue_10589(): + x, y, z = symbols("x, y z") + M1 = Matrix([x, y, z]) + M1 = M1.subs(zip([x, y, z], [1, 2, 3])) + assert M1 == Matrix([[1], [2], [3]]) + + M2 = Matrix([[x, x, x, x, x], [x, x, x, x, x], [x, x, x, x, x]]) + M2 = M2.subs(zip([x], [1])) + assert M2 == Matrix([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]]) + +def test_rmul_pr19860(): + class Foo(ImmutableDenseMatrix): + _op_priority = MutableDenseMatrix._op_priority + 0.01 + + a = Matrix(2, 2, [1, 2, 3, 4]) + b = Foo(2, 2, [1, 2, 3, 4]) + + # This would throw a RecursionError: maximum recursion depth + # since b always has higher priority even after a.as_mutable() + c = a*b + + assert isinstance(c, Foo) + assert c == Matrix([[7, 10], [15, 22]]) + + +def test_issue_18956(): + A = Array([[1, 2], [3, 4]]) + B = Matrix([[1,2],[3,4]]) + raises(TypeError, lambda: B + A) + raises(TypeError, lambda: A + B) + + +def test__eq__(): + class My(object): + def __iter__(self): + yield 1 + yield 2 + return + def __getitem__(self, i): + return list(self)[i] + a = Matrix(2, 1, [1, 2]) + assert a != My() + class My_sympy(My): + def _sympy_(self): + return Matrix(self) + assert a == My_sympy() diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_decompositions.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_decompositions.py new file mode 100644 index 0000000000000000000000000000000000000000..d169ec3a8846fed786981e62d932fd860b6d4951 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_decompositions.py @@ -0,0 +1,474 @@ +from sympy.core.function import expand_mul +from sympy.core.numbers import I, Rational +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.complexes import Abs +from sympy.simplify.simplify import simplify +from sympy.matrices.exceptions import NonSquareMatrixError +from sympy.matrices import Matrix, zeros, eye, SparseMatrix +from sympy.abc import x, y, z +from sympy.testing.pytest import raises, slow +from sympy.testing.matrices import allclose + + +def test_LUdecomp(): + testmat = Matrix([[0, 2, 5, 3], + [3, 3, 7, 4], + [8, 4, 0, 2], + [-2, 6, 3, 4]]) + L, U, p = testmat.LUdecomposition() + assert L.is_lower + assert U.is_upper + assert (L*U).permute_rows(p, 'backward') - testmat == zeros(4) + + testmat = Matrix([[6, -2, 7, 4], + [0, 3, 6, 7], + [1, -2, 7, 4], + [-9, 2, 6, 3]]) + L, U, p = testmat.LUdecomposition() + assert L.is_lower + assert U.is_upper + assert (L*U).permute_rows(p, 'backward') - testmat == zeros(4) + + # non-square + testmat = Matrix([[1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10, 11, 12]]) + L, U, p = testmat.LUdecomposition(rankcheck=False) + assert L.is_lower + assert U.is_upper + assert (L*U).permute_rows(p, 'backward') - testmat == zeros(4, 3) + + # square and singular + testmat = Matrix([[1, 2, 3], + [2, 4, 6], + [4, 5, 6]]) + L, U, p = testmat.LUdecomposition(rankcheck=False) + assert L.is_lower + assert U.is_upper + assert (L*U).permute_rows(p, 'backward') - testmat == zeros(3) + + M = Matrix(((1, x, 1), (2, y, 0), (y, 0, z))) + L, U, p = M.LUdecomposition() + assert L.is_lower + assert U.is_upper + assert (L*U).permute_rows(p, 'backward') - M == zeros(3) + + mL = Matrix(( + (1, 0, 0), + (2, 3, 0), + )) + assert mL.is_lower is True + assert mL.is_upper is False + mU = Matrix(( + (1, 2, 3), + (0, 4, 5), + )) + assert mU.is_lower is False + assert mU.is_upper is True + + # test FF LUdecomp + M = Matrix([[1, 3, 3], + [3, 2, 6], + [3, 2, 2]]) + P, L, Dee, U = M.LUdecompositionFF() + assert P*M == L*Dee.inv()*U + + M = Matrix([[1, 2, 3, 4], + [3, -1, 2, 3], + [3, 1, 3, -2], + [6, -1, 0, 2]]) + P, L, Dee, U = M.LUdecompositionFF() + assert P*M == L*Dee.inv()*U + + M = Matrix([[0, 0, 1], + [2, 3, 0], + [3, 1, 4]]) + P, L, Dee, U = M.LUdecompositionFF() + assert P*M == L*Dee.inv()*U + + # issue 15794 + M = Matrix( + [[1, 2, 3], + [4, 5, 6], + [7, 8, 9]] + ) + raises(ValueError, lambda : M.LUdecomposition_Simple(rankcheck=True)) + +def test_singular_value_decompositionD(): + A = Matrix([[1, 2], [2, 1]]) + U, S, V = A.singular_value_decomposition() + assert U * S * V.T == A + assert U.T * U == eye(U.cols) + assert V.T * V == eye(V.cols) + + B = Matrix([[1, 2]]) + U, S, V = B.singular_value_decomposition() + + assert U * S * V.T == B + assert U.T * U == eye(U.cols) + assert V.T * V == eye(V.cols) + + C = Matrix([ + [1, 0, 0, 0, 2], + [0, 0, 3, 0, 0], + [0, 0, 0, 0, 0], + [0, 2, 0, 0, 0], + ]) + + U, S, V = C.singular_value_decomposition() + + assert U * S * V.T == C + assert U.T * U == eye(U.cols) + assert V.T * V == eye(V.cols) + + D = Matrix([[Rational(1, 3), sqrt(2)], [0, Rational(1, 4)]]) + U, S, V = D.singular_value_decomposition() + assert simplify(U.T * U) == eye(U.cols) + assert simplify(V.T * V) == eye(V.cols) + assert simplify(U * S * V.T) == D + + +def test_QR(): + A = Matrix([[1, 2], [2, 3]]) + Q, S = A.QRdecomposition() + R = Rational + assert Q == Matrix([ + [ 5**R(-1, 2), (R(2)/5)*(R(1)/5)**R(-1, 2)], + [2*5**R(-1, 2), (-R(1)/5)*(R(1)/5)**R(-1, 2)]]) + assert S == Matrix([[5**R(1, 2), 8*5**R(-1, 2)], [0, (R(1)/5)**R(1, 2)]]) + assert Q*S == A + assert Q.T * Q == eye(2) + + A = Matrix([[1, 1, 1], [1, 1, 3], [2, 3, 4]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[12, 0, -51], [6, 0, 167], [-4, 0, 24]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + x = Symbol('x') + A = Matrix([x]) + Q, R = A.QRdecomposition() + assert Q == Matrix([x / Abs(x)]) + assert R == Matrix([Abs(x)]) + + A = Matrix([[x, 0], [0, x]]) + Q, R = A.QRdecomposition() + assert Q == x / Abs(x) * Matrix([[1, 0], [0, 1]]) + assert R == Abs(x) * Matrix([[1, 0], [0, 1]]) + + +def test_QR_non_square(): + # Narrow (cols < rows) matrices + A = Matrix([[9, 0, 26], [12, 0, -7], [0, 4, 4], [0, -3, -3]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[1, -1, 4], [1, 4, -2], [1, 4, 2], [1, -1, 0]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix(2, 1, [1, 2]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + # Wide (cols > rows) matrices + A = Matrix([[1, 2, 3], [4, 5, 6]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[1, 2, 3, 4], [1, 4, 9, 16], [1, 8, 27, 64]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix(1, 2, [1, 2]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + +def test_QR_trivial(): + # Rank deficient matrices + A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]]).T + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + # Zero rank matrices + A = Matrix([[0, 0, 0]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[0, 0, 0]]).T + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[0, 0, 0], [0, 0, 0]]) + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[0, 0, 0], [0, 0, 0]]).T + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + # Rank deficient matrices with zero norm from beginning columns + A = Matrix([[0, 0, 0], [1, 2, 3]]).T + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[0, 0, 0, 0], [1, 2, 3, 4], [0, 0, 0, 0]]).T + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[0, 0, 0, 0], [1, 2, 3, 4], [0, 0, 0, 0], [2, 4, 6, 8]]).T + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + A = Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0], [1, 2, 3]]).T + Q, R = A.QRdecomposition() + assert Q.T * Q == eye(Q.cols) + assert R.is_upper + assert A == Q*R + + +def test_QR_float(): + A = Matrix([[1, 1], [1, 1.01]]) + Q, R = A.QRdecomposition() + assert allclose(Q * R, A) + assert allclose(Q * Q.T, Matrix.eye(2)) + assert allclose(Q.T * Q, Matrix.eye(2)) + + A = Matrix([[1, 1], [1, 1.001]]) + Q, R = A.QRdecomposition() + assert allclose(Q * R, A) + assert allclose(Q * Q.T, Matrix.eye(2)) + assert allclose(Q.T * Q, Matrix.eye(2)) + + +def test_LUdecomposition_Simple_iszerofunc(): + # Test if callable passed to matrices.LUdecomposition_Simple() as iszerofunc keyword argument is used inside + # matrices.LUdecomposition_Simple() + magic_string = "I got passed in!" + def goofyiszero(value): + raise ValueError(magic_string) + + try: + lu, p = Matrix([[1, 0], [0, 1]]).LUdecomposition_Simple(iszerofunc=goofyiszero) + except ValueError as err: + assert magic_string == err.args[0] + return + + assert False + +def test_LUdecomposition_iszerofunc(): + # Test if callable passed to matrices.LUdecomposition() as iszerofunc keyword argument is used inside + # matrices.LUdecomposition_Simple() + magic_string = "I got passed in!" + def goofyiszero(value): + raise ValueError(magic_string) + + try: + l, u, p = Matrix([[1, 0], [0, 1]]).LUdecomposition(iszerofunc=goofyiszero) + except ValueError as err: + assert magic_string == err.args[0] + return + + assert False + +def test_LDLdecomposition(): + raises(NonSquareMatrixError, lambda: Matrix((1, 2)).LDLdecomposition()) + raises(ValueError, lambda: Matrix(((1, 2), (3, 4))).LDLdecomposition()) + raises(ValueError, lambda: Matrix(((5 + I, 0), (0, 1))).LDLdecomposition()) + raises(ValueError, lambda: Matrix(((1, 5), (5, 1))).LDLdecomposition()) + raises(ValueError, lambda: Matrix(((1, 2), (3, 4))).LDLdecomposition(hermitian=False)) + A = Matrix(((1, 5), (5, 1))) + L, D = A.LDLdecomposition(hermitian=False) + assert L * D * L.T == A + A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + L, D = A.LDLdecomposition() + assert L * D * L.T == A + assert L.is_lower + assert L == Matrix([[1, 0, 0], [ Rational(3, 5), 1, 0], [Rational(-1, 5), Rational(1, 3), 1]]) + assert D.is_diagonal() + assert D == Matrix([[25, 0, 0], [0, 9, 0], [0, 0, 9]]) + A = Matrix(((4, -2*I, 2 + 2*I), (2*I, 2, -1 + I), (2 - 2*I, -1 - I, 11))) + L, D = A.LDLdecomposition() + assert expand_mul(L * D * L.H) == A + assert L.expand() == Matrix([[1, 0, 0], [I/2, 1, 0], [S.Half - I/2, 0, 1]]) + assert D.expand() == Matrix(((4, 0, 0), (0, 1, 0), (0, 0, 9))) + + raises(NonSquareMatrixError, lambda: SparseMatrix((1, 2)).LDLdecomposition()) + raises(ValueError, lambda: SparseMatrix(((1, 2), (3, 4))).LDLdecomposition()) + raises(ValueError, lambda: SparseMatrix(((5 + I, 0), (0, 1))).LDLdecomposition()) + raises(ValueError, lambda: SparseMatrix(((1, 5), (5, 1))).LDLdecomposition()) + raises(ValueError, lambda: SparseMatrix(((1, 2), (3, 4))).LDLdecomposition(hermitian=False)) + A = SparseMatrix(((1, 5), (5, 1))) + L, D = A.LDLdecomposition(hermitian=False) + assert L * D * L.T == A + A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + L, D = A.LDLdecomposition() + assert L * D * L.T == A + assert L.is_lower + assert L == Matrix([[1, 0, 0], [ Rational(3, 5), 1, 0], [Rational(-1, 5), Rational(1, 3), 1]]) + assert D.is_diagonal() + assert D == Matrix([[25, 0, 0], [0, 9, 0], [0, 0, 9]]) + A = SparseMatrix(((4, -2*I, 2 + 2*I), (2*I, 2, -1 + I), (2 - 2*I, -1 - I, 11))) + L, D = A.LDLdecomposition() + assert expand_mul(L * D * L.H) == A + assert L == Matrix(((1, 0, 0), (I/2, 1, 0), (S.Half - I/2, 0, 1))) + assert D == Matrix(((4, 0, 0), (0, 1, 0), (0, 0, 9))) + +def test_pinv_succeeds_with_rank_decomposition_method(): + # Test rank decomposition method of pseudoinverse succeeding + As = [Matrix([ + [61, 89, 55, 20, 71, 0], + [62, 96, 85, 85, 16, 0], + [69, 56, 17, 4, 54, 0], + [10, 54, 91, 41, 71, 0], + [ 7, 30, 10, 48, 90, 0], + [0,0,0,0,0,0]])] + for A in As: + A_pinv = A.pinv(method="RD") + AAp = A * A_pinv + ApA = A_pinv * A + assert simplify(AAp * A) == A + assert simplify(ApA * A_pinv) == A_pinv + assert AAp.H == AAp + assert ApA.H == ApA + +def test_rank_decomposition(): + a = Matrix(0, 0, []) + c, f = a.rank_decomposition() + assert f.is_echelon + assert c.cols == f.rows == a.rank() + assert c * f == a + + a = Matrix(1, 1, [5]) + c, f = a.rank_decomposition() + assert f.is_echelon + assert c.cols == f.rows == a.rank() + assert c * f == a + + a = Matrix(3, 3, [1, 2, 3, 1, 2, 3, 1, 2, 3]) + c, f = a.rank_decomposition() + assert f.is_echelon + assert c.cols == f.rows == a.rank() + assert c * f == a + + a = Matrix([ + [0, 0, 1, 2, 2, -5, 3], + [-1, 5, 2, 2, 1, -7, 5], + [0, 0, -2, -3, -3, 8, -5], + [-1, 5, 0, -1, -2, 1, 0]]) + c, f = a.rank_decomposition() + assert f.is_echelon + assert c.cols == f.rows == a.rank() + assert c * f == a + + +@slow +def test_upper_hessenberg_decomposition(): + A = Matrix([ + [1, 0, sqrt(3)], + [sqrt(2), Rational(1, 2), 2], + [1, Rational(1, 4), 3], + ]) + H, P = A.upper_hessenberg_decomposition() + assert simplify(P * P.H) == eye(P.cols) + assert simplify(P.H * P) == eye(P.cols) + assert H.is_upper_hessenberg + assert (simplify(P * H * P.H)) == A + + + B = Matrix([ + [1, 2, 10], + [8, 2, 5], + [3, 12, 34], + ]) + H, P = B.upper_hessenberg_decomposition() + assert simplify(P * P.H) == eye(P.cols) + assert simplify(P.H * P) == eye(P.cols) + assert H.is_upper_hessenberg + assert simplify(P * H * P.H) == B + + C = Matrix([ + [1, sqrt(2), 2, 3], + [0, 5, 3, 4], + [1, 1, 4, sqrt(5)], + [0, 2, 2, 3] + ]) + + H, P = C.upper_hessenberg_decomposition() + assert simplify(P * P.H) == eye(P.cols) + assert simplify(P.H * P) == eye(P.cols) + assert H.is_upper_hessenberg + assert simplify(P * H * P.H) == C + + D = Matrix([ + [1, 2, 3], + [-3, 5, 6], + [4, -8, 9], + ]) + H, P = D.upper_hessenberg_decomposition() + assert simplify(P * P.H) == eye(P.cols) + assert simplify(P.H * P) == eye(P.cols) + assert H.is_upper_hessenberg + assert simplify(P * H * P.H) == D + + E = Matrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [1, 1, 0, 1], + [1, 1, 1, 0] + ]) + + H, P = E.upper_hessenberg_decomposition() + assert simplify(P * P.H) == eye(P.cols) + assert simplify(P.H * P) == eye(P.cols) + assert H.is_upper_hessenberg + assert simplify(P * H * P.H) == E diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_determinant.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_determinant.py new file mode 100644 index 0000000000000000000000000000000000000000..82b42ccf67efa4757bf270782bdf1d65e0efa306 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_determinant.py @@ -0,0 +1,280 @@ +import random +import pytest +from sympy.core.numbers import I +from sympy.core.numbers import Rational +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.polys.polytools import Poly +from sympy.matrices import Matrix, eye, ones +from sympy.abc import x, y, z +from sympy.testing.pytest import raises +from sympy.matrices.exceptions import NonSquareMatrixError +from sympy.functions.combinatorial.factorials import factorial, subfactorial + + +@pytest.mark.parametrize("method", [ + # Evaluating these directly because they are never reached via M.det() + Matrix._eval_det_bareiss, Matrix._eval_det_berkowitz, + Matrix._eval_det_bird, Matrix._eval_det_laplace, Matrix._eval_det_lu +]) +@pytest.mark.parametrize("M, sol", [ + (Matrix(), 1), + (Matrix([[0]]), 0), + (Matrix([[5]]), 5), +]) +def test_eval_determinant(method, M, sol): + assert method(M) == sol + + +@pytest.mark.parametrize("method", [ + "domain-ge", "bareiss", "berkowitz", "bird", "laplace", "lu"]) +@pytest.mark.parametrize("M, sol", [ + (Matrix(( (-3, 2), + ( 8, -5) )), -1), + (Matrix(( (x, 1), + (y, 2*y) )), 2*x*y - y), + (Matrix(( (1, 1, 1), + (1, 2, 3), + (1, 3, 6) )), 1), + (Matrix(( ( 3, -2, 0, 5), + (-2, 1, -2, 2), + ( 0, -2, 5, 0), + ( 5, 0, 3, 4) )), -289), + (Matrix(( ( 1, 2, 3, 4), + ( 5, 6, 7, 8), + ( 9, 10, 11, 12), + (13, 14, 15, 16) )), 0), + (Matrix(( (3, 2, 0, 0, 0), + (0, 3, 2, 0, 0), + (0, 0, 3, 2, 0), + (0, 0, 0, 3, 2), + (2, 0, 0, 0, 3) )), 275), + (Matrix(( ( 3, 0, 0, 0), + (-2, 1, 0, 0), + ( 0, -2, 5, 0), + ( 5, 0, 3, 4) )), 60), + (Matrix(( ( 1, 0, 0, 0), + ( 5, 0, 0, 0), + ( 9, 10, 11, 0), + (13, 14, 15, 16) )), 0), + (Matrix(( (3, 2, 0, 0, 0), + (0, 3, 2, 0, 0), + (0, 0, 3, 2, 0), + (0, 0, 0, 3, 2), + (0, 0, 0, 0, 3) )), 243), + (Matrix(( (1, 0, 1, 2, 12), + (2, 0, 1, 1, 4), + (2, 1, 1, -1, 3), + (3, 2, -1, 1, 8), + (1, 1, 1, 0, 6) )), -55), + (Matrix(( (-5, 2, 3, 4, 5), + ( 1, -4, 3, 4, 5), + ( 1, 2, -3, 4, 5), + ( 1, 2, 3, -2, 5), + ( 1, 2, 3, 4, -1) )), 11664), + (Matrix(( ( 2, 7, -1, 3, 2), + ( 0, 0, 1, 0, 1), + (-2, 0, 7, 0, 2), + (-3, -2, 4, 5, 3), + ( 1, 0, 0, 0, 1) )), 123), + (Matrix(( (x, y, z), + (1, 0, 0), + (y, z, x) )), z**2 - x*y), +]) +def test_determinant(method, M, sol): + assert M.det(method=method) == sol + + +def test_issue_13835(): + a = symbols('a') + M = lambda n: Matrix([[i + a*j for i in range(n)] + for j in range(n)]) + assert M(5).det() == 0 + assert M(6).det() == 0 + assert M(7).det() == 0 + + +def test_issue_14517(): + M = Matrix([ + [ 0, 10*I, 10*I, 0], + [10*I, 0, 0, 10*I], + [10*I, 0, 5 + 2*I, 10*I], + [ 0, 10*I, 10*I, 5 + 2*I]]) + ev = M.eigenvals() + # test one random eigenvalue, the computation is a little slow + test_ev = random.choice(list(ev.keys())) + assert (M - test_ev*eye(4)).det() == 0 + + +@pytest.mark.parametrize("method", [ + "bareis", "det_lu", "det_LU", "Bareis", "BAREISS", "BERKOWITZ", "LU"]) +@pytest.mark.parametrize("M, sol", [ + (Matrix(( ( 3, -2, 0, 5), + (-2, 1, -2, 2), + ( 0, -2, 5, 0), + ( 5, 0, 3, 4) )), -289), + (Matrix(( (-5, 2, 3, 4, 5), + ( 1, -4, 3, 4, 5), + ( 1, 2, -3, 4, 5), + ( 1, 2, 3, -2, 5), + ( 1, 2, 3, 4, -1) )), 11664), +]) +def test_legacy_det(method, M, sol): + # Minimal support for legacy keys for 'method' in det() + # Partially copied from test_determinant() + assert M.det(method=method) == sol + + +def eye_Determinant(n): + return Matrix(n, n, lambda i, j: int(i == j)) + +def zeros_Determinant(n): + return Matrix(n, n, lambda i, j: 0) + +def test_det(): + a = Matrix(2, 3, [1, 2, 3, 4, 5, 6]) + raises(NonSquareMatrixError, lambda: a.det()) + + z = zeros_Determinant(2) + ey = eye_Determinant(2) + assert z.det() == 0 + assert ey.det() == 1 + + x = Symbol('x') + a = Matrix(0, 0, []) + b = Matrix(1, 1, [5]) + c = Matrix(2, 2, [1, 2, 3, 4]) + d = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 8]) + e = Matrix(4, 4, + [x, 1, 2, 3, 4, 5, 6, 7, 2, 9, 10, 11, 12, 13, 14, 14]) + from sympy.abc import i, j, k, l, m, n + f = Matrix(3, 3, [i, l, m, 0, j, n, 0, 0, k]) + g = Matrix(3, 3, [i, 0, 0, l, j, 0, m, n, k]) + h = Matrix(3, 3, [x**3, 0, 0, i, x**-1, 0, j, k, x**-2]) + # the method keyword for `det` doesn't kick in until 4x4 matrices, + # so there is no need to test all methods on smaller ones + + assert a.det() == 1 + assert b.det() == 5 + assert c.det() == -2 + assert d.det() == 3 + assert e.det() == 4*x - 24 + assert e.det(method="domain-ge") == 4*x - 24 + assert e.det(method='bareiss') == 4*x - 24 + assert e.det(method='berkowitz') == 4*x - 24 + assert f.det() == i*j*k + assert g.det() == i*j*k + assert h.det() == 1 + raises(ValueError, lambda: e.det(iszerofunc="test")) + +def test_permanent(): + M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert M.per() == 450 + for i in range(1, 12): + assert ones(i, i).per() == ones(i, i).T.per() == factorial(i) + assert (ones(i, i)-eye(i)).per() == (ones(i, i)-eye(i)).T.per() == subfactorial(i) + + a1, a2, a3, a4, a5 = symbols('a_1 a_2 a_3 a_4 a_5') + M = Matrix([a1, a2, a3, a4, a5]) + assert M.per() == M.T.per() == a1 + a2 + a3 + a4 + a5 + +def test_adjugate(): + x = Symbol('x') + e = Matrix(4, 4, + [x, 1, 2, 3, 4, 5, 6, 7, 2, 9, 10, 11, 12, 13, 14, 14]) + + adj = Matrix([ + [ 4, -8, 4, 0], + [ 76, -14*x - 68, 14*x - 8, -4*x + 24], + [-122, 17*x + 142, -21*x + 4, 8*x - 48], + [ 48, -4*x - 72, 8*x, -4*x + 24]]) + assert e.adjugate() == adj + assert e.adjugate(method='bareiss') == adj + assert e.adjugate(method='berkowitz') == adj + assert e.adjugate(method='bird') == adj + assert e.adjugate(method='laplace') == adj + + a = Matrix(2, 3, [1, 2, 3, 4, 5, 6]) + raises(NonSquareMatrixError, lambda: a.adjugate()) + +def test_util(): + R = Rational + + v1 = Matrix(1, 3, [1, 2, 3]) + v2 = Matrix(1, 3, [3, 4, 5]) + assert v1.norm() == sqrt(14) + assert v1.project(v2) == Matrix(1, 3, [R(39)/25, R(52)/25, R(13)/5]) + assert Matrix.zeros(1, 2) == Matrix(1, 2, [0, 0]) + assert ones(1, 2) == Matrix(1, 2, [1, 1]) + assert v1.copy() == v1 + # cofactor + assert eye(3) == eye(3).cofactor_matrix() + test = Matrix([[1, 3, 2], [2, 6, 3], [2, 3, 6]]) + assert test.cofactor_matrix() == \ + Matrix([[27, -6, -6], [-12, 2, 3], [-3, 1, 0]]) + test = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert test.cofactor_matrix() == \ + Matrix([[-3, 6, -3], [6, -12, 6], [-3, 6, -3]]) + +def test_cofactor_and_minors(): + x = Symbol('x') + e = Matrix(4, 4, + [x, 1, 2, 3, 4, 5, 6, 7, 2, 9, 10, 11, 12, 13, 14, 14]) + + m = Matrix([ + [ x, 1, 3], + [ 2, 9, 11], + [12, 13, 14]]) + cm = Matrix([ + [ 4, 76, -122, 48], + [-8, -14*x - 68, 17*x + 142, -4*x - 72], + [ 4, 14*x - 8, -21*x + 4, 8*x], + [ 0, -4*x + 24, 8*x - 48, -4*x + 24]]) + sub = Matrix([ + [x, 1, 2], + [4, 5, 6], + [2, 9, 10]]) + + assert e.minor_submatrix(1, 2) == m + assert e.minor_submatrix(-1, -1) == sub + assert e.minor(1, 2) == -17*x - 142 + assert e.cofactor(1, 2) == 17*x + 142 + assert e.cofactor_matrix() == cm + assert e.cofactor_matrix(method="bareiss") == cm + assert e.cofactor_matrix(method="berkowitz") == cm + assert e.cofactor_matrix(method="bird") == cm + assert e.cofactor_matrix(method="laplace") == cm + + raises(ValueError, lambda: e.cofactor(4, 5)) + raises(ValueError, lambda: e.minor(4, 5)) + raises(ValueError, lambda: e.minor_submatrix(4, 5)) + + a = Matrix(2, 3, [1, 2, 3, 4, 5, 6]) + assert a.minor_submatrix(0, 0) == Matrix([[5, 6]]) + + raises(ValueError, lambda: + Matrix(0, 0, []).minor_submatrix(0, 0)) + raises(NonSquareMatrixError, lambda: a.cofactor(0, 0)) + raises(NonSquareMatrixError, lambda: a.minor(0, 0)) + raises(NonSquareMatrixError, lambda: a.cofactor_matrix()) + +def test_charpoly(): + x, y = Symbol('x'), Symbol('y') + z, t = Symbol('z'), Symbol('t') + + from sympy.abc import a,b,c + + m = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + + assert eye_Determinant(3).charpoly(x) == Poly((x - 1)**3, x) + assert eye_Determinant(3).charpoly(y) == Poly((y - 1)**3, y) + assert m.charpoly() == Poly(x**3 - 15*x**2 - 18*x, x) + raises(NonSquareMatrixError, lambda: Matrix([[1], [2]]).charpoly()) + n = Matrix(4, 4, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + assert n.charpoly() == Poly(x**4, x) + + n = Matrix(4, 4, [45, 0, 0, 0, 0, 23, 0, 0, 0, 0, 87, 0, 0, 0, 0, 12]) + assert n.charpoly() == Poly(x**4 - 167*x**3 + 8811*x**2 - 173457*x + 1080540, x) + + n = Matrix(3, 3, [x, 0, 0, a, y, 0, b, c, z]) + assert n.charpoly() == Poly(t**3 - (x+y+z)*t**2 + t*(x*y+y*z+x*z) - x*y*z, t) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_domains.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_domains.py new file mode 100644 index 0000000000000000000000000000000000000000..26a54b8879a5c65f3a01b4886d223c08309e733d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_domains.py @@ -0,0 +1,113 @@ +# Test Matrix/DomainMatrix interaction. + + +from sympy import GF, ZZ, QQ, EXRAW +from sympy.polys.matrices import DomainMatrix, DM + +from sympy import ( + Matrix, + MutableMatrix, + ImmutableMatrix, + SparseMatrix, + MutableDenseMatrix, + ImmutableDenseMatrix, + MutableSparseMatrix, + ImmutableSparseMatrix, +) +from sympy import symbols, S, sqrt + +from sympy.testing.pytest import raises + + +x, y = symbols('x y') + + +MATRIX_TYPES = ( + Matrix, + MutableMatrix, + ImmutableMatrix, + SparseMatrix, + MutableDenseMatrix, + ImmutableDenseMatrix, + MutableSparseMatrix, + ImmutableSparseMatrix, +) +IMMUTABLE = ( + ImmutableMatrix, + ImmutableDenseMatrix, + ImmutableSparseMatrix, +) + + +def DMs(items, domain): + return DM(items, domain).to_sparse() + + +def test_Matrix_rep_domain(): + + for Mat in MATRIX_TYPES: + + M = Mat([[1, 2], [3, 4]]) + assert M._rep == DMs([[1, 2], [3, 4]], ZZ) + assert (M / 2)._rep == DMs([[(1,2), 1], [(3,2), 2]], QQ) + if not isinstance(M, IMMUTABLE): + M[0, 0] = x + assert M._rep == DMs([[x, 2], [3, 4]], EXRAW) + + M = Mat([[S(1)/2, 2], [3, 4]]) + assert M._rep == DMs([[(1,2), 2], [3, 4]], QQ) + if not isinstance(M, IMMUTABLE): + M[0, 0] = x + assert M._rep == DMs([[x, 2], [3, 4]], EXRAW) + + dM = DMs([[1, 2], [3, 4]], ZZ) + assert Mat._fromrep(dM)._rep == dM + + # XXX: This is not intended. Perhaps it should be coerced to EXRAW? + # The private _fromrep method is never called like this but perhaps it + # should be guarded. + # + # It is not clear how to integrate domains other than ZZ, QQ and EXRAW with + # the rest of Matrix or if the public type for this needs to be something + # different from Matrix somehow. + K = QQ.algebraic_field(sqrt(2)) + dM = DM([[1, 2], [3, 4]], K) + assert Mat._fromrep(dM)._rep.domain == K + + +def test_Matrix_to_DM(): + + M = Matrix([[1, 2], [3, 4]]) + assert M.to_DM() == DMs([[1, 2], [3, 4]], ZZ) + assert M.to_DM() is not M._rep + assert M.to_DM(field=True) == DMs([[1, 2], [3, 4]], QQ) + assert M.to_DM(domain=QQ) == DMs([[1, 2], [3, 4]], QQ) + assert M.to_DM(domain=QQ[x]) == DMs([[1, 2], [3, 4]], QQ[x]) + assert M.to_DM(domain=GF(3)) == DMs([[1, 2], [0, 1]], GF(3)) + + M = Matrix([[1, 2], [3, 4]]) + M[0, 0] = x + assert M._rep.domain == EXRAW + M[0, 0] = 1 + assert M.to_DM() == DMs([[1, 2], [3, 4]], ZZ) + + M = Matrix([[S(1)/2, 2], [3, 4]]) + assert M.to_DM() == DMs([[QQ(1,2), 2], [3, 4]], QQ) + + M = Matrix([[x, 2], [3, 4]]) + assert M.to_DM() == DMs([[x, 2], [3, 4]], ZZ[x]) + assert M.to_DM(field=True) == DMs([[x, 2], [3, 4]], ZZ.frac_field(x)) + + M = Matrix([[1/x, 2], [3, 4]]) + assert M.to_DM() == DMs([[1/x, 2], [3, 4]], ZZ.frac_field(x)) + + M = Matrix([[1, sqrt(2)], [3, 4]]) + K = QQ.algebraic_field(sqrt(2)) + sqrt2 = K.from_sympy(sqrt(2)) # XXX: Maybe K(sqrt(2)) should work + M_K = DomainMatrix([[K(1), sqrt2], [K(3), K(4)]], (2, 2), K) + assert M.to_DM() == DMs([[1, sqrt(2)], [3, 4]], EXRAW) + assert M.to_DM(extension=True) == M_K.to_sparse() + + # Options cannot be used with the domain parameter + M = Matrix([[1, 2], [3, 4]]) + raises(TypeError, lambda: M.to_DM(domain=QQ, field=True)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_eigen.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_eigen.py new file mode 100644 index 0000000000000000000000000000000000000000..fcf96325519879e0683d29e2ddc32db7bf83baa4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_eigen.py @@ -0,0 +1,712 @@ +from sympy.core.evalf import N +from sympy.core.numbers import (Float, I, Rational) +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.matrices import eye, Matrix +from sympy.core.singleton import S +from sympy.testing.pytest import raises, XFAIL +from sympy.matrices.exceptions import NonSquareMatrixError, MatrixError +from sympy.matrices.expressions.fourier import DFT +from sympy.simplify.simplify import simplify +from sympy.matrices.immutable import ImmutableMatrix +from sympy.testing.pytest import slow +from sympy.testing.matrices import allclose + + +def test_eigen(): + R = Rational + M = Matrix.eye(3) + assert M.eigenvals(multiple=False) == {S.One: 3} + assert M.eigenvals(multiple=True) == [1, 1, 1] + + assert M.eigenvects() == ( + [(1, 3, [Matrix([1, 0, 0]), + Matrix([0, 1, 0]), + Matrix([0, 0, 1])])]) + + assert M.left_eigenvects() == ( + [(1, 3, [Matrix([[1, 0, 0]]), + Matrix([[0, 1, 0]]), + Matrix([[0, 0, 1]])])]) + + M = Matrix([[0, 1, 1], + [1, 0, 0], + [1, 1, 1]]) + + assert M.eigenvals() == {2*S.One: 1, -S.One: 1, S.Zero: 1} + + assert M.eigenvects() == ( + [ + (-1, 1, [Matrix([-1, 1, 0])]), + ( 0, 1, [Matrix([0, -1, 1])]), + ( 2, 1, [Matrix([R(2, 3), R(1, 3), 1])]) + ]) + + assert M.left_eigenvects() == ( + [ + (-1, 1, [Matrix([[-2, 1, 1]])]), + (0, 1, [Matrix([[-1, -1, 1]])]), + (2, 1, [Matrix([[1, 1, 1]])]) + ]) + + a = Symbol('a') + M = Matrix([[a, 0], + [0, 1]]) + + assert M.eigenvals() == {a: 1, S.One: 1} + + M = Matrix([[1, -1], + [1, 3]]) + assert M.eigenvects() == ([(2, 2, [Matrix(2, 1, [-1, 1])])]) + assert M.left_eigenvects() == ([(2, 2, [Matrix([[1, 1]])])]) + + M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + a = R(15, 2) + b = 3*33**R(1, 2) + c = R(13, 2) + d = (R(33, 8) + 3*b/8) + e = (R(33, 8) - 3*b/8) + + def NS(e, n): + return str(N(e, n)) + r = [ + (a - b/2, 1, [Matrix([(12 + 24/(c - b/2))/((c - b/2)*e) + 3/(c - b/2), + (6 + 12/(c - b/2))/e, 1])]), + ( 0, 1, [Matrix([1, -2, 1])]), + (a + b/2, 1, [Matrix([(12 + 24/(c + b/2))/((c + b/2)*d) + 3/(c + b/2), + (6 + 12/(c + b/2))/d, 1])]), + ] + r1 = [(NS(r[i][0], 2), NS(r[i][1], 2), + [NS(j, 2) for j in r[i][2][0]]) for i in range(len(r))] + r = M.eigenvects() + r2 = [(NS(r[i][0], 2), NS(r[i][1], 2), + [NS(j, 2) for j in r[i][2][0]]) for i in range(len(r))] + assert sorted(r1) == sorted(r2) + + eps = Symbol('eps', real=True) + + M = Matrix([[abs(eps), I*eps ], + [-I*eps, abs(eps) ]]) + + assert M.eigenvects() == ( + [ + ( 0, 1, [Matrix([[-I*eps/abs(eps)], [1]])]), + ( 2*abs(eps), 1, [ Matrix([[I*eps/abs(eps)], [1]]) ] ), + ]) + + assert M.left_eigenvects() == ( + [ + (0, 1, [Matrix([[I*eps/Abs(eps), 1]])]), + (2*Abs(eps), 1, [Matrix([[-I*eps/Abs(eps), 1]])]) + ]) + + M = Matrix(3, 3, [1, 2, 0, 0, 3, 0, 2, -4, 2]) + M._eigenvects = M.eigenvects(simplify=False) + assert max(i.q for i in M._eigenvects[0][2][0]) > 1 + M._eigenvects = M.eigenvects(simplify=True) + assert max(i.q for i in M._eigenvects[0][2][0]) == 1 + + M = Matrix([[Rational(1, 4), 1], [1, 1]]) + assert M.eigenvects() == [ + (Rational(5, 8) - sqrt(73)/8, 1, [Matrix([[-sqrt(73)/8 - Rational(3, 8)], [1]])]), + (Rational(5, 8) + sqrt(73)/8, 1, [Matrix([[Rational(-3, 8) + sqrt(73)/8], [1]])])] + + # issue 10719 + assert Matrix([]).eigenvals() == {} + assert Matrix([]).eigenvals(multiple=True) == [] + assert Matrix([]).eigenvects() == [] + + # issue 15119 + raises(NonSquareMatrixError, + lambda: Matrix([[1, 2], [0, 4], [0, 0]]).eigenvals()) + raises(NonSquareMatrixError, + lambda: Matrix([[1, 0], [3, 4], [5, 6]]).eigenvals()) + raises(NonSquareMatrixError, + lambda: Matrix([[1, 2, 3], [0, 5, 6]]).eigenvals()) + raises(NonSquareMatrixError, + lambda: Matrix([[1, 0, 0], [4, 5, 0]]).eigenvals()) + raises(NonSquareMatrixError, + lambda: Matrix([[1, 2, 3], [0, 5, 6]]).eigenvals( + error_when_incomplete = False)) + raises(NonSquareMatrixError, + lambda: Matrix([[1, 0, 0], [4, 5, 0]]).eigenvals( + error_when_incomplete = False)) + + m = Matrix([[1, 2], [3, 4]]) + assert isinstance(m.eigenvals(simplify=True, multiple=False), dict) + assert isinstance(m.eigenvals(simplify=True, multiple=True), list) + assert isinstance(m.eigenvals(simplify=lambda x: x, multiple=False), dict) + assert isinstance(m.eigenvals(simplify=lambda x: x, multiple=True), list) + + +def test_float_eigenvals(): + m = Matrix([[1, .6, .6], [.6, .9, .9], [.9, .6, .6]]) + evals = [ + Rational(5, 4) - sqrt(385)/20, + sqrt(385)/20 + Rational(5, 4), + S.Zero] + + n_evals = m.eigenvals(rational=True, multiple=True) + n_evals = sorted(n_evals) + s_evals = [x.evalf() for x in evals] + s_evals = sorted(s_evals) + + for x, y in zip(n_evals, s_evals): + assert abs(x-y) < 10**-9 + + +@XFAIL +def test_eigen_vects(): + m = Matrix(2, 2, [1, 0, 0, I]) + raises(NotImplementedError, lambda: m.is_diagonalizable(True)) + # !!! bug because of eigenvects() or roots(x**2 + (-1 - I)*x + I, x) + # see issue 5292 + assert not m.is_diagonalizable(True) + raises(MatrixError, lambda: m.diagonalize(True)) + (P, D) = m.diagonalize(True) + +def test_issue_8240(): + # Eigenvalues of large triangular matrices + x, y = symbols('x y') + n = 200 + + diagonal_variables = [Symbol('x%s' % i) for i in range(n)] + M = [[0 for i in range(n)] for j in range(n)] + for i in range(n): + M[i][i] = diagonal_variables[i] + M = Matrix(M) + + eigenvals = M.eigenvals() + assert len(eigenvals) == n + for i in range(n): + assert eigenvals[diagonal_variables[i]] == 1 + + eigenvals = M.eigenvals(multiple=True) + assert set(eigenvals) == set(diagonal_variables) + + # with multiplicity + M = Matrix([[x, 0, 0], [1, y, 0], [2, 3, x]]) + eigenvals = M.eigenvals() + assert eigenvals == {x: 2, y: 1} + + eigenvals = M.eigenvals(multiple=True) + assert len(eigenvals) == 3 + assert eigenvals.count(x) == 2 + assert eigenvals.count(y) == 1 + + +def test_eigenvals(): + M = Matrix([[0, 1, 1], + [1, 0, 0], + [1, 1, 1]]) + assert M.eigenvals() == {2*S.One: 1, -S.One: 1, S.Zero: 1} + + m = Matrix([ + [3, 0, 0, 0, -3], + [0, -3, -3, 0, 3], + [0, 3, 0, 3, 0], + [0, 0, 3, 0, 3], + [3, 0, 0, 3, 0]]) + + # XXX Used dry-run test because arbitrary symbol that appears in + # CRootOf may not be unique. + assert m.eigenvals() + + +def test_eigenvects(): + M = Matrix([[0, 1, 1], + [1, 0, 0], + [1, 1, 1]]) + vecs = M.eigenvects() + for val, mult, vec_list in vecs: + assert len(vec_list) == 1 + assert M*vec_list[0] == val*vec_list[0] + + +def test_left_eigenvects(): + M = Matrix([[0, 1, 1], + [1, 0, 0], + [1, 1, 1]]) + vecs = M.left_eigenvects() + for val, mult, vec_list in vecs: + assert len(vec_list) == 1 + assert vec_list[0]*M == val*vec_list[0] + + +@slow +def test_bidiagonalize(): + M = Matrix([[1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + assert M.bidiagonalize() == M + assert M.bidiagonalize(upper=False) == M + assert M.bidiagonalize() == M + assert M.bidiagonal_decomposition() == (M, M, M) + assert M.bidiagonal_decomposition(upper=False) == (M, M, M) + assert M.bidiagonalize() == M + + import random + #Real Tests + for real_test in range(2): + test_values = [] + row = 2 + col = 2 + for _ in range(row * col): + value = random.randint(-1000000000, 1000000000) + test_values = test_values + [value] + # L -> Lower Bidiagonalization + # M -> Mutable Matrix + # N -> Immutable Matrix + # 0 -> Bidiagonalized form + # 1,2,3 -> Bidiagonal_decomposition matrices + # 4 -> Product of 1 2 3 + M = Matrix(row, col, test_values) + N = ImmutableMatrix(M) + + N1, N2, N3 = N.bidiagonal_decomposition() + M1, M2, M3 = M.bidiagonal_decomposition() + M0 = M.bidiagonalize() + N0 = N.bidiagonalize() + + N4 = N1 * N2 * N3 + M4 = M1 * M2 * M3 + + N2.simplify() + N4.simplify() + N0.simplify() + + M0.simplify() + M2.simplify() + M4.simplify() + + LM0 = M.bidiagonalize(upper=False) + LM1, LM2, LM3 = M.bidiagonal_decomposition(upper=False) + LN0 = N.bidiagonalize(upper=False) + LN1, LN2, LN3 = N.bidiagonal_decomposition(upper=False) + + LN4 = LN1 * LN2 * LN3 + LM4 = LM1 * LM2 * LM3 + + LN2.simplify() + LN4.simplify() + LN0.simplify() + + LM0.simplify() + LM2.simplify() + LM4.simplify() + + assert M == M4 + assert M2 == M0 + assert N == N4 + assert N2 == N0 + assert M == LM4 + assert LM2 == LM0 + assert N == LN4 + assert LN2 == LN0 + + #Complex Tests + for complex_test in range(2): + test_values = [] + size = 2 + for _ in range(size * size): + real = random.randint(-1000000000, 1000000000) + comp = random.randint(-1000000000, 1000000000) + value = real + comp * I + test_values = test_values + [value] + M = Matrix(size, size, test_values) + N = ImmutableMatrix(M) + # L -> Lower Bidiagonalization + # M -> Mutable Matrix + # N -> Immutable Matrix + # 0 -> Bidiagonalized form + # 1,2,3 -> Bidiagonal_decomposition matrices + # 4 -> Product of 1 2 3 + N1, N2, N3 = N.bidiagonal_decomposition() + M1, M2, M3 = M.bidiagonal_decomposition() + M0 = M.bidiagonalize() + N0 = N.bidiagonalize() + + N4 = N1 * N2 * N3 + M4 = M1 * M2 * M3 + + N2.simplify() + N4.simplify() + N0.simplify() + + M0.simplify() + M2.simplify() + M4.simplify() + + LM0 = M.bidiagonalize(upper=False) + LM1, LM2, LM3 = M.bidiagonal_decomposition(upper=False) + LN0 = N.bidiagonalize(upper=False) + LN1, LN2, LN3 = N.bidiagonal_decomposition(upper=False) + + LN4 = LN1 * LN2 * LN3 + LM4 = LM1 * LM2 * LM3 + + LN2.simplify() + LN4.simplify() + LN0.simplify() + + LM0.simplify() + LM2.simplify() + LM4.simplify() + + assert M == M4 + assert M2 == M0 + assert N == N4 + assert N2 == N0 + assert M == LM4 + assert LM2 == LM0 + assert N == LN4 + assert LN2 == LN0 + + M = Matrix(18, 8, range(1, 145)) + M = M.applyfunc(lambda i: Float(i)) + assert M.bidiagonal_decomposition()[1] == M.bidiagonalize() + assert M.bidiagonal_decomposition(upper=False)[1] == M.bidiagonalize(upper=False) + a, b, c = M.bidiagonal_decomposition() + diff = a * b * c - M + assert abs(max(diff)) < 10**-12 + + +def test_diagonalize(): + m = Matrix(2, 2, [0, -1, 1, 0]) + raises(MatrixError, lambda: m.diagonalize(reals_only=True)) + P, D = m.diagonalize() + assert D.is_diagonal() + assert D == Matrix([ + [-I, 0], + [ 0, I]]) + + # make sure we use floats out if floats are passed in + m = Matrix(2, 2, [0, .5, .5, 0]) + P, D = m.diagonalize() + assert all(isinstance(e, Float) for e in D.values()) + assert all(isinstance(e, Float) for e in P.values()) + + _, D2 = m.diagonalize(reals_only=True) + assert D == D2 + + m = Matrix( + [[0, 1, 0, 0], [1, 0, 0, 0.002], [0.002, 0, 0, 1], [0, 0, 1, 0]]) + P, D = m.diagonalize() + assert allclose(P*D, m*P) + + +def test_is_diagonalizable(): + a, b, c = symbols('a b c') + m = Matrix(2, 2, [a, c, c, b]) + assert m.is_symmetric() + assert m.is_diagonalizable() + assert not Matrix(2, 2, [1, 1, 0, 1]).is_diagonalizable() + + m = Matrix(2, 2, [0, -1, 1, 0]) + assert m.is_diagonalizable() + assert not m.is_diagonalizable(reals_only=True) + + +def test_jordan_form(): + m = Matrix(3, 2, [-3, 1, -3, 20, 3, 10]) + raises(NonSquareMatrixError, lambda: m.jordan_form()) + + # the next two tests test the cases where the old + # algorithm failed due to the fact that the block structure can + # *NOT* be determined from algebraic and geometric multiplicity alone + # This can be seen most easily when one lets compute the J.c.f. of a matrix that + # is in J.c.f already. + m = Matrix(4, 4, [2, 1, 0, 0, + 0, 2, 1, 0, + 0, 0, 2, 0, + 0, 0, 0, 2 + ]) + P, J = m.jordan_form() + assert m == J + + m = Matrix(4, 4, [2, 1, 0, 0, + 0, 2, 0, 0, + 0, 0, 2, 1, + 0, 0, 0, 2 + ]) + P, J = m.jordan_form() + assert m == J + + A = Matrix([[ 2, 4, 1, 0], + [-4, 2, 0, 1], + [ 0, 0, 2, 4], + [ 0, 0, -4, 2]]) + P, J = A.jordan_form() + assert simplify(P*J*P.inv()) == A + + assert Matrix(1, 1, [1]).jordan_form() == (Matrix([1]), Matrix([1])) + assert Matrix(1, 1, [1]).jordan_form(calc_transform=False) == Matrix([1]) + + # If we have eigenvalues in CRootOf form, raise errors + m = Matrix([[3, 0, 0, 0, -3], [0, -3, -3, 0, 3], [0, 3, 0, 3, 0], [0, 0, 3, 0, 3], [3, 0, 0, 3, 0]]) + raises(MatrixError, lambda: m.jordan_form()) + + # make sure that if the input has floats, the output does too + m = Matrix([ + [ 0.6875, 0.125 + 0.1875*sqrt(3)], + [0.125 + 0.1875*sqrt(3), 0.3125]]) + P, J = m.jordan_form() + assert all(isinstance(x, Float) or x == 0 for x in P) + assert all(isinstance(x, Float) or x == 0 for x in J) + + +def test_singular_values(): + x = Symbol('x', real=True) + + A = Matrix([[0, 1*I], [2, 0]]) + # if singular values can be sorted, they should be in decreasing order + assert A.singular_values() == [2, 1] + + A = eye(3) + A[1, 1] = x + A[2, 2] = 5 + vals = A.singular_values() + # since Abs(x) cannot be sorted, test set equality + assert set(vals) == {5, 1, Abs(x)} + + A = Matrix([[sin(x), cos(x)], [-cos(x), sin(x)]]) + vals = [sv.trigsimp() for sv in A.singular_values()] + assert vals == [S.One, S.One] + + A = Matrix([ + [2, 4], + [1, 3], + [0, 0], + [0, 0] + ]) + assert A.singular_values() == \ + [sqrt(sqrt(221) + 15), sqrt(15 - sqrt(221))] + assert A.T.singular_values() == \ + [sqrt(sqrt(221) + 15), sqrt(15 - sqrt(221)), 0, 0] + +def test___eq__(): + assert (Matrix( + [[0, 1, 1], + [1, 0, 0], + [1, 1, 1]]) == {}) is False + + +def test_definite(): + # Examples from Gilbert Strang, "Introduction to Linear Algebra" + # Positive definite matrices + m = Matrix([[2, -1, 0], [-1, 2, -1], [0, -1, 2]]) + assert m.is_positive_definite == True + assert m.is_positive_semidefinite == True + assert m.is_negative_definite == False + assert m.is_negative_semidefinite == False + assert m.is_indefinite == False + + m = Matrix([[5, 4], [4, 5]]) + assert m.is_positive_definite == True + assert m.is_positive_semidefinite == True + assert m.is_negative_definite == False + assert m.is_negative_semidefinite == False + assert m.is_indefinite == False + + # Positive semidefinite matrices + m = Matrix([[2, -1, -1], [-1, 2, -1], [-1, -1, 2]]) + assert m.is_positive_definite == False + assert m.is_positive_semidefinite == True + assert m.is_negative_definite == False + assert m.is_negative_semidefinite == False + assert m.is_indefinite == False + + m = Matrix([[1, 2], [2, 4]]) + assert m.is_positive_definite == False + assert m.is_positive_semidefinite == True + assert m.is_negative_definite == False + assert m.is_negative_semidefinite == False + assert m.is_indefinite == False + + # Examples from Mathematica documentation + # Non-hermitian positive definite matrices + m = Matrix([[2, 3], [4, 8]]) + assert m.is_positive_definite == True + assert m.is_positive_semidefinite == True + assert m.is_negative_definite == False + assert m.is_negative_semidefinite == False + assert m.is_indefinite == False + + # Hermetian matrices + m = Matrix([[1, 2*I], [-I, 4]]) + assert m.is_positive_definite == True + assert m.is_positive_semidefinite == True + assert m.is_negative_definite == False + assert m.is_negative_semidefinite == False + assert m.is_indefinite == False + + # Symbolic matrices examples + a = Symbol('a', positive=True) + b = Symbol('b', negative=True) + m = Matrix([[a, 0, 0], [0, a, 0], [0, 0, a]]) + assert m.is_positive_definite == True + assert m.is_positive_semidefinite == True + assert m.is_negative_definite == False + assert m.is_negative_semidefinite == False + assert m.is_indefinite == False + + m = Matrix([[b, 0, 0], [0, b, 0], [0, 0, b]]) + assert m.is_positive_definite == False + assert m.is_positive_semidefinite == False + assert m.is_negative_definite == True + assert m.is_negative_semidefinite == True + assert m.is_indefinite == False + + m = Matrix([[a, 0], [0, b]]) + assert m.is_positive_definite == False + assert m.is_positive_semidefinite == False + assert m.is_negative_definite == False + assert m.is_negative_semidefinite == False + assert m.is_indefinite == True + + m = Matrix([ + [0.0228202735623867, 0.00518748979085398, + -0.0743036351048907, -0.00709135324903921], + [0.00518748979085398, 0.0349045359786350, + 0.0830317991056637, 0.00233147902806909], + [-0.0743036351048907, 0.0830317991056637, + 1.15859676366277, 0.340359081555988], + [-0.00709135324903921, 0.00233147902806909, + 0.340359081555988, 0.928147644848199] + ]) + assert m.is_positive_definite == True + assert m.is_positive_semidefinite == True + assert m.is_indefinite == False + + # test for issue 19547: https://github.com/sympy/sympy/issues/19547 + m = Matrix([ + [0, 0, 0], + [0, 1, 2], + [0, 2, 1] + ]) + assert not m.is_positive_definite + assert not m.is_positive_semidefinite + + +def test_positive_semidefinite_cholesky(): + from sympy.matrices.eigen import _is_positive_semidefinite_cholesky + + m = Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + assert _is_positive_semidefinite_cholesky(m) == True + m = Matrix([[0, 0, 0], [0, 5, -10*I], [0, 10*I, 5]]) + assert _is_positive_semidefinite_cholesky(m) == False + m = Matrix([[1, 0, 0], [0, 0, 0], [0, 0, -1]]) + assert _is_positive_semidefinite_cholesky(m) == False + m = Matrix([[0, 1], [1, 0]]) + assert _is_positive_semidefinite_cholesky(m) == False + + # https://www.value-at-risk.net/cholesky-factorization/ + m = Matrix([[4, -2, -6], [-2, 10, 9], [-6, 9, 14]]) + assert _is_positive_semidefinite_cholesky(m) == True + m = Matrix([[9, -3, 3], [-3, 2, 1], [3, 1, 6]]) + assert _is_positive_semidefinite_cholesky(m) == True + m = Matrix([[4, -2, 2], [-2, 1, -1], [2, -1, 5]]) + assert _is_positive_semidefinite_cholesky(m) == True + m = Matrix([[1, 2, -1], [2, 5, 1], [-1, 1, 9]]) + assert _is_positive_semidefinite_cholesky(m) == False + + +def test_issue_20582(): + A = Matrix([ + [5, -5, -3, 2, -7], + [-2, -5, 0, 2, 1], + [-2, -7, -5, -2, -6], + [7, 10, 3, 9, -2], + [4, -10, 3, -8, -4] + ]) + # XXX Used dry-run test because arbitrary symbol that appears in + # CRootOf may not be unique. + assert A.eigenvects() + +def test_issue_19210(): + t = Symbol('t') + H = Matrix([[3, 0, 0, 0], [0, 1 , 2, 0], [0, 2, 2, 0], [0, 0, 0, 4]]) + A = (-I * H * t).jordan_form() + assert A == (Matrix([ + [0, 1, 0, 0], + [0, 0, -4/(-1 + sqrt(17)), 4/(1 + sqrt(17))], + [0, 0, 1, 1], + [1, 0, 0, 0]]), Matrix([ + [-4*I*t, 0, 0, 0], + [ 0, -3*I*t, 0, 0], + [ 0, 0, t*(-3*I/2 + sqrt(17)*I/2), 0], + [ 0, 0, 0, t*(-sqrt(17)*I/2 - 3*I/2)]])) + + +def test_issue_20275(): + # XXX We use complex expansions because complex exponentials are not + # recognized by polys.domains + A = DFT(3).as_explicit().expand(complex=True) + eigenvects = A.eigenvects() + assert eigenvects[0] == ( + -1, 1, + [Matrix([[1 - sqrt(3)], [1], [1]])] + ) + assert eigenvects[1] == ( + 1, 1, + [Matrix([[1 + sqrt(3)], [1], [1]])] + ) + assert eigenvects[2] == ( + -I, 1, + [Matrix([[0], [-1], [1]])] + ) + + A = DFT(4).as_explicit().expand(complex=True) + eigenvects = A.eigenvects() + assert eigenvects[0] == ( + -1, 1, + [Matrix([[-1], [1], [1], [1]])] + ) + assert eigenvects[1] == ( + 1, 2, + [Matrix([[1], [0], [1], [0]]), Matrix([[2], [1], [0], [1]])] + ) + assert eigenvects[2] == ( + -I, 1, + [Matrix([[0], [-1], [0], [1]])] + ) + + # XXX We skip test for some parts of eigenvectors which are very + # complicated and fragile under expression tree changes + A = DFT(5).as_explicit().expand(complex=True) + eigenvects = A.eigenvects() + assert eigenvects[0] == ( + -1, 1, + [Matrix([[1 - sqrt(5)], [1], [1], [1], [1]])] + ) + assert eigenvects[1] == ( + 1, 2, + [Matrix([[S(1)/2 + sqrt(5)/2], [0], [1], [1], [0]]), + Matrix([[S(1)/2 + sqrt(5)/2], [1], [0], [0], [1]])] + ) + + +def test_issue_20752(): + b = symbols('b', nonzero=True) + m = Matrix([[0, 0, 0], [0, b, 0], [0, 0, b]]) + assert m.is_positive_semidefinite is None + + +def test_issue_25282(): + dd = sd = [0] * 11 + [1] + ds = [2, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0] + ss = ds.copy() + ss[8] = 2 + + def rotate(x, i): + return x[i:] + x[:i] + + mat = [] + for i in range(12): + mat.append(rotate(ss, i) + rotate(sd, i)) + for i in range(12): + mat.append(rotate(ds, i) + rotate(dd, i)) + + assert sum(Matrix(mat).eigenvals().values()) == 24 diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_graph.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..0bf3c819a9477387f53560a034d7949fd76a654f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_graph.py @@ -0,0 +1,108 @@ +from sympy.combinatorics import Permutation +from sympy.core.symbol import symbols +from sympy.matrices import Matrix +from sympy.matrices.expressions import ( + PermutationMatrix, BlockDiagMatrix, BlockMatrix) + + +def test_connected_components(): + a, b, c, d, e, f, g, h, i, j, k, l, m = symbols('a:m') + + M = Matrix([ + [a, 0, 0, 0, b, 0, 0, 0, 0, 0, c, 0, 0], + [0, d, 0, 0, 0, e, 0, 0, 0, 0, 0, f, 0], + [0, 0, g, 0, 0, 0, h, 0, 0, 0, 0, 0, i], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [m, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, m, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, m, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [j, 0, 0, 0, k, 0, 0, 1, 0, 0, l, 0, 0], + [0, j, 0, 0, 0, k, 0, 0, 1, 0, 0, l, 0], + [0, 0, j, 0, 0, 0, k, 0, 0, 1, 0, 0, l], + [0, 0, 0, 0, d, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, d, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, d, 0, 0, 0, 0, 0, 1]]) + cc = M.connected_components() + assert cc == [[0, 4, 7, 10], [1, 5, 8, 11], [2, 6, 9, 12], [3]] + + P, B = M.connected_components_decomposition() + p = Permutation([0, 4, 7, 10, 1, 5, 8, 11, 2, 6, 9, 12, 3]) + assert P == PermutationMatrix(p) + + B0 = Matrix([ + [a, b, 0, c], + [m, 1, 0, 0], + [j, k, 1, l], + [0, d, 0, 1]]) + B1 = Matrix([ + [d, e, 0, f], + [m, 1, 0, 0], + [j, k, 1, l], + [0, d, 0, 1]]) + B2 = Matrix([ + [g, h, 0, i], + [m, 1, 0, 0], + [j, k, 1, l], + [0, d, 0, 1]]) + B3 = Matrix([[1]]) + assert B == BlockDiagMatrix(B0, B1, B2, B3) + + +def test_strongly_connected_components(): + M = Matrix([ + [11, 14, 10, 0, 15, 0], + [0, 44, 0, 0, 45, 0], + [1, 4, 0, 0, 5, 0], + [0, 0, 0, 22, 0, 23], + [0, 54, 0, 0, 55, 0], + [0, 0, 0, 32, 0, 33]]) + scc = M.strongly_connected_components() + assert scc == [[1, 4], [0, 2], [3, 5]] + + P, B = M.strongly_connected_components_decomposition() + p = Permutation([1, 4, 0, 2, 3, 5]) + assert P == PermutationMatrix(p) + assert B == BlockMatrix([ + [ + Matrix([[44, 45], [54, 55]]), + Matrix.zeros(2, 2), + Matrix.zeros(2, 2) + ], + [ + Matrix([[14, 15], [4, 5]]), + Matrix([[11, 10], [1, 0]]), + Matrix.zeros(2, 2) + ], + [ + Matrix.zeros(2, 2), + Matrix.zeros(2, 2), + Matrix([[22, 23], [32, 33]]) + ] + ]) + P = P.as_explicit() + B = B.as_explicit() + assert P.T * B * P == M + + P, B = M.strongly_connected_components_decomposition(lower=False) + p = Permutation([3, 5, 0, 2, 1, 4]) + assert P == PermutationMatrix(p) + assert B == BlockMatrix([ + [ + Matrix([[22, 23], [32, 33]]), + Matrix.zeros(2, 2), + Matrix.zeros(2, 2) + ], + [ + Matrix.zeros(2, 2), + Matrix([[11, 10], [1, 0]]), + Matrix([[14, 15], [4, 5]]) + ], + [ + Matrix.zeros(2, 2), + Matrix.zeros(2, 2), + Matrix([[44, 45], [54, 55]]) + ] + ]) + P = P.as_explicit() + B = B.as_explicit() + assert P.T * B * P == M diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_immutable.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_immutable.py new file mode 100644 index 0000000000000000000000000000000000000000..2b83c1f9fae7f83be9d5f7dd4b484781dc128faf --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_immutable.py @@ -0,0 +1,136 @@ +from itertools import product + +from sympy.core.relational import (Equality, Unequality) +from sympy.core.singleton import S +from sympy.core.sympify import sympify +from sympy.integrals.integrals import integrate +from sympy.matrices.dense import (Matrix, eye, zeros) +from sympy.matrices.immutable import ImmutableMatrix +from sympy.matrices import SparseMatrix +from sympy.matrices.immutable import \ + ImmutableDenseMatrix, ImmutableSparseMatrix +from sympy.abc import x, y +from sympy.testing.pytest import raises + +IM = ImmutableDenseMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +ISM = ImmutableSparseMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +ieye = ImmutableDenseMatrix(eye(3)) + + +def test_creation(): + assert IM.shape == ISM.shape == (3, 3) + assert IM[1, 2] == ISM[1, 2] == 6 + assert IM[2, 2] == ISM[2, 2] == 9 + + +def test_immutability(): + with raises(TypeError): + IM[2, 2] = 5 + with raises(TypeError): + ISM[2, 2] = 5 + + +def test_slicing(): + assert IM[1, :] == ImmutableDenseMatrix([[4, 5, 6]]) + assert IM[:2, :2] == ImmutableDenseMatrix([[1, 2], [4, 5]]) + assert ISM[1, :] == ImmutableSparseMatrix([[4, 5, 6]]) + assert ISM[:2, :2] == ImmutableSparseMatrix([[1, 2], [4, 5]]) + + +def test_subs(): + A = ImmutableMatrix([[1, 2], [3, 4]]) + B = ImmutableMatrix([[1, 2], [x, 4]]) + C = ImmutableMatrix([[-x, x*y], [-(x + y), y**2]]) + assert B.subs(x, 3) == A + assert (x*B).subs(x, 3) == 3*A + assert (x*eye(2) + B).subs(x, 3) == 3*eye(2) + A + assert C.subs([[x, -1], [y, -2]]) == A + assert C.subs([(x, -1), (y, -2)]) == A + assert C.subs({x: -1, y: -2}) == A + assert C.subs({x: y - 1, y: x - 1}, simultaneous=True) == \ + ImmutableMatrix([[1 - y, (x - 1)*(y - 1)], [2 - x - y, (x - 1)**2]]) + + +def test_as_immutable(): + data = [[1, 2], [3, 4]] + X = Matrix(data) + assert sympify(X) == X.as_immutable() == ImmutableMatrix(data) + + data = {(0, 0): 1, (0, 1): 2, (1, 0): 3, (1, 1): 4} + X = SparseMatrix(2, 2, data) + assert sympify(X) == X.as_immutable() == ImmutableSparseMatrix(2, 2, data) + + +def test_function_return_types(): + # Lets ensure that decompositions of immutable matrices remain immutable + # I.e. do MatrixBase methods return the correct class? + X = ImmutableMatrix([[1, 2], [3, 4]]) + Y = ImmutableMatrix([[1], [0]]) + q, r = X.QRdecomposition() + assert (type(q), type(r)) == (ImmutableMatrix, ImmutableMatrix) + + assert type(X.LUsolve(Y)) == ImmutableMatrix + assert type(X.QRsolve(Y)) == ImmutableMatrix + + X = ImmutableMatrix([[5, 2], [2, 7]]) + assert X.T == X + assert X.is_symmetric + assert type(X.cholesky()) == ImmutableMatrix + L, D = X.LDLdecomposition() + assert (type(L), type(D)) == (ImmutableMatrix, ImmutableMatrix) + + X = ImmutableMatrix([[1, 2], [2, 1]]) + assert X.is_diagonalizable() + assert X.det() == -3 + assert X.norm(2) == 3 + + assert type(X.eigenvects()[0][2][0]) == ImmutableMatrix + + assert type(zeros(3, 3).as_immutable().nullspace()[0]) == ImmutableMatrix + + X = ImmutableMatrix([[1, 0], [2, 1]]) + assert type(X.lower_triangular_solve(Y)) == ImmutableMatrix + assert type(X.T.upper_triangular_solve(Y)) == ImmutableMatrix + + assert type(X.minor_submatrix(0, 0)) == ImmutableMatrix + +# issue 6279 +# https://github.com/sympy/sympy/issues/6279 +# Test that Immutable _op_ Immutable => Immutable and not MatExpr + + +def test_immutable_evaluation(): + X = ImmutableMatrix(eye(3)) + A = ImmutableMatrix(3, 3, range(9)) + assert isinstance(X + A, ImmutableMatrix) + assert isinstance(X * A, ImmutableMatrix) + assert isinstance(X * 2, ImmutableMatrix) + assert isinstance(2 * X, ImmutableMatrix) + assert isinstance(A**2, ImmutableMatrix) + + +def test_deterimant(): + assert ImmutableMatrix(4, 4, lambda i, j: i + j).det() == 0 + + +def test_Equality(): + assert Equality(IM, IM) is S.true + assert Unequality(IM, IM) is S.false + assert Equality(IM, IM.subs(1, 2)) is S.false + assert Unequality(IM, IM.subs(1, 2)) is S.true + assert Equality(IM, 2) is S.false + assert Unequality(IM, 2) is S.true + M = ImmutableMatrix([x, y]) + assert Equality(M, IM) is S.false + assert Unequality(M, IM) is S.true + assert Equality(M, M.subs(x, 2)).subs(x, 2) is S.true + assert Unequality(M, M.subs(x, 2)).subs(x, 2) is S.false + assert Equality(M, M.subs(x, 2)).subs(x, 3) is S.false + assert Unequality(M, M.subs(x, 2)).subs(x, 3) is S.true + + +def test_integrate(): + intIM = integrate(IM, x) + assert intIM.shape == IM.shape + assert all(intIM[i, j] == (1 + j + 3*i)*x for i, j in + product(range(3), range(3))) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_interactions.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_interactions.py new file mode 100644 index 0000000000000000000000000000000000000000..f4fc3268368e8dd632fc0df187d57ea5e845120c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_interactions.py @@ -0,0 +1,77 @@ +""" +We have a few different kind of Matrices +Matrix, ImmutableMatrix, MatrixExpr + +Here we test the extent to which they cooperate +""" + +from sympy.core.symbol import symbols +from sympy.matrices import (Matrix, MatrixSymbol, eye, Identity, + ImmutableMatrix) +from sympy.matrices.expressions import MatrixExpr, MatAdd +from sympy.matrices.matrixbase import classof +from sympy.testing.pytest import raises + +SM = MatrixSymbol('X', 3, 3) +SV = MatrixSymbol('v', 3, 1) +MM = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +IM = ImmutableMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +meye = eye(3) +imeye = ImmutableMatrix(eye(3)) +ideye = Identity(3) +a, b, c = symbols('a,b,c') + + +def test_IM_MM(): + assert isinstance(MM + IM, ImmutableMatrix) + assert isinstance(IM + MM, ImmutableMatrix) + assert isinstance(2*IM + MM, ImmutableMatrix) + assert MM.equals(IM) + + +def test_ME_MM(): + assert isinstance(Identity(3) + MM, MatrixExpr) + assert isinstance(SM + MM, MatAdd) + assert isinstance(MM + SM, MatAdd) + assert (Identity(3) + MM)[1, 1] == 6 + + +def test_equality(): + a, b, c = Identity(3), eye(3), ImmutableMatrix(eye(3)) + for x in [a, b, c]: + for y in [a, b, c]: + assert x.equals(y) + + +def test_matrix_symbol_MM(): + X = MatrixSymbol('X', 3, 3) + Y = eye(3) + X + assert Y[1, 1] == 1 + X[1, 1] + + +def test_matrix_symbol_vector_matrix_multiplication(): + A = MM * SV + B = IM * SV + assert A == B + C = (SV.T * MM.T).T + assert B == C + D = (SV.T * IM.T).T + assert C == D + + +def test_indexing_interactions(): + assert (a * IM)[1, 1] == 5*a + assert (SM + IM)[1, 1] == SM[1, 1] + IM[1, 1] + assert (SM * IM)[1, 1] == SM[1, 0]*IM[0, 1] + SM[1, 1]*IM[1, 1] + \ + SM[1, 2]*IM[2, 1] + + +def test_classof(): + A = Matrix(3, 3, range(9)) + B = ImmutableMatrix(3, 3, range(9)) + C = MatrixSymbol('C', 3, 3) + assert classof(A, A) == Matrix + assert classof(B, B) == ImmutableMatrix + assert classof(A, B) == ImmutableMatrix + assert classof(B, A) == ImmutableMatrix + raises(TypeError, lambda: classof(A, C)) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_matrices.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_matrices.py new file mode 100644 index 0000000000000000000000000000000000000000..d9d97341de570e078d652dddce58fb8f5cb99e43 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_matrices.py @@ -0,0 +1,3487 @@ +# +# Code for testing deprecated matrix classes. New test code should not be added +# here. Instead, add it to test_matrixbase.py. +# +# This entire test module and the corresponding sympy/matrices/matrices.py +# module will be removed in a future release. +# +import random +import concurrent.futures +from collections.abc import Hashable + +from sympy.core.add import Add +from sympy.core.function import Function, diff, expand +from sympy.core.numbers import (E, Float, I, Integer, Rational, nan, oo, pi) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt) +from sympy.functions.elementary.trigonometric import (cos, sin, tan) +from sympy.integrals.integrals import integrate +from sympy.matrices.expressions.transpose import transpose +from sympy.physics.quantum.operator import HermitianOperator, Operator, Dagger +from sympy.polys.polytools import (Poly, PurePoly) +from sympy.polys.rootoftools import RootOf +from sympy.printing.str import sstr +from sympy.sets.sets import FiniteSet +from sympy.simplify.simplify import (signsimp, simplify) +from sympy.simplify.trigsimp import trigsimp +from sympy.matrices.exceptions import (ShapeError, MatrixError, + NonSquareMatrixError) +from sympy.matrices.matrixbase import DeferredVector +from sympy.matrices.determinant import _find_reasonable_pivot_naive +from sympy.matrices.utilities import _simplify +from sympy.matrices import ( + GramSchmidt, ImmutableMatrix, ImmutableSparseMatrix, Matrix, + SparseMatrix, casoratian, diag, eye, hessian, + matrix_multiply_elementwise, ones, randMatrix, rot_axis1, rot_axis2, + rot_axis3, wronskian, zeros, MutableDenseMatrix, ImmutableDenseMatrix, + MatrixSymbol, dotprodsimp, rot_ccw_axis1, rot_ccw_axis2, rot_ccw_axis3) +from sympy.matrices.utilities import _dotprodsimp_state +from sympy.core import Tuple, Wild +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.utilities.iterables import flatten, capture, iterable +from sympy.utilities.exceptions import ignore_warnings +from sympy.testing.pytest import (raises, XFAIL, slow, skip, skip_under_pyodide, + warns_deprecated_sympy) +from sympy.assumptions import Q +from sympy.tensor.array import Array +from sympy.tensor.array.array_derivatives import ArrayDerivative +from sympy.matrices.expressions import MatPow +from sympy.algebras import Quaternion + +from sympy import O + +from sympy.abc import a, b, c, d, x, y, z, t + + +# don't re-order this list +classes = (Matrix, SparseMatrix, ImmutableMatrix, ImmutableSparseMatrix) + + +# Test the deprecated matrixmixins +from sympy.matrices.common import _MinimalMatrix, _CastableMatrix +from sympy.matrices.matrices import MatrixSubspaces, MatrixReductions + + +with warns_deprecated_sympy(): + class SubspaceOnlyMatrix(_MinimalMatrix, _CastableMatrix, MatrixSubspaces): + pass + + +with warns_deprecated_sympy(): + class ReductionsOnlyMatrix(_MinimalMatrix, _CastableMatrix, MatrixReductions): + pass + + +def eye_Reductions(n): + return ReductionsOnlyMatrix(n, n, lambda i, j: int(i == j)) + + +def zeros_Reductions(n): + return ReductionsOnlyMatrix(n, n, lambda i, j: 0) + + +def test_args(): + for n, cls in enumerate(classes): + m = cls.zeros(3, 2) + # all should give back the same type of arguments, e.g. ints for shape + assert m.shape == (3, 2) and all(type(i) is int for i in m.shape) + assert m.rows == 3 and type(m.rows) is int + assert m.cols == 2 and type(m.cols) is int + if not n % 2: + assert type(m.flat()) in (list, tuple, Tuple) + else: + assert type(m.todok()) is dict + + +def test_deprecated_mat_smat(): + for cls in Matrix, ImmutableMatrix: + m = cls.zeros(3, 2) + with warns_deprecated_sympy(): + mat = m._mat + assert mat == m.flat() + for cls in SparseMatrix, ImmutableSparseMatrix: + m = cls.zeros(3, 2) + with warns_deprecated_sympy(): + smat = m._smat + assert smat == m.todok() + + +def test_division(): + v = Matrix(1, 2, [x, y]) + assert v/z == Matrix(1, 2, [x/z, y/z]) + + +def test_sum(): + m = Matrix([[1, 2, 3], [x, y, x], [2*y, -50, z*x]]) + assert m + m == Matrix([[2, 4, 6], [2*x, 2*y, 2*x], [4*y, -100, 2*z*x]]) + n = Matrix(1, 2, [1, 2]) + raises(ShapeError, lambda: m + n) + +def test_abs(): + m = Matrix(1, 2, [-3, x]) + n = Matrix(1, 2, [3, Abs(x)]) + assert abs(m) == n + +def test_addition(): + a = Matrix(( + (1, 2), + (3, 1), + )) + + b = Matrix(( + (1, 2), + (3, 0), + )) + + assert a + b == a.add(b) == Matrix([[2, 4], [6, 1]]) + + +def test_fancy_index_matrix(): + for M in (Matrix, SparseMatrix): + a = M(3, 3, range(9)) + assert a == a[:, :] + assert a[1, :] == Matrix(1, 3, [3, 4, 5]) + assert a[:, 1] == Matrix([1, 4, 7]) + assert a[[0, 1], :] == Matrix([[0, 1, 2], [3, 4, 5]]) + assert a[[0, 1], 2] == a[[0, 1], [2]] + assert a[2, [0, 1]] == a[[2], [0, 1]] + assert a[:, [0, 1]] == Matrix([[0, 1], [3, 4], [6, 7]]) + assert a[0, 0] == 0 + assert a[0:2, :] == Matrix([[0, 1, 2], [3, 4, 5]]) + assert a[:, 0:2] == Matrix([[0, 1], [3, 4], [6, 7]]) + assert a[::2, 1] == a[[0, 2], 1] + assert a[1, ::2] == a[1, [0, 2]] + a = M(3, 3, range(9)) + assert a[[0, 2, 1, 2, 1], :] == Matrix([ + [0, 1, 2], + [6, 7, 8], + [3, 4, 5], + [6, 7, 8], + [3, 4, 5]]) + assert a[:, [0,2,1,2,1]] == Matrix([ + [0, 2, 1, 2, 1], + [3, 5, 4, 5, 4], + [6, 8, 7, 8, 7]]) + + a = SparseMatrix.zeros(3) + a[1, 2] = 2 + a[0, 1] = 3 + a[2, 0] = 4 + assert a.extract([1, 1], [2]) == Matrix([ + [2], + [2]]) + assert a.extract([1, 0], [2, 2, 2]) == Matrix([ + [2, 2, 2], + [0, 0, 0]]) + assert a.extract([1, 0, 1, 2], [2, 0, 1, 0]) == Matrix([ + [2, 0, 0, 0], + [0, 0, 3, 0], + [2, 0, 0, 0], + [0, 4, 0, 4]]) + + +def test_multiplication(): + a = Matrix(( + (1, 2), + (3, 1), + (0, 6), + )) + + b = Matrix(( + (1, 2), + (3, 0), + )) + + c = a*b + assert c[0, 0] == 7 + assert c[0, 1] == 2 + assert c[1, 0] == 6 + assert c[1, 1] == 6 + assert c[2, 0] == 18 + assert c[2, 1] == 0 + + try: + eval('c = a @ b') + except SyntaxError: + pass + else: + assert c[0, 0] == 7 + assert c[0, 1] == 2 + assert c[1, 0] == 6 + assert c[1, 1] == 6 + assert c[2, 0] == 18 + assert c[2, 1] == 0 + + h = matrix_multiply_elementwise(a, c) + assert h == a.multiply_elementwise(c) + assert h[0, 0] == 7 + assert h[0, 1] == 4 + assert h[1, 0] == 18 + assert h[1, 1] == 6 + assert h[2, 0] == 0 + assert h[2, 1] == 0 + raises(ShapeError, lambda: matrix_multiply_elementwise(a, b)) + + c = b * Symbol("x") + assert isinstance(c, Matrix) + assert c[0, 0] == x + assert c[0, 1] == 2*x + assert c[1, 0] == 3*x + assert c[1, 1] == 0 + + c2 = x * b + assert c == c2 + + c = 5 * b + assert isinstance(c, Matrix) + assert c[0, 0] == 5 + assert c[0, 1] == 2*5 + assert c[1, 0] == 3*5 + assert c[1, 1] == 0 + + try: + eval('c = 5 @ b') + except SyntaxError: + pass + else: + assert isinstance(c, Matrix) + assert c[0, 0] == 5 + assert c[0, 1] == 2*5 + assert c[1, 0] == 3*5 + assert c[1, 1] == 0 + + +def test_multiplication_inf_zero(): + + M = Matrix([[oo, 0], [0, oo]]) + assert M ** 2 == M + + M = Matrix([[oo, oo], [0, 0]]) + assert M ** 2 == Matrix([[nan, nan], [nan, nan]]) + + A = Matrix([ + [0, 0, 0, -S(1)/2], + [0, 1, 0, 0], + [0, 0, 1, 0], + [-S(1)/2, 0, 0, 0]]) + + B = Matrix([ + [pi*x**2, 0, pi*b*x**4/8 + pi*a*x**4/8 + O(x**5), pi*x**4/2 + pi*b**2*x**6/32 + pi*a*b*x**6/48 + pi*a**2*x**6/32 + O(x**7)], + [0, pi*x**4/4, O(x**6), O(x**8)], + [pi*b*x**4/8 + pi*a*x**4/8 + O(x**5), O(x**6), pi*b**2*x**6/32 + pi*a*b*x**6/48 + pi*a**2*x**6/32 + O(x**7), pi*b*x**6/12 + pi*a*x**6/12 + O(x**7)], + [pi*x**4/2 + pi*b**2*x**6/32 + pi*a*b*x**6/48 + pi*a**2*x**6/32 + O(x**7), O(x**8), pi*b*x**6/12 + pi*a*x**6/12 + O(x**7), pi*x**6/3 + 3*pi*b**2*x**8/64 + pi*a*b*x**8/32 + 3*pi*a**2*x**8/64 + O(x**9)]]) + + C = Matrix([ + [-pi*x**4/4 - pi*b**2*x**6/64 - pi*a*b*x**6/96 - pi*a**2*x**6/64 + O(x**7), O(x**8), -pi*b*x**6/24 - pi*a*x**6/24 + O(x**7), -pi*x**6/6 - 3*pi*b**2*x**8/128 - pi*a*b*x**8/64 - 3*pi*a**2*x**8/128 + O(x**9)], + [ 0, pi*x**4/4, O(x**6), O(x**8)], + [ pi*b*x**4/8 + pi*a*x**4/8 + O(x**5), O(x**6), pi*b**2*x**6/32 + pi*a*b*x**6/48 + pi*a**2*x**6/32 + O(x**7), pi*b*x**6/12 + pi*a*x**6/12 + O(x**7)], + [ -pi*x**2/2, 0, -pi*b*x**4/16 - pi*a*x**4/16 + O(x**5), -pi*x**4/4 - pi*b**2*x**6/64 - pi*a*b*x**6/96 - pi*a**2*x**6/64 + O(x**7)]]) + + C2 = Matrix(4, 4, lambda i, j: Add(*(A[i,k]*B[k,j] for k in range(4)))) + + assert A*B == C == C2 + + +def test_power(): + raises(NonSquareMatrixError, lambda: Matrix((1, 2))**2) + + R = Rational + A = Matrix([[2, 3], [4, 5]]) + assert (A**-3)[:] == [R(-269)/8, R(153)/8, R(51)/2, R(-29)/2] + assert (A**5)[:] == [6140, 8097, 10796, 14237] + A = Matrix([[2, 1, 3], [4, 2, 4], [6, 12, 1]]) + assert (A**3)[:] == [290, 262, 251, 448, 440, 368, 702, 954, 433] + assert A**0 == eye(3) + assert A**1 == A + assert (Matrix([[2]]) ** 100)[0, 0] == 2**100 + assert eye(2)**10000000 == eye(2) + assert Matrix([[1, 2], [3, 4]])**Integer(2) == Matrix([[7, 10], [15, 22]]) + + A = Matrix([[33, 24], [48, 57]]) + assert (A**S.Half)[:] == [5, 2, 4, 7] + A = Matrix([[0, 4], [-1, 5]]) + assert (A**S.Half)**2 == A + + assert Matrix([[1, 0], [1, 1]])**S.Half == Matrix([[1, 0], [S.Half, 1]]) + assert Matrix([[1, 0], [1, 1]])**0.5 == Matrix([[1, 0], [0.5, 1]]) + from sympy.abc import n + assert Matrix([[1, a], [0, 1]])**n == Matrix([[1, a*n], [0, 1]]) + assert Matrix([[b, a], [0, b]])**n == Matrix([[b**n, a*b**(n-1)*n], [0, b**n]]) + assert Matrix([ + [a**n, a**(n - 1)*n, (a**n*n**2 - a**n*n)/(2*a**2)], + [ 0, a**n, a**(n - 1)*n], + [ 0, 0, a**n]]) + assert Matrix([[a, 1, 0], [0, a, 0], [0, 0, b]])**n == Matrix([ + [a**n, a**(n-1)*n, 0], + [0, a**n, 0], + [0, 0, b**n]]) + + A = Matrix([[1, 0], [1, 7]]) + assert A._matrix_pow_by_jordan_blocks(S(3)) == A._eval_pow_by_recursion(3) + A = Matrix([[2]]) + assert A**10 == Matrix([[2**10]]) == A._matrix_pow_by_jordan_blocks(S(10)) == \ + A._eval_pow_by_recursion(10) + + # testing a matrix that cannot be jordan blocked issue 11766 + m = Matrix([[3, 0, 0, 0, -3], [0, -3, -3, 0, 3], [0, 3, 0, 3, 0], [0, 0, 3, 0, 3], [3, 0, 0, 3, 0]]) + raises(MatrixError, lambda: m._matrix_pow_by_jordan_blocks(S(10))) + + # test issue 11964 + raises(MatrixError, lambda: Matrix([[1, 1], [3, 3]])._matrix_pow_by_jordan_blocks(S(-10))) + A = Matrix([[0, 1, 0], [0, 0, 1], [0, 0, 0]]) # Nilpotent jordan block size 3 + assert A**10.0 == Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + raises(ValueError, lambda: A**2.1) + raises(ValueError, lambda: A**Rational(3, 2)) + A = Matrix([[8, 1], [3, 2]]) + assert A**10.0 == Matrix([[1760744107, 272388050], [817164150, 126415807]]) + A = Matrix([[0, 0, 1], [0, 0, 1], [0, 0, 1]]) # Nilpotent jordan block size 1 + assert A**10.0 == Matrix([[0, 0, 1], [0, 0, 1], [0, 0, 1]]) + A = Matrix([[0, 1, 0], [0, 0, 1], [0, 0, 1]]) # Nilpotent jordan block size 2 + assert A**10.0 == Matrix([[0, 0, 1], [0, 0, 1], [0, 0, 1]]) + n = Symbol('n', integer=True) + assert isinstance(A**n, MatPow) + n = Symbol('n', integer=True, negative=True) + raises(ValueError, lambda: A**n) + n = Symbol('n', integer=True, nonnegative=True) + assert A**n == Matrix([ + [KroneckerDelta(0, n), KroneckerDelta(1, n), -KroneckerDelta(0, n) - KroneckerDelta(1, n) + 1], + [ 0, KroneckerDelta(0, n), 1 - KroneckerDelta(0, n)], + [ 0, 0, 1]]) + assert A**(n + 2) == Matrix([[0, 0, 1], [0, 0, 1], [0, 0, 1]]) + raises(ValueError, lambda: A**Rational(3, 2)) + A = Matrix([[0, 0, 1], [3, 0, 1], [4, 3, 1]]) + assert A**5.0 == Matrix([[168, 72, 89], [291, 144, 161], [572, 267, 329]]) + assert A**5.0 == A**5 + A = Matrix([[0, 1, 0],[-1, 0, 0],[0, 0, 0]]) + n = Symbol("n") + An = A**n + assert An.subs(n, 2).doit() == A**2 + raises(ValueError, lambda: An.subs(n, -2).doit()) + assert An * An == A**(2*n) + + # concretizing behavior for non-integer and complex powers + A = Matrix([[0,0,0],[0,0,0],[0,0,0]]) + n = Symbol('n', integer=True, positive=True) + assert A**n == A + n = Symbol('n', integer=True, nonnegative=True) + assert A**n == diag(0**n, 0**n, 0**n) + assert (A**n).subs(n, 0) == eye(3) + assert (A**n).subs(n, 1) == zeros(3) + A = Matrix ([[2,0,0],[0,2,0],[0,0,2]]) + assert A**2.1 == diag (2**2.1, 2**2.1, 2**2.1) + assert A**I == diag (2**I, 2**I, 2**I) + A = Matrix([[0, 1, 0], [0, 0, 1], [0, 0, 1]]) + raises(ValueError, lambda: A**2.1) + raises(ValueError, lambda: A**I) + A = Matrix([[S.Half, S.Half], [S.Half, S.Half]]) + assert A**S.Half == A + A = Matrix([[1, 1],[3, 3]]) + assert A**S.Half == Matrix ([[S.Half, S.Half], [3*S.Half, 3*S.Half]]) + + +def test_issue_17247_expression_blowup_1(): + M = Matrix([[1+x, 1-x], [1-x, 1+x]]) + with dotprodsimp(True): + assert M.exp().expand() == Matrix([ + [ (exp(2*x) + exp(2))/2, (-exp(2*x) + exp(2))/2], + [(-exp(2*x) + exp(2))/2, (exp(2*x) + exp(2))/2]]) + +def test_issue_17247_expression_blowup_2(): + M = Matrix([[1+x, 1-x], [1-x, 1+x]]) + with dotprodsimp(True): + P, J = M.jordan_form () + assert P*J*P.inv() + +def test_issue_17247_expression_blowup_3(): + M = Matrix([[1+x, 1-x], [1-x, 1+x]]) + with dotprodsimp(True): + assert M**100 == Matrix([ + [633825300114114700748351602688*x**100 + 633825300114114700748351602688, 633825300114114700748351602688 - 633825300114114700748351602688*x**100], + [633825300114114700748351602688 - 633825300114114700748351602688*x**100, 633825300114114700748351602688*x**100 + 633825300114114700748351602688]]) + +def test_issue_17247_expression_blowup_4(): +# This matrix takes extremely long on current master even with intermediate simplification so an abbreviated version is used. It is left here for test in case of future optimizations. +# M = Matrix(S('''[ +# [ -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64, -9/32 - I/16, 183/256 - 97*I/128, 3/64 + 13*I/64, -23/32 - 59*I/256, 15/128 - 3*I/32, 19/256 + 551*I/1024], +# [-149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512, -219/128 + 115*I/256, 6301/4096 - 6609*I/1024, 119/128 + 143*I/128, -10879/2048 + 4343*I/4096, 129/256 - 549*I/512, 42533/16384 + 29103*I/8192], +# [ 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64, -9/32 - I/16, 183/256 - 97*I/128, 3/64 + 13*I/64, -23/32 - 59*I/256], +# [ -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512, -219/128 + 115*I/256, 6301/4096 - 6609*I/1024, 119/128 + 143*I/128, -10879/2048 + 4343*I/4096], +# [ 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64, -9/32 - I/16, 183/256 - 97*I/128], +# [ 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512, -219/128 + 115*I/256, 6301/4096 - 6609*I/1024], +# [ -2, 17/4 - 13*I/2, 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64], +# [ 1/4 + 13*I/4, -825/64 - 147*I/32, 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512], +# [ -4*I, 27/2 + 6*I, -2, 17/4 - 13*I/2, 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64], +# [ 1/4 + 5*I/2, -23/8 - 57*I/16, 1/4 + 13*I/4, -825/64 - 147*I/32, 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128], +# [ -4, 9 - 5*I, -4*I, 27/2 + 6*I, -2, 17/4 - 13*I/2, 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16], +# [ -2*I, 119/8 + 29*I/4, 1/4 + 5*I/2, -23/8 - 57*I/16, 1/4 + 13*I/4, -825/64 - 147*I/32, 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128]]''')) +# assert M**10 == Matrix([ +# [ 7*(-221393644768594642173548179825793834595 - 1861633166167425978847110897013541127952*I)/9671406556917033397649408, 15*(31670992489131684885307005100073928751695 + 10329090958303458811115024718207404523808*I)/77371252455336267181195264, 7*(-3710978679372178839237291049477017392703 + 1377706064483132637295566581525806894169*I)/19342813113834066795298816, (9727707023582419994616144751727760051598 - 59261571067013123836477348473611225724433*I)/9671406556917033397649408, (31896723509506857062605551443641668183707 + 54643444538699269118869436271152084599580*I)/38685626227668133590597632, (-2024044860947539028275487595741003997397402 + 130959428791783397562960461903698670485863*I)/309485009821345068724781056, 3*(26190251453797590396533756519358368860907 - 27221191754180839338002754608545400941638*I)/77371252455336267181195264, (1154643595139959842768960128434994698330461 + 3385496216250226964322872072260446072295634*I)/618970019642690137449562112, 3*(-31849347263064464698310044805285774295286 - 11877437776464148281991240541742691164309*I)/77371252455336267181195264, (4661330392283532534549306589669150228040221 - 4171259766019818631067810706563064103956871*I)/1237940039285380274899124224, (9598353794289061833850770474812760144506 + 358027153990999990968244906482319780943983*I)/309485009821345068724781056, (-9755135335127734571547571921702373498554177 - 4837981372692695195747379349593041939686540*I)/2475880078570760549798248448], +# [(-379516731607474268954110071392894274962069 - 422272153179747548473724096872271700878296*I)/77371252455336267181195264, (41324748029613152354787280677832014263339501 - 12715121258662668420833935373453570749288074*I)/1237940039285380274899124224, (-339216903907423793947110742819264306542397 + 494174755147303922029979279454787373566517*I)/77371252455336267181195264, (-18121350839962855576667529908850640619878381 - 37413012454129786092962531597292531089199003*I)/1237940039285380274899124224, (2489661087330511608618880408199633556675926 + 1137821536550153872137379935240732287260863*I)/309485009821345068724781056, (-136644109701594123227587016790354220062972119 + 110130123468183660555391413889600443583585272*I)/4951760157141521099596496896, (1488043981274920070468141664150073426459593 - 9691968079933445130866371609614474474327650*I)/1237940039285380274899124224, 27*(4636797403026872518131756991410164760195942 + 3369103221138229204457272860484005850416533*I)/4951760157141521099596496896, (-8534279107365915284081669381642269800472363 + 2241118846262661434336333368511372725482742*I)/1237940039285380274899124224, (60923350128174260992536531692058086830950875 - 263673488093551053385865699805250505661590126*I)/9903520314283042199192993792, (18520943561240714459282253753348921824172569 + 24846649186468656345966986622110971925703604*I)/4951760157141521099596496896, (-232781130692604829085973604213529649638644431 + 35981505277760667933017117949103953338570617*I)/9903520314283042199192993792], +# [ (8742968295129404279528270438201520488950 + 3061473358639249112126847237482570858327*I)/4835703278458516698824704, (-245657313712011778432792959787098074935273 + 253113767861878869678042729088355086740856*I)/38685626227668133590597632, (1947031161734702327107371192008011621193 - 19462330079296259148177542369999791122762*I)/9671406556917033397649408, (552856485625209001527688949522750288619217 + 392928441196156725372494335248099016686580*I)/77371252455336267181195264, (-44542866621905323121630214897126343414629 + 3265340021421335059323962377647649632959*I)/19342813113834066795298816, (136272594005759723105646069956434264218730 - 330975364731707309489523680957584684763587*I)/38685626227668133590597632, (27392593965554149283318732469825168894401 + 75157071243800133880129376047131061115278*I)/38685626227668133590597632, 7*(-357821652913266734749960136017214096276154 - 45509144466378076475315751988405961498243*I)/309485009821345068724781056, (104485001373574280824835174390219397141149 - 99041000529599568255829489765415726168162*I)/77371252455336267181195264, (1198066993119982409323525798509037696321291 + 4249784165667887866939369628840569844519936*I)/618970019642690137449562112, (-114985392587849953209115599084503853611014 - 52510376847189529234864487459476242883449*I)/77371252455336267181195264, (6094620517051332877965959223269600650951573 - 4683469779240530439185019982269137976201163*I)/1237940039285380274899124224], +# [ (611292255597977285752123848828590587708323 - 216821743518546668382662964473055912169502*I)/77371252455336267181195264, (-1144023204575811464652692396337616594307487 + 12295317806312398617498029126807758490062855*I)/309485009821345068724781056, (-374093027769390002505693378578475235158281 - 573533923565898290299607461660384634333639*I)/77371252455336267181195264, (47405570632186659000138546955372796986832987 - 2837476058950808941605000274055970055096534*I)/1237940039285380274899124224, (-571573207393621076306216726219753090535121 + 533381457185823100878764749236639320783831*I)/77371252455336267181195264, (-7096548151856165056213543560958582513797519 - 24035731898756040059329175131592138642195366*I)/618970019642690137449562112, (2396762128833271142000266170154694033849225 + 1448501087375679588770230529017516492953051*I)/309485009821345068724781056, (-150609293845161968447166237242456473262037053 + 92581148080922977153207018003184520294188436*I)/4951760157141521099596496896, 5*(270278244730804315149356082977618054486347 - 1997830155222496880429743815321662710091562*I)/1237940039285380274899124224, (62978424789588828258068912690172109324360330 + 44803641177219298311493356929537007630129097*I)/2475880078570760549798248448, 19*(-451431106327656743945775812536216598712236 + 114924966793632084379437683991151177407937*I)/1237940039285380274899124224, (63417747628891221594106738815256002143915995 - 261508229397507037136324178612212080871150958*I)/9903520314283042199192993792], +# [ (-2144231934021288786200752920446633703357 + 2305614436009705803670842248131563850246*I)/1208925819614629174706176, (-90720949337459896266067589013987007078153 - 221951119475096403601562347412753844534569*I)/19342813113834066795298816, (11590973613116630788176337262688659880376 + 6514520676308992726483494976339330626159*I)/4835703278458516698824704, 3*(-131776217149000326618649542018343107657237 + 79095042939612668486212006406818285287004*I)/38685626227668133590597632, (10100577916793945997239221374025741184951 - 28631383488085522003281589065994018550748*I)/9671406556917033397649408, 67*(10090295594251078955008130473573667572549 + 10449901522697161049513326446427839676762*I)/77371252455336267181195264, (-54270981296988368730689531355811033930513 - 3413683117592637309471893510944045467443*I)/19342813113834066795298816, (440372322928679910536575560069973699181278 - 736603803202303189048085196176918214409081*I)/77371252455336267181195264, (33220374714789391132887731139763250155295 + 92055083048787219934030779066298919603554*I)/38685626227668133590597632, 5*(-594638554579967244348856981610805281527116 - 82309245323128933521987392165716076704057*I)/309485009821345068724781056, (128056368815300084550013708313312073721955 - 114619107488668120303579745393765245911404*I)/77371252455336267181195264, 21*(59839959255173222962789517794121843393573 + 241507883613676387255359616163487405826334*I)/618970019642690137449562112], +# [ (-13454485022325376674626653802541391955147 + 184471402121905621396582628515905949793486*I)/19342813113834066795298816, (-6158730123400322562149780662133074862437105 - 3416173052604643794120262081623703514107476*I)/154742504910672534362390528, (770558003844914708453618983120686116100419 - 127758381209767638635199674005029818518766*I)/77371252455336267181195264, (-4693005771813492267479835161596671660631703 + 12703585094750991389845384539501921531449948*I)/309485009821345068724781056, (-295028157441149027913545676461260860036601 - 841544569970643160358138082317324743450770*I)/77371252455336267181195264, (56716442796929448856312202561538574275502893 + 7216818824772560379753073185990186711454778*I)/1237940039285380274899124224, 15*(-87061038932753366532685677510172566368387 + 61306141156647596310941396434445461895538*I)/154742504910672534362390528, (-3455315109680781412178133042301025723909347 - 24969329563196972466388460746447646686670670*I)/618970019642690137449562112, (2453418854160886481106557323699250865361849 + 1497886802326243014471854112161398141242514*I)/309485009821345068724781056, (-151343224544252091980004429001205664193082173 + 90471883264187337053549090899816228846836628*I)/4951760157141521099596496896, (1652018205533026103358164026239417416432989 - 9959733619236515024261775397109724431400162*I)/1237940039285380274899124224, 3*(40676374242956907656984876692623172736522006 + 31023357083037817469535762230872667581366205*I)/4951760157141521099596496896], +# [ (-1226990509403328460274658603410696548387 - 4131739423109992672186585941938392788458*I)/1208925819614629174706176, (162392818524418973411975140074368079662703 + 23706194236915374831230612374344230400704*I)/9671406556917033397649408, (-3935678233089814180000602553655565621193 + 2283744757287145199688061892165659502483*I)/1208925819614629174706176, (-2400210250844254483454290806930306285131 - 315571356806370996069052930302295432758205*I)/19342813113834066795298816, (13365917938215281056563183751673390817910 + 15911483133819801118348625831132324863881*I)/4835703278458516698824704, 3*(-215950551370668982657516660700301003897855 + 51684341999223632631602864028309400489378*I)/38685626227668133590597632, (20886089946811765149439844691320027184765 - 30806277083146786592790625980769214361844*I)/9671406556917033397649408, (562180634592713285745940856221105667874855 + 1031543963988260765153550559766662245114916*I)/77371252455336267181195264, (-65820625814810177122941758625652476012867 - 12429918324787060890804395323920477537595*I)/19342813113834066795298816, (319147848192012911298771180196635859221089 - 402403304933906769233365689834404519960394*I)/38685626227668133590597632, (23035615120921026080284733394359587955057 + 115351677687031786114651452775242461310624*I)/38685626227668133590597632, (-3426830634881892756966440108592579264936130 - 1022954961164128745603407283836365128598559*I)/309485009821345068724781056], +# [ (-192574788060137531023716449082856117537757 - 69222967328876859586831013062387845780692*I)/19342813113834066795298816, (2736383768828013152914815341491629299773262 - 2773252698016291897599353862072533475408743*I)/77371252455336267181195264, (-23280005281223837717773057436155921656805 + 214784953368021840006305033048142888879224*I)/19342813113834066795298816, (-3035247484028969580570400133318947903462326 - 2195168903335435855621328554626336958674325*I)/77371252455336267181195264, (984552428291526892214541708637840971548653 - 64006622534521425620714598573494988589378*I)/77371252455336267181195264, (-3070650452470333005276715136041262898509903 + 7286424705750810474140953092161794621989080*I)/154742504910672534362390528, (-147848877109756404594659513386972921139270 - 416306113044186424749331418059456047650861*I)/38685626227668133590597632, (55272118474097814260289392337160619494260781 + 7494019668394781211907115583302403519488058*I)/1237940039285380274899124224, (-581537886583682322424771088996959213068864 + 542191617758465339135308203815256798407429*I)/77371252455336267181195264, (-6422548983676355789975736799494791970390991 - 23524183982209004826464749309156698827737702*I)/618970019642690137449562112, 7*(180747195387024536886923192475064903482083 + 84352527693562434817771649853047924991804*I)/154742504910672534362390528, (-135485179036717001055310712747643466592387031 + 102346575226653028836678855697782273460527608*I)/4951760157141521099596496896], +# [ (3384238362616083147067025892852431152105 + 156724444932584900214919898954874618256*I)/604462909807314587353088, (-59558300950677430189587207338385764871866 + 114427143574375271097298201388331237478857*I)/4835703278458516698824704, (-1356835789870635633517710130971800616227 - 7023484098542340388800213478357340875410*I)/1208925819614629174706176, (234884918567993750975181728413524549575881 + 79757294640629983786895695752733890213506*I)/9671406556917033397649408, (-7632732774935120473359202657160313866419 + 2905452608512927560554702228553291839465*I)/1208925819614629174706176, (52291747908702842344842889809762246649489 - 520996778817151392090736149644507525892649*I)/19342813113834066795298816, (17472406829219127839967951180375981717322 + 23464704213841582137898905375041819568669*I)/4835703278458516698824704, (-911026971811893092350229536132730760943307 + 150799318130900944080399439626714846752360*I)/38685626227668133590597632, (26234457233977042811089020440646443590687 - 45650293039576452023692126463683727692890*I)/9671406556917033397649408, 3*(288348388717468992528382586652654351121357 + 454526517721403048270274049572136109264668*I)/77371252455336267181195264, (-91583492367747094223295011999405657956347 - 12704691128268298435362255538069612411331*I)/19342813113834066795298816, (411208730251327843849027957710164064354221 - 569898526380691606955496789378230959965898*I)/38685626227668133590597632], +# [ (27127513117071487872628354831658811211795 - 37765296987901990355760582016892124833857*I)/4835703278458516698824704, (1741779916057680444272938534338833170625435 + 3083041729779495966997526404685535449810378*I)/77371252455336267181195264, 3*(-60642236251815783728374561836962709533401 - 24630301165439580049891518846174101510744*I)/19342813113834066795298816, 3*(445885207364591681637745678755008757483408 - 350948497734812895032502179455610024541643*I)/38685626227668133590597632, (-47373295621391195484367368282471381775684 + 219122969294089357477027867028071400054973*I)/19342813113834066795298816, (-2801565819673198722993348253876353741520438 - 2250142129822658548391697042460298703335701*I)/77371252455336267181195264, (801448252275607253266997552356128790317119 - 50890367688077858227059515894356594900558*I)/77371252455336267181195264, (-5082187758525931944557763799137987573501207 + 11610432359082071866576699236013484487676124*I)/309485009821345068724781056, (-328925127096560623794883760398247685166830 - 643447969697471610060622160899409680422019*I)/77371252455336267181195264, 15*(2954944669454003684028194956846659916299765 + 33434406416888505837444969347824812608566*I)/1237940039285380274899124224, (-415749104352001509942256567958449835766827 + 479330966144175743357171151440020955412219*I)/77371252455336267181195264, 3*(-4639987285852134369449873547637372282914255 - 11994411888966030153196659207284951579243273*I)/1237940039285380274899124224], +# [ (-478846096206269117345024348666145495601 + 1249092488629201351470551186322814883283*I)/302231454903657293676544, (-17749319421930878799354766626365926894989 - 18264580106418628161818752318217357231971*I)/1208925819614629174706176, (2801110795431528876849623279389579072819 + 363258850073786330770713557775566973248*I)/604462909807314587353088, (-59053496693129013745775512127095650616252 + 78143588734197260279248498898321500167517*I)/4835703278458516698824704, (-283186724922498212468162690097101115349 - 6443437753863179883794497936345437398276*I)/1208925819614629174706176, (188799118826748909206887165661384998787543 + 84274736720556630026311383931055307398820*I)/9671406556917033397649408, (-5482217151670072904078758141270295025989 + 1818284338672191024475557065444481298568*I)/1208925819614629174706176, (56564463395350195513805521309731217952281 - 360208541416798112109946262159695452898431*I)/19342813113834066795298816, 11*(1259539805728870739006416869463689438068 + 1409136581547898074455004171305324917387*I)/4835703278458516698824704, 5*(-123701190701414554945251071190688818343325 + 30997157322590424677294553832111902279712*I)/38685626227668133590597632, (16130917381301373033736295883982414239781 - 32752041297570919727145380131926943374516*I)/9671406556917033397649408, (650301385108223834347093740500375498354925 + 899526407681131828596801223402866051809258*I)/77371252455336267181195264], +# [ (9011388245256140876590294262420614839483 + 8167917972423946282513000869327525382672*I)/1208925819614629174706176, (-426393174084720190126376382194036323028924 + 180692224825757525982858693158209545430621*I)/9671406556917033397649408, (24588556702197802674765733448108154175535 - 45091766022876486566421953254051868331066*I)/4835703278458516698824704, (1872113939365285277373877183750416985089691 + 3030392393733212574744122057679633775773130*I)/77371252455336267181195264, (-222173405538046189185754954524429864167549 - 75193157893478637039381059488387511299116*I)/19342813113834066795298816, (2670821320766222522963689317316937579844558 - 2645837121493554383087981511645435472169191*I)/77371252455336267181195264, 5*(-2100110309556476773796963197283876204940 + 41957457246479840487980315496957337371937*I)/19342813113834066795298816, (-5733743755499084165382383818991531258980593 - 3328949988392698205198574824396695027195732*I)/154742504910672534362390528, (707827994365259025461378911159398206329247 - 265730616623227695108042528694302299777294*I)/77371252455336267181195264, (-1442501604682933002895864804409322823788319 + 11504137805563265043376405214378288793343879*I)/309485009821345068724781056, (-56130472299445561499538726459719629522285 - 61117552419727805035810982426639329818864*I)/9671406556917033397649408, (39053692321126079849054272431599539429908717 - 10209127700342570953247177602860848130710666*I)/1237940039285380274899124224]]) + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512], + [ 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64], + [ -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128], + [ 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16], + [ 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M**10 == Matrix(S('''[ + [ 7369525394972778926719607798014571861/604462909807314587353088 - 229284202061790301477392339912557559*I/151115727451828646838272, -19704281515163975949388435612632058035/1208925819614629174706176 + 14319858347987648723768698170712102887*I/302231454903657293676544, -3623281909451783042932142262164941211/604462909807314587353088 - 6039240602494288615094338643452320495*I/604462909807314587353088, 109260497799140408739847239685705357695/2417851639229258349412352 - 7427566006564572463236368211555511431*I/2417851639229258349412352, -16095803767674394244695716092817006641/2417851639229258349412352 + 10336681897356760057393429626719177583*I/1208925819614629174706176, -42207883340488041844332828574359769743/2417851639229258349412352 - 182332262671671273188016400290188468499*I/4835703278458516698824704], + [50566491050825573392726324995779608259/1208925819614629174706176 - 90047007594468146222002432884052362145*I/2417851639229258349412352, 74273703462900000967697427843983822011/1208925819614629174706176 + 265947522682943571171988741842776095421*I/1208925819614629174706176, -116900341394390200556829767923360888429/2417851639229258349412352 - 53153263356679268823910621474478756845*I/2417851639229258349412352, 195407378023867871243426523048612490249/1208925819614629174706176 - 1242417915995360200584837585002906728929*I/9671406556917033397649408, -863597594389821970177319682495878193/302231454903657293676544 + 476936100741548328800725360758734300481*I/9671406556917033397649408, -3154451590535653853562472176601754835575/19342813113834066795298816 - 232909875490506237386836489998407329215*I/2417851639229258349412352], + [ -1715444997702484578716037230949868543/302231454903657293676544 + 5009695651321306866158517287924120777*I/302231454903657293676544, -30551582497996879620371947949342101301/604462909807314587353088 - 7632518367986526187139161303331519629*I/151115727451828646838272, 312680739924495153190604170938220575/18889465931478580854784 - 108664334509328818765959789219208459*I/75557863725914323419136, -14693696966703036206178521686918865509/604462909807314587353088 + 72345386220900843930147151999899692401*I/1208925819614629174706176, -8218872496728882299722894680635296519/1208925819614629174706176 - 16776782833358893712645864791807664983*I/1208925819614629174706176, 143237839169380078671242929143670635137/2417851639229258349412352 + 2883817094806115974748882735218469447*I/2417851639229258349412352], + [ 3087979417831061365023111800749855987/151115727451828646838272 + 34441942370802869368851419102423997089*I/604462909807314587353088, -148309181940158040917731426845476175667/604462909807314587353088 - 263987151804109387844966835369350904919*I/9671406556917033397649408, 50259518594816377378747711930008883165/1208925819614629174706176 - 95713974916869240305450001443767979653*I/2417851639229258349412352, 153466447023875527996457943521467271119/2417851639229258349412352 + 517285524891117105834922278517084871349*I/2417851639229258349412352, -29184653615412989036678939366291205575/604462909807314587353088 - 27551322282526322041080173287022121083*I/1208925819614629174706176, 196404220110085511863671393922447671649/1208925819614629174706176 - 1204712019400186021982272049902206202145*I/9671406556917033397649408], + [ -2632581805949645784625606590600098779/151115727451828646838272 - 589957435912868015140272627522612771*I/37778931862957161709568, 26727850893953715274702844733506310247/302231454903657293676544 - 10825791956782128799168209600694020481*I/302231454903657293676544, -1036348763702366164044671908440791295/151115727451828646838272 + 3188624571414467767868303105288107375*I/151115727451828646838272, -36814959939970644875593411585393242449/604462909807314587353088 - 18457555789119782404850043842902832647*I/302231454903657293676544, 12454491297984637815063964572803058647/604462909807314587353088 - 340489532842249733975074349495329171*I/302231454903657293676544, -19547211751145597258386735573258916681/604462909807314587353088 + 87299583775782199663414539883938008933*I/1208925819614629174706176], + [ -40281994229560039213253423262678393183/604462909807314587353088 - 2939986850065527327299273003299736641*I/604462909807314587353088, 331940684638052085845743020267462794181/2417851639229258349412352 - 284574901963624403933361315517248458969*I/1208925819614629174706176, 6453843623051745485064693628073010961/302231454903657293676544 + 36062454107479732681350914931391590957*I/604462909807314587353088, -147665869053634695632880753646441962067/604462909807314587353088 - 305987938660447291246597544085345123927*I/9671406556917033397649408, 107821369195275772166593879711259469423/2417851639229258349412352 - 11645185518211204108659001435013326687*I/302231454903657293676544, 64121228424717666402009446088588091619/1208925819614629174706176 + 265557133337095047883844369272389762133*I/1208925819614629174706176]]''')) + +def test_issue_17247_expression_blowup_5(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.charpoly('x') == PurePoly(x**6 + (-6 - 6*I)*x**5 + 36*I*x**4, x, domain='EX') + +def test_issue_17247_expression_blowup_6(): + M = Matrix(8, 8, [x+i for i in range (64)]) + with dotprodsimp(True): + assert M.det('bareiss') == 0 + +def test_issue_17247_expression_blowup_7(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.det('berkowitz') == 0 + +def test_issue_17247_expression_blowup_8(): + M = Matrix(8, 8, [x+i for i in range (64)]) + with dotprodsimp(True): + assert M.det('lu') == 0 + +def test_issue_17247_expression_blowup_9(): + M = Matrix(8, 8, [x+i for i in range (64)]) + with dotprodsimp(True): + assert M.rref() == (Matrix([ + [1, 0, -1, -2, -3, -4, -5, -6], + [0, 1, 2, 3, 4, 5, 6, 7], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]]), (0, 1)) + +def test_issue_17247_expression_blowup_10(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.cofactor(0, 0) == 0 + +def test_issue_17247_expression_blowup_11(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.cofactor_matrix() == Matrix(6, 6, [0]*36) + +def test_issue_17247_expression_blowup_12(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.eigenvals() == {6: 1, 6*I: 1, 0: 4} + +def test_issue_17247_expression_blowup_13(): + M = Matrix([ + [ 0, 1 - x, x + 1, 1 - x], + [1 - x, x + 1, 0, x + 1], + [ 0, 1 - x, x + 1, 1 - x], + [ 0, 0, 1 - x, 0]]) + + ev = M.eigenvects() + assert ev[0] == (0, 2, [Matrix([0, -1, 0, 1])]) + assert ev[1][0] == x - sqrt(2)*(x - 1) + 1 + assert ev[1][1] == 1 + assert ev[1][2][0].expand(deep=False, numer=True) == Matrix([ + [(-x + sqrt(2)*(x - 1) - 1)/(x - 1)], + [-4*x/(x**2 - 2*x + 1) + (x + 1)*(x - sqrt(2)*(x - 1) + 1)/(x**2 - 2*x + 1)], + [(-x + sqrt(2)*(x - 1) - 1)/(x - 1)], + [1] + ]) + + assert ev[2][0] == x + sqrt(2)*(x - 1) + 1 + assert ev[2][1] == 1 + assert ev[2][2][0].expand(deep=False, numer=True) == Matrix([ + [(-x - sqrt(2)*(x - 1) - 1)/(x - 1)], + [-4*x/(x**2 - 2*x + 1) + (x + 1)*(x + sqrt(2)*(x - 1) + 1)/(x**2 - 2*x + 1)], + [(-x - sqrt(2)*(x - 1) - 1)/(x - 1)], + [1] + ]) + + +def test_issue_17247_expression_blowup_14(): + M = Matrix(8, 8, ([1+x, 1-x]*4 + [1-x, 1+x]*4)*4) + with dotprodsimp(True): + assert M.echelon_form() == Matrix([ + [x + 1, 1 - x, x + 1, 1 - x, x + 1, 1 - x, x + 1, 1 - x], + [ 0, 4*x, 0, 4*x, 0, 4*x, 0, 4*x], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0]]) + +def test_issue_17247_expression_blowup_15(): + M = Matrix(8, 8, ([1+x, 1-x]*4 + [1-x, 1+x]*4)*4) + with dotprodsimp(True): + assert M.rowspace() == [Matrix([[x + 1, 1 - x, x + 1, 1 - x, x + 1, 1 - x, x + 1, 1 - x]]), Matrix([[0, 4*x, 0, 4*x, 0, 4*x, 0, 4*x]])] + +def test_issue_17247_expression_blowup_16(): + M = Matrix(8, 8, ([1+x, 1-x]*4 + [1-x, 1+x]*4)*4) + with dotprodsimp(True): + assert M.columnspace() == [Matrix([[x + 1],[1 - x],[x + 1],[1 - x],[x + 1],[1 - x],[x + 1],[1 - x]]), Matrix([[1 - x],[x + 1],[1 - x],[x + 1],[1 - x],[x + 1],[1 - x],[x + 1]])] + +def test_issue_17247_expression_blowup_17(): + M = Matrix(8, 8, [x+i for i in range (64)]) + with dotprodsimp(True): + assert M.nullspace() == [ + Matrix([[1],[-2],[1],[0],[0],[0],[0],[0]]), + Matrix([[2],[-3],[0],[1],[0],[0],[0],[0]]), + Matrix([[3],[-4],[0],[0],[1],[0],[0],[0]]), + Matrix([[4],[-5],[0],[0],[0],[1],[0],[0]]), + Matrix([[5],[-6],[0],[0],[0],[0],[1],[0]]), + Matrix([[6],[-7],[0],[0],[0],[0],[0],[1]])] + +def test_issue_17247_expression_blowup_18(): + M = Matrix(6, 6, ([1+x, 1-x]*3 + [1-x, 1+x]*3)*3) + with dotprodsimp(True): + assert not M.is_nilpotent() + +def test_issue_17247_expression_blowup_19(): + M = Matrix(S('''[ + [ -3/4, 0, 1/4 + I/2, 0], + [ 0, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 1/2 - I, 0, 0, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert not M.is_diagonalizable() + +def test_issue_17247_expression_blowup_20(): + M = Matrix([ + [x + 1, 1 - x, 0, 0], + [1 - x, x + 1, 0, x + 1], + [ 0, 1 - x, x + 1, 0], + [ 0, 0, 0, x + 1]]) + with dotprodsimp(True): + assert M.diagonalize() == (Matrix([ + [1, 1, 0, (x + 1)/(x - 1)], + [1, -1, 0, 0], + [1, 1, 1, 0], + [0, 0, 0, 1]]), + Matrix([ + [2, 0, 0, 0], + [0, 2*x, 0, 0], + [0, 0, x + 1, 0], + [0, 0, 0, x + 1]])) + +def test_issue_17247_expression_blowup_21(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='GE') == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + +def test_issue_17247_expression_blowup_22(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='LU') == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + +def test_issue_17247_expression_blowup_23(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='ADJ').expand() == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + +def test_issue_17247_expression_blowup_24(): + M = SparseMatrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='CH') == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + +def test_issue_17247_expression_blowup_25(): + M = SparseMatrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='LDL') == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + +def test_issue_17247_expression_blowup_26(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64, -9/32 - I/16, 183/256 - 97*I/128], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512, -219/128 + 115*I/256, 6301/4096 - 6609*I/1024], + [ 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64], + [ -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512], + [ 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64], + [ 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128], + [ -2, 17/4 - 13*I/2, 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16], + [ 1/4 + 13*I/4, -825/64 - 147*I/32, 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.rank() == 4 + +def test_issue_17247_expression_blowup_27(): + M = Matrix([ + [ 0, 1 - x, x + 1, 1 - x], + [1 - x, x + 1, 0, x + 1], + [ 0, 1 - x, x + 1, 1 - x], + [ 0, 0, 1 - x, 0]]) + with dotprodsimp(True): + P, J = M.jordan_form() + assert P.expand() == Matrix(S('''[ + [ 0, 4*x/(x**2 - 2*x + 1), -(-17*x**4 + 12*sqrt(2)*x**4 - 4*sqrt(2)*x**3 + 6*x**3 - 6*x - 4*sqrt(2)*x + 12*sqrt(2) + 17)/(-7*x**4 + 5*sqrt(2)*x**4 - 6*sqrt(2)*x**3 + 8*x**3 - 2*x**2 + 8*x + 6*sqrt(2)*x - 5*sqrt(2) - 7), -(12*sqrt(2)*x**4 + 17*x**4 - 6*x**3 - 4*sqrt(2)*x**3 - 4*sqrt(2)*x + 6*x - 17 + 12*sqrt(2))/(7*x**4 + 5*sqrt(2)*x**4 - 6*sqrt(2)*x**3 - 8*x**3 + 2*x**2 - 8*x + 6*sqrt(2)*x - 5*sqrt(2) + 7)], + [x - 1, x/(x - 1) + 1/(x - 1), (-7*x**3 + 5*sqrt(2)*x**3 - x**2 + sqrt(2)*x**2 - sqrt(2)*x - x - 5*sqrt(2) - 7)/(-3*x**3 + 2*sqrt(2)*x**3 - 2*sqrt(2)*x**2 + 3*x**2 + 2*sqrt(2)*x + 3*x - 3 - 2*sqrt(2)), (7*x**3 + 5*sqrt(2)*x**3 + x**2 + sqrt(2)*x**2 - sqrt(2)*x + x - 5*sqrt(2) + 7)/(2*sqrt(2)*x**3 + 3*x**3 - 3*x**2 - 2*sqrt(2)*x**2 - 3*x + 2*sqrt(2)*x - 2*sqrt(2) + 3)], + [ 0, 1, -(-3*x**2 + 2*sqrt(2)*x**2 + 2*x - 3 - 2*sqrt(2))/(-x**2 + sqrt(2)*x**2 - 2*sqrt(2)*x + 1 + sqrt(2)), -(2*sqrt(2)*x**2 + 3*x**2 - 2*x - 2*sqrt(2) + 3)/(x**2 + sqrt(2)*x**2 - 2*sqrt(2)*x - 1 + sqrt(2))], + [1 - x, 0, 1, 1]]''')).expand() + assert J == Matrix(S('''[ + [0, 1, 0, 0], + [0, 0, 0, 0], + [0, 0, x - sqrt(2)*(x - 1) + 1, 0], + [0, 0, 0, x + sqrt(2)*(x - 1) + 1]]''')) + +def test_issue_17247_expression_blowup_28(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.singular_values() == S('''[ + sqrt(14609315/131072 + sqrt(64789115132571/2147483648 - 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3) + 76627253330829751075/(35184372088832*sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))) - 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)))/2 + sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))/2), + sqrt(14609315/131072 - sqrt(64789115132571/2147483648 - 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3) + 76627253330829751075/(35184372088832*sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))) - 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)))/2 + sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))/2), + sqrt(14609315/131072 - sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))/2 + sqrt(64789115132571/2147483648 - 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3) - 76627253330829751075/(35184372088832*sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))) - 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)))/2), + sqrt(14609315/131072 - sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))/2 - sqrt(64789115132571/2147483648 - 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3) - 76627253330829751075/(35184372088832*sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))) - 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)))/2)]''') + + +def test_issue_16823(): + # This still needs to be fixed if not using dotprodsimp. + M = Matrix(S('''[ + [1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I,1/4-5/16*I,65/128+87/64*I,-9/32-1/16*I,183/256-97/128*I,3/64+13/64*I,-23/32-59/256*I,15/128-3/32*I,19/256+551/1024*I], + [21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I,85/256-33/16*I,805/128+2415/512*I,-219/128+115/256*I,6301/4096-6609/1024*I,119/128+143/128*I,-10879/2048+4343/4096*I,129/256-549/512*I,42533/16384+29103/8192*I], + [-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I,1/4-5/16*I,65/128+87/64*I,-9/32-1/16*I,183/256-97/128*I,3/64+13/64*I,-23/32-59/256*I], + [1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I,85/256-33/16*I,805/128+2415/512*I,-219/128+115/256*I,6301/4096-6609/1024*I,119/128+143/128*I,-10879/2048+4343/4096*I], + [-4*I,27/2+6*I,-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I,1/4-5/16*I,65/128+87/64*I,-9/32-1/16*I,183/256-97/128*I], + [1/4+5/2*I,-23/8-57/16*I,1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I,85/256-33/16*I,805/128+2415/512*I,-219/128+115/256*I,6301/4096-6609/1024*I], + [-4,9-5*I,-4*I,27/2+6*I,-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I,1/4-5/16*I,65/128+87/64*I], + [-2*I,119/8+29/4*I,1/4+5/2*I,-23/8-57/16*I,1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I,85/256-33/16*I,805/128+2415/512*I], + [0,-6,-4,9-5*I,-4*I,27/2+6*I,-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I], + [1,-9/4+3*I,-2*I,119/8+29/4*I,1/4+5/2*I,-23/8-57/16*I,1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I], + [0,-4*I,0,-6,-4,9-5*I,-4*I,27/2+6*I,-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I], + [0,1/4+1/2*I,1,-9/4+3*I,-2*I,119/8+29/4*I,1/4+5/2*I,-23/8-57/16*I,1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I]]''')) + with dotprodsimp(True): + assert M.rank() == 8 + + +def test_issue_18531(): + # solve_linear_system still needs fixing but the rref works. + M = Matrix([ + [1, 1, 1, 1, 1, 0, 1, 0, 0], + [1 + sqrt(2), -1 + sqrt(2), 1 - sqrt(2), -sqrt(2) - 1, 1, 1, -1, 1, 1], + [-5 + 2*sqrt(2), -5 - 2*sqrt(2), -5 - 2*sqrt(2), -5 + 2*sqrt(2), -7, 2, -7, -2, 0], + [-3*sqrt(2) - 1, 1 - 3*sqrt(2), -1 + 3*sqrt(2), 1 + 3*sqrt(2), -7, -5, 7, -5, 3], + [7 - 4*sqrt(2), 4*sqrt(2) + 7, 4*sqrt(2) + 7, 7 - 4*sqrt(2), 7, -12, 7, 12, 0], + [-1 + 3*sqrt(2), 1 + 3*sqrt(2), -3*sqrt(2) - 1, 1 - 3*sqrt(2), 7, -5, -7, -5, 3], + [-3 + 2*sqrt(2), -3 - 2*sqrt(2), -3 - 2*sqrt(2), -3 + 2*sqrt(2), -1, 2, -1, -2, 0], + [1 - sqrt(2), -sqrt(2) - 1, 1 + sqrt(2), -1 + sqrt(2), -1, 1, 1, 1, 1] + ]) + with dotprodsimp(True): + assert M.rref() == (Matrix([ + [1, 0, 0, 0, 0, 0, 0, 0, S(1)/2], + [0, 1, 0, 0, 0, 0, 0, 0, -S(1)/2], + [0, 0, 1, 0, 0, 0, 0, 0, S(1)/2], + [0, 0, 0, 1, 0, 0, 0, 0, -S(1)/2], + [0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, -S(1)/2], + [0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, -S(1)/2]]), (0, 1, 2, 3, 4, 5, 6, 7)) + + +def test_creation(): + raises(ValueError, lambda: Matrix(5, 5, range(20))) + raises(ValueError, lambda: Matrix(5, -1, [])) + raises(IndexError, lambda: Matrix((1, 2))[2]) + with raises(IndexError): + Matrix((1, 2))[3] = 5 + + assert Matrix() == Matrix([]) == Matrix(0, 0, []) + assert Matrix([[]]) == Matrix(1, 0, []) + assert Matrix([[], []]) == Matrix(2, 0, []) + + # anything used to be allowed in a matrix + with warns_deprecated_sympy(): + assert Matrix([[[1], (2,)]]).tolist() == [[[1], (2,)]] + with warns_deprecated_sympy(): + assert Matrix([[[1], (2,)]]).T.tolist() == [[[1]], [(2,)]] + M = Matrix([[0]]) + with warns_deprecated_sympy(): + M[0, 0] = S.EmptySet + + a = Matrix([[x, 0], [0, 0]]) + m = a + assert m.cols == m.rows + assert m.cols == 2 + assert m[:] == [x, 0, 0, 0] + + b = Matrix(2, 2, [x, 0, 0, 0]) + m = b + assert m.cols == m.rows + assert m.cols == 2 + assert m[:] == [x, 0, 0, 0] + + assert a == b + + assert Matrix(b) == b + + c23 = Matrix(2, 3, range(1, 7)) + c13 = Matrix(1, 3, range(7, 10)) + c = Matrix([c23, c13]) + assert c.cols == 3 + assert c.rows == 3 + assert c[:] == [1, 2, 3, 4, 5, 6, 7, 8, 9] + + assert Matrix(eye(2)) == eye(2) + assert ImmutableMatrix(ImmutableMatrix(eye(2))) == ImmutableMatrix(eye(2)) + assert ImmutableMatrix(c) == c.as_immutable() + assert Matrix(ImmutableMatrix(c)) == ImmutableMatrix(c).as_mutable() + + assert c is not Matrix(c) + + dat = [[ones(3,2), ones(3,3)*2], [ones(2,3)*3, ones(2,2)*4]] + M = Matrix(dat) + assert M == Matrix([ + [1, 1, 2, 2, 2], + [1, 1, 2, 2, 2], + [1, 1, 2, 2, 2], + [3, 3, 3, 4, 4], + [3, 3, 3, 4, 4]]) + assert M.tolist() != dat + # keep block form if evaluate=False + assert Matrix(dat, evaluate=False).tolist() == dat + A = MatrixSymbol("A", 2, 2) + dat = [ones(2), A] + assert Matrix(dat) == Matrix([ + [ 1, 1], + [ 1, 1], + [A[0, 0], A[0, 1]], + [A[1, 0], A[1, 1]]]) + with warns_deprecated_sympy(): + assert Matrix(dat, evaluate=False).tolist() == [[i] for i in dat] + + # 0-dim tolerance + assert Matrix([ones(2), ones(0)]) == Matrix([ones(2)]) + raises(ValueError, lambda: Matrix([ones(2), ones(0, 3)])) + raises(ValueError, lambda: Matrix([ones(2), ones(3, 0)])) + + # mix of Matrix and iterable + M = Matrix([[1, 2], [3, 4]]) + M2 = Matrix([M, (5, 6)]) + assert M2 == Matrix([[1, 2], [3, 4], [5, 6]]) + + +def test_irregular_block(): + assert Matrix.irregular(3, ones(2,1), ones(3,3)*2, ones(2,2)*3, + ones(1,1)*4, ones(2,2)*5, ones(1,2)*6, ones(1,2)*7) == Matrix([ + [1, 2, 2, 2, 3, 3], + [1, 2, 2, 2, 3, 3], + [4, 2, 2, 2, 5, 5], + [6, 6, 7, 7, 5, 5]]) + + +def test_tolist(): + lst = [[S.One, S.Half, x*y, S.Zero], [x, y, z, x**2], [y, -S.One, z*x, 3]] + m = Matrix(lst) + assert m.tolist() == lst + + +def test_as_mutable(): + assert zeros(0, 3).as_mutable() == zeros(0, 3) + assert zeros(0, 3).as_immutable() == ImmutableMatrix(zeros(0, 3)) + assert zeros(3, 0).as_immutable() == ImmutableMatrix(zeros(3, 0)) + + +def test_slicing(): + m0 = eye(4) + assert m0[:3, :3] == eye(3) + assert m0[2:4, 0:2] == zeros(2) + + m1 = Matrix(3, 3, lambda i, j: i + j) + assert m1[0, :] == Matrix(1, 3, (0, 1, 2)) + assert m1[1:3, 1] == Matrix(2, 1, (2, 3)) + + m2 = Matrix([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]) + assert m2[:, -1] == Matrix(4, 1, [3, 7, 11, 15]) + assert m2[-2:, :] == Matrix([[8, 9, 10, 11], [12, 13, 14, 15]]) + + +def test_submatrix_assignment(): + m = zeros(4) + m[2:4, 2:4] = eye(2) + assert m == Matrix(((0, 0, 0, 0), + (0, 0, 0, 0), + (0, 0, 1, 0), + (0, 0, 0, 1))) + m[:2, :2] = eye(2) + assert m == eye(4) + m[:, 0] = Matrix(4, 1, (1, 2, 3, 4)) + assert m == Matrix(((1, 0, 0, 0), + (2, 1, 0, 0), + (3, 0, 1, 0), + (4, 0, 0, 1))) + m[:, :] = zeros(4) + assert m == zeros(4) + m[:, :] = [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12), (13, 14, 15, 16)] + assert m == Matrix(((1, 2, 3, 4), + (5, 6, 7, 8), + (9, 10, 11, 12), + (13, 14, 15, 16))) + m[:2, 0] = [0, 0] + assert m == Matrix(((0, 2, 3, 4), + (0, 6, 7, 8), + (9, 10, 11, 12), + (13, 14, 15, 16))) + + +def test_extract(): + m = Matrix(4, 3, lambda i, j: i*3 + j) + assert m.extract([0, 1, 3], [0, 1]) == Matrix(3, 2, [0, 1, 3, 4, 9, 10]) + assert m.extract([0, 3], [0, 0, 2]) == Matrix(2, 3, [0, 0, 2, 9, 9, 11]) + assert m.extract(range(4), range(3)) == m + raises(IndexError, lambda: m.extract([4], [0])) + raises(IndexError, lambda: m.extract([0], [3])) + + +def test_reshape(): + m0 = eye(3) + assert m0.reshape(1, 9) == Matrix(1, 9, (1, 0, 0, 0, 1, 0, 0, 0, 1)) + m1 = Matrix(3, 4, lambda i, j: i + j) + assert m1.reshape( + 4, 3) == Matrix(((0, 1, 2), (3, 1, 2), (3, 4, 2), (3, 4, 5))) + assert m1.reshape(2, 6) == Matrix(((0, 1, 2, 3, 1, 2), (3, 4, 2, 3, 4, 5))) + + +def test_applyfunc(): + m0 = eye(3) + assert m0.applyfunc(lambda x: 2*x) == eye(3)*2 + assert m0.applyfunc(lambda x: 0) == zeros(3) + + +def test_expand(): + m0 = Matrix([[x*(x + y), 2], [((x + y)*y)*x, x*(y + x*(x + y))]]) + # Test if expand() returns a matrix + m1 = m0.expand() + assert m1 == Matrix( + [[x*y + x**2, 2], [x*y**2 + y*x**2, x*y + y*x**2 + x**3]]) + + a = Symbol('a', real=True) + + assert Matrix([exp(I*a)]).expand(complex=True) == \ + Matrix([cos(a) + I*sin(a)]) + + assert Matrix([[0, 1, 2], [0, 0, -1], [0, 0, 0]]).exp() == Matrix([ + [1, 1, Rational(3, 2)], + [0, 1, -1], + [0, 0, 1]] + ) + +def test_refine(): + m0 = Matrix([[Abs(x)**2, sqrt(x**2)], + [sqrt(x**2)*Abs(y)**2, sqrt(y**2)*Abs(x)**2]]) + m1 = m0.refine(Q.real(x) & Q.real(y)) + assert m1 == Matrix([[x**2, Abs(x)], [y**2*Abs(x), x**2*Abs(y)]]) + + m1 = m0.refine(Q.positive(x) & Q.positive(y)) + assert m1 == Matrix([[x**2, x], [x*y**2, x**2*y]]) + + m1 = m0.refine(Q.negative(x) & Q.negative(y)) + assert m1 == Matrix([[x**2, -x], [-x*y**2, -x**2*y]]) + +def test_random(): + M = randMatrix(3, 3) + M = randMatrix(3, 3, seed=3) + assert M == randMatrix(3, 3, seed=3) + + M = randMatrix(3, 4, 0, 150) + M = randMatrix(3, seed=4, symmetric=True) + assert M == randMatrix(3, seed=4, symmetric=True) + + S = M.copy() + S.simplify() + assert S == M # doesn't fail when elements are Numbers, not int + + rng = random.Random(4) + assert M == randMatrix(3, symmetric=True, prng=rng) + + # Ensure symmetry + for size in (10, 11): # Test odd and even + for percent in (100, 70, 30): + M = randMatrix(size, symmetric=True, percent=percent, prng=rng) + assert M == M.T + + M = randMatrix(10, min=1, percent=70) + zero_count = 0 + for i in range(M.shape[0]): + for j in range(M.shape[1]): + if M[i, j] == 0: + zero_count += 1 + assert zero_count == 30 + +def test_inverse(): + A = eye(4) + assert A.inv() == eye(4) + assert A.inv(method="LU") == eye(4) + assert A.inv(method="ADJ") == eye(4) + assert A.inv(method="CH") == eye(4) + assert A.inv(method="LDL") == eye(4) + assert A.inv(method="QR") == eye(4) + A = Matrix([[2, 3, 5], + [3, 6, 2], + [8, 3, 6]]) + Ainv = A.inv() + assert A*Ainv == eye(3) + assert A.inv(method="LU") == Ainv + assert A.inv(method="ADJ") == Ainv + assert A.inv(method="CH") == Ainv + assert A.inv(method="LDL") == Ainv + assert A.inv(method="QR") == Ainv + + AA = Matrix([[0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0], + [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1], + [1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0], + [1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0], + [1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1], + [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1], + [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0], + [1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0], + [0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0], + [0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1], + [0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1], + [0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0]]) + assert AA.inv(method="BLOCK") * AA == eye(AA.shape[0]) + # test that immutability is not a problem + cls = ImmutableMatrix + m = cls([[48, 49, 31], + [ 9, 71, 94], + [59, 28, 65]]) + assert all(type(m.inv(s)) is cls for s in 'GE ADJ LU CH LDL QR'.split()) + cls = ImmutableSparseMatrix + m = cls([[48, 49, 31], + [ 9, 71, 94], + [59, 28, 65]]) + assert all(type(m.inv(s)) is cls for s in 'GE ADJ LU CH LDL QR'.split()) + + +def test_jacobian_hessian(): + L = Matrix(1, 2, [x**2*y, 2*y**2 + x*y]) + syms = [x, y] + assert L.jacobian(syms) == Matrix([[2*x*y, x**2], [y, 4*y + x]]) + + L = Matrix(1, 2, [x, x**2*y**3]) + assert L.jacobian(syms) == Matrix([[1, 0], [2*x*y**3, x**2*3*y**2]]) + + f = x**2*y + syms = [x, y] + assert hessian(f, syms) == Matrix([[2*y, 2*x], [2*x, 0]]) + + f = x**2*y**3 + assert hessian(f, syms) == \ + Matrix([[2*y**3, 6*x*y**2], [6*x*y**2, 6*x**2*y]]) + + f = z + x*y**2 + g = x**2 + 2*y**3 + ans = Matrix([[0, 2*y], + [2*y, 2*x]]) + assert ans == hessian(f, Matrix([x, y])) + assert ans == hessian(f, Matrix([x, y]).T) + assert hessian(f, (y, x), [g]) == Matrix([ + [ 0, 6*y**2, 2*x], + [6*y**2, 2*x, 2*y], + [ 2*x, 2*y, 0]]) + + +def test_wronskian(): + assert wronskian([cos(x), sin(x)], x) == cos(x)**2 + sin(x)**2 + assert wronskian([exp(x), exp(2*x)], x) == exp(3*x) + assert wronskian([exp(x), x], x) == exp(x) - x*exp(x) + assert wronskian([1, x, x**2], x) == 2 + w1 = -6*exp(x)*sin(x)*x + 6*cos(x)*exp(x)*x**2 - 6*exp(x)*cos(x)*x - \ + exp(x)*cos(x)*x**3 + exp(x)*sin(x)*x**3 + assert wronskian([exp(x), cos(x), x**3], x).expand() == w1 + assert wronskian([exp(x), cos(x), x**3], x, method='berkowitz').expand() \ + == w1 + w2 = -x**3*cos(x)**2 - x**3*sin(x)**2 - 6*x*cos(x)**2 - 6*x*sin(x)**2 + assert wronskian([sin(x), cos(x), x**3], x).expand() == w2 + assert wronskian([sin(x), cos(x), x**3], x, method='berkowitz').expand() \ + == w2 + assert wronskian([], x) == 1 + + +def test_subs(): + assert Matrix([[1, x], [x, 4]]).subs(x, 5) == Matrix([[1, 5], [5, 4]]) + assert Matrix([[x, 2], [x + y, 4]]).subs([[x, -1], [y, -2]]) == \ + Matrix([[-1, 2], [-3, 4]]) + assert Matrix([[x, 2], [x + y, 4]]).subs([(x, -1), (y, -2)]) == \ + Matrix([[-1, 2], [-3, 4]]) + assert Matrix([[x, 2], [x + y, 4]]).subs({x: -1, y: -2}) == \ + Matrix([[-1, 2], [-3, 4]]) + assert Matrix([x*y]).subs({x: y - 1, y: x - 1}, simultaneous=True) == \ + Matrix([(x - 1)*(y - 1)]) + + for cls in classes: + assert Matrix([[2, 0], [0, 2]]) == cls.eye(2).subs(1, 2) + +def test_xreplace(): + assert Matrix([[1, x], [x, 4]]).xreplace({x: 5}) == \ + Matrix([[1, 5], [5, 4]]) + assert Matrix([[x, 2], [x + y, 4]]).xreplace({x: -1, y: -2}) == \ + Matrix([[-1, 2], [-3, 4]]) + for cls in classes: + assert Matrix([[2, 0], [0, 2]]) == cls.eye(2).xreplace({1: 2}) + +def test_simplify(): + n = Symbol('n') + f = Function('f') + + M = Matrix([[ 1/x + 1/y, (x + x*y) / x ], + [ (f(x) + y*f(x))/f(x), 2 * (1/n - cos(n * pi)/n) / pi ]]) + M.simplify() + assert M == Matrix([[ (x + y)/(x * y), 1 + y ], + [ 1 + y, 2*((1 - 1*cos(pi*n))/(pi*n)) ]]) + eq = (1 + x)**2 + M = Matrix([[eq]]) + M.simplify() + assert M == Matrix([[eq]]) + M.simplify(ratio=oo) + assert M == Matrix([[eq.simplify(ratio=oo)]]) + + +def test_transpose(): + M = Matrix([[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]]) + assert M.T == Matrix( [ [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6], + [7, 7], + [8, 8], + [9, 9], + [0, 0] ]) + assert M.T.T == M + assert M.T == M.transpose() + + +def test_conjugate(): + M = Matrix([[0, I, 5], + [1, 2, 0]]) + + assert M.T == Matrix([[0, 1], + [I, 2], + [5, 0]]) + + assert M.C == Matrix([[0, -I, 5], + [1, 2, 0]]) + assert M.C == M.conjugate() + + assert M.H == M.T.C + assert M.H == Matrix([[ 0, 1], + [-I, 2], + [ 5, 0]]) + + +def test_conj_dirac(): + raises(AttributeError, lambda: eye(3).D) + + M = Matrix([[1, I, I, I], + [0, 1, I, I], + [0, 0, 1, I], + [0, 0, 0, 1]]) + + assert M.D == Matrix([[ 1, 0, 0, 0], + [-I, 1, 0, 0], + [-I, -I, -1, 0], + [-I, -I, I, -1]]) + + +def test_trace(): + M = Matrix([[1, 0, 0], + [0, 5, 0], + [0, 0, 8]]) + assert M.trace() == 14 + + +def test_shape(): + M = Matrix([[x, 0, 0], + [0, y, 0]]) + assert M.shape == (2, 3) + + +def test_col_row_op(): + M = Matrix([[x, 0, 0], + [0, y, 0]]) + M.row_op(1, lambda r, j: r + j + 1) + assert M == Matrix([[x, 0, 0], + [1, y + 2, 3]]) + + M.col_op(0, lambda c, j: c + y**j) + assert M == Matrix([[x + 1, 0, 0], + [1 + y, y + 2, 3]]) + + # neither row nor slice give copies that allow the original matrix to + # be changed + assert M.row(0) == Matrix([[x + 1, 0, 0]]) + r1 = M.row(0) + r1[0] = 42 + assert M[0, 0] == x + 1 + r1 = M[0, :-1] # also testing negative slice + r1[0] = 42 + assert M[0, 0] == x + 1 + c1 = M.col(0) + assert c1 == Matrix([x + 1, 1 + y]) + c1[0] = 0 + assert M[0, 0] == x + 1 + c1 = M[:, 0] + c1[0] = 42 + assert M[0, 0] == x + 1 + + +def test_row_mult(): + M = Matrix([[1,2,3], + [4,5,6]]) + M.row_mult(1,3) + assert M[1,0] == 12 + assert M[0,0] == 1 + assert M[1,2] == 18 + + +def test_row_add(): + M = Matrix([[1,2,3], + [4,5,6], + [1,1,1]]) + M.row_add(2,0,5) + assert M[0,0] == 6 + assert M[1,0] == 4 + assert M[0,2] == 8 + + +def test_zip_row_op(): + for cls in classes[:2]: # XXX: immutable matrices don't support row ops + M = cls.eye(3) + M.zip_row_op(1, 0, lambda v, u: v + 2*u) + assert M == cls([[1, 0, 0], + [2, 1, 0], + [0, 0, 1]]) + + M = cls.eye(3)*2 + M[0, 1] = -1 + M.zip_row_op(1, 0, lambda v, u: v + 2*u); M + assert M == cls([[2, -1, 0], + [4, 0, 0], + [0, 0, 2]]) + +def test_issue_3950(): + m = Matrix([1, 2, 3]) + a = Matrix([1, 2, 3]) + b = Matrix([2, 2, 3]) + assert not (m in []) + assert not (m in [1]) + assert m != 1 + assert m == a + assert m != b + + +def test_issue_3981(): + class Index1: + def __index__(self): + return 1 + + class Index2: + def __index__(self): + return 2 + index1 = Index1() + index2 = Index2() + + m = Matrix([1, 2, 3]) + + assert m[index2] == 3 + + m[index2] = 5 + assert m[2] == 5 + + m = Matrix([[1, 2, 3], [4, 5, 6]]) + assert m[index1, index2] == 6 + assert m[1, index2] == 6 + assert m[index1, 2] == 6 + + m[index1, index2] = 4 + assert m[1, 2] == 4 + m[1, index2] = 6 + assert m[1, 2] == 6 + m[index1, 2] = 8 + assert m[1, 2] == 8 + + +def test_evalf(): + a = Matrix([sqrt(5), 6]) + assert all(a.evalf()[i] == a[i].evalf() for i in range(2)) + assert all(a.evalf(2)[i] == a[i].evalf(2) for i in range(2)) + assert all(a.n(2)[i] == a[i].n(2) for i in range(2)) + + +def test_is_symbolic(): + a = Matrix([[x, x], [x, x]]) + assert a.is_symbolic() is True + a = Matrix([[1, 2, 3, 4], [5, 6, 7, 8]]) + assert a.is_symbolic() is False + a = Matrix([[1, 2, 3, 4], [5, 6, x, 8]]) + assert a.is_symbolic() is True + a = Matrix([[1, x, 3]]) + assert a.is_symbolic() is True + a = Matrix([[1, 2, 3]]) + assert a.is_symbolic() is False + a = Matrix([[1], [x], [3]]) + assert a.is_symbolic() is True + a = Matrix([[1], [2], [3]]) + assert a.is_symbolic() is False + + +def test_is_upper(): + a = Matrix([[1, 2, 3]]) + assert a.is_upper is True + a = Matrix([[1], [2], [3]]) + assert a.is_upper is False + a = zeros(4, 2) + assert a.is_upper is True + + +def test_is_lower(): + a = Matrix([[1, 2, 3]]) + assert a.is_lower is False + a = Matrix([[1], [2], [3]]) + assert a.is_lower is True + + +def test_is_nilpotent(): + a = Matrix(4, 4, [0, 2, 1, 6, 0, 0, 1, 2, 0, 0, 0, 3, 0, 0, 0, 0]) + assert a.is_nilpotent() + a = Matrix([[1, 0], [0, 1]]) + assert not a.is_nilpotent() + a = Matrix([]) + assert a.is_nilpotent() + + +def test_zeros_ones_fill(): + n, m = 3, 5 + + a = zeros(n, m) + a.fill( 5 ) + + b = 5 * ones(n, m) + + assert a == b + assert a.rows == b.rows == 3 + assert a.cols == b.cols == 5 + assert a.shape == b.shape == (3, 5) + assert zeros(2) == zeros(2, 2) + assert ones(2) == ones(2, 2) + assert zeros(2, 3) == Matrix(2, 3, [0]*6) + assert ones(2, 3) == Matrix(2, 3, [1]*6) + + a.fill(0) + assert a == zeros(n, m) + + +def test_empty_zeros(): + a = zeros(0) + assert a == Matrix() + a = zeros(0, 2) + assert a.rows == 0 + assert a.cols == 2 + a = zeros(2, 0) + assert a.rows == 2 + assert a.cols == 0 + + +def test_issue_3749(): + a = Matrix([[x**2, x*y], [x*sin(y), x*cos(y)]]) + assert a.diff(x) == Matrix([[2*x, y], [sin(y), cos(y)]]) + assert Matrix([ + [x, -x, x**2], + [exp(x), 1/x - exp(-x), x + 1/x]]).limit(x, oo) == \ + Matrix([[oo, -oo, oo], [oo, 0, oo]]) + assert Matrix([ + [(exp(x) - 1)/x, 2*x + y*x, x**x ], + [1/x, abs(x), abs(sin(x + 1))]]).limit(x, 0) == \ + Matrix([[1, 0, 1], [oo, 0, sin(1)]]) + assert a.integrate(x) == Matrix([ + [Rational(1, 3)*x**3, y*x**2/2], + [x**2*sin(y)/2, x**2*cos(y)/2]]) + + +def test_inv_iszerofunc(): + A = eye(4) + A.col_swap(0, 1) + for method in "GE", "LU": + assert A.inv(method=method, iszerofunc=lambda x: x == 0) == \ + A.inv(method="ADJ") + + +def test_jacobian_metrics(): + rho, phi = symbols("rho,phi") + X = Matrix([rho*cos(phi), rho*sin(phi)]) + Y = Matrix([rho, phi]) + J = X.jacobian(Y) + assert J == X.jacobian(Y.T) + assert J == (X.T).jacobian(Y) + assert J == (X.T).jacobian(Y.T) + g = J.T*eye(J.shape[0])*J + g = g.applyfunc(trigsimp) + assert g == Matrix([[1, 0], [0, rho**2]]) + + +def test_jacobian2(): + rho, phi = symbols("rho,phi") + X = Matrix([rho*cos(phi), rho*sin(phi), rho**2]) + Y = Matrix([rho, phi]) + J = Matrix([ + [cos(phi), -rho*sin(phi)], + [sin(phi), rho*cos(phi)], + [ 2*rho, 0], + ]) + assert X.jacobian(Y) == J + + +def test_issue_4564(): + X = Matrix([exp(x + y + z), exp(x + y + z), exp(x + y + z)]) + Y = Matrix([x, y, z]) + for i in range(1, 3): + for j in range(1, 3): + X_slice = X[:i, :] + Y_slice = Y[:j, :] + J = X_slice.jacobian(Y_slice) + assert J.rows == i + assert J.cols == j + for k in range(j): + assert J[:, k] == X_slice + + +def test_nonvectorJacobian(): + X = Matrix([[exp(x + y + z), exp(x + y + z)], + [exp(x + y + z), exp(x + y + z)]]) + raises(TypeError, lambda: X.jacobian(Matrix([x, y, z]))) + X = X[0, :] + Y = Matrix([[x, y], [x, z]]) + raises(TypeError, lambda: X.jacobian(Y)) + raises(TypeError, lambda: X.jacobian(Matrix([ [x, y], [x, z] ]))) + + +def test_vec(): + m = Matrix([[1, 3], [2, 4]]) + m_vec = m.vec() + assert m_vec.cols == 1 + for i in range(4): + assert m_vec[i] == i + 1 + + +def test_vech(): + m = Matrix([[1, 2], [2, 3]]) + m_vech = m.vech() + assert m_vech.cols == 1 + for i in range(3): + assert m_vech[i] == i + 1 + m_vech = m.vech(diagonal=False) + assert m_vech[0] == 2 + + m = Matrix([[1, x*(x + y)], [y*x + x**2, 1]]) + m_vech = m.vech(diagonal=False) + assert m_vech[0] == y*x + x**2 + + m = Matrix([[1, x*(x + y)], [y*x, 1]]) + m_vech = m.vech(diagonal=False, check_symmetry=False) + assert m_vech[0] == y*x + + raises(ShapeError, lambda: Matrix([[1, 3]]).vech()) + raises(ValueError, lambda: Matrix([[1, 3], [2, 4]]).vech()) + raises(ShapeError, lambda: Matrix([[1, 3]]).vech()) + raises(ValueError, lambda: Matrix([[1, 3], [2, 4]]).vech()) + + +def test_diag(): + # mostly tested in testcommonmatrix.py + assert diag([1, 2, 3]) == Matrix([1, 2, 3]) + m = [1, 2, [3]] + raises(ValueError, lambda: diag(m)) + assert diag(m, strict=False) == Matrix([1, 2, 3]) + + +def test_get_diag_blocks1(): + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + assert a.get_diag_blocks() == [a] + assert b.get_diag_blocks() == [b] + assert c.get_diag_blocks() == [c] + + +def test_get_diag_blocks2(): + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + assert diag(a, b, b).get_diag_blocks() == [a, b, b] + assert diag(a, b, c).get_diag_blocks() == [a, b, c] + assert diag(a, c, b).get_diag_blocks() == [a, c, b] + assert diag(c, c, b).get_diag_blocks() == [c, c, b] + + +def test_inv_block(): + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + A = diag(a, b, b) + assert A.inv(try_block_diag=True) == diag(a.inv(), b.inv(), b.inv()) + A = diag(a, b, c) + assert A.inv(try_block_diag=True) == diag(a.inv(), b.inv(), c.inv()) + A = diag(a, c, b) + assert A.inv(try_block_diag=True) == diag(a.inv(), c.inv(), b.inv()) + A = diag(a, a, b, a, c, a) + assert A.inv(try_block_diag=True) == diag( + a.inv(), a.inv(), b.inv(), a.inv(), c.inv(), a.inv()) + assert A.inv(try_block_diag=True, method="ADJ") == diag( + a.inv(method="ADJ"), a.inv(method="ADJ"), b.inv(method="ADJ"), + a.inv(method="ADJ"), c.inv(method="ADJ"), a.inv(method="ADJ")) + + +def test_creation_args(): + """ + Check that matrix dimensions can be specified using any reasonable type + (see issue 4614). + """ + raises(ValueError, lambda: zeros(3, -1)) + raises(TypeError, lambda: zeros(1, 2, 3, 4)) + assert zeros(int(3)) == zeros(3) + assert zeros(Integer(3)) == zeros(3) + raises(ValueError, lambda: zeros(3.)) + assert eye(int(3)) == eye(3) + assert eye(Integer(3)) == eye(3) + raises(ValueError, lambda: eye(3.)) + assert ones(int(3), Integer(4)) == ones(3, 4) + raises(TypeError, lambda: Matrix(5)) + raises(TypeError, lambda: Matrix(1, 2)) + raises(ValueError, lambda: Matrix([1, [2]])) + + +def test_diagonal_symmetrical(): + m = Matrix(2, 2, [0, 1, 1, 0]) + assert not m.is_diagonal() + assert m.is_symmetric() + assert m.is_symmetric(simplify=False) + + m = Matrix(2, 2, [1, 0, 0, 1]) + assert m.is_diagonal() + + m = diag(1, 2, 3) + assert m.is_diagonal() + assert m.is_symmetric() + + m = Matrix(3, 3, [1, 0, 0, 0, 2, 0, 0, 0, 3]) + assert m == diag(1, 2, 3) + + m = Matrix(2, 3, zeros(2, 3)) + assert not m.is_symmetric() + assert m.is_diagonal() + + m = Matrix(((5, 0), (0, 6), (0, 0))) + assert m.is_diagonal() + + m = Matrix(((5, 0, 0), (0, 6, 0))) + assert m.is_diagonal() + + m = Matrix(3, 3, [1, x**2 + 2*x + 1, y, (x + 1)**2, 2, 0, y, 0, 3]) + assert m.is_symmetric() + assert not m.is_symmetric(simplify=False) + assert m.expand().is_symmetric(simplify=False) + + +def test_diagonalization(): + m = Matrix([[1, 2+I], [2-I, 3]]) + assert m.is_diagonalizable() + + m = Matrix(3, 2, [-3, 1, -3, 20, 3, 10]) + assert not m.is_diagonalizable() + assert not m.is_symmetric() + raises(NonSquareMatrixError, lambda: m.diagonalize()) + + # diagonalizable + m = diag(1, 2, 3) + (P, D) = m.diagonalize() + assert P == eye(3) + assert D == m + + m = Matrix(2, 2, [0, 1, 1, 0]) + assert m.is_symmetric() + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + + m = Matrix(2, 2, [1, 0, 0, 3]) + assert m.is_symmetric() + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + assert P == eye(2) + assert D == m + + m = Matrix(2, 2, [1, 1, 0, 0]) + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + + m = Matrix(3, 3, [1, 2, 0, 0, 3, 0, 2, -4, 2]) + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + for i in P: + assert i.as_numer_denom()[1] == 1 + + m = Matrix(2, 2, [1, 0, 0, 0]) + assert m.is_diagonal() + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + assert P == Matrix([[0, 1], [1, 0]]) + + # diagonalizable, complex only + m = Matrix(2, 2, [0, 1, -1, 0]) + assert not m.is_diagonalizable(True) + raises(MatrixError, lambda: m.diagonalize(True)) + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + + # not diagonalizable + m = Matrix(2, 2, [0, 1, 0, 0]) + assert not m.is_diagonalizable() + raises(MatrixError, lambda: m.diagonalize()) + + m = Matrix(3, 3, [-3, 1, -3, 20, 3, 10, 2, -2, 4]) + assert not m.is_diagonalizable() + raises(MatrixError, lambda: m.diagonalize()) + + # symbolic + a, b, c, d = symbols('a b c d') + m = Matrix(2, 2, [a, c, c, b]) + assert m.is_symmetric() + assert m.is_diagonalizable() + + +def test_issue_15887(): + # Mutable matrix should not use cache + a = MutableDenseMatrix([[0, 1], [1, 0]]) + assert a.is_diagonalizable() is True + a[1, 0] = 0 + assert a.is_diagonalizable() is False + + a = MutableDenseMatrix([[0, 1], [1, 0]]) + a.diagonalize() + a[1, 0] = 0 + raises(MatrixError, lambda: a.diagonalize()) + + +def test_jordan_form(): + + m = Matrix(3, 2, [-3, 1, -3, 20, 3, 10]) + raises(NonSquareMatrixError, lambda: m.jordan_form()) + + # diagonalizable + m = Matrix(3, 3, [7, -12, 6, 10, -19, 10, 12, -24, 13]) + Jmust = Matrix(3, 3, [-1, 0, 0, 0, 1, 0, 0, 0, 1]) + P, J = m.jordan_form() + assert Jmust == J + assert Jmust == m.diagonalize()[1] + + # m = Matrix(3, 3, [0, 6, 3, 1, 3, 1, -2, 2, 1]) + # m.jordan_form() # very long + # m.jordan_form() # + + # diagonalizable, complex only + + # Jordan cells + # complexity: one of eigenvalues is zero + m = Matrix(3, 3, [0, 1, 0, -4, 4, 0, -2, 1, 2]) + # The blocks are ordered according to the value of their eigenvalues, + # in order to make the matrix compatible with .diagonalize() + Jmust = Matrix(3, 3, [2, 1, 0, 0, 2, 0, 0, 0, 2]) + P, J = m.jordan_form() + assert Jmust == J + + # complexity: all of eigenvalues are equal + m = Matrix(3, 3, [2, 6, -15, 1, 1, -5, 1, 2, -6]) + # Jmust = Matrix(3, 3, [-1, 0, 0, 0, -1, 1, 0, 0, -1]) + # same here see 1456ff + Jmust = Matrix(3, 3, [-1, 1, 0, 0, -1, 0, 0, 0, -1]) + P, J = m.jordan_form() + assert Jmust == J + + # complexity: two of eigenvalues are zero + m = Matrix(3, 3, [4, -5, 2, 5, -7, 3, 6, -9, 4]) + Jmust = Matrix(3, 3, [0, 1, 0, 0, 0, 0, 0, 0, 1]) + P, J = m.jordan_form() + assert Jmust == J + + m = Matrix(4, 4, [6, 5, -2, -3, -3, -1, 3, 3, 2, 1, -2, -3, -1, 1, 5, 5]) + Jmust = Matrix(4, 4, [2, 1, 0, 0, + 0, 2, 0, 0, + 0, 0, 2, 1, + 0, 0, 0, 2] + ) + P, J = m.jordan_form() + assert Jmust == J + + m = Matrix(4, 4, [6, 2, -8, -6, -3, 2, 9, 6, 2, -2, -8, -6, -1, 0, 3, 4]) + # Jmust = Matrix(4, 4, [2, 0, 0, 0, 0, 2, 1, 0, 0, 0, 2, 0, 0, 0, 0, -2]) + # same here see 1456ff + Jmust = Matrix(4, 4, [-2, 0, 0, 0, + 0, 2, 1, 0, + 0, 0, 2, 0, + 0, 0, 0, 2]) + P, J = m.jordan_form() + assert Jmust == J + + m = Matrix(4, 4, [5, 4, 2, 1, 0, 1, -1, -1, -1, -1, 3, 0, 1, 1, -1, 2]) + assert not m.is_diagonalizable() + Jmust = Matrix(4, 4, [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 4, 1, 0, 0, 0, 4]) + P, J = m.jordan_form() + assert Jmust == J + + # checking for maximum precision to remain unchanged + m = Matrix([[Float('1.0', precision=110), Float('2.0', precision=110)], + [Float('3.14159265358979323846264338327', precision=110), Float('4.0', precision=110)]]) + P, J = m.jordan_form() + for term in J.values(): + if isinstance(term, Float): + assert term._prec == 110 + + +def test_jordan_form_complex_issue_9274(): + A = Matrix([[ 2, 4, 1, 0], + [-4, 2, 0, 1], + [ 0, 0, 2, 4], + [ 0, 0, -4, 2]]) + p = 2 - 4*I + q = 2 + 4*I + Jmust1 = Matrix([[p, 1, 0, 0], + [0, p, 0, 0], + [0, 0, q, 1], + [0, 0, 0, q]]) + Jmust2 = Matrix([[q, 1, 0, 0], + [0, q, 0, 0], + [0, 0, p, 1], + [0, 0, 0, p]]) + P, J = A.jordan_form() + assert J == Jmust1 or J == Jmust2 + assert simplify(P*J*P.inv()) == A + +def test_issue_10220(): + # two non-orthogonal Jordan blocks with eigenvalue 1 + M = Matrix([[1, 0, 0, 1], + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 1]]) + P, J = M.jordan_form() + assert P == Matrix([[0, 1, 0, 1], + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0]]) + assert J == Matrix([ + [1, 1, 0, 0], + [0, 1, 1, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + +def test_jordan_form_issue_15858(): + A = Matrix([ + [1, 1, 1, 0], + [-2, -1, 0, -1], + [0, 0, -1, -1], + [0, 0, 2, 1]]) + (P, J) = A.jordan_form() + assert P.expand() == Matrix([ + [ -I, -I/2, I, I/2], + [-1 + I, 0, -1 - I, 0], + [ 0, -S(1)/2 - I/2, 0, -S(1)/2 + I/2], + [ 0, 1, 0, 1]]) + assert J == Matrix([ + [-I, 1, 0, 0], + [0, -I, 0, 0], + [0, 0, I, 1], + [0, 0, 0, I]]) + +def test_Matrix_berkowitz_charpoly(): + UA, K_i, K_w = symbols('UA K_i K_w') + + A = Matrix([[-K_i - UA + K_i**2/(K_i + K_w), K_i*K_w/(K_i + K_w)], + [ K_i*K_w/(K_i + K_w), -K_w + K_w**2/(K_i + K_w)]]) + + charpoly = A.charpoly(x) + + assert charpoly == \ + Poly(x**2 + (K_i*UA + K_w*UA + 2*K_i*K_w)/(K_i + K_w)*x + + K_i*K_w*UA/(K_i + K_w), x, domain='ZZ(K_i,K_w,UA)') + + assert type(charpoly) is PurePoly + + A = Matrix([[1, 3], [2, 0]]) + assert A.charpoly() == A.charpoly(x) == PurePoly(x**2 - x - 6) + + A = Matrix([[1, 2], [x, 0]]) + p = A.charpoly(x) + assert p.gen != x + assert p.as_expr().subs(p.gen, x) == x**2 - 3*x + + +def test_exp_jordan_block(): + l = Symbol('lamda') + + m = Matrix.jordan_block(1, l) + assert m._eval_matrix_exp_jblock() == Matrix([[exp(l)]]) + + m = Matrix.jordan_block(3, l) + assert m._eval_matrix_exp_jblock() == \ + Matrix([ + [exp(l), exp(l), exp(l)/2], + [0, exp(l), exp(l)], + [0, 0, exp(l)]]) + + +def test_exp(): + m = Matrix([[3, 4], [0, -2]]) + m_exp = Matrix([[exp(3), -4*exp(-2)/5 + 4*exp(3)/5], [0, exp(-2)]]) + assert m.exp() == m_exp + assert exp(m) == m_exp + + m = Matrix([[1, 0], [0, 1]]) + assert m.exp() == Matrix([[E, 0], [0, E]]) + assert exp(m) == Matrix([[E, 0], [0, E]]) + + m = Matrix([[1, -1], [1, 1]]) + assert m.exp() == Matrix([[E*cos(1), -E*sin(1)], [E*sin(1), E*cos(1)]]) + + +def test_log(): + l = Symbol('lamda') + + m = Matrix.jordan_block(1, l) + assert m._eval_matrix_log_jblock() == Matrix([[log(l)]]) + + m = Matrix.jordan_block(4, l) + assert m._eval_matrix_log_jblock() == \ + Matrix( + [ + [log(l), 1/l, -1/(2*l**2), 1/(3*l**3)], + [0, log(l), 1/l, -1/(2*l**2)], + [0, 0, log(l), 1/l], + [0, 0, 0, log(l)] + ] + ) + + m = Matrix( + [[0, 0, 1], + [0, 0, 0], + [-1, 0, 0]] + ) + raises(MatrixError, lambda: m.log()) + + +def test_has(): + A = Matrix(((x, y), (2, 3))) + assert A.has(x) + assert not A.has(z) + assert A.has(Symbol) + + A = A.subs(x, 2) + assert not A.has(x) + + +def test_find_reasonable_pivot_naive_finds_guaranteed_nonzero1(): + # Test if matrices._find_reasonable_pivot_naive() + # finds a guaranteed non-zero pivot when the + # some of the candidate pivots are symbolic expressions. + # Keyword argument: simpfunc=None indicates that no simplifications + # should be performed during the search. + x = Symbol('x') + column = Matrix(3, 1, [x, cos(x)**2 + sin(x)**2, S.Half]) + pivot_offset, pivot_val, pivot_assumed_nonzero, simplified =\ + _find_reasonable_pivot_naive(column) + assert pivot_val == S.Half + +def test_find_reasonable_pivot_naive_finds_guaranteed_nonzero2(): + # Test if matrices._find_reasonable_pivot_naive() + # finds a guaranteed non-zero pivot when the + # some of the candidate pivots are symbolic expressions. + # Keyword argument: simpfunc=_simplify indicates that the search + # should attempt to simplify candidate pivots. + x = Symbol('x') + column = Matrix(3, 1, + [x, + cos(x)**2+sin(x)**2+x**2, + cos(x)**2+sin(x)**2]) + pivot_offset, pivot_val, pivot_assumed_nonzero, simplified =\ + _find_reasonable_pivot_naive(column, simpfunc=_simplify) + assert pivot_val == 1 + +def test_find_reasonable_pivot_naive_simplifies(): + # Test if matrices._find_reasonable_pivot_naive() + # simplifies candidate pivots, and reports + # their offsets correctly. + x = Symbol('x') + column = Matrix(3, 1, + [x, + cos(x)**2+sin(x)**2+x, + cos(x)**2+sin(x)**2]) + pivot_offset, pivot_val, pivot_assumed_nonzero, simplified =\ + _find_reasonable_pivot_naive(column, simpfunc=_simplify) + + assert len(simplified) == 2 + assert simplified[0][0] == 1 + assert simplified[0][1] == 1+x + assert simplified[1][0] == 2 + assert simplified[1][1] == 1 + +def test_errors(): + raises(ValueError, lambda: Matrix([[1, 2], [1]])) + raises(IndexError, lambda: Matrix([[1, 2]])[1.2, 5]) + raises(IndexError, lambda: Matrix([[1, 2]])[1, 5.2]) + raises(ValueError, lambda: randMatrix(3, c=4, symmetric=True)) + raises(ValueError, lambda: Matrix([1, 2]).reshape(4, 6)) + raises(ShapeError, + lambda: Matrix([[1, 2], [3, 4]]).copyin_matrix([1, 0], Matrix([1, 2]))) + raises(TypeError, lambda: Matrix([[1, 2], [3, 4]]).copyin_list([0, + 1], set())) + raises(NonSquareMatrixError, lambda: Matrix([[1, 2, 3], [2, 3, 0]]).inv()) + raises(ShapeError, + lambda: Matrix(1, 2, [1, 2]).row_join(Matrix([[1, 2], [3, 4]]))) + raises( + ShapeError, lambda: Matrix([1, 2]).col_join(Matrix([[1, 2], [3, 4]]))) + raises(ShapeError, lambda: Matrix([1]).row_insert(1, Matrix([[1, + 2], [3, 4]]))) + raises(ShapeError, lambda: Matrix([1]).col_insert(1, Matrix([[1, + 2], [3, 4]]))) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).trace()) + raises(TypeError, lambda: Matrix([1]).applyfunc(1)) + raises(ValueError, lambda: Matrix([[1, 2], [3, 4]]).minor(4, 5)) + raises(ValueError, lambda: Matrix([[1, 2], [3, 4]]).minor_submatrix(4, 5)) + raises(TypeError, lambda: Matrix([1, 2, 3]).cross(1)) + raises(TypeError, lambda: Matrix([1, 2, 3]).dot(1)) + raises(ShapeError, lambda: Matrix([1, 2, 3]).dot(Matrix([1, 2]))) + raises(ShapeError, lambda: Matrix([1, 2]).dot([])) + raises(TypeError, lambda: Matrix([1, 2]).dot('a')) + raises(ShapeError, lambda: Matrix([1, 2]).dot([1, 2, 3])) + raises(NonSquareMatrixError, lambda: Matrix([1, 2, 3]).exp()) + raises(ShapeError, lambda: Matrix([[1, 2], [3, 4]]).normalized()) + raises(ValueError, lambda: Matrix([1, 2]).inv(method='not a method')) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).inverse_GE()) + raises(ValueError, lambda: Matrix([[1, 2], [1, 2]]).inverse_GE()) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).inverse_ADJ()) + raises(ValueError, lambda: Matrix([[1, 2], [1, 2]]).inverse_ADJ()) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).inverse_LU()) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).is_nilpotent()) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).det()) + raises(ValueError, + lambda: Matrix([[1, 2], [3, 4]]).det(method='Not a real method')) + raises(ValueError, + lambda: Matrix([[1, 2, 3, 4], [5, 6, 7, 8], + [9, 10, 11, 12], [13, 14, 15, 16]]).det(iszerofunc="Not function")) + raises(ValueError, + lambda: Matrix([[1, 2, 3, 4], [5, 6, 7, 8], + [9, 10, 11, 12], [13, 14, 15, 16]]).det(iszerofunc=False)) + raises(ValueError, + lambda: hessian(Matrix([[1, 2], [3, 4]]), Matrix([[1, 2], [2, 1]]))) + raises(ValueError, lambda: hessian(Matrix([[1, 2], [3, 4]]), [])) + raises(ValueError, lambda: hessian(Symbol('x')**2, 'a')) + raises(IndexError, lambda: eye(3)[5, 2]) + raises(IndexError, lambda: eye(3)[2, 5]) + M = Matrix(((1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12), (13, 14, 15, 16))) + raises(ValueError, lambda: M.det('method=LU_decomposition()')) + V = Matrix([[10, 10, 10]]) + M = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(ValueError, lambda: M.row_insert(4.7, V)) + M = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(ValueError, lambda: M.col_insert(-4.2, V)) + +def test_len(): + assert len(Matrix()) == 0 + assert len(Matrix([[1, 2]])) == len(Matrix([[1], [2]])) == 2 + assert len(Matrix(0, 2, lambda i, j: 0)) == \ + len(Matrix(2, 0, lambda i, j: 0)) == 0 + assert len(Matrix([[0, 1, 2], [3, 4, 5]])) == 6 + assert Matrix([1]) == Matrix([[1]]) + assert not Matrix() + assert Matrix() == Matrix([]) + + +def test_integrate(): + A = Matrix(((1, 4, x), (y, 2, 4), (10, 5, x**2))) + assert A.integrate(x) == \ + Matrix(((x, 4*x, x**2/2), (x*y, 2*x, 4*x), (10*x, 5*x, x**3/3))) + assert A.integrate(y) == \ + Matrix(((y, 4*y, x*y), (y**2/2, 2*y, 4*y), (10*y, 5*y, y*x**2))) + + +def test_limit(): + A = Matrix(((1, 4, sin(x)/x), (y, 2, 4), (10, 5, x**2 + 1))) + assert A.limit(x, 0) == Matrix(((1, 4, 1), (y, 2, 4), (10, 5, 1))) + + +def test_diff(): + A = MutableDenseMatrix(((1, 4, x), (y, 2, 4), (10, 5, x**2 + 1))) + assert isinstance(A.diff(x), type(A)) + assert A.diff(x) == MutableDenseMatrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + assert A.diff(y) == MutableDenseMatrix(((0, 0, 0), (1, 0, 0), (0, 0, 0))) + + assert diff(A, x) == MutableDenseMatrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + assert diff(A, y) == MutableDenseMatrix(((0, 0, 0), (1, 0, 0), (0, 0, 0))) + + A_imm = A.as_immutable() + assert isinstance(A_imm.diff(x), type(A_imm)) + assert A_imm.diff(x) == ImmutableDenseMatrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + assert A_imm.diff(y) == ImmutableDenseMatrix(((0, 0, 0), (1, 0, 0), (0, 0, 0))) + + assert diff(A_imm, x) == ImmutableDenseMatrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + assert diff(A_imm, y) == ImmutableDenseMatrix(((0, 0, 0), (1, 0, 0), (0, 0, 0))) + + assert A.diff(x, evaluate=False) == ArrayDerivative(A, x, evaluate=False) + assert diff(A, x, evaluate=False) == ArrayDerivative(A, x, evaluate=False) + + +def test_diff_by_matrix(): + + # Derive matrix by matrix: + + A = MutableDenseMatrix([[x, y], [z, t]]) + assert A.diff(A) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + assert diff(A, A) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + + A_imm = A.as_immutable() + assert A_imm.diff(A_imm) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + assert diff(A_imm, A_imm) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + + # Derive a constant matrix: + assert A.diff(a) == MutableDenseMatrix([[0, 0], [0, 0]]) + + B = ImmutableDenseMatrix([a, b]) + assert A.diff(B) == Array.zeros(2, 1, 2, 2) + assert A.diff(A) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + + # Test diff with tuples: + + dB = B.diff([[a, b]]) + assert dB.shape == (2, 2, 1) + assert dB == Array([[[1], [0]], [[0], [1]]]) + + f = Function("f") + fxyz = f(x, y, z) + assert fxyz.diff([[x, y, z]]) == Array([fxyz.diff(x), fxyz.diff(y), fxyz.diff(z)]) + assert fxyz.diff(([x, y, z], 2)) == Array([ + [fxyz.diff(x, 2), fxyz.diff(x, y), fxyz.diff(x, z)], + [fxyz.diff(x, y), fxyz.diff(y, 2), fxyz.diff(y, z)], + [fxyz.diff(x, z), fxyz.diff(z, y), fxyz.diff(z, 2)], + ]) + + expr = sin(x)*exp(y) + assert expr.diff([[x, y]]) == Array([cos(x)*exp(y), sin(x)*exp(y)]) + assert expr.diff(y, ((x, y),)) == Array([cos(x)*exp(y), sin(x)*exp(y)]) + assert expr.diff(x, ((x, y),)) == Array([-sin(x)*exp(y), cos(x)*exp(y)]) + assert expr.diff(((y, x),), [[x, y]]) == Array([[cos(x)*exp(y), -sin(x)*exp(y)], [sin(x)*exp(y), cos(x)*exp(y)]]) + + # Test different notations: + + assert fxyz.diff(x).diff(y).diff(x) == fxyz.diff(((x, y, z),), 3)[0, 1, 0] + assert fxyz.diff(z).diff(y).diff(x) == fxyz.diff(((x, y, z),), 3)[2, 1, 0] + assert fxyz.diff([[x, y, z]], ((z, y, x),)) == Array([[fxyz.diff(i).diff(j) for i in (x, y, z)] for j in (z, y, x)]) + + # Test scalar derived by matrix remains matrix: + res = x.diff(Matrix([[x, y]])) + assert isinstance(res, ImmutableDenseMatrix) + assert res == Matrix([[1, 0]]) + res = (x**3).diff(Matrix([[x, y]])) + assert isinstance(res, ImmutableDenseMatrix) + assert res == Matrix([[3*x**2, 0]]) + + +def test_getattr(): + A = Matrix(((1, 4, x), (y, 2, 4), (10, 5, x**2 + 1))) + raises(AttributeError, lambda: A.nonexistantattribute) + assert getattr(A, 'diff')(x) == Matrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + + +def test_hessenberg(): + A = Matrix([[3, 4, 1], [2, 4, 5], [0, 1, 2]]) + assert A.is_upper_hessenberg + A = A.T + assert A.is_lower_hessenberg + A[0, -1] = 1 + assert A.is_lower_hessenberg is False + + A = Matrix([[3, 4, 1], [2, 4, 5], [3, 1, 2]]) + assert not A.is_upper_hessenberg + + A = zeros(5, 2) + assert A.is_upper_hessenberg + + +def test_cholesky(): + raises(NonSquareMatrixError, lambda: Matrix((1, 2)).cholesky()) + raises(ValueError, lambda: Matrix(((1, 2), (3, 4))).cholesky()) + raises(ValueError, lambda: Matrix(((5 + I, 0), (0, 1))).cholesky()) + raises(ValueError, lambda: Matrix(((1, 5), (5, 1))).cholesky()) + raises(ValueError, lambda: Matrix(((1, 2), (3, 4))).cholesky(hermitian=False)) + assert Matrix(((5 + I, 0), (0, 1))).cholesky(hermitian=False) == Matrix([ + [sqrt(5 + I), 0], [0, 1]]) + A = Matrix(((1, 5), (5, 1))) + L = A.cholesky(hermitian=False) + assert L == Matrix([[1, 0], [5, 2*sqrt(6)*I]]) + assert L*L.T == A + A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + L = A.cholesky() + assert L * L.T == A + assert L.is_lower + assert L == Matrix([[5, 0, 0], [3, 3, 0], [-1, 1, 3]]) + A = Matrix(((4, -2*I, 2 + 2*I), (2*I, 2, -1 + I), (2 - 2*I, -1 - I, 11))) + assert A.cholesky().expand() == Matrix(((2, 0, 0), (I, 1, 0), (1 - I, 0, 3))) + + raises(NonSquareMatrixError, lambda: SparseMatrix((1, 2)).cholesky()) + raises(ValueError, lambda: SparseMatrix(((1, 2), (3, 4))).cholesky()) + raises(ValueError, lambda: SparseMatrix(((5 + I, 0), (0, 1))).cholesky()) + raises(ValueError, lambda: SparseMatrix(((1, 5), (5, 1))).cholesky()) + raises(ValueError, lambda: SparseMatrix(((1, 2), (3, 4))).cholesky(hermitian=False)) + assert SparseMatrix(((5 + I, 0), (0, 1))).cholesky(hermitian=False) == Matrix([ + [sqrt(5 + I), 0], [0, 1]]) + A = SparseMatrix(((1, 5), (5, 1))) + L = A.cholesky(hermitian=False) + assert L == Matrix([[1, 0], [5, 2*sqrt(6)*I]]) + assert L*L.T == A + A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + L = A.cholesky() + assert L * L.T == A + assert L.is_lower + assert L == Matrix([[5, 0, 0], [3, 3, 0], [-1, 1, 3]]) + A = SparseMatrix(((4, -2*I, 2 + 2*I), (2*I, 2, -1 + I), (2 - 2*I, -1 - I, 11))) + assert A.cholesky() == Matrix(((2, 0, 0), (I, 1, 0), (1 - I, 0, 3))) + + +def test_matrix_norm(): + # Vector Tests + # Test columns and symbols + x = Symbol('x', real=True) + v = Matrix([cos(x), sin(x)]) + assert trigsimp(v.norm(2)) == 1 + assert v.norm(10) == Pow(cos(x)**10 + sin(x)**10, Rational(1, 10)) + + # Test Rows + A = Matrix([[5, Rational(3, 2)]]) + assert A.norm() == Pow(25 + Rational(9, 4), S.Half) + assert A.norm(oo) == max(A) + assert A.norm(-oo) == min(A) + + # Matrix Tests + # Intuitive test + A = Matrix([[1, 1], [1, 1]]) + assert A.norm(2) == 2 + assert A.norm(-2) == 0 + assert A.norm('frobenius') == 2 + assert eye(10).norm(2) == eye(10).norm(-2) == 1 + assert A.norm(oo) == 2 + + # Test with Symbols and more complex entries + A = Matrix([[3, y, y], [x, S.Half, -pi]]) + assert (A.norm('fro') + == sqrt(Rational(37, 4) + 2*abs(y)**2 + pi**2 + x**2)) + + # Check non-square + A = Matrix([[1, 2, -3], [4, 5, Rational(13, 2)]]) + assert A.norm(2) == sqrt(Rational(389, 8) + sqrt(78665)/8) + assert A.norm(-2) is S.Zero + assert A.norm('frobenius') == sqrt(389)/2 + + # Test properties of matrix norms + # https://en.wikipedia.org/wiki/Matrix_norm#Definition + # Two matrices + A = Matrix([[1, 2], [3, 4]]) + B = Matrix([[5, 5], [-2, 2]]) + C = Matrix([[0, -I], [I, 0]]) + D = Matrix([[1, 0], [0, -1]]) + L = [A, B, C, D] + alpha = Symbol('alpha', real=True) + + for order in ['fro', 2, -2]: + # Zero Check + assert zeros(3).norm(order) is S.Zero + # Check Triangle Inequality for all Pairs of Matrices + for X in L: + for Y in L: + dif = (X.norm(order) + Y.norm(order) - + (X + Y).norm(order)) + assert (dif >= 0) + # Scalar multiplication linearity + for M in [A, B, C, D]: + dif = simplify((alpha*M).norm(order) - + abs(alpha) * M.norm(order)) + assert dif == 0 + + # Test Properties of Vector Norms + # https://en.wikipedia.org/wiki/Vector_norm + # Two column vectors + a = Matrix([1, 1 - 1*I, -3]) + b = Matrix([S.Half, 1*I, 1]) + c = Matrix([-1, -1, -1]) + d = Matrix([3, 2, I]) + e = Matrix([Integer(1e2), Rational(1, 1e2), 1]) + L = [a, b, c, d, e] + alpha = Symbol('alpha', real=True) + + for order in [1, 2, -1, -2, S.Infinity, S.NegativeInfinity, pi]: + # Zero Check + if order > 0: + assert Matrix([0, 0, 0]).norm(order) is S.Zero + # Triangle inequality on all pairs + if order >= 1: # Triangle InEq holds only for these norms + for X in L: + for Y in L: + dif = (X.norm(order) + Y.norm(order) - + (X + Y).norm(order)) + assert simplify(dif >= 0) is S.true + # Linear to scalar multiplication + if order in [1, 2, -1, -2, S.Infinity, S.NegativeInfinity]: + for X in L: + dif = simplify((alpha*X).norm(order) - + (abs(alpha) * X.norm(order))) + assert dif == 0 + + # ord=1 + M = Matrix(3, 3, [1, 3, 0, -2, -1, 0, 3, 9, 6]) + assert M.norm(1) == 13 + + +def test_condition_number(): + x = Symbol('x', real=True) + A = eye(3) + A[0, 0] = 10 + A[2, 2] = Rational(1, 10) + assert A.condition_number() == 100 + + A[1, 1] = x + assert A.condition_number() == Max(10, Abs(x)) / Min(Rational(1, 10), Abs(x)) + + M = Matrix([[cos(x), sin(x)], [-sin(x), cos(x)]]) + Mc = M.condition_number() + assert all(Float(1.).epsilon_eq(Mc.subs(x, val).evalf()) for val in + [Rational(1, 5), S.Half, Rational(1, 10), pi/2, pi, pi*Rational(7, 4) ]) + + #issue 10782 + assert Matrix([]).condition_number() == 0 + + +def test_equality(): + A = Matrix(((1, 2, 3), (4, 5, 6), (7, 8, 9))) + B = Matrix(((9, 8, 7), (6, 5, 4), (3, 2, 1))) + assert A == A[:, :] + assert not A != A[:, :] + assert not A == B + assert A != B + assert A != 10 + assert not A == 10 + + # A SparseMatrix can be equal to a Matrix + C = SparseMatrix(((1, 0, 0), (0, 1, 0), (0, 0, 1))) + D = Matrix(((1, 0, 0), (0, 1, 0), (0, 0, 1))) + assert C == D + assert not C != D + + +def test_col_join(): + assert eye(3).col_join(Matrix([[7, 7, 7]])) == \ + Matrix([[1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [7, 7, 7]]) + + +def test_row_insert(): + r4 = Matrix([[4, 4, 4]]) + for i in range(-4, 5): + l = [1, 0, 0] + l.insert(i, 4) + assert flatten(eye(3).row_insert(i, r4).col(0).tolist()) == l + + +def test_col_insert(): + c4 = Matrix([4, 4, 4]) + for i in range(-4, 5): + l = [0, 0, 0] + l.insert(i, 4) + assert flatten(zeros(3).col_insert(i, c4).row(0).tolist()) == l + + +def test_normalized(): + assert Matrix([3, 4]).normalized() == \ + Matrix([Rational(3, 5), Rational(4, 5)]) + + # Zero vector trivial cases + assert Matrix([0, 0, 0]).normalized() == Matrix([0, 0, 0]) + + # Machine precision error truncation trivial cases + m = Matrix([0,0,1.e-100]) + assert m.normalized( + iszerofunc=lambda x: x.evalf(n=10, chop=True).is_zero + ) == Matrix([0, 0, 0]) + + +def test_print_nonzero(): + assert capture(lambda: eye(3).print_nonzero()) == \ + '[X ]\n[ X ]\n[ X]\n' + assert capture(lambda: eye(3).print_nonzero('.')) == \ + '[. ]\n[ . ]\n[ .]\n' + + +def test_zeros_eye(): + assert Matrix.eye(3) == eye(3) + assert Matrix.zeros(3) == zeros(3) + assert ones(3, 4) == Matrix(3, 4, [1]*12) + + i = Matrix([[1, 0], [0, 1]]) + z = Matrix([[0, 0], [0, 0]]) + for cls in classes: + m = cls.eye(2) + assert i == m # but m == i will fail if m is immutable + assert i == eye(2, cls=cls) + assert type(m) == cls + m = cls.zeros(2) + assert z == m + assert z == zeros(2, cls=cls) + assert type(m) == cls + + +def test_is_zero(): + assert Matrix().is_zero_matrix + assert Matrix([[0, 0], [0, 0]]).is_zero_matrix + assert zeros(3, 4).is_zero_matrix + assert not eye(3).is_zero_matrix + assert Matrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert SparseMatrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert ImmutableMatrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert ImmutableSparseMatrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert Matrix([[x, 1], [0, 0]]).is_zero_matrix == False + a = Symbol('a', nonzero=True) + assert Matrix([[a, 0], [0, 0]]).is_zero_matrix == False + + +def test_rotation_matrices(): + # This tests the rotation matrices by rotating about an axis and back. + theta = pi/3 + r3_plus = rot_axis3(theta) + r3_minus = rot_axis3(-theta) + r2_plus = rot_axis2(theta) + r2_minus = rot_axis2(-theta) + r1_plus = rot_axis1(theta) + r1_minus = rot_axis1(-theta) + assert r3_minus*r3_plus*eye(3) == eye(3) + assert r2_minus*r2_plus*eye(3) == eye(3) + assert r1_minus*r1_plus*eye(3) == eye(3) + + # Check the correctness of the trace of the rotation matrix + assert r1_plus.trace() == 1 + 2*cos(theta) + assert r2_plus.trace() == 1 + 2*cos(theta) + assert r3_plus.trace() == 1 + 2*cos(theta) + + # Check that a rotation with zero angle doesn't change anything. + assert rot_axis1(0) == eye(3) + assert rot_axis2(0) == eye(3) + assert rot_axis3(0) == eye(3) + + # Check left-hand convention + # see Issue #24529 + q1 = Quaternion.from_axis_angle([1, 0, 0], pi / 2) + q2 = Quaternion.from_axis_angle([0, 1, 0], pi / 2) + q3 = Quaternion.from_axis_angle([0, 0, 1], pi / 2) + assert rot_axis1(- pi / 2) == q1.to_rotation_matrix() + assert rot_axis2(- pi / 2) == q2.to_rotation_matrix() + assert rot_axis3(- pi / 2) == q3.to_rotation_matrix() + # Check right-hand convention + assert rot_ccw_axis1(+ pi / 2) == q1.to_rotation_matrix() + assert rot_ccw_axis2(+ pi / 2) == q2.to_rotation_matrix() + assert rot_ccw_axis3(+ pi / 2) == q3.to_rotation_matrix() + + +def test_DeferredVector(): + assert str(DeferredVector("vector")[4]) == "vector[4]" + assert sympify(DeferredVector("d")) == DeferredVector("d") + raises(IndexError, lambda: DeferredVector("d")[-1]) + assert str(DeferredVector("d")) == "d" + assert repr(DeferredVector("test")) == "DeferredVector('test')" + +def test_DeferredVector_not_iterable(): + assert not iterable(DeferredVector('X')) + +def test_DeferredVector_Matrix(): + raises(TypeError, lambda: Matrix(DeferredVector("V"))) + +def test_GramSchmidt(): + R = Rational + m1 = Matrix(1, 2, [1, 2]) + m2 = Matrix(1, 2, [2, 3]) + assert GramSchmidt([m1, m2]) == \ + [Matrix(1, 2, [1, 2]), Matrix(1, 2, [R(2)/5, R(-1)/5])] + assert GramSchmidt([m1.T, m2.T]) == \ + [Matrix(2, 1, [1, 2]), Matrix(2, 1, [R(2)/5, R(-1)/5])] + # from wikipedia + assert GramSchmidt([Matrix([3, 1]), Matrix([2, 2])], True) == [ + Matrix([3*sqrt(10)/10, sqrt(10)/10]), + Matrix([-sqrt(10)/10, 3*sqrt(10)/10])] + # https://github.com/sympy/sympy/issues/9488 + L = FiniteSet(Matrix([1])) + assert GramSchmidt(L) == [Matrix([[1]])] + + +def test_casoratian(): + assert casoratian([1, 2, 3, 4], 1) == 0 + assert casoratian([1, 2, 3, 4], 1, zero=False) == 0 + + +def test_zero_dimension_multiply(): + assert (Matrix()*zeros(0, 3)).shape == (0, 3) + assert zeros(3, 0)*zeros(0, 3) == zeros(3, 3) + assert zeros(0, 3)*zeros(3, 0) == Matrix() + + +def test_slice_issue_2884(): + m = Matrix(2, 2, range(4)) + assert m[1, :] == Matrix([[2, 3]]) + assert m[-1, :] == Matrix([[2, 3]]) + assert m[:, 1] == Matrix([[1, 3]]).T + assert m[:, -1] == Matrix([[1, 3]]).T + raises(IndexError, lambda: m[2, :]) + raises(IndexError, lambda: m[2, 2]) + + +def test_slice_issue_3401(): + assert zeros(0, 3)[:, -1].shape == (0, 1) + assert zeros(3, 0)[0, :] == Matrix(1, 0, []) + + +def test_copyin(): + s = zeros(3, 3) + s[3] = 1 + assert s[:, 0] == Matrix([0, 1, 0]) + assert s[3] == 1 + assert s[3: 4] == [1] + s[1, 1] = 42 + assert s[1, 1] == 42 + assert s[1, 1:] == Matrix([[42, 0]]) + s[1, 1:] = Matrix([[5, 6]]) + assert s[1, :] == Matrix([[1, 5, 6]]) + s[1, 1:] = [[42, 43]] + assert s[1, :] == Matrix([[1, 42, 43]]) + s[0, 0] = 17 + assert s[:, :1] == Matrix([17, 1, 0]) + s[0, 0] = [1, 1, 1] + assert s[:, 0] == Matrix([1, 1, 1]) + s[0, 0] = Matrix([1, 1, 1]) + assert s[:, 0] == Matrix([1, 1, 1]) + s[0, 0] = SparseMatrix([1, 1, 1]) + assert s[:, 0] == Matrix([1, 1, 1]) + + +def test_invertible_check(): + # sometimes a singular matrix will have a pivot vector shorter than + # the number of rows in a matrix... + assert Matrix([[1, 2], [1, 2]]).rref() == (Matrix([[1, 2], [0, 0]]), (0,)) + raises(ValueError, lambda: Matrix([[1, 2], [1, 2]]).inv()) + m = Matrix([ + [-1, -1, 0], + [ x, 1, 1], + [ 1, x, -1], + ]) + assert len(m.rref()[1]) != m.rows + # in addition, unless simplify=True in the call to rref, the identity + # matrix will be returned even though m is not invertible + assert m.rref()[0] != eye(3) + assert m.rref(simplify=signsimp)[0] != eye(3) + raises(ValueError, lambda: m.inv(method="ADJ")) + raises(ValueError, lambda: m.inv(method="GE")) + raises(ValueError, lambda: m.inv(method="LU")) + + +def test_issue_3959(): + x, y = symbols('x, y') + e = x*y + assert e.subs(x, Matrix([3, 5, 3])) == Matrix([3, 5, 3])*y + + +def test_issue_5964(): + assert str(Matrix([[1, 2], [3, 4]])) == 'Matrix([[1, 2], [3, 4]])' + + +def test_issue_7604(): + x, y = symbols("x y") + assert sstr(Matrix([[x, 2*y], [y**2, x + 3]])) == \ + 'Matrix([\n[ x, 2*y],\n[y**2, x + 3]])' + + +def test_is_Identity(): + assert eye(3).is_Identity + assert eye(3).as_immutable().is_Identity + assert not zeros(3).is_Identity + assert not ones(3).is_Identity + # issue 6242 + assert not Matrix([[1, 0, 0]]).is_Identity + # issue 8854 + assert SparseMatrix(3,3, {(0,0):1, (1,1):1, (2,2):1}).is_Identity + assert not SparseMatrix(2,3, range(6)).is_Identity + assert not SparseMatrix(3,3, {(0,0):1, (1,1):1}).is_Identity + assert not SparseMatrix(3,3, {(0,0):1, (1,1):1, (2,2):1, (0,1):2, (0,2):3}).is_Identity + + +def test_dot(): + assert ones(1, 3).dot(ones(3, 1)) == 3 + assert ones(1, 3).dot([1, 1, 1]) == 3 + assert Matrix([1, 2, 3]).dot(Matrix([1, 2, 3])) == 14 + assert Matrix([1, 2, 3*I]).dot(Matrix([I, 2, 3*I])) == -5 + I + assert Matrix([1, 2, 3*I]).dot(Matrix([I, 2, 3*I]), hermitian=False) == -5 + I + assert Matrix([1, 2, 3*I]).dot(Matrix([I, 2, 3*I]), hermitian=True) == 13 + I + assert Matrix([1, 2, 3*I]).dot(Matrix([I, 2, 3*I]), hermitian=True, conjugate_convention="physics") == 13 - I + assert Matrix([1, 2, 3*I]).dot(Matrix([4, 5*I, 6]), hermitian=True, conjugate_convention="right") == 4 + 8*I + assert Matrix([1, 2, 3*I]).dot(Matrix([4, 5*I, 6]), hermitian=True, conjugate_convention="left") == 4 - 8*I + assert Matrix([I, 2*I]).dot(Matrix([I, 2*I]), hermitian=False, conjugate_convention="left") == -5 + assert Matrix([I, 2*I]).dot(Matrix([I, 2*I]), conjugate_convention="left") == 5 + raises(ValueError, lambda: Matrix([1, 2]).dot(Matrix([3, 4]), hermitian=True, conjugate_convention="test")) + + +def test_dual(): + B_x, B_y, B_z, E_x, E_y, E_z = symbols( + 'B_x B_y B_z E_x E_y E_z', real=True) + F = Matrix(( + ( 0, E_x, E_y, E_z), + (-E_x, 0, B_z, -B_y), + (-E_y, -B_z, 0, B_x), + (-E_z, B_y, -B_x, 0) + )) + Fd = Matrix(( + ( 0, -B_x, -B_y, -B_z), + (B_x, 0, E_z, -E_y), + (B_y, -E_z, 0, E_x), + (B_z, E_y, -E_x, 0) + )) + assert F.dual().equals(Fd) + assert eye(3).dual().equals(zeros(3)) + assert F.dual().dual().equals(-F) + + +def test_anti_symmetric(): + assert Matrix([1, 2]).is_anti_symmetric() is False + m = Matrix(3, 3, [0, x**2 + 2*x + 1, y, -(x + 1)**2, 0, x*y, -y, -x*y, 0]) + assert m.is_anti_symmetric() is True + assert m.is_anti_symmetric(simplify=False) is None + assert m.is_anti_symmetric(simplify=lambda x: x) is None + + # tweak to fail + m[2, 1] = -m[2, 1] + assert m.is_anti_symmetric() is None + # untweak + m[2, 1] = -m[2, 1] + + m = m.expand() + assert m.is_anti_symmetric(simplify=False) is True + m[0, 0] = 1 + assert m.is_anti_symmetric() is False + + +def test_normalize_sort_diogonalization(): + A = Matrix(((1, 2), (2, 1))) + P, Q = A.diagonalize(normalize=True) + assert P*P.T == P.T*P == eye(P.cols) + P, Q = A.diagonalize(normalize=True, sort=True) + assert P*P.T == P.T*P == eye(P.cols) + assert P*Q*P.inv() == A + + +def test_issue_5321(): + raises(ValueError, lambda: Matrix([[1, 2, 3], Matrix(0, 1, [])])) + + +def test_issue_5320(): + assert Matrix.hstack(eye(2), 2*eye(2)) == Matrix([ + [1, 0, 2, 0], + [0, 1, 0, 2] + ]) + assert Matrix.vstack(eye(2), 2*eye(2)) == Matrix([ + [1, 0], + [0, 1], + [2, 0], + [0, 2] + ]) + cls = SparseMatrix + assert cls.hstack(cls(eye(2)), cls(2*eye(2))) == Matrix([ + [1, 0, 2, 0], + [0, 1, 0, 2] + ]) + +def test_issue_11944(): + A = Matrix([[1]]) + AIm = sympify(A) + assert Matrix.hstack(AIm, A) == Matrix([[1, 1]]) + assert Matrix.vstack(AIm, A) == Matrix([[1], [1]]) + +def test_cross(): + a = [1, 2, 3] + b = [3, 4, 5] + col = Matrix([-2, 4, -2]) + row = col.T + + def test(M, ans): + assert ans == M + assert type(M) == cls + for cls in classes: + A = cls(a) + B = cls(b) + test(A.cross(B), col) + test(A.cross(B.T), col) + test(A.T.cross(B.T), row) + test(A.T.cross(B), row) + raises(ShapeError, lambda: + Matrix(1, 2, [1, 1]).cross(Matrix(1, 2, [1, 1]))) + +def test_hat_vee(): + v1 = Matrix([x, y, z]) + v2 = Matrix([a, b, c]) + assert v1.hat() * v2 == v1.cross(v2) + assert v1.hat().is_anti_symmetric() + assert v1.hat().vee() == v1 + +def test_hash(): + for cls in classes[-2:]: + s = {cls.eye(1), cls.eye(1)} + assert len(s) == 1 and s.pop() == cls.eye(1) + # issue 3979 + for cls in classes[:2]: + assert not isinstance(cls.eye(1), Hashable) + + +@XFAIL +def test_issue_3979(): + # when this passes, delete this and change the [1:2] + # to [:2] in the test_hash above for issue 3979 + cls = classes[0] + raises(AttributeError, lambda: hash(cls.eye(1))) + + +def test_adjoint(): + dat = [[0, I], [1, 0]] + ans = Matrix([[0, 1], [-I, 0]]) + for cls in classes: + assert ans == cls(dat).adjoint() + + +def test_adjoint_with_operator(): + # Regression test for issue 25130: adjoint() should propagate to operators + import sympy.physics.quantum + a = sympy.physics.quantum.operator.Operator('a') + a_dag = sympy.physics.quantum.Dagger(a) + dat = [[0, I * a], [0, a_dag]] + ans = Matrix([[0, 0], [-I * a_dag, a]]) + for cls in classes: + assert ans == cls(dat).adjoint() + + +def test_simplify_immutable(): + assert simplify(ImmutableMatrix([[sin(x)**2 + cos(x)**2]])) == \ + ImmutableMatrix([[1]]) + +def test_replace(): + F, G = symbols('F, G', cls=Function) + K = Matrix(2, 2, lambda i, j: G(i+j)) + M = Matrix(2, 2, lambda i, j: F(i+j)) + N = M.replace(F, G) + assert N == K + + +def test_atoms(): + m = Matrix([[1, 2], [x, 1 - 1/x]]) + assert m.atoms() == {S.One,S(2),S.NegativeOne, x} + assert m.atoms(Symbol) == {x} + + +def test_pinv(): + # Pseudoinverse of an invertible matrix is the inverse. + A1 = Matrix([[a, b], [c, d]]) + assert simplify(A1.pinv(method="RD")) == simplify(A1.inv()) + + # Test the four properties of the pseudoinverse for various matrices. + As = [Matrix([[13, 104], [2212, 3], [-3, 5]]), + Matrix([[1, 7, 9], [11, 17, 19]]), + Matrix([a, b])] + + for A in As: + A_pinv = A.pinv(method="RD") + AAp = A * A_pinv + ApA = A_pinv * A + assert simplify(AAp * A) == A + assert simplify(ApA * A_pinv) == A_pinv + assert AAp.H == AAp + assert ApA.H == ApA + + # XXX Pinv with diagonalization makes expression too complicated. + for A in As: + A_pinv = simplify(A.pinv(method="ED")) + AAp = A * A_pinv + ApA = A_pinv * A + assert simplify(AAp * A) == A + assert simplify(ApA * A_pinv) == A_pinv + assert AAp.H == AAp + assert ApA.H == ApA + + # XXX Computing pinv using diagonalization makes an expression that + # is too complicated to simplify. + # A1 = Matrix([[a, b], [c, d]]) + # assert simplify(A1.pinv(method="ED")) == simplify(A1.inv()) + # so this is tested numerically at a fixed random point + + from sympy.core.numbers import comp + q = A1.pinv(method="ED") + w = A1.inv() + reps = {a: -73633, b: 11362, c: 55486, d: 62570} + assert all( + comp(i.n(), j.n()) + for i, j in zip(q.subs(reps), w.subs(reps)) + ) + + +@slow +def test_pinv_rank_deficient_when_diagonalization_fails(): + # Test the four properties of the pseudoinverse for matrices when + # diagonalization of A.H*A fails. + As = [ + Matrix([ + [61, 89, 55, 20, 71, 0], + [62, 96, 85, 85, 16, 0], + [69, 56, 17, 4, 54, 0], + [10, 54, 91, 41, 71, 0], + [ 7, 30, 10, 48, 90, 0], + [0, 0, 0, 0, 0, 0]]) + ] + for A in As: + A_pinv = A.pinv(method="ED") + AAp = A * A_pinv + ApA = A_pinv * A + assert AAp.H == AAp + + # Here ApA.H and ApA are equivalent expressions but they are very + # complicated expressions involving RootOfs. Using simplify would be + # too slow and so would evalf so we substitute approximate values for + # the RootOfs and then evalf which is less accurate but good enough to + # confirm that these two matrices are equivalent. + # + # assert ApA.H == ApA # <--- would fail (structural equality) + # assert simplify(ApA.H - ApA).is_zero_matrix # <--- too slow + # (ApA.H - ApA).evalf() # <--- too slow + + def allclose(M1, M2): + rootofs = M1.atoms(RootOf) + rootofs_approx = {r: r.evalf() for r in rootofs} + diff_approx = (M1 - M2).xreplace(rootofs_approx).evalf() + return all(abs(e) < 1e-10 for e in diff_approx) + + assert allclose(ApA.H, ApA) + + +def test_issue_7201(): + assert ones(0, 1) + ones(0, 1) == Matrix(0, 1, []) + assert ones(1, 0) + ones(1, 0) == Matrix(1, 0, []) + +def test_free_symbols(): + for M in ImmutableMatrix, ImmutableSparseMatrix, Matrix, SparseMatrix: + assert M([[x], [0]]).free_symbols == {x} + +def test_from_ndarray(): + """See issue 7465.""" + try: + from numpy import array + except ImportError: + skip('NumPy must be available to test creating matrices from ndarrays') + + assert Matrix(array([1, 2, 3])) == Matrix([1, 2, 3]) + assert Matrix(array([[1, 2, 3]])) == Matrix([[1, 2, 3]]) + assert Matrix(array([[1, 2, 3], [4, 5, 6]])) == \ + Matrix([[1, 2, 3], [4, 5, 6]]) + assert Matrix(array([x, y, z])) == Matrix([x, y, z]) + raises(NotImplementedError, + lambda: Matrix(array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]))) + assert Matrix([array([1, 2]), array([3, 4])]) == Matrix([[1, 2], [3, 4]]) + assert Matrix([array([1, 2]), [3, 4]]) == Matrix([[1, 2], [3, 4]]) + assert Matrix([array([]), array([])]) == Matrix(2, 0, []) != Matrix(0, 0, []) + +def test_17522_numpy(): + from sympy.matrices.common import _matrixify + try: + from numpy import array, matrix + except ImportError: + skip('NumPy must be available to test indexing matrixified NumPy ndarrays and matrices') + + m = _matrixify(array([[1, 2], [3, 4]])) + assert m[3] == 4 + assert list(m) == [1, 2, 3, 4] + + with ignore_warnings(PendingDeprecationWarning): + m = _matrixify(matrix([[1, 2], [3, 4]])) + assert m[3] == 4 + assert list(m) == [1, 2, 3, 4] + +def test_17522_mpmath(): + from sympy.matrices.common import _matrixify + try: + from mpmath import matrix + except ImportError: + skip('mpmath must be available to test indexing matrixified mpmath matrices') + + m = _matrixify(matrix([[1, 2], [3, 4]])) + assert m[3] == 4.0 + assert list(m) == [1.0, 2.0, 3.0, 4.0] + +def test_17522_scipy(): + from sympy.matrices.common import _matrixify + try: + from scipy.sparse import csr_matrix + except ImportError: + skip('SciPy must be available to test indexing matrixified SciPy sparse matrices') + + m = _matrixify(csr_matrix([[1, 2], [3, 4]])) + assert m[3] == 4 + assert list(m) == [1, 2, 3, 4] + +def test_hermitian(): + a = Matrix([[1, I], [-I, 1]]) + assert a.is_hermitian + a[0, 0] = 2*I + assert a.is_hermitian is False + a[0, 0] = x + assert a.is_hermitian is None + a[0, 1] = a[1, 0]*I + assert a.is_hermitian is False + b = HermitianOperator("b") + c = Operator("c") + assert Matrix([[b]]).is_hermitian is True + assert Matrix([[b, c], [Dagger(c), b]]).is_hermitian is True + assert Matrix([[b, c], [c, b]]).is_hermitian is False + assert Matrix([[b, c], [transpose(c), b]]).is_hermitian is False + +def test_doit(): + a = Matrix([[Add(x,x, evaluate=False)]]) + assert a[0] != 2*x + assert a.doit() == Matrix([[2*x]]) + +def test_issue_9457_9467_9876(): + # for row_del(index) + M = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + M.row_del(1) + assert M == Matrix([[1, 2, 3], [3, 4, 5]]) + N = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + N.row_del(-2) + assert N == Matrix([[1, 2, 3], [3, 4, 5]]) + O = Matrix([[1, 2, 3], [5, 6, 7], [9, 10, 11]]) + O.row_del(-1) + assert O == Matrix([[1, 2, 3], [5, 6, 7]]) + P = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(IndexError, lambda: P.row_del(10)) + Q = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(IndexError, lambda: Q.row_del(-10)) + + # for col_del(index) + M = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + M.col_del(1) + assert M == Matrix([[1, 3], [2, 4], [3, 5]]) + N = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + N.col_del(-2) + assert N == Matrix([[1, 3], [2, 4], [3, 5]]) + P = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(IndexError, lambda: P.col_del(10)) + Q = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(IndexError, lambda: Q.col_del(-10)) + +def test_issue_9422(): + x, y = symbols('x y', commutative=False) + a, b = symbols('a b') + M = eye(2) + M1 = Matrix(2, 2, [x, y, y, z]) + assert y*x*M != x*y*M + assert b*a*M == a*b*M + assert x*M1 != M1*x + assert a*M1 == M1*a + assert y*x*M == Matrix([[y*x, 0], [0, y*x]]) + + +def test_issue_10770(): + M = Matrix([]) + a = ['col_insert', 'row_join'], Matrix([9, 6, 3]) + b = ['row_insert', 'col_join'], a[1].T + c = ['row_insert', 'col_insert'], Matrix([[1, 2], [3, 4]]) + for ops, m in (a, b, c): + for op in ops: + f = getattr(M, op) + new = f(m) if 'join' in op else f(42, m) + assert new == m and id(new) != id(m) + + +def test_issue_10658(): + A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert A.extract([0, 1, 2], [True, True, False]) == \ + Matrix([[1, 2], [4, 5], [7, 8]]) + assert A.extract([0, 1, 2], [True, False, False]) == Matrix([[1], [4], [7]]) + assert A.extract([True, False, False], [0, 1, 2]) == Matrix([[1, 2, 3]]) + assert A.extract([True, False, True], [0, 1, 2]) == \ + Matrix([[1, 2, 3], [7, 8, 9]]) + assert A.extract([0, 1, 2], [False, False, False]) == Matrix(3, 0, []) + assert A.extract([False, False, False], [0, 1, 2]) == Matrix(0, 3, []) + assert A.extract([True, False, True], [False, True, False]) == \ + Matrix([[2], [8]]) + +def test_opportunistic_simplification(): + # this test relates to issue #10718, #9480, #11434 + + # issue #9480 + m = Matrix([[-5 + 5*sqrt(2), -5], [-5*sqrt(2)/2 + 5, -5*sqrt(2)/2]]) + assert m.rank() == 1 + + # issue #10781 + m = Matrix([[3+3*sqrt(3)*I, -9],[4,-3+3*sqrt(3)*I]]) + assert simplify(m.rref()[0] - Matrix([[1, -9/(3 + 3*sqrt(3)*I)], [0, 0]])) == zeros(2, 2) + + # issue #11434 + ax,ay,bx,by,cx,cy,dx,dy,ex,ey,t0,t1 = symbols('a_x a_y b_x b_y c_x c_y d_x d_y e_x e_y t_0 t_1') + m = Matrix([[ax,ay,ax*t0,ay*t0,0],[bx,by,bx*t0,by*t0,0],[cx,cy,cx*t0,cy*t0,1],[dx,dy,dx*t0,dy*t0,1],[ex,ey,2*ex*t1-ex*t0,2*ey*t1-ey*t0,0]]) + assert m.rank() == 4 + +def test_partial_pivoting(): + # example from https://en.wikipedia.org/wiki/Pivot_element + # partial pivoting with back substitution gives a perfect result + # naive pivoting give an error ~1e-13, so anything better than + # 1e-15 is good + mm=Matrix([[0.003, 59.14, 59.17], [5.291, -6.13, 46.78]]) + assert (mm.rref()[0] - Matrix([[1.0, 0, 10.0], + [ 0, 1.0, 1.0]])).norm() < 1e-15 + + # issue #11549 + m_mixed = Matrix([[6e-17, 1.0, 4], + [ -1.0, 0, 8], + [ 0, 0, 1]]) + m_float = Matrix([[6e-17, 1.0, 4.], + [ -1.0, 0., 8.], + [ 0., 0., 1.]]) + m_inv = Matrix([[ 0, -1.0, 8.0], + [1.0, 6.0e-17, -4.0], + [ 0, 0, 1]]) + # this example is numerically unstable and involves a matrix with a norm >= 8, + # this comparing the difference of the results with 1e-15 is numerically sound. + assert (m_mixed.inv() - m_inv).norm() < 1e-15 + assert (m_float.inv() - m_inv).norm() < 1e-15 + +def test_iszero_substitution(): + """ When doing numerical computations, all elements that pass + the iszerofunc test should be set to numerically zero if they + aren't already. """ + + # Matrix from issue #9060 + m = Matrix([[0.9, -0.1, -0.2, 0],[-0.8, 0.9, -0.4, 0],[-0.1, -0.8, 0.6, 0]]) + m_rref = m.rref(iszerofunc=lambda x: abs(x)<6e-15)[0] + m_correct = Matrix([[1.0, 0, -0.301369863013699, 0],[ 0, 1.0, -0.712328767123288, 0],[ 0, 0, 0, 0]]) + m_diff = m_rref - m_correct + assert m_diff.norm() < 1e-15 + # if a zero-substitution wasn't made, this entry will be -1.11022302462516e-16 + assert m_rref[2,2] == 0 + +def test_issue_11238(): + from sympy.geometry.point import Point + xx = 8*tan(pi*Rational(13, 45))/(tan(pi*Rational(13, 45)) + sqrt(3)) + yy = (-8*sqrt(3)*tan(pi*Rational(13, 45))**2 + 24*tan(pi*Rational(13, 45)))/(-3 + tan(pi*Rational(13, 45))**2) + p1 = Point(0, 0) + p2 = Point(1, -sqrt(3)) + p0 = Point(xx,yy) + m1 = Matrix([p1 - simplify(p0), p2 - simplify(p0)]) + m2 = Matrix([p1 - p0, p2 - p0]) + m3 = Matrix([simplify(p1 - p0), simplify(p2 - p0)]) + + # This system has expressions which are zero and + # cannot be easily proved to be such, so without + # numerical testing, these assertions will fail. + Z = lambda x: abs(x.n()) < 1e-20 + assert m1.rank(simplify=True, iszerofunc=Z) == 1 + assert m2.rank(simplify=True, iszerofunc=Z) == 1 + assert m3.rank(simplify=True, iszerofunc=Z) == 1 + +def test_as_real_imag(): + m1 = Matrix(2,2,[1,2,3,4]) + m2 = m1*S.ImaginaryUnit + m3 = m1 + m2 + + for kls in classes: + a,b = kls(m3).as_real_imag() + assert list(a) == list(m1) + assert list(b) == list(m1) + +def test_deprecated(): + # Maintain tests for deprecated functions. We must capture + # the deprecation warnings. When the deprecated functionality is + # removed, the corresponding tests should be removed. + + m = Matrix(3, 3, [0, 1, 0, -4, 4, 0, -2, 1, 2]) + P, Jcells = m.jordan_cells() + assert Jcells[1] == Matrix(1, 1, [2]) + assert Jcells[0] == Matrix(2, 2, [2, 1, 0, 2]) + + +def test_issue_14489(): + from sympy.core.mod import Mod + A = Matrix([-1, 1, 2]) + B = Matrix([10, 20, -15]) + + assert Mod(A, 3) == Matrix([2, 1, 2]) + assert Mod(B, 4) == Matrix([2, 0, 1]) + +def test_issue_14943(): + # Test that __array__ accepts the optional dtype argument + try: + from numpy import array + except ImportError: + skip('NumPy must be available to test creating matrices from ndarrays') + + M = Matrix([[1,2], [3,4]]) + assert array(M, dtype=float).dtype.name == 'float64' + +def test_case_6913(): + m = MatrixSymbol('m', 1, 1) + a = Symbol("a") + a = m[0, 0]>0 + assert str(a) == 'm[0, 0] > 0' + +def test_issue_11948(): + A = MatrixSymbol('A', 3, 3) + a = Wild('a') + assert A.match(a) == {a: A} + +def test_gramschmidt_conjugate_dot(): + vecs = [Matrix([1, I]), Matrix([1, -I])] + assert Matrix.orthogonalize(*vecs) == \ + [Matrix([[1], [I]]), Matrix([[1], [-I]])] + + vecs = [Matrix([1, I, 0]), Matrix([I, 0, -I])] + assert Matrix.orthogonalize(*vecs) == \ + [Matrix([[1], [I], [0]]), Matrix([[I/2], [S(1)/2], [-I]])] + + mat = Matrix([[1, I], [1, -I]]) + Q, R = mat.QRdecomposition() + assert Q * Q.H == Matrix.eye(2) + +def test_issue_8207(): + a = Matrix(MatrixSymbol('a', 3, 1)) + b = Matrix(MatrixSymbol('b', 3, 1)) + c = a.dot(b) + d = diff(c, a[0, 0]) + e = diff(d, a[0, 0]) + assert d == b[0, 0] + assert e == 0 + +def test_func(): + from sympy.simplify.simplify import nthroot + + A = Matrix([[1, 2],[0, 3]]) + assert A.analytic_func(sin(x*t), x) == Matrix([[sin(t), sin(3*t) - sin(t)], [0, sin(3*t)]]) + + A = Matrix([[2, 1],[1, 2]]) + assert (pi * A / 6).analytic_func(cos(x), x) == Matrix([[sqrt(3)/4, -sqrt(3)/4], [-sqrt(3)/4, sqrt(3)/4]]) + + + raises(ValueError, lambda : zeros(5).analytic_func(log(x), x)) + raises(ValueError, lambda : (A*x).analytic_func(log(x), x)) + + A = Matrix([[0, -1, -2, 3], [0, -1, -2, 3], [0, 1, 0, -1], [0, 0, -1, 1]]) + assert A.analytic_func(exp(x), x) == A.exp() + raises(ValueError, lambda : A.analytic_func(sqrt(x), x)) + + A = Matrix([[41, 12],[12, 34]]) + assert simplify(A.analytic_func(sqrt(x), x)**2) == A + + A = Matrix([[3, -12, 4], [-1, 0, -2], [-1, 5, -1]]) + assert simplify(A.analytic_func(nthroot(x, 3), x)**3) == A + + A = Matrix([[2, 0, 0, 0], [1, 2, 0, 0], [0, 1, 3, 0], [0, 0, 1, 3]]) + assert A.analytic_func(exp(x), x) == A.exp() + + A = Matrix([[0, 2, 1, 6], [0, 0, 1, 2], [0, 0, 0, 3], [0, 0, 0, 0]]) + assert A.analytic_func(exp(x*t), x) == expand(simplify((A*t).exp())) + + +@skip_under_pyodide("Cannot create threads under pyodide.") +def test_issue_19809(): + + def f(): + assert _dotprodsimp_state.state == None + m = Matrix([[1]]) + m = m * m + return True + + with dotprodsimp(True): + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(f) + assert future.result() + + +def test_issue_23276(): + M = Matrix([x, y]) + assert integrate(M, (x, 0, 1), (y, 0, 1)) == Matrix([ + [S.Half], + [S.Half]]) + + +# SubspaceOnlyMatrix tests +def test_columnspace_one(): + m = SubspaceOnlyMatrix([[ 1, 2, 0, 2, 5], + [-2, -5, 1, -1, -8], + [ 0, -3, 3, 4, 1], + [ 3, 6, 0, -7, 2]]) + + basis = m.columnspace() + assert basis[0] == Matrix([1, -2, 0, 3]) + assert basis[1] == Matrix([2, -5, -3, 6]) + assert basis[2] == Matrix([2, -1, 4, -7]) + + assert len(basis) == 3 + assert Matrix.hstack(m, *basis).columnspace() == basis + + +def test_rowspace(): + m = SubspaceOnlyMatrix([[ 1, 2, 0, 2, 5], + [-2, -5, 1, -1, -8], + [ 0, -3, 3, 4, 1], + [ 3, 6, 0, -7, 2]]) + + basis = m.rowspace() + assert basis[0] == Matrix([[1, 2, 0, 2, 5]]) + assert basis[1] == Matrix([[0, -1, 1, 3, 2]]) + assert basis[2] == Matrix([[0, 0, 0, 5, 5]]) + + assert len(basis) == 3 + + +def test_nullspace_one(): + m = SubspaceOnlyMatrix([[ 1, 2, 0, 2, 5], + [-2, -5, 1, -1, -8], + [ 0, -3, 3, 4, 1], + [ 3, 6, 0, -7, 2]]) + + basis = m.nullspace() + assert basis[0] == Matrix([-2, 1, 1, 0, 0]) + assert basis[1] == Matrix([-1, -1, 0, -1, 1]) + # make sure the null space is really gets zeroed + assert all(e.is_zero for e in m*basis[0]) + assert all(e.is_zero for e in m*basis[1]) + + +# ReductionsOnlyMatrix tests +def test_row_op(): + e = eye_Reductions(3) + + raises(ValueError, lambda: e.elementary_row_op("abc")) + raises(ValueError, lambda: e.elementary_row_op()) + raises(ValueError, lambda: e.elementary_row_op('n->kn', row=5, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->kn', row=-5, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=1, row2=5)) + raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=5, row2=1)) + raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=-5, row2=1)) + raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=1, row2=-5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=5, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=5, row2=1, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=-5, row2=1, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=-5, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=1, k=5)) + + # test various ways to set arguments + assert e.elementary_row_op("n->kn", 0, 5) == Matrix([[5, 0, 0], [0, 1, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->kn", 1, 5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->kn", row=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->kn", row1=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_row_op("n<->m", 0, 1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_row_op("n<->m", row1=0, row2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_row_op("n<->m", row=0, row2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->n+km", 0, 5, 1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->n+km", row=0, k=5, row2=1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->n+km", row1=0, k=5, row2=1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]]) + + # make sure the matrix doesn't change size + a = ReductionsOnlyMatrix(2, 3, [0]*6) + assert a.elementary_row_op("n->kn", 1, 5) == Matrix(2, 3, [0]*6) + assert a.elementary_row_op("n<->m", 0, 1) == Matrix(2, 3, [0]*6) + assert a.elementary_row_op("n->n+km", 0, 5, 1) == Matrix(2, 3, [0]*6) + + +def test_col_op(): + e = eye_Reductions(3) + + raises(ValueError, lambda: e.elementary_col_op("abc")) + raises(ValueError, lambda: e.elementary_col_op()) + raises(ValueError, lambda: e.elementary_col_op('n->kn', col=5, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->kn', col=-5, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=1, col2=5)) + raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=5, col2=1)) + raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=-5, col2=1)) + raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=1, col2=-5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=5, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=5, col2=1, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=-5, col2=1, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=-5, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=1, k=5)) + + # test various ways to set arguments + assert e.elementary_col_op("n->kn", 0, 5) == Matrix([[5, 0, 0], [0, 1, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->kn", 1, 5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->kn", col=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->kn", col1=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_col_op("n<->m", 0, 1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_col_op("n<->m", col1=0, col2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_col_op("n<->m", col=0, col2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->n+km", 0, 5, 1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->n+km", col=0, k=5, col2=1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->n+km", col1=0, k=5, col2=1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]]) + + # make sure the matrix doesn't change size + a = ReductionsOnlyMatrix(2, 3, [0]*6) + assert a.elementary_col_op("n->kn", 1, 5) == Matrix(2, 3, [0]*6) + assert a.elementary_col_op("n<->m", 0, 1) == Matrix(2, 3, [0]*6) + assert a.elementary_col_op("n->n+km", 0, 5, 1) == Matrix(2, 3, [0]*6) + + +def test_is_echelon(): + zro = zeros_Reductions(3) + ident = eye_Reductions(3) + + assert zro.is_echelon + assert ident.is_echelon + + a = ReductionsOnlyMatrix(0, 0, []) + assert a.is_echelon + + a = ReductionsOnlyMatrix(2, 3, [3, 2, 1, 0, 0, 6]) + assert a.is_echelon + + a = ReductionsOnlyMatrix(2, 3, [0, 0, 6, 3, 2, 1]) + assert not a.is_echelon + + x = Symbol('x') + a = ReductionsOnlyMatrix(3, 1, [x, 0, 0]) + assert a.is_echelon + + a = ReductionsOnlyMatrix(3, 1, [x, x, 0]) + assert not a.is_echelon + + a = ReductionsOnlyMatrix(3, 3, [0, 0, 0, 1, 2, 3, 0, 0, 0]) + assert not a.is_echelon + + +def test_echelon_form(): + # echelon form is not unique, but the result + # must be row-equivalent to the original matrix + # and it must be in echelon form. + + a = zeros_Reductions(3) + e = eye_Reductions(3) + + # we can assume the zero matrix and the identity matrix shouldn't change + assert a.echelon_form() == a + assert e.echelon_form() == e + + a = ReductionsOnlyMatrix(0, 0, []) + assert a.echelon_form() == a + + a = ReductionsOnlyMatrix(1, 1, [5]) + assert a.echelon_form() == a + + # now we get to the real tests + + def verify_row_null_space(mat, rows, nulls): + for v in nulls: + assert all(t.is_zero for t in a_echelon*v) + for v in rows: + if not all(t.is_zero for t in v): + assert not all(t.is_zero for t in a_echelon*v.transpose()) + + a = ReductionsOnlyMatrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + nulls = [Matrix([ + [ 1], + [-2], + [ 1]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + + a = ReductionsOnlyMatrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 8]) + nulls = [] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + a = ReductionsOnlyMatrix(3, 3, [2, 1, 3, 0, 0, 0, 2, 1, 3]) + nulls = [Matrix([ + [Rational(-1, 2)], + [ 1], + [ 0]]), + Matrix([ + [Rational(-3, 2)], + [ 0], + [ 1]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + # this one requires a row swap + a = ReductionsOnlyMatrix(3, 3, [2, 1, 3, 0, 0, 0, 1, 1, 3]) + nulls = [Matrix([ + [ 0], + [ -3], + [ 1]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + a = ReductionsOnlyMatrix(3, 3, [0, 3, 3, 0, 2, 2, 0, 1, 1]) + nulls = [Matrix([ + [1], + [0], + [0]]), + Matrix([ + [ 0], + [-1], + [ 1]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + a = ReductionsOnlyMatrix(2, 3, [2, 2, 3, 3, 3, 0]) + nulls = [Matrix([ + [-1], + [1], + [0]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + +def test_rref(): + e = ReductionsOnlyMatrix(0, 0, []) + assert e.rref(pivots=False) == e + + e = ReductionsOnlyMatrix(1, 1, [1]) + a = ReductionsOnlyMatrix(1, 1, [5]) + assert e.rref(pivots=False) == a.rref(pivots=False) == e + + a = ReductionsOnlyMatrix(3, 1, [1, 2, 3]) + assert a.rref(pivots=False) == Matrix([[1], [0], [0]]) + + a = ReductionsOnlyMatrix(1, 3, [1, 2, 3]) + assert a.rref(pivots=False) == Matrix([[1, 2, 3]]) + + a = ReductionsOnlyMatrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + assert a.rref(pivots=False) == Matrix([ + [1, 0, -1], + [0, 1, 2], + [0, 0, 0]]) + + a = ReductionsOnlyMatrix(3, 3, [1, 2, 3, 1, 2, 3, 1, 2, 3]) + b = ReductionsOnlyMatrix(3, 3, [1, 2, 3, 0, 0, 0, 0, 0, 0]) + c = ReductionsOnlyMatrix(3, 3, [0, 0, 0, 1, 2, 3, 0, 0, 0]) + d = ReductionsOnlyMatrix(3, 3, [0, 0, 0, 0, 0, 0, 1, 2, 3]) + assert a.rref(pivots=False) == \ + b.rref(pivots=False) == \ + c.rref(pivots=False) == \ + d.rref(pivots=False) == b + + e = eye_Reductions(3) + z = zeros_Reductions(3) + assert e.rref(pivots=False) == e + assert z.rref(pivots=False) == z + + a = ReductionsOnlyMatrix([ + [ 0, 0, 1, 2, 2, -5, 3], + [-1, 5, 2, 2, 1, -7, 5], + [ 0, 0, -2, -3, -3, 8, -5], + [-1, 5, 0, -1, -2, 1, 0]]) + mat, pivot_offsets = a.rref() + assert mat == Matrix([ + [1, -5, 0, 0, 1, 1, -1], + [0, 0, 1, 0, 0, -1, 1], + [0, 0, 0, 1, 1, -2, 1], + [0, 0, 0, 0, 0, 0, 0]]) + assert pivot_offsets == (0, 2, 3) + + a = ReductionsOnlyMatrix([[Rational(1, 19), Rational(1, 5), 2, 3], + [ 4, 5, 6, 7], + [ 8, 9, 10, 11], + [ 12, 13, 14, 15]]) + assert a.rref(pivots=False) == Matrix([ + [1, 0, 0, Rational(-76, 157)], + [0, 1, 0, Rational(-5, 157)], + [0, 0, 1, Rational(238, 157)], + [0, 0, 0, 0]]) + + x = Symbol('x') + a = ReductionsOnlyMatrix(2, 3, [x, 1, 1, sqrt(x), x, 1]) + for i, j in zip(a.rref(pivots=False), + [1, 0, sqrt(x)*(-x + 1)/(-x**Rational(5, 2) + x), + 0, 1, 1/(sqrt(x) + x + 1)]): + assert simplify(i - j).is_zero + + +def test_rref_rhs(): + a, b, c, d = symbols('a b c d') + A = Matrix([[0, 0], [0, 0], [1, 2], [3, 4]]) + B = Matrix([a, b, c, d]) + assert A.rref_rhs(B) == (Matrix([ + [1, 0], + [0, 1], + [0, 0], + [0, 0]]), Matrix([ + [ -2*c + d], + [3*c/2 - d/2], + [ a], + [ b]])) + + +def test_issue_17827(): + C = Matrix([ + [3, 4, -1, 1], + [9, 12, -3, 3], + [0, 2, 1, 3], + [2, 3, 0, -2], + [0, 3, 3, -5], + [8, 15, 0, 6] + ]) + # Tests for row/col within valid range + D = C.elementary_row_op('n<->m', row1=2, row2=5) + E = C.elementary_row_op('n->n+km', row1=5, row2=3, k=-4) + F = C.elementary_row_op('n->kn', row=5, k=2) + assert(D[5, :] == Matrix([[0, 2, 1, 3]])) + assert(E[5, :] == Matrix([[0, 3, 0, 14]])) + assert(F[5, :] == Matrix([[16, 30, 0, 12]])) + # Tests for row/col out of range + raises(ValueError, lambda: C.elementary_row_op('n<->m', row1=2, row2=6)) + raises(ValueError, lambda: C.elementary_row_op('n->kn', row=7, k=2)) + raises(ValueError, lambda: C.elementary_row_op('n->n+km', row1=-1, row2=5, k=2)) + +def test_rank(): + m = Matrix([[1, 2], [x, 1 - 1/x]]) + assert m.rank() == 2 + n = Matrix(3, 3, range(1, 10)) + assert n.rank() == 2 + p = zeros(3) + assert p.rank() == 0 + +def test_issue_11434(): + ax, ay, bx, by, cx, cy, dx, dy, ex, ey, t0, t1 = \ + symbols('a_x a_y b_x b_y c_x c_y d_x d_y e_x e_y t_0 t_1') + M = Matrix([[ax, ay, ax*t0, ay*t0, 0], + [bx, by, bx*t0, by*t0, 0], + [cx, cy, cx*t0, cy*t0, 1], + [dx, dy, dx*t0, dy*t0, 1], + [ex, ey, 2*ex*t1 - ex*t0, 2*ey*t1 - ey*t0, 0]]) + assert M.rank() == 4 + +def test_rank_regression_from_so(): + # see: + # https://stackoverflow.com/questions/19072700/why-does-sympy-give-me-the-wrong-answer-when-i-row-reduce-a-symbolic-matrix + + nu, lamb = symbols('nu, lambda') + A = Matrix([[-3*nu, 1, 0, 0], + [ 3*nu, -2*nu - 1, 2, 0], + [ 0, 2*nu, (-1*nu) - lamb - 2, 3], + [ 0, 0, nu + lamb, -3]]) + expected_reduced = Matrix([[1, 0, 0, 1/(nu**2*(-lamb - nu))], + [0, 1, 0, 3/(nu*(-lamb - nu))], + [0, 0, 1, 3/(-lamb - nu)], + [0, 0, 0, 0]]) + expected_pivots = (0, 1, 2) + + reduced, pivots = A.rref() + + assert simplify(expected_reduced - reduced) == zeros(*A.shape) + assert pivots == expected_pivots + +def test_issue_15872(): + A = Matrix([[1, 1, 1, 0], [-2, -1, 0, -1], [0, 0, -1, -1], [0, 0, 2, 1]]) + B = A - Matrix.eye(4) * I + assert B.rank() == 3 + assert (B**2).rank() == 2 + assert (B**3).rank() == 2 diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_matrixbase.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_matrixbase.py new file mode 100644 index 0000000000000000000000000000000000000000..a77f51596c6622dc427feeeb9383214592fab632 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_matrixbase.py @@ -0,0 +1,3795 @@ +import concurrent.futures +import random +from collections.abc import Hashable + +from sympy import ( + Abs, Add, Array, DeferredVector, E, Expr, FiniteSet, Float, Function, + GramSchmidt, I, ImmutableDenseMatrix, ImmutableMatrix, + ImmutableSparseMatrix, Integer, KroneckerDelta, MatPow, Matrix, + MatrixSymbol, Max, Min, MutableDenseMatrix, MutableSparseMatrix, Poly, Pow, + PurePoly, Q, Quaternion, Rational, RootOf, S, SparseMatrix, Symbol, Tuple, + Wild, banded, casoratian, cos, diag, diff, exp, expand, eye, floor, hessian, + integrate, log, matrix_multiply_elementwise, nan, ones, oo, pi, randMatrix, + rot_axis1, rot_axis2, rot_axis3, rot_ccw_axis1, rot_ccw_axis2, + rot_ccw_axis3, signsimp, simplify, sin, sqrt, sstr, symbols, sympify, tan, + trigsimp, wronskian, zeros, cancel) +from sympy.abc import a, b, c, d, t, x, y, z +from sympy.core.kind import NumberKind, UndefinedKind +from sympy.matrices.determinant import _find_reasonable_pivot_naive +from sympy.matrices.exceptions import ( + MatrixError, NonSquareMatrixError, ShapeError) +from sympy.matrices.kind import MatrixKind +from sympy.matrices.utilities import _dotprodsimp_state, _simplify, dotprodsimp +from sympy.tensor.array.array_derivatives import ArrayDerivative +from sympy.testing.pytest import ( + ignore_warnings, raises, skip, skip_under_pyodide, slow, + warns_deprecated_sympy) +from sympy.utilities.iterables import capture, iterable +from importlib.metadata import version + +all_classes = (Matrix, SparseMatrix, ImmutableMatrix, ImmutableSparseMatrix) +mutable_classes = (Matrix, SparseMatrix) +immutable_classes = (ImmutableMatrix, ImmutableSparseMatrix) + + +def test__MinimalMatrix(): + x = Matrix(2, 3, [1, 2, 3, 4, 5, 6]) + assert x.rows == 2 + assert x.cols == 3 + assert x[2] == 3 + assert x[1, 1] == 5 + assert list(x) == [1, 2, 3, 4, 5, 6] + assert list(x[1, :]) == [4, 5, 6] + assert list(x[:, 1]) == [2, 5] + assert list(x[:, :]) == list(x) + assert x[:, :] == x + assert Matrix(x) == x + assert Matrix([[1, 2, 3], [4, 5, 6]]) == x + assert Matrix(([1, 2, 3], [4, 5, 6])) == x + assert Matrix([(1, 2, 3), (4, 5, 6)]) == x + assert Matrix(((1, 2, 3), (4, 5, 6))) == x + assert not (Matrix([[1, 2], [3, 4], [5, 6]]) == x) + + +def test_kind(): + assert Matrix([[1, 2], [3, 4]]).kind == MatrixKind(NumberKind) + assert Matrix([[0, 0], [0, 0]]).kind == MatrixKind(NumberKind) + assert Matrix(0, 0, []).kind == MatrixKind(NumberKind) + assert Matrix([[x]]).kind == MatrixKind(NumberKind) + assert Matrix([[1, Matrix([[1]])]]).kind == MatrixKind(UndefinedKind) + assert SparseMatrix([[1]]).kind == MatrixKind(NumberKind) + assert SparseMatrix([[1, Matrix([[1]])]]).kind == MatrixKind(UndefinedKind) + + +def test_todok(): + a, b, c, d = symbols('a:d') + m1 = MutableDenseMatrix([[a, b], [c, d]]) + m2 = ImmutableDenseMatrix([[a, b], [c, d]]) + m3 = MutableSparseMatrix([[a, b], [c, d]]) + m4 = ImmutableSparseMatrix([[a, b], [c, d]]) + assert m1.todok() == m2.todok() == m3.todok() == m4.todok() == \ + {(0, 0): a, (0, 1): b, (1, 0): c, (1, 1): d} + + +def test_tolist(): + lst = [[S.One, S.Half, x*y, S.Zero], [x, y, z, x**2], [y, -S.One, z*x, 3]] + flat_lst = [S.One, S.Half, x*y, S.Zero, x, y, z, x**2, y, -S.One, z*x, 3] + m = Matrix(3, 4, flat_lst) + assert m.tolist() == lst + + +def test_todod(): + m = Matrix([[S.One, 0], [0, S.Half], [x, 0]]) + dict = {0: {0: S.One}, 1: {1: S.Half}, 2: {0: x}} + assert m.todod() == dict + + +def test_row_col_del(): + e = ImmutableMatrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + raises(IndexError, lambda: e.row_del(5)) + raises(IndexError, lambda: e.row_del(-5)) + raises(IndexError, lambda: e.col_del(5)) + raises(IndexError, lambda: e.col_del(-5)) + + assert e.row_del(2) == e.row_del(-1) == Matrix([[1, 2, 3], [4, 5, 6]]) + assert e.col_del(2) == e.col_del(-1) == Matrix([[1, 2], [4, 5], [7, 8]]) + + assert e.row_del(1) == e.row_del(-2) == Matrix([[1, 2, 3], [7, 8, 9]]) + assert e.col_del(1) == e.col_del(-2) == Matrix([[1, 3], [4, 6], [7, 9]]) + + +def test_get_diag_blocks1(): + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + assert a.get_diag_blocks() == [a] + assert b.get_diag_blocks() == [b] + assert c.get_diag_blocks() == [c] + + +def test_get_diag_blocks2(): + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + A, B, C, D = diag(a, b, b), diag(a, b, c), diag(a, c, b), diag(c, c, b) + A = Matrix(A.rows, A.cols, A) + B = Matrix(B.rows, B.cols, B) + C = Matrix(C.rows, C.cols, C) + D = Matrix(D.rows, D.cols, D) + + assert A.get_diag_blocks() == [a, b, b] + assert B.get_diag_blocks() == [a, b, c] + assert C.get_diag_blocks() == [a, c, b] + assert D.get_diag_blocks() == [c, c, b] + + +def test_row_col(): + m = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + assert m.row(0) == Matrix(1, 3, [1, 2, 3]) + assert m.col(0) == Matrix(3, 1, [1, 4, 7]) + + +def test_row_join(): + assert eye(3).row_join(Matrix([7, 7, 7])) == \ + Matrix([[1, 0, 0, 7], + [0, 1, 0, 7], + [0, 0, 1, 7]]) + + +def test_col_join(): + assert eye(3).col_join(Matrix([[7, 7, 7]])) == \ + Matrix([[1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [7, 7, 7]]) + + +def test_row_insert(): + r4 = Matrix([[4, 4, 4]]) + for i in range(-4, 5): + l = [1, 0, 0] + l.insert(i, 4) + assert eye(3).row_insert(i, r4).col(0).flat() == l + + +def test_col_insert(): + c4 = Matrix([4, 4, 4]) + for i in range(-4, 5): + l = [0, 0, 0] + l.insert(i, 4) + assert zeros(3).col_insert(i, c4).row(0).flat() == l + # issue 13643 + assert eye(6).col_insert(3, Matrix([[2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]])) == \ + Matrix([[1, 0, 0, 2, 2, 0, 0, 0], + [0, 1, 0, 2, 2, 0, 0, 0], + [0, 0, 1, 2, 2, 0, 0, 0], + [0, 0, 0, 2, 2, 1, 0, 0], + [0, 0, 0, 2, 2, 0, 1, 0], + [0, 0, 0, 2, 2, 0, 0, 1]]) + + +def test_extract(): + m = Matrix(4, 3, lambda i, j: i*3 + j) + assert m.extract([0, 1, 3], [0, 1]) == Matrix(3, 2, [0, 1, 3, 4, 9, 10]) + assert m.extract([0, 3], [0, 0, 2]) == Matrix(2, 3, [0, 0, 2, 9, 9, 11]) + assert m.extract(range(4), range(3)) == m + raises(IndexError, lambda: m.extract([4], [0])) + raises(IndexError, lambda: m.extract([0], [3])) + + +def test_hstack(): + m = Matrix(4, 3, lambda i, j: i*3 + j) + m2 = Matrix(3, 4, lambda i, j: i*3 + j) + assert m == m.hstack(m) + assert m.hstack(m, m, m) == Matrix.hstack(m, m, m) == Matrix([ + [0, 1, 2, 0, 1, 2, 0, 1, 2], + [3, 4, 5, 3, 4, 5, 3, 4, 5], + [6, 7, 8, 6, 7, 8, 6, 7, 8], + [9, 10, 11, 9, 10, 11, 9, 10, 11]]) + raises(ShapeError, lambda: m.hstack(m, m2)) + assert Matrix.hstack() == Matrix() + + # test regression #12938 + M1 = Matrix.zeros(0, 0) + M2 = Matrix.zeros(0, 1) + M3 = Matrix.zeros(0, 2) + M4 = Matrix.zeros(0, 3) + m = Matrix.hstack(M1, M2, M3, M4) + assert m.rows == 0 and m.cols == 6 + + +def test_vstack(): + m = Matrix(4, 3, lambda i, j: i*3 + j) + m2 = Matrix(3, 4, lambda i, j: i*3 + j) + assert m == m.vstack(m) + assert m.vstack(m, m, m) == Matrix.vstack(m, m, m) == Matrix([ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11], + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11], + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11]]) + raises(ShapeError, lambda: m.vstack(m, m2)) + assert Matrix.vstack() == Matrix() + + +def test_has(): + A = Matrix(((x, y), (2, 3))) + assert A.has(x) + assert not A.has(z) + assert A.has(Symbol) + + A = Matrix(((2, y), (2, 3))) + assert not A.has(x) + + +def test_is_anti_symmetric(): + x = symbols('x') + assert Matrix(2, 1, [1, 2]).is_anti_symmetric() is False + m = Matrix(3, 3, [0, x**2 + 2*x + 1, y, -(x + 1)**2, 0, x*y, -y, -x*y, 0]) + assert m.is_anti_symmetric() is True + assert m.is_anti_symmetric(simplify=False) is None + assert m.is_anti_symmetric(simplify=lambda x: x) is None + + m = Matrix(3, 3, [x.expand() for x in m]) + assert m.is_anti_symmetric(simplify=False) is True + m = Matrix(3, 3, [x.expand() for x in [S.One] + list(m)[1:]]) + assert m.is_anti_symmetric() is False + + +def test_is_hermitian(): + a = Matrix([[1, I], [-I, 1]]) + assert a.is_hermitian + a = Matrix([[2*I, I], [-I, 1]]) + assert a.is_hermitian is False + a = Matrix([[x, I], [-I, 1]]) + assert a.is_hermitian is None + a = Matrix([[x, 1], [-I, 1]]) + assert a.is_hermitian is False + + +def test_is_symbolic(): + a = Matrix([[x, x], [x, x]]) + assert a.is_symbolic() is True + a = Matrix([[1, 2, 3, 4], [5, 6, 7, 8]]) + assert a.is_symbolic() is False + a = Matrix([[1, 2, 3, 4], [5, 6, x, 8]]) + assert a.is_symbolic() is True + a = Matrix([[1, x, 3]]) + assert a.is_symbolic() is True + a = Matrix([[1, 2, 3]]) + assert a.is_symbolic() is False + a = Matrix([[1], [x], [3]]) + assert a.is_symbolic() is True + a = Matrix([[1], [2], [3]]) + assert a.is_symbolic() is False + + +def test_is_square(): + m = Matrix([[1], [1]]) + m2 = Matrix([[2, 2], [2, 2]]) + assert not m.is_square + assert m2.is_square + + +def test_is_symmetric(): + m = Matrix(2, 2, [0, 1, 1, 0]) + assert m.is_symmetric() + m = Matrix(2, 2, [0, 1, 0, 1]) + assert not m.is_symmetric() + + +def test_is_hessenberg(): + A = Matrix([[3, 4, 1], [2, 4, 5], [0, 1, 2]]) + assert A.is_upper_hessenberg + A = Matrix(3, 3, [3, 2, 0, 4, 4, 1, 1, 5, 2]) + assert A.is_lower_hessenberg + A = Matrix(3, 3, [3, 2, -1, 4, 4, 1, 1, 5, 2]) + assert A.is_lower_hessenberg is False + assert A.is_upper_hessenberg is False + + A = Matrix([[3, 4, 1], [2, 4, 5], [3, 1, 2]]) + assert not A.is_upper_hessenberg + + +def test_values(): + assert set(Matrix(2, 2, [0, 1, 2, 3] + ).values()) == {1, 2, 3} + x = Symbol('x', real=True) + assert set(Matrix(2, 2, [x, 0, 0, 1] + ).values()) == {x, 1} + + +def test_conjugate(): + M = Matrix([[0, I, 5], + [1, 2, 0]]) + + assert M.T == Matrix([[0, 1], + [I, 2], + [5, 0]]) + + assert M.C == Matrix([[0, -I, 5], + [1, 2, 0]]) + assert M.C == M.conjugate() + + assert M.H == M.T.C + assert M.H == Matrix([[ 0, 1], + [-I, 2], + [ 5, 0]]) + + +def test_doit(): + a = Matrix([[Add(x, x, evaluate=False)]]) + assert a[0] != 2*x + assert a.doit() == Matrix([[2*x]]) + + +def test_evalf(): + a = Matrix(2, 1, [sqrt(5), 6]) + assert all(a.evalf()[i] == a[i].evalf() for i in range(2)) + assert all(a.evalf(2)[i] == a[i].evalf(2) for i in range(2)) + assert all(a.n(2)[i] == a[i].n(2) for i in range(2)) + + +def test_replace(): + F, G = symbols('F, G', cls=Function) + K = Matrix(2, 2, lambda i, j: G(i+j)) + M = Matrix(2, 2, lambda i, j: F(i+j)) + N = M.replace(F, G) + assert N == K + + +def test_replace_map(): + F, G = symbols('F, G', cls=Function) + M = Matrix(2, 2, lambda i, j: F(i+j)) + N, d = M.replace(F, G, True) + assert N == Matrix(2, 2, lambda i, j: G(i+j)) + assert d == {F(0): G(0), F(1): G(1), F(2): G(2)} + +def test_numpy_conversion(): + try: + from numpy import array, array_equal + except ImportError: + skip('NumPy must be available to test creating matrices from ndarrays') + A = Matrix([[1,2], [3,4]]) + np_array = array([[1,2], [3,4]]) + assert array_equal(array(A), np_array) + assert array_equal(array(A, copy=True), np_array) + if(int(version('numpy').split('.')[0]) >= 2): #run this test only if numpy is new enough that copy variable is passed properly. + raises(TypeError, lambda: array(A, copy=False)) + +def test_rot90(): + A = Matrix([[1, 2], [3, 4]]) + assert A == A.rot90(0) == A.rot90(4) + assert A.rot90(2) == A.rot90(-2) == A.rot90(6) == Matrix(((4, 3), (2, 1))) + assert A.rot90(3) == A.rot90(-1) == A.rot90(7) == Matrix(((2, 4), (1, 3))) + assert A.rot90() == A.rot90(-7) == A.rot90(-3) == Matrix(((3, 1), (4, 2))) + + +def test_subs(): + assert Matrix([[1, x], [x, 4]]).subs(x, 5) == Matrix([[1, 5], [5, 4]]) + assert Matrix([[x, 2], [x + y, 4]]).subs([[x, -1], [y, -2]]) == \ + Matrix([[-1, 2], [-3, 4]]) + assert Matrix([[x, 2], [x + y, 4]]).subs([(x, -1), (y, -2)]) == \ + Matrix([[-1, 2], [-3, 4]]) + assert Matrix([[x, 2], [x + y, 4]]).subs({x: -1, y: -2}) == \ + Matrix([[-1, 2], [-3, 4]]) + assert Matrix([[x*y]]).subs({x: y - 1, y: x - 1}, simultaneous=True) == \ + Matrix([[(x - 1)*(y - 1)]]) + + +def test_permute(): + a = Matrix(3, 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + + raises(IndexError, lambda: a.permute([[0, 5]])) + raises(ValueError, lambda: a.permute(Symbol('x'))) + b = a.permute_rows([[0, 2], [0, 1]]) + assert a.permute([[0, 2], [0, 1]]) == b == Matrix([ + [5, 6, 7, 8], + [9, 10, 11, 12], + [1, 2, 3, 4]]) + + b = a.permute_cols([[0, 2], [0, 1]]) + assert a.permute([[0, 2], [0, 1]], orientation='cols') == b ==\ + Matrix([ + [ 2, 3, 1, 4], + [ 6, 7, 5, 8], + [10, 11, 9, 12]]) + + b = a.permute_cols([[0, 2], [0, 1]], direction='backward') + assert a.permute([[0, 2], [0, 1]], orientation='cols', direction='backward') == b ==\ + Matrix([ + [ 3, 1, 2, 4], + [ 7, 5, 6, 8], + [11, 9, 10, 12]]) + + assert a.permute([1, 2, 0, 3]) == Matrix([ + [5, 6, 7, 8], + [9, 10, 11, 12], + [1, 2, 3, 4]]) + + from sympy.combinatorics import Permutation + assert a.permute(Permutation([1, 2, 0, 3])) == Matrix([ + [5, 6, 7, 8], + [9, 10, 11, 12], + [1, 2, 3, 4]]) + +def test_upper_triangular(): + + A = Matrix([ + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1] + ]) + + R = A.upper_triangular(2) + assert R == Matrix([ + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0], + [0, 0, 0, 0] + ]) + + R = A.upper_triangular(-2) + assert R == Matrix([ + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [0, 1, 1, 1] + ]) + + R = A.upper_triangular() + assert R == Matrix([ + [1, 1, 1, 1], + [0, 1, 1, 1], + [0, 0, 1, 1], + [0, 0, 0, 1] + ]) + + +def test_lower_triangular(): + A = Matrix([ + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1] + ]) + + L = A.lower_triangular() + assert L == Matrix([ + [1, 0, 0, 0], + [1, 1, 0, 0], + [1, 1, 1, 0], + [1, 1, 1, 1]]) + + L = A.lower_triangular(2) + assert L == Matrix([ + [1, 1, 1, 0], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1] + ]) + + L = A.lower_triangular(-2) + assert L == Matrix([ + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 0, 0, 0], + [1, 1, 0, 0] + ]) + + +def test_add(): + m = Matrix([[1, 2, 3], [x, y, x], [2*y, -50, z*x]]) + assert m + m == Matrix([[2, 4, 6], [2*x, 2*y, 2*x], [4*y, -100, 2*z*x]]) + n = Matrix(1, 2, [1, 2]) + raises(ShapeError, lambda: m + n) + + +def test_matmul(): + a = Matrix([[1, 2], [3, 4]]) + + assert a.__matmul__(2) == NotImplemented + + assert a.__rmatmul__(2) == NotImplemented + + #This is done this way because @ is only supported in Python 3.5+ + #To check 2@a case + try: + eval('2 @ a') + except SyntaxError: + pass + except TypeError: #TypeError is raised in case of NotImplemented is returned + pass + + #Check a@2 case + try: + eval('a @ 2') + except SyntaxError: + pass + except TypeError: #TypeError is raised in case of NotImplemented is returned + pass + + +def test_non_matmul(): + """ + Test that if explicitly specified as non-matrix, mul reverts + to scalar multiplication. + """ + class foo(Expr): + is_Matrix=False + is_MatrixLike=False + shape = (1, 1) + + A = Matrix([[1, 2], [3, 4]]) + b = foo() + assert b*A == Matrix([[b, 2*b], [3*b, 4*b]]) + assert A*b == Matrix([[b, 2*b], [3*b, 4*b]]) + + +def test_neg(): + n = Matrix(1, 2, [1, 2]) + assert -n == Matrix(1, 2, [-1, -2]) + + +def test_sub(): + n = Matrix(1, 2, [1, 2]) + assert n - n == Matrix(1, 2, [0, 0]) + + +def test_div(): + n = Matrix(1, 2, [1, 2]) + assert n/2 == Matrix(1, 2, [S.Half, S(2)/2]) + + +def test_eye(): + assert list(Matrix.eye(2, 2)) == [1, 0, 0, 1] + assert list(Matrix.eye(2)) == [1, 0, 0, 1] + assert type(Matrix.eye(2)) == Matrix + assert type(Matrix.eye(2, cls=Matrix)) == Matrix + + +def test_ones(): + assert list(Matrix.ones(2, 2)) == [1, 1, 1, 1] + assert list(Matrix.ones(2)) == [1, 1, 1, 1] + assert Matrix.ones(2, 3) == Matrix([[1, 1, 1], [1, 1, 1]]) + assert type(Matrix.ones(2)) == Matrix + assert type(Matrix.ones(2, cls=Matrix)) == Matrix + + +def test_zeros(): + assert list(Matrix.zeros(2, 2)) == [0, 0, 0, 0] + assert list(Matrix.zeros(2)) == [0, 0, 0, 0] + assert Matrix.zeros(2, 3) == Matrix([[0, 0, 0], [0, 0, 0]]) + assert type(Matrix.zeros(2)) == Matrix + assert type(Matrix.zeros(2, cls=Matrix)) == Matrix + + +def test_diag_make(): + diag = Matrix.diag + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + assert diag(a, b, b) == Matrix([ + [1, 2, 0, 0, 0, 0], + [2, 3, 0, 0, 0, 0], + [0, 0, 3, x, 0, 0], + [0, 0, y, 3, 0, 0], + [0, 0, 0, 0, 3, x], + [0, 0, 0, 0, y, 3], + ]) + assert diag(a, b, c) == Matrix([ + [1, 2, 0, 0, 0, 0, 0], + [2, 3, 0, 0, 0, 0, 0], + [0, 0, 3, x, 0, 0, 0], + [0, 0, y, 3, 0, 0, 0], + [0, 0, 0, 0, 3, x, 3], + [0, 0, 0, 0, y, 3, z], + [0, 0, 0, 0, x, y, z], + ]) + assert diag(a, c, b) == Matrix([ + [1, 2, 0, 0, 0, 0, 0], + [2, 3, 0, 0, 0, 0, 0], + [0, 0, 3, x, 3, 0, 0], + [0, 0, y, 3, z, 0, 0], + [0, 0, x, y, z, 0, 0], + [0, 0, 0, 0, 0, 3, x], + [0, 0, 0, 0, 0, y, 3], + ]) + a = Matrix([x, y, z]) + b = Matrix([[1, 2], [3, 4]]) + c = Matrix([[5, 6]]) + # this "wandering diagonal" is what makes this + # a block diagonal where each block is independent + # of the others + assert diag(a, 7, b, c) == Matrix([ + [x, 0, 0, 0, 0, 0], + [y, 0, 0, 0, 0, 0], + [z, 0, 0, 0, 0, 0], + [0, 7, 0, 0, 0, 0], + [0, 0, 1, 2, 0, 0], + [0, 0, 3, 4, 0, 0], + [0, 0, 0, 0, 5, 6]]) + raises(ValueError, lambda: diag(a, 7, b, c, rows=5)) + assert diag(1) == Matrix([[1]]) + assert diag(1, rows=2) == Matrix([[1, 0], [0, 0]]) + assert diag(1, cols=2) == Matrix([[1, 0], [0, 0]]) + assert diag(1, rows=3, cols=2) == Matrix([[1, 0], [0, 0], [0, 0]]) + assert diag(*[2, 3]) == Matrix([ + [2, 0], + [0, 3]]) + assert diag(Matrix([2, 3])) == Matrix([ + [2], + [3]]) + assert diag([1, [2, 3], 4], unpack=False) == \ + diag([[1], [2, 3], [4]], unpack=False) == Matrix([ + [1, 0], + [2, 3], + [4, 0]]) + assert type(diag(1)) == Matrix + assert type(diag(1, cls=Matrix)) == Matrix + assert Matrix.diag([1, 2, 3]) == Matrix.diag(1, 2, 3) + assert Matrix.diag([1, 2, 3], unpack=False).shape == (3, 1) + assert Matrix.diag([[1, 2, 3]]).shape == (3, 1) + assert Matrix.diag([[1, 2, 3]], unpack=False).shape == (1, 3) + assert Matrix.diag([[[1, 2, 3]]]).shape == (1, 3) + # kerning can be used to move the starting point + assert Matrix.diag(ones(0, 2), 1, 2) == Matrix([ + [0, 0, 1, 0], + [0, 0, 0, 2]]) + assert Matrix.diag(ones(2, 0), 1, 2) == Matrix([ + [0, 0], + [0, 0], + [1, 0], + [0, 2]]) + + +def test_diagonal(): + m = Matrix(3, 3, range(9)) + d = m.diagonal() + assert d == m.diagonal(0) + assert tuple(d) == (0, 4, 8) + assert tuple(m.diagonal(1)) == (1, 5) + assert tuple(m.diagonal(-1)) == (3, 7) + assert tuple(m.diagonal(2)) == (2,) + assert type(m.diagonal()) == type(m) + s = SparseMatrix(3, 3, {(1, 1): 1}) + assert type(s.diagonal()) == type(s) + assert type(m) != type(s) + raises(ValueError, lambda: m.diagonal(3)) + raises(ValueError, lambda: m.diagonal(-3)) + raises(ValueError, lambda: m.diagonal(pi)) + M = ones(2, 3) + assert banded({i: list(M.diagonal(i)) + for i in range(1-M.rows, M.cols)}) == M + + +def test_jordan_block(): + assert Matrix.jordan_block(3, 2) == Matrix.jordan_block(3, eigenvalue=2) \ + == Matrix.jordan_block(size=3, eigenvalue=2) \ + == Matrix.jordan_block(3, 2, band='upper') \ + == Matrix.jordan_block( + size=3, eigenval=2, eigenvalue=2) \ + == Matrix([ + [2, 1, 0], + [0, 2, 1], + [0, 0, 2]]) + + assert Matrix.jordan_block(3, 2, band='lower') == Matrix([ + [2, 0, 0], + [1, 2, 0], + [0, 1, 2]]) + # missing eigenvalue + raises(ValueError, lambda: Matrix.jordan_block(2)) + # non-integral size + raises(ValueError, lambda: Matrix.jordan_block(3.5, 2)) + # size not specified + raises(ValueError, lambda: Matrix.jordan_block(eigenvalue=2)) + # inconsistent eigenvalue + raises(ValueError, + lambda: Matrix.jordan_block( + eigenvalue=2, eigenval=4)) + + # Using alias keyword + assert Matrix.jordan_block(size=3, eigenvalue=2) == \ + Matrix.jordan_block(size=3, eigenval=2) + + +def test_orthogonalize(): + m = Matrix([[1, 2], [3, 4]]) + assert m.orthogonalize(Matrix([[2], [1]])) == [Matrix([[2], [1]])] + assert m.orthogonalize(Matrix([[2], [1]]), normalize=True) == \ + [Matrix([[2*sqrt(5)/5], [sqrt(5)/5]])] + assert m.orthogonalize(Matrix([[1], [2]]), Matrix([[-1], [4]])) == \ + [Matrix([[1], [2]]), Matrix([[Rational(-12, 5)], [Rational(6, 5)]])] + assert m.orthogonalize(Matrix([[0], [0]]), Matrix([[-1], [4]])) == \ + [Matrix([[-1], [4]])] + assert m.orthogonalize(Matrix([[0], [0]])) == [] + + n = Matrix([[9, 1, 9], [3, 6, 10], [8, 5, 2]]) + vecs = [Matrix([[-5], [1]]), Matrix([[-5], [2]]), Matrix([[-5], [-2]])] + assert n.orthogonalize(*vecs) == \ + [Matrix([[-5], [1]]), Matrix([[Rational(5, 26)], [Rational(25, 26)]])] + + vecs = [Matrix([0, 0, 0]), Matrix([1, 2, 3]), Matrix([1, 4, 5])] + raises(ValueError, lambda: Matrix.orthogonalize(*vecs, rankcheck=True)) + + vecs = [Matrix([1, 2, 3]), Matrix([4, 5, 6]), Matrix([7, 8, 9])] + raises(ValueError, lambda: Matrix.orthogonalize(*vecs, rankcheck=True)) + +def test_wilkinson(): + + wminus, wplus = Matrix.wilkinson(1) + assert wminus == Matrix([ + [-1, 1, 0], + [1, 0, 1], + [0, 1, 1]]) + assert wplus == Matrix([ + [1, 1, 0], + [1, 0, 1], + [0, 1, 1]]) + + wminus, wplus = Matrix.wilkinson(3) + assert wminus == Matrix([ + [-3, 1, 0, 0, 0, 0, 0], + [1, -2, 1, 0, 0, 0, 0], + [0, 1, -1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0], + [0, 0, 0, 0, 1, 2, 1], + + [0, 0, 0, 0, 0, 1, 3]]) + + assert wplus == Matrix([ + [3, 1, 0, 0, 0, 0, 0], + [1, 2, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0], + [0, 0, 0, 0, 1, 2, 1], + [0, 0, 0, 0, 0, 1, 3]]) + + +def test_limit(): + x, y = symbols('x y') + m = Matrix(2, 1, [1/x, y]) + assert m.limit(x, 5) == Matrix(2, 1, [Rational(1, 5), y]) + A = Matrix(((1, 4, sin(x)/x), (y, 2, 4), (10, 5, x**2 + 1))) + assert A.limit(x, 0) == Matrix(((1, 4, 1), (y, 2, 4), (10, 5, 1))) + + +def test_issue_13774(): + M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + v = [1, 1, 1] + raises(TypeError, lambda: M*v) + raises(TypeError, lambda: v*M) + + +def test_companion(): + x = Symbol('x') + y = Symbol('y') + raises(ValueError, lambda: Matrix.companion(1)) + raises(ValueError, lambda: Matrix.companion(Poly([1], x))) + raises(ValueError, lambda: Matrix.companion(Poly([2, 1], x))) + raises(ValueError, lambda: Matrix.companion(Poly(x*y, [x, y]))) + + c0, c1, c2 = symbols('c0:3') + assert Matrix.companion(Poly([1, c0], x)) == Matrix([-c0]) + assert Matrix.companion(Poly([1, c1, c0], x)) == \ + Matrix([[0, -c0], [1, -c1]]) + assert Matrix.companion(Poly([1, c2, c1, c0], x)) == \ + Matrix([[0, 0, -c0], [1, 0, -c1], [0, 1, -c2]]) + + +def test_issue_10589(): + x, y, z = symbols("x, y z") + M1 = Matrix([x, y, z]) + M1 = M1.subs(zip([x, y, z], [1, 2, 3])) + assert M1 == Matrix([[1], [2], [3]]) + + M2 = Matrix([[x, x, x, x, x], [x, x, x, x, x], [x, x, x, x, x]]) + M2 = M2.subs(zip([x], [1])) + assert M2 == Matrix([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]]) + + +def test_rmul_pr19860(): + class Foo(ImmutableDenseMatrix): + _op_priority = MutableDenseMatrix._op_priority + 0.01 + + a = Matrix(2, 2, [1, 2, 3, 4]) + b = Foo(2, 2, [1, 2, 3, 4]) + + # This would throw a RecursionError: maximum recursion depth + # since b always has higher priority even after a.as_mutable() + c = a*b + + assert isinstance(c, Foo) + assert c == Matrix([[7, 10], [15, 22]]) + + +def test_issue_18956(): + A = Array([[1, 2], [3, 4]]) + B = Matrix([[1,2],[3,4]]) + raises(TypeError, lambda: B + A) + raises(TypeError, lambda: A + B) + + +def test__eq__(): + class My(object): + def __iter__(self): + yield 1 + yield 2 + return + def __getitem__(self, i): + return list(self)[i] + a = Matrix(2, 1, [1, 2]) + assert a != My() + class My_sympy(My): + def _sympy_(self): + return Matrix(self) + assert a == My_sympy() + + +def test_args(): + for n, cls in enumerate(all_classes): + m = cls.zeros(3, 2) + # all should give back the same type of arguments, e.g. ints for shape + assert m.shape == (3, 2) and all(type(i) is int for i in m.shape) + assert m.rows == 3 and type(m.rows) is int + assert m.cols == 2 and type(m.cols) is int + if not n % 2: + assert type(m.flat()) in (list, tuple, Tuple) + else: + assert type(m.todok()) is dict + + +def test_deprecated_mat_smat(): + for cls in Matrix, ImmutableMatrix: + m = cls.zeros(3, 2) + with warns_deprecated_sympy(): + mat = m._mat + assert mat == m.flat() + for cls in SparseMatrix, ImmutableSparseMatrix: + m = cls.zeros(3, 2) + with warns_deprecated_sympy(): + smat = m._smat + assert smat == m.todok() + + +def test_division(): + v = Matrix(1, 2, [x, y]) + assert v/z == Matrix(1, 2, [x/z, y/z]) + + +def test_sum(): + m = Matrix([[1, 2, 3], [x, y, x], [2*y, -50, z*x]]) + assert m + m == Matrix([[2, 4, 6], [2*x, 2*y, 2*x], [4*y, -100, 2*z*x]]) + n = Matrix(1, 2, [1, 2]) + raises(ShapeError, lambda: m + n) + + +def test_abs(): + m = Matrix([[1, -2], [x, y]]) + assert abs(m) == Matrix([[1, 2], [Abs(x), Abs(y)]]) + m = Matrix(1, 2, [-3, x]) + n = Matrix(1, 2, [3, Abs(x)]) + assert abs(m) == n + + +def test_addition(): + a = Matrix(( + (1, 2), + (3, 1), + )) + + b = Matrix(( + (1, 2), + (3, 0), + )) + + assert a + b == a.add(b) == Matrix([[2, 4], [6, 1]]) + + +def test_fancy_index_matrix(): + for M in (Matrix, SparseMatrix): + a = M(3, 3, range(9)) + assert a == a[:, :] + assert a[1, :] == Matrix(1, 3, [3, 4, 5]) + assert a[:, 1] == Matrix([1, 4, 7]) + assert a[[0, 1], :] == Matrix([[0, 1, 2], [3, 4, 5]]) + assert a[[0, 1], 2] == a[[0, 1], [2]] + assert a[2, [0, 1]] == a[[2], [0, 1]] + assert a[:, [0, 1]] == Matrix([[0, 1], [3, 4], [6, 7]]) + assert a[0, 0] == 0 + assert a[0:2, :] == Matrix([[0, 1, 2], [3, 4, 5]]) + assert a[:, 0:2] == Matrix([[0, 1], [3, 4], [6, 7]]) + assert a[::2, 1] == a[[0, 2], 1] + assert a[1, ::2] == a[1, [0, 2]] + a = M(3, 3, range(9)) + assert a[[0, 2, 1, 2, 1], :] == Matrix([ + [0, 1, 2], + [6, 7, 8], + [3, 4, 5], + [6, 7, 8], + [3, 4, 5]]) + assert a[:, [0,2,1,2,1]] == Matrix([ + [0, 2, 1, 2, 1], + [3, 5, 4, 5, 4], + [6, 8, 7, 8, 7]]) + + a = SparseMatrix.zeros(3) + a[1, 2] = 2 + a[0, 1] = 3 + a[2, 0] = 4 + assert a.extract([1, 1], [2]) == Matrix([ + [2], + [2]]) + assert a.extract([1, 0], [2, 2, 2]) == Matrix([ + [2, 2, 2], + [0, 0, 0]]) + assert a.extract([1, 0, 1, 2], [2, 0, 1, 0]) == Matrix([ + [2, 0, 0, 0], + [0, 0, 3, 0], + [2, 0, 0, 0], + [0, 4, 0, 4]]) + + +def test_multiplication(): + a = Matrix(( + (1, 2), + (3, 1), + (0, 6), + )) + + b = Matrix(( + (1, 2), + (3, 0), + )) + + raises(ShapeError, lambda: b*a) + raises(TypeError, lambda: a*{}) + + c = a*b + assert c[0, 0] == 7 + assert c[0, 1] == 2 + assert c[1, 0] == 6 + assert c[1, 1] == 6 + assert c[2, 0] == 18 + assert c[2, 1] == 0 + + c = a @ b + assert c[0, 0] == 7 + assert c[0, 1] == 2 + assert c[1, 0] == 6 + assert c[1, 1] == 6 + assert c[2, 0] == 18 + assert c[2, 1] == 0 + + h = matrix_multiply_elementwise(a, c) + assert h == a.multiply_elementwise(c) + assert h[0, 0] == 7 + assert h[0, 1] == 4 + assert h[1, 0] == 18 + assert h[1, 1] == 6 + assert h[2, 0] == 0 + assert h[2, 1] == 0 + raises(ShapeError, lambda: matrix_multiply_elementwise(a, b)) + + c = b * Symbol("x") + assert isinstance(c, Matrix) + assert c[0, 0] == x + assert c[0, 1] == 2*x + assert c[1, 0] == 3*x + assert c[1, 1] == 0 + + c2 = x * b + assert c == c2 + + c = 5 * b + assert isinstance(c, Matrix) + assert c[0, 0] == 5 + assert c[0, 1] == 2*5 + assert c[1, 0] == 3*5 + assert c[1, 1] == 0 + + M = Matrix([[oo, 0], [0, oo]]) + assert M ** 2 == M + + M = Matrix([[oo, oo], [0, 0]]) + assert M ** 2 == Matrix([[nan, nan], [nan, nan]]) + + # https://github.com/sympy/sympy/issues/22353 + A = Matrix(ones(3, 1)) + _h = -Rational(1, 2) + B = Matrix([_h, _h, _h]) + assert A.multiply_elementwise(B) == Matrix([ + [_h], + [_h], + [_h]]) + + +def test_power(): + raises(NonSquareMatrixError, lambda: Matrix((1, 2))**2) + + A = Matrix([[2, 3], [4, 5]]) + assert A**5 == Matrix([[6140, 8097], [10796, 14237]]) + A = Matrix([[2, 1, 3], [4, 2, 4], [6, 12, 1]]) + assert A**3 == Matrix([[290, 262, 251], [448, 440, 368], [702, 954, 433]]) + assert A**0 == eye(3) + assert A**1 == A + assert (Matrix([[2]]) ** 100)[0, 0] == 2**100 + assert Matrix([[1, 2], [3, 4]])**Integer(2) == Matrix([[7, 10], [15, 22]]) + A = Matrix([[1,2],[4,5]]) + assert A.pow(20, method='cayley') == A.pow(20, method='multiply') + assert A**Integer(2) == Matrix([[9, 12], [24, 33]]) + assert eye(2)**10000000 == eye(2) + + A = Matrix([[33, 24], [48, 57]]) + assert (A**S.Half)[:] == [5, 2, 4, 7] + A = Matrix([[0, 4], [-1, 5]]) + assert (A**S.Half)**2 == A + + assert Matrix([[1, 0], [1, 1]])**S.Half == Matrix([[1, 0], [S.Half, 1]]) + assert Matrix([[1, 0], [1, 1]])**0.5 == Matrix([[1, 0], [0.5, 1]]) + from sympy.abc import n + assert Matrix([[1, a], [0, 1]])**n == Matrix([[1, a*n], [0, 1]]) + assert Matrix([[b, a], [0, b]])**n == Matrix([[b**n, a*b**(n-1)*n], [0, b**n]]) + assert Matrix([ + [a**n, a**(n - 1)*n, (a**n*n**2 - a**n*n)/(2*a**2)], + [ 0, a**n, a**(n - 1)*n], + [ 0, 0, a**n]]) + assert Matrix([[a, 1, 0], [0, a, 0], [0, 0, b]])**n == Matrix([ + [a**n, a**(n-1)*n, 0], + [0, a**n, 0], + [0, 0, b**n]]) + + A = Matrix([[1, 0], [1, 7]]) + assert A._matrix_pow_by_jordan_blocks(S(3)) == A._eval_pow_by_recursion(3) + A = Matrix([[2]]) + assert A**10 == Matrix([[2**10]]) == A._matrix_pow_by_jordan_blocks(S(10)) == \ + A._eval_pow_by_recursion(10) + + # testing a matrix that cannot be jordan blocked issue 11766 + m = Matrix([[3, 0, 0, 0, -3], [0, -3, -3, 0, 3], [0, 3, 0, 3, 0], [0, 0, 3, 0, 3], [3, 0, 0, 3, 0]]) + raises(MatrixError, lambda: m._matrix_pow_by_jordan_blocks(S(10))) + + # test issue 11964 + raises(MatrixError, lambda: Matrix([[1, 1], [3, 3]])._matrix_pow_by_jordan_blocks(S(-10))) + A = Matrix([[0, 1, 0], [0, 0, 1], [0, 0, 0]]) # Nilpotent jordan block size 3 + assert A**10.0 == Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + raises(ValueError, lambda: A**2.1) + raises(ValueError, lambda: A**Rational(3, 2)) + A = Matrix([[8, 1], [3, 2]]) + assert A**10.0 == Matrix([[1760744107, 272388050], [817164150, 126415807]]) + A = Matrix([[0, 0, 1], [0, 0, 1], [0, 0, 1]]) # Nilpotent jordan block size 1 + assert A**10.0 == Matrix([[0, 0, 1], [0, 0, 1], [0, 0, 1]]) + A = Matrix([[0, 1, 0], [0, 0, 1], [0, 0, 1]]) # Nilpotent jordan block size 2 + assert A**10.0 == Matrix([[0, 0, 1], [0, 0, 1], [0, 0, 1]]) + n = Symbol('n', integer=True) + assert isinstance(A**n, MatPow) + n = Symbol('n', integer=True, negative=True) + raises(ValueError, lambda: A**n) + n = Symbol('n', integer=True, nonnegative=True) + assert A**n == Matrix([ + [KroneckerDelta(0, n), KroneckerDelta(1, n), -KroneckerDelta(0, n) - KroneckerDelta(1, n) + 1], + [ 0, KroneckerDelta(0, n), 1 - KroneckerDelta(0, n)], + [ 0, 0, 1]]) + assert A**(n + 2) == Matrix([[0, 0, 1], [0, 0, 1], [0, 0, 1]]) + raises(ValueError, lambda: A**Rational(3, 2)) + A = Matrix([[0, 0, 1], [3, 0, 1], [4, 3, 1]]) + assert A**5.0 == Matrix([[168, 72, 89], [291, 144, 161], [572, 267, 329]]) + assert A**5.0 == A**5 + A = Matrix([[0, 1, 0],[-1, 0, 0],[0, 0, 0]]) + n = Symbol("n") + An = A**n + assert An.subs(n, 2).doit() == A**2 + raises(ValueError, lambda: An.subs(n, -2).doit()) + assert An * An == A**(2*n) + + # concretizing behavior for non-integer and complex powers + A = Matrix([[0,0,0],[0,0,0],[0,0,0]]) + n = Symbol('n', integer=True, positive=True) + assert A**n == A + n = Symbol('n', integer=True, nonnegative=True) + assert A**n == diag(0**n, 0**n, 0**n) + assert (A**n).subs(n, 0) == eye(3) + assert (A**n).subs(n, 1) == zeros(3) + A = Matrix ([[2,0,0],[0,2,0],[0,0,2]]) + assert A**2.1 == diag (2**2.1, 2**2.1, 2**2.1) + assert A**I == diag (2**I, 2**I, 2**I) + A = Matrix([[0, 1, 0], [0, 0, 1], [0, 0, 1]]) + raises(ValueError, lambda: A**2.1) + raises(ValueError, lambda: A**I) + A = Matrix([[S.Half, S.Half], [S.Half, S.Half]]) + assert A**S.Half == A + A = Matrix([[1, 1],[3, 3]]) + assert A**S.Half == Matrix ([[S.Half, S.Half], [3*S.Half, 3*S.Half]]) + + +def test_issue_17247_expression_blowup_1(): + M = Matrix([[1+x, 1-x], [1-x, 1+x]]) + with dotprodsimp(True): + assert M.exp().expand() == Matrix([ + [ (exp(2*x) + exp(2))/2, (-exp(2*x) + exp(2))/2], + [(-exp(2*x) + exp(2))/2, (exp(2*x) + exp(2))/2]]) + + +def test_issue_17247_expression_blowup_2(): + M = Matrix([[1+x, 1-x], [1-x, 1+x]]) + with dotprodsimp(True): + P, J = M.jordan_form () + assert P*J*P.inv() + + +def test_issue_17247_expression_blowup_3(): + M = Matrix([[1+x, 1-x], [1-x, 1+x]]) + with dotprodsimp(True): + assert M**100 == Matrix([ + [633825300114114700748351602688*x**100 + 633825300114114700748351602688, 633825300114114700748351602688 - 633825300114114700748351602688*x**100], + [633825300114114700748351602688 - 633825300114114700748351602688*x**100, 633825300114114700748351602688*x**100 + 633825300114114700748351602688]]) + + +def test_issue_17247_expression_blowup_4(): +# This matrix takes extremely long on current master even with intermediate simplification so an abbreviated version is used. It is left here for test in case of future optimizations. +# M = Matrix(S('''[ +# [ -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64, -9/32 - I/16, 183/256 - 97*I/128, 3/64 + 13*I/64, -23/32 - 59*I/256, 15/128 - 3*I/32, 19/256 + 551*I/1024], +# [-149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512, -219/128 + 115*I/256, 6301/4096 - 6609*I/1024, 119/128 + 143*I/128, -10879/2048 + 4343*I/4096, 129/256 - 549*I/512, 42533/16384 + 29103*I/8192], +# [ 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64, -9/32 - I/16, 183/256 - 97*I/128, 3/64 + 13*I/64, -23/32 - 59*I/256], +# [ -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512, -219/128 + 115*I/256, 6301/4096 - 6609*I/1024, 119/128 + 143*I/128, -10879/2048 + 4343*I/4096], +# [ 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64, -9/32 - I/16, 183/256 - 97*I/128], +# [ 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512, -219/128 + 115*I/256, 6301/4096 - 6609*I/1024], +# [ -2, 17/4 - 13*I/2, 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64], +# [ 1/4 + 13*I/4, -825/64 - 147*I/32, 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512], +# [ -4*I, 27/2 + 6*I, -2, 17/4 - 13*I/2, 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64], +# [ 1/4 + 5*I/2, -23/8 - 57*I/16, 1/4 + 13*I/4, -825/64 - 147*I/32, 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128], +# [ -4, 9 - 5*I, -4*I, 27/2 + 6*I, -2, 17/4 - 13*I/2, 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16], +# [ -2*I, 119/8 + 29*I/4, 1/4 + 5*I/2, -23/8 - 57*I/16, 1/4 + 13*I/4, -825/64 - 147*I/32, 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128]]''')) +# assert M**10 == Matrix([ +# [ 7*(-221393644768594642173548179825793834595 - 1861633166167425978847110897013541127952*I)/9671406556917033397649408, 15*(31670992489131684885307005100073928751695 + 10329090958303458811115024718207404523808*I)/77371252455336267181195264, 7*(-3710978679372178839237291049477017392703 + 1377706064483132637295566581525806894169*I)/19342813113834066795298816, (9727707023582419994616144751727760051598 - 59261571067013123836477348473611225724433*I)/9671406556917033397649408, (31896723509506857062605551443641668183707 + 54643444538699269118869436271152084599580*I)/38685626227668133590597632, (-2024044860947539028275487595741003997397402 + 130959428791783397562960461903698670485863*I)/309485009821345068724781056, 3*(26190251453797590396533756519358368860907 - 27221191754180839338002754608545400941638*I)/77371252455336267181195264, (1154643595139959842768960128434994698330461 + 3385496216250226964322872072260446072295634*I)/618970019642690137449562112, 3*(-31849347263064464698310044805285774295286 - 11877437776464148281991240541742691164309*I)/77371252455336267181195264, (4661330392283532534549306589669150228040221 - 4171259766019818631067810706563064103956871*I)/1237940039285380274899124224, (9598353794289061833850770474812760144506 + 358027153990999990968244906482319780943983*I)/309485009821345068724781056, (-9755135335127734571547571921702373498554177 - 4837981372692695195747379349593041939686540*I)/2475880078570760549798248448], +# [(-379516731607474268954110071392894274962069 - 422272153179747548473724096872271700878296*I)/77371252455336267181195264, (41324748029613152354787280677832014263339501 - 12715121258662668420833935373453570749288074*I)/1237940039285380274899124224, (-339216903907423793947110742819264306542397 + 494174755147303922029979279454787373566517*I)/77371252455336267181195264, (-18121350839962855576667529908850640619878381 - 37413012454129786092962531597292531089199003*I)/1237940039285380274899124224, (2489661087330511608618880408199633556675926 + 1137821536550153872137379935240732287260863*I)/309485009821345068724781056, (-136644109701594123227587016790354220062972119 + 110130123468183660555391413889600443583585272*I)/4951760157141521099596496896, (1488043981274920070468141664150073426459593 - 9691968079933445130866371609614474474327650*I)/1237940039285380274899124224, 27*(4636797403026872518131756991410164760195942 + 3369103221138229204457272860484005850416533*I)/4951760157141521099596496896, (-8534279107365915284081669381642269800472363 + 2241118846262661434336333368511372725482742*I)/1237940039285380274899124224, (60923350128174260992536531692058086830950875 - 263673488093551053385865699805250505661590126*I)/9903520314283042199192993792, (18520943561240714459282253753348921824172569 + 24846649186468656345966986622110971925703604*I)/4951760157141521099596496896, (-232781130692604829085973604213529649638644431 + 35981505277760667933017117949103953338570617*I)/9903520314283042199192993792], +# [ (8742968295129404279528270438201520488950 + 3061473358639249112126847237482570858327*I)/4835703278458516698824704, (-245657313712011778432792959787098074935273 + 253113767861878869678042729088355086740856*I)/38685626227668133590597632, (1947031161734702327107371192008011621193 - 19462330079296259148177542369999791122762*I)/9671406556917033397649408, (552856485625209001527688949522750288619217 + 392928441196156725372494335248099016686580*I)/77371252455336267181195264, (-44542866621905323121630214897126343414629 + 3265340021421335059323962377647649632959*I)/19342813113834066795298816, (136272594005759723105646069956434264218730 - 330975364731707309489523680957584684763587*I)/38685626227668133590597632, (27392593965554149283318732469825168894401 + 75157071243800133880129376047131061115278*I)/38685626227668133590597632, 7*(-357821652913266734749960136017214096276154 - 45509144466378076475315751988405961498243*I)/309485009821345068724781056, (104485001373574280824835174390219397141149 - 99041000529599568255829489765415726168162*I)/77371252455336267181195264, (1198066993119982409323525798509037696321291 + 4249784165667887866939369628840569844519936*I)/618970019642690137449562112, (-114985392587849953209115599084503853611014 - 52510376847189529234864487459476242883449*I)/77371252455336267181195264, (6094620517051332877965959223269600650951573 - 4683469779240530439185019982269137976201163*I)/1237940039285380274899124224], +# [ (611292255597977285752123848828590587708323 - 216821743518546668382662964473055912169502*I)/77371252455336267181195264, (-1144023204575811464652692396337616594307487 + 12295317806312398617498029126807758490062855*I)/309485009821345068724781056, (-374093027769390002505693378578475235158281 - 573533923565898290299607461660384634333639*I)/77371252455336267181195264, (47405570632186659000138546955372796986832987 - 2837476058950808941605000274055970055096534*I)/1237940039285380274899124224, (-571573207393621076306216726219753090535121 + 533381457185823100878764749236639320783831*I)/77371252455336267181195264, (-7096548151856165056213543560958582513797519 - 24035731898756040059329175131592138642195366*I)/618970019642690137449562112, (2396762128833271142000266170154694033849225 + 1448501087375679588770230529017516492953051*I)/309485009821345068724781056, (-150609293845161968447166237242456473262037053 + 92581148080922977153207018003184520294188436*I)/4951760157141521099596496896, 5*(270278244730804315149356082977618054486347 - 1997830155222496880429743815321662710091562*I)/1237940039285380274899124224, (62978424789588828258068912690172109324360330 + 44803641177219298311493356929537007630129097*I)/2475880078570760549798248448, 19*(-451431106327656743945775812536216598712236 + 114924966793632084379437683991151177407937*I)/1237940039285380274899124224, (63417747628891221594106738815256002143915995 - 261508229397507037136324178612212080871150958*I)/9903520314283042199192993792], +# [ (-2144231934021288786200752920446633703357 + 2305614436009705803670842248131563850246*I)/1208925819614629174706176, (-90720949337459896266067589013987007078153 - 221951119475096403601562347412753844534569*I)/19342813113834066795298816, (11590973613116630788176337262688659880376 + 6514520676308992726483494976339330626159*I)/4835703278458516698824704, 3*(-131776217149000326618649542018343107657237 + 79095042939612668486212006406818285287004*I)/38685626227668133590597632, (10100577916793945997239221374025741184951 - 28631383488085522003281589065994018550748*I)/9671406556917033397649408, 67*(10090295594251078955008130473573667572549 + 10449901522697161049513326446427839676762*I)/77371252455336267181195264, (-54270981296988368730689531355811033930513 - 3413683117592637309471893510944045467443*I)/19342813113834066795298816, (440372322928679910536575560069973699181278 - 736603803202303189048085196176918214409081*I)/77371252455336267181195264, (33220374714789391132887731139763250155295 + 92055083048787219934030779066298919603554*I)/38685626227668133590597632, 5*(-594638554579967244348856981610805281527116 - 82309245323128933521987392165716076704057*I)/309485009821345068724781056, (128056368815300084550013708313312073721955 - 114619107488668120303579745393765245911404*I)/77371252455336267181195264, 21*(59839959255173222962789517794121843393573 + 241507883613676387255359616163487405826334*I)/618970019642690137449562112], +# [ (-13454485022325376674626653802541391955147 + 184471402121905621396582628515905949793486*I)/19342813113834066795298816, (-6158730123400322562149780662133074862437105 - 3416173052604643794120262081623703514107476*I)/154742504910672534362390528, (770558003844914708453618983120686116100419 - 127758381209767638635199674005029818518766*I)/77371252455336267181195264, (-4693005771813492267479835161596671660631703 + 12703585094750991389845384539501921531449948*I)/309485009821345068724781056, (-295028157441149027913545676461260860036601 - 841544569970643160358138082317324743450770*I)/77371252455336267181195264, (56716442796929448856312202561538574275502893 + 7216818824772560379753073185990186711454778*I)/1237940039285380274899124224, 15*(-87061038932753366532685677510172566368387 + 61306141156647596310941396434445461895538*I)/154742504910672534362390528, (-3455315109680781412178133042301025723909347 - 24969329563196972466388460746447646686670670*I)/618970019642690137449562112, (2453418854160886481106557323699250865361849 + 1497886802326243014471854112161398141242514*I)/309485009821345068724781056, (-151343224544252091980004429001205664193082173 + 90471883264187337053549090899816228846836628*I)/4951760157141521099596496896, (1652018205533026103358164026239417416432989 - 9959733619236515024261775397109724431400162*I)/1237940039285380274899124224, 3*(40676374242956907656984876692623172736522006 + 31023357083037817469535762230872667581366205*I)/4951760157141521099596496896], +# [ (-1226990509403328460274658603410696548387 - 4131739423109992672186585941938392788458*I)/1208925819614629174706176, (162392818524418973411975140074368079662703 + 23706194236915374831230612374344230400704*I)/9671406556917033397649408, (-3935678233089814180000602553655565621193 + 2283744757287145199688061892165659502483*I)/1208925819614629174706176, (-2400210250844254483454290806930306285131 - 315571356806370996069052930302295432758205*I)/19342813113834066795298816, (13365917938215281056563183751673390817910 + 15911483133819801118348625831132324863881*I)/4835703278458516698824704, 3*(-215950551370668982657516660700301003897855 + 51684341999223632631602864028309400489378*I)/38685626227668133590597632, (20886089946811765149439844691320027184765 - 30806277083146786592790625980769214361844*I)/9671406556917033397649408, (562180634592713285745940856221105667874855 + 1031543963988260765153550559766662245114916*I)/77371252455336267181195264, (-65820625814810177122941758625652476012867 - 12429918324787060890804395323920477537595*I)/19342813113834066795298816, (319147848192012911298771180196635859221089 - 402403304933906769233365689834404519960394*I)/38685626227668133590597632, (23035615120921026080284733394359587955057 + 115351677687031786114651452775242461310624*I)/38685626227668133590597632, (-3426830634881892756966440108592579264936130 - 1022954961164128745603407283836365128598559*I)/309485009821345068724781056], +# [ (-192574788060137531023716449082856117537757 - 69222967328876859586831013062387845780692*I)/19342813113834066795298816, (2736383768828013152914815341491629299773262 - 2773252698016291897599353862072533475408743*I)/77371252455336267181195264, (-23280005281223837717773057436155921656805 + 214784953368021840006305033048142888879224*I)/19342813113834066795298816, (-3035247484028969580570400133318947903462326 - 2195168903335435855621328554626336958674325*I)/77371252455336267181195264, (984552428291526892214541708637840971548653 - 64006622534521425620714598573494988589378*I)/77371252455336267181195264, (-3070650452470333005276715136041262898509903 + 7286424705750810474140953092161794621989080*I)/154742504910672534362390528, (-147848877109756404594659513386972921139270 - 416306113044186424749331418059456047650861*I)/38685626227668133590597632, (55272118474097814260289392337160619494260781 + 7494019668394781211907115583302403519488058*I)/1237940039285380274899124224, (-581537886583682322424771088996959213068864 + 542191617758465339135308203815256798407429*I)/77371252455336267181195264, (-6422548983676355789975736799494791970390991 - 23524183982209004826464749309156698827737702*I)/618970019642690137449562112, 7*(180747195387024536886923192475064903482083 + 84352527693562434817771649853047924991804*I)/154742504910672534362390528, (-135485179036717001055310712747643466592387031 + 102346575226653028836678855697782273460527608*I)/4951760157141521099596496896], +# [ (3384238362616083147067025892852431152105 + 156724444932584900214919898954874618256*I)/604462909807314587353088, (-59558300950677430189587207338385764871866 + 114427143574375271097298201388331237478857*I)/4835703278458516698824704, (-1356835789870635633517710130971800616227 - 7023484098542340388800213478357340875410*I)/1208925819614629174706176, (234884918567993750975181728413524549575881 + 79757294640629983786895695752733890213506*I)/9671406556917033397649408, (-7632732774935120473359202657160313866419 + 2905452608512927560554702228553291839465*I)/1208925819614629174706176, (52291747908702842344842889809762246649489 - 520996778817151392090736149644507525892649*I)/19342813113834066795298816, (17472406829219127839967951180375981717322 + 23464704213841582137898905375041819568669*I)/4835703278458516698824704, (-911026971811893092350229536132730760943307 + 150799318130900944080399439626714846752360*I)/38685626227668133590597632, (26234457233977042811089020440646443590687 - 45650293039576452023692126463683727692890*I)/9671406556917033397649408, 3*(288348388717468992528382586652654351121357 + 454526517721403048270274049572136109264668*I)/77371252455336267181195264, (-91583492367747094223295011999405657956347 - 12704691128268298435362255538069612411331*I)/19342813113834066795298816, (411208730251327843849027957710164064354221 - 569898526380691606955496789378230959965898*I)/38685626227668133590597632], +# [ (27127513117071487872628354831658811211795 - 37765296987901990355760582016892124833857*I)/4835703278458516698824704, (1741779916057680444272938534338833170625435 + 3083041729779495966997526404685535449810378*I)/77371252455336267181195264, 3*(-60642236251815783728374561836962709533401 - 24630301165439580049891518846174101510744*I)/19342813113834066795298816, 3*(445885207364591681637745678755008757483408 - 350948497734812895032502179455610024541643*I)/38685626227668133590597632, (-47373295621391195484367368282471381775684 + 219122969294089357477027867028071400054973*I)/19342813113834066795298816, (-2801565819673198722993348253876353741520438 - 2250142129822658548391697042460298703335701*I)/77371252455336267181195264, (801448252275607253266997552356128790317119 - 50890367688077858227059515894356594900558*I)/77371252455336267181195264, (-5082187758525931944557763799137987573501207 + 11610432359082071866576699236013484487676124*I)/309485009821345068724781056, (-328925127096560623794883760398247685166830 - 643447969697471610060622160899409680422019*I)/77371252455336267181195264, 15*(2954944669454003684028194956846659916299765 + 33434406416888505837444969347824812608566*I)/1237940039285380274899124224, (-415749104352001509942256567958449835766827 + 479330966144175743357171151440020955412219*I)/77371252455336267181195264, 3*(-4639987285852134369449873547637372282914255 - 11994411888966030153196659207284951579243273*I)/1237940039285380274899124224], +# [ (-478846096206269117345024348666145495601 + 1249092488629201351470551186322814883283*I)/302231454903657293676544, (-17749319421930878799354766626365926894989 - 18264580106418628161818752318217357231971*I)/1208925819614629174706176, (2801110795431528876849623279389579072819 + 363258850073786330770713557775566973248*I)/604462909807314587353088, (-59053496693129013745775512127095650616252 + 78143588734197260279248498898321500167517*I)/4835703278458516698824704, (-283186724922498212468162690097101115349 - 6443437753863179883794497936345437398276*I)/1208925819614629174706176, (188799118826748909206887165661384998787543 + 84274736720556630026311383931055307398820*I)/9671406556917033397649408, (-5482217151670072904078758141270295025989 + 1818284338672191024475557065444481298568*I)/1208925819614629174706176, (56564463395350195513805521309731217952281 - 360208541416798112109946262159695452898431*I)/19342813113834066795298816, 11*(1259539805728870739006416869463689438068 + 1409136581547898074455004171305324917387*I)/4835703278458516698824704, 5*(-123701190701414554945251071190688818343325 + 30997157322590424677294553832111902279712*I)/38685626227668133590597632, (16130917381301373033736295883982414239781 - 32752041297570919727145380131926943374516*I)/9671406556917033397649408, (650301385108223834347093740500375498354925 + 899526407681131828596801223402866051809258*I)/77371252455336267181195264], +# [ (9011388245256140876590294262420614839483 + 8167917972423946282513000869327525382672*I)/1208925819614629174706176, (-426393174084720190126376382194036323028924 + 180692224825757525982858693158209545430621*I)/9671406556917033397649408, (24588556702197802674765733448108154175535 - 45091766022876486566421953254051868331066*I)/4835703278458516698824704, (1872113939365285277373877183750416985089691 + 3030392393733212574744122057679633775773130*I)/77371252455336267181195264, (-222173405538046189185754954524429864167549 - 75193157893478637039381059488387511299116*I)/19342813113834066795298816, (2670821320766222522963689317316937579844558 - 2645837121493554383087981511645435472169191*I)/77371252455336267181195264, 5*(-2100110309556476773796963197283876204940 + 41957457246479840487980315496957337371937*I)/19342813113834066795298816, (-5733743755499084165382383818991531258980593 - 3328949988392698205198574824396695027195732*I)/154742504910672534362390528, (707827994365259025461378911159398206329247 - 265730616623227695108042528694302299777294*I)/77371252455336267181195264, (-1442501604682933002895864804409322823788319 + 11504137805563265043376405214378288793343879*I)/309485009821345068724781056, (-56130472299445561499538726459719629522285 - 61117552419727805035810982426639329818864*I)/9671406556917033397649408, (39053692321126079849054272431599539429908717 - 10209127700342570953247177602860848130710666*I)/1237940039285380274899124224]]) + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512], + [ 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64], + [ -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128], + [ 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16], + [ 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M**10 == Matrix(S('''[ + [ 7369525394972778926719607798014571861/604462909807314587353088 - 229284202061790301477392339912557559*I/151115727451828646838272, -19704281515163975949388435612632058035/1208925819614629174706176 + 14319858347987648723768698170712102887*I/302231454903657293676544, -3623281909451783042932142262164941211/604462909807314587353088 - 6039240602494288615094338643452320495*I/604462909807314587353088, 109260497799140408739847239685705357695/2417851639229258349412352 - 7427566006564572463236368211555511431*I/2417851639229258349412352, -16095803767674394244695716092817006641/2417851639229258349412352 + 10336681897356760057393429626719177583*I/1208925819614629174706176, -42207883340488041844332828574359769743/2417851639229258349412352 - 182332262671671273188016400290188468499*I/4835703278458516698824704], + [50566491050825573392726324995779608259/1208925819614629174706176 - 90047007594468146222002432884052362145*I/2417851639229258349412352, 74273703462900000967697427843983822011/1208925819614629174706176 + 265947522682943571171988741842776095421*I/1208925819614629174706176, -116900341394390200556829767923360888429/2417851639229258349412352 - 53153263356679268823910621474478756845*I/2417851639229258349412352, 195407378023867871243426523048612490249/1208925819614629174706176 - 1242417915995360200584837585002906728929*I/9671406556917033397649408, -863597594389821970177319682495878193/302231454903657293676544 + 476936100741548328800725360758734300481*I/9671406556917033397649408, -3154451590535653853562472176601754835575/19342813113834066795298816 - 232909875490506237386836489998407329215*I/2417851639229258349412352], + [ -1715444997702484578716037230949868543/302231454903657293676544 + 5009695651321306866158517287924120777*I/302231454903657293676544, -30551582497996879620371947949342101301/604462909807314587353088 - 7632518367986526187139161303331519629*I/151115727451828646838272, 312680739924495153190604170938220575/18889465931478580854784 - 108664334509328818765959789219208459*I/75557863725914323419136, -14693696966703036206178521686918865509/604462909807314587353088 + 72345386220900843930147151999899692401*I/1208925819614629174706176, -8218872496728882299722894680635296519/1208925819614629174706176 - 16776782833358893712645864791807664983*I/1208925819614629174706176, 143237839169380078671242929143670635137/2417851639229258349412352 + 2883817094806115974748882735218469447*I/2417851639229258349412352], + [ 3087979417831061365023111800749855987/151115727451828646838272 + 34441942370802869368851419102423997089*I/604462909807314587353088, -148309181940158040917731426845476175667/604462909807314587353088 - 263987151804109387844966835369350904919*I/9671406556917033397649408, 50259518594816377378747711930008883165/1208925819614629174706176 - 95713974916869240305450001443767979653*I/2417851639229258349412352, 153466447023875527996457943521467271119/2417851639229258349412352 + 517285524891117105834922278517084871349*I/2417851639229258349412352, -29184653615412989036678939366291205575/604462909807314587353088 - 27551322282526322041080173287022121083*I/1208925819614629174706176, 196404220110085511863671393922447671649/1208925819614629174706176 - 1204712019400186021982272049902206202145*I/9671406556917033397649408], + [ -2632581805949645784625606590600098779/151115727451828646838272 - 589957435912868015140272627522612771*I/37778931862957161709568, 26727850893953715274702844733506310247/302231454903657293676544 - 10825791956782128799168209600694020481*I/302231454903657293676544, -1036348763702366164044671908440791295/151115727451828646838272 + 3188624571414467767868303105288107375*I/151115727451828646838272, -36814959939970644875593411585393242449/604462909807314587353088 - 18457555789119782404850043842902832647*I/302231454903657293676544, 12454491297984637815063964572803058647/604462909807314587353088 - 340489532842249733975074349495329171*I/302231454903657293676544, -19547211751145597258386735573258916681/604462909807314587353088 + 87299583775782199663414539883938008933*I/1208925819614629174706176], + [ -40281994229560039213253423262678393183/604462909807314587353088 - 2939986850065527327299273003299736641*I/604462909807314587353088, 331940684638052085845743020267462794181/2417851639229258349412352 - 284574901963624403933361315517248458969*I/1208925819614629174706176, 6453843623051745485064693628073010961/302231454903657293676544 + 36062454107479732681350914931391590957*I/604462909807314587353088, -147665869053634695632880753646441962067/604462909807314587353088 - 305987938660447291246597544085345123927*I/9671406556917033397649408, 107821369195275772166593879711259469423/2417851639229258349412352 - 11645185518211204108659001435013326687*I/302231454903657293676544, 64121228424717666402009446088588091619/1208925819614629174706176 + 265557133337095047883844369272389762133*I/1208925819614629174706176]]''')) + + +def test_issue_17247_expression_blowup_5(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.charpoly('x') == PurePoly(x**6 + (-6 - 6*I)*x**5 + 36*I*x**4, x, domain='EX') + + +def test_issue_17247_expression_blowup_6(): + M = Matrix(8, 8, [x+i for i in range (64)]) + with dotprodsimp(True): + assert M.det('bareiss') == 0 + + +def test_issue_17247_expression_blowup_7(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.det('berkowitz') == 0 + + +def test_issue_17247_expression_blowup_8(): + M = Matrix(8, 8, [x+i for i in range (64)]) + with dotprodsimp(True): + assert M.det('lu') == 0 + + +def test_issue_17247_expression_blowup_9(): + M = Matrix(8, 8, [x+i for i in range (64)]) + with dotprodsimp(True): + assert M.rref() == (Matrix([ + [1, 0, -1, -2, -3, -4, -5, -6], + [0, 1, 2, 3, 4, 5, 6, 7], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]]), (0, 1)) + + +def test_issue_17247_expression_blowup_10(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.cofactor(0, 0) == 0 + + +def test_issue_17247_expression_blowup_11(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.cofactor_matrix() == Matrix(6, 6, [0]*36) + + +def test_issue_17247_expression_blowup_12(): + M = Matrix(6, 6, lambda i, j: 1 + (-1)**(i+j)*I) + with dotprodsimp(True): + assert M.eigenvals() == {6: 1, 6*I: 1, 0: 4} + + +def test_issue_17247_expression_blowup_13(): + M = Matrix([ + [ 0, 1 - x, x + 1, 1 - x], + [1 - x, x + 1, 0, x + 1], + [ 0, 1 - x, x + 1, 1 - x], + [ 0, 0, 1 - x, 0]]) + + ev = M.eigenvects() + assert ev[0] == (0, 2, [Matrix([0, -1, 0, 1])]) + assert ev[1][0] == x - sqrt(2)*(x - 1) + 1 + assert ev[1][1] == 1 + assert ev[1][2][0].expand(deep=False, numer=True) == Matrix([ + [(-x + sqrt(2)*(x - 1) - 1)/(x - 1)], + [-4*x/(x**2 - 2*x + 1) + (x + 1)*(x - sqrt(2)*(x - 1) + 1)/(x**2 - 2*x + 1)], + [(-x + sqrt(2)*(x - 1) - 1)/(x - 1)], + [1] + ]) + + assert ev[2][0] == x + sqrt(2)*(x - 1) + 1 + assert ev[2][1] == 1 + assert ev[2][2][0].expand(deep=False, numer=True) == Matrix([ + [(-x - sqrt(2)*(x - 1) - 1)/(x - 1)], + [-4*x/(x**2 - 2*x + 1) + (x + 1)*(x + sqrt(2)*(x - 1) + 1)/(x**2 - 2*x + 1)], + [(-x - sqrt(2)*(x - 1) - 1)/(x - 1)], + [1] + ]) + + +def test_issue_17247_expression_blowup_14(): + M = Matrix(8, 8, ([1+x, 1-x]*4 + [1-x, 1+x]*4)*4) + with dotprodsimp(True): + assert M.echelon_form() == Matrix([ + [x + 1, 1 - x, x + 1, 1 - x, x + 1, 1 - x, x + 1, 1 - x], + [ 0, 4*x, 0, 4*x, 0, 4*x, 0, 4*x], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0]]) + + +def test_issue_17247_expression_blowup_15(): + M = Matrix(8, 8, ([1+x, 1-x]*4 + [1-x, 1+x]*4)*4) + with dotprodsimp(True): + assert M.rowspace() == [Matrix([[x + 1, 1 - x, x + 1, 1 - x, x + 1, 1 - x, x + 1, 1 - x]]), Matrix([[0, 4*x, 0, 4*x, 0, 4*x, 0, 4*x]])] + + +def test_issue_17247_expression_blowup_16(): + M = Matrix(8, 8, ([1+x, 1-x]*4 + [1-x, 1+x]*4)*4) + with dotprodsimp(True): + assert M.columnspace() == [Matrix([[x + 1],[1 - x],[x + 1],[1 - x],[x + 1],[1 - x],[x + 1],[1 - x]]), Matrix([[1 - x],[x + 1],[1 - x],[x + 1],[1 - x],[x + 1],[1 - x],[x + 1]])] + + +def test_issue_17247_expression_blowup_17(): + M = Matrix(8, 8, [x+i for i in range (64)]) + with dotprodsimp(True): + assert M.nullspace() == [ + Matrix([[1],[-2],[1],[0],[0],[0],[0],[0]]), + Matrix([[2],[-3],[0],[1],[0],[0],[0],[0]]), + Matrix([[3],[-4],[0],[0],[1],[0],[0],[0]]), + Matrix([[4],[-5],[0],[0],[0],[1],[0],[0]]), + Matrix([[5],[-6],[0],[0],[0],[0],[1],[0]]), + Matrix([[6],[-7],[0],[0],[0],[0],[0],[1]])] + + +def test_issue_17247_expression_blowup_18(): + M = Matrix(6, 6, ([1+x, 1-x]*3 + [1-x, 1+x]*3)*3) + with dotprodsimp(True): + assert not M.is_nilpotent() + + +def test_issue_17247_expression_blowup_19(): + M = Matrix(S('''[ + [ -3/4, 0, 1/4 + I/2, 0], + [ 0, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 1/2 - I, 0, 0, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert not M.is_diagonalizable() + + +def test_issue_17247_expression_blowup_20(): + M = Matrix([ + [x + 1, 1 - x, 0, 0], + [1 - x, x + 1, 0, x + 1], + [ 0, 1 - x, x + 1, 0], + [ 0, 0, 0, x + 1]]) + with dotprodsimp(True): + assert M.diagonalize() == (Matrix([ + [1, 1, 0, (x + 1)/(x - 1)], + [1, -1, 0, 0], + [1, 1, 1, 0], + [0, 0, 0, 1]]), + Matrix([ + [2, 0, 0, 0], + [0, 2*x, 0, 0], + [0, 0, x + 1, 0], + [0, 0, 0, x + 1]])) + + +def test_issue_17247_expression_blowup_21(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='GE') == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + + +def test_issue_17247_expression_blowup_22(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='LU') == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + + +def test_issue_17247_expression_blowup_23(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='ADJ').expand() == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + + +def test_issue_17247_expression_blowup_24(): + M = SparseMatrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='CH') == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + + +def test_issue_17247_expression_blowup_25(): + M = SparseMatrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.inv(method='LDL') == Matrix(S('''[ + [-26194832/3470993 - 31733264*I/3470993, 156352/3470993 + 10325632*I/3470993, 0, -7741283181072/3306971225785 + 2999007604624*I/3306971225785], + [4408224/3470993 - 9675328*I/3470993, -2422272/3470993 + 1523712*I/3470993, 0, -1824666489984/3306971225785 - 1401091949952*I/3306971225785], + [-26406945676288/22270005630769 + 10245925485056*I/22270005630769, 7453523312640/22270005630769 + 1601616519168*I/22270005630769, 633088/6416033 - 140288*I/6416033, 872209227109521408/21217636514687010905 + 6066405081802389504*I/21217636514687010905], + [0, 0, 0, -11328/952745 + 87616*I/952745]]''')) + + +def test_issue_17247_expression_blowup_26(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64, -9/32 - I/16, 183/256 - 97*I/128], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512, -219/128 + 115*I/256, 6301/4096 - 6609*I/1024], + [ 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64, 1/4 - 5*I/16, 65/128 + 87*I/64], + [ -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128, 85/256 - 33*I/16, 805/128 + 2415*I/512], + [ 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16, 1/4 + I/2, -129/64 - 9*I/64], + [ 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128, 125/64 + 87*I/64, -2063/256 + 541*I/128], + [ -2, 17/4 - 13*I/2, 1 + I, -19/4 + 5*I/4, 1/2 - I, 9/4 + 55*I/16, -3/4, 45/32 - 37*I/16], + [ 1/4 + 13*I/4, -825/64 - 147*I/32, 21/8 + I, -537/64 + 143*I/16, -5/8 - 39*I/16, 2473/256 + 137*I/64, -149/64 + 49*I/32, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.rank() == 4 + + +def test_issue_17247_expression_blowup_27(): + M = Matrix([ + [ 0, 1 - x, x + 1, 1 - x], + [1 - x, x + 1, 0, x + 1], + [ 0, 1 - x, x + 1, 1 - x], + [ 0, 0, 1 - x, 0]]) + with dotprodsimp(True): + P, J = M.jordan_form() + assert P.expand() == Matrix(S('''[ + [ 0, 4*x/(x**2 - 2*x + 1), -(-17*x**4 + 12*sqrt(2)*x**4 - 4*sqrt(2)*x**3 + 6*x**3 - 6*x - 4*sqrt(2)*x + 12*sqrt(2) + 17)/(-7*x**4 + 5*sqrt(2)*x**4 - 6*sqrt(2)*x**3 + 8*x**3 - 2*x**2 + 8*x + 6*sqrt(2)*x - 5*sqrt(2) - 7), -(12*sqrt(2)*x**4 + 17*x**4 - 6*x**3 - 4*sqrt(2)*x**3 - 4*sqrt(2)*x + 6*x - 17 + 12*sqrt(2))/(7*x**4 + 5*sqrt(2)*x**4 - 6*sqrt(2)*x**3 - 8*x**3 + 2*x**2 - 8*x + 6*sqrt(2)*x - 5*sqrt(2) + 7)], + [x - 1, x/(x - 1) + 1/(x - 1), (-7*x**3 + 5*sqrt(2)*x**3 - x**2 + sqrt(2)*x**2 - sqrt(2)*x - x - 5*sqrt(2) - 7)/(-3*x**3 + 2*sqrt(2)*x**3 - 2*sqrt(2)*x**2 + 3*x**2 + 2*sqrt(2)*x + 3*x - 3 - 2*sqrt(2)), (7*x**3 + 5*sqrt(2)*x**3 + x**2 + sqrt(2)*x**2 - sqrt(2)*x + x - 5*sqrt(2) + 7)/(2*sqrt(2)*x**3 + 3*x**3 - 3*x**2 - 2*sqrt(2)*x**2 - 3*x + 2*sqrt(2)*x - 2*sqrt(2) + 3)], + [ 0, 1, -(-3*x**2 + 2*sqrt(2)*x**2 + 2*x - 3 - 2*sqrt(2))/(-x**2 + sqrt(2)*x**2 - 2*sqrt(2)*x + 1 + sqrt(2)), -(2*sqrt(2)*x**2 + 3*x**2 - 2*x - 2*sqrt(2) + 3)/(x**2 + sqrt(2)*x**2 - 2*sqrt(2)*x - 1 + sqrt(2))], + [1 - x, 0, 1, 1]]''')).expand() + assert J == Matrix(S('''[ + [0, 1, 0, 0], + [0, 0, 0, 0], + [0, 0, x - sqrt(2)*(x - 1) + 1, 0], + [0, 0, 0, x + sqrt(2)*(x - 1) + 1]]''')) + + +def test_issue_17247_expression_blowup_28(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.singular_values() == S('''[ + sqrt(14609315/131072 + sqrt(64789115132571/2147483648 - 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3) + 76627253330829751075/(35184372088832*sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))) - 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)))/2 + sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))/2), + sqrt(14609315/131072 - sqrt(64789115132571/2147483648 - 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3) + 76627253330829751075/(35184372088832*sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))) - 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)))/2 + sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))/2), + sqrt(14609315/131072 - sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))/2 + sqrt(64789115132571/2147483648 - 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3) - 76627253330829751075/(35184372088832*sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))) - 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)))/2), + sqrt(14609315/131072 - sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))/2 - sqrt(64789115132571/2147483648 - 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3) - 76627253330829751075/(35184372088832*sqrt(64789115132571/4294967296 + 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)) + 2*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3))) - 3546944054712886603889144627/(110680464442257309696*(25895222463957462655758224991455280215303/633825300114114700748351602688 + sqrt(1213909058710955930446995195883114969038524625997915131236390724543989220134670)*I/22282920707136844948184236032)**(1/3)))/2)]''') + + +def test_issue_16823(): + # This still needs to be fixed if not using dotprodsimp. + M = Matrix(S('''[ + [1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I,1/4-5/16*I,65/128+87/64*I,-9/32-1/16*I,183/256-97/128*I,3/64+13/64*I,-23/32-59/256*I,15/128-3/32*I,19/256+551/1024*I], + [21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I,85/256-33/16*I,805/128+2415/512*I,-219/128+115/256*I,6301/4096-6609/1024*I,119/128+143/128*I,-10879/2048+4343/4096*I,129/256-549/512*I,42533/16384+29103/8192*I], + [-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I,1/4-5/16*I,65/128+87/64*I,-9/32-1/16*I,183/256-97/128*I,3/64+13/64*I,-23/32-59/256*I], + [1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I,85/256-33/16*I,805/128+2415/512*I,-219/128+115/256*I,6301/4096-6609/1024*I,119/128+143/128*I,-10879/2048+4343/4096*I], + [-4*I,27/2+6*I,-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I,1/4-5/16*I,65/128+87/64*I,-9/32-1/16*I,183/256-97/128*I], + [1/4+5/2*I,-23/8-57/16*I,1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I,85/256-33/16*I,805/128+2415/512*I,-219/128+115/256*I,6301/4096-6609/1024*I], + [-4,9-5*I,-4*I,27/2+6*I,-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I,1/4-5/16*I,65/128+87/64*I], + [-2*I,119/8+29/4*I,1/4+5/2*I,-23/8-57/16*I,1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I,85/256-33/16*I,805/128+2415/512*I], + [0,-6,-4,9-5*I,-4*I,27/2+6*I,-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I,1/4+1/2*I,-129/64-9/64*I], + [1,-9/4+3*I,-2*I,119/8+29/4*I,1/4+5/2*I,-23/8-57/16*I,1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I,125/64+87/64*I,-2063/256+541/128*I], + [0,-4*I,0,-6,-4,9-5*I,-4*I,27/2+6*I,-2,17/4-13/2*I,1+I,-19/4+5/4*I,1/2-I,9/4+55/16*I,-3/4,45/32-37/16*I], + [0,1/4+1/2*I,1,-9/4+3*I,-2*I,119/8+29/4*I,1/4+5/2*I,-23/8-57/16*I,1/4+13/4*I,-825/64-147/32*I,21/8+I,-537/64+143/16*I,-5/8-39/16*I,2473/256+137/64*I,-149/64+49/32*I,-177/128-1369/128*I]]''')) + with dotprodsimp(True): + assert M.rank() == 8 + + +def test_issue_18531(): + # solve_linear_system still needs fixing but the rref works. + M = Matrix([ + [1, 1, 1, 1, 1, 0, 1, 0, 0], + [1 + sqrt(2), -1 + sqrt(2), 1 - sqrt(2), -sqrt(2) - 1, 1, 1, -1, 1, 1], + [-5 + 2*sqrt(2), -5 - 2*sqrt(2), -5 - 2*sqrt(2), -5 + 2*sqrt(2), -7, 2, -7, -2, 0], + [-3*sqrt(2) - 1, 1 - 3*sqrt(2), -1 + 3*sqrt(2), 1 + 3*sqrt(2), -7, -5, 7, -5, 3], + [7 - 4*sqrt(2), 4*sqrt(2) + 7, 4*sqrt(2) + 7, 7 - 4*sqrt(2), 7, -12, 7, 12, 0], + [-1 + 3*sqrt(2), 1 + 3*sqrt(2), -3*sqrt(2) - 1, 1 - 3*sqrt(2), 7, -5, -7, -5, 3], + [-3 + 2*sqrt(2), -3 - 2*sqrt(2), -3 - 2*sqrt(2), -3 + 2*sqrt(2), -1, 2, -1, -2, 0], + [1 - sqrt(2), -sqrt(2) - 1, 1 + sqrt(2), -1 + sqrt(2), -1, 1, 1, 1, 1] + ]) + with dotprodsimp(True): + assert M.rref() == (Matrix([ + [1, 0, 0, 0, 0, 0, 0, 0, S(1)/2], + [0, 1, 0, 0, 0, 0, 0, 0, -S(1)/2], + [0, 0, 1, 0, 0, 0, 0, 0, S(1)/2], + [0, 0, 0, 1, 0, 0, 0, 0, -S(1)/2], + [0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, -S(1)/2], + [0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, -S(1)/2]]), (0, 1, 2, 3, 4, 5, 6, 7)) + + +def test_creation(): + raises(ValueError, lambda: Matrix(5, 5, range(20))) + raises(ValueError, lambda: Matrix(5, -1, [])) + raises(IndexError, lambda: Matrix((1, 2))[2]) + with raises(IndexError): + Matrix((1, 2))[3] = 5 + + assert Matrix() == Matrix([]) == Matrix(0, 0, []) + assert Matrix([[]]) == Matrix(1, 0, []) + assert Matrix([[], []]) == Matrix(2, 0, []) + + # anything used to be allowed in a matrix + with warns_deprecated_sympy(): + assert Matrix([[[1], (2,)]]).tolist() == [[[1], (2,)]] + with warns_deprecated_sympy(): + assert Matrix([[[1], (2,)]]).T.tolist() == [[[1]], [(2,)]] + M = Matrix([[0]]) + with warns_deprecated_sympy(): + M[0, 0] = S.EmptySet + + a = Matrix([[x, 0], [0, 0]]) + m = a + assert m.cols == m.rows + assert m.cols == 2 + assert m[:] == [x, 0, 0, 0] + + b = Matrix(2, 2, [x, 0, 0, 0]) + m = b + assert m.cols == m.rows + assert m.cols == 2 + assert m[:] == [x, 0, 0, 0] + + assert a == b + + assert Matrix(b) == b + + c23 = Matrix(2, 3, range(1, 7)) + c13 = Matrix(1, 3, range(7, 10)) + c = Matrix([c23, c13]) + assert c.cols == 3 + assert c.rows == 3 + assert c[:] == [1, 2, 3, 4, 5, 6, 7, 8, 9] + + assert Matrix(eye(2)) == eye(2) + assert ImmutableMatrix(ImmutableMatrix(eye(2))) == ImmutableMatrix(eye(2)) + assert ImmutableMatrix(c) == c.as_immutable() + assert Matrix(ImmutableMatrix(c)) == ImmutableMatrix(c).as_mutable() + + assert c is not Matrix(c) + + dat = [[ones(3,2), ones(3,3)*2], [ones(2,3)*3, ones(2,2)*4]] + M = Matrix(dat) + assert M == Matrix([ + [1, 1, 2, 2, 2], + [1, 1, 2, 2, 2], + [1, 1, 2, 2, 2], + [3, 3, 3, 4, 4], + [3, 3, 3, 4, 4]]) + assert M.tolist() != dat + # keep block form if evaluate=False + assert Matrix(dat, evaluate=False).tolist() == dat + A = MatrixSymbol("A", 2, 2) + dat = [ones(2), A] + assert Matrix(dat) == Matrix([ + [ 1, 1], + [ 1, 1], + [A[0, 0], A[0, 1]], + [A[1, 0], A[1, 1]]]) + with warns_deprecated_sympy(): + assert Matrix(dat, evaluate=False).tolist() == [[i] for i in dat] + + # 0-dim tolerance + assert Matrix([ones(2), ones(0)]) == Matrix([ones(2)]) + raises(ValueError, lambda: Matrix([ones(2), ones(0, 3)])) + raises(ValueError, lambda: Matrix([ones(2), ones(3, 0)])) + + # mix of Matrix and iterable + M = Matrix([[1, 2], [3, 4]]) + M2 = Matrix([M, (5, 6)]) + assert M2 == Matrix([[1, 2], [3, 4], [5, 6]]) + + +def test_irregular_block(): + assert Matrix.irregular(3, ones(2,1), ones(3,3)*2, ones(2,2)*3, + ones(1,1)*4, ones(2,2)*5, ones(1,2)*6, ones(1,2)*7) == Matrix([ + [1, 2, 2, 2, 3, 3], + [1, 2, 2, 2, 3, 3], + [4, 2, 2, 2, 5, 5], + [6, 6, 7, 7, 5, 5]]) + + +def test_slicing(): + m0 = eye(4) + assert m0[:3, :3] == eye(3) + assert m0[2:4, 0:2] == zeros(2) + + m1 = Matrix(3, 3, lambda i, j: i + j) + assert m1[0, :] == Matrix(1, 3, (0, 1, 2)) + assert m1[1:3, 1] == Matrix(2, 1, (2, 3)) + + m2 = Matrix([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]) + assert m2[:, -1] == Matrix(4, 1, [3, 7, 11, 15]) + assert m2[-2:, :] == Matrix([[8, 9, 10, 11], [12, 13, 14, 15]]) + + +def test_submatrix_assignment(): + m = zeros(4) + m[2:4, 2:4] = eye(2) + assert m == Matrix(((0, 0, 0, 0), + (0, 0, 0, 0), + (0, 0, 1, 0), + (0, 0, 0, 1))) + m[:2, :2] = eye(2) + assert m == eye(4) + m[:, 0] = Matrix(4, 1, (1, 2, 3, 4)) + assert m == Matrix(((1, 0, 0, 0), + (2, 1, 0, 0), + (3, 0, 1, 0), + (4, 0, 0, 1))) + m[:, :] = zeros(4) + assert m == zeros(4) + m[:, :] = [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12), (13, 14, 15, 16)] + assert m == Matrix(((1, 2, 3, 4), + (5, 6, 7, 8), + (9, 10, 11, 12), + (13, 14, 15, 16))) + m[:2, 0] = [0, 0] + assert m == Matrix(((0, 2, 3, 4), + (0, 6, 7, 8), + (9, 10, 11, 12), + (13, 14, 15, 16))) + + +def test_reshape(): + m0 = eye(3) + assert m0.reshape(1, 9) == Matrix(1, 9, (1, 0, 0, 0, 1, 0, 0, 0, 1)) + m1 = Matrix(3, 4, lambda i, j: i + j) + assert m1.reshape( + 4, 3) == Matrix(((0, 1, 2), (3, 1, 2), (3, 4, 2), (3, 4, 5))) + assert m1.reshape(2, 6) == Matrix(((0, 1, 2, 3, 1, 2), (3, 4, 2, 3, 4, 5))) + + +def test_applyfunc(): + m0 = eye(3) + assert m0.applyfunc(lambda x: 2*x) == eye(3)*2 + assert m0.applyfunc(lambda x: 0) == zeros(3) + + +def test_expand(): + m0 = Matrix([[x*(x + y), 2], [((x + y)*y)*x, x*(y + x*(x + y))]]) + # Test if expand() returns a matrix + m1 = m0.expand() + assert m1 == Matrix( + [[x*y + x**2, 2], [x*y**2 + y*x**2, x*y + y*x**2 + x**3]]) + + a = Symbol('a', real=True) + + assert Matrix([exp(I*a)]).expand(complex=True) == \ + Matrix([cos(a) + I*sin(a)]) + + assert Matrix([[0, 1, 2], [0, 0, -1], [0, 0, 0]]).exp() == Matrix([ + [1, 1, Rational(3, 2)], + [0, 1, -1], + [0, 0, 1]] + ) + + +def test_refine(): + m0 = Matrix([[Abs(x)**2, sqrt(x**2)], + [sqrt(x**2)*Abs(y)**2, sqrt(y**2)*Abs(x)**2]]) + m1 = m0.refine(Q.real(x) & Q.real(y)) + assert m1 == Matrix([[x**2, Abs(x)], [y**2*Abs(x), x**2*Abs(y)]]) + + m1 = m0.refine(Q.positive(x) & Q.positive(y)) + assert m1 == Matrix([[x**2, x], [x*y**2, x**2*y]]) + + m1 = m0.refine(Q.negative(x) & Q.negative(y)) + assert m1 == Matrix([[x**2, -x], [-x*y**2, -x**2*y]]) + + +def test_random(): + M = randMatrix(3, 3) + M = randMatrix(3, 3, seed=3) + assert M == randMatrix(3, 3, seed=3) + + M = randMatrix(3, 4, 0, 150) + M = randMatrix(3, seed=4, symmetric=True) + assert M == randMatrix(3, seed=4, symmetric=True) + + S = M.copy() + S.simplify() + assert S == M # doesn't fail when elements are Numbers, not int + + rng = random.Random(4) + assert M == randMatrix(3, symmetric=True, prng=rng) + + # Ensure symmetry + for size in (10, 11): # Test odd and even + for percent in (100, 70, 30): + M = randMatrix(size, symmetric=True, percent=percent, prng=rng) + assert M == M.T + + M = randMatrix(10, min=1, percent=70) + zero_count = 0 + for i in range(M.shape[0]): + for j in range(M.shape[1]): + if M[i, j] == 0: + zero_count += 1 + assert zero_count == 30 + + +def test_inverse(): + A = eye(4) + assert A.inv() == eye(4) + assert A.inv(method="LU") == eye(4) + assert A.inv(method="ADJ") == eye(4) + assert A.inv(method="CH") == eye(4) + assert A.inv(method="LDL") == eye(4) + assert A.inv(method="QR") == eye(4) + A = Matrix([[2, 3, 5], + [3, 6, 2], + [8, 3, 6]]) + Ainv = A.inv() + assert A*Ainv == eye(3) + assert A.inv(method="LU") == Ainv + assert A.inv(method="ADJ") == Ainv + assert A.inv(method="CH") == Ainv + assert A.inv(method="LDL") == Ainv + assert A.inv(method="QR") == Ainv + + AA = Matrix([[0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0], + [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1], + [1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0], + [1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0], + [1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1], + [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1], + [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0], + [1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0], + [0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0], + [0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1], + [0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1], + [0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0]]) + assert AA.inv(method="BLOCK") * AA == eye(AA.shape[0]) + # test that immutability is not a problem + cls = ImmutableMatrix + m = cls([[48, 49, 31], + [ 9, 71, 94], + [59, 28, 65]]) + assert all(type(m.inv(s)) is cls for s in 'GE ADJ LU CH LDL QR'.split()) + cls = ImmutableSparseMatrix + m = cls([[48, 49, 31], + [ 9, 71, 94], + [59, 28, 65]]) + assert all(type(m.inv(s)) is cls for s in 'GE ADJ LU CH LDL QR'.split()) + + +def test_inverse_symbolic_float_issue_26821(): + Tau, Tau_syn_in, Tau_syn_ex, C_m, Tau_syn_gap = symbols("Tau Tau_syn_in Tau_syn_ex C_m Tau_syn_gap") + __h = symbols("__h") + + M = Matrix([ + [0,0,0,0,0,(1.0*Tau*__h-1.0*Tau_syn_in*__h)/(2.0*Tau-1.0*Tau_syn_in),-1.0*Tau*Tau_syn_in/(2.0*Tau-1.0*Tau_syn_in)], + [0,0,0,0,0,(-1.0*Tau*__h+1.0*Tau_syn_in*__h)/(2.0*Tau*Tau_syn_in-1.0*Tau_syn_in**2),1.0], + [0,(1.0*Tau*__h-1.0*Tau_syn_ex*__h)/(2.0*Tau-1.0*Tau_syn_ex),-1.0*Tau*Tau_syn_ex/(2.0*Tau-1.0*Tau_syn_ex),0,0,0,0], + [0,(-1.0*Tau*__h+1.0*Tau_syn_ex*__h)/(2.0*Tau*Tau_syn_ex-1.0*Tau_syn_ex**2),1.0,0,0,0,0], + [0,0,0,(1.0*Tau*__h-1.0*Tau_syn_gap*__h)/(2.0*Tau-1.0*Tau_syn_gap),-1.0*Tau*Tau_syn_gap/(2.0*Tau-1.0*Tau_syn_gap),0,0], + [0,0,0,(-1.0*Tau*__h+1.0*Tau_syn_gap*__h)/(2.0*Tau*Tau_syn_gap-1.0*Tau_syn_gap**2),1.0,0,0], + [1.0,-1.0*Tau*Tau_syn_ex*__h/(2.0*C_m*Tau-1.0*C_m*Tau_syn_ex),0,-1.0*Tau*Tau_syn_gap*__h/(2.0*C_m*Tau-1.0*C_m*Tau_syn_gap),0,-1.0*Tau*Tau_syn_in*__h/(2.0*C_m*Tau-1.0*C_m*Tau_syn_in),0] + ]) + + Mi = M.inv() + + assert (M*Mi - eye(7)).applyfunc(cancel) == zeros(7) + + # https://github.com/sympy/sympy/issues/26821 + # Previously very large floats were in the result. + assert max(abs(f) for f in Mi.atoms(Float)) < 1e3 + + +@slow +def test_matrix_exponential_issue_26821(): + # The symbol names matter in the original bug... + a, b, c, d, e = symbols("Tau, Tau_syn_in, Tau_syn_ex, C_m, Tau_syn_gap") + t = symbols("__h") + M = Matrix([ + [ 0, 1.0, 0, 0, 0, 0, 0], + [-1/b**2, -2/b, 0, 0, 0, 0, 0], + [ 0, 0, 0, 1.0, 0, 0, 0], + [ 0, 0, -1/c**2, -2/c, 0, 0, 0], + [ 0, 0, 0, 0, 0, 1, 0], + [ 0, 0, 0, 0, -1/e**2, -2/e, 0], + [ 1/d, 0, 1/d, 0, 1/d, 0, -1/a] + ]) + + Me = (t*M).exp() + assert (Me.diff(t) - M*Me).applyfunc(cancel) == zeros(7) + # https://github.com/sympy/sympy/issues/26821 + # Previously very large floats were in the result. + assert max(abs(f) for f in Me.atoms(Float)) < 1e3 + + +def test_jacobian_hessian(): + L = Matrix(1, 2, [x**2*y, 2*y**2 + x*y]) + syms = [x, y] + assert L.jacobian(syms) == Matrix([[2*x*y, x**2], [y, 4*y + x]]) + + L = Matrix(1, 2, [x, x**2*y**3]) + assert L.jacobian(syms) == Matrix([[1, 0], [2*x*y**3, x**2*3*y**2]]) + + f = x**2*y + syms = [x, y] + assert hessian(f, syms) == Matrix([[2*y, 2*x], [2*x, 0]]) + + f = x**2*y**3 + assert hessian(f, syms) == \ + Matrix([[2*y**3, 6*x*y**2], [6*x*y**2, 6*x**2*y]]) + + f = z + x*y**2 + g = x**2 + 2*y**3 + ans = Matrix([[0, 2*y], + [2*y, 2*x]]) + assert ans == hessian(f, Matrix([x, y])) + assert ans == hessian(f, Matrix([x, y]).T) + assert hessian(f, (y, x), [g]) == Matrix([ + [ 0, 6*y**2, 2*x], + [6*y**2, 2*x, 2*y], + [ 2*x, 2*y, 0]]) + + +def test_wronskian(): + assert wronskian([cos(x), sin(x)], x) == cos(x)**2 + sin(x)**2 + assert wronskian([exp(x), exp(2*x)], x) == exp(3*x) + assert wronskian([exp(x), x], x) == exp(x) - x*exp(x) + assert wronskian([1, x, x**2], x) == 2 + w1 = -6*exp(x)*sin(x)*x + 6*cos(x)*exp(x)*x**2 - 6*exp(x)*cos(x)*x - \ + exp(x)*cos(x)*x**3 + exp(x)*sin(x)*x**3 + assert wronskian([exp(x), cos(x), x**3], x).expand() == w1 + assert wronskian([exp(x), cos(x), x**3], x, method='berkowitz').expand() \ + == w1 + w2 = -x**3*cos(x)**2 - x**3*sin(x)**2 - 6*x*cos(x)**2 - 6*x*sin(x)**2 + assert wronskian([sin(x), cos(x), x**3], x).expand() == w2 + assert wronskian([sin(x), cos(x), x**3], x, method='berkowitz').expand() \ + == w2 + assert wronskian([], x) == 1 + + +def test_xreplace(): + assert Matrix([[1, x], [x, 4]]).xreplace({x: 5}) == \ + Matrix([[1, 5], [5, 4]]) + assert Matrix([[x, 2], [x + y, 4]]).xreplace({x: -1, y: -2}) == \ + Matrix([[-1, 2], [-3, 4]]) + for cls in all_classes: + assert Matrix([[2, 0], [0, 2]]) == cls.eye(2).xreplace({1: 2}) + + +def test_simplify(): + n = Symbol('n') + f = Function('f') + + M = Matrix([[ 1/x + 1/y, (x + x*y) / x ], + [ (f(x) + y*f(x))/f(x), 2 * (1/n - cos(n * pi)/n) / pi ]]) + M.simplify() + assert M == Matrix([[ (x + y)/(x * y), 1 + y ], + [ 1 + y, 2*((1 - 1*cos(pi*n))/(pi*n)) ]]) + eq = (1 + x)**2 + M = Matrix([[eq]]) + M.simplify() + assert M == Matrix([[eq]]) + M.simplify(ratio=oo) + assert M == Matrix([[eq.simplify(ratio=oo)]]) + + n = Symbol('n') + f = Function('f') + + M = ImmutableMatrix([ + [ 1/x + 1/y, (x + x*y) / x ], + [ (f(x) + y*f(x))/f(x), 2 * (1/n - cos(n * pi)/n) / pi ] + ]) + assert M.simplify() == Matrix([ + [ (x + y)/(x * y), 1 + y ], + [ 1 + y, 2*((1 - 1*cos(pi*n))/(pi*n)) ] + ]) + + eq = (1 + x)**2 + M = ImmutableMatrix([[eq]]) + assert M.simplify() == Matrix([[eq]]) + assert M.simplify(ratio=oo) == Matrix([[eq.simplify(ratio=oo)]]) + + assert simplify(ImmutableMatrix([[sin(x)**2 + cos(x)**2]])) == \ + ImmutableMatrix([[1]]) + + # https://github.com/sympy/sympy/issues/19353 + m = Matrix([[30, 2], [3, 4]]) + assert (1/(m.trace())).simplify() == Rational(1, 34) + +def test_transpose(): + M = Matrix([[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]]) + assert M.T == Matrix( [ [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6], + [7, 7], + [8, 8], + [9, 9], + [0, 0] ]) + assert M.T.T == M + assert M.T == M.transpose() + + +def test_conj_dirac(): + raises(AttributeError, lambda: eye(3).D) + + M = Matrix([[1, I, I, I], + [0, 1, I, I], + [0, 0, 1, I], + [0, 0, 0, 1]]) + + assert M.D == Matrix([[ 1, 0, 0, 0], + [-I, 1, 0, 0], + [-I, -I, -1, 0], + [-I, -I, I, -1]]) + + +def test_trace(): + M = Matrix([[1, 0, 0], + [0, 5, 0], + [0, 0, 8]]) + assert M.trace() == 14 + + +def test_shape(): + m = Matrix(1, 2, [0, 0]) + assert m.shape == (1, 2) + M = Matrix([[x, 0, 0], + [0, y, 0]]) + assert M.shape == (2, 3) + + +def test_col_row_op(): + M = Matrix([[x, 0, 0], + [0, y, 0]]) + M.row_op(1, lambda r, j: r + j + 1) + assert M == Matrix([[x, 0, 0], + [1, y + 2, 3]]) + + M.col_op(0, lambda c, j: c + y**j) + assert M == Matrix([[x + 1, 0, 0], + [1 + y, y + 2, 3]]) + + # neither row nor slice give copies that allow the original matrix to + # be changed + assert M.row(0) == Matrix([[x + 1, 0, 0]]) + r1 = M.row(0) + r1[0] = 42 + assert M[0, 0] == x + 1 + r1 = M[0, :-1] # also testing negative slice + r1[0] = 42 + assert M[0, 0] == x + 1 + c1 = M.col(0) + assert c1 == Matrix([x + 1, 1 + y]) + c1[0] = 0 + assert M[0, 0] == x + 1 + c1 = M[:, 0] + c1[0] = 42 + assert M[0, 0] == x + 1 + + +def test_row_mult(): + M = Matrix([[1,2,3], + [4,5,6]]) + M.row_mult(1,3) + assert M[1,0] == 12 + assert M[0,0] == 1 + assert M[1,2] == 18 + + +def test_row_add(): + M = Matrix([[1,2,3], + [4,5,6], + [1,1,1]]) + M.row_add(2,0,5) + assert M[0,0] == 6 + assert M[1,0] == 4 + assert M[0,2] == 8 + + +def test_zip_row_op(): + for cls in mutable_classes: # XXX: immutable matrices don't support row ops + M = cls.eye(3) + M.zip_row_op(1, 0, lambda v, u: v + 2*u) + assert M == cls([[1, 0, 0], + [2, 1, 0], + [0, 0, 1]]) + + M = cls.eye(3)*2 + M[0, 1] = -1 + M.zip_row_op(1, 0, lambda v, u: v + 2*u); M + assert M == cls([[2, -1, 0], + [4, 0, 0], + [0, 0, 2]]) + + +def test_issue_3950(): + m = Matrix([1, 2, 3]) + a = Matrix([1, 2, 3]) + b = Matrix([2, 2, 3]) + assert not (m in []) + assert not (m in [1]) + assert m != 1 + assert m == a + assert m != b + + +def test_issue_3981(): + class Index1: + def __index__(self): + return 1 + + class Index2: + def __index__(self): + return 2 + index1 = Index1() + index2 = Index2() + + m = Matrix([1, 2, 3]) + + assert m[index2] == 3 + + m[index2] = 5 + assert m[2] == 5 + + m = Matrix([[1, 2, 3], [4, 5, 6]]) + assert m[index1, index2] == 6 + assert m[1, index2] == 6 + assert m[index1, 2] == 6 + + m[index1, index2] = 4 + assert m[1, 2] == 4 + m[1, index2] = 6 + assert m[1, 2] == 6 + m[index1, 2] = 8 + assert m[1, 2] == 8 + + +def test_is_upper(): + a = Matrix([[1, 2, 3]]) + assert a.is_upper is True + a = Matrix([[1], [2], [3]]) + assert a.is_upper is False + a = zeros(4, 2) + assert a.is_upper is True + + +def test_is_lower(): + a = Matrix([[1, 2, 3]]) + assert a.is_lower is False + a = Matrix([[1], [2], [3]]) + assert a.is_lower is True + + +def test_is_nilpotent(): + a = Matrix(4, 4, [0, 2, 1, 6, 0, 0, 1, 2, 0, 0, 0, 3, 0, 0, 0, 0]) + assert a.is_nilpotent() + a = Matrix([[1, 0], [0, 1]]) + assert not a.is_nilpotent() + a = Matrix([]) + assert a.is_nilpotent() + + +def test_zeros_ones_fill(): + n, m = 3, 5 + + a = zeros(n, m) + a.fill( 5 ) + + b = 5 * ones(n, m) + + assert a == b + assert a.rows == b.rows == 3 + assert a.cols == b.cols == 5 + assert a.shape == b.shape == (3, 5) + assert zeros(2) == zeros(2, 2) + assert ones(2) == ones(2, 2) + assert zeros(2, 3) == Matrix(2, 3, [0]*6) + assert ones(2, 3) == Matrix(2, 3, [1]*6) + + a.fill(0) + assert a == zeros(n, m) + + +def test_empty_zeros(): + a = zeros(0) + assert a == Matrix() + a = zeros(0, 2) + assert a.rows == 0 + assert a.cols == 2 + a = zeros(2, 0) + assert a.rows == 2 + assert a.cols == 0 + + +def test_issue_3749(): + a = Matrix([[x**2, x*y], [x*sin(y), x*cos(y)]]) + assert a.diff(x) == Matrix([[2*x, y], [sin(y), cos(y)]]) + assert Matrix([ + [x, -x, x**2], + [exp(x), 1/x - exp(-x), x + 1/x]]).limit(x, oo) == \ + Matrix([[oo, -oo, oo], [oo, 0, oo]]) + assert Matrix([ + [(exp(x) - 1)/x, 2*x + y*x, x**x ], + [1/x, abs(x), abs(sin(x + 1))]]).limit(x, 0) == \ + Matrix([[1, 0, 1], [oo, 0, sin(1)]]) + assert a.integrate(x) == Matrix([ + [Rational(1, 3)*x**3, y*x**2/2], + [x**2*sin(y)/2, x**2*cos(y)/2]]) + + +def test_inv_iszerofunc(): + A = eye(4) + A.col_swap(0, 1) + for method in "GE", "LU": + assert A.inv(method=method, iszerofunc=lambda x: x == 0) == \ + A.inv(method="ADJ") + + +def test_jacobian_metrics(): + rho, phi = symbols("rho,phi") + X = Matrix([rho*cos(phi), rho*sin(phi)]) + Y = Matrix([rho, phi]) + J = X.jacobian(Y) + assert J == X.jacobian(Y.T) + assert J == (X.T).jacobian(Y) + assert J == (X.T).jacobian(Y.T) + g = J.T*eye(J.shape[0])*J + g = g.applyfunc(trigsimp) + assert g == Matrix([[1, 0], [0, rho**2]]) + + +def test_jacobian2(): + rho, phi = symbols("rho,phi") + X = Matrix([rho*cos(phi), rho*sin(phi), rho**2]) + Y = Matrix([rho, phi]) + J = Matrix([ + [cos(phi), -rho*sin(phi)], + [sin(phi), rho*cos(phi)], + [ 2*rho, 0], + ]) + assert X.jacobian(Y) == J + + +def test_issue_4564(): + X = Matrix([exp(x + y + z), exp(x + y + z), exp(x + y + z)]) + Y = Matrix([x, y, z]) + for i in range(1, 3): + for j in range(1, 3): + X_slice = X[:i, :] + Y_slice = Y[:j, :] + J = X_slice.jacobian(Y_slice) + assert J.rows == i + assert J.cols == j + for k in range(j): + assert J[:, k] == X_slice + + +def test_nonvectorJacobian(): + X = Matrix([[exp(x + y + z), exp(x + y + z)], + [exp(x + y + z), exp(x + y + z)]]) + raises(TypeError, lambda: X.jacobian(Matrix([x, y, z]))) + X = X[0, :] + Y = Matrix([[x, y], [x, z]]) + raises(TypeError, lambda: X.jacobian(Y)) + raises(TypeError, lambda: X.jacobian(Matrix([ [x, y], [x, z] ]))) + + +def test_vec(): + m = Matrix([[1, 3], [2, 4]]) + m_vec = m.vec() + assert m_vec.cols == 1 + for i in range(4): + assert m_vec[i] == i + 1 + + +def test_vech(): + m = Matrix([[1, 2], [2, 3]]) + m_vech = m.vech() + assert m_vech.cols == 1 + for i in range(3): + assert m_vech[i] == i + 1 + m_vech = m.vech(diagonal=False) + assert m_vech[0] == 2 + + m = Matrix([[1, x*(x + y)], [y*x + x**2, 1]]) + m_vech = m.vech(diagonal=False) + assert m_vech[0] == y*x + x**2 + + m = Matrix([[1, x*(x + y)], [y*x, 1]]) + m_vech = m.vech(diagonal=False, check_symmetry=False) + assert m_vech[0] == y*x + + raises(ShapeError, lambda: Matrix([[1, 3]]).vech()) + raises(ValueError, lambda: Matrix([[1, 3], [2, 4]]).vech()) + raises(ShapeError, lambda: Matrix([[1, 3]]).vech()) + raises(ValueError, lambda: Matrix([[1, 3], [2, 4]]).vech()) + + +def test_diag(): + # mostly tested in testcommonmatrix.py + assert diag([1, 2, 3]) == Matrix([1, 2, 3]) + m = [1, 2, [3]] + raises(ValueError, lambda: diag(m)) + assert diag(m, strict=False) == Matrix([1, 2, 3]) + + +def test_inv_block(): + a = Matrix([[1, 2], [2, 3]]) + b = Matrix([[3, x], [y, 3]]) + c = Matrix([[3, x, 3], [y, 3, z], [x, y, z]]) + A = diag(a, b, b) + assert A.inv(try_block_diag=True) == diag(a.inv(), b.inv(), b.inv()) + A = diag(a, b, c) + assert A.inv(try_block_diag=True) == diag(a.inv(), b.inv(), c.inv()) + A = diag(a, c, b) + assert A.inv(try_block_diag=True) == diag(a.inv(), c.inv(), b.inv()) + A = diag(a, a, b, a, c, a) + assert A.inv(try_block_diag=True) == diag( + a.inv(), a.inv(), b.inv(), a.inv(), c.inv(), a.inv()) + assert A.inv(try_block_diag=True, method="ADJ") == diag( + a.inv(method="ADJ"), a.inv(method="ADJ"), b.inv(method="ADJ"), + a.inv(method="ADJ"), c.inv(method="ADJ"), a.inv(method="ADJ")) + + +def test_creation_args(): + """ + Check that matrix dimensions can be specified using any reasonable type + (see issue 4614). + """ + raises(ValueError, lambda: zeros(3, -1)) + raises(TypeError, lambda: zeros(1, 2, 3, 4)) + assert zeros(int(3)) == zeros(3) + assert zeros(Integer(3)) == zeros(3) + raises(ValueError, lambda: zeros(3.)) + assert eye(int(3)) == eye(3) + assert eye(Integer(3)) == eye(3) + raises(ValueError, lambda: eye(3.)) + assert ones(int(3), Integer(4)) == ones(3, 4) + raises(TypeError, lambda: Matrix(5)) + raises(TypeError, lambda: Matrix(1, 2)) + raises(ValueError, lambda: Matrix([1, [2]])) + + +def test_diagonal_symmetrical(): + m = Matrix(2, 2, [0, 1, 1, 0]) + assert not m.is_diagonal() + assert m.is_symmetric() + assert m.is_symmetric(simplify=False) + + m = Matrix(2, 2, [1, 0, 0, 1]) + assert m.is_diagonal() + + m = diag(1, 2, 3) + assert m.is_diagonal() + assert m.is_symmetric() + + m = Matrix(3, 3, [1, 0, 0, 0, 2, 0, 0, 0, 3]) + assert m == diag(1, 2, 3) + + m = Matrix(2, 3, zeros(2, 3)) + assert not m.is_symmetric() + assert m.is_diagonal() + + m = Matrix(((5, 0), (0, 6), (0, 0))) + assert m.is_diagonal() + + m = Matrix(((5, 0, 0), (0, 6, 0))) + assert m.is_diagonal() + + m = Matrix(3, 3, [1, x**2 + 2*x + 1, y, (x + 1)**2, 2, 0, y, 0, 3]) + assert m.is_symmetric() + assert not m.is_symmetric(simplify=False) + assert m.expand().is_symmetric(simplify=False) + + +def test_diagonalization(): + m = Matrix([[1, 2+I], [2-I, 3]]) + assert m.is_diagonalizable() + + m = Matrix(3, 2, [-3, 1, -3, 20, 3, 10]) + assert not m.is_diagonalizable() + assert not m.is_symmetric() + raises(NonSquareMatrixError, lambda: m.diagonalize()) + + # diagonalizable + m = diag(1, 2, 3) + (P, D) = m.diagonalize() + assert P == eye(3) + assert D == m + + m = Matrix(2, 2, [0, 1, 1, 0]) + assert m.is_symmetric() + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + + m = Matrix(2, 2, [1, 0, 0, 3]) + assert m.is_symmetric() + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + assert P == eye(2) + assert D == m + + m = Matrix(2, 2, [1, 1, 0, 0]) + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + + m = Matrix(3, 3, [1, 2, 0, 0, 3, 0, 2, -4, 2]) + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + for i in P: + assert i.as_numer_denom()[1] == 1 + + m = Matrix(2, 2, [1, 0, 0, 0]) + assert m.is_diagonal() + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + assert P == Matrix([[0, 1], [1, 0]]) + + # diagonalizable, complex only + m = Matrix(2, 2, [0, 1, -1, 0]) + assert not m.is_diagonalizable(True) + raises(MatrixError, lambda: m.diagonalize(True)) + assert m.is_diagonalizable() + (P, D) = m.diagonalize() + assert P.inv() * m * P == D + + # not diagonalizable + m = Matrix(2, 2, [0, 1, 0, 0]) + assert not m.is_diagonalizable() + raises(MatrixError, lambda: m.diagonalize()) + + m = Matrix(3, 3, [-3, 1, -3, 20, 3, 10, 2, -2, 4]) + assert not m.is_diagonalizable() + raises(MatrixError, lambda: m.diagonalize()) + + # symbolic + a, b, c, d = symbols('a b c d') + m = Matrix(2, 2, [a, c, c, b]) + assert m.is_symmetric() + assert m.is_diagonalizable() + + +def test_issue_15887(): + # Mutable matrix should not use cache + a = MutableDenseMatrix([[0, 1], [1, 0]]) + assert a.is_diagonalizable() is True + a[1, 0] = 0 + assert a.is_diagonalizable() is False + + a = MutableDenseMatrix([[0, 1], [1, 0]]) + a.diagonalize() + a[1, 0] = 0 + raises(MatrixError, lambda: a.diagonalize()) + + +def test_jordan_form(): + + m = Matrix(3, 2, [-3, 1, -3, 20, 3, 10]) + raises(NonSquareMatrixError, lambda: m.jordan_form()) + + # diagonalizable + m = Matrix(3, 3, [7, -12, 6, 10, -19, 10, 12, -24, 13]) + Jmust = Matrix(3, 3, [-1, 0, 0, 0, 1, 0, 0, 0, 1]) + P, J = m.jordan_form() + assert Jmust == J + assert Jmust == m.diagonalize()[1] + + # m = Matrix(3, 3, [0, 6, 3, 1, 3, 1, -2, 2, 1]) + # m.jordan_form() # very long + # m.jordan_form() # + + # diagonalizable, complex only + + # Jordan cells + # complexity: one of eigenvalues is zero + m = Matrix(3, 3, [0, 1, 0, -4, 4, 0, -2, 1, 2]) + # The blocks are ordered according to the value of their eigenvalues, + # in order to make the matrix compatible with .diagonalize() + Jmust = Matrix(3, 3, [2, 1, 0, 0, 2, 0, 0, 0, 2]) + P, J = m.jordan_form() + assert Jmust == J + + # complexity: all of eigenvalues are equal + m = Matrix(3, 3, [2, 6, -15, 1, 1, -5, 1, 2, -6]) + # Jmust = Matrix(3, 3, [-1, 0, 0, 0, -1, 1, 0, 0, -1]) + # same here see 1456ff + Jmust = Matrix(3, 3, [-1, 1, 0, 0, -1, 0, 0, 0, -1]) + P, J = m.jordan_form() + assert Jmust == J + + # complexity: two of eigenvalues are zero + m = Matrix(3, 3, [4, -5, 2, 5, -7, 3, 6, -9, 4]) + Jmust = Matrix(3, 3, [0, 1, 0, 0, 0, 0, 0, 0, 1]) + P, J = m.jordan_form() + assert Jmust == J + + m = Matrix(4, 4, [6, 5, -2, -3, -3, -1, 3, 3, 2, 1, -2, -3, -1, 1, 5, 5]) + Jmust = Matrix(4, 4, [2, 1, 0, 0, + 0, 2, 0, 0, + 0, 0, 2, 1, + 0, 0, 0, 2] + ) + P, J = m.jordan_form() + assert Jmust == J + + m = Matrix(4, 4, [6, 2, -8, -6, -3, 2, 9, 6, 2, -2, -8, -6, -1, 0, 3, 4]) + # Jmust = Matrix(4, 4, [2, 0, 0, 0, 0, 2, 1, 0, 0, 0, 2, 0, 0, 0, 0, -2]) + # same here see 1456ff + Jmust = Matrix(4, 4, [-2, 0, 0, 0, + 0, 2, 1, 0, + 0, 0, 2, 0, + 0, 0, 0, 2]) + P, J = m.jordan_form() + assert Jmust == J + + m = Matrix(4, 4, [5, 4, 2, 1, 0, 1, -1, -1, -1, -1, 3, 0, 1, 1, -1, 2]) + assert not m.is_diagonalizable() + Jmust = Matrix(4, 4, [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 4, 1, 0, 0, 0, 4]) + P, J = m.jordan_form() + assert Jmust == J + + # checking for maximum precision to remain unchanged + m = Matrix([[Float('1.0', precision=110), Float('2.0', precision=110)], + [Float('3.14159265358979323846264338327', precision=110), Float('4.0', precision=110)]]) + P, J = m.jordan_form() + for term in J.values(): + if isinstance(term, Float): + assert term._prec == 110 + + +def test_jordan_form_complex_issue_9274(): + A = Matrix([[ 2, 4, 1, 0], + [-4, 2, 0, 1], + [ 0, 0, 2, 4], + [ 0, 0, -4, 2]]) + p = 2 - 4*I + q = 2 + 4*I + Jmust1 = Matrix([[p, 1, 0, 0], + [0, p, 0, 0], + [0, 0, q, 1], + [0, 0, 0, q]]) + Jmust2 = Matrix([[q, 1, 0, 0], + [0, q, 0, 0], + [0, 0, p, 1], + [0, 0, 0, p]]) + P, J = A.jordan_form() + assert J == Jmust1 or J == Jmust2 + assert simplify(P*J*P.inv()) == A + + +def test_issue_10220(): + # two non-orthogonal Jordan blocks with eigenvalue 1 + M = Matrix([[1, 0, 0, 1], + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 1]]) + P, J = M.jordan_form() + assert P == Matrix([[0, 1, 0, 1], + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0]]) + assert J == Matrix([ + [1, 1, 0, 0], + [0, 1, 1, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + + +def test_jordan_form_issue_15858(): + A = Matrix([ + [1, 1, 1, 0], + [-2, -1, 0, -1], + [0, 0, -1, -1], + [0, 0, 2, 1]]) + (P, J) = A.jordan_form() + assert P.expand() == Matrix([ + [ -I, -I/2, I, I/2], + [-1 + I, 0, -1 - I, 0], + [ 0, -S(1)/2 - I/2, 0, -S(1)/2 + I/2], + [ 0, 1, 0, 1]]) + assert J == Matrix([ + [-I, 1, 0, 0], + [0, -I, 0, 0], + [0, 0, I, 1], + [0, 0, 0, I]]) + + +def test_Matrix_berkowitz_charpoly(): + UA, K_i, K_w = symbols('UA K_i K_w') + + A = Matrix([[-K_i - UA + K_i**2/(K_i + K_w), K_i*K_w/(K_i + K_w)], + [ K_i*K_w/(K_i + K_w), -K_w + K_w**2/(K_i + K_w)]]) + + charpoly = A.charpoly(x) + + assert charpoly == \ + Poly(x**2 + (K_i*UA + K_w*UA + 2*K_i*K_w)/(K_i + K_w)*x + + K_i*K_w*UA/(K_i + K_w), x, domain='ZZ(K_i,K_w,UA)') + + assert type(charpoly) is PurePoly + + A = Matrix([[1, 3], [2, 0]]) + assert A.charpoly() == A.charpoly(x) == PurePoly(x**2 - x - 6) + + A = Matrix([[1, 2], [x, 0]]) + p = A.charpoly(x) + assert p.gen != x + assert p.as_expr().subs(p.gen, x) == x**2 - 3*x + + +def test_exp_jordan_block(): + l = Symbol('lamda') + + m = Matrix.jordan_block(1, l) + assert m._eval_matrix_exp_jblock() == Matrix([[exp(l)]]) + + m = Matrix.jordan_block(3, l) + assert m._eval_matrix_exp_jblock() == \ + Matrix([ + [exp(l), exp(l), exp(l)/2], + [0, exp(l), exp(l)], + [0, 0, exp(l)]]) + + +def test_exp(): + m = Matrix([[3, 4], [0, -2]]) + m_exp = Matrix([[exp(3), -4*exp(-2)/5 + 4*exp(3)/5], [0, exp(-2)]]) + assert m.exp() == m_exp + assert exp(m) == m_exp + + m = Matrix([[1, 0], [0, 1]]) + assert m.exp() == Matrix([[E, 0], [0, E]]) + assert exp(m) == Matrix([[E, 0], [0, E]]) + + m = Matrix([[1, -1], [1, 1]]) + assert m.exp() == Matrix([[E*cos(1), -E*sin(1)], [E*sin(1), E*cos(1)]]) + + +def test_log(): + l = Symbol('lamda') + + m = Matrix.jordan_block(1, l) + assert m._eval_matrix_log_jblock() == Matrix([[log(l)]]) + + m = Matrix.jordan_block(4, l) + assert m._eval_matrix_log_jblock() == \ + Matrix( + [ + [log(l), 1/l, -1/(2*l**2), 1/(3*l**3)], + [0, log(l), 1/l, -1/(2*l**2)], + [0, 0, log(l), 1/l], + [0, 0, 0, log(l)] + ] + ) + + m = Matrix( + [[0, 0, 1], + [0, 0, 0], + [-1, 0, 0]] + ) + raises(MatrixError, lambda: m.log()) + + +def test_find_reasonable_pivot_naive_finds_guaranteed_nonzero1(): + # Test if matrices._find_reasonable_pivot_naive() + # finds a guaranteed non-zero pivot when the + # some of the candidate pivots are symbolic expressions. + # Keyword argument: simpfunc=None indicates that no simplifications + # should be performed during the search. + x = Symbol('x') + column = Matrix(3, 1, [x, cos(x)**2 + sin(x)**2, S.Half]) + pivot_offset, pivot_val, pivot_assumed_nonzero, simplified =\ + _find_reasonable_pivot_naive(column) + assert pivot_val == S.Half + + +def test_find_reasonable_pivot_naive_finds_guaranteed_nonzero2(): + # Test if matrices._find_reasonable_pivot_naive() + # finds a guaranteed non-zero pivot when the + # some of the candidate pivots are symbolic expressions. + # Keyword argument: simpfunc=_simplify indicates that the search + # should attempt to simplify candidate pivots. + x = Symbol('x') + column = Matrix(3, 1, + [x, + cos(x)**2+sin(x)**2+x**2, + cos(x)**2+sin(x)**2]) + pivot_offset, pivot_val, pivot_assumed_nonzero, simplified =\ + _find_reasonable_pivot_naive(column, simpfunc=_simplify) + assert pivot_val == 1 + + +def test_find_reasonable_pivot_naive_simplifies(): + # Test if matrices._find_reasonable_pivot_naive() + # simplifies candidate pivots, and reports + # their offsets correctly. + x = Symbol('x') + column = Matrix(3, 1, + [x, + cos(x)**2+sin(x)**2+x, + cos(x)**2+sin(x)**2]) + pivot_offset, pivot_val, pivot_assumed_nonzero, simplified =\ + _find_reasonable_pivot_naive(column, simpfunc=_simplify) + + assert len(simplified) == 2 + assert simplified[0][0] == 1 + assert simplified[0][1] == 1+x + assert simplified[1][0] == 2 + assert simplified[1][1] == 1 + + +def test_errors(): + raises(ValueError, lambda: Matrix([[1, 2], [1]])) + raises(IndexError, lambda: Matrix([[1, 2]])[1.2, 5]) + raises(IndexError, lambda: Matrix([[1, 2]])[1, 5.2]) + raises(ValueError, lambda: randMatrix(3, c=4, symmetric=True)) + raises(ValueError, lambda: Matrix([1, 2]).reshape(4, 6)) + raises(ShapeError, + lambda: Matrix([[1, 2], [3, 4]]).copyin_matrix([1, 0], Matrix([1, 2]))) + raises(TypeError, lambda: Matrix([[1, 2], [3, 4]]).copyin_list([0, + 1], set())) + raises(NonSquareMatrixError, lambda: Matrix([[1, 2, 3], [2, 3, 0]]).inv()) + raises(ShapeError, + lambda: Matrix(1, 2, [1, 2]).row_join(Matrix([[1, 2], [3, 4]]))) + raises( + ShapeError, lambda: Matrix([1, 2]).col_join(Matrix([[1, 2], [3, 4]]))) + raises(ShapeError, lambda: Matrix([1]).row_insert(1, Matrix([[1, + 2], [3, 4]]))) + raises(ShapeError, lambda: Matrix([1]).col_insert(1, Matrix([[1, + 2], [3, 4]]))) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).trace()) + raises(TypeError, lambda: Matrix([1]).applyfunc(1)) + raises(ValueError, lambda: Matrix([[1, 2], [3, 4]]).minor(4, 5)) + raises(ValueError, lambda: Matrix([[1, 2], [3, 4]]).minor_submatrix(4, 5)) + raises(TypeError, lambda: Matrix([1, 2, 3]).cross(1)) + raises(TypeError, lambda: Matrix([1, 2, 3]).dot(1)) + raises(ShapeError, lambda: Matrix([1, 2, 3]).dot(Matrix([1, 2]))) + raises(ShapeError, lambda: Matrix([1, 2]).dot([])) + raises(TypeError, lambda: Matrix([1, 2]).dot('a')) + raises(ShapeError, lambda: Matrix([1, 2]).dot([1, 2, 3])) + raises(NonSquareMatrixError, lambda: Matrix([1, 2, 3]).exp()) + raises(ShapeError, lambda: Matrix([[1, 2], [3, 4]]).normalized()) + raises(ValueError, lambda: Matrix([1, 2]).inv(method='not a method')) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).inverse_GE()) + raises(ValueError, lambda: Matrix([[1, 2], [1, 2]]).inverse_GE()) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).inverse_ADJ()) + raises(ValueError, lambda: Matrix([[1, 2], [1, 2]]).inverse_ADJ()) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).inverse_LU()) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).is_nilpotent()) + raises(NonSquareMatrixError, lambda: Matrix([1, 2]).det()) + raises(ValueError, + lambda: Matrix([[1, 2], [3, 4]]).det(method='Not a real method')) + raises(ValueError, + lambda: Matrix([[1, 2, 3, 4], [5, 6, 7, 8], + [9, 10, 11, 12], [13, 14, 15, 16]]).det(iszerofunc="Not function")) + raises(ValueError, + lambda: Matrix([[1, 2, 3, 4], [5, 6, 7, 8], + [9, 10, 11, 12], [13, 14, 15, 16]]).det(iszerofunc=False)) + raises(ValueError, + lambda: hessian(Matrix([[1, 2], [3, 4]]), Matrix([[1, 2], [2, 1]]))) + raises(ValueError, lambda: hessian(Matrix([[1, 2], [3, 4]]), [])) + raises(ValueError, lambda: hessian(Symbol('x')**2, 'a')) + raises(IndexError, lambda: eye(3)[5, 2]) + raises(IndexError, lambda: eye(3)[2, 5]) + M = Matrix(((1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12), (13, 14, 15, 16))) + raises(ValueError, lambda: M.det('method=LU_decomposition()')) + V = Matrix([[10, 10, 10]]) + M = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(ValueError, lambda: M.row_insert(4.7, V)) + M = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(ValueError, lambda: M.col_insert(-4.2, V)) + + +def test_len(): + assert len(Matrix()) == 0 + assert len(Matrix([[1, 2]])) == len(Matrix([[1], [2]])) == 2 + assert len(Matrix(0, 2, lambda i, j: 0)) == \ + len(Matrix(2, 0, lambda i, j: 0)) == 0 + assert len(Matrix([[0, 1, 2], [3, 4, 5]])) == 6 + assert Matrix([1]) == Matrix([[1]]) + assert not Matrix() + assert Matrix() == Matrix([]) + + +def test_integrate(): + A = Matrix(((1, 4, x), (y, 2, 4), (10, 5, x**2))) + assert A.integrate(x) == \ + Matrix(((x, 4*x, x**2/2), (x*y, 2*x, 4*x), (10*x, 5*x, x**3/3))) + assert A.integrate(y) == \ + Matrix(((y, 4*y, x*y), (y**2/2, 2*y, 4*y), (10*y, 5*y, y*x**2))) + m = Matrix(2, 1, [x, y]) + assert m.integrate(x) == Matrix(2, 1, [x**2/2, y*x]) + + +def test_diff(): + A = MutableDenseMatrix(((1, 4, x), (y, 2, 4), (10, 5, x**2 + 1))) + assert isinstance(A.diff(x), type(A)) + assert A.diff(x) == MutableDenseMatrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + assert A.diff(y) == MutableDenseMatrix(((0, 0, 0), (1, 0, 0), (0, 0, 0))) + + assert diff(A, x) == MutableDenseMatrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + assert diff(A, y) == MutableDenseMatrix(((0, 0, 0), (1, 0, 0), (0, 0, 0))) + + A_imm = A.as_immutable() + assert isinstance(A_imm.diff(x), type(A_imm)) + assert A_imm.diff(x) == ImmutableDenseMatrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + assert A_imm.diff(y) == ImmutableDenseMatrix(((0, 0, 0), (1, 0, 0), (0, 0, 0))) + + assert diff(A_imm, x) == ImmutableDenseMatrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + assert diff(A_imm, y) == ImmutableDenseMatrix(((0, 0, 0), (1, 0, 0), (0, 0, 0))) + + assert A.diff(x, evaluate=False) == ArrayDerivative(A, x, evaluate=False) + assert diff(A, x, evaluate=False) == ArrayDerivative(A, x, evaluate=False) + + +def test_diff_by_matrix(): + + # Derive matrix by matrix: + + A = MutableDenseMatrix([[x, y], [z, t]]) + assert A.diff(A) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + assert diff(A, A) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + + A_imm = A.as_immutable() + assert A_imm.diff(A_imm) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + assert diff(A_imm, A_imm) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + + # Derive a constant matrix: + assert A.diff(a) == MutableDenseMatrix([[0, 0], [0, 0]]) + + B = ImmutableDenseMatrix([a, b]) + assert A.diff(B) == Array.zeros(2, 1, 2, 2) + assert A.diff(A) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + + # Test diff with tuples: + + dB = B.diff([[a, b]]) + assert dB.shape == (2, 2, 1) + assert dB == Array([[[1], [0]], [[0], [1]]]) + + f = Function("f") + fxyz = f(x, y, z) + assert fxyz.diff([[x, y, z]]) == Array([fxyz.diff(x), fxyz.diff(y), fxyz.diff(z)]) + assert fxyz.diff(([x, y, z], 2)) == Array([ + [fxyz.diff(x, 2), fxyz.diff(x, y), fxyz.diff(x, z)], + [fxyz.diff(x, y), fxyz.diff(y, 2), fxyz.diff(y, z)], + [fxyz.diff(x, z), fxyz.diff(z, y), fxyz.diff(z, 2)], + ]) + + expr = sin(x)*exp(y) + assert expr.diff([[x, y]]) == Array([cos(x)*exp(y), sin(x)*exp(y)]) + assert expr.diff(y, ((x, y),)) == Array([cos(x)*exp(y), sin(x)*exp(y)]) + assert expr.diff(x, ((x, y),)) == Array([-sin(x)*exp(y), cos(x)*exp(y)]) + assert expr.diff(((y, x),), [[x, y]]) == Array([[cos(x)*exp(y), -sin(x)*exp(y)], [sin(x)*exp(y), cos(x)*exp(y)]]) + + # Test different notations: + + assert fxyz.diff(x).diff(y).diff(x) == fxyz.diff(((x, y, z),), 3)[0, 1, 0] + assert fxyz.diff(z).diff(y).diff(x) == fxyz.diff(((x, y, z),), 3)[2, 1, 0] + assert fxyz.diff([[x, y, z]], ((z, y, x),)) == Array([[fxyz.diff(i).diff(j) for i in (x, y, z)] for j in (z, y, x)]) + + # Test scalar derived by matrix remains matrix: + res = x.diff(Matrix([[x, y]])) + assert isinstance(res, ImmutableDenseMatrix) + assert res == Matrix([[1, 0]]) + res = (x**3).diff(Matrix([[x, y]])) + assert isinstance(res, ImmutableDenseMatrix) + assert res == Matrix([[3*x**2, 0]]) + + +def test_getattr(): + A = Matrix(((1, 4, x), (y, 2, 4), (10, 5, x**2 + 1))) + raises(AttributeError, lambda: A.nonexistantattribute) + assert getattr(A, 'diff')(x) == Matrix(((0, 0, 1), (0, 0, 0), (0, 0, 2*x))) + + +def test_hessenberg(): + A = Matrix([[3, 4, 1], [2, 4, 5], [0, 1, 2]]) + assert A.is_upper_hessenberg + A = A.T + assert A.is_lower_hessenberg + A[0, -1] = 1 + assert A.is_lower_hessenberg is False + + A = Matrix([[3, 4, 1], [2, 4, 5], [3, 1, 2]]) + assert not A.is_upper_hessenberg + + A = zeros(5, 2) + assert A.is_upper_hessenberg + + +def test_cholesky(): + raises(NonSquareMatrixError, lambda: Matrix((1, 2)).cholesky()) + raises(ValueError, lambda: Matrix(((1, 2), (3, 4))).cholesky()) + raises(ValueError, lambda: Matrix(((5 + I, 0), (0, 1))).cholesky()) + raises(ValueError, lambda: Matrix(((1, 5), (5, 1))).cholesky()) + raises(ValueError, lambda: Matrix(((1, 2), (3, 4))).cholesky(hermitian=False)) + assert Matrix(((5 + I, 0), (0, 1))).cholesky(hermitian=False) == Matrix([ + [sqrt(5 + I), 0], [0, 1]]) + A = Matrix(((1, 5), (5, 1))) + L = A.cholesky(hermitian=False) + assert L == Matrix([[1, 0], [5, 2*sqrt(6)*I]]) + assert L*L.T == A + A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + L = A.cholesky() + assert L * L.T == A + assert L.is_lower + assert L == Matrix([[5, 0, 0], [3, 3, 0], [-1, 1, 3]]) + A = Matrix(((4, -2*I, 2 + 2*I), (2*I, 2, -1 + I), (2 - 2*I, -1 - I, 11))) + assert A.cholesky().expand() == Matrix(((2, 0, 0), (I, 1, 0), (1 - I, 0, 3))) + + raises(NonSquareMatrixError, lambda: SparseMatrix((1, 2)).cholesky()) + raises(ValueError, lambda: SparseMatrix(((1, 2), (3, 4))).cholesky()) + raises(ValueError, lambda: SparseMatrix(((5 + I, 0), (0, 1))).cholesky()) + raises(ValueError, lambda: SparseMatrix(((1, 5), (5, 1))).cholesky()) + raises(ValueError, lambda: SparseMatrix(((1, 2), (3, 4))).cholesky(hermitian=False)) + assert SparseMatrix(((5 + I, 0), (0, 1))).cholesky(hermitian=False) == Matrix([ + [sqrt(5 + I), 0], [0, 1]]) + A = SparseMatrix(((1, 5), (5, 1))) + L = A.cholesky(hermitian=False) + assert L == Matrix([[1, 0], [5, 2*sqrt(6)*I]]) + assert L*L.T == A + A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + L = A.cholesky() + assert L * L.T == A + assert L.is_lower + assert L == Matrix([[5, 0, 0], [3, 3, 0], [-1, 1, 3]]) + A = SparseMatrix(((4, -2*I, 2 + 2*I), (2*I, 2, -1 + I), (2 - 2*I, -1 - I, 11))) + assert A.cholesky() == Matrix(((2, 0, 0), (I, 1, 0), (1 - I, 0, 3))) + + +def test_matrix_norm(): + # Vector Tests + # Test columns and symbols + x = Symbol('x', real=True) + v = Matrix([cos(x), sin(x)]) + assert trigsimp(v.norm(2)) == 1 + assert v.norm(10) == Pow(cos(x)**10 + sin(x)**10, Rational(1, 10)) + + # Test Rows + A = Matrix([[5, Rational(3, 2)]]) + assert A.norm() == Pow(25 + Rational(9, 4), S.Half) + assert A.norm(oo) == max(A) + assert A.norm(-oo) == min(A) + + # Matrix Tests + # Intuitive test + A = Matrix([[1, 1], [1, 1]]) + assert A.norm(2) == 2 + assert A.norm(-2) == 0 + assert A.norm('frobenius') == 2 + assert eye(10).norm(2) == eye(10).norm(-2) == 1 + assert A.norm(oo) == 2 + + # Test with Symbols and more complex entries + A = Matrix([[3, y, y], [x, S.Half, -pi]]) + assert (A.norm('fro') + == sqrt(Rational(37, 4) + 2*abs(y)**2 + pi**2 + x**2)) + + # Check non-square + A = Matrix([[1, 2, -3], [4, 5, Rational(13, 2)]]) + assert A.norm(2) == sqrt(Rational(389, 8) + sqrt(78665)/8) + assert A.norm(-2) is S.Zero + assert A.norm('frobenius') == sqrt(389)/2 + + # Test properties of matrix norms + # https://en.wikipedia.org/wiki/Matrix_norm#Definition + # Two matrices + A = Matrix([[1, 2], [3, 4]]) + B = Matrix([[5, 5], [-2, 2]]) + C = Matrix([[0, -I], [I, 0]]) + D = Matrix([[1, 0], [0, -1]]) + L = [A, B, C, D] + alpha = Symbol('alpha', real=True) + + for order in ['fro', 2, -2]: + # Zero Check + assert zeros(3).norm(order) is S.Zero + # Check Triangle Inequality for all Pairs of Matrices + for X in L: + for Y in L: + dif = (X.norm(order) + Y.norm(order) - + (X + Y).norm(order)) + assert (dif >= 0) + # Scalar multiplication linearity + for M in [A, B, C, D]: + dif = simplify((alpha*M).norm(order) - + abs(alpha) * M.norm(order)) + assert dif == 0 + + # Test Properties of Vector Norms + # https://en.wikipedia.org/wiki/Vector_norm + # Two column vectors + a = Matrix([1, 1 - 1*I, -3]) + b = Matrix([S.Half, 1*I, 1]) + c = Matrix([-1, -1, -1]) + d = Matrix([3, 2, I]) + e = Matrix([Integer(1e2), Rational(1, 1e2), 1]) + L = [a, b, c, d, e] + alpha = Symbol('alpha', real=True) + + for order in [1, 2, -1, -2, S.Infinity, S.NegativeInfinity, pi]: + # Zero Check + if order > 0: + assert Matrix([0, 0, 0]).norm(order) is S.Zero + # Triangle inequality on all pairs + if order >= 1: # Triangle InEq holds only for these norms + for X in L: + for Y in L: + dif = (X.norm(order) + Y.norm(order) - + (X + Y).norm(order)) + assert simplify(dif >= 0) is S.true + # Linear to scalar multiplication + if order in [1, 2, -1, -2, S.Infinity, S.NegativeInfinity]: + for X in L: + dif = simplify((alpha*X).norm(order) - + (abs(alpha) * X.norm(order))) + assert dif == 0 + + # ord=1 + M = Matrix(3, 3, [1, 3, 0, -2, -1, 0, 3, 9, 6]) + assert M.norm(1) == 13 + + +def test_condition_number(): + x = Symbol('x', real=True) + A = eye(3) + A[0, 0] = 10 + A[2, 2] = Rational(1, 10) + assert A.condition_number() == 100 + + A[1, 1] = x + assert A.condition_number() == Max(10, Abs(x)) / Min(Rational(1, 10), Abs(x)) + + M = Matrix([[cos(x), sin(x)], [-sin(x), cos(x)]]) + Mc = M.condition_number() + assert all(Float(1.).epsilon_eq(Mc.subs(x, val).evalf()) for val in + [Rational(1, 5), S.Half, Rational(1, 10), pi/2, pi, pi*Rational(7, 4) ]) + + #issue 10782 + assert Matrix([]).condition_number() == 0 + + +def test_equality(): + A = Matrix(((1, 2, 3), (4, 5, 6), (7, 8, 9))) + B = Matrix(((9, 8, 7), (6, 5, 4), (3, 2, 1))) + assert A == A[:, :] + assert not A != A[:, :] + assert not A == B + assert A != B + assert A != 10 + assert not A == 10 + + # A SparseMatrix can be equal to a Matrix + C = SparseMatrix(((1, 0, 0), (0, 1, 0), (0, 0, 1))) + D = Matrix(((1, 0, 0), (0, 1, 0), (0, 0, 1))) + assert C == D + assert not C != D + + +def test_normalized(): + assert Matrix([3, 4]).normalized() == \ + Matrix([Rational(3, 5), Rational(4, 5)]) + + # Zero vector trivial cases + assert Matrix([0, 0, 0]).normalized() == Matrix([0, 0, 0]) + + # Machine precision error truncation trivial cases + m = Matrix([0,0,1.e-100]) + assert m.normalized( + iszerofunc=lambda x: x.evalf(n=10, chop=True).is_zero + ) == Matrix([0, 0, 0]) + + +def test_print_nonzero(): + assert capture(lambda: eye(3).print_nonzero()) == \ + '[X ]\n[ X ]\n[ X]\n' + assert capture(lambda: eye(3).print_nonzero('.')) == \ + '[. ]\n[ . ]\n[ .]\n' + + +def test_zeros_eye(): + assert Matrix.eye(3) == eye(3) + assert Matrix.zeros(3) == zeros(3) + assert ones(3, 4) == Matrix(3, 4, [1]*12) + + i = Matrix([[1, 0], [0, 1]]) + z = Matrix([[0, 0], [0, 0]]) + for cls in all_classes: + m = cls.eye(2) + assert i == m # but m == i will fail if m is immutable + assert i == eye(2, cls=cls) + assert type(m) == cls + m = cls.zeros(2) + assert z == m + assert z == zeros(2, cls=cls) + assert type(m) == cls + + +def test_is_zero(): + assert Matrix().is_zero_matrix + assert Matrix([[0, 0], [0, 0]]).is_zero_matrix + assert zeros(3, 4).is_zero_matrix + assert not eye(3).is_zero_matrix + assert Matrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert SparseMatrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert ImmutableMatrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert ImmutableSparseMatrix([[x, 0], [0, 0]]).is_zero_matrix == None + assert Matrix([[x, 1], [0, 0]]).is_zero_matrix == False + a = Symbol('a', nonzero=True) + assert Matrix([[a, 0], [0, 0]]).is_zero_matrix == False + + +def test_rotation_matrices(): + # This tests the rotation matrices by rotating about an axis and back. + theta = pi/3 + r3_plus = rot_axis3(theta) + r3_minus = rot_axis3(-theta) + r2_plus = rot_axis2(theta) + r2_minus = rot_axis2(-theta) + r1_plus = rot_axis1(theta) + r1_minus = rot_axis1(-theta) + assert r3_minus*r3_plus*eye(3) == eye(3) + assert r2_minus*r2_plus*eye(3) == eye(3) + assert r1_minus*r1_plus*eye(3) == eye(3) + + # Check the correctness of the trace of the rotation matrix + assert r1_plus.trace() == 1 + 2*cos(theta) + assert r2_plus.trace() == 1 + 2*cos(theta) + assert r3_plus.trace() == 1 + 2*cos(theta) + + # Check that a rotation with zero angle doesn't change anything. + assert rot_axis1(0) == eye(3) + assert rot_axis2(0) == eye(3) + assert rot_axis3(0) == eye(3) + + # Check left-hand convention + # see Issue #24529 + q1 = Quaternion.from_axis_angle([1, 0, 0], pi / 2) + q2 = Quaternion.from_axis_angle([0, 1, 0], pi / 2) + q3 = Quaternion.from_axis_angle([0, 0, 1], pi / 2) + assert rot_axis1(- pi / 2) == q1.to_rotation_matrix() + assert rot_axis2(- pi / 2) == q2.to_rotation_matrix() + assert rot_axis3(- pi / 2) == q3.to_rotation_matrix() + # Check right-hand convention + assert rot_ccw_axis1(+ pi / 2) == q1.to_rotation_matrix() + assert rot_ccw_axis2(+ pi / 2) == q2.to_rotation_matrix() + assert rot_ccw_axis3(+ pi / 2) == q3.to_rotation_matrix() + + +def test_DeferredVector(): + assert str(DeferredVector("vector")[4]) == "vector[4]" + assert sympify(DeferredVector("d")) == DeferredVector("d") + raises(IndexError, lambda: DeferredVector("d")[-1]) + assert str(DeferredVector("d")) == "d" + assert repr(DeferredVector("test")) == "DeferredVector('test')" + + +def test_DeferredVector_not_iterable(): + assert not iterable(DeferredVector('X')) + + +def test_DeferredVector_Matrix(): + raises(TypeError, lambda: Matrix(DeferredVector("V"))) + + +def test_GramSchmidt(): + R = Rational + m1 = Matrix(1, 2, [1, 2]) + m2 = Matrix(1, 2, [2, 3]) + assert GramSchmidt([m1, m2]) == \ + [Matrix(1, 2, [1, 2]), Matrix(1, 2, [R(2)/5, R(-1)/5])] + assert GramSchmidt([m1.T, m2.T]) == \ + [Matrix(2, 1, [1, 2]), Matrix(2, 1, [R(2)/5, R(-1)/5])] + # from wikipedia + assert GramSchmidt([Matrix([3, 1]), Matrix([2, 2])], True) == [ + Matrix([3*sqrt(10)/10, sqrt(10)/10]), + Matrix([-sqrt(10)/10, 3*sqrt(10)/10])] + # https://github.com/sympy/sympy/issues/9488 + L = FiniteSet(Matrix([1])) + assert GramSchmidt(L) == [Matrix([[1]])] + + +def test_casoratian(): + assert casoratian([1, 2, 3, 4], 1) == 0 + assert casoratian([1, 2, 3, 4], 1, zero=False) == 0 + + +def test_zero_dimension_multiply(): + assert (Matrix()*zeros(0, 3)).shape == (0, 3) + assert zeros(3, 0)*zeros(0, 3) == zeros(3, 3) + assert zeros(0, 3)*zeros(3, 0) == Matrix() + + +def test_slice_issue_2884(): + m = Matrix(2, 2, range(4)) + assert m[1, :] == Matrix([[2, 3]]) + assert m[-1, :] == Matrix([[2, 3]]) + assert m[:, 1] == Matrix([[1, 3]]).T + assert m[:, -1] == Matrix([[1, 3]]).T + raises(IndexError, lambda: m[2, :]) + raises(IndexError, lambda: m[2, 2]) + + +def test_slice_issue_3401(): + assert zeros(0, 3)[:, -1].shape == (0, 1) + assert zeros(3, 0)[0, :] == Matrix(1, 0, []) + + +def test_copyin(): + s = zeros(3, 3) + s[3] = 1 + assert s[:, 0] == Matrix([0, 1, 0]) + assert s[3] == 1 + assert s[3: 4] == [1] + s[1, 1] = 42 + assert s[1, 1] == 42 + assert s[1, 1:] == Matrix([[42, 0]]) + s[1, 1:] = Matrix([[5, 6]]) + assert s[1, :] == Matrix([[1, 5, 6]]) + s[1, 1:] = [[42, 43]] + assert s[1, :] == Matrix([[1, 42, 43]]) + s[0, 0] = 17 + assert s[:, :1] == Matrix([17, 1, 0]) + s[0, 0] = [1, 1, 1] + assert s[:, 0] == Matrix([1, 1, 1]) + s[0, 0] = Matrix([1, 1, 1]) + assert s[:, 0] == Matrix([1, 1, 1]) + s[0, 0] = SparseMatrix([1, 1, 1]) + assert s[:, 0] == Matrix([1, 1, 1]) + + +def test_invertible_check(): + # sometimes a singular matrix will have a pivot vector shorter than + # the number of rows in a matrix... + assert Matrix([[1, 2], [1, 2]]).rref() == (Matrix([[1, 2], [0, 0]]), (0,)) + raises(ValueError, lambda: Matrix([[1, 2], [1, 2]]).inv()) + m = Matrix([ + [-1, -1, 0], + [ x, 1, 1], + [ 1, x, -1], + ]) + assert len(m.rref()[1]) != m.rows + # in addition, unless simplify=True in the call to rref, the identity + # matrix will be returned even though m is not invertible + assert m.rref()[0] != eye(3) + assert m.rref(simplify=signsimp)[0] != eye(3) + raises(ValueError, lambda: m.inv(method="ADJ")) + raises(ValueError, lambda: m.inv(method="GE")) + raises(ValueError, lambda: m.inv(method="LU")) + + +def test_issue_3959(): + x, y = symbols('x, y') + e = x*y + assert e.subs(x, Matrix([3, 5, 3])) == Matrix([3, 5, 3])*y + + +def test_issue_5964(): + assert str(Matrix([[1, 2], [3, 4]])) == 'Matrix([[1, 2], [3, 4]])' + + +def test_issue_7604(): + x, y = symbols("x y") + assert sstr(Matrix([[x, 2*y], [y**2, x + 3]])) == \ + 'Matrix([\n[ x, 2*y],\n[y**2, x + 3]])' + + +def test_is_Identity(): + assert eye(3).is_Identity + assert eye(3).as_immutable().is_Identity + assert not zeros(3).is_Identity + assert not ones(3).is_Identity + # issue 6242 + assert not Matrix([[1, 0, 0]]).is_Identity + # issue 8854 + assert SparseMatrix(3,3, {(0,0):1, (1,1):1, (2,2):1}).is_Identity + assert not SparseMatrix(2,3, range(6)).is_Identity + assert not SparseMatrix(3,3, {(0,0):1, (1,1):1}).is_Identity + assert not SparseMatrix(3,3, {(0,0):1, (1,1):1, (2,2):1, (0,1):2, (0,2):3}).is_Identity + + +def test_dot(): + assert ones(1, 3).dot(ones(3, 1)) == 3 + assert ones(1, 3).dot([1, 1, 1]) == 3 + assert Matrix([1, 2, 3]).dot(Matrix([1, 2, 3])) == 14 + assert Matrix([1, 2, 3*I]).dot(Matrix([I, 2, 3*I])) == -5 + I + assert Matrix([1, 2, 3*I]).dot(Matrix([I, 2, 3*I]), hermitian=False) == -5 + I + assert Matrix([1, 2, 3*I]).dot(Matrix([I, 2, 3*I]), hermitian=True) == 13 + I + assert Matrix([1, 2, 3*I]).dot(Matrix([I, 2, 3*I]), hermitian=True, conjugate_convention="physics") == 13 - I + assert Matrix([1, 2, 3*I]).dot(Matrix([4, 5*I, 6]), hermitian=True, conjugate_convention="right") == 4 + 8*I + assert Matrix([1, 2, 3*I]).dot(Matrix([4, 5*I, 6]), hermitian=True, conjugate_convention="left") == 4 - 8*I + assert Matrix([I, 2*I]).dot(Matrix([I, 2*I]), hermitian=False, conjugate_convention="left") == -5 + assert Matrix([I, 2*I]).dot(Matrix([I, 2*I]), conjugate_convention="left") == 5 + raises(ValueError, lambda: Matrix([1, 2]).dot(Matrix([3, 4]), hermitian=True, conjugate_convention="test")) + + +def test_dual(): + B_x, B_y, B_z, E_x, E_y, E_z = symbols( + 'B_x B_y B_z E_x E_y E_z', real=True) + F = Matrix(( + ( 0, E_x, E_y, E_z), + (-E_x, 0, B_z, -B_y), + (-E_y, -B_z, 0, B_x), + (-E_z, B_y, -B_x, 0) + )) + Fd = Matrix(( + ( 0, -B_x, -B_y, -B_z), + (B_x, 0, E_z, -E_y), + (B_y, -E_z, 0, E_x), + (B_z, E_y, -E_x, 0) + )) + assert F.dual().equals(Fd) + assert eye(3).dual().equals(zeros(3)) + assert F.dual().dual().equals(-F) + + +def test_anti_symmetric(): + assert Matrix([1, 2]).is_anti_symmetric() is False + m = Matrix(3, 3, [0, x**2 + 2*x + 1, y, -(x + 1)**2, 0, x*y, -y, -x*y, 0]) + assert m.is_anti_symmetric() is True + assert m.is_anti_symmetric(simplify=False) is None + assert m.is_anti_symmetric(simplify=lambda x: x) is None + + # tweak to fail + m[2, 1] = -m[2, 1] + assert m.is_anti_symmetric() is None + # untweak + m[2, 1] = -m[2, 1] + + m = m.expand() + assert m.is_anti_symmetric(simplify=False) is True + m[0, 0] = 1 + assert m.is_anti_symmetric() is False + + +def test_normalize_sort_diogonalization(): + A = Matrix(((1, 2), (2, 1))) + P, Q = A.diagonalize(normalize=True) + assert P*P.T == P.T*P == eye(P.cols) + P, Q = A.diagonalize(normalize=True, sort=True) + assert P*P.T == P.T*P == eye(P.cols) + assert P*Q*P.inv() == A + + +def test_issue_5321(): + raises(ValueError, lambda: Matrix([[1, 2, 3], Matrix(0, 1, [])])) + + +def test_issue_5320(): + assert Matrix.hstack(eye(2), 2*eye(2)) == Matrix([ + [1, 0, 2, 0], + [0, 1, 0, 2] + ]) + assert Matrix.vstack(eye(2), 2*eye(2)) == Matrix([ + [1, 0], + [0, 1], + [2, 0], + [0, 2] + ]) + cls = SparseMatrix + assert cls.hstack(cls(eye(2)), cls(2*eye(2))) == Matrix([ + [1, 0, 2, 0], + [0, 1, 0, 2] + ]) + + +def test_issue_11944(): + A = Matrix([[1]]) + AIm = sympify(A) + assert Matrix.hstack(AIm, A) == Matrix([[1, 1]]) + assert Matrix.vstack(AIm, A) == Matrix([[1], [1]]) + + +def test_cross(): + a = [1, 2, 3] + b = [3, 4, 5] + col = Matrix([-2, 4, -2]) + row = col.T + + def test(M, ans): + assert ans == M + assert type(M) == cls + for cls in all_classes: + A = cls(a) + B = cls(b) + test(A.cross(B), col) + test(A.cross(B.T), col) + test(A.T.cross(B.T), row) + test(A.T.cross(B), row) + raises(ShapeError, lambda: + Matrix(1, 2, [1, 1]).cross(Matrix(1, 2, [1, 1]))) + + +def test_hat_vee(): + v1 = Matrix([x, y, z]) + v2 = Matrix([a, b, c]) + assert v1.hat() * v2 == v1.cross(v2) + assert v1.hat().is_anti_symmetric() + assert v1.hat().vee() == v1 + + +def test_hash(): + for cls in immutable_classes: + s = {cls.eye(1), cls.eye(1)} + assert len(s) == 1 and s.pop() == cls.eye(1) + # issue 3979 + for cls in mutable_classes: + assert not isinstance(cls.eye(1), Hashable) + + +def test_adjoint(): + dat = [[0, I], [1, 0]] + ans = Matrix([[0, 1], [-I, 0]]) + for cls in all_classes: + assert ans == cls(dat).adjoint() + + +def test_atoms(): + m = Matrix([[1, 2], [x, 1 - 1/x]]) + assert m.atoms() == {S.One,S(2),S.NegativeOne, x} + assert m.atoms(Symbol) == {x} + + +def test_pinv(): + # Pseudoinverse of an invertible matrix is the inverse. + A1 = Matrix([[a, b], [c, d]]) + assert simplify(A1.pinv(method="RD")) == simplify(A1.inv()) + + # Test the four properties of the pseudoinverse for various matrices. + As = [Matrix([[13, 104], [2212, 3], [-3, 5]]), + Matrix([[1, 7, 9], [11, 17, 19]]), + Matrix([a, b])] + + for A in As: + A_pinv = A.pinv(method="RD") + AAp = A * A_pinv + ApA = A_pinv * A + assert simplify(AAp * A) == A + assert simplify(ApA * A_pinv) == A_pinv + assert AAp.H == AAp + assert ApA.H == ApA + + # XXX Pinv with diagonalization makes expression too complicated. + for A in As: + A_pinv = simplify(A.pinv(method="ED")) + AAp = A * A_pinv + ApA = A_pinv * A + assert simplify(AAp * A) == A + assert simplify(ApA * A_pinv) == A_pinv + assert AAp.H == AAp + assert ApA.H == ApA + + # XXX Computing pinv using diagonalization makes an expression that + # is too complicated to simplify. + # A1 = Matrix([[a, b], [c, d]]) + # assert simplify(A1.pinv(method="ED")) == simplify(A1.inv()) + # so this is tested numerically at a fixed random point + + from sympy.core.numbers import comp + q = A1.pinv(method="ED") + w = A1.inv() + reps = {a: -73633, b: 11362, c: 55486, d: 62570} + assert all( + comp(i.n(), j.n()) + for i, j in zip(q.subs(reps), w.subs(reps)) + ) + + +@slow +def test_pinv_rank_deficient_when_diagonalization_fails(): + # Test the four properties of the pseudoinverse for matrices when + # diagonalization of A.H*A fails. + As = [ + Matrix([ + [61, 89, 55, 20, 71, 0], + [62, 96, 85, 85, 16, 0], + [69, 56, 17, 4, 54, 0], + [10, 54, 91, 41, 71, 0], + [ 7, 30, 10, 48, 90, 0], + [0, 0, 0, 0, 0, 0]]) + ] + for A in As: + A_pinv = A.pinv(method="ED") + AAp = A * A_pinv + ApA = A_pinv * A + assert AAp.H == AAp + + # Here ApA.H and ApA are equivalent expressions but they are very + # complicated expressions involving RootOfs. Using simplify would be + # too slow and so would evalf so we substitute approximate values for + # the RootOfs and then evalf which is less accurate but good enough to + # confirm that these two matrices are equivalent. + # + # assert ApA.H == ApA # <--- would fail (structural equality) + # assert simplify(ApA.H - ApA).is_zero_matrix # <--- too slow + # (ApA.H - ApA).evalf() # <--- too slow + + def allclose(M1, M2): + rootofs = M1.atoms(RootOf) + rootofs_approx = {r: r.evalf() for r in rootofs} + diff_approx = (M1 - M2).xreplace(rootofs_approx).evalf() + return all(abs(e) < 1e-10 for e in diff_approx) + + assert allclose(ApA.H, ApA) + + +def test_issue_7201(): + assert ones(0, 1) + ones(0, 1) == Matrix(0, 1, []) + assert ones(1, 0) + ones(1, 0) == Matrix(1, 0, []) + + +def test_free_symbols(): + for M in ImmutableMatrix, ImmutableSparseMatrix, Matrix, SparseMatrix: + assert M([[x], [0]]).free_symbols == {x} + + +def test_from_ndarray(): + """See issue 7465.""" + try: + from numpy import array + except ImportError: + skip('NumPy must be available to test creating matrices from ndarrays') + + assert Matrix(array([1, 2, 3])) == Matrix([1, 2, 3]) + assert Matrix(array([[1, 2, 3]])) == Matrix([[1, 2, 3]]) + assert Matrix(array([[1, 2, 3], [4, 5, 6]])) == \ + Matrix([[1, 2, 3], [4, 5, 6]]) + assert Matrix(array([x, y, z])) == Matrix([x, y, z]) + raises(NotImplementedError, + lambda: Matrix(array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]))) + assert Matrix([array([1, 2]), array([3, 4])]) == Matrix([[1, 2], [3, 4]]) + assert Matrix([array([1, 2]), [3, 4]]) == Matrix([[1, 2], [3, 4]]) + assert Matrix([array([]), array([])]) == Matrix(2, 0, []) != Matrix([]) + + +def test_17522_numpy(): + from sympy.matrices.common import _matrixify + try: + from numpy import array, matrix + except ImportError: + skip('NumPy must be available to test indexing matrixified NumPy ndarrays and matrices') + + m = _matrixify(array([[1, 2], [3, 4]])) + assert m[3] == 4 + assert list(m) == [1, 2, 3, 4] + + with ignore_warnings(PendingDeprecationWarning): + m = _matrixify(matrix([[1, 2], [3, 4]])) + assert m[3] == 4 + assert list(m) == [1, 2, 3, 4] + + +def test_17522_mpmath(): + from sympy.matrices.common import _matrixify + try: + from mpmath import matrix + except ImportError: + skip('mpmath must be available to test indexing matrixified mpmath matrices') + + m = _matrixify(matrix([[1, 2], [3, 4]])) + assert m[3] == 4.0 + assert list(m) == [1.0, 2.0, 3.0, 4.0] + + +def test_17522_scipy(): + from sympy.matrices.common import _matrixify + try: + from scipy.sparse import csr_matrix + except ImportError: + skip('SciPy must be available to test indexing matrixified SciPy sparse matrices') + + m = _matrixify(csr_matrix([[1, 2], [3, 4]])) + assert m[3] == 4 + assert list(m) == [1, 2, 3, 4] + + +def test_hermitian(): + a = Matrix([[1, I], [-I, 1]]) + assert a.is_hermitian + a[0, 0] = 2*I + assert a.is_hermitian is False + a[0, 0] = x + assert a.is_hermitian is None + a[0, 1] = a[1, 0]*I + assert a.is_hermitian is False + + +def test_issue_9457_9467_9876(): + # for row_del(index) + M = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + M.row_del(1) + assert M == Matrix([[1, 2, 3], [3, 4, 5]]) + N = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + N.row_del(-2) + assert N == Matrix([[1, 2, 3], [3, 4, 5]]) + O = Matrix([[1, 2, 3], [5, 6, 7], [9, 10, 11]]) + O.row_del(-1) + assert O == Matrix([[1, 2, 3], [5, 6, 7]]) + P = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(IndexError, lambda: P.row_del(10)) + Q = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(IndexError, lambda: Q.row_del(-10)) + + # for col_del(index) + M = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + M.col_del(1) + assert M == Matrix([[1, 3], [2, 4], [3, 5]]) + N = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + N.col_del(-2) + assert N == Matrix([[1, 3], [2, 4], [3, 5]]) + P = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(IndexError, lambda: P.col_del(10)) + Q = Matrix([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + raises(IndexError, lambda: Q.col_del(-10)) + + +def test_issue_9422(): + x, y = symbols('x y', commutative=False) + a, b = symbols('a b') + M = eye(2) + M1 = Matrix(2, 2, [x, y, y, z]) + assert y*x*M != x*y*M + assert b*a*M == a*b*M + assert x*M1 != M1*x + assert a*M1 == M1*a + assert y*x*M == Matrix([[y*x, 0], [0, y*x]]) + + +def test_issue_10770(): + M = Matrix([]) + a = ['col_insert', 'row_join'], Matrix([9, 6, 3]) + b = ['row_insert', 'col_join'], a[1].T + c = ['row_insert', 'col_insert'], Matrix([[1, 2], [3, 4]]) + for ops, m in (a, b, c): + for op in ops: + f = getattr(M, op) + new = f(m) if 'join' in op else f(42, m) + assert new == m and id(new) != id(m) + + +def test_issue_10658(): + A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert A.extract([0, 1, 2], [True, True, False]) == \ + Matrix([[1, 2], [4, 5], [7, 8]]) + assert A.extract([0, 1, 2], [True, False, False]) == Matrix([[1], [4], [7]]) + assert A.extract([True, False, False], [0, 1, 2]) == Matrix([[1, 2, 3]]) + assert A.extract([True, False, True], [0, 1, 2]) == \ + Matrix([[1, 2, 3], [7, 8, 9]]) + assert A.extract([0, 1, 2], [False, False, False]) == Matrix(3, 0, []) + assert A.extract([False, False, False], [0, 1, 2]) == Matrix(0, 3, []) + assert A.extract([True, False, True], [False, True, False]) == \ + Matrix([[2], [8]]) + + +def test_opportunistic_simplification(): + # this test relates to issue #10718, #9480, #11434 + + # issue #9480 + m = Matrix([[-5 + 5*sqrt(2), -5], [-5*sqrt(2)/2 + 5, -5*sqrt(2)/2]]) + assert m.rank() == 1 + + # issue #10781 + m = Matrix([[3+3*sqrt(3)*I, -9],[4,-3+3*sqrt(3)*I]]) + assert simplify(m.rref()[0] - Matrix([[1, -9/(3 + 3*sqrt(3)*I)], [0, 0]])) == zeros(2, 2) + + # issue #11434 + ax,ay,bx,by,cx,cy,dx,dy,ex,ey,t0,t1 = symbols('a_x a_y b_x b_y c_x c_y d_x d_y e_x e_y t_0 t_1') + m = Matrix([[ax,ay,ax*t0,ay*t0,0],[bx,by,bx*t0,by*t0,0],[cx,cy,cx*t0,cy*t0,1],[dx,dy,dx*t0,dy*t0,1],[ex,ey,2*ex*t1-ex*t0,2*ey*t1-ey*t0,0]]) + assert m.rank() == 4 + + +def test_partial_pivoting(): + # example from https://en.wikipedia.org/wiki/Pivot_element + # partial pivoting with back substitution gives a perfect result + # naive pivoting give an error ~1e-13, so anything better than + # 1e-15 is good + mm=Matrix([[0.003, 59.14, 59.17], [5.291, -6.13, 46.78]]) + assert (mm.rref()[0] - Matrix([[1.0, 0, 10.0], + [ 0, 1.0, 1.0]])).norm() < 1e-15 + + # issue #11549 + m_mixed = Matrix([[6e-17, 1.0, 4], + [ -1.0, 0, 8], + [ 0, 0, 1]]) + m_float = Matrix([[6e-17, 1.0, 4.], + [ -1.0, 0., 8.], + [ 0., 0., 1.]]) + m_inv = Matrix([[ 0, -1.0, 8.0], + [1.0, 6.0e-17, -4.0], + [ 0, 0, 1]]) + # this example is numerically unstable and involves a matrix with a norm >= 8, + # this comparing the difference of the results with 1e-15 is numerically sound. + assert (m_mixed.inv() - m_inv).norm() < 1e-15 + assert (m_float.inv() - m_inv).norm() < 1e-15 + + +def test_iszero_substitution(): + """ When doing numerical computations, all elements that pass + the iszerofunc test should be set to numerically zero if they + aren't already. """ + + # Matrix from issue #9060 + m = Matrix([[0.9, -0.1, -0.2, 0],[-0.8, 0.9, -0.4, 0],[-0.1, -0.8, 0.6, 0]]) + m_rref = m.rref(iszerofunc=lambda x: abs(x)<6e-15)[0] + m_correct = Matrix([[1.0, 0, -0.301369863013699, 0],[ 0, 1.0, -0.712328767123288, 0],[ 0, 0, 0, 0]]) + m_diff = m_rref - m_correct + assert m_diff.norm() < 1e-15 + # if a zero-substitution wasn't made, this entry will be -1.11022302462516e-16 + assert m_rref[2,2] == 0 + + +def test_issue_11238(): + from sympy.geometry.point import Point + xx = 8*tan(pi*Rational(13, 45))/(tan(pi*Rational(13, 45)) + sqrt(3)) + yy = (-8*sqrt(3)*tan(pi*Rational(13, 45))**2 + 24*tan(pi*Rational(13, 45)))/(-3 + tan(pi*Rational(13, 45))**2) + p1 = Point(0, 0) + p2 = Point(1, -sqrt(3)) + p0 = Point(xx,yy) + m1 = Matrix([p1 - simplify(p0), p2 - simplify(p0)]) + m2 = Matrix([p1 - p0, p2 - p0]) + m3 = Matrix([simplify(p1 - p0), simplify(p2 - p0)]) + + # This system has expressions which are zero and + # cannot be easily proved to be such, so without + # numerical testing, these assertions will fail. + Z = lambda x: abs(x.n()) < 1e-20 + assert m1.rank(simplify=True, iszerofunc=Z) == 1 + assert m2.rank(simplify=True, iszerofunc=Z) == 1 + assert m3.rank(simplify=True, iszerofunc=Z) == 1 + + +def test_as_real_imag(): + m1 = Matrix(2,2,[1,2,3,4]) + m2 = m1*S.ImaginaryUnit + m3 = m1 + m2 + + for kls in all_classes: + a,b = kls(m3).as_real_imag() + assert list(a) == list(m1) + assert list(b) == list(m1) + + +def test_deprecated(): + # Maintain tests for deprecated functions. We must capture + # the deprecation warnings. When the deprecated functionality is + # removed, the corresponding tests should be removed. + + m = Matrix(3, 3, [0, 1, 0, -4, 4, 0, -2, 1, 2]) + P, Jcells = m.jordan_cells() + assert Jcells[1] == Matrix(1, 1, [2]) + assert Jcells[0] == Matrix(2, 2, [2, 1, 0, 2]) + + +def test_issue_14489(): + from sympy.core.mod import Mod + A = Matrix([-1, 1, 2]) + B = Matrix([10, 20, -15]) + + assert Mod(A, 3) == Matrix([2, 1, 2]) + assert Mod(B, 4) == Matrix([2, 0, 1]) + + +def test_issue_14943(): + # Test that __array__ accepts the optional dtype argument + try: + from numpy import array + except ImportError: + skip('NumPy must be available to test creating matrices from ndarrays') + + M = Matrix([[1,2], [3,4]]) + assert array(M, dtype=float).dtype.name == 'float64' + + +def test_case_6913(): + m = MatrixSymbol('m', 1, 1) + a = Symbol("a") + a = m[0, 0]>0 + assert str(a) == 'm[0, 0] > 0' + + +def test_issue_11948(): + A = MatrixSymbol('A', 3, 3) + a = Wild('a') + assert A.match(a) == {a: A} + + +def test_gramschmidt_conjugate_dot(): + vecs = [Matrix([1, I]), Matrix([1, -I])] + assert Matrix.orthogonalize(*vecs) == \ + [Matrix([[1], [I]]), Matrix([[1], [-I]])] + + vecs = [Matrix([1, I, 0]), Matrix([I, 0, -I])] + assert Matrix.orthogonalize(*vecs) == \ + [Matrix([[1], [I], [0]]), Matrix([[I/2], [S(1)/2], [-I]])] + + mat = Matrix([[1, I], [1, -I]]) + Q, R = mat.QRdecomposition() + assert Q * Q.H == Matrix.eye(2) + + +def test_issue_8207(): + a = Matrix(MatrixSymbol('a', 3, 1)) + b = Matrix(MatrixSymbol('b', 3, 1)) + c = a.dot(b) + d = diff(c, a[0, 0]) + e = diff(d, a[0, 0]) + assert d == b[0, 0] + assert e == 0 + + +def test_func(): + from sympy.simplify.simplify import nthroot + + A = Matrix([[1, 2],[0, 3]]) + assert A.analytic_func(sin(x*t), x) == Matrix([[sin(t), sin(3*t) - sin(t)], [0, sin(3*t)]]) + + A = Matrix([[2, 1],[1, 2]]) + assert (pi * A / 6).analytic_func(cos(x), x) == Matrix([[sqrt(3)/4, -sqrt(3)/4], [-sqrt(3)/4, sqrt(3)/4]]) + + + raises(ValueError, lambda : zeros(5).analytic_func(log(x), x)) + raises(ValueError, lambda : (A*x).analytic_func(log(x), x)) + + A = Matrix([[0, -1, -2, 3], [0, -1, -2, 3], [0, 1, 0, -1], [0, 0, -1, 1]]) + assert A.analytic_func(exp(x), x) == A.exp() + raises(ValueError, lambda : A.analytic_func(sqrt(x), x)) + + A = Matrix([[41, 12],[12, 34]]) + assert simplify(A.analytic_func(sqrt(x), x)**2) == A + + A = Matrix([[3, -12, 4], [-1, 0, -2], [-1, 5, -1]]) + assert simplify(A.analytic_func(nthroot(x, 3), x)**3) == A + + A = Matrix([[2, 0, 0, 0], [1, 2, 0, 0], [0, 1, 3, 0], [0, 0, 1, 3]]) + assert A.analytic_func(exp(x), x) == A.exp() + + A = Matrix([[0, 2, 1, 6], [0, 0, 1, 2], [0, 0, 0, 3], [0, 0, 0, 0]]) + assert A.analytic_func(exp(x*t), x) == expand(simplify((A*t).exp())) + + +@skip_under_pyodide("Cannot create threads under pyodide.") +def test_issue_19809(): + + def f(): + assert _dotprodsimp_state.state == None + m = Matrix([[1]]) + m = m * m + return True + + with dotprodsimp(True): + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(f) + assert future.result() + + +def test_issue_23276(): + M = Matrix([x, y]) + assert integrate(M, (x, 0, 1), (y, 0, 1)) == Matrix([ + [S.Half], + [S.Half]]) + + +def test_issue_27225(): + # https://github.com/sympy/sympy/issues/27225 + raises(TypeError, lambda : floor(Matrix([1, 1, 0]))) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_normalforms.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_normalforms.py new file mode 100644 index 0000000000000000000000000000000000000000..47ee52d73539f7fb79295443e1cf7e0a49e30a5e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_normalforms.py @@ -0,0 +1,111 @@ +from sympy.testing.pytest import warns_deprecated_sympy + +from sympy.core.symbol import Symbol +from sympy.polys.polytools import Poly +from sympy.matrices import Matrix, randMatrix +from sympy.matrices.normalforms import ( + invariant_factors, + smith_normal_form, + smith_normal_decomp, + hermite_normal_form, + is_smith_normal_form, +) +from sympy.polys.domains import ZZ, QQ +from sympy.core.numbers import Integer + +import random + + +def test_smith_normal(): + m = Matrix([[12,6,4,8],[3,9,6,12],[2,16,14,28],[20,10,10,20]]) + smf = Matrix([[1, 0, 0, 0], [0, 10, 0, 0], [0, 0, 30, 0], [0, 0, 0, 0]]) + assert smith_normal_form(m) == smf + + a, s, t = smith_normal_decomp(m) + assert a == s * m * t + + x = Symbol('x') + with warns_deprecated_sympy(): + m = Matrix([[Poly(x-1), Poly(1, x),Poly(-1,x)], + [0, Poly(x), Poly(-1,x)], + [Poly(0,x),Poly(-1,x),Poly(x)]]) + invs = 1, x - 1, x**2 - 1 + assert invariant_factors(m, domain=QQ[x]) == invs + + m = Matrix([[2, 4]]) + smf = Matrix([[2, 0]]) + assert smith_normal_form(m) == smf + + prng = random.Random(0) + for i in range(6): + for j in range(6): + for _ in range(10 if i*j else 1): + m = randMatrix(i, j, max=5, percent=50, prng=prng) + a, s, t = smith_normal_decomp(m) + assert a == s * m * t + assert is_smith_normal_form(a) + s.inv().to_DM(ZZ) + t.inv().to_DM(ZZ) + + a, s, t = smith_normal_decomp(m, QQ) + assert a == s * m * t + assert is_smith_normal_form(a) + s.inv() + t.inv() + + +def test_smith_normal_deprecated(): + from sympy.polys.solvers import RawMatrix as Matrix + + with warns_deprecated_sympy(): + m = Matrix([[12, 6, 4,8],[3,9,6,12],[2,16,14,28],[20,10,10,20]]) + setattr(m, 'ring', ZZ) + with warns_deprecated_sympy(): + smf = Matrix([[1, 0, 0, 0], [0, 10, 0, 0], [0, 0, 30, 0], [0, 0, 0, 0]]) + assert smith_normal_form(m) == smf + + x = Symbol('x') + with warns_deprecated_sympy(): + m = Matrix([[Poly(x-1), Poly(1, x),Poly(-1,x)], + [0, Poly(x), Poly(-1,x)], + [Poly(0,x),Poly(-1,x),Poly(x)]]) + setattr(m, 'ring', QQ[x]) + invs = (Poly(1, x, domain='QQ'), Poly(x - 1, domain='QQ'), Poly(x**2 - 1, domain='QQ')) + assert invariant_factors(m) == invs + + with warns_deprecated_sympy(): + m = Matrix([[2, 4]]) + setattr(m, 'ring', ZZ) + with warns_deprecated_sympy(): + smf = Matrix([[2, 0]]) + assert smith_normal_form(m) == smf + + +def test_hermite_normal(): + m = Matrix([[2, 7, 17, 29, 41], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]]) + hnf = Matrix([[1, 0, 0], [0, 2, 1], [0, 0, 1]]) + assert hermite_normal_form(m) == hnf + + tr_hnf = Matrix([[37, 0, 19], [222, -6, 113], [48, 0, 25], [0, 2, 1], [0, 0, 1]]) + assert hermite_normal_form(m.transpose()) == tr_hnf + + m = Matrix([[8, 28, 68, 116, 164], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]]) + hnf = Matrix([[4, 0, 0], [0, 2, 1], [0, 0, 1]]) + assert hermite_normal_form(m) == hnf + assert hermite_normal_form(m, D=8) == hnf + assert hermite_normal_form(m, D=ZZ(8)) == hnf + assert hermite_normal_form(m, D=Integer(8)) == hnf + + m = Matrix([[10, 8, 6, 30, 2], [45, 36, 27, 18, 9], [5, 4, 3, 2, 1]]) + hnf = Matrix([[26, 2], [0, 9], [0, 1]]) + assert hermite_normal_form(m) == hnf + + m = Matrix([[2, 7], [0, 0], [0, 0]]) + hnf = Matrix([[1], [0], [0]]) + assert hermite_normal_form(m) == hnf + + +def test_issue_23410(): + A = Matrix([[1, 12], [0, 8], [0, 5]]) + H = Matrix([[1, 0], [0, 8], [0, 5]]) + assert hermite_normal_form(A) == H diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_reductions.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_reductions.py new file mode 100644 index 0000000000000000000000000000000000000000..32c98c6f249b1afafc8193f4248dc9493bb803e0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_reductions.py @@ -0,0 +1,351 @@ +from sympy.core.numbers import I +from sympy.core.symbol import symbols +from sympy.testing.pytest import raises +from sympy.matrices import Matrix, zeros, eye +from sympy.core.symbol import Symbol +from sympy.core.numbers import Rational +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.simplify.simplify import simplify +from sympy.abc import x + + +# Matrix tests +def test_row_op(): + e = eye(3) + + raises(ValueError, lambda: e.elementary_row_op("abc")) + raises(ValueError, lambda: e.elementary_row_op()) + raises(ValueError, lambda: e.elementary_row_op('n->kn', row=5, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->kn', row=-5, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=1, row2=5)) + raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=5, row2=1)) + raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=-5, row2=1)) + raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=1, row2=-5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=5, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=5, row2=1, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=-5, row2=1, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=-5, k=5)) + raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=1, k=5)) + + # test various ways to set arguments + assert e.elementary_row_op("n->kn", 0, 5) == Matrix([[5, 0, 0], [0, 1, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->kn", 1, 5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->kn", row=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->kn", row1=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_row_op("n<->m", 0, 1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_row_op("n<->m", row1=0, row2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_row_op("n<->m", row=0, row2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->n+km", 0, 5, 1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->n+km", row=0, k=5, row2=1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]]) + assert e.elementary_row_op("n->n+km", row1=0, k=5, row2=1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]]) + + # make sure the matrix doesn't change size + a = Matrix(2, 3, [0]*6) + assert a.elementary_row_op("n->kn", 1, 5) == Matrix(2, 3, [0]*6) + assert a.elementary_row_op("n<->m", 0, 1) == Matrix(2, 3, [0]*6) + assert a.elementary_row_op("n->n+km", 0, 5, 1) == Matrix(2, 3, [0]*6) + + +def test_col_op(): + e = eye(3) + + raises(ValueError, lambda: e.elementary_col_op("abc")) + raises(ValueError, lambda: e.elementary_col_op()) + raises(ValueError, lambda: e.elementary_col_op('n->kn', col=5, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->kn', col=-5, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=1, col2=5)) + raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=5, col2=1)) + raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=-5, col2=1)) + raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=1, col2=-5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=5, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=5, col2=1, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=-5, col2=1, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=-5, k=5)) + raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=1, k=5)) + + # test various ways to set arguments + assert e.elementary_col_op("n->kn", 0, 5) == Matrix([[5, 0, 0], [0, 1, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->kn", 1, 5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->kn", col=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->kn", col1=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]]) + assert e.elementary_col_op("n<->m", 0, 1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_col_op("n<->m", col1=0, col2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_col_op("n<->m", col=0, col2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->n+km", 0, 5, 1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->n+km", col=0, k=5, col2=1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]]) + assert e.elementary_col_op("n->n+km", col1=0, k=5, col2=1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]]) + + # make sure the matrix doesn't change size + a = Matrix(2, 3, [0]*6) + assert a.elementary_col_op("n->kn", 1, 5) == Matrix(2, 3, [0]*6) + assert a.elementary_col_op("n<->m", 0, 1) == Matrix(2, 3, [0]*6) + assert a.elementary_col_op("n->n+km", 0, 5, 1) == Matrix(2, 3, [0]*6) + + +def test_is_echelon(): + zro = zeros(3) + ident = eye(3) + + assert zro.is_echelon + assert ident.is_echelon + + a = Matrix(0, 0, []) + assert a.is_echelon + + a = Matrix(2, 3, [3, 2, 1, 0, 0, 6]) + assert a.is_echelon + + a = Matrix(2, 3, [0, 0, 6, 3, 2, 1]) + assert not a.is_echelon + + x = Symbol('x') + a = Matrix(3, 1, [x, 0, 0]) + assert a.is_echelon + + a = Matrix(3, 1, [x, x, 0]) + assert not a.is_echelon + + a = Matrix(3, 3, [0, 0, 0, 1, 2, 3, 0, 0, 0]) + assert not a.is_echelon + + +def test_echelon_form(): + # echelon form is not unique, but the result + # must be row-equivalent to the original matrix + # and it must be in echelon form. + + a = zeros(3) + e = eye(3) + + # we can assume the zero matrix and the identity matrix shouldn't change + assert a.echelon_form() == a + assert e.echelon_form() == e + + a = Matrix(0, 0, []) + assert a.echelon_form() == a + + a = Matrix(1, 1, [5]) + assert a.echelon_form() == a + + # now we get to the real tests + + def verify_row_null_space(mat, rows, nulls): + for v in nulls: + assert all(t.is_zero for t in a_echelon*v) + for v in rows: + if not all(t.is_zero for t in v): + assert not all(t.is_zero for t in a_echelon*v.transpose()) + + a = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + nulls = [Matrix([ + [ 1], + [-2], + [ 1]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + + a = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 8]) + nulls = [] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + a = Matrix(3, 3, [2, 1, 3, 0, 0, 0, 2, 1, 3]) + nulls = [Matrix([ + [Rational(-1, 2)], + [ 1], + [ 0]]), + Matrix([ + [Rational(-3, 2)], + [ 0], + [ 1]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + # this one requires a row swap + a = Matrix(3, 3, [2, 1, 3, 0, 0, 0, 1, 1, 3]) + nulls = [Matrix([ + [ 0], + [ -3], + [ 1]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + a = Matrix(3, 3, [0, 3, 3, 0, 2, 2, 0, 1, 1]) + nulls = [Matrix([ + [1], + [0], + [0]]), + Matrix([ + [ 0], + [-1], + [ 1]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + a = Matrix(2, 3, [2, 2, 3, 3, 3, 0]) + nulls = [Matrix([ + [-1], + [1], + [0]])] + rows = [a[i, :] for i in range(a.rows)] + a_echelon = a.echelon_form() + assert a_echelon.is_echelon + verify_row_null_space(a, rows, nulls) + + +def test_rref(): + e = Matrix(0, 0, []) + assert e.rref(pivots=False) == e + + e = Matrix(1, 1, [1]) + a = Matrix(1, 1, [5]) + assert e.rref(pivots=False) == a.rref(pivots=False) == e + + a = Matrix(3, 1, [1, 2, 3]) + assert a.rref(pivots=False) == Matrix([[1], [0], [0]]) + + a = Matrix(1, 3, [1, 2, 3]) + assert a.rref(pivots=False) == Matrix([[1, 2, 3]]) + + a = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + assert a.rref(pivots=False) == Matrix([ + [1, 0, -1], + [0, 1, 2], + [0, 0, 0]]) + + a = Matrix(3, 3, [1, 2, 3, 1, 2, 3, 1, 2, 3]) + b = Matrix(3, 3, [1, 2, 3, 0, 0, 0, 0, 0, 0]) + c = Matrix(3, 3, [0, 0, 0, 1, 2, 3, 0, 0, 0]) + d = Matrix(3, 3, [0, 0, 0, 0, 0, 0, 1, 2, 3]) + assert a.rref(pivots=False) == \ + b.rref(pivots=False) == \ + c.rref(pivots=False) == \ + d.rref(pivots=False) == b + + e = eye(3) + z = zeros(3) + assert e.rref(pivots=False) == e + assert z.rref(pivots=False) == z + + a = Matrix([ + [ 0, 0, 1, 2, 2, -5, 3], + [-1, 5, 2, 2, 1, -7, 5], + [ 0, 0, -2, -3, -3, 8, -5], + [-1, 5, 0, -1, -2, 1, 0]]) + mat, pivot_offsets = a.rref() + assert mat == Matrix([ + [1, -5, 0, 0, 1, 1, -1], + [0, 0, 1, 0, 0, -1, 1], + [0, 0, 0, 1, 1, -2, 1], + [0, 0, 0, 0, 0, 0, 0]]) + assert pivot_offsets == (0, 2, 3) + + a = Matrix([[Rational(1, 19), Rational(1, 5), 2, 3], + [ 4, 5, 6, 7], + [ 8, 9, 10, 11], + [ 12, 13, 14, 15]]) + assert a.rref(pivots=False) == Matrix([ + [1, 0, 0, Rational(-76, 157)], + [0, 1, 0, Rational(-5, 157)], + [0, 0, 1, Rational(238, 157)], + [0, 0, 0, 0]]) + + x = Symbol('x') + a = Matrix(2, 3, [x, 1, 1, sqrt(x), x, 1]) + for i, j in zip(a.rref(pivots=False), + [1, 0, sqrt(x)*(-x + 1)/(-x**Rational(5, 2) + x), + 0, 1, 1/(sqrt(x) + x + 1)]): + assert simplify(i - j).is_zero + + +def test_rref_rhs(): + a, b, c, d = symbols('a b c d') + A = Matrix([[0, 0], [0, 0], [1, 2], [3, 4]]) + B = Matrix([a, b, c, d]) + assert A.rref_rhs(B) == (Matrix([ + [1, 0], + [0, 1], + [0, 0], + [0, 0]]), Matrix([ + [ -2*c + d], + [3*c/2 - d/2], + [ a], + [ b]])) + + +def test_issue_17827(): + C = Matrix([ + [3, 4, -1, 1], + [9, 12, -3, 3], + [0, 2, 1, 3], + [2, 3, 0, -2], + [0, 3, 3, -5], + [8, 15, 0, 6] + ]) + # Tests for row/col within valid range + D = C.elementary_row_op('n<->m', row1=2, row2=5) + E = C.elementary_row_op('n->n+km', row1=5, row2=3, k=-4) + F = C.elementary_row_op('n->kn', row=5, k=2) + assert(D[5, :] == Matrix([[0, 2, 1, 3]])) + assert(E[5, :] == Matrix([[0, 3, 0, 14]])) + assert(F[5, :] == Matrix([[16, 30, 0, 12]])) + # Tests for row/col out of range + raises(ValueError, lambda: C.elementary_row_op('n<->m', row1=2, row2=6)) + raises(ValueError, lambda: C.elementary_row_op('n->kn', row=7, k=2)) + raises(ValueError, lambda: C.elementary_row_op('n->n+km', row1=-1, row2=5, k=2)) + +def test_rank(): + m = Matrix([[1, 2], [x, 1 - 1/x]]) + assert m.rank() == 2 + n = Matrix(3, 3, range(1, 10)) + assert n.rank() == 2 + p = zeros(3) + assert p.rank() == 0 + +def test_issue_11434(): + ax, ay, bx, by, cx, cy, dx, dy, ex, ey, t0, t1 = \ + symbols('a_x a_y b_x b_y c_x c_y d_x d_y e_x e_y t_0 t_1') + M = Matrix([[ax, ay, ax*t0, ay*t0, 0], + [bx, by, bx*t0, by*t0, 0], + [cx, cy, cx*t0, cy*t0, 1], + [dx, dy, dx*t0, dy*t0, 1], + [ex, ey, 2*ex*t1 - ex*t0, 2*ey*t1 - ey*t0, 0]]) + assert M.rank() == 4 + +def test_rank_regression_from_so(): + # see: + # https://stackoverflow.com/questions/19072700/why-does-sympy-give-me-the-wrong-answer-when-i-row-reduce-a-symbolic-matrix + + nu, lamb = symbols('nu, lambda') + A = Matrix([[-3*nu, 1, 0, 0], + [ 3*nu, -2*nu - 1, 2, 0], + [ 0, 2*nu, (-1*nu) - lamb - 2, 3], + [ 0, 0, nu + lamb, -3]]) + expected_reduced = Matrix([[1, 0, 0, 1/(nu**2*(-lamb - nu))], + [0, 1, 0, 3/(nu*(-lamb - nu))], + [0, 0, 1, 3/(-lamb - nu)], + [0, 0, 0, 0]]) + expected_pivots = (0, 1, 2) + + reduced, pivots = A.rref() + + assert simplify(expected_reduced - reduced) == zeros(*A.shape) + assert pivots == expected_pivots + +def test_issue_15872(): + A = Matrix([[1, 1, 1, 0], [-2, -1, 0, -1], [0, 0, -1, -1], [0, 0, 2, 1]]) + B = A - Matrix.eye(4) * I + assert B.rank() == 3 + assert (B**2).rank() == 2 + assert (B**3).rank() == 2 diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_repmatrix.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_repmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..ee36de004705f29eaa49ea8e06fd65a8a2baa718 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_repmatrix.py @@ -0,0 +1,62 @@ +from sympy.testing.pytest import raises +from sympy.matrices.exceptions import NonSquareMatrixError, NonInvertibleMatrixError + +from sympy import Matrix, Rational + + +def test_lll(): + A = Matrix([[1, 0, 0, 0, -20160], + [0, 1, 0, 0, 33768], + [0, 0, 1, 0, 39578], + [0, 0, 0, 1, 47757]]) + L = Matrix([[ 10, -3, -2, 8, -4], + [ 3, -9, 8, 1, -11], + [ -3, 13, -9, -3, -9], + [-12, -7, -11, 9, -1]]) + T = Matrix([[ 10, -3, -2, 8], + [ 3, -9, 8, 1], + [ -3, 13, -9, -3], + [-12, -7, -11, 9]]) + assert A.lll() == L + assert A.lll_transform() == (L, T) + assert T * A == L + + +def test_matrix_inv_mod(): + A = Matrix(2, 1, [1, 0]) + raises(NonSquareMatrixError, lambda: A.inv_mod(2)) + A = Matrix(2, 2, [1, 0, 0, 0]) + raises(NonInvertibleMatrixError, lambda: A.inv_mod(2)) + A = Matrix(2, 2, [1, 2, 3, 4]) + Ai = Matrix(2, 2, [1, 1, 0, 1]) + assert A.inv_mod(3) == Ai + A = Matrix(2, 2, [1, 0, 0, 1]) + assert A.inv_mod(2) == A + A = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + raises(NonInvertibleMatrixError, lambda: A.inv_mod(5)) + A = Matrix(3, 3, [5, 1, 3, 2, 6, 0, 2, 1, 1]) + Ai = Matrix(3, 3, [6, 8, 0, 1, 5, 6, 5, 6, 4]) + assert A.inv_mod(9) == Ai + A = Matrix(3, 3, [1, 6, -3, 4, 1, -5, 3, -5, 5]) + Ai = Matrix(3, 3, [4, 3, 3, 1, 2, 5, 1, 5, 1]) + assert A.inv_mod(6) == Ai + A = Matrix(3, 3, [1, 6, 1, 4, 1, 5, 3, 2, 5]) + Ai = Matrix(3, 3, [6, 0, 3, 6, 6, 4, 1, 6, 1]) + assert A.inv_mod(7) == Ai + A = Matrix([[1, 2], [3, Rational(3,4)]]) + raises(ValueError, lambda: A.inv_mod(2)) + A = Matrix([[1, 2], [3, 4]]) + raises(TypeError, lambda: A.inv_mod(Rational(1, 2))) + # https://github.com/sympy/sympy/issues/27663 + M = Matrix([ + [2, 3, 1, 4], + [1, 5, 3, 2], + [3, 2, 4, 1], + [4, 1, 2, 5], + ]) + assert M.inv_mod(26) == Matrix([ + [7, 21, 10, 10], + [1, 7, 19, 3], + [14, 1, 15, 1], + [25, 23, 3, 12], + ]) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_solvers.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_solvers.py new file mode 100644 index 0000000000000000000000000000000000000000..c1347062c0482336affbeb4bb9a95aedfcc0ae53 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_solvers.py @@ -0,0 +1,615 @@ +import pytest +from sympy.core.function import expand_mul +from sympy.core.numbers import (I, Rational) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.simplify.simplify import simplify +from sympy.matrices.exceptions import (ShapeError, NonSquareMatrixError) +from sympy.matrices import ( + ImmutableMatrix, Matrix, eye, ones, ImmutableDenseMatrix, dotprodsimp) +from sympy.matrices.determinant import _det_laplace +from sympy.testing.pytest import raises +from sympy.matrices.exceptions import NonInvertibleMatrixError +from sympy.polys.matrices.exceptions import DMShapeError +from sympy.solvers.solveset import linsolve +from sympy.abc import x, y + +def test_issue_17247_expression_blowup_29(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.gauss_jordan_solve(ones(4, 1)) == (Matrix(S('''[ + [ -32549314808672/3306971225785 - 17397006745216*I/3306971225785], + [ 67439348256/3306971225785 - 9167503335872*I/3306971225785], + [-15091965363354518272/21217636514687010905 + 16890163109293858304*I/21217636514687010905], + [ -11328/952745 + 87616*I/952745]]''')), Matrix(0, 1, [])) + +def test_issue_17247_expression_blowup_30(): + M = Matrix(S('''[ + [ -3/4, 45/32 - 37*I/16, 0, 0], + [-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128], + [ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0], + [ 0, 0, 0, -177/128 - 1369*I/128]]''')) + with dotprodsimp(True): + assert M.cholesky_solve(ones(4, 1)) == Matrix(S('''[ + [ -32549314808672/3306971225785 - 17397006745216*I/3306971225785], + [ 67439348256/3306971225785 - 9167503335872*I/3306971225785], + [-15091965363354518272/21217636514687010905 + 16890163109293858304*I/21217636514687010905], + [ -11328/952745 + 87616*I/952745]]''')) + +# @XFAIL # This calculation hangs with dotprodsimp. +# def test_issue_17247_expression_blowup_31(): +# M = Matrix([ +# [x + 1, 1 - x, 0, 0], +# [1 - x, x + 1, 0, x + 1], +# [ 0, 1 - x, x + 1, 0], +# [ 0, 0, 0, x + 1]]) +# with dotprodsimp(True): +# assert M.LDLsolve(ones(4, 1)) == Matrix([ +# [(x + 1)/(4*x)], +# [(x - 1)/(4*x)], +# [(x + 1)/(4*x)], +# [ 1/(x + 1)]]) + + +def test_LUsolve_iszerofunc(): + # taken from https://github.com/sympy/sympy/issues/24679 + + M = Matrix([[(x + 1)**2 - (x**2 + 2*x + 1), x], [x, 0]]) + b = Matrix([1, 1]) + is_zero_func = lambda e: False if e._random() else True + + x_exp = Matrix([1/x, (1-(-x**2 - 2*x + (x+1)**2 - 1)/x)/x]) + + assert (x_exp - M.LUsolve(b, iszerofunc=is_zero_func)) == Matrix([0, 0]) + + +def test_issue_17247_expression_blowup_32(): + M = Matrix([ + [x + 1, 1 - x, 0, 0], + [1 - x, x + 1, 0, x + 1], + [ 0, 1 - x, x + 1, 0], + [ 0, 0, 0, x + 1]]) + with dotprodsimp(True): + assert M.LUsolve(ones(4, 1)) == Matrix([ + [(x + 1)/(4*x)], + [(x - 1)/(4*x)], + [(x + 1)/(4*x)], + [ 1/(x + 1)]]) + +def test_LUsolve(): + A = Matrix([[2, 3, 5], + [3, 6, 2], + [8, 3, 6]]) + x = Matrix(3, 1, [3, 7, 5]) + b = A*x + soln = A.LUsolve(b) + assert soln == x + A = Matrix([[0, -1, 2], + [5, 10, 7], + [8, 3, 4]]) + x = Matrix(3, 1, [-1, 2, 5]) + b = A*x + soln = A.LUsolve(b) + assert soln == x + A = Matrix([[2, 1], [1, 0], [1, 0]]) # issue 14548 + b = Matrix([3, 1, 1]) + assert A.LUsolve(b) == Matrix([1, 1]) + b = Matrix([3, 1, 2]) # inconsistent + raises(ValueError, lambda: A.LUsolve(b)) + A = Matrix([[0, -1, 2], + [5, 10, 7], + [8, 3, 4], + [2, 3, 5], + [3, 6, 2], + [8, 3, 6]]) + x = Matrix([2, 1, -4]) + b = A*x + soln = A.LUsolve(b) + assert soln == x + A = Matrix([[0, -1, 2], [5, 10, 7]]) # underdetermined + x = Matrix([-1, 2, 0]) + b = A*x + raises(NotImplementedError, lambda: A.LUsolve(b)) + + A = Matrix(4, 4, lambda i, j: 1/(i+j+1) if i != 3 else 0) + b = Matrix.zeros(4, 1) + raises(NonInvertibleMatrixError, lambda: A.LUsolve(b)) + + +def test_LUsolve_noncommutative(): + a0, a1, a2, a3 = symbols("a:4", commutative=False) + b0, b1 = symbols("b:2", commutative=False) + A = Matrix([[a0, a1], [a2, a3]]) + check = A * A.LUsolve(Matrix([b0, b1])) + assert check[0, 0].expand() == b0 + # Because sympy simplification is very limited with noncommutative expressions, + # perform an explicit check with the second element + assert check[1, 0] == ( + a2*a0**(-1)*(-a1*(-a2*a0**(-1)*a1 + a3)**(-1)*(-a2*a0**(-1)*b0 + b1) + b0) + + a3*(-a2*a0**(-1)*a1 + a3)**(-1)*(-a2*a0**(-1)*b0 + b1) + ) + + +def test_QRsolve(): + A = Matrix([[2, 3, 5], + [3, 6, 2], + [8, 3, 6]]) + x = Matrix(3, 1, [3, 7, 5]) + b = A*x + soln = A.QRsolve(b) + assert soln == x + x = Matrix([[1, 2], [3, 4], [5, 6]]) + b = A*x + soln = A.QRsolve(b) + assert soln == x + + A = Matrix([[0, -1, 2], + [5, 10, 7], + [8, 3, 4]]) + x = Matrix(3, 1, [-1, 2, 5]) + b = A*x + soln = A.QRsolve(b) + assert soln == x + x = Matrix([[7, 8], [9, 10], [11, 12]]) + b = A*x + soln = A.QRsolve(b) + assert soln == x + +def test_errors(): + raises(ShapeError, lambda: Matrix([1]).LUsolve(Matrix([[1, 2], [3, 4]]))) + +def test_cholesky_solve(): + A = Matrix([[2, 3, 5], + [3, 6, 2], + [8, 3, 6]]) + x = Matrix(3, 1, [3, 7, 5]) + b = A*x + soln = A.cholesky_solve(b) + assert soln == x + A = Matrix([[0, -1, 2], + [5, 10, 7], + [8, 3, 4]]) + x = Matrix(3, 1, [-1, 2, 5]) + b = A*x + soln = A.cholesky_solve(b) + assert soln == x + A = Matrix(((1, 5), (5, 1))) + x = Matrix((4, -3)) + b = A*x + soln = A.cholesky_solve(b) + assert soln == x + A = Matrix(((9, 3*I), (-3*I, 5))) + x = Matrix((-2, 1)) + b = A*x + soln = A.cholesky_solve(b) + assert expand_mul(soln) == x + A = Matrix(((9*I, 3), (-3 + I, 5))) + x = Matrix((2 + 3*I, -1)) + b = A*x + soln = A.cholesky_solve(b) + assert expand_mul(soln) == x + a00, a01, a11, b0, b1 = symbols('a00, a01, a11, b0, b1') + A = Matrix(((a00, a01), (a01, a11))) + b = Matrix((b0, b1)) + x = A.cholesky_solve(b) + assert simplify(A*x) == b + + +def test_LDLsolve(): + A = Matrix([[2, 3, 5], + [3, 6, 2], + [8, 3, 6]]) + x = Matrix(3, 1, [3, 7, 5]) + b = A*x + soln = A.LDLsolve(b) + assert soln == x + + A = Matrix([[0, -1, 2], + [5, 10, 7], + [8, 3, 4]]) + x = Matrix(3, 1, [-1, 2, 5]) + b = A*x + soln = A.LDLsolve(b) + assert soln == x + + A = Matrix(((9, 3*I), (-3*I, 5))) + x = Matrix((-2, 1)) + b = A*x + soln = A.LDLsolve(b) + assert expand_mul(soln) == x + + A = Matrix(((9*I, 3), (-3 + I, 5))) + x = Matrix((2 + 3*I, -1)) + b = A*x + soln = A.LDLsolve(b) + assert expand_mul(soln) == x + + A = Matrix(((9, 3), (3, 9))) + x = Matrix((1, 1)) + b = A * x + soln = A.LDLsolve(b) + assert expand_mul(soln) == x + + A = Matrix([[-5, -3, -4], [-3, -7, 7]]) + x = Matrix([[8], [7], [-2]]) + b = A * x + raises(NotImplementedError, lambda: A.LDLsolve(b)) + + +def test_lower_triangular_solve(): + + raises(NonSquareMatrixError, + lambda: Matrix([1, 0]).lower_triangular_solve(Matrix([0, 1]))) + raises(ShapeError, + lambda: Matrix([[1, 0], [0, 1]]).lower_triangular_solve(Matrix([1]))) + raises(ValueError, + lambda: Matrix([[2, 1], [1, 2]]).lower_triangular_solve( + Matrix([[1, 0], [0, 1]]))) + + A = Matrix([[1, 0], [0, 1]]) + B = Matrix([[x, y], [y, x]]) + C = Matrix([[4, 8], [2, 9]]) + + assert A.lower_triangular_solve(B) == B + assert A.lower_triangular_solve(C) == C + + +def test_upper_triangular_solve(): + + raises(NonSquareMatrixError, + lambda: Matrix([1, 0]).upper_triangular_solve(Matrix([0, 1]))) + raises(ShapeError, + lambda: Matrix([[1, 0], [0, 1]]).upper_triangular_solve(Matrix([1]))) + raises(TypeError, + lambda: Matrix([[2, 1], [1, 2]]).upper_triangular_solve( + Matrix([[1, 0], [0, 1]]))) + + A = Matrix([[1, 0], [0, 1]]) + B = Matrix([[x, y], [y, x]]) + C = Matrix([[2, 4], [3, 8]]) + + assert A.upper_triangular_solve(B) == B + assert A.upper_triangular_solve(C) == C + + +def test_diagonal_solve(): + raises(TypeError, lambda: Matrix([1, 1]).diagonal_solve(Matrix([1]))) + A = Matrix([[1, 0], [0, 1]])*2 + B = Matrix([[x, y], [y, x]]) + assert A.diagonal_solve(B) == B/2 + + A = Matrix([[1, 0], [1, 2]]) + raises(TypeError, lambda: A.diagonal_solve(B)) + +def test_pinv_solve(): + # Fully determined system (unique result, identical to other solvers). + A = Matrix([[1, 5], [7, 9]]) + B = Matrix([12, 13]) + assert A.pinv_solve(B) == A.cholesky_solve(B) + assert A.pinv_solve(B) == A.LDLsolve(B) + assert A.pinv_solve(B) == Matrix([sympify('-43/26'), sympify('71/26')]) + assert A * A.pinv() * B == B + # Fully determined, with two-dimensional B matrix. + B = Matrix([[12, 13, 14], [15, 16, 17]]) + assert A.pinv_solve(B) == A.cholesky_solve(B) + assert A.pinv_solve(B) == A.LDLsolve(B) + assert A.pinv_solve(B) == Matrix([[-33, -37, -41], [69, 75, 81]]) / 26 + assert A * A.pinv() * B == B + # Underdetermined system (infinite results). + A = Matrix([[1, 0, 1], [0, 1, 1]]) + B = Matrix([5, 7]) + solution = A.pinv_solve(B) + w = {} + for s in solution.atoms(Symbol): + # Extract dummy symbols used in the solution. + w[s.name] = s + assert solution == Matrix([[w['w0_0']/3 + w['w1_0']/3 - w['w2_0']/3 + 1], + [w['w0_0']/3 + w['w1_0']/3 - w['w2_0']/3 + 3], + [-w['w0_0']/3 - w['w1_0']/3 + w['w2_0']/3 + 4]]) + assert A * A.pinv() * B == B + # Overdetermined system (least squares results). + A = Matrix([[1, 0], [0, 0], [0, 1]]) + B = Matrix([3, 2, 1]) + assert A.pinv_solve(B) == Matrix([3, 1]) + # Proof the solution is not exact. + assert A * A.pinv() * B != B + +def test_pinv_rank_deficient(): + # Test the four properties of the pseudoinverse for various matrices. + As = [Matrix([[1, 1, 1], [2, 2, 2]]), + Matrix([[1, 0], [0, 0]]), + Matrix([[1, 2], [2, 4], [3, 6]])] + + for A in As: + A_pinv = A.pinv(method="RD") + AAp = A * A_pinv + ApA = A_pinv * A + assert simplify(AAp * A) == A + assert simplify(ApA * A_pinv) == A_pinv + assert AAp.H == AAp + assert ApA.H == ApA + + for A in As: + A_pinv = A.pinv(method="ED") + AAp = A * A_pinv + ApA = A_pinv * A + assert simplify(AAp * A) == A + assert simplify(ApA * A_pinv) == A_pinv + assert AAp.H == AAp + assert ApA.H == ApA + + # Test solving with rank-deficient matrices. + A = Matrix([[1, 0], [0, 0]]) + # Exact, non-unique solution. + B = Matrix([3, 0]) + solution = A.pinv_solve(B) + w1 = solution.atoms(Symbol).pop() + assert w1.name == 'w1_0' + assert solution == Matrix([3, w1]) + assert A * A.pinv() * B == B + # Least squares, non-unique solution. + B = Matrix([3, 1]) + solution = A.pinv_solve(B) + w1 = solution.atoms(Symbol).pop() + assert w1.name == 'w1_0' + assert solution == Matrix([3, w1]) + assert A * A.pinv() * B != B + +def test_gauss_jordan_solve(): + + # Square, full rank, unique solution + A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) + b = Matrix([3, 6, 9]) + sol, params = A.gauss_jordan_solve(b) + assert sol == Matrix([[-1], [2], [0]]) + assert params == Matrix(0, 1, []) + + # Square, full rank, unique solution, B has more columns than rows + A = eye(3) + B = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) + sol, params = A.gauss_jordan_solve(B) + assert sol == B + assert params == Matrix(0, 4, []) + + # Square, reduced rank, parametrized solution + A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + b = Matrix([3, 6, 9]) + sol, params, freevar = A.gauss_jordan_solve(b, freevar=True) + w = {} + for s in sol.atoms(Symbol): + # Extract dummy symbols used in the solution. + w[s.name] = s + assert sol == Matrix([[w['tau0'] - 1], [-2*w['tau0'] + 2], [w['tau0']]]) + assert params == Matrix([[w['tau0']]]) + assert freevar == [2] + + # Square, reduced rank, parametrized solution, B has two columns + A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + B = Matrix([[3, 4], [6, 8], [9, 12]]) + sol, params, freevar = A.gauss_jordan_solve(B, freevar=True) + w = {} + for s in sol.atoms(Symbol): + # Extract dummy symbols used in the solution. + w[s.name] = s + assert sol == Matrix([[w['tau0'] - 1, w['tau1'] - Rational(4, 3)], + [-2*w['tau0'] + 2, -2*w['tau1'] + Rational(8, 3)], + [w['tau0'], w['tau1']],]) + assert params == Matrix([[w['tau0'], w['tau1']]]) + assert freevar == [2] + + # Square, reduced rank, parametrized solution + A = Matrix([[1, 2, 3], [2, 4, 6], [3, 6, 9]]) + b = Matrix([0, 0, 0]) + sol, params = A.gauss_jordan_solve(b) + w = {} + for s in sol.atoms(Symbol): + w[s.name] = s + assert sol == Matrix([[-2*w['tau0'] - 3*w['tau1']], + [w['tau0']], [w['tau1']]]) + assert params == Matrix([[w['tau0']], [w['tau1']]]) + + # Square, reduced rank, parametrized solution + A = Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + b = Matrix([0, 0, 0]) + sol, params = A.gauss_jordan_solve(b) + w = {} + for s in sol.atoms(Symbol): + w[s.name] = s + assert sol == Matrix([[w['tau0']], [w['tau1']], [w['tau2']]]) + assert params == Matrix([[w['tau0']], [w['tau1']], [w['tau2']]]) + + # Square, reduced rank, no solution + A = Matrix([[1, 2, 3], [2, 4, 6], [3, 6, 9]]) + b = Matrix([0, 0, 1]) + raises(ValueError, lambda: A.gauss_jordan_solve(b)) + + # Rectangular, tall, full rank, unique solution + A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]]) + b = Matrix([0, 0, 1, 0]) + sol, params = A.gauss_jordan_solve(b) + assert sol == Matrix([[Rational(-1, 2)], [0], [Rational(1, 6)]]) + assert params == Matrix(0, 1, []) + + # Rectangular, tall, full rank, unique solution, B has less columns than rows + A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]]) + B = Matrix([[0,0], [0, 0], [1, 2], [0, 0]]) + sol, params = A.gauss_jordan_solve(B) + assert sol == Matrix([[Rational(-1, 2), Rational(-2, 2)], [0, 0], [Rational(1, 6), Rational(2, 6)]]) + assert params == Matrix(0, 2, []) + + # Rectangular, tall, full rank, no solution + A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]]) + b = Matrix([0, 0, 0, 1]) + raises(ValueError, lambda: A.gauss_jordan_solve(b)) + + # Rectangular, tall, full rank, no solution, B has two columns (2nd has no solution) + A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]]) + B = Matrix([[0,0], [0, 0], [1, 0], [0, 1]]) + raises(ValueError, lambda: A.gauss_jordan_solve(B)) + + # Rectangular, tall, full rank, no solution, B has two columns (1st has no solution) + A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]]) + B = Matrix([[0,0], [0, 0], [0, 1], [1, 0]]) + raises(ValueError, lambda: A.gauss_jordan_solve(B)) + + # Rectangular, tall, reduced rank, parametrized solution + A = Matrix([[1, 5, 3], [2, 10, 6], [3, 15, 9], [1, 4, 3]]) + b = Matrix([0, 0, 0, 1]) + sol, params = A.gauss_jordan_solve(b) + w = {} + for s in sol.atoms(Symbol): + w[s.name] = s + assert sol == Matrix([[-3*w['tau0'] + 5], [-1], [w['tau0']]]) + assert params == Matrix([[w['tau0']]]) + + # Rectangular, tall, reduced rank, no solution + A = Matrix([[1, 5, 3], [2, 10, 6], [3, 15, 9], [1, 4, 3]]) + b = Matrix([0, 0, 1, 1]) + raises(ValueError, lambda: A.gauss_jordan_solve(b)) + + # Rectangular, wide, full rank, parametrized solution + A = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 1, 12]]) + b = Matrix([1, 1, 1]) + sol, params = A.gauss_jordan_solve(b) + w = {} + for s in sol.atoms(Symbol): + w[s.name] = s + assert sol == Matrix([[2*w['tau0'] - 1], [-3*w['tau0'] + 1], [0], + [w['tau0']]]) + assert params == Matrix([[w['tau0']]]) + + # Rectangular, wide, reduced rank, parametrized solution + A = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [2, 4, 6, 8]]) + b = Matrix([0, 1, 0]) + sol, params = A.gauss_jordan_solve(b) + w = {} + for s in sol.atoms(Symbol): + w[s.name] = s + assert sol == Matrix([[w['tau0'] + 2*w['tau1'] + S.Half], + [-2*w['tau0'] - 3*w['tau1'] - Rational(1, 4)], + [w['tau0']], [w['tau1']]]) + assert params == Matrix([[w['tau0']], [w['tau1']]]) + # watch out for clashing symbols + x0, x1, x2, _x0 = symbols('_tau0 _tau1 _tau2 tau1') + M = Matrix([[0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, _x0]]) + A = M[:, :-1] + b = M[:, -1:] + sol, params = A.gauss_jordan_solve(b) + assert params == Matrix(3, 1, [x0, x1, x2]) + assert sol == Matrix(5, 1, [x0, 0, x1, _x0, x2]) + + # Rectangular, wide, reduced rank, no solution + A = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [2, 4, 6, 8]]) + b = Matrix([1, 1, 1]) + raises(ValueError, lambda: A.gauss_jordan_solve(b)) + + # Test for immutable matrix + A = ImmutableMatrix([[1, 0], [0, 1]]) + B = ImmutableMatrix([1, 2]) + sol, params = A.gauss_jordan_solve(B) + assert sol == ImmutableMatrix([1, 2]) + assert params == ImmutableMatrix(0, 1, []) + assert sol.__class__ == ImmutableDenseMatrix + assert params.__class__ == ImmutableDenseMatrix + + # Test placement of free variables + A = Matrix([[1, 0, 0, 0], [0, 0, 0, 1]]) + b = Matrix([1, 1]) + sol, params = A.gauss_jordan_solve(b) + w = {} + for s in sol.atoms(Symbol): + w[s.name] = s + assert sol == Matrix([[1], [w['tau0']], [w['tau1']], [1]]) + assert params == Matrix([[w['tau0']], [w['tau1']]]) + + +def test_linsolve_underdetermined_AND_gauss_jordan_solve(): + #Test placement of free variables as per issue 19815 + A = Matrix([[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1]]) + B = Matrix([1, 2, 1, 1, 1, 1, 1, 2]) + sol, params = A.gauss_jordan_solve(B) + w = {} + for s in sol.atoms(Symbol): + w[s.name] = s + assert params == Matrix([[w['tau0']], [w['tau1']], [w['tau2']], + [w['tau3']], [w['tau4']], [w['tau5']]]) + assert sol == Matrix([[1 - 1*w['tau2']], + [w['tau2']], + [1 - 1*w['tau0'] + w['tau1']], + [w['tau0']], + [w['tau3'] + w['tau4']], + [-1*w['tau3'] - 1*w['tau4'] - 1*w['tau1']], + [1 - 1*w['tau2']], + [w['tau1']], + [w['tau2']], + [w['tau3']], + [w['tau4']], + [1 - 1*w['tau5']], + [w['tau5']], + [1]]) + + from sympy.abc import j,f + # https://github.com/sympy/sympy/issues/20046 + A = Matrix([ + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, -1, 0, -1, 0, -1, 0, -1, -j], + [0, 0, 0, 0, 1, 1, 1, 1, f] + ]) + + sol_1=Matrix(list(linsolve(A))[0]) + + tau0, tau1, tau2, tau3, tau4 = symbols('tau:5') + + assert sol_1 == Matrix([[-f - j - tau0 + tau2 + tau4 + 1], + [j - tau1 - tau2 - tau4], + [tau0], + [tau1], + [f - tau2 - tau3 - tau4], + [tau2], + [tau3], + [tau4]]) + + # https://github.com/sympy/sympy/issues/19815 + sol_2 = A[:, : -1 ] * sol_1 - A[:, -1 ] + assert sol_2 == Matrix([[0], [0], [0]]) + + +@pytest.mark.parametrize("det_method", ["bird", "laplace"]) +@pytest.mark.parametrize("M, rhs", [ + (Matrix([[2, 3, 5], [3, 6, 2], [8, 3, 6]]), Matrix(3, 1, [3, 7, 5])), + (Matrix([[2, 3, 5], [3, 6, 2], [8, 3, 6]]), + Matrix([[1, 2], [3, 4], [5, 6]])), + (Matrix(2, 2, symbols("a:4")), Matrix(2, 1, symbols("b:2"))), +]) +def test_cramer_solve(det_method, M, rhs): + assert simplify(M.cramer_solve(rhs, det_method=det_method) - M.LUsolve(rhs) + ) == Matrix.zeros(M.rows, rhs.cols) + + +@pytest.mark.parametrize("det_method, error", [ + ("bird", DMShapeError), (_det_laplace, NonSquareMatrixError)]) +def test_cramer_solve_errors(det_method, error): + # Non-square matrix + A = Matrix([[0, -1, 2], [5, 10, 7]]) + b = Matrix([-2, 15]) + raises(error, lambda: A.cramer_solve(b, det_method=det_method)) + + +def test_solve(): + A = Matrix([[1,2], [2,4]]) + b = Matrix([[3], [4]]) + raises(ValueError, lambda: A.solve(b)) #no solution + b = Matrix([[ 4], [8]]) + raises(ValueError, lambda: A.solve(b)) #infinite solution diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_sparse.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_sparse.py new file mode 100644 index 0000000000000000000000000000000000000000..4d257c8062f220cc06bc0dabdc7ac40ce9dc4adc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_sparse.py @@ -0,0 +1,745 @@ +from sympy.core.numbers import (Float, I, Rational) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import Abs +from sympy.polys.polytools import PurePoly +from sympy.matrices import \ + Matrix, MutableSparseMatrix, ImmutableSparseMatrix, SparseMatrix, eye, \ + ones, zeros, ShapeError, NonSquareMatrixError +from sympy.testing.pytest import raises + + +def test_sparse_creation(): + a = SparseMatrix(2, 2, {(0, 0): [[1, 2], [3, 4]]}) + assert a == SparseMatrix([[1, 2], [3, 4]]) + a = SparseMatrix(2, 2, {(0, 0): [[1, 2]]}) + assert a == SparseMatrix([[1, 2], [0, 0]]) + a = SparseMatrix(2, 2, {(0, 0): [1, 2]}) + assert a == SparseMatrix([[1, 0], [2, 0]]) + + +def test_sparse_matrix(): + def sparse_eye(n): + return SparseMatrix.eye(n) + + def sparse_zeros(n): + return SparseMatrix.zeros(n) + + # creation args + raises(TypeError, lambda: SparseMatrix(1, 2)) + + a = SparseMatrix(( + (1, 0), + (0, 1) + )) + assert SparseMatrix(a) == a + + from sympy.matrices import MutableDenseMatrix + a = MutableSparseMatrix([]) + b = MutableDenseMatrix([1, 2]) + assert a.row_join(b) == b + assert a.col_join(b) == b + assert type(a.row_join(b)) == type(a) + assert type(a.col_join(b)) == type(a) + + # make sure 0 x n matrices get stacked correctly + sparse_matrices = [SparseMatrix.zeros(0, n) for n in range(4)] + assert SparseMatrix.hstack(*sparse_matrices) == Matrix(0, 6, []) + sparse_matrices = [SparseMatrix.zeros(n, 0) for n in range(4)] + assert SparseMatrix.vstack(*sparse_matrices) == Matrix(6, 0, []) + + # test element assignment + a = SparseMatrix(( + (1, 0), + (0, 1) + )) + + a[3] = 4 + assert a[1, 1] == 4 + a[3] = 1 + + a[0, 0] = 2 + assert a == SparseMatrix(( + (2, 0), + (0, 1) + )) + a[1, 0] = 5 + assert a == SparseMatrix(( + (2, 0), + (5, 1) + )) + a[1, 1] = 0 + assert a == SparseMatrix(( + (2, 0), + (5, 0) + )) + assert a.todok() == {(0, 0): 2, (1, 0): 5} + + # test_multiplication + a = SparseMatrix(( + (1, 2), + (3, 1), + (0, 6), + )) + + b = SparseMatrix(( + (1, 2), + (3, 0), + )) + + c = a*b + assert c[0, 0] == 7 + assert c[0, 1] == 2 + assert c[1, 0] == 6 + assert c[1, 1] == 6 + assert c[2, 0] == 18 + assert c[2, 1] == 0 + + try: + eval('c = a @ b') + except SyntaxError: + pass + else: + assert c[0, 0] == 7 + assert c[0, 1] == 2 + assert c[1, 0] == 6 + assert c[1, 1] == 6 + assert c[2, 0] == 18 + assert c[2, 1] == 0 + + x = Symbol("x") + + c = b * Symbol("x") + assert isinstance(c, SparseMatrix) + assert c[0, 0] == x + assert c[0, 1] == 2*x + assert c[1, 0] == 3*x + assert c[1, 1] == 0 + + c = 5 * b + assert isinstance(c, SparseMatrix) + assert c[0, 0] == 5 + assert c[0, 1] == 2*5 + assert c[1, 0] == 3*5 + assert c[1, 1] == 0 + + #test_power + A = SparseMatrix([[2, 3], [4, 5]]) + assert (A**5)[:] == [6140, 8097, 10796, 14237] + A = SparseMatrix([[2, 1, 3], [4, 2, 4], [6, 12, 1]]) + assert (A**3)[:] == [290, 262, 251, 448, 440, 368, 702, 954, 433] + + # test_creation + x = Symbol("x") + a = SparseMatrix([[x, 0], [0, 0]]) + m = a + assert m.cols == m.rows + assert m.cols == 2 + assert m[:] == [x, 0, 0, 0] + b = SparseMatrix(2, 2, [x, 0, 0, 0]) + m = b + assert m.cols == m.rows + assert m.cols == 2 + assert m[:] == [x, 0, 0, 0] + + assert a == b + S = sparse_eye(3) + S.row_del(1) + assert S == SparseMatrix([ + [1, 0, 0], + [0, 0, 1]]) + S = sparse_eye(3) + S.col_del(1) + assert S == SparseMatrix([ + [1, 0], + [0, 0], + [0, 1]]) + S = SparseMatrix.eye(3) + S[2, 1] = 2 + S.col_swap(1, 0) + assert S == SparseMatrix([ + [0, 1, 0], + [1, 0, 0], + [2, 0, 1]]) + S.row_swap(0, 1) + assert S == SparseMatrix([ + [1, 0, 0], + [0, 1, 0], + [2, 0, 1]]) + + a = SparseMatrix(1, 2, [1, 2]) + b = a.copy() + c = a.copy() + assert a[0] == 1 + a.row_del(0) + assert a == SparseMatrix(0, 2, []) + b.col_del(1) + assert b == SparseMatrix(1, 1, [1]) + + assert SparseMatrix([[1, 2, 3], [1, 2], [1]]) == Matrix([ + [1, 2, 3], + [1, 2, 0], + [1, 0, 0]]) + assert SparseMatrix(4, 4, {(1, 1): sparse_eye(2)}) == Matrix([ + [0, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 0]]) + raises(ValueError, lambda: SparseMatrix(1, 1, {(1, 1): 1})) + assert SparseMatrix(1, 2, [1, 2]).tolist() == [[1, 2]] + assert SparseMatrix(2, 2, [1, [2, 3]]).tolist() == [[1, 0], [2, 3]] + raises(ValueError, lambda: SparseMatrix(2, 2, [1])) + raises(ValueError, lambda: SparseMatrix(1, 1, [[1, 2]])) + assert SparseMatrix([.1]).has(Float) + # autosizing + assert SparseMatrix(None, {(0, 1): 0}).shape == (0, 0) + assert SparseMatrix(None, {(0, 1): 1}).shape == (1, 2) + assert SparseMatrix(None, None, {(0, 1): 1}).shape == (1, 2) + raises(ValueError, lambda: SparseMatrix(None, 1, [[1, 2]])) + raises(ValueError, lambda: SparseMatrix(1, None, [[1, 2]])) + raises(ValueError, lambda: SparseMatrix(3, 3, {(0, 0): ones(2), (1, 1): 2})) + + # test_determinant + x, y = Symbol('x'), Symbol('y') + + assert SparseMatrix(1, 1, [0]).det() == 0 + + assert SparseMatrix([[1]]).det() == 1 + + assert SparseMatrix(((-3, 2), (8, -5))).det() == -1 + + assert SparseMatrix(((x, 1), (y, 2*y))).det() == 2*x*y - y + + assert SparseMatrix(( (1, 1, 1), + (1, 2, 3), + (1, 3, 6) )).det() == 1 + + assert SparseMatrix(( ( 3, -2, 0, 5), + (-2, 1, -2, 2), + ( 0, -2, 5, 0), + ( 5, 0, 3, 4) )).det() == -289 + + assert SparseMatrix(( ( 1, 2, 3, 4), + ( 5, 6, 7, 8), + ( 9, 10, 11, 12), + (13, 14, 15, 16) )).det() == 0 + + assert SparseMatrix(( (3, 2, 0, 0, 0), + (0, 3, 2, 0, 0), + (0, 0, 3, 2, 0), + (0, 0, 0, 3, 2), + (2, 0, 0, 0, 3) )).det() == 275 + + assert SparseMatrix(( (1, 0, 1, 2, 12), + (2, 0, 1, 1, 4), + (2, 1, 1, -1, 3), + (3, 2, -1, 1, 8), + (1, 1, 1, 0, 6) )).det() == -55 + + assert SparseMatrix(( (-5, 2, 3, 4, 5), + ( 1, -4, 3, 4, 5), + ( 1, 2, -3, 4, 5), + ( 1, 2, 3, -2, 5), + ( 1, 2, 3, 4, -1) )).det() == 11664 + + assert SparseMatrix(( ( 3, 0, 0, 0), + (-2, 1, 0, 0), + ( 0, -2, 5, 0), + ( 5, 0, 3, 4) )).det() == 60 + + assert SparseMatrix(( ( 1, 0, 0, 0), + ( 5, 0, 0, 0), + ( 9, 10, 11, 0), + (13, 14, 15, 16) )).det() == 0 + + assert SparseMatrix(( (3, 2, 0, 0, 0), + (0, 3, 2, 0, 0), + (0, 0, 3, 2, 0), + (0, 0, 0, 3, 2), + (0, 0, 0, 0, 3) )).det() == 243 + + assert SparseMatrix(( ( 2, 7, -1, 3, 2), + ( 0, 0, 1, 0, 1), + (-2, 0, 7, 0, 2), + (-3, -2, 4, 5, 3), + ( 1, 0, 0, 0, 1) )).det() == 123 + + # test_slicing + m0 = sparse_eye(4) + assert m0[:3, :3] == sparse_eye(3) + assert m0[2:4, 0:2] == sparse_zeros(2) + + m1 = SparseMatrix(3, 3, lambda i, j: i + j) + assert m1[0, :] == SparseMatrix(1, 3, (0, 1, 2)) + assert m1[1:3, 1] == SparseMatrix(2, 1, (2, 3)) + + m2 = SparseMatrix( + [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]) + assert m2[:, -1] == SparseMatrix(4, 1, [3, 7, 11, 15]) + assert m2[-2:, :] == SparseMatrix([[8, 9, 10, 11], [12, 13, 14, 15]]) + + assert SparseMatrix([[1, 2], [3, 4]])[[1], [1]] == Matrix([[4]]) + + # test_submatrix_assignment + m = sparse_zeros(4) + m[2:4, 2:4] = sparse_eye(2) + assert m == SparseMatrix([(0, 0, 0, 0), + (0, 0, 0, 0), + (0, 0, 1, 0), + (0, 0, 0, 1)]) + assert len(m.todok()) == 2 + m[:2, :2] = sparse_eye(2) + assert m == sparse_eye(4) + m[:, 0] = SparseMatrix(4, 1, (1, 2, 3, 4)) + assert m == SparseMatrix([(1, 0, 0, 0), + (2, 1, 0, 0), + (3, 0, 1, 0), + (4, 0, 0, 1)]) + m[:, :] = sparse_zeros(4) + assert m == sparse_zeros(4) + m[:, :] = ((1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12), (13, 14, 15, 16)) + assert m == SparseMatrix((( 1, 2, 3, 4), + ( 5, 6, 7, 8), + ( 9, 10, 11, 12), + (13, 14, 15, 16))) + m[:2, 0] = [0, 0] + assert m == SparseMatrix((( 0, 2, 3, 4), + ( 0, 6, 7, 8), + ( 9, 10, 11, 12), + (13, 14, 15, 16))) + + # test_reshape + m0 = sparse_eye(3) + assert m0.reshape(1, 9) == SparseMatrix(1, 9, (1, 0, 0, 0, 1, 0, 0, 0, 1)) + m1 = SparseMatrix(3, 4, lambda i, j: i + j) + assert m1.reshape(4, 3) == \ + SparseMatrix([(0, 1, 2), (3, 1, 2), (3, 4, 2), (3, 4, 5)]) + assert m1.reshape(2, 6) == \ + SparseMatrix([(0, 1, 2, 3, 1, 2), (3, 4, 2, 3, 4, 5)]) + + # test_applyfunc + m0 = sparse_eye(3) + assert m0.applyfunc(lambda x: 2*x) == sparse_eye(3)*2 + assert m0.applyfunc(lambda x: 0 ) == sparse_zeros(3) + + # test__eval_Abs + assert abs(SparseMatrix(((x, 1), (y, 2*y)))) == SparseMatrix(((Abs(x), 1), (Abs(y), 2*Abs(y)))) + + # test_LUdecomp + testmat = SparseMatrix([[ 0, 2, 5, 3], + [ 3, 3, 7, 4], + [ 8, 4, 0, 2], + [-2, 6, 3, 4]]) + L, U, p = testmat.LUdecomposition() + assert L.is_lower + assert U.is_upper + assert (L*U).permute_rows(p, 'backward') - testmat == sparse_zeros(4) + + testmat = SparseMatrix([[ 6, -2, 7, 4], + [ 0, 3, 6, 7], + [ 1, -2, 7, 4], + [-9, 2, 6, 3]]) + L, U, p = testmat.LUdecomposition() + assert L.is_lower + assert U.is_upper + assert (L*U).permute_rows(p, 'backward') - testmat == sparse_zeros(4) + + x, y, z = Symbol('x'), Symbol('y'), Symbol('z') + M = Matrix(((1, x, 1), (2, y, 0), (y, 0, z))) + L, U, p = M.LUdecomposition() + assert L.is_lower + assert U.is_upper + assert (L*U).permute_rows(p, 'backward') - M == sparse_zeros(3) + + # test_LUsolve + A = SparseMatrix([[2, 3, 5], + [3, 6, 2], + [8, 3, 6]]) + x = SparseMatrix(3, 1, [3, 7, 5]) + b = A*x + soln = A.LUsolve(b) + assert soln == x + A = SparseMatrix([[0, -1, 2], + [5, 10, 7], + [8, 3, 4]]) + x = SparseMatrix(3, 1, [-1, 2, 5]) + b = A*x + soln = A.LUsolve(b) + assert soln == x + + # test_inverse + A = sparse_eye(4) + assert A.inv() == sparse_eye(4) + assert A.inv(method="CH") == sparse_eye(4) + assert A.inv(method="LDL") == sparse_eye(4) + + A = SparseMatrix([[2, 3, 5], + [3, 6, 2], + [7, 2, 6]]) + Ainv = SparseMatrix(Matrix(A).inv()) + assert A*Ainv == sparse_eye(3) + assert A.inv(method="CH") == Ainv + assert A.inv(method="LDL") == Ainv + + A = SparseMatrix([[2, 3, 5], + [3, 6, 2], + [5, 2, 6]]) + Ainv = SparseMatrix(Matrix(A).inv()) + assert A*Ainv == sparse_eye(3) + assert A.inv(method="CH") == Ainv + assert A.inv(method="LDL") == Ainv + + # test_cross + v1 = Matrix(1, 3, [1, 2, 3]) + v2 = Matrix(1, 3, [3, 4, 5]) + assert v1.cross(v2) == Matrix(1, 3, [-2, 4, -2]) + assert v1.norm(2)**2 == 14 + + # conjugate + a = SparseMatrix(((1, 2 + I), (3, 4))) + assert a.C == SparseMatrix([ + [1, 2 - I], + [3, 4] + ]) + + # mul + assert a*Matrix(2, 2, [1, 0, 0, 1]) == a + assert a + Matrix(2, 2, [1, 1, 1, 1]) == SparseMatrix([ + [2, 3 + I], + [4, 5] + ]) + + # col join + assert a.col_join(sparse_eye(2)) == SparseMatrix([ + [1, 2 + I], + [3, 4], + [1, 0], + [0, 1] + ]) + + # row insert + assert a.row_insert(2, sparse_eye(2)) == SparseMatrix([ + [1, 2 + I], + [3, 4], + [1, 0], + [0, 1] + ]) + + # col insert + assert a.col_insert(2, SparseMatrix.zeros(2, 1)) == SparseMatrix([ + [1, 2 + I, 0], + [3, 4, 0], + ]) + + # symmetric + assert not a.is_symmetric(simplify=False) + + # col op + M = SparseMatrix.eye(3)*2 + M[1, 0] = -1 + M.col_op(1, lambda v, i: v + 2*M[i, 0]) + assert M == SparseMatrix([ + [ 2, 4, 0], + [-1, 0, 0], + [ 0, 0, 2] + ]) + + # fill + M = SparseMatrix.eye(3) + M.fill(2) + assert M == SparseMatrix([ + [2, 2, 2], + [2, 2, 2], + [2, 2, 2], + ]) + + # test_cofactor + assert sparse_eye(3) == sparse_eye(3).cofactor_matrix() + test = SparseMatrix([[1, 3, 2], [2, 6, 3], [2, 3, 6]]) + assert test.cofactor_matrix() == \ + SparseMatrix([[27, -6, -6], [-12, 2, 3], [-3, 1, 0]]) + test = SparseMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert test.cofactor_matrix() == \ + SparseMatrix([[-3, 6, -3], [6, -12, 6], [-3, 6, -3]]) + + # test_jacobian + x = Symbol('x') + y = Symbol('y') + L = SparseMatrix(1, 2, [x**2*y, 2*y**2 + x*y]) + syms = [x, y] + assert L.jacobian(syms) == Matrix([[2*x*y, x**2], [y, 4*y + x]]) + + L = SparseMatrix(1, 2, [x, x**2*y**3]) + assert L.jacobian(syms) == SparseMatrix([[1, 0], [2*x*y**3, x**2*3*y**2]]) + + # test_QR + A = Matrix([[1, 2], [2, 3]]) + Q, S = A.QRdecomposition() + R = Rational + assert Q == Matrix([ + [ 5**R(-1, 2), (R(2)/5)*(R(1)/5)**R(-1, 2)], + [2*5**R(-1, 2), (-R(1)/5)*(R(1)/5)**R(-1, 2)]]) + assert S == Matrix([ + [5**R(1, 2), 8*5**R(-1, 2)], + [ 0, (R(1)/5)**R(1, 2)]]) + assert Q*S == A + assert Q.T * Q == sparse_eye(2) + + R = Rational + # test nullspace + # first test reduced row-ech form + + M = SparseMatrix([[5, 7, 2, 1], + [1, 6, 2, -1]]) + out, tmp = M.rref() + assert out == Matrix([[1, 0, -R(2)/23, R(13)/23], + [0, 1, R(8)/23, R(-6)/23]]) + + M = SparseMatrix([[ 1, 3, 0, 2, 6, 3, 1], + [-2, -6, 0, -2, -8, 3, 1], + [ 3, 9, 0, 0, 6, 6, 2], + [-1, -3, 0, 1, 0, 9, 3]]) + + out, tmp = M.rref() + assert out == Matrix([[1, 3, 0, 0, 2, 0, 0], + [0, 0, 0, 1, 2, 0, 0], + [0, 0, 0, 0, 0, 1, R(1)/3], + [0, 0, 0, 0, 0, 0, 0]]) + # now check the vectors + basis = M.nullspace() + assert basis[0] == Matrix([-3, 1, 0, 0, 0, 0, 0]) + assert basis[1] == Matrix([0, 0, 1, 0, 0, 0, 0]) + assert basis[2] == Matrix([-2, 0, 0, -2, 1, 0, 0]) + assert basis[3] == Matrix([0, 0, 0, 0, 0, R(-1)/3, 1]) + + # test eigen + x = Symbol('x') + y = Symbol('y') + sparse_eye3 = sparse_eye(3) + assert sparse_eye3.charpoly(x) == PurePoly((x - 1)**3) + assert sparse_eye3.charpoly(y) == PurePoly((y - 1)**3) + + # test values + M = Matrix([( 0, 1, -1), + ( 1, 1, 0), + (-1, 0, 1)]) + vals = M.eigenvals() + assert sorted(vals.keys()) == [-1, 1, 2] + + R = Rational + M = Matrix([[1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + assert M.eigenvects() == [(1, 3, [ + Matrix([1, 0, 0]), + Matrix([0, 1, 0]), + Matrix([0, 0, 1])])] + M = Matrix([[5, 0, 2], + [3, 2, 0], + [0, 0, 1]]) + assert M.eigenvects() == [(1, 1, [Matrix([R(-1)/2, R(3)/2, 1])]), + (2, 1, [Matrix([0, 1, 0])]), + (5, 1, [Matrix([1, 1, 0])])] + + assert M.zeros(3, 5) == SparseMatrix(3, 5, {}) + A = SparseMatrix(10, 10, {(0, 0): 18, (0, 9): 12, (1, 4): 18, (2, 7): 16, (3, 9): 12, (4, 2): 19, (5, 7): 16, (6, 2): 12, (9, 7): 18}) + assert A.row_list() == [(0, 0, 18), (0, 9, 12), (1, 4, 18), (2, 7, 16), (3, 9, 12), (4, 2, 19), (5, 7, 16), (6, 2, 12), (9, 7, 18)] + assert A.col_list() == [(0, 0, 18), (4, 2, 19), (6, 2, 12), (1, 4, 18), (2, 7, 16), (5, 7, 16), (9, 7, 18), (0, 9, 12), (3, 9, 12)] + assert SparseMatrix.eye(2).nnz() == 2 + + +def test_scalar_multiply(): + assert SparseMatrix([[1, 2]]).scalar_multiply(3) == SparseMatrix([[3, 6]]) + + +def test_transpose(): + assert SparseMatrix(((1, 2), (3, 4))).transpose() == \ + SparseMatrix(((1, 3), (2, 4))) + + +def test_trace(): + assert SparseMatrix(((1, 2), (3, 4))).trace() == 5 + assert SparseMatrix(((0, 0), (0, 4))).trace() == 4 + + +def test_CL_RL(): + assert SparseMatrix(((1, 2), (3, 4))).row_list() == \ + [(0, 0, 1), (0, 1, 2), (1, 0, 3), (1, 1, 4)] + assert SparseMatrix(((1, 2), (3, 4))).col_list() == \ + [(0, 0, 1), (1, 0, 3), (0, 1, 2), (1, 1, 4)] + + +def test_add(): + assert SparseMatrix(((1, 0), (0, 1))) + SparseMatrix(((0, 1), (1, 0))) == \ + SparseMatrix(((1, 1), (1, 1))) + a = SparseMatrix(100, 100, lambda i, j: int(j != 0 and i % j == 0)) + b = SparseMatrix(100, 100, lambda i, j: int(i != 0 and j % i == 0)) + assert (len(a.todok()) + len(b.todok()) - len((a + b).todok()) > 0) + + +def test_errors(): + raises(ValueError, lambda: SparseMatrix(1.4, 2, lambda i, j: 0)) + raises(TypeError, lambda: SparseMatrix([1, 2, 3], [1, 2])) + raises(ValueError, lambda: SparseMatrix([[1, 2], [3, 4]])[(1, 2, 3)]) + raises(IndexError, lambda: SparseMatrix([[1, 2], [3, 4]])[5]) + raises(ValueError, lambda: SparseMatrix([[1, 2], [3, 4]])[1, 2, 3]) + raises(TypeError, + lambda: SparseMatrix([[1, 2], [3, 4]]).copyin_list([0, 1], set())) + raises( + IndexError, lambda: SparseMatrix([[1, 2], [3, 4]])[1, 2]) + raises(TypeError, lambda: SparseMatrix([1, 2, 3]).cross(1)) + raises(IndexError, lambda: SparseMatrix(1, 2, [1, 2])[3]) + raises(ShapeError, + lambda: SparseMatrix(1, 2, [1, 2]) + SparseMatrix(2, 1, [2, 1])) + + +def test_len(): + assert not SparseMatrix() + assert SparseMatrix() == SparseMatrix([]) + assert SparseMatrix() == SparseMatrix([[]]) + + +def test_sparse_zeros_sparse_eye(): + assert SparseMatrix.eye(3) == eye(3, cls=SparseMatrix) + assert len(SparseMatrix.eye(3).todok()) == 3 + assert SparseMatrix.zeros(3) == zeros(3, cls=SparseMatrix) + assert len(SparseMatrix.zeros(3).todok()) == 0 + + +def test_copyin(): + s = SparseMatrix(3, 3, {}) + s[1, 0] = 1 + assert s[:, 0] == SparseMatrix(Matrix([0, 1, 0])) + assert s[3] == 1 + assert s[3: 4] == [1] + s[1, 1] = 42 + assert s[1, 1] == 42 + assert s[1, 1:] == SparseMatrix([[42, 0]]) + s[1, 1:] = Matrix([[5, 6]]) + assert s[1, :] == SparseMatrix([[1, 5, 6]]) + s[1, 1:] = [[42, 43]] + assert s[1, :] == SparseMatrix([[1, 42, 43]]) + s[0, 0] = 17 + assert s[:, :1] == SparseMatrix([17, 1, 0]) + s[0, 0] = [1, 1, 1] + assert s[:, 0] == SparseMatrix([1, 1, 1]) + s[0, 0] = Matrix([1, 1, 1]) + assert s[:, 0] == SparseMatrix([1, 1, 1]) + s[0, 0] = SparseMatrix([1, 1, 1]) + assert s[:, 0] == SparseMatrix([1, 1, 1]) + + +def test_sparse_solve(): + A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + assert A.cholesky() == Matrix([ + [ 5, 0, 0], + [ 3, 3, 0], + [-1, 1, 3]]) + assert A.cholesky() * A.cholesky().T == Matrix([ + [25, 15, -5], + [15, 18, 0], + [-5, 0, 11]]) + + A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) + L, D = A.LDLdecomposition() + assert 15*L == Matrix([ + [15, 0, 0], + [ 9, 15, 0], + [-3, 5, 15]]) + assert D == Matrix([ + [25, 0, 0], + [ 0, 9, 0], + [ 0, 0, 9]]) + assert L * D * L.T == A + + A = SparseMatrix(((3, 0, 2), (0, 0, 1), (1, 2, 0))) + assert A.inv() * A == SparseMatrix(eye(3)) + + A = SparseMatrix([ + [ 2, -1, 0], + [-1, 2, -1], + [ 0, 0, 2]]) + ans = SparseMatrix([ + [Rational(2, 3), Rational(1, 3), Rational(1, 6)], + [Rational(1, 3), Rational(2, 3), Rational(1, 3)], + [ 0, 0, S.Half]]) + assert A.inv(method='CH') == ans + assert A.inv(method='LDL') == ans + assert A * ans == SparseMatrix(eye(3)) + + s = A.solve(A[:, 0], 'LDL') + assert A*s == A[:, 0] + s = A.solve(A[:, 0], 'CH') + assert A*s == A[:, 0] + A = A.col_join(A) + s = A.solve_least_squares(A[:, 0], 'CH') + assert A*s == A[:, 0] + s = A.solve_least_squares(A[:, 0], 'LDL') + assert A*s == A[:, 0] + + +def test_lower_triangular_solve(): + raises(NonSquareMatrixError, lambda: + SparseMatrix([[1, 2]]).lower_triangular_solve(Matrix([[1, 2]]))) + raises(ShapeError, lambda: + SparseMatrix([[1, 2], [0, 4]]).lower_triangular_solve(Matrix([1]))) + raises(ValueError, lambda: + SparseMatrix([[1, 2], [3, 4]]).lower_triangular_solve(Matrix([[1, 2], [3, 4]]))) + + a, b, c, d = symbols('a:d') + u, v, w, x = symbols('u:x') + + A = SparseMatrix([[a, 0], [c, d]]) + B = MutableSparseMatrix([[u, v], [w, x]]) + C = ImmutableSparseMatrix([[u, v], [w, x]]) + + sol = Matrix([[u/a, v/a], [(w - c*u/a)/d, (x - c*v/a)/d]]) + assert A.lower_triangular_solve(B) == sol + assert A.lower_triangular_solve(C) == sol + + +def test_upper_triangular_solve(): + raises(NonSquareMatrixError, lambda: + SparseMatrix([[1, 2]]).upper_triangular_solve(Matrix([[1, 2]]))) + raises(ShapeError, lambda: + SparseMatrix([[1, 2], [0, 4]]).upper_triangular_solve(Matrix([1]))) + raises(TypeError, lambda: + SparseMatrix([[1, 2], [3, 4]]).upper_triangular_solve(Matrix([[1, 2], [3, 4]]))) + + a, b, c, d = symbols('a:d') + u, v, w, x = symbols('u:x') + + A = SparseMatrix([[a, b], [0, d]]) + B = MutableSparseMatrix([[u, v], [w, x]]) + C = ImmutableSparseMatrix([[u, v], [w, x]]) + + sol = Matrix([[(u - b*w/d)/a, (v - b*x/d)/a], [w/d, x/d]]) + assert A.upper_triangular_solve(B) == sol + assert A.upper_triangular_solve(C) == sol + + +def test_diagonal_solve(): + a, d = symbols('a d') + u, v, w, x = symbols('u:x') + + A = SparseMatrix([[a, 0], [0, d]]) + B = MutableSparseMatrix([[u, v], [w, x]]) + C = ImmutableSparseMatrix([[u, v], [w, x]]) + + sol = Matrix([[u/a, v/a], [w/d, x/d]]) + assert A.diagonal_solve(B) == sol + assert A.diagonal_solve(C) == sol + + +def test_hermitian(): + x = Symbol('x') + a = SparseMatrix([[0, I], [-I, 0]]) + assert a.is_hermitian + a = SparseMatrix([[1, I], [-I, 1]]) + assert a.is_hermitian + a[0, 0] = 2*I + assert a.is_hermitian is False + a[0, 0] = x + assert a.is_hermitian is None + a[0, 1] = a[1, 0]*I + assert a.is_hermitian is False diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_sparsetools.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_sparsetools.py new file mode 100644 index 0000000000000000000000000000000000000000..244944c31da06460d4bc7beff8bce0f91fea9f14 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_sparsetools.py @@ -0,0 +1,132 @@ +from sympy.matrices.sparsetools import _doktocsr, _csrtodok, banded +from sympy.matrices.dense import (Matrix, eye, ones, zeros) +from sympy.matrices import SparseMatrix +from sympy.testing.pytest import raises + + +def test_doktocsr(): + a = SparseMatrix([[1, 2, 0, 0], [0, 3, 9, 0], [0, 1, 4, 0]]) + b = SparseMatrix(4, 6, [10, 20, 0, 0, 0, 0, 0, 30, 0, 40, 0, 0, 0, 0, 50, + 60, 70, 0, 0, 0, 0, 0, 0, 80]) + c = SparseMatrix(4, 4, [0, 0, 0, 0, 0, 12, 0, 2, 15, 0, 12, 0, 0, 0, 0, 4]) + d = SparseMatrix(10, 10, {(1, 1): 12, (3, 5): 7, (7, 8): 12}) + e = SparseMatrix([[0, 0, 0], [1, 0, 2], [3, 0, 0]]) + f = SparseMatrix(7, 8, {(2, 3): 5, (4, 5):12}) + assert _doktocsr(a) == [[1, 2, 3, 9, 1, 4], [0, 1, 1, 2, 1, 2], + [0, 2, 4, 6], [3, 4]] + assert _doktocsr(b) == [[10, 20, 30, 40, 50, 60, 70, 80], + [0, 1, 1, 3, 2, 3, 4, 5], [0, 2, 4, 7, 8], [4, 6]] + assert _doktocsr(c) == [[12, 2, 15, 12, 4], [1, 3, 0, 2, 3], + [0, 0, 2, 4, 5], [4, 4]] + assert _doktocsr(d) == [[12, 7, 12], [1, 5, 8], + [0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3], [10, 10]] + assert _doktocsr(e) == [[1, 2, 3], [0, 2, 0], [0, 0, 2, 3], [3, 3]] + assert _doktocsr(f) == [[5, 12], [3, 5], [0, 0, 0, 1, 1, 2, 2, 2], [7, 8]] + + +def test_csrtodok(): + h = [[5, 7, 5], [2, 1, 3], [0, 1, 1, 3], [3, 4]] + g = [[12, 5, 4], [2, 4, 2], [0, 1, 2, 3], [3, 7]] + i = [[1, 3, 12], [0, 2, 4], [0, 2, 3], [2, 5]] + j = [[11, 15, 12, 15], [2, 4, 1, 2], [0, 1, 1, 2, 3, 4], [5, 8]] + k = [[1, 3], [2, 1], [0, 1, 1, 2], [3, 3]] + m = _csrtodok(h) + assert isinstance(m, SparseMatrix) + assert m == SparseMatrix(3, 4, + {(0, 2): 5, (2, 1): 7, (2, 3): 5}) + assert _csrtodok(g) == SparseMatrix(3, 7, + {(0, 2): 12, (1, 4): 5, (2, 2): 4}) + assert _csrtodok(i) == SparseMatrix([[1, 0, 3, 0, 0], [0, 0, 0, 0, 12]]) + assert _csrtodok(j) == SparseMatrix(5, 8, + {(0, 2): 11, (2, 4): 15, (3, 1): 12, (4, 2): 15}) + assert _csrtodok(k) == SparseMatrix(3, 3, {(0, 2): 1, (2, 1): 3}) + + +def test_banded(): + raises(TypeError, lambda: banded()) + raises(TypeError, lambda: banded(1)) + raises(TypeError, lambda: banded(1, 2)) + raises(TypeError, lambda: banded(1, 2, 3)) + raises(TypeError, lambda: banded(1, 2, 3, 4)) + raises(ValueError, lambda: banded({0: (1, 2)}, rows=1)) + raises(ValueError, lambda: banded({0: (1, 2)}, cols=1)) + raises(ValueError, lambda: banded(1, {0: (1, 2)})) + raises(ValueError, lambda: banded(2, 1, {0: (1, 2)})) + raises(ValueError, lambda: banded(1, 2, {0: (1, 2)})) + + assert isinstance(banded(2, 4, {}), SparseMatrix) + assert banded(2, 4, {}) == zeros(2, 4) + assert banded({0: 0, 1: 0}) == zeros(0) + assert banded({0: Matrix([1, 2])}) == Matrix([1, 2]) + assert banded({1: [1, 2, 3, 0], -1: [4, 5, 6]}) == \ + banded({1: (1, 2, 3), -1: (4, 5, 6)}) == \ + Matrix([ + [0, 1, 0, 0], + [4, 0, 2, 0], + [0, 5, 0, 3], + [0, 0, 6, 0]]) + assert banded(3, 4, {-1: 1, 0: 2, 1: 3}) == \ + Matrix([ + [2, 3, 0, 0], + [1, 2, 3, 0], + [0, 1, 2, 3]]) + s = lambda d: (1 + d)**2 + assert banded(5, {0: s, 2: s}) == \ + Matrix([ + [1, 0, 1, 0, 0], + [0, 4, 0, 4, 0], + [0, 0, 9, 0, 9], + [0, 0, 0, 16, 0], + [0, 0, 0, 0, 25]]) + assert banded(2, {0: 1}) == \ + Matrix([ + [1, 0], + [0, 1]]) + assert banded(2, 3, {0: 1}) == \ + Matrix([ + [1, 0, 0], + [0, 1, 0]]) + vert = Matrix([1, 2, 3]) + assert banded({0: vert}, cols=3) == \ + Matrix([ + [1, 0, 0], + [2, 1, 0], + [3, 2, 1], + [0, 3, 2], + [0, 0, 3]]) + assert banded(4, {0: ones(2)}) == \ + Matrix([ + [1, 1, 0, 0], + [1, 1, 0, 0], + [0, 0, 1, 1], + [0, 0, 1, 1]]) + raises(ValueError, lambda: banded({0: 2, 1: ones(2)}, rows=5)) + assert banded({0: 2, 2: (ones(2),)*3}) == \ + Matrix([ + [2, 0, 1, 1, 0, 0, 0, 0], + [0, 2, 1, 1, 0, 0, 0, 0], + [0, 0, 2, 0, 1, 1, 0, 0], + [0, 0, 0, 2, 1, 1, 0, 0], + [0, 0, 0, 0, 2, 0, 1, 1], + [0, 0, 0, 0, 0, 2, 1, 1]]) + raises(ValueError, lambda: banded({0: (2,)*5, 1: (ones(2),)*3})) + u2 = Matrix([[1, 1], [0, 1]]) + assert banded({0: (2,)*5, 1: (u2,)*3}) == \ + Matrix([ + [2, 1, 1, 0, 0, 0, 0], + [0, 2, 1, 0, 0, 0, 0], + [0, 0, 2, 1, 1, 0, 0], + [0, 0, 0, 2, 1, 0, 0], + [0, 0, 0, 0, 2, 1, 1], + [0, 0, 0, 0, 0, 0, 1]]) + assert banded({0:(0, ones(2)), 2: 2}) == \ + Matrix([ + [0, 0, 2], + [0, 1, 1], + [0, 1, 1]]) + raises(ValueError, lambda: banded({0: (0, ones(2)), 1: 2})) + assert banded({0: 1}, cols=3) == banded({0: 1}, rows=3) == eye(3) + assert banded({1: 1}, rows=3) == Matrix([ + [0, 1, 0], + [0, 0, 1], + [0, 0, 0]]) diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_subspaces.py b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_subspaces.py new file mode 100644 index 0000000000000000000000000000000000000000..0bd853e321eb06f754c17e7bd0c11deb870506f5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/tests/test_subspaces.py @@ -0,0 +1,109 @@ +from sympy.matrices import Matrix +from sympy.core.numbers import Rational +from sympy.core.symbol import symbols +from sympy.solvers import solve + + +def test_columnspace_one(): + m = Matrix([[ 1, 2, 0, 2, 5], + [-2, -5, 1, -1, -8], + [ 0, -3, 3, 4, 1], + [ 3, 6, 0, -7, 2]]) + + basis = m.columnspace() + assert basis[0] == Matrix([1, -2, 0, 3]) + assert basis[1] == Matrix([2, -5, -3, 6]) + assert basis[2] == Matrix([2, -1, 4, -7]) + + assert len(basis) == 3 + assert Matrix.hstack(m, *basis).columnspace() == basis + + +def test_rowspace(): + m = Matrix([[ 1, 2, 0, 2, 5], + [-2, -5, 1, -1, -8], + [ 0, -3, 3, 4, 1], + [ 3, 6, 0, -7, 2]]) + + basis = m.rowspace() + assert basis[0] == Matrix([[1, 2, 0, 2, 5]]) + assert basis[1] == Matrix([[0, -1, 1, 3, 2]]) + assert basis[2] == Matrix([[0, 0, 0, 5, 5]]) + + assert len(basis) == 3 + + +def test_nullspace_one(): + m = Matrix([[ 1, 2, 0, 2, 5], + [-2, -5, 1, -1, -8], + [ 0, -3, 3, 4, 1], + [ 3, 6, 0, -7, 2]]) + + basis = m.nullspace() + assert basis[0] == Matrix([-2, 1, 1, 0, 0]) + assert basis[1] == Matrix([-1, -1, 0, -1, 1]) + # make sure the null space is really gets zeroed + assert all(e.is_zero for e in m*basis[0]) + assert all(e.is_zero for e in m*basis[1]) + +def test_nullspace_second(): + # first test reduced row-ech form + R = Rational + + M = Matrix([[5, 7, 2, 1], + [1, 6, 2, -1]]) + out, tmp = M.rref() + assert out == Matrix([[1, 0, -R(2)/23, R(13)/23], + [0, 1, R(8)/23, R(-6)/23]]) + + M = Matrix([[-5, -1, 4, -3, -1], + [ 1, -1, -1, 1, 0], + [-1, 0, 0, 0, 0], + [ 4, 1, -4, 3, 1], + [-2, 0, 2, -2, -1]]) + assert M*M.nullspace()[0] == Matrix(5, 1, [0]*5) + + M = Matrix([[ 1, 3, 0, 2, 6, 3, 1], + [-2, -6, 0, -2, -8, 3, 1], + [ 3, 9, 0, 0, 6, 6, 2], + [-1, -3, 0, 1, 0, 9, 3]]) + out, tmp = M.rref() + assert out == Matrix([[1, 3, 0, 0, 2, 0, 0], + [0, 0, 0, 1, 2, 0, 0], + [0, 0, 0, 0, 0, 1, R(1)/3], + [0, 0, 0, 0, 0, 0, 0]]) + + # now check the vectors + basis = M.nullspace() + assert basis[0] == Matrix([-3, 1, 0, 0, 0, 0, 0]) + assert basis[1] == Matrix([0, 0, 1, 0, 0, 0, 0]) + assert basis[2] == Matrix([-2, 0, 0, -2, 1, 0, 0]) + assert basis[3] == Matrix([0, 0, 0, 0, 0, R(-1)/3, 1]) + + # issue 4797; just see that we can do it when rows > cols + M = Matrix([[1, 2], [2, 4], [3, 6]]) + assert M.nullspace() + + +def test_columnspace_second(): + M = Matrix([[ 1, 2, 0, 2, 5], + [-2, -5, 1, -1, -8], + [ 0, -3, 3, 4, 1], + [ 3, 6, 0, -7, 2]]) + + # now check the vectors + basis = M.columnspace() + assert basis[0] == Matrix([1, -2, 0, 3]) + assert basis[1] == Matrix([2, -5, -3, 6]) + assert basis[2] == Matrix([2, -1, 4, -7]) + + #check by columnspace definition + a, b, c, d, e = symbols('a b c d e') + X = Matrix([a, b, c, d, e]) + for i in range(len(basis)): + eq=M*X-basis[i] + assert len(solve(eq, X)) != 0 + + #check if rank-nullity theorem holds + assert M.rank() == len(basis) + assert len(M.nullspace()) + len(M.columnspace()) == M.cols diff --git a/venv/lib/python3.10/site-packages/sympy/matrices/utilities.py b/venv/lib/python3.10/site-packages/sympy/matrices/utilities.py new file mode 100644 index 0000000000000000000000000000000000000000..b8a680b47e63615e210e561639a192ba47c642d3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/matrices/utilities.py @@ -0,0 +1,72 @@ +from contextlib import contextmanager +from threading import local + +from sympy.core.function import expand_mul + + +class DotProdSimpState(local): + def __init__(self): + self.state = None + +_dotprodsimp_state = DotProdSimpState() + +@contextmanager +def dotprodsimp(x): + old = _dotprodsimp_state.state + + try: + _dotprodsimp_state.state = x + yield + finally: + _dotprodsimp_state.state = old + + +def _dotprodsimp(expr, withsimp=False): + """Wrapper for simplify.dotprodsimp to avoid circular imports.""" + from sympy.simplify.simplify import dotprodsimp as dps + return dps(expr, withsimp=withsimp) + + +def _get_intermediate_simp(deffunc=lambda x: x, offfunc=lambda x: x, + onfunc=_dotprodsimp, dotprodsimp=None): + """Support function for controlling intermediate simplification. Returns a + simplification function according to the global setting of dotprodsimp + operation. + + ``deffunc`` - Function to be used by default. + ``offfunc`` - Function to be used if dotprodsimp has been turned off. + ``onfunc`` - Function to be used if dotprodsimp has been turned on. + ``dotprodsimp`` - True, False or None. Will be overridden by global + _dotprodsimp_state.state if that is not None. + """ + + if dotprodsimp is False or _dotprodsimp_state.state is False: + return offfunc + if dotprodsimp is True or _dotprodsimp_state.state is True: + return onfunc + + return deffunc # None, None + + +def _get_intermediate_simp_bool(default=False, dotprodsimp=None): + """Same as ``_get_intermediate_simp`` but returns bools instead of functions + by default.""" + + return _get_intermediate_simp(default, False, True, dotprodsimp) + + +def _iszero(x): + """Returns True if x is zero.""" + return getattr(x, 'is_zero', None) + + +def _is_zero_after_expand_mul(x): + """Tests by expand_mul only, suitable for polynomials and rational + functions.""" + return expand_mul(x) == 0 + + +def _simplify(expr): + """ Wrapper to avoid circular imports. """ + from sympy.simplify.simplify import simplify + return simplify(expr) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18308e9bd26c942f9a0bf82ad1a10f2b7d04063c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/appellseqs.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/appellseqs.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98ade26bc8b0d67987962a0c5255fdae888c9031 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/appellseqs.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/compatibility.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/compatibility.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fa3011c232ec25b173b612687e5fd71c0cc54f9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/compatibility.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/constructor.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/constructor.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5131a59205fade23275686af10ad7cd43258c0e9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/constructor.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densearith.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densearith.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d32e0578a3b0dfcc5799fab56674e3af7324ca90 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densearith.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densebasic.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densebasic.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1431b920c7db1e4ce735f0dbd348d683c2bc442 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densebasic.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densetools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densetools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..560a4029ddfba6c0b260c9303e4465e46dc219f2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/densetools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/dispersion.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/dispersion.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf54169b1f756bb9a42b64cbfb49d4ea16a057d2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/dispersion.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/distributedmodules.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/distributedmodules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ccc5788c4213ca229c960b9232359fd2692879e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/distributedmodules.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/domainmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/domainmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72789ea41ca5d062176e1294c2ebd74231023049 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/domainmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/euclidtools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/euclidtools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e3601d0437c8d725bb4e6114d39f0c5d959f9e4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/euclidtools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/factortools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/factortools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..547d4ae2930c5a4dd3c987d4c9acd247932459a4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/factortools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/fglmtools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/fglmtools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..683bcfeb20de0d67891d4d702b3402588416b73c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/fglmtools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/fields.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/fields.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2f0ae716e73bbe2353c5c601ef9cdeb4f257f9c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/fields.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/galoistools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/galoistools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bc3a744fab7206f3ead872ba3d5645b53dde12f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/galoistools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/groebnertools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/groebnertools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..683f833c137eb0dcc6a16e78e2e38006ec586906 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/groebnertools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/heuristicgcd.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/heuristicgcd.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9748c5cdea59686acd5ee10b08f280bd8fa1fc51 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/heuristicgcd.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/modulargcd.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/modulargcd.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50fab7a52f112ad6753742b07466f1861692b19f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/modulargcd.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/monomials.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/monomials.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bd8c56efcac8a865cb94d954a7e6a75de1ec55b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/monomials.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/multivariate_resultants.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/multivariate_resultants.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92c9e7661df4cbe4025423cd5e9edd632116238b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/multivariate_resultants.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/orderings.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/orderings.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ceda08e12e3c6cb9036ef3d3e52463230b434ffe Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/orderings.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/orthopolys.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/orthopolys.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ff4c31cab65726c4a3476c86d0f83b2c948e277 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/orthopolys.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/partfrac.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/partfrac.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..414cdffd094585073c50bbe2a2705b953a2b6062 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/partfrac.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyclasses.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyclasses.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41d2eb6c30d0eca737c43ede5c0281de21f9cc7e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyclasses.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyconfig.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyconfig.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac5e5cf33ff283f990e4f4e8678c276cdd97610e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyconfig.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyerrors.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyerrors.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44a15ce11fcc0c383806b0df7ae8371ba557348a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyerrors.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyfuncs.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyfuncs.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abc801903d5a2623c016e88710457b05a6c8746c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyfuncs.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polymatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polymatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9b83bfa9fcd24fa969804bafb226d7d09ea0acf Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polymatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyoptions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyoptions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c995397e8a81f4856c0244dc6b978c0cc4913b21 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyoptions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyroots.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyroots.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a47a194496da51e2562e2bfa59bef3ef5dc51c45 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyroots.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyutils.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyutils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bba21f891a7b8682e9805cbc241a535b676be5a8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/polyutils.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/puiseux.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/puiseux.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f20c84fb1c48510a4f5090e3f8c225525bc6b926 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/puiseux.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rationaltools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rationaltools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75d5b5612323f3d7a7cf737c4414453227ad7ff3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rationaltools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/ring_series.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/ring_series.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8adbfe658c357f618af6bfb92979c152385f1dd2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/ring_series.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rings.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rings.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c615cf18b228a5aa1fee5e13980cbed5fcca899e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rings.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rootisolation.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rootisolation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..487cbf60ed2d7af62cca3296c3c5665201b934fe Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rootisolation.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rootoftools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rootoftools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..036b4a8386284081f00cb8b4a6198a24fd9c5399 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/rootoftools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/solvers.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/solvers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33d0a0838b8e8f1ce727d985e8e3eb61d7f51d8a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/solvers.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/specialpolys.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/specialpolys.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96e1adf671661c2d2f943bfd5fe627a5d70404b2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/specialpolys.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/sqfreetools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/sqfreetools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..453af27a9f33ea480b31e5e9de401336a970c65d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/sqfreetools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/subresultants_qq_zz.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/subresultants_qq_zz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9c56adbf6c98e92cf46ecf1b7b2485b8eb464e6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/__pycache__/subresultants_qq_zz.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/__init__.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..43486eec86f7453ec470a22b8f8decaa316cbe70 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/__init__.py @@ -0,0 +1,5 @@ +"""Module for algebraic geometry and commutative algebra.""" + +from .homomorphisms import homomorphism + +__all__ = ['homomorphism'] diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3af70d37411f2c381b970f0142d5c9632e8cf5b4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/extensions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/extensions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6dd0ad896e0f21ff4c8c88f867a352fd8d658cd1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/extensions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/homomorphisms.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/homomorphisms.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46542a829dceac8b33011a54ff3c5e9b808d197a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/homomorphisms.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/ideals.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/ideals.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d52a748cc2acd0fde96ee0621ca5ba6cc514cc1b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/ideals.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/modules.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/modules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07e4aebd0ed6595b4868408265b44e619507b123 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/__pycache__/modules.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/extensions.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/extensions.py new file mode 100644 index 0000000000000000000000000000000000000000..2668f792b5721db877f275e57ed54961b2e4df93 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/extensions.py @@ -0,0 +1,356 @@ +"""Finite extensions of ring domains.""" + +from sympy.polys.domains.domain import Domain +from sympy.polys.domains.domainelement import DomainElement +from sympy.polys.polyerrors import (CoercionFailed, NotInvertible, + GeneratorsError) +from sympy.polys.polytools import Poly +from sympy.printing.defaults import DefaultPrinting + + +class ExtensionElement(DomainElement, DefaultPrinting): + """ + Element of a finite extension. + + A class of univariate polynomials modulo the ``modulus`` + of the extension ``ext``. It is represented by the + unique polynomial ``rep`` of lowest degree. Both + ``rep`` and the representation ``mod`` of ``modulus`` + are of class DMP. + + """ + __slots__ = ('rep', 'ext') + + def __init__(self, rep, ext): + self.rep = rep + self.ext = ext + + def parent(f): + return f.ext + + def as_expr(f): + return f.ext.to_sympy(f) + + def __bool__(f): + return bool(f.rep) + + def __pos__(f): + return f + + def __neg__(f): + return ExtElem(-f.rep, f.ext) + + def _get_rep(f, g): + if isinstance(g, ExtElem): + if g.ext == f.ext: + return g.rep + else: + return None + else: + try: + g = f.ext.convert(g) + return g.rep + except CoercionFailed: + return None + + def __add__(f, g): + rep = f._get_rep(g) + if rep is not None: + return ExtElem(f.rep + rep, f.ext) + else: + return NotImplemented + + __radd__ = __add__ + + def __sub__(f, g): + rep = f._get_rep(g) + if rep is not None: + return ExtElem(f.rep - rep, f.ext) + else: + return NotImplemented + + def __rsub__(f, g): + rep = f._get_rep(g) + if rep is not None: + return ExtElem(rep - f.rep, f.ext) + else: + return NotImplemented + + def __mul__(f, g): + rep = f._get_rep(g) + if rep is not None: + return ExtElem((f.rep * rep) % f.ext.mod, f.ext) + else: + return NotImplemented + + __rmul__ = __mul__ + + def _divcheck(f): + """Raise if division is not implemented for this divisor""" + if not f: + raise NotInvertible('Zero divisor') + elif f.ext.is_Field: + return True + elif f.rep.is_ground and f.ext.domain.is_unit(f.rep.LC()): + return True + else: + # Some cases like (2*x + 2)/2 over ZZ will fail here. It is + # unclear how to implement division in general if the ground + # domain is not a field so for now it was decided to restrict the + # implementation to division by invertible constants. + msg = (f"Can not invert {f} in {f.ext}. " + "Only division by invertible constants is implemented.") + raise NotImplementedError(msg) + + def inverse(f): + """Multiplicative inverse. + + Raises + ====== + + NotInvertible + If the element is a zero divisor. + + """ + f._divcheck() + + if f.ext.is_Field: + invrep = f.rep.invert(f.ext.mod) + else: + R = f.ext.ring + invrep = R.exquo(R.one, f.rep) + + return ExtElem(invrep, f.ext) + + def __truediv__(f, g): + rep = f._get_rep(g) + if rep is None: + return NotImplemented + g = ExtElem(rep, f.ext) + + try: + ginv = g.inverse() + except NotInvertible: + raise ZeroDivisionError(f"{f} / {g}") + + return f * ginv + + __floordiv__ = __truediv__ + + def __rtruediv__(f, g): + try: + g = f.ext.convert(g) + except CoercionFailed: + return NotImplemented + return g / f + + __rfloordiv__ = __rtruediv__ + + def __mod__(f, g): + rep = f._get_rep(g) + if rep is None: + return NotImplemented + g = ExtElem(rep, f.ext) + + try: + g._divcheck() + except NotInvertible: + raise ZeroDivisionError(f"{f} % {g}") + + # Division where defined is always exact so there is no remainder + return f.ext.zero + + def __rmod__(f, g): + try: + g = f.ext.convert(g) + except CoercionFailed: + return NotImplemented + return g % f + + def __pow__(f, n): + if not isinstance(n, int): + raise TypeError("exponent of type 'int' expected") + if n < 0: + try: + f, n = f.inverse(), -n + except NotImplementedError: + raise ValueError("negative powers are not defined") + + b = f.rep + m = f.ext.mod + r = f.ext.one.rep + while n > 0: + if n % 2: + r = (r*b) % m + b = (b*b) % m + n //= 2 + + return ExtElem(r, f.ext) + + def __eq__(f, g): + if isinstance(g, ExtElem): + return f.rep == g.rep and f.ext == g.ext + else: + return NotImplemented + + def __ne__(f, g): + return not f == g + + def __hash__(f): + return hash((f.rep, f.ext)) + + def __str__(f): + from sympy.printing.str import sstr + return sstr(f.as_expr()) + + __repr__ = __str__ + + @property + def is_ground(f): + return f.rep.is_ground + + def to_ground(f): + [c] = f.rep.to_list() + return c + +ExtElem = ExtensionElement + + +class MonogenicFiniteExtension(Domain): + r""" + Finite extension generated by an integral element. + + The generator is defined by a monic univariate + polynomial derived from the argument ``mod``. + + A shorter alias is ``FiniteExtension``. + + Examples + ======== + + Quadratic integer ring $\mathbb{Z}[\sqrt2]$: + + >>> from sympy import Symbol, Poly + >>> from sympy.polys.agca.extensions import FiniteExtension + >>> x = Symbol('x') + >>> R = FiniteExtension(Poly(x**2 - 2)); R + ZZ[x]/(x**2 - 2) + >>> R.rank + 2 + >>> R(1 + x)*(3 - 2*x) + x - 1 + + Finite field $GF(5^3)$ defined by the primitive + polynomial $x^3 + x^2 + 2$ (over $\mathbb{Z}_5$). + + >>> F = FiniteExtension(Poly(x**3 + x**2 + 2, modulus=5)); F + GF(5)[x]/(x**3 + x**2 + 2) + >>> F.basis + (1, x, x**2) + >>> F(x + 3)/(x**2 + 2) + -2*x**2 + x + 2 + + Function field of an elliptic curve: + + >>> t = Symbol('t') + >>> FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True)) + ZZ(x)[t]/(t**2 - x**3 - x + 1) + + """ + is_FiniteExtension = True + + dtype = ExtensionElement + + def __init__(self, mod): + if not (isinstance(mod, Poly) and mod.is_univariate): + raise TypeError("modulus must be a univariate Poly") + + # Using auto=True (default) potentially changes the ground domain to a + # field whereas auto=False raises if division is not exact. We'll let + # the caller decide whether or not they want to put the ground domain + # over a field. In most uses mod is already monic. + mod = mod.monic(auto=False) + + self.rank = mod.degree() + self.modulus = mod + self.mod = mod.rep # DMP representation + + self.domain = dom = mod.domain + self.ring = dom.old_poly_ring(*mod.gens) + + self.zero = self.convert(self.ring.zero) + self.one = self.convert(self.ring.one) + + gen = self.ring.gens[0] + self.symbol = self.ring.symbols[0] + self.generator = self.convert(gen) + self.basis = tuple(self.convert(gen**i) for i in range(self.rank)) + + # XXX: It might be necessary to check mod.is_irreducible here + self.is_Field = self.domain.is_Field + + def new(self, arg): + rep = self.ring.convert(arg) + return ExtElem(rep % self.mod, self) + + def __eq__(self, other): + if not isinstance(other, FiniteExtension): + return False + return self.modulus == other.modulus + + def __hash__(self): + return hash((self.__class__.__name__, self.modulus)) + + def __str__(self): + return "%s/(%s)" % (self.ring, self.modulus.as_expr()) + + __repr__ = __str__ + + @property + def has_CharacteristicZero(self): + return self.domain.has_CharacteristicZero + + def characteristic(self): + return self.domain.characteristic() + + def convert(self, f, base=None): + rep = self.ring.convert(f, base) + return ExtElem(rep % self.mod, self) + + def convert_from(self, f, base): + rep = self.ring.convert(f, base) + return ExtElem(rep % self.mod, self) + + def to_sympy(self, f): + return self.ring.to_sympy(f.rep) + + def from_sympy(self, f): + return self.convert(f) + + def set_domain(self, K): + mod = self.modulus.set_domain(K) + return self.__class__(mod) + + def drop(self, *symbols): + if self.symbol in symbols: + raise GeneratorsError('Can not drop generator from FiniteExtension') + K = self.domain.drop(*symbols) + return self.set_domain(K) + + def quo(self, f, g): + return self.exquo(f, g) + + def exquo(self, f, g): + rep = self.ring.exquo(f.rep, g.rep) + return ExtElem(rep % self.mod, self) + + def is_negative(self, a): + return False + + def is_unit(self, a): + if self.is_Field: + return bool(a) + elif a.is_ground: + return self.domain.is_unit(a.to_ground()) + +FiniteExtension = MonogenicFiniteExtension diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/homomorphisms.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/homomorphisms.py new file mode 100644 index 0000000000000000000000000000000000000000..45e9549980a8848eee944000d321922576961a00 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/homomorphisms.py @@ -0,0 +1,691 @@ +""" +Computations with homomorphisms of modules and rings. + +This module implements classes for representing homomorphisms of rings and +their modules. Instead of instantiating the classes directly, you should use +the function ``homomorphism(from, to, matrix)`` to create homomorphism objects. +""" + + +from sympy.polys.agca.modules import (Module, FreeModule, QuotientModule, + SubModule, SubQuotientModule) +from sympy.polys.polyerrors import CoercionFailed + +# The main computational task for module homomorphisms is kernels. +# For this reason, the concrete classes are organised by domain module type. + + +class ModuleHomomorphism: + """ + Abstract base class for module homomoprhisms. Do not instantiate. + + Instead, use the ``homomorphism`` function: + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> homomorphism(F, F, [[1, 0], [0, 1]]) + Matrix([ + [1, 0], : QQ[x]**2 -> QQ[x]**2 + [0, 1]]) + + Attributes: + + - ring - the ring over which we are considering modules + - domain - the domain module + - codomain - the codomain module + - _ker - cached kernel + - _img - cached image + + Non-implemented methods: + + - _kernel + - _image + - _restrict_domain + - _restrict_codomain + - _quotient_domain + - _quotient_codomain + - _apply + - _mul_scalar + - _compose + - _add + """ + + def __init__(self, domain, codomain): + if not isinstance(domain, Module): + raise TypeError('Source must be a module, got %s' % domain) + if not isinstance(codomain, Module): + raise TypeError('Target must be a module, got %s' % codomain) + if domain.ring != codomain.ring: + raise ValueError('Source and codomain must be over same ring, ' + 'got %s != %s' % (domain, codomain)) + self.domain = domain + self.codomain = codomain + self.ring = domain.ring + self._ker = None + self._img = None + + def kernel(self): + r""" + Compute the kernel of ``self``. + + That is, if ``self`` is the homomorphism `\phi: M \to N`, then compute + `ker(\phi) = \{x \in M | \phi(x) = 0\}`. This is a submodule of `M`. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> homomorphism(F, F, [[1, 0], [x, 0]]).kernel() + <[x, -1]> + """ + if self._ker is None: + self._ker = self._kernel() + return self._ker + + def image(self): + r""" + Compute the image of ``self``. + + That is, if ``self`` is the homomorphism `\phi: M \to N`, then compute + `im(\phi) = \{\phi(x) | x \in M \}`. This is a submodule of `N`. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> homomorphism(F, F, [[1, 0], [x, 0]]).image() == F.submodule([1, 0]) + True + """ + if self._img is None: + self._img = self._image() + return self._img + + def _kernel(self): + """Compute the kernel of ``self``.""" + raise NotImplementedError + + def _image(self): + """Compute the image of ``self``.""" + raise NotImplementedError + + def _restrict_domain(self, sm): + """Implementation of domain restriction.""" + raise NotImplementedError + + def _restrict_codomain(self, sm): + """Implementation of codomain restriction.""" + raise NotImplementedError + + def _quotient_domain(self, sm): + """Implementation of domain quotient.""" + raise NotImplementedError + + def _quotient_codomain(self, sm): + """Implementation of codomain quotient.""" + raise NotImplementedError + + def restrict_domain(self, sm): + """ + Return ``self``, with the domain restricted to ``sm``. + + Here ``sm`` has to be a submodule of ``self.domain``. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> h = homomorphism(F, F, [[1, 0], [x, 0]]) + >>> h + Matrix([ + [1, x], : QQ[x]**2 -> QQ[x]**2 + [0, 0]]) + >>> h.restrict_domain(F.submodule([1, 0])) + Matrix([ + [1, x], : <[1, 0]> -> QQ[x]**2 + [0, 0]]) + + This is the same as just composing on the right with the submodule + inclusion: + + >>> h * F.submodule([1, 0]).inclusion_hom() + Matrix([ + [1, x], : <[1, 0]> -> QQ[x]**2 + [0, 0]]) + """ + if not self.domain.is_submodule(sm): + raise ValueError('sm must be a submodule of %s, got %s' + % (self.domain, sm)) + if sm == self.domain: + return self + return self._restrict_domain(sm) + + def restrict_codomain(self, sm): + """ + Return ``self``, with codomain restricted to to ``sm``. + + Here ``sm`` has to be a submodule of ``self.codomain`` containing the + image. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> h = homomorphism(F, F, [[1, 0], [x, 0]]) + >>> h + Matrix([ + [1, x], : QQ[x]**2 -> QQ[x]**2 + [0, 0]]) + >>> h.restrict_codomain(F.submodule([1, 0])) + Matrix([ + [1, x], : QQ[x]**2 -> <[1, 0]> + [0, 0]]) + """ + if not sm.is_submodule(self.image()): + raise ValueError('the image %s must contain sm, got %s' + % (self.image(), sm)) + if sm == self.codomain: + return self + return self._restrict_codomain(sm) + + def quotient_domain(self, sm): + """ + Return ``self`` with domain replaced by ``domain/sm``. + + Here ``sm`` must be a submodule of ``self.kernel()``. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> h = homomorphism(F, F, [[1, 0], [x, 0]]) + >>> h + Matrix([ + [1, x], : QQ[x]**2 -> QQ[x]**2 + [0, 0]]) + >>> h.quotient_domain(F.submodule([-x, 1])) + Matrix([ + [1, x], : QQ[x]**2/<[-x, 1]> -> QQ[x]**2 + [0, 0]]) + """ + if not self.kernel().is_submodule(sm): + raise ValueError('kernel %s must contain sm, got %s' % + (self.kernel(), sm)) + if sm.is_zero(): + return self + return self._quotient_domain(sm) + + def quotient_codomain(self, sm): + """ + Return ``self`` with codomain replaced by ``codomain/sm``. + + Here ``sm`` must be a submodule of ``self.codomain``. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> h = homomorphism(F, F, [[1, 0], [x, 0]]) + >>> h + Matrix([ + [1, x], : QQ[x]**2 -> QQ[x]**2 + [0, 0]]) + >>> h.quotient_codomain(F.submodule([1, 1])) + Matrix([ + [1, x], : QQ[x]**2 -> QQ[x]**2/<[1, 1]> + [0, 0]]) + + This is the same as composing with the quotient map on the left: + + >>> (F/[(1, 1)]).quotient_hom() * h + Matrix([ + [1, x], : QQ[x]**2 -> QQ[x]**2/<[1, 1]> + [0, 0]]) + """ + if not self.codomain.is_submodule(sm): + raise ValueError('sm must be a submodule of codomain %s, got %s' + % (self.codomain, sm)) + if sm.is_zero(): + return self + return self._quotient_codomain(sm) + + def _apply(self, elem): + """Apply ``self`` to ``elem``.""" + raise NotImplementedError + + def __call__(self, elem): + return self.codomain.convert(self._apply(self.domain.convert(elem))) + + def _compose(self, oth): + """ + Compose ``self`` with ``oth``, that is, return the homomorphism + obtained by first applying then ``self``, then ``oth``. + + (This method is private since in this syntax, it is non-obvious which + homomorphism is executed first.) + """ + raise NotImplementedError + + def _mul_scalar(self, c): + """Scalar multiplication. ``c`` is guaranteed in self.ring.""" + raise NotImplementedError + + def _add(self, oth): + """ + Homomorphism addition. + ``oth`` is guaranteed to be a homomorphism with same domain/codomain. + """ + raise NotImplementedError + + def _check_hom(self, oth): + """Helper to check that oth is a homomorphism with same domain/codomain.""" + if not isinstance(oth, ModuleHomomorphism): + return False + return oth.domain == self.domain and oth.codomain == self.codomain + + def __mul__(self, oth): + if isinstance(oth, ModuleHomomorphism) and self.domain == oth.codomain: + return oth._compose(self) + try: + return self._mul_scalar(self.ring.convert(oth)) + except CoercionFailed: + return NotImplemented + + # NOTE: _compose will never be called from rmul + __rmul__ = __mul__ + + def __truediv__(self, oth): + try: + return self._mul_scalar(1/self.ring.convert(oth)) + except CoercionFailed: + return NotImplemented + + def __add__(self, oth): + if self._check_hom(oth): + return self._add(oth) + return NotImplemented + + def __sub__(self, oth): + if self._check_hom(oth): + return self._add(oth._mul_scalar(self.ring.convert(-1))) + return NotImplemented + + def is_injective(self): + """ + Return True if ``self`` is injective. + + That is, check if the elements of the domain are mapped to the same + codomain element. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> h = homomorphism(F, F, [[1, 0], [x, 0]]) + >>> h.is_injective() + False + >>> h.quotient_domain(h.kernel()).is_injective() + True + """ + return self.kernel().is_zero() + + def is_surjective(self): + """ + Return True if ``self`` is surjective. + + That is, check if every element of the codomain has at least one + preimage. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> h = homomorphism(F, F, [[1, 0], [x, 0]]) + >>> h.is_surjective() + False + >>> h.restrict_codomain(h.image()).is_surjective() + True + """ + return self.image() == self.codomain + + def is_isomorphism(self): + """ + Return True if ``self`` is an isomorphism. + + That is, check if every element of the codomain has precisely one + preimage. Equivalently, ``self`` is both injective and surjective. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> h = homomorphism(F, F, [[1, 0], [x, 0]]) + >>> h = h.restrict_codomain(h.image()) + >>> h.is_isomorphism() + False + >>> h.quotient_domain(h.kernel()).is_isomorphism() + True + """ + return self.is_injective() and self.is_surjective() + + def is_zero(self): + """ + Return True if ``self`` is a zero morphism. + + That is, check if every element of the domain is mapped to zero + under self. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> h = homomorphism(F, F, [[1, 0], [x, 0]]) + >>> h.is_zero() + False + >>> h.restrict_domain(F.submodule()).is_zero() + True + >>> h.quotient_codomain(h.image()).is_zero() + True + """ + return self.image().is_zero() + + def __eq__(self, oth): + try: + return (self - oth).is_zero() + except TypeError: + return False + + def __ne__(self, oth): + return not (self == oth) + + +class MatrixHomomorphism(ModuleHomomorphism): + r""" + Helper class for all homomoprhisms which are expressed via a matrix. + + That is, for such homomorphisms ``domain`` is contained in a module + generated by finitely many elements `e_1, \ldots, e_n`, so that the + homomorphism is determined uniquely by its action on the `e_i`. It + can thus be represented as a vector of elements of the codomain module, + or potentially a supermodule of the codomain module + (and hence conventionally as a matrix, if there is a similar interpretation + for elements of the codomain module). + + Note that this class does *not* assume that the `e_i` freely generate a + submodule, nor that ``domain`` is even all of this submodule. It exists + only to unify the interface. + + Do not instantiate. + + Attributes: + + - matrix - the list of images determining the homomorphism. + NOTE: the elements of matrix belong to either self.codomain or + self.codomain.container + + Still non-implemented methods: + + - kernel + - _apply + """ + + def __init__(self, domain, codomain, matrix): + ModuleHomomorphism.__init__(self, domain, codomain) + if len(matrix) != domain.rank: + raise ValueError('Need to provide %s elements, got %s' + % (domain.rank, len(matrix))) + + converter = self.codomain.convert + if isinstance(self.codomain, (SubModule, SubQuotientModule)): + converter = self.codomain.container.convert + self.matrix = tuple(converter(x) for x in matrix) + + def _sympy_matrix(self): + """Helper function which returns a SymPy matrix ``self.matrix``.""" + from sympy.matrices import Matrix + c = lambda x: x + if isinstance(self.codomain, (QuotientModule, SubQuotientModule)): + c = lambda x: x.data + return Matrix([[self.ring.to_sympy(y) for y in c(x)] for x in self.matrix]).T + + def __repr__(self): + lines = repr(self._sympy_matrix()).split('\n') + t = " : %s -> %s" % (self.domain, self.codomain) + s = ' '*len(t) + n = len(lines) + for i in range(n // 2): + lines[i] += s + lines[n // 2] += t + for i in range(n//2 + 1, n): + lines[i] += s + return '\n'.join(lines) + + def _restrict_domain(self, sm): + """Implementation of domain restriction.""" + return SubModuleHomomorphism(sm, self.codomain, self.matrix) + + def _restrict_codomain(self, sm): + """Implementation of codomain restriction.""" + return self.__class__(self.domain, sm, self.matrix) + + def _quotient_domain(self, sm): + """Implementation of domain quotient.""" + return self.__class__(self.domain/sm, self.codomain, self.matrix) + + def _quotient_codomain(self, sm): + """Implementation of codomain quotient.""" + Q = self.codomain/sm + converter = Q.convert + if isinstance(self.codomain, SubModule): + converter = Q.container.convert + return self.__class__(self.domain, self.codomain/sm, + [converter(x) for x in self.matrix]) + + def _add(self, oth): + return self.__class__(self.domain, self.codomain, + [x + y for x, y in zip(self.matrix, oth.matrix)]) + + def _mul_scalar(self, c): + return self.__class__(self.domain, self.codomain, [c*x for x in self.matrix]) + + def _compose(self, oth): + return self.__class__(self.domain, oth.codomain, [oth(x) for x in self.matrix]) + + +class FreeModuleHomomorphism(MatrixHomomorphism): + """ + Concrete class for homomorphisms with domain a free module or a quotient + thereof. + + Do not instantiate; the constructor does not check that your data is well + defined. Use the ``homomorphism`` function instead: + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> homomorphism(F, F, [[1, 0], [0, 1]]) + Matrix([ + [1, 0], : QQ[x]**2 -> QQ[x]**2 + [0, 1]]) + """ + + def _apply(self, elem): + if isinstance(self.domain, QuotientModule): + elem = elem.data + return sum(x * e for x, e in zip(elem, self.matrix)) + + def _image(self): + return self.codomain.submodule(*self.matrix) + + def _kernel(self): + # The domain is either a free module or a quotient thereof. + # It does not matter if it is a quotient, because that won't increase + # the kernel. + # Our generators {e_i} are sent to the matrix entries {b_i}. + # The kernel is essentially the syzygy module of these {b_i}. + syz = self.image().syzygy_module() + return self.domain.submodule(*syz.gens) + + +class SubModuleHomomorphism(MatrixHomomorphism): + """ + Concrete class for homomorphism with domain a submodule of a free module + or a quotient thereof. + + Do not instantiate; the constructor does not check that your data is well + defined. Use the ``homomorphism`` function instead: + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> M = QQ.old_poly_ring(x).free_module(2)*x + >>> homomorphism(M, M, [[1, 0], [0, 1]]) + Matrix([ + [1, 0], : <[x, 0], [0, x]> -> <[x, 0], [0, x]> + [0, 1]]) + """ + + def _apply(self, elem): + if isinstance(self.domain, SubQuotientModule): + elem = elem.data + return sum(x * e for x, e in zip(elem, self.matrix)) + + def _image(self): + return self.codomain.submodule(*[self(x) for x in self.domain.gens]) + + def _kernel(self): + syz = self.image().syzygy_module() + return self.domain.submodule( + *[sum(xi*gi for xi, gi in zip(s, self.domain.gens)) + for s in syz.gens]) + + +def homomorphism(domain, codomain, matrix): + r""" + Create a homomorphism object. + + This function tries to build a homomorphism from ``domain`` to ``codomain`` + via the matrix ``matrix``. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> from sympy.polys.agca import homomorphism + + >>> R = QQ.old_poly_ring(x) + >>> T = R.free_module(2) + + If ``domain`` is a free module generated by `e_1, \ldots, e_n`, then + ``matrix`` should be an n-element iterable `(b_1, \ldots, b_n)` where + the `b_i` are elements of ``codomain``. The constructed homomorphism is the + unique homomorphism sending `e_i` to `b_i`. + + >>> F = R.free_module(2) + >>> h = homomorphism(F, T, [[1, x], [x**2, 0]]) + >>> h + Matrix([ + [1, x**2], : QQ[x]**2 -> QQ[x]**2 + [x, 0]]) + >>> h([1, 0]) + [1, x] + >>> h([0, 1]) + [x**2, 0] + >>> h([1, 1]) + [x**2 + 1, x] + + If ``domain`` is a submodule of a free module, them ``matrix`` determines + a homomoprhism from the containing free module to ``codomain``, and the + homomorphism returned is obtained by restriction to ``domain``. + + >>> S = F.submodule([1, 0], [0, x]) + >>> homomorphism(S, T, [[1, x], [x**2, 0]]) + Matrix([ + [1, x**2], : <[1, 0], [0, x]> -> QQ[x]**2 + [x, 0]]) + + If ``domain`` is a (sub)quotient `N/K`, then ``matrix`` determines a + homomorphism from `N` to ``codomain``. If the kernel contains `K`, this + homomorphism descends to ``domain`` and is returned; otherwise an exception + is raised. + + >>> homomorphism(S/[(1, 0)], T, [0, [x**2, 0]]) + Matrix([ + [0, x**2], : <[1, 0] + <[1, 0]>, [0, x] + <[1, 0]>, [1, 0] + <[1, 0]>> -> QQ[x]**2 + [0, 0]]) + >>> homomorphism(S/[(0, x)], T, [0, [x**2, 0]]) + Traceback (most recent call last): + ... + ValueError: kernel <[1, 0], [0, 0]> must contain sm, got <[0,x]> + + """ + def freepres(module): + """ + Return a tuple ``(F, S, Q, c)`` where ``F`` is a free module, ``S`` is a + submodule of ``F``, and ``Q`` a submodule of ``S``, such that + ``module = S/Q``, and ``c`` is a conversion function. + """ + if isinstance(module, FreeModule): + return module, module, module.submodule(), lambda x: module.convert(x) + if isinstance(module, QuotientModule): + return (module.base, module.base, module.killed_module, + lambda x: module.convert(x).data) + if isinstance(module, SubQuotientModule): + return (module.base.container, module.base, module.killed_module, + lambda x: module.container.convert(x).data) + # an ordinary submodule + return (module.container, module, module.submodule(), + lambda x: module.container.convert(x)) + + SF, SS, SQ, _ = freepres(domain) + TF, TS, TQ, c = freepres(codomain) + # NOTE this is probably a bit inefficient (redundant checks) + return FreeModuleHomomorphism(SF, TF, [c(x) for x in matrix] + ).restrict_domain(SS).restrict_codomain(TS + ).quotient_codomain(TQ).quotient_domain(SQ) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/ideals.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/ideals.py new file mode 100644 index 0000000000000000000000000000000000000000..1969554a1d674bc36ded1a3e312d587c66104086 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/ideals.py @@ -0,0 +1,395 @@ +"""Computations with ideals of polynomial rings.""" + +from sympy.polys.polyerrors import CoercionFailed +from sympy.polys.polyutils import IntegerPowerable + + +class Ideal(IntegerPowerable): + """ + Abstract base class for ideals. + + Do not instantiate - use explicit constructors in the ring class instead: + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> QQ.old_poly_ring(x).ideal(x+1) + + + Attributes + + - ring - the ring this ideal belongs to + + Non-implemented methods: + + - _contains_elem + - _contains_ideal + - _quotient + - _intersect + - _union + - _product + - is_whole_ring + - is_zero + - is_prime, is_maximal, is_primary, is_radical + - is_principal + - height, depth + - radical + + Methods that likely should be overridden in subclasses: + + - reduce_element + """ + + def _contains_elem(self, x): + """Implementation of element containment.""" + raise NotImplementedError + + def _contains_ideal(self, I): + """Implementation of ideal containment.""" + raise NotImplementedError + + def _quotient(self, J): + """Implementation of ideal quotient.""" + raise NotImplementedError + + def _intersect(self, J): + """Implementation of ideal intersection.""" + raise NotImplementedError + + def is_whole_ring(self): + """Return True if ``self`` is the whole ring.""" + raise NotImplementedError + + def is_zero(self): + """Return True if ``self`` is the zero ideal.""" + raise NotImplementedError + + def _equals(self, J): + """Implementation of ideal equality.""" + return self._contains_ideal(J) and J._contains_ideal(self) + + def is_prime(self): + """Return True if ``self`` is a prime ideal.""" + raise NotImplementedError + + def is_maximal(self): + """Return True if ``self`` is a maximal ideal.""" + raise NotImplementedError + + def is_radical(self): + """Return True if ``self`` is a radical ideal.""" + raise NotImplementedError + + def is_primary(self): + """Return True if ``self`` is a primary ideal.""" + raise NotImplementedError + + def is_principal(self): + """Return True if ``self`` is a principal ideal.""" + raise NotImplementedError + + def radical(self): + """Compute the radical of ``self``.""" + raise NotImplementedError + + def depth(self): + """Compute the depth of ``self``.""" + raise NotImplementedError + + def height(self): + """Compute the height of ``self``.""" + raise NotImplementedError + + # TODO more + + # non-implemented methods end here + + def __init__(self, ring): + self.ring = ring + + def _check_ideal(self, J): + """Helper to check ``J`` is an ideal of our ring.""" + if not isinstance(J, Ideal) or J.ring != self.ring: + raise ValueError( + 'J must be an ideal of %s, got %s' % (self.ring, J)) + + def contains(self, elem): + """ + Return True if ``elem`` is an element of this ideal. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).ideal(x+1, x-1).contains(3) + True + >>> QQ.old_poly_ring(x).ideal(x**2, x**3).contains(x) + False + """ + return self._contains_elem(self.ring.convert(elem)) + + def subset(self, other): + """ + Returns True if ``other`` is is a subset of ``self``. + + Here ``other`` may be an ideal. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> I = QQ.old_poly_ring(x).ideal(x+1) + >>> I.subset([x**2 - 1, x**2 + 2*x + 1]) + True + >>> I.subset([x**2 + 1, x + 1]) + False + >>> I.subset(QQ.old_poly_ring(x).ideal(x**2 - 1)) + True + """ + if isinstance(other, Ideal): + return self._contains_ideal(other) + return all(self._contains_elem(x) for x in other) + + def quotient(self, J, **opts): + r""" + Compute the ideal quotient of ``self`` by ``J``. + + That is, if ``self`` is the ideal `I`, compute the set + `I : J = \{x \in R | xJ \subset I \}`. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> R = QQ.old_poly_ring(x, y) + >>> R.ideal(x*y).quotient(R.ideal(x)) + + """ + self._check_ideal(J) + return self._quotient(J, **opts) + + def intersect(self, J): + """ + Compute the intersection of self with ideal J. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> R = QQ.old_poly_ring(x, y) + >>> R.ideal(x).intersect(R.ideal(y)) + + """ + self._check_ideal(J) + return self._intersect(J) + + def saturate(self, J): + r""" + Compute the ideal saturation of ``self`` by ``J``. + + That is, if ``self`` is the ideal `I`, compute the set + `I : J^\infty = \{x \in R | xJ^n \subset I \text{ for some } n\}`. + """ + raise NotImplementedError + # Note this can be implemented using repeated quotient + + def union(self, J): + """ + Compute the ideal generated by the union of ``self`` and ``J``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).ideal(x**2 - 1).union(QQ.old_poly_ring(x).ideal((x+1)**2)) == QQ.old_poly_ring(x).ideal(x+1) + True + """ + self._check_ideal(J) + return self._union(J) + + def product(self, J): + r""" + Compute the ideal product of ``self`` and ``J``. + + That is, compute the ideal generated by products `xy`, for `x` an element + of ``self`` and `y \in J`. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> QQ.old_poly_ring(x, y).ideal(x).product(QQ.old_poly_ring(x, y).ideal(y)) + + """ + self._check_ideal(J) + return self._product(J) + + def reduce_element(self, x): + """ + Reduce the element ``x`` of our ring modulo the ideal ``self``. + + Here "reduce" has no specific meaning: it could return a unique normal + form, simplify the expression a bit, or just do nothing. + """ + return x + + def __add__(self, e): + if not isinstance(e, Ideal): + R = self.ring.quotient_ring(self) + if isinstance(e, R.dtype): + return e + if isinstance(e, R.ring.dtype): + return R(e) + return R.convert(e) + self._check_ideal(e) + return self.union(e) + + __radd__ = __add__ + + def __mul__(self, e): + if not isinstance(e, Ideal): + try: + e = self.ring.ideal(e) + except CoercionFailed: + return NotImplemented + self._check_ideal(e) + return self.product(e) + + __rmul__ = __mul__ + + def _zeroth_power(self): + return self.ring.ideal(1) + + def _first_power(self): + # Raising to any power but 1 returns a new instance. So we mult by 1 + # here so that the first power is no exception. + return self * 1 + + def __eq__(self, e): + if not isinstance(e, Ideal) or e.ring != self.ring: + return False + return self._equals(e) + + def __ne__(self, e): + return not (self == e) + + +class ModuleImplementedIdeal(Ideal): + """ + Ideal implementation relying on the modules code. + + Attributes: + + - _module - the underlying module + """ + + def __init__(self, ring, module): + Ideal.__init__(self, ring) + self._module = module + + def _contains_elem(self, x): + return self._module.contains([x]) + + def _contains_ideal(self, J): + if not isinstance(J, ModuleImplementedIdeal): + raise NotImplementedError + return self._module.is_submodule(J._module) + + def _intersect(self, J): + if not isinstance(J, ModuleImplementedIdeal): + raise NotImplementedError + return self.__class__(self.ring, self._module.intersect(J._module)) + + def _quotient(self, J, **opts): + if not isinstance(J, ModuleImplementedIdeal): + raise NotImplementedError + return self._module.module_quotient(J._module, **opts) + + def _union(self, J): + if not isinstance(J, ModuleImplementedIdeal): + raise NotImplementedError + return self.__class__(self.ring, self._module.union(J._module)) + + @property + def gens(self): + """ + Return generators for ``self``. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x, y + >>> list(QQ.old_poly_ring(x, y).ideal(x, y, x**2 + y).gens) + [DMP_Python([[1], []], QQ), DMP_Python([[1, 0]], QQ), DMP_Python([[1], [], [1, 0]], QQ)] + """ + return (x[0] for x in self._module.gens) + + def is_zero(self): + """ + Return True if ``self`` is the zero ideal. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).ideal(x).is_zero() + False + >>> QQ.old_poly_ring(x).ideal().is_zero() + True + """ + return self._module.is_zero() + + def is_whole_ring(self): + """ + Return True if ``self`` is the whole ring, i.e. one generator is a unit. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ, ilex + >>> QQ.old_poly_ring(x).ideal(x).is_whole_ring() + False + >>> QQ.old_poly_ring(x).ideal(3).is_whole_ring() + True + >>> QQ.old_poly_ring(x, order=ilex).ideal(2 + x).is_whole_ring() + True + """ + return self._module.is_full_module() + + def __repr__(self): + from sympy.printing.str import sstr + gens = [self.ring.to_sympy(x) for [x] in self._module.gens] + return '<' + ','.join(sstr(g) for g in gens) + '>' + + # NOTE this is the only method using the fact that the module is a SubModule + def _product(self, J): + if not isinstance(J, ModuleImplementedIdeal): + raise NotImplementedError + return self.__class__(self.ring, self._module.submodule( + *[[x*y] for [x] in self._module.gens for [y] in J._module.gens])) + + def in_terms_of_generators(self, e): + """ + Express ``e`` in terms of the generators of ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> I = QQ.old_poly_ring(x).ideal(x**2 + 1, x) + >>> I.in_terms_of_generators(1) # doctest: +SKIP + [DMP_Python([1], QQ), DMP_Python([-1, 0], QQ)] + """ + return self._module.in_terms_of_generators([e]) + + def reduce_element(self, x, **options): + return self._module.reduce_element([x], **options)[0] diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/modules.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/modules.py new file mode 100644 index 0000000000000000000000000000000000000000..0a2e2ed814f4143b4b49f8b1f10c2a07cb32d06a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/modules.py @@ -0,0 +1,1488 @@ +""" +Computations with modules over polynomial rings. + +This module implements various classes that encapsulate groebner basis +computations for modules. Most of them should not be instantiated by hand. +Instead, use the constructing routines on objects you already have. + +For example, to construct a free module over ``QQ[x, y]``, call +``QQ[x, y].free_module(rank)`` instead of the ``FreeModule`` constructor. +In fact ``FreeModule`` is an abstract base class that should not be +instantiated, the ``free_module`` method instead returns the implementing class +``FreeModulePolyRing``. + +In general, the abstract base classes implement most functionality in terms of +a few non-implemented methods. The concrete base classes supply only these +non-implemented methods. They may also supply new implementations of the +convenience methods, for example if there are faster algorithms available. +""" + + +from copy import copy +from functools import reduce + +from sympy.polys.agca.ideals import Ideal +from sympy.polys.domains.field import Field +from sympy.polys.orderings import ProductOrder, monomial_key +from sympy.polys.polyclasses import DMP +from sympy.polys.polyerrors import CoercionFailed +from sympy.core.basic import _aresame +from sympy.utilities.iterables import iterable + +# TODO +# - module saturation +# - module quotient/intersection for quotient rings +# - free resoltutions / syzygies +# - finding small/minimal generating sets +# - ... + +########################################################################## +## Abstract base classes ################################################# +########################################################################## + + +class Module: + """ + Abstract base class for modules. + + Do not instantiate - use ring explicit constructors instead: + + >>> from sympy import QQ + >>> from sympy.abc import x + >>> QQ.old_poly_ring(x).free_module(2) + QQ[x]**2 + + Attributes: + + - dtype - type of elements + - ring - containing ring + + Non-implemented methods: + + - submodule + - quotient_module + - is_zero + - is_submodule + - multiply_ideal + + The method convert likely needs to be changed in subclasses. + """ + + def __init__(self, ring): + self.ring = ring + + def convert(self, elem, M=None): + """ + Convert ``elem`` into internal representation of this module. + + If ``M`` is not None, it should be a module containing it. + """ + if not isinstance(elem, self.dtype): + raise CoercionFailed + return elem + + def submodule(self, *gens): + """Generate a submodule.""" + raise NotImplementedError + + def quotient_module(self, other): + """Generate a quotient module.""" + raise NotImplementedError + + def __truediv__(self, e): + if not isinstance(e, Module): + e = self.submodule(*e) + return self.quotient_module(e) + + def contains(self, elem): + """Return True if ``elem`` is an element of this module.""" + try: + self.convert(elem) + return True + except CoercionFailed: + return False + + def __contains__(self, elem): + return self.contains(elem) + + def subset(self, other): + """ + Returns True if ``other`` is is a subset of ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> F.subset([(1, x), (x, 2)]) + True + >>> F.subset([(1/x, x), (x, 2)]) + False + """ + return all(self.contains(x) for x in other) + + def __eq__(self, other): + return self.is_submodule(other) and other.is_submodule(self) + + def __ne__(self, other): + return not (self == other) + + def is_zero(self): + """Returns True if ``self`` is a zero module.""" + raise NotImplementedError + + def is_submodule(self, other): + """Returns True if ``other`` is a submodule of ``self``.""" + raise NotImplementedError + + def multiply_ideal(self, other): + """ + Multiply ``self`` by the ideal ``other``. + """ + raise NotImplementedError + + def __mul__(self, e): + if not isinstance(e, Ideal): + try: + e = self.ring.ideal(e) + except (CoercionFailed, NotImplementedError): + return NotImplemented + return self.multiply_ideal(e) + + __rmul__ = __mul__ + + def identity_hom(self): + """Return the identity homomorphism on ``self``.""" + raise NotImplementedError + + +class ModuleElement: + """ + Base class for module element wrappers. + + Use this class to wrap primitive data types as module elements. It stores + a reference to the containing module, and implements all the arithmetic + operators. + + Attributes: + + - module - containing module + - data - internal data + + Methods that likely need change in subclasses: + + - add + - mul + - div + - eq + """ + + def __init__(self, module, data): + self.module = module + self.data = data + + def add(self, d1, d2): + """Add data ``d1`` and ``d2``.""" + return d1 + d2 + + def mul(self, m, d): + """Multiply module data ``m`` by coefficient d.""" + return m * d + + def div(self, m, d): + """Divide module data ``m`` by coefficient d.""" + return m / d + + def eq(self, d1, d2): + """Return true if d1 and d2 represent the same element.""" + return d1 == d2 + + def __add__(self, om): + if not isinstance(om, self.__class__) or om.module != self.module: + try: + om = self.module.convert(om) + except CoercionFailed: + return NotImplemented + return self.__class__(self.module, self.add(self.data, om.data)) + + __radd__ = __add__ + + def __neg__(self): + return self.__class__(self.module, self.mul(self.data, + self.module.ring.convert(-1))) + + def __sub__(self, om): + if not isinstance(om, self.__class__) or om.module != self.module: + try: + om = self.module.convert(om) + except CoercionFailed: + return NotImplemented + return self.__add__(-om) + + def __rsub__(self, om): + return (-self).__add__(om) + + def __mul__(self, o): + if not isinstance(o, self.module.ring.dtype): + try: + o = self.module.ring.convert(o) + except CoercionFailed: + return NotImplemented + return self.__class__(self.module, self.mul(self.data, o)) + + __rmul__ = __mul__ + + def __truediv__(self, o): + if not isinstance(o, self.module.ring.dtype): + try: + o = self.module.ring.convert(o) + except CoercionFailed: + return NotImplemented + return self.__class__(self.module, self.div(self.data, o)) + + def __eq__(self, om): + if not isinstance(om, self.__class__) or om.module != self.module: + try: + om = self.module.convert(om) + except CoercionFailed: + return False + return self.eq(self.data, om.data) + + def __ne__(self, om): + return not self == om + +########################################################################## +## Free Modules ########################################################## +########################################################################## + + +class FreeModuleElement(ModuleElement): + """Element of a free module. Data stored as a tuple.""" + + def add(self, d1, d2): + return tuple(x + y for x, y in zip(d1, d2)) + + def mul(self, d, p): + return tuple(x * p for x in d) + + def div(self, d, p): + return tuple(x / p for x in d) + + def __repr__(self): + from sympy.printing.str import sstr + data = self.data + if any(isinstance(x, DMP) for x in data): + data = [self.module.ring.to_sympy(x) for x in data] + return '[' + ', '.join(sstr(x) for x in data) + ']' + + def __iter__(self): + return self.data.__iter__() + + def __getitem__(self, idx): + return self.data[idx] + + +class FreeModule(Module): + """ + Abstract base class for free modules. + + Additional attributes: + + - rank - rank of the free module + + Non-implemented methods: + + - submodule + """ + + dtype = FreeModuleElement + + def __init__(self, ring, rank): + Module.__init__(self, ring) + self.rank = rank + + def __repr__(self): + return repr(self.ring) + "**" + repr(self.rank) + + def is_submodule(self, other): + """ + Returns True if ``other`` is a submodule of ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> M = F.submodule([2, x]) + >>> F.is_submodule(F) + True + >>> F.is_submodule(M) + True + >>> M.is_submodule(F) + False + """ + if isinstance(other, SubModule): + return other.container == self + if isinstance(other, FreeModule): + return other.ring == self.ring and other.rank == self.rank + return False + + def convert(self, elem, M=None): + """ + Convert ``elem`` into the internal representation. + + This method is called implicitly whenever computations involve elements + not in the internal representation. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> F.convert([1, 0]) + [1, 0] + """ + if isinstance(elem, FreeModuleElement): + if elem.module is self: + return elem + if elem.module.rank != self.rank: + raise CoercionFailed + return FreeModuleElement(self, + tuple(self.ring.convert(x, elem.module.ring) for x in elem.data)) + elif iterable(elem): + tpl = tuple(self.ring.convert(x) for x in elem) + if len(tpl) != self.rank: + raise CoercionFailed + return FreeModuleElement(self, tpl) + elif _aresame(elem, 0): + return FreeModuleElement(self, (self.ring.convert(0),)*self.rank) + else: + raise CoercionFailed + + def is_zero(self): + """ + Returns True if ``self`` is a zero module. + + (If, as this implementation assumes, the coefficient ring is not the + zero ring, then this is equivalent to the rank being zero.) + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).free_module(0).is_zero() + True + >>> QQ.old_poly_ring(x).free_module(1).is_zero() + False + """ + return self.rank == 0 + + def basis(self): + """ + Return a set of basis elements. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).free_module(3).basis() + ([1, 0, 0], [0, 1, 0], [0, 0, 1]) + """ + from sympy.matrices import eye + M = eye(self.rank) + return tuple(self.convert(M.row(i)) for i in range(self.rank)) + + def quotient_module(self, submodule): + """ + Return a quotient module. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> M = QQ.old_poly_ring(x).free_module(2) + >>> M.quotient_module(M.submodule([1, x], [x, 2])) + QQ[x]**2/<[1, x], [x, 2]> + + Or more conicisely, using the overloaded division operator: + + >>> QQ.old_poly_ring(x).free_module(2) / [[1, x], [x, 2]] + QQ[x]**2/<[1, x], [x, 2]> + """ + return QuotientModule(self.ring, self, submodule) + + def multiply_ideal(self, other): + """ + Multiply ``self`` by the ideal ``other``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> I = QQ.old_poly_ring(x).ideal(x) + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> F.multiply_ideal(I) + <[x, 0], [0, x]> + """ + return self.submodule(*self.basis()).multiply_ideal(other) + + def identity_hom(self): + """ + Return the identity homomorphism on ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).free_module(2).identity_hom() + Matrix([ + [1, 0], : QQ[x]**2 -> QQ[x]**2 + [0, 1]]) + """ + from sympy.polys.agca.homomorphisms import homomorphism + return homomorphism(self, self, self.basis()) + + +class FreeModulePolyRing(FreeModule): + """ + Free module over a generalized polynomial ring. + + Do not instantiate this, use the constructor method of the ring instead: + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(3) + >>> F + QQ[x]**3 + >>> F.contains([x, 1, 0]) + True + >>> F.contains([1/x, 0, 1]) + False + """ + + def __init__(self, ring, rank): + from sympy.polys.domains.old_polynomialring import PolynomialRingBase + FreeModule.__init__(self, ring, rank) + if not isinstance(ring, PolynomialRingBase): + raise NotImplementedError('This implementation only works over ' + + 'polynomial rings, got %s' % ring) + if not isinstance(ring.dom, Field): + raise NotImplementedError('Ground domain must be a field, ' + + 'got %s' % ring.dom) + + def submodule(self, *gens, **opts): + """ + Generate a submodule. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> M = QQ.old_poly_ring(x, y).free_module(2).submodule([x, x + y]) + >>> M + <[x, x + y]> + >>> M.contains([2*x, 2*x + 2*y]) + True + >>> M.contains([x, y]) + False + """ + return SubModulePolyRing(gens, self, **opts) + + +class FreeModuleQuotientRing(FreeModule): + """ + Free module over a quotient ring. + + Do not instantiate this, use the constructor method of the ring instead: + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(3) + >>> F + (QQ[x]/)**3 + + Attributes + + - quot - the quotient module `R^n / IR^n`, where `R/I` is our ring + """ + + def __init__(self, ring, rank): + from sympy.polys.domains.quotientring import QuotientRing + FreeModule.__init__(self, ring, rank) + if not isinstance(ring, QuotientRing): + raise NotImplementedError('This implementation only works over ' + + 'quotient rings, got %s' % ring) + F = self.ring.ring.free_module(self.rank) + self.quot = F / (self.ring.base_ideal*F) + + def __repr__(self): + return "(" + repr(self.ring) + ")" + "**" + repr(self.rank) + + def submodule(self, *gens, **opts): + """ + Generate a submodule. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> M = (QQ.old_poly_ring(x, y)/[x**2 - y**2]).free_module(2).submodule([x, x + y]) + >>> M + <[x + , x + y + ]> + >>> M.contains([y**2, x**2 + x*y]) + True + >>> M.contains([x, y]) + False + """ + return SubModuleQuotientRing(gens, self, **opts) + + def lift(self, elem): + """ + Lift the element ``elem`` of self to the module self.quot. + + Note that self.quot is the same set as self, just as an R-module + and not as an R/I-module, so this makes sense. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(2) + >>> e = F.convert([1, 0]) + >>> e + [1 + , 0 + ] + >>> L = F.quot + >>> l = F.lift(e) + >>> l + [1, 0] + <[x**2 + 1, 0], [0, x**2 + 1]> + >>> L.contains(l) + True + """ + return self.quot.convert([x.data for x in elem]) + + def unlift(self, elem): + """ + Push down an element of self.quot to self. + + This undoes ``lift``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(2) + >>> e = F.convert([1, 0]) + >>> l = F.lift(e) + >>> e == l + False + >>> e == F.unlift(l) + True + """ + return self.convert(elem.data) + +########################################################################## +## Submodules and subquotients ########################################### +########################################################################## + + +class SubModule(Module): + """ + Base class for submodules. + + Attributes: + + - container - containing module + - gens - generators (subset of containing module) + - rank - rank of containing module + + Non-implemented methods: + + - _contains + - _syzygies + - _in_terms_of_generators + - _intersect + - _module_quotient + + Methods that likely need change in subclasses: + + - reduce_element + """ + + def __init__(self, gens, container): + Module.__init__(self, container.ring) + self.gens = tuple(container.convert(x) for x in gens) + self.container = container + self.rank = container.rank + self.ring = container.ring + self.dtype = container.dtype + + def __repr__(self): + return "<" + ", ".join(repr(x) for x in self.gens) + ">" + + def _contains(self, other): + """Implementation of containment. + Other is guaranteed to be FreeModuleElement.""" + raise NotImplementedError + + def _syzygies(self): + """Implementation of syzygy computation wrt self generators.""" + raise NotImplementedError + + def _in_terms_of_generators(self, e): + """Implementation of expression in terms of generators.""" + raise NotImplementedError + + def convert(self, elem, M=None): + """ + Convert ``elem`` into the internal represantition. + + Mostly called implicitly. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> M = QQ.old_poly_ring(x).free_module(2).submodule([1, x]) + >>> M.convert([2, 2*x]) + [2, 2*x] + """ + if isinstance(elem, self.container.dtype) and elem.module is self: + return elem + r = copy(self.container.convert(elem, M)) + r.module = self + if not self._contains(r): + raise CoercionFailed + return r + + def _intersect(self, other): + """Implementation of intersection. + Other is guaranteed to be a submodule of same free module.""" + raise NotImplementedError + + def _module_quotient(self, other): + """Implementation of quotient. + Other is guaranteed to be a submodule of same free module.""" + raise NotImplementedError + + def intersect(self, other, **options): + """ + Returns the intersection of ``self`` with submodule ``other``. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x, y).free_module(2) + >>> F.submodule([x, x]).intersect(F.submodule([y, y])) + <[x*y, x*y]> + + Some implementation allow further options to be passed. Currently, to + only one implemented is ``relations=True``, in which case the function + will return a triple ``(res, rela, relb)``, where ``res`` is the + intersection module, and ``rela`` and ``relb`` are lists of coefficient + vectors, expressing the generators of ``res`` in terms of the + generators of ``self`` (``rela``) and ``other`` (``relb``). + + >>> F.submodule([x, x]).intersect(F.submodule([y, y]), relations=True) + (<[x*y, x*y]>, [(DMP_Python([[1, 0]], QQ),)], [(DMP_Python([[1], []], QQ),)]) + + The above result says: the intersection module is generated by the + single element `(-xy, -xy) = -y (x, x) = -x (y, y)`, where + `(x, x)` and `(y, y)` respectively are the unique generators of + the two modules being intersected. + """ + if not isinstance(other, SubModule): + raise TypeError('%s is not a SubModule' % other) + if other.container != self.container: + raise ValueError( + '%s is contained in a different free module' % other) + return self._intersect(other, **options) + + def module_quotient(self, other, **options): + r""" + Returns the module quotient of ``self`` by submodule ``other``. + + That is, if ``self`` is the module `M` and ``other`` is `N`, then + return the ideal `\{f \in R | fN \subset M\}`. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.abc import x, y + >>> F = QQ.old_poly_ring(x, y).free_module(2) + >>> S = F.submodule([x*y, x*y]) + >>> T = F.submodule([x, x]) + >>> S.module_quotient(T) + + + Some implementations allow further options to be passed. Currently, the + only one implemented is ``relations=True``, which may only be passed + if ``other`` is principal. In this case the function + will return a pair ``(res, rel)`` where ``res`` is the ideal, and + ``rel`` is a list of coefficient vectors, expressing the generators of + the ideal, multiplied by the generator of ``other`` in terms of + generators of ``self``. + + >>> S.module_quotient(T, relations=True) + (, [[DMP_Python([[1]], QQ)]]) + + This means that the quotient ideal is generated by the single element + `y`, and that `y (x, x) = 1 (xy, xy)`, `(x, x)` and `(xy, xy)` being + the generators of `T` and `S`, respectively. + """ + if not isinstance(other, SubModule): + raise TypeError('%s is not a SubModule' % other) + if other.container != self.container: + raise ValueError( + '%s is contained in a different free module' % other) + return self._module_quotient(other, **options) + + def union(self, other): + """ + Returns the module generated by the union of ``self`` and ``other``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(1) + >>> M = F.submodule([x**2 + x]) # + >>> N = F.submodule([x**2 - 1]) # <(x-1)(x+1)> + >>> M.union(N) == F.submodule([x+1]) + True + """ + if not isinstance(other, SubModule): + raise TypeError('%s is not a SubModule' % other) + if other.container != self.container: + raise ValueError( + '%s is contained in a different free module' % other) + return self.__class__(self.gens + other.gens, self.container) + + def is_zero(self): + """ + Return True if ``self`` is a zero module. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> F.submodule([x, 1]).is_zero() + False + >>> F.submodule([0, 0]).is_zero() + True + """ + return all(x == 0 for x in self.gens) + + def submodule(self, *gens): + """ + Generate a submodule. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> M = QQ.old_poly_ring(x).free_module(2).submodule([x, 1]) + >>> M.submodule([x**2, x]) + <[x**2, x]> + """ + if not self.subset(gens): + raise ValueError('%s not a subset of %s' % (gens, self)) + return self.__class__(gens, self.container) + + def is_full_module(self): + """ + Return True if ``self`` is the entire free module. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> F.submodule([x, 1]).is_full_module() + False + >>> F.submodule([1, 1], [1, 2]).is_full_module() + True + """ + return all(self.contains(x) for x in self.container.basis()) + + def is_submodule(self, other): + """ + Returns True if ``other`` is a submodule of ``self``. + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> M = F.submodule([2, x]) + >>> N = M.submodule([2*x, x**2]) + >>> M.is_submodule(M) + True + >>> M.is_submodule(N) + True + >>> N.is_submodule(M) + False + """ + if isinstance(other, SubModule): + return self.container == other.container and \ + all(self.contains(x) for x in other.gens) + if isinstance(other, (FreeModule, QuotientModule)): + return self.container == other and self.is_full_module() + return False + + def syzygy_module(self, **opts): + r""" + Compute the syzygy module of the generators of ``self``. + + Suppose `M` is generated by `f_1, \ldots, f_n` over the ring + `R`. Consider the homomorphism `\phi: R^n \to M`, given by + sending `(r_1, \ldots, r_n) \to r_1 f_1 + \cdots + r_n f_n`. + The syzygy module is defined to be the kernel of `\phi`. + + Examples + ======== + + The syzygy module is zero iff the generators generate freely a free + submodule: + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).free_module(2).submodule([1, 0], [1, 1]).syzygy_module().is_zero() + True + + A slightly more interesting example: + + >>> M = QQ.old_poly_ring(x, y).free_module(2).submodule([x, 2*x], [y, 2*y]) + >>> S = QQ.old_poly_ring(x, y).free_module(2).submodule([y, -x]) + >>> M.syzygy_module() == S + True + """ + F = self.ring.free_module(len(self.gens)) + # NOTE we filter out zero syzygies. This is for convenience of the + # _syzygies function and not meant to replace any real "generating set + # reduction" algorithm + return F.submodule(*[x for x in self._syzygies() if F.convert(x) != 0], + **opts) + + def in_terms_of_generators(self, e): + """ + Express element ``e`` of ``self`` in terms of the generators. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> M = F.submodule([1, 0], [1, 1]) + >>> M.in_terms_of_generators([x, x**2]) # doctest: +SKIP + [DMP_Python([-1, 1, 0], QQ), DMP_Python([1, 0, 0], QQ)] + """ + try: + e = self.convert(e) + except CoercionFailed: + raise ValueError('%s is not an element of %s' % (e, self)) + return self._in_terms_of_generators(e) + + def reduce_element(self, x): + """ + Reduce the element ``x`` of our ring modulo the ideal ``self``. + + Here "reduce" has no specific meaning, it could return a unique normal + form, simplify the expression a bit, or just do nothing. + """ + return x + + def quotient_module(self, other, **opts): + """ + Return a quotient module. + + This is the same as taking a submodule of a quotient of the containing + module. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> S1 = F.submodule([x, 1]) + >>> S2 = F.submodule([x**2, x]) + >>> S1.quotient_module(S2) + <[x, 1] + <[x**2, x]>> + + Or more coincisely, using the overloaded division operator: + + >>> F.submodule([x, 1]) / [(x**2, x)] + <[x, 1] + <[x**2, x]>> + """ + if not self.is_submodule(other): + raise ValueError('%s not a submodule of %s' % (other, self)) + return SubQuotientModule(self.gens, + self.container.quotient_module(other), **opts) + + def __add__(self, oth): + return self.container.quotient_module(self).convert(oth) + + __radd__ = __add__ + + def multiply_ideal(self, I): + """ + Multiply ``self`` by the ideal ``I``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> I = QQ.old_poly_ring(x).ideal(x**2) + >>> M = QQ.old_poly_ring(x).free_module(2).submodule([1, 1]) + >>> I*M + <[x**2, x**2]> + """ + return self.submodule(*[x*g for [x] in I._module.gens for g in self.gens]) + + def inclusion_hom(self): + """ + Return a homomorphism representing the inclusion map of ``self``. + + That is, the natural map from ``self`` to ``self.container``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).free_module(2).submodule([x, x]).inclusion_hom() + Matrix([ + [1, 0], : <[x, x]> -> QQ[x]**2 + [0, 1]]) + """ + return self.container.identity_hom().restrict_domain(self) + + def identity_hom(self): + """ + Return the identity homomorphism on ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> QQ.old_poly_ring(x).free_module(2).submodule([x, x]).identity_hom() + Matrix([ + [1, 0], : <[x, x]> -> <[x, x]> + [0, 1]]) + """ + return self.container.identity_hom().restrict_domain( + self).restrict_codomain(self) + + +class SubQuotientModule(SubModule): + """ + Submodule of a quotient module. + + Equivalently, quotient module of a submodule. + + Do not instantiate this, instead use the submodule or quotient_module + constructing methods: + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> S = F.submodule([1, 0], [1, x]) + >>> Q = F/[(1, 0)] + >>> S/[(1, 0)] == Q.submodule([5, x]) + True + + Attributes: + + - base - base module we are quotient of + - killed_module - submodule used to form the quotient + """ + def __init__(self, gens, container, **opts): + SubModule.__init__(self, gens, container) + self.killed_module = self.container.killed_module + # XXX it is important for some code below that the generators of base + # are in this particular order! + self.base = self.container.base.submodule( + *[x.data for x in self.gens], **opts).union(self.killed_module) + + def _contains(self, elem): + return self.base.contains(elem.data) + + def _syzygies(self): + # let N = self.killed_module be generated by e_1, ..., e_r + # let F = self.base be generated by f_1, ..., f_s and e_1, ..., e_r + # Then self = F/N. + # Let phi: R**s --> self be the evident surjection. + # Similarly psi: R**(s + r) --> F. + # We need to find generators for ker(phi). Let chi: R**s --> F be the + # evident lift of phi. For X in R**s, phi(X) = 0 iff chi(X) is + # contained in N, iff there exists Y in R**r such that + # psi(X, Y) = 0. + # Hence if alpha: R**(s + r) --> R**s is the projection map, then + # ker(phi) = alpha ker(psi). + return [X[:len(self.gens)] for X in self.base._syzygies()] + + def _in_terms_of_generators(self, e): + return self.base._in_terms_of_generators(e.data)[:len(self.gens)] + + def is_full_module(self): + """ + Return True if ``self`` is the entire free module. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> F.submodule([x, 1]).is_full_module() + False + >>> F.submodule([1, 1], [1, 2]).is_full_module() + True + """ + return self.base.is_full_module() + + def quotient_hom(self): + """ + Return the quotient homomorphism to self. + + That is, return the natural map from ``self.base`` to ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> M = (QQ.old_poly_ring(x).free_module(2) / [(1, x)]).submodule([1, 0]) + >>> M.quotient_hom() + Matrix([ + [1, 0], : <[1, 0], [1, x]> -> <[1, 0] + <[1, x]>, [1, x] + <[1, x]>> + [0, 1]]) + """ + return self.base.identity_hom().quotient_codomain(self.killed_module) + + +_subs0 = lambda x: x[0] +_subs1 = lambda x: x[1:] + + +class ModuleOrder(ProductOrder): + """A product monomial order with a zeroth term as module index.""" + + def __init__(self, o1, o2, TOP): + if TOP: + ProductOrder.__init__(self, (o2, _subs1), (o1, _subs0)) + else: + ProductOrder.__init__(self, (o1, _subs0), (o2, _subs1)) + + +class SubModulePolyRing(SubModule): + """ + Submodule of a free module over a generalized polynomial ring. + + Do not instantiate this, use the constructor method of FreeModule instead: + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x, y).free_module(2) + >>> F.submodule([x, y], [1, 0]) + <[x, y], [1, 0]> + + Attributes: + + - order - monomial order used + """ + + #self._gb - cached groebner basis + #self._gbe - cached groebner basis relations + + def __init__(self, gens, container, order="lex", TOP=True): + SubModule.__init__(self, gens, container) + if not isinstance(container, FreeModulePolyRing): + raise NotImplementedError('This implementation is for submodules of ' + + 'FreeModulePolyRing, got %s' % container) + self.order = ModuleOrder(monomial_key(order), self.ring.order, TOP) + self._gb = None + self._gbe = None + + def __eq__(self, other): + if isinstance(other, SubModulePolyRing) and self.order != other.order: + return False + return SubModule.__eq__(self, other) + + def _groebner(self, extended=False): + """Returns a standard basis in sdm form.""" + from sympy.polys.distributedmodules import sdm_groebner, sdm_nf_mora + if self._gbe is None and extended: + gb, gbe = sdm_groebner( + [self.ring._vector_to_sdm(x, self.order) for x in self.gens], + sdm_nf_mora, self.order, self.ring.dom, extended=True) + self._gb, self._gbe = tuple(gb), tuple(gbe) + if self._gb is None: + self._gb = tuple(sdm_groebner( + [self.ring._vector_to_sdm(x, self.order) for x in self.gens], + sdm_nf_mora, self.order, self.ring.dom)) + if extended: + return self._gb, self._gbe + else: + return self._gb + + def _groebner_vec(self, extended=False): + """Returns a standard basis in element form.""" + if not extended: + return [FreeModuleElement(self, + tuple(self.ring._sdm_to_vector(x, self.rank))) + for x in self._groebner()] + gb, gbe = self._groebner(extended=True) + return ([self.convert(self.ring._sdm_to_vector(x, self.rank)) + for x in gb], + [self.ring._sdm_to_vector(x, len(self.gens)) for x in gbe]) + + def _contains(self, x): + from sympy.polys.distributedmodules import sdm_zero, sdm_nf_mora + return sdm_nf_mora(self.ring._vector_to_sdm(x, self.order), + self._groebner(), self.order, self.ring.dom) == \ + sdm_zero() + + def _syzygies(self): + """Compute syzygies. See [SCA, algorithm 2.5.4].""" + # NOTE if self.gens is a standard basis, this can be done more + # efficiently using Schreyer's theorem + + # First bullet point + k = len(self.gens) + r = self.rank + zero = self.ring.convert(0) + one = self.ring.convert(1) + Rkr = self.ring.free_module(r + k) + newgens = [] + for j, f in enumerate(self.gens): + m = [0]*(r + k) + for i, v in enumerate(f): + m[i] = v + for i in range(k): + m[r + i] = one if j == i else zero + m = FreeModuleElement(Rkr, tuple(m)) + newgens.append(m) + # Note: we need *descending* order on module index, and TOP=False to + # get an elimination order + F = Rkr.submodule(*newgens, order='ilex', TOP=False) + + # Second bullet point: standard basis of F + G = F._groebner_vec() + + # Third bullet point: G0 = G intersect the new k components + G0 = [x[r:] for x in G if all(y == zero for y in x[:r])] + + # Fourth and fifth bullet points: we are done + return G0 + + def _in_terms_of_generators(self, e): + """Expression in terms of generators. See [SCA, 2.8.1].""" + # NOTE: if gens is a standard basis, this can be done more efficiently + M = self.ring.free_module(self.rank).submodule(*((e,) + self.gens)) + S = M.syzygy_module( + order="ilex", TOP=False) # We want decreasing order! + G = S._groebner_vec() + # This list cannot not be empty since e is an element + e = [x for x in G if self.ring.is_unit(x[0])][0] + return [-x/e[0] for x in e[1:]] + + def reduce_element(self, x, NF=None): + """ + Reduce the element ``x`` of our container modulo ``self``. + + This applies the normal form ``NF`` to ``x``. If ``NF`` is passed + as none, the default Mora normal form is used (which is not unique!). + """ + from sympy.polys.distributedmodules import sdm_nf_mora + if NF is None: + NF = sdm_nf_mora + return self.container.convert(self.ring._sdm_to_vector(NF( + self.ring._vector_to_sdm(x, self.order), self._groebner(), + self.order, self.ring.dom), + self.rank)) + + def _intersect(self, other, relations=False): + # See: [SCA, section 2.8.2] + fi = self.gens + hi = other.gens + r = self.rank + ci = [[0]*(2*r) for _ in range(r)] + for k in range(r): + ci[k][k] = 1 + ci[k][r + k] = 1 + di = [list(f) + [0]*r for f in fi] + ei = [[0]*r + list(h) for h in hi] + syz = self.ring.free_module(2*r).submodule(*(ci + di + ei))._syzygies() + nonzero = [x for x in syz if any(y != self.ring.zero for y in x[:r])] + res = self.container.submodule(*([-y for y in x[:r]] for x in nonzero)) + reln1 = [x[r:r + len(fi)] for x in nonzero] + reln2 = [x[r + len(fi):] for x in nonzero] + if relations: + return res, reln1, reln2 + return res + + def _module_quotient(self, other, relations=False): + # See: [SCA, section 2.8.4] + if relations and len(other.gens) != 1: + raise NotImplementedError + if len(other.gens) == 0: + return self.ring.ideal(1) + elif len(other.gens) == 1: + # We do some trickery. Let f be the (vector!) generating ``other`` + # and f1, .., fn be the (vectors) generating self. + # Consider the submodule of R^{r+1} generated by (f, 1) and + # {(fi, 0) | i}. Then the intersection with the last module + # component yields the quotient. + g1 = list(other.gens[0]) + [1] + gi = [list(x) + [0] for x in self.gens] + # NOTE: We *need* to use an elimination order + M = self.ring.free_module(self.rank + 1).submodule(*([g1] + gi), + order='ilex', TOP=False) + if not relations: + return self.ring.ideal(*[x[-1] for x in M._groebner_vec() if + all(y == self.ring.zero for y in x[:-1])]) + else: + G, R = M._groebner_vec(extended=True) + indices = [i for i, x in enumerate(G) if + all(y == self.ring.zero for y in x[:-1])] + return (self.ring.ideal(*[G[i][-1] for i in indices]), + [[-x for x in R[i][1:]] for i in indices]) + # For more generators, we use I : = intersection of + # {I : | i} + # TODO this can be done more efficiently + return reduce(lambda x, y: x.intersect(y), + (self._module_quotient(self.container.submodule(x)) for x in other.gens)) + + +class SubModuleQuotientRing(SubModule): + """ + Class for submodules of free modules over quotient rings. + + Do not instantiate this. Instead use the submodule methods. + + >>> from sympy.abc import x, y + >>> from sympy import QQ + >>> M = (QQ.old_poly_ring(x, y)/[x**2 - y**2]).free_module(2).submodule([x, x + y]) + >>> M + <[x + , x + y + ]> + >>> M.contains([y**2, x**2 + x*y]) + True + >>> M.contains([x, y]) + False + + Attributes: + + - quot - the subquotient of `R^n/IR^n` generated by lifts of our generators + """ + + def __init__(self, gens, container): + SubModule.__init__(self, gens, container) + self.quot = self.container.quot.submodule( + *[self.container.lift(x) for x in self.gens]) + + def _contains(self, elem): + return self.quot._contains(self.container.lift(elem)) + + def _syzygies(self): + return [tuple(self.ring.convert(y, self.quot.ring) for y in x) + for x in self.quot._syzygies()] + + def _in_terms_of_generators(self, elem): + return [self.ring.convert(x, self.quot.ring) for x in + self.quot._in_terms_of_generators(self.container.lift(elem))] + +########################################################################## +## Quotient Modules ###################################################### +########################################################################## + + +class QuotientModuleElement(ModuleElement): + """Element of a quotient module.""" + + def eq(self, d1, d2): + """Equality comparison.""" + return self.module.killed_module.contains(d1 - d2) + + def __repr__(self): + return repr(self.data) + " + " + repr(self.module.killed_module) + + +class QuotientModule(Module): + """ + Class for quotient modules. + + Do not instantiate this directly. For subquotients, see the + SubQuotientModule class. + + Attributes: + + - base - the base module we are a quotient of + - killed_module - the submodule used to form the quotient + - rank of the base + """ + + dtype = QuotientModuleElement + + def __init__(self, ring, base, submodule): + Module.__init__(self, ring) + if not base.is_submodule(submodule): + raise ValueError('%s is not a submodule of %s' % (submodule, base)) + self.base = base + self.killed_module = submodule + self.rank = base.rank + + def __repr__(self): + return repr(self.base) + "/" + repr(self.killed_module) + + def is_zero(self): + """ + Return True if ``self`` is a zero module. + + This happens if and only if the base module is the same as the + submodule being killed. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) + >>> (F/[(1, 0)]).is_zero() + False + >>> (F/[(1, 0), (0, 1)]).is_zero() + True + """ + return self.base == self.killed_module + + def is_submodule(self, other): + """ + Return True if ``other`` is a submodule of ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> Q = QQ.old_poly_ring(x).free_module(2) / [(x, x)] + >>> S = Q.submodule([1, 0]) + >>> Q.is_submodule(S) + True + >>> S.is_submodule(Q) + False + """ + if isinstance(other, QuotientModule): + return self.killed_module == other.killed_module and \ + self.base.is_submodule(other.base) + if isinstance(other, SubQuotientModule): + return other.container == self + return False + + def submodule(self, *gens, **opts): + """ + Generate a submodule. + + This is the same as taking a quotient of a submodule of the base + module. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> Q = QQ.old_poly_ring(x).free_module(2) / [(x, x)] + >>> Q.submodule([x, 0]) + <[x, 0] + <[x, x]>> + """ + return SubQuotientModule(gens, self, **opts) + + def convert(self, elem, M=None): + """ + Convert ``elem`` into the internal representation. + + This method is called implicitly whenever computations involve elements + not in the internal representation. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> F = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)] + >>> F.convert([1, 0]) + [1, 0] + <[1, 2], [1, x]> + """ + if isinstance(elem, QuotientModuleElement): + if elem.module is self: + return elem + if self.killed_module.is_submodule(elem.module.killed_module): + return QuotientModuleElement(self, self.base.convert(elem.data)) + raise CoercionFailed + return QuotientModuleElement(self, self.base.convert(elem)) + + def identity_hom(self): + """ + Return the identity homomorphism on ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> M = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)] + >>> M.identity_hom() + Matrix([ + [1, 0], : QQ[x]**2/<[1, 2], [1, x]> -> QQ[x]**2/<[1, 2], [1, x]> + [0, 1]]) + """ + return self.base.identity_hom().quotient_codomain( + self.killed_module).quotient_domain(self.killed_module) + + def quotient_hom(self): + """ + Return the quotient homomorphism to ``self``. + + That is, return a homomorphism representing the natural map from + ``self.base`` to ``self``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import QQ + >>> M = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)] + >>> M.quotient_hom() + Matrix([ + [1, 0], : QQ[x]**2 -> QQ[x]**2/<[1, 2], [1, x]> + [0, 1]]) + """ + return self.base.identity_hom().quotient_codomain( + self.killed_module) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f213ad1cd3ebf4c9770536fbcdbc9043e2f3d5e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_extensions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_extensions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be9b654b9a48a9add778dd575b01196764322de1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_extensions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_homomorphisms.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_homomorphisms.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e27b86194baecb0f0b1dcddc8319204238bfd70b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_homomorphisms.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_ideals.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_ideals.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6239193b62f3b1dfa5890821d9e5fa23715a812 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_ideals.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_modules.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_modules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..768c338f1a067479b06515735b7e9941623c4d34 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/__pycache__/test_modules.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_extensions.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_extensions.py new file mode 100644 index 0000000000000000000000000000000000000000..4becf4fd800a7a34c16989adaaf97e312c18f01c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_extensions.py @@ -0,0 +1,196 @@ +from sympy.core.symbol import symbols +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.polys import QQ, ZZ +from sympy.polys.polytools import Poly +from sympy.polys.polyerrors import NotInvertible +from sympy.polys.agca.extensions import FiniteExtension +from sympy.polys.domainmatrix import DomainMatrix + +from sympy.testing.pytest import raises + +from sympy.abc import x, y, t + + +def test_FiniteExtension(): + # Gaussian integers + A = FiniteExtension(Poly(x**2 + 1, x)) + assert A.rank == 2 + assert str(A) == 'ZZ[x]/(x**2 + 1)' + i = A.generator + assert i.parent() is A + + assert i*i == A(-1) + raises(TypeError, lambda: i*()) + + assert A.basis == (A.one, i) + assert A(1) == A.one + assert i**2 == A(-1) + assert i**2 != -1 # no coercion + assert (2 + i)*(1 - i) == 3 - i + assert (1 + i)**8 == A(16) + assert A(1).inverse() == A(1) + raises(NotImplementedError, lambda: A(2).inverse()) + + # Finite field of order 27 + F = FiniteExtension(Poly(x**3 - x + 1, x, modulus=3)) + assert F.rank == 3 + a = F.generator # also generates the cyclic group F - {0} + assert F.basis == (F(1), a, a**2) + assert a**27 == a + assert a**26 == F(1) + assert a**13 == F(-1) + assert a**9 == a + 1 + assert a**3 == a - 1 + assert a**6 == a**2 + a + 1 + assert F(x**2 + x).inverse() == 1 - a + assert F(x + 2)**(-1) == F(x + 2).inverse() + assert a**19 * a**(-19) == F(1) + assert (a - 1) / (2*a**2 - 1) == a**2 + 1 + assert (a - 1) // (2*a**2 - 1) == a**2 + 1 + assert 2/(a**2 + 1) == a**2 - a + 1 + assert (a**2 + 1)/2 == -a**2 - 1 + raises(NotInvertible, lambda: F(0).inverse()) + + # Function field of an elliptic curve + K = FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True)) + assert K.rank == 2 + assert str(K) == 'ZZ(x)[t]/(t**2 - x**3 - x + 1)' + y = K.generator + c = 1/(x**3 - x**2 + x - 1) + assert ((y + x)*(y - x)).inverse() == K(c) + assert (y + x)*(y - x)*c == K(1) # explicit inverse of y + x + + +def test_FiniteExtension_eq_hash(): + # Test eq and hash + p1 = Poly(x**2 - 2, x, domain=ZZ) + p2 = Poly(x**2 - 2, x, domain=QQ) + K1 = FiniteExtension(p1) + K2 = FiniteExtension(p2) + assert K1 == FiniteExtension(Poly(x**2 - 2)) + assert K2 != FiniteExtension(Poly(x**2 - 2)) + assert len({K1, K2, FiniteExtension(p1)}) == 2 + + +def test_FiniteExtension_mod(): + # Test mod + K = FiniteExtension(Poly(x**3 + 1, x, domain=QQ)) + xf = K(x) + assert (xf**2 - 1) % 1 == K.zero + assert 1 % (xf**2 - 1) == K.zero + assert (xf**2 - 1) / (xf - 1) == xf + 1 + assert (xf**2 - 1) // (xf - 1) == xf + 1 + assert (xf**2 - 1) % (xf - 1) == K.zero + raises(ZeroDivisionError, lambda: (xf**2 - 1) % 0) + raises(TypeError, lambda: xf % []) + raises(TypeError, lambda: [] % xf) + + # Test mod over ring + K = FiniteExtension(Poly(x**3 + 1, x, domain=ZZ)) + xf = K(x) + assert (xf**2 - 1) % 1 == K.zero + raises(NotImplementedError, lambda: (xf**2 - 1) % (xf - 1)) + + +def test_FiniteExtension_from_sympy(): + # Test to_sympy/from_sympy + K = FiniteExtension(Poly(x**3 + 1, x, domain=ZZ)) + xf = K(x) + assert K.from_sympy(x) == xf + assert K.to_sympy(xf) == x + + +def test_FiniteExtension_set_domain(): + KZ = FiniteExtension(Poly(x**2 + 1, x, domain='ZZ')) + KQ = FiniteExtension(Poly(x**2 + 1, x, domain='QQ')) + assert KZ.set_domain(QQ) == KQ + + +def test_FiniteExtension_exquo(): + # Test exquo + K = FiniteExtension(Poly(x**4 + 1)) + xf = K(x) + assert K.exquo(xf**2 - 1, xf - 1) == xf + 1 + + +def test_FiniteExtension_convert(): + # Test from_MonogenicFiniteExtension + K1 = FiniteExtension(Poly(x**2 + 1)) + K2 = QQ[x] + x1, x2 = K1(x), K2(x) + assert K1.convert(x2) == x1 + assert K2.convert(x1) == x2 + + K = FiniteExtension(Poly(x**2 - 1, domain=QQ)) + assert K.convert_from(QQ(1, 2), QQ) == K.one/2 + + +def test_FiniteExtension_division_ring(): + # Test division in FiniteExtension over a ring + KQ = FiniteExtension(Poly(x**2 - 1, x, domain=QQ)) + KZ = FiniteExtension(Poly(x**2 - 1, x, domain=ZZ)) + KQt = FiniteExtension(Poly(x**2 - 1, x, domain=QQ[t])) + KQtf = FiniteExtension(Poly(x**2 - 1, x, domain=QQ.frac_field(t))) + assert KQ.is_Field is True + assert KZ.is_Field is False + assert KQt.is_Field is False + assert KQtf.is_Field is True + for K in KQ, KZ, KQt, KQtf: + xK = K.convert(x) + assert xK / K.one == xK + assert xK // K.one == xK + assert xK % K.one == K.zero + raises(ZeroDivisionError, lambda: xK / K.zero) + raises(ZeroDivisionError, lambda: xK // K.zero) + raises(ZeroDivisionError, lambda: xK % K.zero) + if K.is_Field: + assert xK / xK == K.one + assert xK // xK == K.one + assert xK % xK == K.zero + else: + raises(NotImplementedError, lambda: xK / xK) + raises(NotImplementedError, lambda: xK // xK) + raises(NotImplementedError, lambda: xK % xK) + + +def test_FiniteExtension_Poly(): + K = FiniteExtension(Poly(x**2 - 2)) + p = Poly(x, y, domain=K) + assert p.domain == K + assert p.as_expr() == x + assert (p**2).as_expr() == 2 + + K = FiniteExtension(Poly(x**2 - 2, x, domain=QQ)) + K2 = FiniteExtension(Poly(t**2 - 2, t, domain=K)) + assert str(K2) == 'QQ[x]/(x**2 - 2)[t]/(t**2 - 2)' + + eK = K2.convert(x + t) + assert K2.to_sympy(eK) == x + t + assert K2.to_sympy(eK ** 2) == 4 + 2*x*t + p = Poly(x + t, y, domain=K2) + assert p**2 == Poly(4 + 2*x*t, y, domain=K2) + + +def test_FiniteExtension_sincos_jacobian(): + # Use FiniteExtensino to compute the Jacobian of a matrix involving sin + # and cos of different symbols. + r, p, t = symbols('rho, phi, theta') + elements = [ + [sin(p)*cos(t), r*cos(p)*cos(t), -r*sin(p)*sin(t)], + [sin(p)*sin(t), r*cos(p)*sin(t), r*sin(p)*cos(t)], + [ cos(p), -r*sin(p), 0], + ] + + def make_extension(K): + K = FiniteExtension(Poly(sin(p)**2+cos(p)**2-1, sin(p), domain=K[cos(p)])) + K = FiniteExtension(Poly(sin(t)**2+cos(t)**2-1, sin(t), domain=K[cos(t)])) + return K + + Ksc1 = make_extension(ZZ[r]) + Ksc2 = make_extension(ZZ)[r] + + for K in [Ksc1, Ksc2]: + elements_K = [[K.convert(e) for e in row] for row in elements] + J = DomainMatrix(elements_K, (3, 3), K) + det = J.charpoly()[-1] * (-K.one)**3 + assert det == K.convert(r**2*sin(p)) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_homomorphisms.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_homomorphisms.py new file mode 100644 index 0000000000000000000000000000000000000000..2e63838e09ed9b9436a58a7d8041175e731bc4ef --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_homomorphisms.py @@ -0,0 +1,113 @@ +"""Tests for homomorphisms.""" + +from sympy.core.singleton import S +from sympy.polys.domains.rationalfield import QQ +from sympy.abc import x, y +from sympy.polys.agca import homomorphism +from sympy.testing.pytest import raises + + +def test_printing(): + R = QQ.old_poly_ring(x) + + assert str(homomorphism(R.free_module(1), R.free_module(1), [0])) == \ + 'Matrix([[0]]) : QQ[x]**1 -> QQ[x]**1' + assert str(homomorphism(R.free_module(2), R.free_module(2), [0, 0])) == \ + 'Matrix([ \n[0, 0], : QQ[x]**2 -> QQ[x]**2\n[0, 0]]) ' + assert str(homomorphism(R.free_module(1), R.free_module(1) / [[x]], [0])) == \ + 'Matrix([[0]]) : QQ[x]**1 -> QQ[x]**1/<[x]>' + assert str(R.free_module(0).identity_hom()) == 'Matrix(0, 0, []) : QQ[x]**0 -> QQ[x]**0' + +def test_operations(): + F = QQ.old_poly_ring(x).free_module(2) + G = QQ.old_poly_ring(x).free_module(3) + f = F.identity_hom() + g = homomorphism(F, F, [0, [1, x]]) + h = homomorphism(F, F, [[1, 0], 0]) + i = homomorphism(F, G, [[1, 0, 0], [0, 1, 0]]) + + assert f == f + assert f != g + assert f != i + assert (f != F.identity_hom()) is False + assert 2*f == f*2 == homomorphism(F, F, [[2, 0], [0, 2]]) + assert f/2 == homomorphism(F, F, [[S.Half, 0], [0, S.Half]]) + assert f + g == homomorphism(F, F, [[1, 0], [1, x + 1]]) + assert f - g == homomorphism(F, F, [[1, 0], [-1, 1 - x]]) + assert f*g == g == g*f + assert h*g == homomorphism(F, F, [0, [1, 0]]) + assert g*h == homomorphism(F, F, [0, 0]) + assert i*f == i + assert f([1, 2]) == [1, 2] + assert g([1, 2]) == [2, 2*x] + + assert i.restrict_domain(F.submodule([x, x]))([x, x]) == i([x, x]) + h1 = h.quotient_domain(F.submodule([0, 1])) + assert h1([1, 0]) == h([1, 0]) + assert h1.restrict_domain(h1.domain.submodule([x, 0]))([x, 0]) == h([x, 0]) + + raises(TypeError, lambda: f/g) + raises(TypeError, lambda: f + 1) + raises(TypeError, lambda: f + i) + raises(TypeError, lambda: f - 1) + raises(TypeError, lambda: f*i) + + +def test_creation(): + F = QQ.old_poly_ring(x).free_module(3) + G = QQ.old_poly_ring(x).free_module(2) + SM = F.submodule([1, 1, 1]) + Q = F / SM + SQ = Q.submodule([1, 0, 0]) + + matrix = [[1, 0], [0, 1], [-1, -1]] + h = homomorphism(F, G, matrix) + h2 = homomorphism(Q, G, matrix) + assert h.quotient_domain(SM) == h2 + raises(ValueError, lambda: h.quotient_domain(F.submodule([1, 0, 0]))) + assert h2.restrict_domain(SQ) == homomorphism(SQ, G, matrix) + raises(ValueError, lambda: h.restrict_domain(G)) + raises(ValueError, lambda: h.restrict_codomain(G.submodule([1, 0]))) + raises(ValueError, lambda: h.quotient_codomain(F)) + + im = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + for M in [F, SM, Q, SQ]: + assert M.identity_hom() == homomorphism(M, M, im) + assert SM.inclusion_hom() == homomorphism(SM, F, im) + assert SQ.inclusion_hom() == homomorphism(SQ, Q, im) + assert Q.quotient_hom() == homomorphism(F, Q, im) + assert SQ.quotient_hom() == homomorphism(SQ.base, SQ, im) + + class conv: + def convert(x, y=None): + return x + + class dummy: + container = conv() + + def submodule(*args): + return None + raises(TypeError, lambda: homomorphism(dummy(), G, matrix)) + raises(TypeError, lambda: homomorphism(F, dummy(), matrix)) + raises( + ValueError, lambda: homomorphism(QQ.old_poly_ring(x, y).free_module(3), G, matrix)) + raises(ValueError, lambda: homomorphism(F, G, [0, 0])) + + +def test_properties(): + R = QQ.old_poly_ring(x, y) + F = R.free_module(2) + h = homomorphism(F, F, [[x, 0], [y, 0]]) + assert h.kernel() == F.submodule([-y, x]) + assert h.image() == F.submodule([x, 0], [y, 0]) + assert not h.is_injective() + assert not h.is_surjective() + assert h.restrict_codomain(h.image()).is_surjective() + assert h.restrict_domain(F.submodule([1, 0])).is_injective() + assert h.quotient_domain( + h.kernel()).restrict_codomain(h.image()).is_isomorphism() + + R2 = QQ.old_poly_ring(x, y, order=(("lex", x), ("ilex", y))) / [x**2 + 1] + F = R2.free_module(2) + h = homomorphism(F, F, [[x, 0], [y, y + 1]]) + assert h.is_isomorphism() diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_ideals.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_ideals.py new file mode 100644 index 0000000000000000000000000000000000000000..b7fff0674b54a22e2a5acba5110d62d96a877074 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_ideals.py @@ -0,0 +1,131 @@ +"""Test ideals.py code.""" + +from sympy.polys import QQ, ilex +from sympy.abc import x, y, z +from sympy.testing.pytest import raises + + +def test_ideal_operations(): + R = QQ.old_poly_ring(x, y) + I = R.ideal(x) + J = R.ideal(y) + S = R.ideal(x*y) + T = R.ideal(x, y) + + assert not (I == J) + assert I == I + + assert I.union(J) == T + assert I + J == T + assert I + T == T + + assert not I.subset(T) + assert T.subset(I) + + assert I.product(J) == S + assert I*J == S + assert x*J == S + assert I*y == S + assert R.convert(x)*J == S + assert I*R.convert(y) == S + + assert not I.is_zero() + assert not J.is_whole_ring() + + assert R.ideal(x**2 + 1, x).is_whole_ring() + assert R.ideal() == R.ideal(0) + assert R.ideal().is_zero() + + assert T.contains(x*y) + assert T.subset([x, y]) + + assert T.in_terms_of_generators(x) == [R(1), R(0)] + + assert T**0 == R.ideal(1) + assert T**1 == T + assert T**2 == R.ideal(x**2, y**2, x*y) + assert I**5 == R.ideal(x**5) + + +def test_exceptions(): + I = QQ.old_poly_ring(x).ideal(x) + J = QQ.old_poly_ring(y).ideal(1) + raises(ValueError, lambda: I.union(x)) + raises(ValueError, lambda: I + J) + raises(ValueError, lambda: I * J) + raises(ValueError, lambda: I.union(J)) + assert (I == J) is False + assert I != J + + +def test_nontriv_global(): + R = QQ.old_poly_ring(x, y, z) + + def contains(I, f): + return R.ideal(*I).contains(f) + + assert contains([x, y], x) + assert contains([x, y], x + y) + assert not contains([x, y], 1) + assert not contains([x, y], z) + assert contains([x**2 + y, x**2 + x], x - y) + assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2) + assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**3) + assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4) + assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y**2) + assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4 + y**3 + 2*z*y*x) + assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y*z) + assert contains([x, 1 + x + y, 5 - 7*y], 1) + assert contains( + [x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z], + x**3) + assert not contains( + [x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z], + x**2 + y**2) + + # compare local order + assert not contains([x*(1 + x + y), y*(1 + z)], x) + assert not contains([x*(1 + x + y), y*(1 + z)], x + y) + + +def test_nontriv_local(): + R = QQ.old_poly_ring(x, y, z, order=ilex) + + def contains(I, f): + return R.ideal(*I).contains(f) + + assert contains([x, y], x) + assert contains([x, y], x + y) + assert not contains([x, y], 1) + assert not contains([x, y], z) + assert contains([x**2 + y, x**2 + x], x - y) + assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2) + assert contains([x*(1 + x + y), y*(1 + z)], x) + assert contains([x*(1 + x + y), y*(1 + z)], x + y) + + +def test_intersection(): + R = QQ.old_poly_ring(x, y, z) + # SCA, example 1.8.11 + assert R.ideal(x, y).intersect(R.ideal(y**2, z)) == R.ideal(y**2, y*z, x*z) + + assert R.ideal(x, y).intersect(R.ideal()).is_zero() + + R = QQ.old_poly_ring(x, y, z, order="ilex") + assert R.ideal(x, y).intersect(R.ideal(y**2 + y**2*z, z + z*x**3*y)) == \ + R.ideal(y**2, y*z, x*z) + + +def test_quotient(): + # SCA, example 1.8.13 + R = QQ.old_poly_ring(x, y, z) + assert R.ideal(x, y).quotient(R.ideal(y**2, z)) == R.ideal(x, y) + + +def test_reduction(): + from sympy.polys.distributedmodules import sdm_nf_buchberger_reduced + R = QQ.old_poly_ring(x, y) + I = R.ideal(x**5, y) + e = R.convert(x**3 + y**2) + assert I.reduce_element(e) == e + assert I.reduce_element(e, NF=sdm_nf_buchberger_reduced) == R.convert(x**3) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_modules.py b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_modules.py new file mode 100644 index 0000000000000000000000000000000000000000..29c2d4ce45f452f6f61420654be64a67d13b396b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/agca/tests/test_modules.py @@ -0,0 +1,408 @@ +"""Test modules.py code.""" + +from sympy.polys.agca.modules import FreeModule, ModuleOrder, FreeModulePolyRing +from sympy.polys import CoercionFailed, QQ, lex, grlex, ilex, ZZ +from sympy.abc import x, y, z +from sympy.testing.pytest import raises +from sympy.core.numbers import Rational + + +def test_FreeModuleElement(): + M = QQ.old_poly_ring(x).free_module(3) + e = M.convert([1, x, x**2]) + f = [QQ.old_poly_ring(x).convert(1), QQ.old_poly_ring(x).convert(x), QQ.old_poly_ring(x).convert(x**2)] + assert list(e) == f + assert f[0] == e[0] + assert f[1] == e[1] + assert f[2] == e[2] + raises(IndexError, lambda: e[3]) + + g = M.convert([x, 0, 0]) + assert e + g == M.convert([x + 1, x, x**2]) + assert f + g == M.convert([x + 1, x, x**2]) + assert -e == M.convert([-1, -x, -x**2]) + assert e - g == M.convert([1 - x, x, x**2]) + assert e != g + + assert M.convert([x, x, x]) / QQ.old_poly_ring(x).convert(x) == [1, 1, 1] + R = QQ.old_poly_ring(x, order="ilex") + assert R.free_module(1).convert([x]) / R.convert(x) == [1] + + +def test_FreeModule(): + M1 = FreeModule(QQ.old_poly_ring(x), 2) + assert M1 == FreeModule(QQ.old_poly_ring(x), 2) + assert M1 != FreeModule(QQ.old_poly_ring(y), 2) + assert M1 != FreeModule(QQ.old_poly_ring(x), 3) + M2 = FreeModule(QQ.old_poly_ring(x, order="ilex"), 2) + + assert [x, 1] in M1 + assert [x] not in M1 + assert [2, y] not in M1 + assert [1/(x + 1), 2] not in M1 + + e = M1.convert([x, x**2 + 1]) + X = QQ.old_poly_ring(x).convert(x) + assert e == [X, X**2 + 1] + assert e == [x, x**2 + 1] + assert 2*e == [2*x, 2*x**2 + 2] + assert e*2 == [2*x, 2*x**2 + 2] + assert e/2 == [x/2, (x**2 + 1)/2] + assert x*e == [x**2, x**3 + x] + assert e*x == [x**2, x**3 + x] + assert X*e == [x**2, x**3 + x] + assert e*X == [x**2, x**3 + x] + + assert [x, 1] in M2 + assert [x] not in M2 + assert [2, y] not in M2 + assert [1/(x + 1), 2] in M2 + + e = M2.convert([x, x**2 + 1]) + X = QQ.old_poly_ring(x, order="ilex").convert(x) + assert e == [X, X**2 + 1] + assert e == [x, x**2 + 1] + assert 2*e == [2*x, 2*x**2 + 2] + assert e*2 == [2*x, 2*x**2 + 2] + assert e/2 == [x/2, (x**2 + 1)/2] + assert x*e == [x**2, x**3 + x] + assert e*x == [x**2, x**3 + x] + assert e/(1 + x) == [x/(1 + x), (x**2 + 1)/(1 + x)] + assert X*e == [x**2, x**3 + x] + assert e*X == [x**2, x**3 + x] + + M3 = FreeModule(QQ.old_poly_ring(x, y), 2) + assert M3.convert(e) == M3.convert([x, x**2 + 1]) + + assert not M3.is_submodule(0) + assert not M3.is_zero() + + raises(NotImplementedError, lambda: ZZ.old_poly_ring(x).free_module(2)) + raises(NotImplementedError, lambda: FreeModulePolyRing(ZZ, 2)) + raises(CoercionFailed, lambda: M1.convert(QQ.old_poly_ring(x).free_module(3) + .convert([1, 2, 3]))) + raises(CoercionFailed, lambda: M3.convert(1)) + + +def test_ModuleOrder(): + o1 = ModuleOrder(lex, grlex, False) + o2 = ModuleOrder(ilex, lex, False) + + assert o1 == ModuleOrder(lex, grlex, False) + assert (o1 != ModuleOrder(lex, grlex, False)) is False + assert o1 != o2 + + assert o1((1, 2, 3)) == (1, (5, (2, 3))) + assert o2((1, 2, 3)) == (-1, (2, 3)) + + +def test_SubModulePolyRing_global(): + R = QQ.old_poly_ring(x, y) + F = R.free_module(3) + Fd = F.submodule([1, 0, 0], [1, 2, 0], [1, 2, 3]) + M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1]) + + assert F == Fd + assert Fd == F + assert F != M + assert M != F + assert Fd != M + assert M != Fd + assert Fd == F.submodule(*F.basis()) + + assert Fd.is_full_module() + assert not M.is_full_module() + assert not Fd.is_zero() + assert not M.is_zero() + assert Fd.submodule().is_zero() + + assert M.contains([x**2 + y**2 + x, 1 + y, 1]) + assert not M.contains([x**2 + y**2 + x, 1 + y, 2]) + assert M.contains([y**2, 1 - x*y, -x]) + + assert not F.submodule([1 + x, 0, 0]) == F.submodule([1, 0, 0]) + assert F.submodule([1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1])) == F + assert not M.is_submodule(0) + + m = F.convert([x**2 + y**2, 1, 0]) + n = M.convert(m) + assert m.module is F + assert n.module is M + + raises(ValueError, lambda: M.submodule([1, 0, 0])) + raises(TypeError, lambda: M.union(1)) + raises(ValueError, lambda: M.union(R.free_module(1).submodule([x]))) + + assert F.submodule([x, x, x]) != F.submodule([x, x, x], order="ilex") + + +def test_SubModulePolyRing_local(): + R = QQ.old_poly_ring(x, y, order=ilex) + F = R.free_module(3) + Fd = F.submodule([1 + x, 0, 0], [1 + y, 2 + 2*y, 0], [1, 2, 3]) + M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1]) + + assert F == Fd + assert Fd == F + assert F != M + assert M != F + assert Fd != M + assert M != Fd + assert Fd == F.submodule(*F.basis()) + + assert Fd.is_full_module() + assert not M.is_full_module() + assert not Fd.is_zero() + assert not M.is_zero() + assert Fd.submodule().is_zero() + + assert M.contains([x**2 + y**2 + x, 1 + y, 1]) + assert not M.contains([x**2 + y**2 + x, 1 + y, 2]) + assert M.contains([y**2, 1 - x*y, -x]) + + assert F.submodule([1 + x, 0, 0]) == F.submodule([1, 0, 0]) + assert F.submodule( + [1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1 + x*y])) == F + + raises(ValueError, lambda: M.submodule([1, 0, 0])) + + +def test_SubModulePolyRing_nontriv_global(): + R = QQ.old_poly_ring(x, y, z) + F = R.free_module(1) + + def contains(I, f): + return F.submodule(*[[g] for g in I]).contains([f]) + + assert contains([x, y], x) + assert contains([x, y], x + y) + assert not contains([x, y], 1) + assert not contains([x, y], z) + assert contains([x**2 + y, x**2 + x], x - y) + assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2) + assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**3) + assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4) + assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y**2) + assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4 + y**3 + 2*z*y*x) + assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y*z) + assert contains([x, 1 + x + y, 5 - 7*y], 1) + assert contains( + [x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z], + x**3) + assert not contains( + [x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z], + x**2 + y**2) + + # compare local order + assert not contains([x*(1 + x + y), y*(1 + z)], x) + assert not contains([x*(1 + x + y), y*(1 + z)], x + y) + + +def test_SubModulePolyRing_nontriv_local(): + R = QQ.old_poly_ring(x, y, z, order=ilex) + F = R.free_module(1) + + def contains(I, f): + return F.submodule(*[[g] for g in I]).contains([f]) + + assert contains([x, y], x) + assert contains([x, y], x + y) + assert not contains([x, y], 1) + assert not contains([x, y], z) + assert contains([x**2 + y, x**2 + x], x - y) + assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2) + assert contains([x*(1 + x + y), y*(1 + z)], x) + assert contains([x*(1 + x + y), y*(1 + z)], x + y) + + +def test_syzygy(): + R = QQ.old_poly_ring(x, y, z) + M = R.free_module(1).submodule([x*y], [y*z], [x*z]) + S = R.free_module(3).submodule([0, x, -y], [z, -x, 0]) + assert M.syzygy_module() == S + + M2 = M / ([x*y*z],) + S2 = R.free_module(3).submodule([z, 0, 0], [0, x, 0], [0, 0, y]) + assert M2.syzygy_module() == S2 + + F = R.free_module(3) + assert F.submodule(*F.basis()).syzygy_module() == F.submodule() + + R2 = QQ.old_poly_ring(x, y, z) / [x*y*z] + M3 = R2.free_module(1).submodule([x*y], [y*z], [x*z]) + S3 = R2.free_module(3).submodule([z, 0, 0], [0, x, 0], [0, 0, y]) + assert M3.syzygy_module() == S3 + + +def test_in_terms_of_generators(): + R = QQ.old_poly_ring(x, order="ilex") + M = R.free_module(2).submodule([2*x, 0], [1, 2]) + assert M.in_terms_of_generators( + [x, x]) == [R.convert(Rational(1, 4)), R.convert(x/2)] + raises(ValueError, lambda: M.in_terms_of_generators([1, 0])) + + M = R.free_module(2) / ([x, 0], [1, 1]) + SM = M.submodule([1, x]) + assert SM.in_terms_of_generators([2, 0]) == [R.convert(-2/(x - 1))] + + R = QQ.old_poly_ring(x, y) / [x**2 - y**2] + M = R.free_module(2) + SM = M.submodule([x, 0], [0, y]) + assert SM.in_terms_of_generators( + [x**2, x**2]) == [R.convert(x), R.convert(y)] + + +def test_QuotientModuleElement(): + R = QQ.old_poly_ring(x) + F = R.free_module(3) + N = F.submodule([1, x, x**2]) + M = F/N + e = M.convert([x**2, 2, 0]) + + assert M.convert([x + 1, x**2 + x, x**3 + x**2]) == 0 + assert e == [x**2, 2, 0] + N == F.convert([x**2, 2, 0]) + N == \ + M.convert(F.convert([x**2, 2, 0])) + + assert M.convert([x**2 + 1, 2*x + 2, x**2]) == e + [0, x, 0] == \ + e + M.convert([0, x, 0]) == e + F.convert([0, x, 0]) + assert M.convert([x**2 + 1, 2, x**2]) == e - [0, x, 0] == \ + e - M.convert([0, x, 0]) == e - F.convert([0, x, 0]) + assert M.convert([0, 2, 0]) == M.convert([x**2, 4, 0]) - e == \ + [x**2, 4, 0] - e == F.convert([x**2, 4, 0]) - e + assert M.convert([x**3 + x**2, 2*x + 2, 0]) == (1 + x)*e == \ + R.convert(1 + x)*e == e*(1 + x) == e*R.convert(1 + x) + assert -e == [-x**2, -2, 0] + + f = [x, x, 0] + N + assert M.convert([1, 1, 0]) == f / x == f / R.convert(x) + + M2 = F/[(2, 2*x, 2*x**2), (0, 0, 1)] + G = R.free_module(2) + M3 = G/[[1, x]] + M4 = F.submodule([1, x, x**2], [1, 0, 0]) / N + raises(CoercionFailed, lambda: M.convert(G.convert([1, x]))) + raises(CoercionFailed, lambda: M.convert(M3.convert([1, x]))) + raises(CoercionFailed, lambda: M.convert(M2.convert([1, x, x]))) + assert M2.convert(M.convert([2, x, x**2])) == [2, x, 0] + assert M.convert(M4.convert([2, 0, 0])) == [2, 0, 0] + + +def test_QuotientModule(): + R = QQ.old_poly_ring(x) + F = R.free_module(3) + N = F.submodule([1, x, x**2]) + M = F/N + + assert M != F + assert M != N + assert M == F / [(1, x, x**2)] + assert not M.is_zero() + assert (F / F.basis()).is_zero() + + SQ = F.submodule([1, x, x**2], [2, 0, 0]) / N + assert SQ == M.submodule([2, x, x**2]) + assert SQ != M.submodule([2, 1, 0]) + assert SQ != M + assert M.is_submodule(SQ) + assert not SQ.is_full_module() + + raises(ValueError, lambda: N/F) + raises(ValueError, lambda: F.submodule([2, 0, 0]) / N) + raises(ValueError, lambda: R.free_module(2)/F) + raises(CoercionFailed, lambda: F.convert(M.convert([1, x, x**2]))) + + M1 = F / [[1, 1, 1]] + M2 = M1.submodule([1, 0, 0], [0, 1, 0]) + assert M1 == M2 + + +def test_ModulesQuotientRing(): + R = QQ.old_poly_ring(x, y, order=(("lex", x), ("ilex", y))) / [x**2 + 1] + M1 = R.free_module(2) + assert M1 == R.free_module(2) + assert M1 != QQ.old_poly_ring(x).free_module(2) + assert M1 != R.free_module(3) + + assert [x, 1] in M1 + assert [x] not in M1 + assert [1/(R.convert(x) + 1), 2] in M1 + assert [1, 2/(1 + y)] in M1 + assert [1, 2/y] not in M1 + + assert M1.convert([x**2, y]) == [-1, y] + + F = R.free_module(3) + Fd = F.submodule([x**2, 0, 0], [1, 2, 0], [1, 2, 3]) + M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1]) + + assert F == Fd + assert Fd == F + assert F != M + assert M != F + assert Fd != M + assert M != Fd + assert Fd == F.submodule(*F.basis()) + + assert Fd.is_full_module() + assert not M.is_full_module() + assert not Fd.is_zero() + assert not M.is_zero() + assert Fd.submodule().is_zero() + + assert M.contains([x**2 + y**2 + x, -x**2 + y, 1]) + assert not M.contains([x**2 + y**2 + x, 1 + y, 2]) + assert M.contains([y**2, 1 - x*y, -x]) + + assert F.submodule([x, 0, 0]) == F.submodule([1, 0, 0]) + assert not F.submodule([y, 0, 0]) == F.submodule([1, 0, 0]) + assert F.submodule([1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1])) == F + assert not M.is_submodule(0) + + +def test_module_mul(): + R = QQ.old_poly_ring(x) + M = R.free_module(2) + S1 = M.submodule([x, 0], [0, x]) + S2 = M.submodule([x**2, 0], [0, x**2]) + I = R.ideal(x) + + assert I*M == M*I == S1 == x*M == M*x + assert I*S1 == S2 == x*S1 + + +def test_intersection(): + # SCA, example 2.8.5 + F = QQ.old_poly_ring(x, y).free_module(2) + M1 = F.submodule([x, y], [y, 1]) + M2 = F.submodule([0, y - 1], [x, 1], [y, x]) + I = F.submodule([x, y], [y**2 - y, y - 1], [x*y + y, x + 1]) + I1, rel1, rel2 = M1.intersect(M2, relations=True) + assert I1 == M2.intersect(M1) == I + for i, g in enumerate(I1.gens): + assert g == sum(c*x for c, x in zip(rel1[i], M1.gens)) \ + == sum(d*y for d, y in zip(rel2[i], M2.gens)) + + assert F.submodule([x, y]).intersect(F.submodule([y, x])).is_zero() + + +def test_quotient(): + # SCA, example 2.8.6 + R = QQ.old_poly_ring(x, y, z) + F = R.free_module(2) + assert F.submodule([x*y, x*z], [y*z, x*y]).module_quotient( + F.submodule([y, z], [z, y])) == QQ.old_poly_ring(x, y, z).ideal(x**2*y**2 - x*y*z**2) + assert F.submodule([x, y]).module_quotient(F.submodule()).is_whole_ring() + + M = F.submodule([x**2, x**2], [y**2, y**2]) + N = F.submodule([x + y, x + y]) + q, rel = M.module_quotient(N, relations=True) + assert q == R.ideal(y**2, x - y) + for i, g in enumerate(q.gens): + assert g*N.gens[0] == sum(c*x for c, x in zip(rel[i], M.gens)) + + +def test_groebner_extendend(): + M = QQ.old_poly_ring(x, y, z).free_module(3).submodule([x + 1, y, 1], [x*y, z, z**2]) + G, R = M._groebner_vec(extended=True) + for i, g in enumerate(G): + assert g == sum(c*gen for c, gen in zip(R[i], M.gens)) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__init__.py b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a928ddd2226b353798c72e0f53b2728da11f7bca Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/bench_galoispolys.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/bench_galoispolys.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d6cc87143abea8d68ac3983774f6ea87a21704f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/bench_galoispolys.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/bench_groebnertools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/bench_groebnertools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a17d4c85cdea6112dfb363b9d67c137d1d80249f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/__pycache__/bench_groebnertools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_galoispolys.py b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_galoispolys.py new file mode 100644 index 0000000000000000000000000000000000000000..8b2a0329a0cf96be2e8359a3741d8e2de13fa37a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_galoispolys.py @@ -0,0 +1,66 @@ +"""Benchmarks for polynomials over Galois fields. """ + + +from sympy.polys.galoistools import gf_from_dict, gf_factor_sqf +from sympy.polys.domains import ZZ +from sympy.core.numbers import pi +from sympy.ntheory.generate import nextprime + + +def gathen_poly(n, p, K): + return gf_from_dict({n: K.one, 1: K.one, 0: K.one}, p, K) + + +def shoup_poly(n, p, K): + f = [K.one] * (n + 1) + for i in range(1, n + 1): + f[i] = (f[i - 1]**2 + K.one) % p + return f + + +def genprime(n, K): + return K(nextprime(int((2**n * pi).evalf()))) + +p_10 = genprime(10, ZZ) +f_10 = gathen_poly(10, p_10, ZZ) + +p_20 = genprime(20, ZZ) +f_20 = gathen_poly(20, p_20, ZZ) + + +def timeit_gathen_poly_f10_zassenhaus(): + gf_factor_sqf(f_10, p_10, ZZ, method='zassenhaus') + + +def timeit_gathen_poly_f10_shoup(): + gf_factor_sqf(f_10, p_10, ZZ, method='shoup') + + +def timeit_gathen_poly_f20_zassenhaus(): + gf_factor_sqf(f_20, p_20, ZZ, method='zassenhaus') + + +def timeit_gathen_poly_f20_shoup(): + gf_factor_sqf(f_20, p_20, ZZ, method='shoup') + +P_08 = genprime(8, ZZ) +F_10 = shoup_poly(10, P_08, ZZ) + +P_18 = genprime(18, ZZ) +F_20 = shoup_poly(20, P_18, ZZ) + + +def timeit_shoup_poly_F10_zassenhaus(): + gf_factor_sqf(F_10, P_08, ZZ, method='zassenhaus') + + +def timeit_shoup_poly_F10_shoup(): + gf_factor_sqf(F_10, P_08, ZZ, method='shoup') + + +def timeit_shoup_poly_F20_zassenhaus(): + gf_factor_sqf(F_20, P_18, ZZ, method='zassenhaus') + + +def timeit_shoup_poly_F20_shoup(): + gf_factor_sqf(F_20, P_18, ZZ, method='shoup') diff --git a/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_groebnertools.py b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_groebnertools.py new file mode 100644 index 0000000000000000000000000000000000000000..e709f4f6d2cb42c0980d2e49725e01a7a2aa2b87 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_groebnertools.py @@ -0,0 +1,25 @@ +"""Benchmark of the Groebner bases algorithms. """ + + +from sympy.polys.rings import ring +from sympy.polys.domains import QQ +from sympy.polys.groebnertools import groebner + +R, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12 = ring("x1:13", QQ) + +V = R.gens +E = [(x1, x2), (x2, x3), (x1, x4), (x1, x6), (x1, x12), (x2, x5), (x2, x7), (x3, x8), + (x3, x10), (x4, x11), (x4, x9), (x5, x6), (x6, x7), (x7, x8), (x8, x9), (x9, x10), + (x10, x11), (x11, x12), (x5, x12), (x5, x9), (x6, x10), (x7, x11), (x8, x12)] + +F3 = [ x**3 - 1 for x in V ] +Fg = [ x**2 + x*y + y**2 for x, y in E ] + +F_1 = F3 + Fg +F_2 = F3 + Fg + [x3**2 + x3*x4 + x4**2] + +def time_vertex_color_12_vertices_23_edges(): + assert groebner(F_1, R) != [1] + +def time_vertex_color_12_vertices_24_edges(): + assert groebner(F_2, R) == [1] diff --git a/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_solvers.py b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_solvers.py new file mode 100644 index 0000000000000000000000000000000000000000..ed3ce5e246db2f5589e6a5dba9f18b7388c179c4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/benchmarks/bench_solvers.py @@ -0,0 +1,543 @@ +from sympy.polys.rings import ring +from sympy.polys.fields import field +from sympy.polys.domains import ZZ, QQ +from sympy.polys.solvers import solve_lin_sys + +# Expected times on 3.4 GHz i7: + +# In [1]: %timeit time_solve_lin_sys_189x49() +# 1 loops, best of 3: 864 ms per loop +# In [2]: %timeit time_solve_lin_sys_165x165() +# 1 loops, best of 3: 1.83 s per loop +# In [3]: %timeit time_solve_lin_sys_10x8() +# 1 loops, best of 3: 2.31 s per loop + +# Benchmark R_165: shows how fast are arithmetics in QQ. + +R_165, uk_0, uk_1, uk_2, uk_3, uk_4, uk_5, uk_6, uk_7, uk_8, uk_9, uk_10, uk_11, uk_12, uk_13, uk_14, uk_15, uk_16, uk_17, uk_18, uk_19, uk_20, uk_21, uk_22, uk_23, uk_24, uk_25, uk_26, uk_27, uk_28, uk_29, uk_30, uk_31, uk_32, uk_33, uk_34, uk_35, uk_36, uk_37, uk_38, uk_39, uk_40, uk_41, uk_42, uk_43, uk_44, uk_45, uk_46, uk_47, uk_48, uk_49, uk_50, uk_51, uk_52, uk_53, uk_54, uk_55, uk_56, uk_57, uk_58, uk_59, uk_60, uk_61, uk_62, uk_63, uk_64, uk_65, uk_66, uk_67, uk_68, uk_69, uk_70, uk_71, uk_72, uk_73, uk_74, uk_75, uk_76, uk_77, uk_78, uk_79, uk_80, uk_81, uk_82, uk_83, uk_84, uk_85, uk_86, uk_87, uk_88, uk_89, uk_90, uk_91, uk_92, uk_93, uk_94, uk_95, uk_96, uk_97, uk_98, uk_99, uk_100, uk_101, uk_102, uk_103, uk_104, uk_105, uk_106, uk_107, uk_108, uk_109, uk_110, uk_111, uk_112, uk_113, uk_114, uk_115, uk_116, uk_117, uk_118, uk_119, uk_120, uk_121, uk_122, uk_123, uk_124, uk_125, uk_126, uk_127, uk_128, uk_129, uk_130, uk_131, uk_132, uk_133, uk_134, uk_135, uk_136, uk_137, uk_138, uk_139, uk_140, uk_141, uk_142, uk_143, uk_144, uk_145, uk_146, uk_147, uk_148, uk_149, uk_150, uk_151, uk_152, uk_153, uk_154, uk_155, uk_156, uk_157, uk_158, uk_159, uk_160, uk_161, uk_162, uk_163, uk_164 = ring("uk_:165", QQ) + +def eqs_165x165(): + return [ + uk_0 + 50719*uk_1 + 2789545*uk_10 + 411400*uk_100 + 1683000*uk_101 + 166375*uk_103 + 680625*uk_104 + 2784375*uk_106 + 729*uk_109 + 456471*uk_11 + 4131*uk_110 + 11016*uk_111 + 4455*uk_112 + 18225*uk_113 + 23409*uk_115 + 62424*uk_116 + 25245*uk_117 + 103275*uk_118 + 2586669*uk_12 + 166464*uk_120 + 67320*uk_121 + 275400*uk_122 + 27225*uk_124 + 111375*uk_125 + 455625*uk_127 + 6897784*uk_13 + 132651*uk_130 + 353736*uk_131 + 143055*uk_132 + 585225*uk_133 + 943296*uk_135 + 381480*uk_136 + 1560600*uk_137 + 154275*uk_139 + 2789545*uk_14 + 631125*uk_140 + 2581875*uk_142 + 2515456*uk_145 + 1017280*uk_146 + 4161600*uk_147 + 411400*uk_149 + 11411775*uk_15 + 1683000*uk_150 + 6885000*uk_152 + 166375*uk_155 + 680625*uk_156 + 2784375*uk_158 + 11390625*uk_161 + 3025*uk_17 + 495*uk_18 + 2805*uk_19 + 55*uk_2 + 7480*uk_20 + 3025*uk_21 + 12375*uk_22 + 81*uk_24 + 459*uk_25 + 1224*uk_26 + 495*uk_27 + 2025*uk_28 + 9*uk_3 + 2601*uk_30 + 6936*uk_31 + 2805*uk_32 + 11475*uk_33 + 18496*uk_35 + 7480*uk_36 + 30600*uk_37 + 3025*uk_39 + 51*uk_4 + 12375*uk_40 + 50625*uk_42 + 130470415844959*uk_45 + 141482932855*uk_46 + 23151752649*uk_47 + 131193265011*uk_48 + 349848706696*uk_49 + 136*uk_5 + 141482932855*uk_50 + 578793816225*uk_51 + 153424975*uk_53 + 25105905*uk_54 + 142266795*uk_55 + 379378120*uk_56 + 153424975*uk_57 + 627647625*uk_58 + 55*uk_6 + 4108239*uk_60 + 23280021*uk_61 + 62080056*uk_62 + 25105905*uk_63 + 102705975*uk_64 + 131920119*uk_66 + 351786984*uk_67 + 142266795*uk_68 + 582000525*uk_69 + 225*uk_7 + 938098624*uk_71 + 379378120*uk_72 + 1552001400*uk_73 + 153424975*uk_75 + 627647625*uk_76 + 2567649375*uk_78 + 166375*uk_81 + 27225*uk_82 + 154275*uk_83 + 411400*uk_84 + 166375*uk_85 + 680625*uk_86 + 4455*uk_88 + 25245*uk_89 + 2572416961*uk_9 + 67320*uk_90 + 27225*uk_91 + 111375*uk_92 + 143055*uk_94 + 381480*uk_95 + 154275*uk_96 + 631125*uk_97 + 1017280*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 413820*uk_100 + 1633500*uk_101 + 65340*uk_102 + 178695*uk_103 + 705375*uk_104 + 28215*uk_105 + 2784375*uk_106 + 111375*uk_107 + 4455*uk_108 + 97336*uk_109 + 2333074*uk_11 + 19044*uk_110 + 279312*uk_111 + 120612*uk_112 + 476100*uk_113 + 19044*uk_114 + 3726*uk_115 + 54648*uk_116 + 23598*uk_117 + 93150*uk_118 + 3726*uk_119 + 456471*uk_12 + 801504*uk_120 + 346104*uk_121 + 1366200*uk_122 + 54648*uk_123 + 149454*uk_124 + 589950*uk_125 + 23598*uk_126 + 2328750*uk_127 + 93150*uk_128 + 3726*uk_129 + 6694908*uk_13 + 729*uk_130 + 10692*uk_131 + 4617*uk_132 + 18225*uk_133 + 729*uk_134 + 156816*uk_135 + 67716*uk_136 + 267300*uk_137 + 10692*uk_138 + 29241*uk_139 + 2890983*uk_14 + 115425*uk_140 + 4617*uk_141 + 455625*uk_142 + 18225*uk_143 + 729*uk_144 + 2299968*uk_145 + 993168*uk_146 + 3920400*uk_147 + 156816*uk_148 + 428868*uk_149 + 11411775*uk_15 + 1692900*uk_150 + 67716*uk_151 + 6682500*uk_152 + 267300*uk_153 + 10692*uk_154 + 185193*uk_155 + 731025*uk_156 + 29241*uk_157 + 2885625*uk_158 + 115425*uk_159 + 456471*uk_16 + 4617*uk_160 + 11390625*uk_161 + 455625*uk_162 + 18225*uk_163 + 729*uk_164 + 3025*uk_17 + 2530*uk_18 + 495*uk_19 + 55*uk_2 + 7260*uk_20 + 3135*uk_21 + 12375*uk_22 + 495*uk_23 + 2116*uk_24 + 414*uk_25 + 6072*uk_26 + 2622*uk_27 + 10350*uk_28 + 414*uk_29 + 46*uk_3 + 81*uk_30 + 1188*uk_31 + 513*uk_32 + 2025*uk_33 + 81*uk_34 + 17424*uk_35 + 7524*uk_36 + 29700*uk_37 + 1188*uk_38 + 3249*uk_39 + 9*uk_4 + 12825*uk_40 + 513*uk_41 + 50625*uk_42 + 2025*uk_43 + 81*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 118331180206*uk_47 + 23151752649*uk_48 + 339559038852*uk_49 + 132*uk_5 + 146627766777*uk_50 + 578793816225*uk_51 + 23151752649*uk_52 + 153424975*uk_53 + 128319070*uk_54 + 25105905*uk_55 + 368219940*uk_56 + 159004065*uk_57 + 627647625*uk_58 + 25105905*uk_59 + 57*uk_6 + 107321404*uk_60 + 20997666*uk_61 + 307965768*uk_62 + 132985218*uk_63 + 524941650*uk_64 + 20997666*uk_65 + 4108239*uk_66 + 60254172*uk_67 + 26018847*uk_68 + 102705975*uk_69 + 225*uk_7 + 4108239*uk_70 + 883727856*uk_71 + 381609756*uk_72 + 1506354300*uk_73 + 60254172*uk_74 + 164786031*uk_75 + 650471175*uk_76 + 26018847*uk_77 + 2567649375*uk_78 + 102705975*uk_79 + 9*uk_8 + 4108239*uk_80 + 166375*uk_81 + 139150*uk_82 + 27225*uk_83 + 399300*uk_84 + 172425*uk_85 + 680625*uk_86 + 27225*uk_87 + 116380*uk_88 + 22770*uk_89 + 2572416961*uk_9 + 333960*uk_90 + 144210*uk_91 + 569250*uk_92 + 22770*uk_93 + 4455*uk_94 + 65340*uk_95 + 28215*uk_96 + 111375*uk_97 + 4455*uk_98 + 958320*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 402380*uk_100 + 1534500*uk_101 + 313720*uk_102 + 191455*uk_103 + 730125*uk_104 + 149270*uk_105 + 2784375*uk_106 + 569250*uk_107 + 116380*uk_108 + 912673*uk_109 + 4919743*uk_11 + 432814*uk_110 + 1166716*uk_111 + 555131*uk_112 + 2117025*uk_113 + 432814*uk_114 + 205252*uk_115 + 553288*uk_116 + 263258*uk_117 + 1003950*uk_118 + 205252*uk_119 + 2333074*uk_12 + 1491472*uk_120 + 709652*uk_121 + 2706300*uk_122 + 553288*uk_123 + 337657*uk_124 + 1287675*uk_125 + 263258*uk_126 + 4910625*uk_127 + 1003950*uk_128 + 205252*uk_129 + 6289156*uk_13 + 97336*uk_130 + 262384*uk_131 + 124844*uk_132 + 476100*uk_133 + 97336*uk_134 + 707296*uk_135 + 336536*uk_136 + 1283400*uk_137 + 262384*uk_138 + 160126*uk_139 + 2992421*uk_14 + 610650*uk_140 + 124844*uk_141 + 2328750*uk_142 + 476100*uk_143 + 97336*uk_144 + 1906624*uk_145 + 907184*uk_146 + 3459600*uk_147 + 707296*uk_148 + 431644*uk_149 + 11411775*uk_15 + 1646100*uk_150 + 336536*uk_151 + 6277500*uk_152 + 1283400*uk_153 + 262384*uk_154 + 205379*uk_155 + 783225*uk_156 + 160126*uk_157 + 2986875*uk_158 + 610650*uk_159 + 2333074*uk_16 + 124844*uk_160 + 11390625*uk_161 + 2328750*uk_162 + 476100*uk_163 + 97336*uk_164 + 3025*uk_17 + 5335*uk_18 + 2530*uk_19 + 55*uk_2 + 6820*uk_20 + 3245*uk_21 + 12375*uk_22 + 2530*uk_23 + 9409*uk_24 + 4462*uk_25 + 12028*uk_26 + 5723*uk_27 + 21825*uk_28 + 4462*uk_29 + 97*uk_3 + 2116*uk_30 + 5704*uk_31 + 2714*uk_32 + 10350*uk_33 + 2116*uk_34 + 15376*uk_35 + 7316*uk_36 + 27900*uk_37 + 5704*uk_38 + 3481*uk_39 + 46*uk_4 + 13275*uk_40 + 2714*uk_41 + 50625*uk_42 + 10350*uk_43 + 2116*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 249524445217*uk_47 + 118331180206*uk_48 + 318979703164*uk_49 + 124*uk_5 + 151772600699*uk_50 + 578793816225*uk_51 + 118331180206*uk_52 + 153424975*uk_53 + 270585865*uk_54 + 128319070*uk_55 + 345903580*uk_56 + 164583155*uk_57 + 627647625*uk_58 + 128319070*uk_59 + 59*uk_6 + 477215071*uk_60 + 226308178*uk_61 + 610048132*uk_62 + 290264837*uk_63 + 1106942175*uk_64 + 226308178*uk_65 + 107321404*uk_66 + 289301176*uk_67 + 137651366*uk_68 + 524941650*uk_69 + 225*uk_7 + 107321404*uk_70 + 779855344*uk_71 + 371060204*uk_72 + 1415060100*uk_73 + 289301176*uk_74 + 176552839*uk_75 + 673294725*uk_76 + 137651366*uk_77 + 2567649375*uk_78 + 524941650*uk_79 + 46*uk_8 + 107321404*uk_80 + 166375*uk_81 + 293425*uk_82 + 139150*uk_83 + 375100*uk_84 + 178475*uk_85 + 680625*uk_86 + 139150*uk_87 + 517495*uk_88 + 245410*uk_89 + 2572416961*uk_9 + 661540*uk_90 + 314765*uk_91 + 1200375*uk_92 + 245410*uk_93 + 116380*uk_94 + 313720*uk_95 + 149270*uk_96 + 569250*uk_97 + 116380*uk_98 + 845680*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 389180*uk_100 + 1435500*uk_101 + 618860*uk_102 + 204655*uk_103 + 754875*uk_104 + 325435*uk_105 + 2784375*uk_106 + 1200375*uk_107 + 517495*uk_108 + 3375000*uk_109 + 7607850*uk_11 + 2182500*uk_110 + 2610000*uk_111 + 1372500*uk_112 + 5062500*uk_113 + 2182500*uk_114 + 1411350*uk_115 + 1687800*uk_116 + 887550*uk_117 + 3273750*uk_118 + 1411350*uk_119 + 4919743*uk_12 + 2018400*uk_120 + 1061400*uk_121 + 3915000*uk_122 + 1687800*uk_123 + 558150*uk_124 + 2058750*uk_125 + 887550*uk_126 + 7593750*uk_127 + 3273750*uk_128 + 1411350*uk_129 + 5883404*uk_13 + 912673*uk_130 + 1091444*uk_131 + 573949*uk_132 + 2117025*uk_133 + 912673*uk_134 + 1305232*uk_135 + 686372*uk_136 + 2531700*uk_137 + 1091444*uk_138 + 360937*uk_139 + 3093859*uk_14 + 1331325*uk_140 + 573949*uk_141 + 4910625*uk_142 + 2117025*uk_143 + 912673*uk_144 + 1560896*uk_145 + 820816*uk_146 + 3027600*uk_147 + 1305232*uk_148 + 431636*uk_149 + 11411775*uk_15 + 1592100*uk_150 + 686372*uk_151 + 5872500*uk_152 + 2531700*uk_153 + 1091444*uk_154 + 226981*uk_155 + 837225*uk_156 + 360937*uk_157 + 3088125*uk_158 + 1331325*uk_159 + 4919743*uk_16 + 573949*uk_160 + 11390625*uk_161 + 4910625*uk_162 + 2117025*uk_163 + 912673*uk_164 + 3025*uk_17 + 8250*uk_18 + 5335*uk_19 + 55*uk_2 + 6380*uk_20 + 3355*uk_21 + 12375*uk_22 + 5335*uk_23 + 22500*uk_24 + 14550*uk_25 + 17400*uk_26 + 9150*uk_27 + 33750*uk_28 + 14550*uk_29 + 150*uk_3 + 9409*uk_30 + 11252*uk_31 + 5917*uk_32 + 21825*uk_33 + 9409*uk_34 + 13456*uk_35 + 7076*uk_36 + 26100*uk_37 + 11252*uk_38 + 3721*uk_39 + 97*uk_4 + 13725*uk_40 + 5917*uk_41 + 50625*uk_42 + 21825*uk_43 + 9409*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 385862544150*uk_47 + 249524445217*uk_48 + 298400367476*uk_49 + 116*uk_5 + 156917434621*uk_50 + 578793816225*uk_51 + 249524445217*uk_52 + 153424975*uk_53 + 418431750*uk_54 + 270585865*uk_55 + 323587220*uk_56 + 170162245*uk_57 + 627647625*uk_58 + 270585865*uk_59 + 61*uk_6 + 1141177500*uk_60 + 737961450*uk_61 + 882510600*uk_62 + 464078850*uk_63 + 1711766250*uk_64 + 737961450*uk_65 + 477215071*uk_66 + 570690188*uk_67 + 300104323*uk_68 + 1106942175*uk_69 + 225*uk_7 + 477215071*uk_70 + 682474864*uk_71 + 358887644*uk_72 + 1323765900*uk_73 + 570690188*uk_74 + 188725399*uk_75 + 696118275*uk_76 + 300104323*uk_77 + 2567649375*uk_78 + 1106942175*uk_79 + 97*uk_8 + 477215071*uk_80 + 166375*uk_81 + 453750*uk_82 + 293425*uk_83 + 350900*uk_84 + 184525*uk_85 + 680625*uk_86 + 293425*uk_87 + 1237500*uk_88 + 800250*uk_89 + 2572416961*uk_9 + 957000*uk_90 + 503250*uk_91 + 1856250*uk_92 + 800250*uk_93 + 517495*uk_94 + 618860*uk_95 + 325435*uk_96 + 1200375*uk_97 + 517495*uk_98 + 740080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 374220*uk_100 + 1336500*uk_101 + 891000*uk_102 + 218295*uk_103 + 779625*uk_104 + 519750*uk_105 + 2784375*uk_106 + 1856250*uk_107 + 1237500*uk_108 + 7189057*uk_109 + 9788767*uk_11 + 5587350*uk_110 + 4022892*uk_111 + 2346687*uk_112 + 8381025*uk_113 + 5587350*uk_114 + 4342500*uk_115 + 3126600*uk_116 + 1823850*uk_117 + 6513750*uk_118 + 4342500*uk_119 + 7607850*uk_12 + 2251152*uk_120 + 1313172*uk_121 + 4689900*uk_122 + 3126600*uk_123 + 766017*uk_124 + 2735775*uk_125 + 1823850*uk_126 + 9770625*uk_127 + 6513750*uk_128 + 4342500*uk_129 + 5477652*uk_13 + 3375000*uk_130 + 2430000*uk_131 + 1417500*uk_132 + 5062500*uk_133 + 3375000*uk_134 + 1749600*uk_135 + 1020600*uk_136 + 3645000*uk_137 + 2430000*uk_138 + 595350*uk_139 + 3195297*uk_14 + 2126250*uk_140 + 1417500*uk_141 + 7593750*uk_142 + 5062500*uk_143 + 3375000*uk_144 + 1259712*uk_145 + 734832*uk_146 + 2624400*uk_147 + 1749600*uk_148 + 428652*uk_149 + 11411775*uk_15 + 1530900*uk_150 + 1020600*uk_151 + 5467500*uk_152 + 3645000*uk_153 + 2430000*uk_154 + 250047*uk_155 + 893025*uk_156 + 595350*uk_157 + 3189375*uk_158 + 2126250*uk_159 + 7607850*uk_16 + 1417500*uk_160 + 11390625*uk_161 + 7593750*uk_162 + 5062500*uk_163 + 3375000*uk_164 + 3025*uk_17 + 10615*uk_18 + 8250*uk_19 + 55*uk_2 + 5940*uk_20 + 3465*uk_21 + 12375*uk_22 + 8250*uk_23 + 37249*uk_24 + 28950*uk_25 + 20844*uk_26 + 12159*uk_27 + 43425*uk_28 + 28950*uk_29 + 193*uk_3 + 22500*uk_30 + 16200*uk_31 + 9450*uk_32 + 33750*uk_33 + 22500*uk_34 + 11664*uk_35 + 6804*uk_36 + 24300*uk_37 + 16200*uk_38 + 3969*uk_39 + 150*uk_4 + 14175*uk_40 + 9450*uk_41 + 50625*uk_42 + 33750*uk_43 + 22500*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 496476473473*uk_47 + 385862544150*uk_48 + 277821031788*uk_49 + 108*uk_5 + 162062268543*uk_50 + 578793816225*uk_51 + 385862544150*uk_52 + 153424975*uk_53 + 538382185*uk_54 + 418431750*uk_55 + 301270860*uk_56 + 175741335*uk_57 + 627647625*uk_58 + 418431750*uk_59 + 63*uk_6 + 1889232031*uk_60 + 1468315050*uk_61 + 1057186836*uk_62 + 616692321*uk_63 + 2202472575*uk_64 + 1468315050*uk_65 + 1141177500*uk_66 + 821647800*uk_67 + 479294550*uk_68 + 1711766250*uk_69 + 225*uk_7 + 1141177500*uk_70 + 591586416*uk_71 + 345092076*uk_72 + 1232471700*uk_73 + 821647800*uk_74 + 201303711*uk_75 + 718941825*uk_76 + 479294550*uk_77 + 2567649375*uk_78 + 1711766250*uk_79 + 150*uk_8 + 1141177500*uk_80 + 166375*uk_81 + 583825*uk_82 + 453750*uk_83 + 326700*uk_84 + 190575*uk_85 + 680625*uk_86 + 453750*uk_87 + 2048695*uk_88 + 1592250*uk_89 + 2572416961*uk_9 + 1146420*uk_90 + 668745*uk_91 + 2388375*uk_92 + 1592250*uk_93 + 1237500*uk_94 + 891000*uk_95 + 519750*uk_96 + 1856250*uk_97 + 1237500*uk_98 + 641520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 357500*uk_100 + 1237500*uk_101 + 1061500*uk_102 + 232375*uk_103 + 804375*uk_104 + 689975*uk_105 + 2784375*uk_106 + 2388375*uk_107 + 2048695*uk_108 + 9800344*uk_109 + 10853866*uk_11 + 8838628*uk_110 + 4579600*uk_111 + 2976740*uk_112 + 10304100*uk_113 + 8838628*uk_114 + 7971286*uk_115 + 4130200*uk_116 + 2684630*uk_117 + 9292950*uk_118 + 7971286*uk_119 + 9788767*uk_12 + 2140000*uk_120 + 1391000*uk_121 + 4815000*uk_122 + 4130200*uk_123 + 904150*uk_124 + 3129750*uk_125 + 2684630*uk_126 + 10833750*uk_127 + 9292950*uk_128 + 7971286*uk_129 + 5071900*uk_13 + 7189057*uk_130 + 3724900*uk_131 + 2421185*uk_132 + 8381025*uk_133 + 7189057*uk_134 + 1930000*uk_135 + 1254500*uk_136 + 4342500*uk_137 + 3724900*uk_138 + 815425*uk_139 + 3296735*uk_14 + 2822625*uk_140 + 2421185*uk_141 + 9770625*uk_142 + 8381025*uk_143 + 7189057*uk_144 + 1000000*uk_145 + 650000*uk_146 + 2250000*uk_147 + 1930000*uk_148 + 422500*uk_149 + 11411775*uk_15 + 1462500*uk_150 + 1254500*uk_151 + 5062500*uk_152 + 4342500*uk_153 + 3724900*uk_154 + 274625*uk_155 + 950625*uk_156 + 815425*uk_157 + 3290625*uk_158 + 2822625*uk_159 + 9788767*uk_16 + 2421185*uk_160 + 11390625*uk_161 + 9770625*uk_162 + 8381025*uk_163 + 7189057*uk_164 + 3025*uk_17 + 11770*uk_18 + 10615*uk_19 + 55*uk_2 + 5500*uk_20 + 3575*uk_21 + 12375*uk_22 + 10615*uk_23 + 45796*uk_24 + 41302*uk_25 + 21400*uk_26 + 13910*uk_27 + 48150*uk_28 + 41302*uk_29 + 214*uk_3 + 37249*uk_30 + 19300*uk_31 + 12545*uk_32 + 43425*uk_33 + 37249*uk_34 + 10000*uk_35 + 6500*uk_36 + 22500*uk_37 + 19300*uk_38 + 4225*uk_39 + 193*uk_4 + 14625*uk_40 + 12545*uk_41 + 50625*uk_42 + 43425*uk_43 + 37249*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 550497229654*uk_47 + 496476473473*uk_48 + 257241696100*uk_49 + 100*uk_5 + 167207102465*uk_50 + 578793816225*uk_51 + 496476473473*uk_52 + 153424975*uk_53 + 596962630*uk_54 + 538382185*uk_55 + 278954500*uk_56 + 181320425*uk_57 + 627647625*uk_58 + 538382185*uk_59 + 65*uk_6 + 2322727324*uk_60 + 2094796138*uk_61 + 1085386600*uk_62 + 705501290*uk_63 + 2442119850*uk_64 + 2094796138*uk_65 + 1889232031*uk_66 + 978876700*uk_67 + 636269855*uk_68 + 2202472575*uk_69 + 225*uk_7 + 1889232031*uk_70 + 507190000*uk_71 + 329673500*uk_72 + 1141177500*uk_73 + 978876700*uk_74 + 214287775*uk_75 + 741765375*uk_76 + 636269855*uk_77 + 2567649375*uk_78 + 2202472575*uk_79 + 193*uk_8 + 1889232031*uk_80 + 166375*uk_81 + 647350*uk_82 + 583825*uk_83 + 302500*uk_84 + 196625*uk_85 + 680625*uk_86 + 583825*uk_87 + 2518780*uk_88 + 2271610*uk_89 + 2572416961*uk_9 + 1177000*uk_90 + 765050*uk_91 + 2648250*uk_92 + 2271610*uk_93 + 2048695*uk_94 + 1061500*uk_95 + 689975*uk_96 + 2388375*uk_97 + 2048695*uk_98 + 550000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 339020*uk_100 + 1138500*uk_101 + 1082840*uk_102 + 246895*uk_103 + 829125*uk_104 + 788590*uk_105 + 2784375*uk_106 + 2648250*uk_107 + 2518780*uk_108 + 8120601*uk_109 + 10194519*uk_11 + 8645814*uk_110 + 3716892*uk_111 + 2706867*uk_112 + 9090225*uk_113 + 8645814*uk_114 + 9204996*uk_115 + 3957288*uk_116 + 2881938*uk_117 + 9678150*uk_118 + 9204996*uk_119 + 10853866*uk_12 + 1701264*uk_120 + 1238964*uk_121 + 4160700*uk_122 + 3957288*uk_123 + 902289*uk_124 + 3030075*uk_125 + 2881938*uk_126 + 10175625*uk_127 + 9678150*uk_128 + 9204996*uk_129 + 4666148*uk_13 + 9800344*uk_130 + 4213232*uk_131 + 3068332*uk_132 + 10304100*uk_133 + 9800344*uk_134 + 1811296*uk_135 + 1319096*uk_136 + 4429800*uk_137 + 4213232*uk_138 + 960646*uk_139 + 3398173*uk_14 + 3226050*uk_140 + 3068332*uk_141 + 10833750*uk_142 + 10304100*uk_143 + 9800344*uk_144 + 778688*uk_145 + 567088*uk_146 + 1904400*uk_147 + 1811296*uk_148 + 412988*uk_149 + 11411775*uk_15 + 1386900*uk_150 + 1319096*uk_151 + 4657500*uk_152 + 4429800*uk_153 + 4213232*uk_154 + 300763*uk_155 + 1010025*uk_156 + 960646*uk_157 + 3391875*uk_158 + 3226050*uk_159 + 10853866*uk_16 + 3068332*uk_160 + 11390625*uk_161 + 10833750*uk_162 + 10304100*uk_163 + 9800344*uk_164 + 3025*uk_17 + 11055*uk_18 + 11770*uk_19 + 55*uk_2 + 5060*uk_20 + 3685*uk_21 + 12375*uk_22 + 11770*uk_23 + 40401*uk_24 + 43014*uk_25 + 18492*uk_26 + 13467*uk_27 + 45225*uk_28 + 43014*uk_29 + 201*uk_3 + 45796*uk_30 + 19688*uk_31 + 14338*uk_32 + 48150*uk_33 + 45796*uk_34 + 8464*uk_35 + 6164*uk_36 + 20700*uk_37 + 19688*uk_38 + 4489*uk_39 + 214*uk_4 + 15075*uk_40 + 14338*uk_41 + 50625*uk_42 + 48150*uk_43 + 45796*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 517055809161*uk_47 + 550497229654*uk_48 + 236662360412*uk_49 + 92*uk_5 + 172351936387*uk_50 + 578793816225*uk_51 + 550497229654*uk_52 + 153424975*uk_53 + 560698545*uk_54 + 596962630*uk_55 + 256638140*uk_56 + 186899515*uk_57 + 627647625*uk_58 + 596962630*uk_59 + 67*uk_6 + 2049098319*uk_60 + 2181627066*uk_61 + 937895748*uk_62 + 683032773*uk_63 + 2293766775*uk_64 + 2181627066*uk_65 + 2322727324*uk_66 + 998555672*uk_67 + 727209022*uk_68 + 2442119850*uk_69 + 225*uk_7 + 2322727324*uk_70 + 429285616*uk_71 + 312631916*uk_72 + 1049883300*uk_73 + 998555672*uk_74 + 227677591*uk_75 + 764588925*uk_76 + 727209022*uk_77 + 2567649375*uk_78 + 2442119850*uk_79 + 214*uk_8 + 2322727324*uk_80 + 166375*uk_81 + 608025*uk_82 + 647350*uk_83 + 278300*uk_84 + 202675*uk_85 + 680625*uk_86 + 647350*uk_87 + 2222055*uk_88 + 2365770*uk_89 + 2572416961*uk_9 + 1017060*uk_90 + 740685*uk_91 + 2487375*uk_92 + 2365770*uk_93 + 2518780*uk_94 + 1082840*uk_95 + 788590*uk_96 + 2648250*uk_97 + 2518780*uk_98 + 465520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 318780*uk_100 + 1039500*uk_101 + 928620*uk_102 + 261855*uk_103 + 853875*uk_104 + 762795*uk_105 + 2784375*uk_106 + 2487375*uk_107 + 2222055*uk_108 + 2863288*uk_109 + 7202098*uk_11 + 4052964*uk_110 + 1693776*uk_111 + 1391316*uk_112 + 4536900*uk_113 + 4052964*uk_114 + 5736942*uk_115 + 2397528*uk_116 + 1969398*uk_117 + 6421950*uk_118 + 5736942*uk_119 + 10194519*uk_12 + 1001952*uk_120 + 823032*uk_121 + 2683800*uk_122 + 2397528*uk_123 + 676062*uk_124 + 2204550*uk_125 + 1969398*uk_126 + 7188750*uk_127 + 6421950*uk_128 + 5736942*uk_129 + 4260396*uk_13 + 8120601*uk_130 + 3393684*uk_131 + 2787669*uk_132 + 9090225*uk_133 + 8120601*uk_134 + 1418256*uk_135 + 1164996*uk_136 + 3798900*uk_137 + 3393684*uk_138 + 956961*uk_139 + 3499611*uk_14 + 3120525*uk_140 + 2787669*uk_141 + 10175625*uk_142 + 9090225*uk_143 + 8120601*uk_144 + 592704*uk_145 + 486864*uk_146 + 1587600*uk_147 + 1418256*uk_148 + 399924*uk_149 + 11411775*uk_15 + 1304100*uk_150 + 1164996*uk_151 + 4252500*uk_152 + 3798900*uk_153 + 3393684*uk_154 + 328509*uk_155 + 1071225*uk_156 + 956961*uk_157 + 3493125*uk_158 + 3120525*uk_159 + 10194519*uk_16 + 2787669*uk_160 + 11390625*uk_161 + 10175625*uk_162 + 9090225*uk_163 + 8120601*uk_164 + 3025*uk_17 + 7810*uk_18 + 11055*uk_19 + 55*uk_2 + 4620*uk_20 + 3795*uk_21 + 12375*uk_22 + 11055*uk_23 + 20164*uk_24 + 28542*uk_25 + 11928*uk_26 + 9798*uk_27 + 31950*uk_28 + 28542*uk_29 + 142*uk_3 + 40401*uk_30 + 16884*uk_31 + 13869*uk_32 + 45225*uk_33 + 40401*uk_34 + 7056*uk_35 + 5796*uk_36 + 18900*uk_37 + 16884*uk_38 + 4761*uk_39 + 201*uk_4 + 15525*uk_40 + 13869*uk_41 + 50625*uk_42 + 45225*uk_43 + 40401*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 365283208462*uk_47 + 517055809161*uk_48 + 216083024724*uk_49 + 84*uk_5 + 177496770309*uk_50 + 578793816225*uk_51 + 517055809161*uk_52 + 153424975*uk_53 + 396115390*uk_54 + 560698545*uk_55 + 234321780*uk_56 + 192478605*uk_57 + 627647625*uk_58 + 560698545*uk_59 + 69*uk_6 + 1022697916*uk_60 + 1447621698*uk_61 + 604976232*uk_62 + 496944762*uk_63 + 1620472050*uk_64 + 1447621698*uk_65 + 2049098319*uk_66 + 856339596*uk_67 + 703421811*uk_68 + 2293766775*uk_69 + 225*uk_7 + 2049098319*uk_70 + 357873264*uk_71 + 293967324*uk_72 + 958589100*uk_73 + 856339596*uk_74 + 241473159*uk_75 + 787412475*uk_76 + 703421811*uk_77 + 2567649375*uk_78 + 2293766775*uk_79 + 201*uk_8 + 2049098319*uk_80 + 166375*uk_81 + 429550*uk_82 + 608025*uk_83 + 254100*uk_84 + 208725*uk_85 + 680625*uk_86 + 608025*uk_87 + 1109020*uk_88 + 1569810*uk_89 + 2572416961*uk_9 + 656040*uk_90 + 538890*uk_91 + 1757250*uk_92 + 1569810*uk_93 + 2222055*uk_94 + 928620*uk_95 + 762795*uk_96 + 2487375*uk_97 + 2222055*uk_98 + 388080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 296780*uk_100 + 940500*uk_101 + 593560*uk_102 + 277255*uk_103 + 878625*uk_104 + 554510*uk_105 + 2784375*uk_106 + 1757250*uk_107 + 1109020*uk_108 + 15625*uk_109 + 1267975*uk_11 + 88750*uk_110 + 47500*uk_111 + 44375*uk_112 + 140625*uk_113 + 88750*uk_114 + 504100*uk_115 + 269800*uk_116 + 252050*uk_117 + 798750*uk_118 + 504100*uk_119 + 7202098*uk_12 + 144400*uk_120 + 134900*uk_121 + 427500*uk_122 + 269800*uk_123 + 126025*uk_124 + 399375*uk_125 + 252050*uk_126 + 1265625*uk_127 + 798750*uk_128 + 504100*uk_129 + 3854644*uk_13 + 2863288*uk_130 + 1532464*uk_131 + 1431644*uk_132 + 4536900*uk_133 + 2863288*uk_134 + 820192*uk_135 + 766232*uk_136 + 2428200*uk_137 + 1532464*uk_138 + 715822*uk_139 + 3601049*uk_14 + 2268450*uk_140 + 1431644*uk_141 + 7188750*uk_142 + 4536900*uk_143 + 2863288*uk_144 + 438976*uk_145 + 410096*uk_146 + 1299600*uk_147 + 820192*uk_148 + 383116*uk_149 + 11411775*uk_15 + 1214100*uk_150 + 766232*uk_151 + 3847500*uk_152 + 2428200*uk_153 + 1532464*uk_154 + 357911*uk_155 + 1134225*uk_156 + 715822*uk_157 + 3594375*uk_158 + 2268450*uk_159 + 7202098*uk_16 + 1431644*uk_160 + 11390625*uk_161 + 7188750*uk_162 + 4536900*uk_163 + 2863288*uk_164 + 3025*uk_17 + 1375*uk_18 + 7810*uk_19 + 55*uk_2 + 4180*uk_20 + 3905*uk_21 + 12375*uk_22 + 7810*uk_23 + 625*uk_24 + 3550*uk_25 + 1900*uk_26 + 1775*uk_27 + 5625*uk_28 + 3550*uk_29 + 25*uk_3 + 20164*uk_30 + 10792*uk_31 + 10082*uk_32 + 31950*uk_33 + 20164*uk_34 + 5776*uk_35 + 5396*uk_36 + 17100*uk_37 + 10792*uk_38 + 5041*uk_39 + 142*uk_4 + 15975*uk_40 + 10082*uk_41 + 50625*uk_42 + 31950*uk_43 + 20164*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 64310424025*uk_47 + 365283208462*uk_48 + 195503689036*uk_49 + 76*uk_5 + 182641604231*uk_50 + 578793816225*uk_51 + 365283208462*uk_52 + 153424975*uk_53 + 69738625*uk_54 + 396115390*uk_55 + 212005420*uk_56 + 198057695*uk_57 + 627647625*uk_58 + 396115390*uk_59 + 71*uk_6 + 31699375*uk_60 + 180052450*uk_61 + 96366100*uk_62 + 90026225*uk_63 + 285294375*uk_64 + 180052450*uk_65 + 1022697916*uk_66 + 547359448*uk_67 + 511348958*uk_68 + 1620472050*uk_69 + 225*uk_7 + 1022697916*uk_70 + 292952944*uk_71 + 273679724*uk_72 + 867294900*uk_73 + 547359448*uk_74 + 255674479*uk_75 + 810236025*uk_76 + 511348958*uk_77 + 2567649375*uk_78 + 1620472050*uk_79 + 142*uk_8 + 1022697916*uk_80 + 166375*uk_81 + 75625*uk_82 + 429550*uk_83 + 229900*uk_84 + 214775*uk_85 + 680625*uk_86 + 429550*uk_87 + 34375*uk_88 + 195250*uk_89 + 2572416961*uk_9 + 104500*uk_90 + 97625*uk_91 + 309375*uk_92 + 195250*uk_93 + 1109020*uk_94 + 593560*uk_95 + 554510*uk_96 + 1757250*uk_97 + 1109020*uk_98 + 317680*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 321200*uk_100 + 990000*uk_101 + 110000*uk_102 + 293095*uk_103 + 903375*uk_104 + 100375*uk_105 + 2784375*uk_106 + 309375*uk_107 + 34375*uk_108 + 185193*uk_109 + 2890983*uk_11 + 81225*uk_110 + 259920*uk_111 + 237177*uk_112 + 731025*uk_113 + 81225*uk_114 + 35625*uk_115 + 114000*uk_116 + 104025*uk_117 + 320625*uk_118 + 35625*uk_119 + 1267975*uk_12 + 364800*uk_120 + 332880*uk_121 + 1026000*uk_122 + 114000*uk_123 + 303753*uk_124 + 936225*uk_125 + 104025*uk_126 + 2885625*uk_127 + 320625*uk_128 + 35625*uk_129 + 4057520*uk_13 + 15625*uk_130 + 50000*uk_131 + 45625*uk_132 + 140625*uk_133 + 15625*uk_134 + 160000*uk_135 + 146000*uk_136 + 450000*uk_137 + 50000*uk_138 + 133225*uk_139 + 3702487*uk_14 + 410625*uk_140 + 45625*uk_141 + 1265625*uk_142 + 140625*uk_143 + 15625*uk_144 + 512000*uk_145 + 467200*uk_146 + 1440000*uk_147 + 160000*uk_148 + 426320*uk_149 + 11411775*uk_15 + 1314000*uk_150 + 146000*uk_151 + 4050000*uk_152 + 450000*uk_153 + 50000*uk_154 + 389017*uk_155 + 1199025*uk_156 + 133225*uk_157 + 3695625*uk_158 + 410625*uk_159 + 1267975*uk_16 + 45625*uk_160 + 11390625*uk_161 + 1265625*uk_162 + 140625*uk_163 + 15625*uk_164 + 3025*uk_17 + 3135*uk_18 + 1375*uk_19 + 55*uk_2 + 4400*uk_20 + 4015*uk_21 + 12375*uk_22 + 1375*uk_23 + 3249*uk_24 + 1425*uk_25 + 4560*uk_26 + 4161*uk_27 + 12825*uk_28 + 1425*uk_29 + 57*uk_3 + 625*uk_30 + 2000*uk_31 + 1825*uk_32 + 5625*uk_33 + 625*uk_34 + 6400*uk_35 + 5840*uk_36 + 18000*uk_37 + 2000*uk_38 + 5329*uk_39 + 25*uk_4 + 16425*uk_40 + 1825*uk_41 + 50625*uk_42 + 5625*uk_43 + 625*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 146627766777*uk_47 + 64310424025*uk_48 + 205793356880*uk_49 + 80*uk_5 + 187786438153*uk_50 + 578793816225*uk_51 + 64310424025*uk_52 + 153424975*uk_53 + 159004065*uk_54 + 69738625*uk_55 + 223163600*uk_56 + 203636785*uk_57 + 627647625*uk_58 + 69738625*uk_59 + 73*uk_6 + 164786031*uk_60 + 72274575*uk_61 + 231278640*uk_62 + 211041759*uk_63 + 650471175*uk_64 + 72274575*uk_65 + 31699375*uk_66 + 101438000*uk_67 + 92562175*uk_68 + 285294375*uk_69 + 225*uk_7 + 31699375*uk_70 + 324601600*uk_71 + 296198960*uk_72 + 912942000*uk_73 + 101438000*uk_74 + 270281551*uk_75 + 833059575*uk_76 + 92562175*uk_77 + 2567649375*uk_78 + 285294375*uk_79 + 25*uk_8 + 31699375*uk_80 + 166375*uk_81 + 172425*uk_82 + 75625*uk_83 + 242000*uk_84 + 220825*uk_85 + 680625*uk_86 + 75625*uk_87 + 178695*uk_88 + 78375*uk_89 + 2572416961*uk_9 + 250800*uk_90 + 228855*uk_91 + 705375*uk_92 + 78375*uk_93 + 34375*uk_94 + 110000*uk_95 + 100375*uk_96 + 309375*uk_97 + 34375*uk_98 + 352000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 297000*uk_100 + 891000*uk_101 + 225720*uk_102 + 309375*uk_103 + 928125*uk_104 + 235125*uk_105 + 2784375*uk_106 + 705375*uk_107 + 178695*uk_108 + 6859*uk_109 + 963661*uk_11 + 20577*uk_110 + 25992*uk_111 + 27075*uk_112 + 81225*uk_113 + 20577*uk_114 + 61731*uk_115 + 77976*uk_116 + 81225*uk_117 + 243675*uk_118 + 61731*uk_119 + 2890983*uk_12 + 98496*uk_120 + 102600*uk_121 + 307800*uk_122 + 77976*uk_123 + 106875*uk_124 + 320625*uk_125 + 81225*uk_126 + 961875*uk_127 + 243675*uk_128 + 61731*uk_129 + 3651768*uk_13 + 185193*uk_130 + 233928*uk_131 + 243675*uk_132 + 731025*uk_133 + 185193*uk_134 + 295488*uk_135 + 307800*uk_136 + 923400*uk_137 + 233928*uk_138 + 320625*uk_139 + 3803925*uk_14 + 961875*uk_140 + 243675*uk_141 + 2885625*uk_142 + 731025*uk_143 + 185193*uk_144 + 373248*uk_145 + 388800*uk_146 + 1166400*uk_147 + 295488*uk_148 + 405000*uk_149 + 11411775*uk_15 + 1215000*uk_150 + 307800*uk_151 + 3645000*uk_152 + 923400*uk_153 + 233928*uk_154 + 421875*uk_155 + 1265625*uk_156 + 320625*uk_157 + 3796875*uk_158 + 961875*uk_159 + 2890983*uk_16 + 243675*uk_160 + 11390625*uk_161 + 2885625*uk_162 + 731025*uk_163 + 185193*uk_164 + 3025*uk_17 + 1045*uk_18 + 3135*uk_19 + 55*uk_2 + 3960*uk_20 + 4125*uk_21 + 12375*uk_22 + 3135*uk_23 + 361*uk_24 + 1083*uk_25 + 1368*uk_26 + 1425*uk_27 + 4275*uk_28 + 1083*uk_29 + 19*uk_3 + 3249*uk_30 + 4104*uk_31 + 4275*uk_32 + 12825*uk_33 + 3249*uk_34 + 5184*uk_35 + 5400*uk_36 + 16200*uk_37 + 4104*uk_38 + 5625*uk_39 + 57*uk_4 + 16875*uk_40 + 4275*uk_41 + 50625*uk_42 + 12825*uk_43 + 3249*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 48875922259*uk_47 + 146627766777*uk_48 + 185214021192*uk_49 + 72*uk_5 + 192931272075*uk_50 + 578793816225*uk_51 + 146627766777*uk_52 + 153424975*uk_53 + 53001355*uk_54 + 159004065*uk_55 + 200847240*uk_56 + 209215875*uk_57 + 627647625*uk_58 + 159004065*uk_59 + 75*uk_6 + 18309559*uk_60 + 54928677*uk_61 + 69383592*uk_62 + 72274575*uk_63 + 216823725*uk_64 + 54928677*uk_65 + 164786031*uk_66 + 208150776*uk_67 + 216823725*uk_68 + 650471175*uk_69 + 225*uk_7 + 164786031*uk_70 + 262927296*uk_71 + 273882600*uk_72 + 821647800*uk_73 + 208150776*uk_74 + 285294375*uk_75 + 855883125*uk_76 + 216823725*uk_77 + 2567649375*uk_78 + 650471175*uk_79 + 57*uk_8 + 164786031*uk_80 + 166375*uk_81 + 57475*uk_82 + 172425*uk_83 + 217800*uk_84 + 226875*uk_85 + 680625*uk_86 + 172425*uk_87 + 19855*uk_88 + 59565*uk_89 + 2572416961*uk_9 + 75240*uk_90 + 78375*uk_91 + 235125*uk_92 + 59565*uk_93 + 178695*uk_94 + 225720*uk_95 + 235125*uk_96 + 705375*uk_97 + 178695*uk_98 + 285120*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 304920*uk_100 + 891000*uk_101 + 75240*uk_102 + 326095*uk_103 + 952875*uk_104 + 80465*uk_105 + 2784375*uk_106 + 235125*uk_107 + 19855*uk_108 + 148877*uk_109 + 2688107*uk_11 + 53371*uk_110 + 202248*uk_111 + 216293*uk_112 + 632025*uk_113 + 53371*uk_114 + 19133*uk_115 + 72504*uk_116 + 77539*uk_117 + 226575*uk_118 + 19133*uk_119 + 963661*uk_12 + 274752*uk_120 + 293832*uk_121 + 858600*uk_122 + 72504*uk_123 + 314237*uk_124 + 918225*uk_125 + 77539*uk_126 + 2683125*uk_127 + 226575*uk_128 + 19133*uk_129 + 3651768*uk_13 + 6859*uk_130 + 25992*uk_131 + 27797*uk_132 + 81225*uk_133 + 6859*uk_134 + 98496*uk_135 + 105336*uk_136 + 307800*uk_137 + 25992*uk_138 + 112651*uk_139 + 3905363*uk_14 + 329175*uk_140 + 27797*uk_141 + 961875*uk_142 + 81225*uk_143 + 6859*uk_144 + 373248*uk_145 + 399168*uk_146 + 1166400*uk_147 + 98496*uk_148 + 426888*uk_149 + 11411775*uk_15 + 1247400*uk_150 + 105336*uk_151 + 3645000*uk_152 + 307800*uk_153 + 25992*uk_154 + 456533*uk_155 + 1334025*uk_156 + 112651*uk_157 + 3898125*uk_158 + 329175*uk_159 + 963661*uk_16 + 27797*uk_160 + 11390625*uk_161 + 961875*uk_162 + 81225*uk_163 + 6859*uk_164 + 3025*uk_17 + 2915*uk_18 + 1045*uk_19 + 55*uk_2 + 3960*uk_20 + 4235*uk_21 + 12375*uk_22 + 1045*uk_23 + 2809*uk_24 + 1007*uk_25 + 3816*uk_26 + 4081*uk_27 + 11925*uk_28 + 1007*uk_29 + 53*uk_3 + 361*uk_30 + 1368*uk_31 + 1463*uk_32 + 4275*uk_33 + 361*uk_34 + 5184*uk_35 + 5544*uk_36 + 16200*uk_37 + 1368*uk_38 + 5929*uk_39 + 19*uk_4 + 17325*uk_40 + 1463*uk_41 + 50625*uk_42 + 4275*uk_43 + 361*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 136338098933*uk_47 + 48875922259*uk_48 + 185214021192*uk_49 + 72*uk_5 + 198076105997*uk_50 + 578793816225*uk_51 + 48875922259*uk_52 + 153424975*uk_53 + 147845885*uk_54 + 53001355*uk_55 + 200847240*uk_56 + 214794965*uk_57 + 627647625*uk_58 + 53001355*uk_59 + 77*uk_6 + 142469671*uk_60 + 51074033*uk_61 + 193543704*uk_62 + 206984239*uk_63 + 604824075*uk_64 + 51074033*uk_65 + 18309559*uk_66 + 69383592*uk_67 + 74201897*uk_68 + 216823725*uk_69 + 225*uk_7 + 18309559*uk_70 + 262927296*uk_71 + 281186136*uk_72 + 821647800*uk_73 + 69383592*uk_74 + 300712951*uk_75 + 878706675*uk_76 + 74201897*uk_77 + 2567649375*uk_78 + 216823725*uk_79 + 19*uk_8 + 18309559*uk_80 + 166375*uk_81 + 160325*uk_82 + 57475*uk_83 + 217800*uk_84 + 232925*uk_85 + 680625*uk_86 + 57475*uk_87 + 154495*uk_88 + 55385*uk_89 + 2572416961*uk_9 + 209880*uk_90 + 224455*uk_91 + 655875*uk_92 + 55385*uk_93 + 19855*uk_94 + 75240*uk_95 + 80465*uk_96 + 235125*uk_97 + 19855*uk_98 + 285120*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 278080*uk_100 + 792000*uk_101 + 186560*uk_102 + 343255*uk_103 + 977625*uk_104 + 230285*uk_105 + 2784375*uk_106 + 655875*uk_107 + 154495*uk_108 + uk_109 + 50719*uk_11 + 53*uk_110 + 64*uk_111 + 79*uk_112 + 225*uk_113 + 53*uk_114 + 2809*uk_115 + 3392*uk_116 + 4187*uk_117 + 11925*uk_118 + 2809*uk_119 + 2688107*uk_12 + 4096*uk_120 + 5056*uk_121 + 14400*uk_122 + 3392*uk_123 + 6241*uk_124 + 17775*uk_125 + 4187*uk_126 + 50625*uk_127 + 11925*uk_128 + 2809*uk_129 + 3246016*uk_13 + 148877*uk_130 + 179776*uk_131 + 221911*uk_132 + 632025*uk_133 + 148877*uk_134 + 217088*uk_135 + 267968*uk_136 + 763200*uk_137 + 179776*uk_138 + 330773*uk_139 + 4006801*uk_14 + 942075*uk_140 + 221911*uk_141 + 2683125*uk_142 + 632025*uk_143 + 148877*uk_144 + 262144*uk_145 + 323584*uk_146 + 921600*uk_147 + 217088*uk_148 + 399424*uk_149 + 11411775*uk_15 + 1137600*uk_150 + 267968*uk_151 + 3240000*uk_152 + 763200*uk_153 + 179776*uk_154 + 493039*uk_155 + 1404225*uk_156 + 330773*uk_157 + 3999375*uk_158 + 942075*uk_159 + 2688107*uk_16 + 221911*uk_160 + 11390625*uk_161 + 2683125*uk_162 + 632025*uk_163 + 148877*uk_164 + 3025*uk_17 + 55*uk_18 + 2915*uk_19 + 55*uk_2 + 3520*uk_20 + 4345*uk_21 + 12375*uk_22 + 2915*uk_23 + uk_24 + 53*uk_25 + 64*uk_26 + 79*uk_27 + 225*uk_28 + 53*uk_29 + uk_3 + 2809*uk_30 + 3392*uk_31 + 4187*uk_32 + 11925*uk_33 + 2809*uk_34 + 4096*uk_35 + 5056*uk_36 + 14400*uk_37 + 3392*uk_38 + 6241*uk_39 + 53*uk_4 + 17775*uk_40 + 4187*uk_41 + 50625*uk_42 + 11925*uk_43 + 2809*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 2572416961*uk_47 + 136338098933*uk_48 + 164634685504*uk_49 + 64*uk_5 + 203220939919*uk_50 + 578793816225*uk_51 + 136338098933*uk_52 + 153424975*uk_53 + 2789545*uk_54 + 147845885*uk_55 + 178530880*uk_56 + 220374055*uk_57 + 627647625*uk_58 + 147845885*uk_59 + 79*uk_6 + 50719*uk_60 + 2688107*uk_61 + 3246016*uk_62 + 4006801*uk_63 + 11411775*uk_64 + 2688107*uk_65 + 142469671*uk_66 + 172038848*uk_67 + 212360453*uk_68 + 604824075*uk_69 + 225*uk_7 + 142469671*uk_70 + 207745024*uk_71 + 256435264*uk_72 + 730353600*uk_73 + 172038848*uk_74 + 316537279*uk_75 + 901530225*uk_76 + 212360453*uk_77 + 2567649375*uk_78 + 604824075*uk_79 + 53*uk_8 + 142469671*uk_80 + 166375*uk_81 + 3025*uk_82 + 160325*uk_83 + 193600*uk_84 + 238975*uk_85 + 680625*uk_86 + 160325*uk_87 + 55*uk_88 + 2915*uk_89 + 2572416961*uk_9 + 3520*uk_90 + 4345*uk_91 + 12375*uk_92 + 2915*uk_93 + 154495*uk_94 + 186560*uk_95 + 230285*uk_96 + 655875*uk_97 + 154495*uk_98 + 225280*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 285120*uk_100 + 792000*uk_101 + 3520*uk_102 + 360855*uk_103 + 1002375*uk_104 + 4455*uk_105 + 2784375*uk_106 + 12375*uk_107 + 55*uk_108 + 2197*uk_109 + 659347*uk_11 + 169*uk_110 + 10816*uk_111 + 13689*uk_112 + 38025*uk_113 + 169*uk_114 + 13*uk_115 + 832*uk_116 + 1053*uk_117 + 2925*uk_118 + 13*uk_119 + 50719*uk_12 + 53248*uk_120 + 67392*uk_121 + 187200*uk_122 + 832*uk_123 + 85293*uk_124 + 236925*uk_125 + 1053*uk_126 + 658125*uk_127 + 2925*uk_128 + 13*uk_129 + 3246016*uk_13 + uk_130 + 64*uk_131 + 81*uk_132 + 225*uk_133 + uk_134 + 4096*uk_135 + 5184*uk_136 + 14400*uk_137 + 64*uk_138 + 6561*uk_139 + 4108239*uk_14 + 18225*uk_140 + 81*uk_141 + 50625*uk_142 + 225*uk_143 + uk_144 + 262144*uk_145 + 331776*uk_146 + 921600*uk_147 + 4096*uk_148 + 419904*uk_149 + 11411775*uk_15 + 1166400*uk_150 + 5184*uk_151 + 3240000*uk_152 + 14400*uk_153 + 64*uk_154 + 531441*uk_155 + 1476225*uk_156 + 6561*uk_157 + 4100625*uk_158 + 18225*uk_159 + 50719*uk_16 + 81*uk_160 + 11390625*uk_161 + 50625*uk_162 + 225*uk_163 + uk_164 + 3025*uk_17 + 715*uk_18 + 55*uk_19 + 55*uk_2 + 3520*uk_20 + 4455*uk_21 + 12375*uk_22 + 55*uk_23 + 169*uk_24 + 13*uk_25 + 832*uk_26 + 1053*uk_27 + 2925*uk_28 + 13*uk_29 + 13*uk_3 + uk_30 + 64*uk_31 + 81*uk_32 + 225*uk_33 + uk_34 + 4096*uk_35 + 5184*uk_36 + 14400*uk_37 + 64*uk_38 + 6561*uk_39 + uk_4 + 18225*uk_40 + 81*uk_41 + 50625*uk_42 + 225*uk_43 + uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 33441420493*uk_47 + 2572416961*uk_48 + 164634685504*uk_49 + 64*uk_5 + 208365773841*uk_50 + 578793816225*uk_51 + 2572416961*uk_52 + 153424975*uk_53 + 36264085*uk_54 + 2789545*uk_55 + 178530880*uk_56 + 225953145*uk_57 + 627647625*uk_58 + 2789545*uk_59 + 81*uk_6 + 8571511*uk_60 + 659347*uk_61 + 42198208*uk_62 + 53407107*uk_63 + 148353075*uk_64 + 659347*uk_65 + 50719*uk_66 + 3246016*uk_67 + 4108239*uk_68 + 11411775*uk_69 + 225*uk_7 + 50719*uk_70 + 207745024*uk_71 + 262927296*uk_72 + 730353600*uk_73 + 3246016*uk_74 + 332767359*uk_75 + 924353775*uk_76 + 4108239*uk_77 + 2567649375*uk_78 + 11411775*uk_79 + uk_8 + 50719*uk_80 + 166375*uk_81 + 39325*uk_82 + 3025*uk_83 + 193600*uk_84 + 245025*uk_85 + 680625*uk_86 + 3025*uk_87 + 9295*uk_88 + 715*uk_89 + 2572416961*uk_9 + 45760*uk_90 + 57915*uk_91 + 160875*uk_92 + 715*uk_93 + 55*uk_94 + 3520*uk_95 + 4455*uk_96 + 12375*uk_97 + 55*uk_98 + 225280*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 273900*uk_100 + 742500*uk_101 + 42900*uk_102 + 378895*uk_103 + 1027125*uk_104 + 59345*uk_105 + 2784375*uk_106 + 160875*uk_107 + 9295*uk_108 + 216*uk_109 + 304314*uk_11 + 468*uk_110 + 2160*uk_111 + 2988*uk_112 + 8100*uk_113 + 468*uk_114 + 1014*uk_115 + 4680*uk_116 + 6474*uk_117 + 17550*uk_118 + 1014*uk_119 + 659347*uk_12 + 21600*uk_120 + 29880*uk_121 + 81000*uk_122 + 4680*uk_123 + 41334*uk_124 + 112050*uk_125 + 6474*uk_126 + 303750*uk_127 + 17550*uk_128 + 1014*uk_129 + 3043140*uk_13 + 2197*uk_130 + 10140*uk_131 + 14027*uk_132 + 38025*uk_133 + 2197*uk_134 + 46800*uk_135 + 64740*uk_136 + 175500*uk_137 + 10140*uk_138 + 89557*uk_139 + 4209677*uk_14 + 242775*uk_140 + 14027*uk_141 + 658125*uk_142 + 38025*uk_143 + 2197*uk_144 + 216000*uk_145 + 298800*uk_146 + 810000*uk_147 + 46800*uk_148 + 413340*uk_149 + 11411775*uk_15 + 1120500*uk_150 + 64740*uk_151 + 3037500*uk_152 + 175500*uk_153 + 10140*uk_154 + 571787*uk_155 + 1550025*uk_156 + 89557*uk_157 + 4201875*uk_158 + 242775*uk_159 + 659347*uk_16 + 14027*uk_160 + 11390625*uk_161 + 658125*uk_162 + 38025*uk_163 + 2197*uk_164 + 3025*uk_17 + 330*uk_18 + 715*uk_19 + 55*uk_2 + 3300*uk_20 + 4565*uk_21 + 12375*uk_22 + 715*uk_23 + 36*uk_24 + 78*uk_25 + 360*uk_26 + 498*uk_27 + 1350*uk_28 + 78*uk_29 + 6*uk_3 + 169*uk_30 + 780*uk_31 + 1079*uk_32 + 2925*uk_33 + 169*uk_34 + 3600*uk_35 + 4980*uk_36 + 13500*uk_37 + 780*uk_38 + 6889*uk_39 + 13*uk_4 + 18675*uk_40 + 1079*uk_41 + 50625*uk_42 + 2925*uk_43 + 169*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 15434501766*uk_47 + 33441420493*uk_48 + 154345017660*uk_49 + 60*uk_5 + 213510607763*uk_50 + 578793816225*uk_51 + 33441420493*uk_52 + 153424975*uk_53 + 16737270*uk_54 + 36264085*uk_55 + 167372700*uk_56 + 231532235*uk_57 + 627647625*uk_58 + 36264085*uk_59 + 83*uk_6 + 1825884*uk_60 + 3956082*uk_61 + 18258840*uk_62 + 25258062*uk_63 + 68470650*uk_64 + 3956082*uk_65 + 8571511*uk_66 + 39560820*uk_67 + 54725801*uk_68 + 148353075*uk_69 + 225*uk_7 + 8571511*uk_70 + 182588400*uk_71 + 252580620*uk_72 + 684706500*uk_73 + 39560820*uk_74 + 349403191*uk_75 + 947177325*uk_76 + 54725801*uk_77 + 2567649375*uk_78 + 148353075*uk_79 + 13*uk_8 + 8571511*uk_80 + 166375*uk_81 + 18150*uk_82 + 39325*uk_83 + 181500*uk_84 + 251075*uk_85 + 680625*uk_86 + 39325*uk_87 + 1980*uk_88 + 4290*uk_89 + 2572416961*uk_9 + 19800*uk_90 + 27390*uk_91 + 74250*uk_92 + 4290*uk_93 + 9295*uk_94 + 42900*uk_95 + 59345*uk_96 + 160875*uk_97 + 9295*uk_98 + 198000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 280500*uk_100 + 742500*uk_101 + 19800*uk_102 + 397375*uk_103 + 1051875*uk_104 + 28050*uk_105 + 2784375*uk_106 + 74250*uk_107 + 1980*uk_108 + 205379*uk_109 + 2992421*uk_11 + 20886*uk_110 + 208860*uk_111 + 295885*uk_112 + 783225*uk_113 + 20886*uk_114 + 2124*uk_115 + 21240*uk_116 + 30090*uk_117 + 79650*uk_118 + 2124*uk_119 + 304314*uk_12 + 212400*uk_120 + 300900*uk_121 + 796500*uk_122 + 21240*uk_123 + 426275*uk_124 + 1128375*uk_125 + 30090*uk_126 + 2986875*uk_127 + 79650*uk_128 + 2124*uk_129 + 3043140*uk_13 + 216*uk_130 + 2160*uk_131 + 3060*uk_132 + 8100*uk_133 + 216*uk_134 + 21600*uk_135 + 30600*uk_136 + 81000*uk_137 + 2160*uk_138 + 43350*uk_139 + 4311115*uk_14 + 114750*uk_140 + 3060*uk_141 + 303750*uk_142 + 8100*uk_143 + 216*uk_144 + 216000*uk_145 + 306000*uk_146 + 810000*uk_147 + 21600*uk_148 + 433500*uk_149 + 11411775*uk_15 + 1147500*uk_150 + 30600*uk_151 + 3037500*uk_152 + 81000*uk_153 + 2160*uk_154 + 614125*uk_155 + 1625625*uk_156 + 43350*uk_157 + 4303125*uk_158 + 114750*uk_159 + 304314*uk_16 + 3060*uk_160 + 11390625*uk_161 + 303750*uk_162 + 8100*uk_163 + 216*uk_164 + 3025*uk_17 + 3245*uk_18 + 330*uk_19 + 55*uk_2 + 3300*uk_20 + 4675*uk_21 + 12375*uk_22 + 330*uk_23 + 3481*uk_24 + 354*uk_25 + 3540*uk_26 + 5015*uk_27 + 13275*uk_28 + 354*uk_29 + 59*uk_3 + 36*uk_30 + 360*uk_31 + 510*uk_32 + 1350*uk_33 + 36*uk_34 + 3600*uk_35 + 5100*uk_36 + 13500*uk_37 + 360*uk_38 + 7225*uk_39 + 6*uk_4 + 19125*uk_40 + 510*uk_41 + 50625*uk_42 + 1350*uk_43 + 36*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 151772600699*uk_47 + 15434501766*uk_48 + 154345017660*uk_49 + 60*uk_5 + 218655441685*uk_50 + 578793816225*uk_51 + 15434501766*uk_52 + 153424975*uk_53 + 164583155*uk_54 + 16737270*uk_55 + 167372700*uk_56 + 237111325*uk_57 + 627647625*uk_58 + 16737270*uk_59 + 85*uk_6 + 176552839*uk_60 + 17954526*uk_61 + 179545260*uk_62 + 254355785*uk_63 + 673294725*uk_64 + 17954526*uk_65 + 1825884*uk_66 + 18258840*uk_67 + 25866690*uk_68 + 68470650*uk_69 + 225*uk_7 + 1825884*uk_70 + 182588400*uk_71 + 258666900*uk_72 + 684706500*uk_73 + 18258840*uk_74 + 366444775*uk_75 + 970000875*uk_76 + 25866690*uk_77 + 2567649375*uk_78 + 68470650*uk_79 + 6*uk_8 + 1825884*uk_80 + 166375*uk_81 + 178475*uk_82 + 18150*uk_83 + 181500*uk_84 + 257125*uk_85 + 680625*uk_86 + 18150*uk_87 + 191455*uk_88 + 19470*uk_89 + 2572416961*uk_9 + 194700*uk_90 + 275825*uk_91 + 730125*uk_92 + 19470*uk_93 + 1980*uk_94 + 19800*uk_95 + 28050*uk_96 + 74250*uk_97 + 1980*uk_98 + 198000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 267960*uk_100 + 693000*uk_101 + 181720*uk_102 + 416295*uk_103 + 1076625*uk_104 + 282315*uk_105 + 2784375*uk_106 + 730125*uk_107 + 191455*uk_108 + 614125*uk_109 + 4311115*uk_11 + 426275*uk_110 + 404600*uk_111 + 628575*uk_112 + 1625625*uk_113 + 426275*uk_114 + 295885*uk_115 + 280840*uk_116 + 436305*uk_117 + 1128375*uk_118 + 295885*uk_119 + 2992421*uk_12 + 266560*uk_120 + 414120*uk_121 + 1071000*uk_122 + 280840*uk_123 + 643365*uk_124 + 1663875*uk_125 + 436305*uk_126 + 4303125*uk_127 + 1128375*uk_128 + 295885*uk_129 + 2840264*uk_13 + 205379*uk_130 + 194936*uk_131 + 302847*uk_132 + 783225*uk_133 + 205379*uk_134 + 185024*uk_135 + 287448*uk_136 + 743400*uk_137 + 194936*uk_138 + 446571*uk_139 + 4412553*uk_14 + 1154925*uk_140 + 302847*uk_141 + 2986875*uk_142 + 783225*uk_143 + 205379*uk_144 + 175616*uk_145 + 272832*uk_146 + 705600*uk_147 + 185024*uk_148 + 423864*uk_149 + 11411775*uk_15 + 1096200*uk_150 + 287448*uk_151 + 2835000*uk_152 + 743400*uk_153 + 194936*uk_154 + 658503*uk_155 + 1703025*uk_156 + 446571*uk_157 + 4404375*uk_158 + 1154925*uk_159 + 2992421*uk_16 + 302847*uk_160 + 11390625*uk_161 + 2986875*uk_162 + 783225*uk_163 + 205379*uk_164 + 3025*uk_17 + 4675*uk_18 + 3245*uk_19 + 55*uk_2 + 3080*uk_20 + 4785*uk_21 + 12375*uk_22 + 3245*uk_23 + 7225*uk_24 + 5015*uk_25 + 4760*uk_26 + 7395*uk_27 + 19125*uk_28 + 5015*uk_29 + 85*uk_3 + 3481*uk_30 + 3304*uk_31 + 5133*uk_32 + 13275*uk_33 + 3481*uk_34 + 3136*uk_35 + 4872*uk_36 + 12600*uk_37 + 3304*uk_38 + 7569*uk_39 + 59*uk_4 + 19575*uk_40 + 5133*uk_41 + 50625*uk_42 + 13275*uk_43 + 3481*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 218655441685*uk_47 + 151772600699*uk_48 + 144055349816*uk_49 + 56*uk_5 + 223800275607*uk_50 + 578793816225*uk_51 + 151772600699*uk_52 + 153424975*uk_53 + 237111325*uk_54 + 164583155*uk_55 + 156214520*uk_56 + 242690415*uk_57 + 627647625*uk_58 + 164583155*uk_59 + 87*uk_6 + 366444775*uk_60 + 254355785*uk_61 + 241422440*uk_62 + 375067005*uk_63 + 970000875*uk_64 + 254355785*uk_65 + 176552839*uk_66 + 167575576*uk_67 + 260340627*uk_68 + 673294725*uk_69 + 225*uk_7 + 176552839*uk_70 + 159054784*uk_71 + 247102968*uk_72 + 639059400*uk_73 + 167575576*uk_74 + 383892111*uk_75 + 992824425*uk_76 + 260340627*uk_77 + 2567649375*uk_78 + 673294725*uk_79 + 59*uk_8 + 176552839*uk_80 + 166375*uk_81 + 257125*uk_82 + 178475*uk_83 + 169400*uk_84 + 263175*uk_85 + 680625*uk_86 + 178475*uk_87 + 397375*uk_88 + 275825*uk_89 + 2572416961*uk_9 + 261800*uk_90 + 406725*uk_91 + 1051875*uk_92 + 275825*uk_93 + 191455*uk_94 + 181720*uk_95 + 282315*uk_96 + 730125*uk_97 + 191455*uk_98 + 172480*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 254540*uk_100 + 643500*uk_101 + 243100*uk_102 + 435655*uk_103 + 1101375*uk_104 + 416075*uk_105 + 2784375*uk_106 + 1051875*uk_107 + 397375*uk_108 + 474552*uk_109 + 3956082*uk_11 + 517140*uk_110 + 316368*uk_111 + 541476*uk_112 + 1368900*uk_113 + 517140*uk_114 + 563550*uk_115 + 344760*uk_116 + 590070*uk_117 + 1491750*uk_118 + 563550*uk_119 + 4311115*uk_12 + 210912*uk_120 + 360984*uk_121 + 912600*uk_122 + 344760*uk_123 + 617838*uk_124 + 1561950*uk_125 + 590070*uk_126 + 3948750*uk_127 + 1491750*uk_128 + 563550*uk_129 + 2637388*uk_13 + 614125*uk_130 + 375700*uk_131 + 643025*uk_132 + 1625625*uk_133 + 614125*uk_134 + 229840*uk_135 + 393380*uk_136 + 994500*uk_137 + 375700*uk_138 + 673285*uk_139 + 4513991*uk_14 + 1702125*uk_140 + 643025*uk_141 + 4303125*uk_142 + 1625625*uk_143 + 614125*uk_144 + 140608*uk_145 + 240656*uk_146 + 608400*uk_147 + 229840*uk_148 + 411892*uk_149 + 11411775*uk_15 + 1041300*uk_150 + 393380*uk_151 + 2632500*uk_152 + 994500*uk_153 + 375700*uk_154 + 704969*uk_155 + 1782225*uk_156 + 673285*uk_157 + 4505625*uk_158 + 1702125*uk_159 + 4311115*uk_16 + 643025*uk_160 + 11390625*uk_161 + 4303125*uk_162 + 1625625*uk_163 + 614125*uk_164 + 3025*uk_17 + 4290*uk_18 + 4675*uk_19 + 55*uk_2 + 2860*uk_20 + 4895*uk_21 + 12375*uk_22 + 4675*uk_23 + 6084*uk_24 + 6630*uk_25 + 4056*uk_26 + 6942*uk_27 + 17550*uk_28 + 6630*uk_29 + 78*uk_3 + 7225*uk_30 + 4420*uk_31 + 7565*uk_32 + 19125*uk_33 + 7225*uk_34 + 2704*uk_35 + 4628*uk_36 + 11700*uk_37 + 4420*uk_38 + 7921*uk_39 + 85*uk_4 + 20025*uk_40 + 7565*uk_41 + 50625*uk_42 + 19125*uk_43 + 7225*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 200648522958*uk_47 + 218655441685*uk_48 + 133765681972*uk_49 + 52*uk_5 + 228945109529*uk_50 + 578793816225*uk_51 + 218655441685*uk_52 + 153424975*uk_53 + 217584510*uk_54 + 237111325*uk_55 + 145056340*uk_56 + 248269505*uk_57 + 627647625*uk_58 + 237111325*uk_59 + 89*uk_6 + 308574396*uk_60 + 336266970*uk_61 + 205716264*uk_62 + 352091298*uk_63 + 890118450*uk_64 + 336266970*uk_65 + 366444775*uk_66 + 224177980*uk_67 + 383689235*uk_68 + 970000875*uk_69 + 225*uk_7 + 366444775*uk_70 + 137144176*uk_71 + 234727532*uk_72 + 593412300*uk_73 + 224177980*uk_74 + 401745199*uk_75 + 1015647975*uk_76 + 383689235*uk_77 + 2567649375*uk_78 + 970000875*uk_79 + 85*uk_8 + 366444775*uk_80 + 166375*uk_81 + 235950*uk_82 + 257125*uk_83 + 157300*uk_84 + 269225*uk_85 + 680625*uk_86 + 257125*uk_87 + 334620*uk_88 + 364650*uk_89 + 2572416961*uk_9 + 223080*uk_90 + 381810*uk_91 + 965250*uk_92 + 364650*uk_93 + 397375*uk_94 + 243100*uk_95 + 416075*uk_96 + 1051875*uk_97 + 397375*uk_98 + 148720*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 240240*uk_100 + 594000*uk_101 + 205920*uk_102 + 455455*uk_103 + 1126125*uk_104 + 390390*uk_105 + 2784375*uk_106 + 965250*uk_107 + 334620*uk_108 + 32768*uk_109 + 1623008*uk_11 + 79872*uk_110 + 49152*uk_111 + 93184*uk_112 + 230400*uk_113 + 79872*uk_114 + 194688*uk_115 + 119808*uk_116 + 227136*uk_117 + 561600*uk_118 + 194688*uk_119 + 3956082*uk_12 + 73728*uk_120 + 139776*uk_121 + 345600*uk_122 + 119808*uk_123 + 264992*uk_124 + 655200*uk_125 + 227136*uk_126 + 1620000*uk_127 + 561600*uk_128 + 194688*uk_129 + 2434512*uk_13 + 474552*uk_130 + 292032*uk_131 + 553644*uk_132 + 1368900*uk_133 + 474552*uk_134 + 179712*uk_135 + 340704*uk_136 + 842400*uk_137 + 292032*uk_138 + 645918*uk_139 + 4615429*uk_14 + 1597050*uk_140 + 553644*uk_141 + 3948750*uk_142 + 1368900*uk_143 + 474552*uk_144 + 110592*uk_145 + 209664*uk_146 + 518400*uk_147 + 179712*uk_148 + 397488*uk_149 + 11411775*uk_15 + 982800*uk_150 + 340704*uk_151 + 2430000*uk_152 + 842400*uk_153 + 292032*uk_154 + 753571*uk_155 + 1863225*uk_156 + 645918*uk_157 + 4606875*uk_158 + 1597050*uk_159 + 3956082*uk_16 + 553644*uk_160 + 11390625*uk_161 + 3948750*uk_162 + 1368900*uk_163 + 474552*uk_164 + 3025*uk_17 + 1760*uk_18 + 4290*uk_19 + 55*uk_2 + 2640*uk_20 + 5005*uk_21 + 12375*uk_22 + 4290*uk_23 + 1024*uk_24 + 2496*uk_25 + 1536*uk_26 + 2912*uk_27 + 7200*uk_28 + 2496*uk_29 + 32*uk_3 + 6084*uk_30 + 3744*uk_31 + 7098*uk_32 + 17550*uk_33 + 6084*uk_34 + 2304*uk_35 + 4368*uk_36 + 10800*uk_37 + 3744*uk_38 + 8281*uk_39 + 78*uk_4 + 20475*uk_40 + 7098*uk_41 + 50625*uk_42 + 17550*uk_43 + 6084*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 82317342752*uk_47 + 200648522958*uk_48 + 123476014128*uk_49 + 48*uk_5 + 234089943451*uk_50 + 578793816225*uk_51 + 200648522958*uk_52 + 153424975*uk_53 + 89265440*uk_54 + 217584510*uk_55 + 133898160*uk_56 + 253848595*uk_57 + 627647625*uk_58 + 217584510*uk_59 + 91*uk_6 + 51936256*uk_60 + 126594624*uk_61 + 77904384*uk_62 + 147693728*uk_63 + 365176800*uk_64 + 126594624*uk_65 + 308574396*uk_66 + 189891936*uk_67 + 360003462*uk_68 + 890118450*uk_69 + 225*uk_7 + 308574396*uk_70 + 116856576*uk_71 + 221540592*uk_72 + 547765200*uk_73 + 189891936*uk_74 + 420004039*uk_75 + 1038471525*uk_76 + 360003462*uk_77 + 2567649375*uk_78 + 890118450*uk_79 + 78*uk_8 + 308574396*uk_80 + 166375*uk_81 + 96800*uk_82 + 235950*uk_83 + 145200*uk_84 + 275275*uk_85 + 680625*uk_86 + 235950*uk_87 + 56320*uk_88 + 137280*uk_89 + 2572416961*uk_9 + 84480*uk_90 + 160160*uk_91 + 396000*uk_92 + 137280*uk_93 + 334620*uk_94 + 205920*uk_95 + 390390*uk_96 + 965250*uk_97 + 334620*uk_98 + 126720*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 245520*uk_100 + 594000*uk_101 + 84480*uk_102 + 475695*uk_103 + 1150875*uk_104 + 163680*uk_105 + 2784375*uk_106 + 396000*uk_107 + 56320*uk_108 + 39304*uk_109 + 1724446*uk_11 + 36992*uk_110 + 55488*uk_111 + 107508*uk_112 + 260100*uk_113 + 36992*uk_114 + 34816*uk_115 + 52224*uk_116 + 101184*uk_117 + 244800*uk_118 + 34816*uk_119 + 1623008*uk_12 + 78336*uk_120 + 151776*uk_121 + 367200*uk_122 + 52224*uk_123 + 294066*uk_124 + 711450*uk_125 + 101184*uk_126 + 1721250*uk_127 + 244800*uk_128 + 34816*uk_129 + 2434512*uk_13 + 32768*uk_130 + 49152*uk_131 + 95232*uk_132 + 230400*uk_133 + 32768*uk_134 + 73728*uk_135 + 142848*uk_136 + 345600*uk_137 + 49152*uk_138 + 276768*uk_139 + 4716867*uk_14 + 669600*uk_140 + 95232*uk_141 + 1620000*uk_142 + 230400*uk_143 + 32768*uk_144 + 110592*uk_145 + 214272*uk_146 + 518400*uk_147 + 73728*uk_148 + 415152*uk_149 + 11411775*uk_15 + 1004400*uk_150 + 142848*uk_151 + 2430000*uk_152 + 345600*uk_153 + 49152*uk_154 + 804357*uk_155 + 1946025*uk_156 + 276768*uk_157 + 4708125*uk_158 + 669600*uk_159 + 1623008*uk_16 + 95232*uk_160 + 11390625*uk_161 + 1620000*uk_162 + 230400*uk_163 + 32768*uk_164 + 3025*uk_17 + 1870*uk_18 + 1760*uk_19 + 55*uk_2 + 2640*uk_20 + 5115*uk_21 + 12375*uk_22 + 1760*uk_23 + 1156*uk_24 + 1088*uk_25 + 1632*uk_26 + 3162*uk_27 + 7650*uk_28 + 1088*uk_29 + 34*uk_3 + 1024*uk_30 + 1536*uk_31 + 2976*uk_32 + 7200*uk_33 + 1024*uk_34 + 2304*uk_35 + 4464*uk_36 + 10800*uk_37 + 1536*uk_38 + 8649*uk_39 + 32*uk_4 + 20925*uk_40 + 2976*uk_41 + 50625*uk_42 + 7200*uk_43 + 1024*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 87462176674*uk_47 + 82317342752*uk_48 + 123476014128*uk_49 + 48*uk_5 + 239234777373*uk_50 + 578793816225*uk_51 + 82317342752*uk_52 + 153424975*uk_53 + 94844530*uk_54 + 89265440*uk_55 + 133898160*uk_56 + 259427685*uk_57 + 627647625*uk_58 + 89265440*uk_59 + 93*uk_6 + 58631164*uk_60 + 55182272*uk_61 + 82773408*uk_62 + 160373478*uk_63 + 388000350*uk_64 + 55182272*uk_65 + 51936256*uk_66 + 77904384*uk_67 + 150939744*uk_68 + 365176800*uk_69 + 225*uk_7 + 51936256*uk_70 + 116856576*uk_71 + 226409616*uk_72 + 547765200*uk_73 + 77904384*uk_74 + 438668631*uk_75 + 1061295075*uk_76 + 150939744*uk_77 + 2567649375*uk_78 + 365176800*uk_79 + 32*uk_8 + 51936256*uk_80 + 166375*uk_81 + 102850*uk_82 + 96800*uk_83 + 145200*uk_84 + 281325*uk_85 + 680625*uk_86 + 96800*uk_87 + 63580*uk_88 + 59840*uk_89 + 2572416961*uk_9 + 89760*uk_90 + 173910*uk_91 + 420750*uk_92 + 59840*uk_93 + 56320*uk_94 + 84480*uk_95 + 163680*uk_96 + 396000*uk_97 + 56320*uk_98 + 126720*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 250800*uk_100 + 594000*uk_101 + 89760*uk_102 + 496375*uk_103 + 1175625*uk_104 + 177650*uk_105 + 2784375*uk_106 + 420750*uk_107 + 63580*uk_108 + 592704*uk_109 + 4260396*uk_11 + 239904*uk_110 + 338688*uk_111 + 670320*uk_112 + 1587600*uk_113 + 239904*uk_114 + 97104*uk_115 + 137088*uk_116 + 271320*uk_117 + 642600*uk_118 + 97104*uk_119 + 1724446*uk_12 + 193536*uk_120 + 383040*uk_121 + 907200*uk_122 + 137088*uk_123 + 758100*uk_124 + 1795500*uk_125 + 271320*uk_126 + 4252500*uk_127 + 642600*uk_128 + 97104*uk_129 + 2434512*uk_13 + 39304*uk_130 + 55488*uk_131 + 109820*uk_132 + 260100*uk_133 + 39304*uk_134 + 78336*uk_135 + 155040*uk_136 + 367200*uk_137 + 55488*uk_138 + 306850*uk_139 + 4818305*uk_14 + 726750*uk_140 + 109820*uk_141 + 1721250*uk_142 + 260100*uk_143 + 39304*uk_144 + 110592*uk_145 + 218880*uk_146 + 518400*uk_147 + 78336*uk_148 + 433200*uk_149 + 11411775*uk_15 + 1026000*uk_150 + 155040*uk_151 + 2430000*uk_152 + 367200*uk_153 + 55488*uk_154 + 857375*uk_155 + 2030625*uk_156 + 306850*uk_157 + 4809375*uk_158 + 726750*uk_159 + 1724446*uk_16 + 109820*uk_160 + 11390625*uk_161 + 1721250*uk_162 + 260100*uk_163 + 39304*uk_164 + 3025*uk_17 + 4620*uk_18 + 1870*uk_19 + 55*uk_2 + 2640*uk_20 + 5225*uk_21 + 12375*uk_22 + 1870*uk_23 + 7056*uk_24 + 2856*uk_25 + 4032*uk_26 + 7980*uk_27 + 18900*uk_28 + 2856*uk_29 + 84*uk_3 + 1156*uk_30 + 1632*uk_31 + 3230*uk_32 + 7650*uk_33 + 1156*uk_34 + 2304*uk_35 + 4560*uk_36 + 10800*uk_37 + 1632*uk_38 + 9025*uk_39 + 34*uk_4 + 21375*uk_40 + 3230*uk_41 + 50625*uk_42 + 7650*uk_43 + 1156*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 216083024724*uk_47 + 87462176674*uk_48 + 123476014128*uk_49 + 48*uk_5 + 244379611295*uk_50 + 578793816225*uk_51 + 87462176674*uk_52 + 153424975*uk_53 + 234321780*uk_54 + 94844530*uk_55 + 133898160*uk_56 + 265006775*uk_57 + 627647625*uk_58 + 94844530*uk_59 + 95*uk_6 + 357873264*uk_60 + 144853464*uk_61 + 204499008*uk_62 + 404737620*uk_63 + 958589100*uk_64 + 144853464*uk_65 + 58631164*uk_66 + 82773408*uk_67 + 163822370*uk_68 + 388000350*uk_69 + 225*uk_7 + 58631164*uk_70 + 116856576*uk_71 + 231278640*uk_72 + 547765200*uk_73 + 82773408*uk_74 + 457738975*uk_75 + 1084118625*uk_76 + 163822370*uk_77 + 2567649375*uk_78 + 388000350*uk_79 + 34*uk_8 + 58631164*uk_80 + 166375*uk_81 + 254100*uk_82 + 102850*uk_83 + 145200*uk_84 + 287375*uk_85 + 680625*uk_86 + 102850*uk_87 + 388080*uk_88 + 157080*uk_89 + 2572416961*uk_9 + 221760*uk_90 + 438900*uk_91 + 1039500*uk_92 + 157080*uk_93 + 63580*uk_94 + 89760*uk_95 + 177650*uk_96 + 420750*uk_97 + 63580*uk_98 + 126720*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 234740*uk_100 + 544500*uk_101 + 203280*uk_102 + 517495*uk_103 + 1200375*uk_104 + 448140*uk_105 + 2784375*uk_106 + 1039500*uk_107 + 388080*uk_108 + 614125*uk_109 + 4311115*uk_11 + 606900*uk_110 + 317900*uk_111 + 700825*uk_112 + 1625625*uk_113 + 606900*uk_114 + 599760*uk_115 + 314160*uk_116 + 692580*uk_117 + 1606500*uk_118 + 599760*uk_119 + 4260396*uk_12 + 164560*uk_120 + 362780*uk_121 + 841500*uk_122 + 314160*uk_123 + 799765*uk_124 + 1855125*uk_125 + 692580*uk_126 + 4303125*uk_127 + 1606500*uk_128 + 599760*uk_129 + 2231636*uk_13 + 592704*uk_130 + 310464*uk_131 + 684432*uk_132 + 1587600*uk_133 + 592704*uk_134 + 162624*uk_135 + 358512*uk_136 + 831600*uk_137 + 310464*uk_138 + 790356*uk_139 + 4919743*uk_14 + 1833300*uk_140 + 684432*uk_141 + 4252500*uk_142 + 1587600*uk_143 + 592704*uk_144 + 85184*uk_145 + 187792*uk_146 + 435600*uk_147 + 162624*uk_148 + 413996*uk_149 + 11411775*uk_15 + 960300*uk_150 + 358512*uk_151 + 2227500*uk_152 + 831600*uk_153 + 310464*uk_154 + 912673*uk_155 + 2117025*uk_156 + 790356*uk_157 + 4910625*uk_158 + 1833300*uk_159 + 4260396*uk_16 + 684432*uk_160 + 11390625*uk_161 + 4252500*uk_162 + 1587600*uk_163 + 592704*uk_164 + 3025*uk_17 + 4675*uk_18 + 4620*uk_19 + 55*uk_2 + 2420*uk_20 + 5335*uk_21 + 12375*uk_22 + 4620*uk_23 + 7225*uk_24 + 7140*uk_25 + 3740*uk_26 + 8245*uk_27 + 19125*uk_28 + 7140*uk_29 + 85*uk_3 + 7056*uk_30 + 3696*uk_31 + 8148*uk_32 + 18900*uk_33 + 7056*uk_34 + 1936*uk_35 + 4268*uk_36 + 9900*uk_37 + 3696*uk_38 + 9409*uk_39 + 84*uk_4 + 21825*uk_40 + 8148*uk_41 + 50625*uk_42 + 18900*uk_43 + 7056*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 218655441685*uk_47 + 216083024724*uk_48 + 113186346284*uk_49 + 44*uk_5 + 249524445217*uk_50 + 578793816225*uk_51 + 216083024724*uk_52 + 153424975*uk_53 + 237111325*uk_54 + 234321780*uk_55 + 122739980*uk_56 + 270585865*uk_57 + 627647625*uk_58 + 234321780*uk_59 + 97*uk_6 + 366444775*uk_60 + 362133660*uk_61 + 189689060*uk_62 + 418178155*uk_63 + 970000875*uk_64 + 362133660*uk_65 + 357873264*uk_66 + 187457424*uk_67 + 413258412*uk_68 + 958589100*uk_69 + 225*uk_7 + 357873264*uk_70 + 98191984*uk_71 + 216468692*uk_72 + 502118100*uk_73 + 187457424*uk_74 + 477215071*uk_75 + 1106942175*uk_76 + 413258412*uk_77 + 2567649375*uk_78 + 958589100*uk_79 + 84*uk_8 + 357873264*uk_80 + 166375*uk_81 + 257125*uk_82 + 254100*uk_83 + 133100*uk_84 + 293425*uk_85 + 680625*uk_86 + 254100*uk_87 + 397375*uk_88 + 392700*uk_89 + 2572416961*uk_9 + 205700*uk_90 + 453475*uk_91 + 1051875*uk_92 + 392700*uk_93 + 388080*uk_94 + 203280*uk_95 + 448140*uk_96 + 1039500*uk_97 + 388080*uk_98 + 106480*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 217800*uk_100 + 495000*uk_101 + 187000*uk_102 + 539055*uk_103 + 1225125*uk_104 + 462825*uk_105 + 2784375*uk_106 + 1051875*uk_107 + 397375*uk_108 + 29791*uk_109 + 1572289*uk_11 + 81685*uk_110 + 38440*uk_111 + 95139*uk_112 + 216225*uk_113 + 81685*uk_114 + 223975*uk_115 + 105400*uk_116 + 260865*uk_117 + 592875*uk_118 + 223975*uk_119 + 4311115*uk_12 + 49600*uk_120 + 122760*uk_121 + 279000*uk_122 + 105400*uk_123 + 303831*uk_124 + 690525*uk_125 + 260865*uk_126 + 1569375*uk_127 + 592875*uk_128 + 223975*uk_129 + 2028760*uk_13 + 614125*uk_130 + 289000*uk_131 + 715275*uk_132 + 1625625*uk_133 + 614125*uk_134 + 136000*uk_135 + 336600*uk_136 + 765000*uk_137 + 289000*uk_138 + 833085*uk_139 + 5021181*uk_14 + 1893375*uk_140 + 715275*uk_141 + 4303125*uk_142 + 1625625*uk_143 + 614125*uk_144 + 64000*uk_145 + 158400*uk_146 + 360000*uk_147 + 136000*uk_148 + 392040*uk_149 + 11411775*uk_15 + 891000*uk_150 + 336600*uk_151 + 2025000*uk_152 + 765000*uk_153 + 289000*uk_154 + 970299*uk_155 + 2205225*uk_156 + 833085*uk_157 + 5011875*uk_158 + 1893375*uk_159 + 4311115*uk_16 + 715275*uk_160 + 11390625*uk_161 + 4303125*uk_162 + 1625625*uk_163 + 614125*uk_164 + 3025*uk_17 + 1705*uk_18 + 4675*uk_19 + 55*uk_2 + 2200*uk_20 + 5445*uk_21 + 12375*uk_22 + 4675*uk_23 + 961*uk_24 + 2635*uk_25 + 1240*uk_26 + 3069*uk_27 + 6975*uk_28 + 2635*uk_29 + 31*uk_3 + 7225*uk_30 + 3400*uk_31 + 8415*uk_32 + 19125*uk_33 + 7225*uk_34 + 1600*uk_35 + 3960*uk_36 + 9000*uk_37 + 3400*uk_38 + 9801*uk_39 + 85*uk_4 + 22275*uk_40 + 8415*uk_41 + 50625*uk_42 + 19125*uk_43 + 7225*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 79744925791*uk_47 + 218655441685*uk_48 + 102896678440*uk_49 + 40*uk_5 + 254669279139*uk_50 + 578793816225*uk_51 + 218655441685*uk_52 + 153424975*uk_53 + 86475895*uk_54 + 237111325*uk_55 + 111581800*uk_56 + 276164955*uk_57 + 627647625*uk_58 + 237111325*uk_59 + 99*uk_6 + 48740959*uk_60 + 133644565*uk_61 + 62891560*uk_62 + 155656611*uk_63 + 353765025*uk_64 + 133644565*uk_65 + 366444775*uk_66 + 172444600*uk_67 + 426800385*uk_68 + 970000875*uk_69 + 225*uk_7 + 366444775*uk_70 + 81150400*uk_71 + 200847240*uk_72 + 456471000*uk_73 + 172444600*uk_74 + 497096919*uk_75 + 1129765725*uk_76 + 426800385*uk_77 + 2567649375*uk_78 + 970000875*uk_79 + 85*uk_8 + 366444775*uk_80 + 166375*uk_81 + 93775*uk_82 + 257125*uk_83 + 121000*uk_84 + 299475*uk_85 + 680625*uk_86 + 257125*uk_87 + 52855*uk_88 + 144925*uk_89 + 2572416961*uk_9 + 68200*uk_90 + 168795*uk_91 + 383625*uk_92 + 144925*uk_93 + 397375*uk_94 + 187000*uk_95 + 462825*uk_96 + 1051875*uk_97 + 397375*uk_98 + 88000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 222200*uk_100 + 495000*uk_101 + 68200*uk_102 + 561055*uk_103 + 1249875*uk_104 + 172205*uk_105 + 2784375*uk_106 + 383625*uk_107 + 52855*uk_108 + 4913*uk_109 + 862223*uk_11 + 8959*uk_110 + 11560*uk_111 + 29189*uk_112 + 65025*uk_113 + 8959*uk_114 + 16337*uk_115 + 21080*uk_116 + 53227*uk_117 + 118575*uk_118 + 16337*uk_119 + 1572289*uk_12 + 27200*uk_120 + 68680*uk_121 + 153000*uk_122 + 21080*uk_123 + 173417*uk_124 + 386325*uk_125 + 53227*uk_126 + 860625*uk_127 + 118575*uk_128 + 16337*uk_129 + 2028760*uk_13 + 29791*uk_130 + 38440*uk_131 + 97061*uk_132 + 216225*uk_133 + 29791*uk_134 + 49600*uk_135 + 125240*uk_136 + 279000*uk_137 + 38440*uk_138 + 316231*uk_139 + 5122619*uk_14 + 704475*uk_140 + 97061*uk_141 + 1569375*uk_142 + 216225*uk_143 + 29791*uk_144 + 64000*uk_145 + 161600*uk_146 + 360000*uk_147 + 49600*uk_148 + 408040*uk_149 + 11411775*uk_15 + 909000*uk_150 + 125240*uk_151 + 2025000*uk_152 + 279000*uk_153 + 38440*uk_154 + 1030301*uk_155 + 2295225*uk_156 + 316231*uk_157 + 5113125*uk_158 + 704475*uk_159 + 1572289*uk_16 + 97061*uk_160 + 11390625*uk_161 + 1569375*uk_162 + 216225*uk_163 + 29791*uk_164 + 3025*uk_17 + 935*uk_18 + 1705*uk_19 + 55*uk_2 + 2200*uk_20 + 5555*uk_21 + 12375*uk_22 + 1705*uk_23 + 289*uk_24 + 527*uk_25 + 680*uk_26 + 1717*uk_27 + 3825*uk_28 + 527*uk_29 + 17*uk_3 + 961*uk_30 + 1240*uk_31 + 3131*uk_32 + 6975*uk_33 + 961*uk_34 + 1600*uk_35 + 4040*uk_36 + 9000*uk_37 + 1240*uk_38 + 10201*uk_39 + 31*uk_4 + 22725*uk_40 + 3131*uk_41 + 50625*uk_42 + 6975*uk_43 + 961*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 43731088337*uk_47 + 79744925791*uk_48 + 102896678440*uk_49 + 40*uk_5 + 259814113061*uk_50 + 578793816225*uk_51 + 79744925791*uk_52 + 153424975*uk_53 + 47422265*uk_54 + 86475895*uk_55 + 111581800*uk_56 + 281744045*uk_57 + 627647625*uk_58 + 86475895*uk_59 + 101*uk_6 + 14657791*uk_60 + 26728913*uk_61 + 34488920*uk_62 + 87084523*uk_63 + 194000175*uk_64 + 26728913*uk_65 + 48740959*uk_66 + 62891560*uk_67 + 158801189*uk_68 + 353765025*uk_69 + 225*uk_7 + 48740959*uk_70 + 81150400*uk_71 + 204904760*uk_72 + 456471000*uk_73 + 62891560*uk_74 + 517384519*uk_75 + 1152589275*uk_76 + 158801189*uk_77 + 2567649375*uk_78 + 353765025*uk_79 + 31*uk_8 + 48740959*uk_80 + 166375*uk_81 + 51425*uk_82 + 93775*uk_83 + 121000*uk_84 + 305525*uk_85 + 680625*uk_86 + 93775*uk_87 + 15895*uk_88 + 28985*uk_89 + 2572416961*uk_9 + 37400*uk_90 + 94435*uk_91 + 210375*uk_92 + 28985*uk_93 + 52855*uk_94 + 68200*uk_95 + 172205*uk_96 + 383625*uk_97 + 52855*uk_98 + 88000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 226600*uk_100 + 495000*uk_101 + 37400*uk_102 + 583495*uk_103 + 1274625*uk_104 + 96305*uk_105 + 2784375*uk_106 + 210375*uk_107 + 15895*uk_108 + 79507*uk_109 + 2180917*uk_11 + 31433*uk_110 + 73960*uk_111 + 190447*uk_112 + 416025*uk_113 + 31433*uk_114 + 12427*uk_115 + 29240*uk_116 + 75293*uk_117 + 164475*uk_118 + 12427*uk_119 + 862223*uk_12 + 68800*uk_120 + 177160*uk_121 + 387000*uk_122 + 29240*uk_123 + 456187*uk_124 + 996525*uk_125 + 75293*uk_126 + 2176875*uk_127 + 164475*uk_128 + 12427*uk_129 + 2028760*uk_13 + 4913*uk_130 + 11560*uk_131 + 29767*uk_132 + 65025*uk_133 + 4913*uk_134 + 27200*uk_135 + 70040*uk_136 + 153000*uk_137 + 11560*uk_138 + 180353*uk_139 + 5224057*uk_14 + 393975*uk_140 + 29767*uk_141 + 860625*uk_142 + 65025*uk_143 + 4913*uk_144 + 64000*uk_145 + 164800*uk_146 + 360000*uk_147 + 27200*uk_148 + 424360*uk_149 + 11411775*uk_15 + 927000*uk_150 + 70040*uk_151 + 2025000*uk_152 + 153000*uk_153 + 11560*uk_154 + 1092727*uk_155 + 2387025*uk_156 + 180353*uk_157 + 5214375*uk_158 + 393975*uk_159 + 862223*uk_16 + 29767*uk_160 + 11390625*uk_161 + 860625*uk_162 + 65025*uk_163 + 4913*uk_164 + 3025*uk_17 + 2365*uk_18 + 935*uk_19 + 55*uk_2 + 2200*uk_20 + 5665*uk_21 + 12375*uk_22 + 935*uk_23 + 1849*uk_24 + 731*uk_25 + 1720*uk_26 + 4429*uk_27 + 9675*uk_28 + 731*uk_29 + 43*uk_3 + 289*uk_30 + 680*uk_31 + 1751*uk_32 + 3825*uk_33 + 289*uk_34 + 1600*uk_35 + 4120*uk_36 + 9000*uk_37 + 680*uk_38 + 10609*uk_39 + 17*uk_4 + 23175*uk_40 + 1751*uk_41 + 50625*uk_42 + 3825*uk_43 + 289*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 110613929323*uk_47 + 43731088337*uk_48 + 102896678440*uk_49 + 40*uk_5 + 264958946983*uk_50 + 578793816225*uk_51 + 43731088337*uk_52 + 153424975*uk_53 + 119950435*uk_54 + 47422265*uk_55 + 111581800*uk_56 + 287323135*uk_57 + 627647625*uk_58 + 47422265*uk_59 + 103*uk_6 + 93779431*uk_60 + 37075589*uk_61 + 87236680*uk_62 + 224634451*uk_63 + 490706325*uk_64 + 37075589*uk_65 + 14657791*uk_66 + 34488920*uk_67 + 88808969*uk_68 + 194000175*uk_69 + 225*uk_7 + 14657791*uk_70 + 81150400*uk_71 + 208962280*uk_72 + 456471000*uk_73 + 34488920*uk_74 + 538077871*uk_75 + 1175412825*uk_76 + 88808969*uk_77 + 2567649375*uk_78 + 194000175*uk_79 + 17*uk_8 + 14657791*uk_80 + 166375*uk_81 + 130075*uk_82 + 51425*uk_83 + 121000*uk_84 + 311575*uk_85 + 680625*uk_86 + 51425*uk_87 + 101695*uk_88 + 40205*uk_89 + 2572416961*uk_9 + 94600*uk_90 + 243595*uk_91 + 532125*uk_92 + 40205*uk_93 + 15895*uk_94 + 37400*uk_95 + 96305*uk_96 + 210375*uk_97 + 15895*uk_98 + 88000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 207900*uk_100 + 445500*uk_101 + 85140*uk_102 + 606375*uk_103 + 1299375*uk_104 + 248325*uk_105 + 2784375*uk_106 + 532125*uk_107 + 101695*uk_108 + 64*uk_109 + 202876*uk_11 + 688*uk_110 + 576*uk_111 + 1680*uk_112 + 3600*uk_113 + 688*uk_114 + 7396*uk_115 + 6192*uk_116 + 18060*uk_117 + 38700*uk_118 + 7396*uk_119 + 2180917*uk_12 + 5184*uk_120 + 15120*uk_121 + 32400*uk_122 + 6192*uk_123 + 44100*uk_124 + 94500*uk_125 + 18060*uk_126 + 202500*uk_127 + 38700*uk_128 + 7396*uk_129 + 1825884*uk_13 + 79507*uk_130 + 66564*uk_131 + 194145*uk_132 + 416025*uk_133 + 79507*uk_134 + 55728*uk_135 + 162540*uk_136 + 348300*uk_137 + 66564*uk_138 + 474075*uk_139 + 5325495*uk_14 + 1015875*uk_140 + 194145*uk_141 + 2176875*uk_142 + 416025*uk_143 + 79507*uk_144 + 46656*uk_145 + 136080*uk_146 + 291600*uk_147 + 55728*uk_148 + 396900*uk_149 + 11411775*uk_15 + 850500*uk_150 + 162540*uk_151 + 1822500*uk_152 + 348300*uk_153 + 66564*uk_154 + 1157625*uk_155 + 2480625*uk_156 + 474075*uk_157 + 5315625*uk_158 + 1015875*uk_159 + 2180917*uk_16 + 194145*uk_160 + 11390625*uk_161 + 2176875*uk_162 + 416025*uk_163 + 79507*uk_164 + 3025*uk_17 + 220*uk_18 + 2365*uk_19 + 55*uk_2 + 1980*uk_20 + 5775*uk_21 + 12375*uk_22 + 2365*uk_23 + 16*uk_24 + 172*uk_25 + 144*uk_26 + 420*uk_27 + 900*uk_28 + 172*uk_29 + 4*uk_3 + 1849*uk_30 + 1548*uk_31 + 4515*uk_32 + 9675*uk_33 + 1849*uk_34 + 1296*uk_35 + 3780*uk_36 + 8100*uk_37 + 1548*uk_38 + 11025*uk_39 + 43*uk_4 + 23625*uk_40 + 4515*uk_41 + 50625*uk_42 + 9675*uk_43 + 1849*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 10289667844*uk_47 + 110613929323*uk_48 + 92607010596*uk_49 + 36*uk_5 + 270103780905*uk_50 + 578793816225*uk_51 + 110613929323*uk_52 + 153424975*uk_53 + 11158180*uk_54 + 119950435*uk_55 + 100423620*uk_56 + 292902225*uk_57 + 627647625*uk_58 + 119950435*uk_59 + 105*uk_6 + 811504*uk_60 + 8723668*uk_61 + 7303536*uk_62 + 21301980*uk_63 + 45647100*uk_64 + 8723668*uk_65 + 93779431*uk_66 + 78513012*uk_67 + 228996285*uk_68 + 490706325*uk_69 + 225*uk_7 + 93779431*uk_70 + 65731824*uk_71 + 191717820*uk_72 + 410823900*uk_73 + 78513012*uk_74 + 559176975*uk_75 + 1198236375*uk_76 + 228996285*uk_77 + 2567649375*uk_78 + 490706325*uk_79 + 43*uk_8 + 93779431*uk_80 + 166375*uk_81 + 12100*uk_82 + 130075*uk_83 + 108900*uk_84 + 317625*uk_85 + 680625*uk_86 + 130075*uk_87 + 880*uk_88 + 9460*uk_89 + 2572416961*uk_9 + 7920*uk_90 + 23100*uk_91 + 49500*uk_92 + 9460*uk_93 + 101695*uk_94 + 85140*uk_95 + 248325*uk_96 + 532125*uk_97 + 101695*uk_98 + 71280*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 211860*uk_100 + 445500*uk_101 + 7920*uk_102 + 629695*uk_103 + 1324125*uk_104 + 23540*uk_105 + 2784375*uk_106 + 49500*uk_107 + 880*uk_108 + uk_109 + 50719*uk_11 + 4*uk_110 + 36*uk_111 + 107*uk_112 + 225*uk_113 + 4*uk_114 + 16*uk_115 + 144*uk_116 + 428*uk_117 + 900*uk_118 + 16*uk_119 + 202876*uk_12 + 1296*uk_120 + 3852*uk_121 + 8100*uk_122 + 144*uk_123 + 11449*uk_124 + 24075*uk_125 + 428*uk_126 + 50625*uk_127 + 900*uk_128 + 16*uk_129 + 1825884*uk_13 + 64*uk_130 + 576*uk_131 + 1712*uk_132 + 3600*uk_133 + 64*uk_134 + 5184*uk_135 + 15408*uk_136 + 32400*uk_137 + 576*uk_138 + 45796*uk_139 + 5426933*uk_14 + 96300*uk_140 + 1712*uk_141 + 202500*uk_142 + 3600*uk_143 + 64*uk_144 + 46656*uk_145 + 138672*uk_146 + 291600*uk_147 + 5184*uk_148 + 412164*uk_149 + 11411775*uk_15 + 866700*uk_150 + 15408*uk_151 + 1822500*uk_152 + 32400*uk_153 + 576*uk_154 + 1225043*uk_155 + 2576025*uk_156 + 45796*uk_157 + 5416875*uk_158 + 96300*uk_159 + 202876*uk_16 + 1712*uk_160 + 11390625*uk_161 + 202500*uk_162 + 3600*uk_163 + 64*uk_164 + 3025*uk_17 + 55*uk_18 + 220*uk_19 + 55*uk_2 + 1980*uk_20 + 5885*uk_21 + 12375*uk_22 + 220*uk_23 + uk_24 + 4*uk_25 + 36*uk_26 + 107*uk_27 + 225*uk_28 + 4*uk_29 + uk_3 + 16*uk_30 + 144*uk_31 + 428*uk_32 + 900*uk_33 + 16*uk_34 + 1296*uk_35 + 3852*uk_36 + 8100*uk_37 + 144*uk_38 + 11449*uk_39 + 4*uk_4 + 24075*uk_40 + 428*uk_41 + 50625*uk_42 + 900*uk_43 + 16*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 2572416961*uk_47 + 10289667844*uk_48 + 92607010596*uk_49 + 36*uk_5 + 275248614827*uk_50 + 578793816225*uk_51 + 10289667844*uk_52 + 153424975*uk_53 + 2789545*uk_54 + 11158180*uk_55 + 100423620*uk_56 + 298481315*uk_57 + 627647625*uk_58 + 11158180*uk_59 + 107*uk_6 + 50719*uk_60 + 202876*uk_61 + 1825884*uk_62 + 5426933*uk_63 + 11411775*uk_64 + 202876*uk_65 + 811504*uk_66 + 7303536*uk_67 + 21707732*uk_68 + 45647100*uk_69 + 225*uk_7 + 811504*uk_70 + 65731824*uk_71 + 195369588*uk_72 + 410823900*uk_73 + 7303536*uk_74 + 580681831*uk_75 + 1221059925*uk_76 + 21707732*uk_77 + 2567649375*uk_78 + 45647100*uk_79 + 4*uk_8 + 811504*uk_80 + 166375*uk_81 + 3025*uk_82 + 12100*uk_83 + 108900*uk_84 + 323675*uk_85 + 680625*uk_86 + 12100*uk_87 + 55*uk_88 + 220*uk_89 + 2572416961*uk_9 + 1980*uk_90 + 5885*uk_91 + 12375*uk_92 + 220*uk_93 + 880*uk_94 + 7920*uk_95 + 23540*uk_96 + 49500*uk_97 + 880*uk_98 + 71280*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 215820*uk_100 + 445500*uk_101 + 1980*uk_102 + 653455*uk_103 + 1348875*uk_104 + 5995*uk_105 + 2784375*uk_106 + 12375*uk_107 + 55*uk_108 + 39304*uk_109 + 1724446*uk_11 + 1156*uk_110 + 41616*uk_111 + 126004*uk_112 + 260100*uk_113 + 1156*uk_114 + 34*uk_115 + 1224*uk_116 + 3706*uk_117 + 7650*uk_118 + 34*uk_119 + 50719*uk_12 + 44064*uk_120 + 133416*uk_121 + 275400*uk_122 + 1224*uk_123 + 403954*uk_124 + 833850*uk_125 + 3706*uk_126 + 1721250*uk_127 + 7650*uk_128 + 34*uk_129 + 1825884*uk_13 + uk_130 + 36*uk_131 + 109*uk_132 + 225*uk_133 + uk_134 + 1296*uk_135 + 3924*uk_136 + 8100*uk_137 + 36*uk_138 + 11881*uk_139 + 5528371*uk_14 + 24525*uk_140 + 109*uk_141 + 50625*uk_142 + 225*uk_143 + uk_144 + 46656*uk_145 + 141264*uk_146 + 291600*uk_147 + 1296*uk_148 + 427716*uk_149 + 11411775*uk_15 + 882900*uk_150 + 3924*uk_151 + 1822500*uk_152 + 8100*uk_153 + 36*uk_154 + 1295029*uk_155 + 2673225*uk_156 + 11881*uk_157 + 5518125*uk_158 + 24525*uk_159 + 50719*uk_16 + 109*uk_160 + 11390625*uk_161 + 50625*uk_162 + 225*uk_163 + uk_164 + 3025*uk_17 + 1870*uk_18 + 55*uk_19 + 55*uk_2 + 1980*uk_20 + 5995*uk_21 + 12375*uk_22 + 55*uk_23 + 1156*uk_24 + 34*uk_25 + 1224*uk_26 + 3706*uk_27 + 7650*uk_28 + 34*uk_29 + 34*uk_3 + uk_30 + 36*uk_31 + 109*uk_32 + 225*uk_33 + uk_34 + 1296*uk_35 + 3924*uk_36 + 8100*uk_37 + 36*uk_38 + 11881*uk_39 + uk_4 + 24525*uk_40 + 109*uk_41 + 50625*uk_42 + 225*uk_43 + uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 87462176674*uk_47 + 2572416961*uk_48 + 92607010596*uk_49 + 36*uk_5 + 280393448749*uk_50 + 578793816225*uk_51 + 2572416961*uk_52 + 153424975*uk_53 + 94844530*uk_54 + 2789545*uk_55 + 100423620*uk_56 + 304060405*uk_57 + 627647625*uk_58 + 2789545*uk_59 + 109*uk_6 + 58631164*uk_60 + 1724446*uk_61 + 62080056*uk_62 + 187964614*uk_63 + 388000350*uk_64 + 1724446*uk_65 + 50719*uk_66 + 1825884*uk_67 + 5528371*uk_68 + 11411775*uk_69 + 225*uk_7 + 50719*uk_70 + 65731824*uk_71 + 199021356*uk_72 + 410823900*uk_73 + 1825884*uk_74 + 602592439*uk_75 + 1243883475*uk_76 + 5528371*uk_77 + 2567649375*uk_78 + 11411775*uk_79 + uk_8 + 50719*uk_80 + 166375*uk_81 + 102850*uk_82 + 3025*uk_83 + 108900*uk_84 + 329725*uk_85 + 680625*uk_86 + 3025*uk_87 + 63580*uk_88 + 1870*uk_89 + 2572416961*uk_9 + 67320*uk_90 + 203830*uk_91 + 420750*uk_92 + 1870*uk_93 + 55*uk_94 + 1980*uk_95 + 5995*uk_96 + 12375*uk_97 + 55*uk_98 + 71280*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 219780*uk_100 + 445500*uk_101 + 67320*uk_102 + 677655*uk_103 + 1373625*uk_104 + 207570*uk_105 + 2784375*uk_106 + 420750*uk_107 + 63580*uk_108 + 1092727*uk_109 + 5224057*uk_11 + 360706*uk_110 + 381924*uk_111 + 1177599*uk_112 + 2387025*uk_113 + 360706*uk_114 + 119068*uk_115 + 126072*uk_116 + 388722*uk_117 + 787950*uk_118 + 119068*uk_119 + 1724446*uk_12 + 133488*uk_120 + 411588*uk_121 + 834300*uk_122 + 126072*uk_123 + 1269063*uk_124 + 2572425*uk_125 + 388722*uk_126 + 5214375*uk_127 + 787950*uk_128 + 119068*uk_129 + 1825884*uk_13 + 39304*uk_130 + 41616*uk_131 + 128316*uk_132 + 260100*uk_133 + 39304*uk_134 + 44064*uk_135 + 135864*uk_136 + 275400*uk_137 + 41616*uk_138 + 418914*uk_139 + 5629809*uk_14 + 849150*uk_140 + 128316*uk_141 + 1721250*uk_142 + 260100*uk_143 + 39304*uk_144 + 46656*uk_145 + 143856*uk_146 + 291600*uk_147 + 44064*uk_148 + 443556*uk_149 + 11411775*uk_15 + 899100*uk_150 + 135864*uk_151 + 1822500*uk_152 + 275400*uk_153 + 41616*uk_154 + 1367631*uk_155 + 2772225*uk_156 + 418914*uk_157 + 5619375*uk_158 + 849150*uk_159 + 1724446*uk_16 + 128316*uk_160 + 11390625*uk_161 + 1721250*uk_162 + 260100*uk_163 + 39304*uk_164 + 3025*uk_17 + 5665*uk_18 + 1870*uk_19 + 55*uk_2 + 1980*uk_20 + 6105*uk_21 + 12375*uk_22 + 1870*uk_23 + 10609*uk_24 + 3502*uk_25 + 3708*uk_26 + 11433*uk_27 + 23175*uk_28 + 3502*uk_29 + 103*uk_3 + 1156*uk_30 + 1224*uk_31 + 3774*uk_32 + 7650*uk_33 + 1156*uk_34 + 1296*uk_35 + 3996*uk_36 + 8100*uk_37 + 1224*uk_38 + 12321*uk_39 + 34*uk_4 + 24975*uk_40 + 3774*uk_41 + 50625*uk_42 + 7650*uk_43 + 1156*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 264958946983*uk_47 + 87462176674*uk_48 + 92607010596*uk_49 + 36*uk_5 + 285538282671*uk_50 + 578793816225*uk_51 + 87462176674*uk_52 + 153424975*uk_53 + 287323135*uk_54 + 94844530*uk_55 + 100423620*uk_56 + 309639495*uk_57 + 627647625*uk_58 + 94844530*uk_59 + 111*uk_6 + 538077871*uk_60 + 177617938*uk_61 + 188066052*uk_62 + 579870327*uk_63 + 1175412825*uk_64 + 177617938*uk_65 + 58631164*uk_66 + 62080056*uk_67 + 191413506*uk_68 + 388000350*uk_69 + 225*uk_7 + 58631164*uk_70 + 65731824*uk_71 + 202673124*uk_72 + 410823900*uk_73 + 62080056*uk_74 + 624908799*uk_75 + 1266707025*uk_76 + 191413506*uk_77 + 2567649375*uk_78 + 388000350*uk_79 + 34*uk_8 + 58631164*uk_80 + 166375*uk_81 + 311575*uk_82 + 102850*uk_83 + 108900*uk_84 + 335775*uk_85 + 680625*uk_86 + 102850*uk_87 + 583495*uk_88 + 192610*uk_89 + 2572416961*uk_9 + 203940*uk_90 + 628815*uk_91 + 1274625*uk_92 + 192610*uk_93 + 63580*uk_94 + 67320*uk_95 + 207570*uk_96 + 420750*uk_97 + 63580*uk_98 + 71280*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 198880*uk_100 + 396000*uk_101 + 181280*uk_102 + 702295*uk_103 + 1398375*uk_104 + 640145*uk_105 + 2784375*uk_106 + 1274625*uk_107 + 583495*uk_108 + 857375*uk_109 + 4818305*uk_11 + 929575*uk_110 + 288800*uk_111 + 1019825*uk_112 + 2030625*uk_113 + 929575*uk_114 + 1007855*uk_115 + 313120*uk_116 + 1105705*uk_117 + 2201625*uk_118 + 1007855*uk_119 + 5224057*uk_12 + 97280*uk_120 + 343520*uk_121 + 684000*uk_122 + 313120*uk_123 + 1213055*uk_124 + 2415375*uk_125 + 1105705*uk_126 + 4809375*uk_127 + 2201625*uk_128 + 1007855*uk_129 + 1623008*uk_13 + 1092727*uk_130 + 339488*uk_131 + 1198817*uk_132 + 2387025*uk_133 + 1092727*uk_134 + 105472*uk_135 + 372448*uk_136 + 741600*uk_137 + 339488*uk_138 + 1315207*uk_139 + 5731247*uk_14 + 2618775*uk_140 + 1198817*uk_141 + 5214375*uk_142 + 2387025*uk_143 + 1092727*uk_144 + 32768*uk_145 + 115712*uk_146 + 230400*uk_147 + 105472*uk_148 + 408608*uk_149 + 11411775*uk_15 + 813600*uk_150 + 372448*uk_151 + 1620000*uk_152 + 741600*uk_153 + 339488*uk_154 + 1442897*uk_155 + 2873025*uk_156 + 1315207*uk_157 + 5720625*uk_158 + 2618775*uk_159 + 5224057*uk_16 + 1198817*uk_160 + 11390625*uk_161 + 5214375*uk_162 + 2387025*uk_163 + 1092727*uk_164 + 3025*uk_17 + 5225*uk_18 + 5665*uk_19 + 55*uk_2 + 1760*uk_20 + 6215*uk_21 + 12375*uk_22 + 5665*uk_23 + 9025*uk_24 + 9785*uk_25 + 3040*uk_26 + 10735*uk_27 + 21375*uk_28 + 9785*uk_29 + 95*uk_3 + 10609*uk_30 + 3296*uk_31 + 11639*uk_32 + 23175*uk_33 + 10609*uk_34 + 1024*uk_35 + 3616*uk_36 + 7200*uk_37 + 3296*uk_38 + 12769*uk_39 + 103*uk_4 + 25425*uk_40 + 11639*uk_41 + 50625*uk_42 + 23175*uk_43 + 10609*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 244379611295*uk_47 + 264958946983*uk_48 + 82317342752*uk_49 + 32*uk_5 + 290683116593*uk_50 + 578793816225*uk_51 + 264958946983*uk_52 + 153424975*uk_53 + 265006775*uk_54 + 287323135*uk_55 + 89265440*uk_56 + 315218585*uk_57 + 627647625*uk_58 + 287323135*uk_59 + 113*uk_6 + 457738975*uk_60 + 496285415*uk_61 + 154185760*uk_62 + 544468465*uk_63 + 1084118625*uk_64 + 496285415*uk_65 + 538077871*uk_66 + 167169824*uk_67 + 590318441*uk_68 + 1175412825*uk_69 + 225*uk_7 + 538077871*uk_70 + 51936256*uk_71 + 183399904*uk_72 + 365176800*uk_73 + 167169824*uk_74 + 647630911*uk_75 + 1289530575*uk_76 + 590318441*uk_77 + 2567649375*uk_78 + 1175412825*uk_79 + 103*uk_8 + 538077871*uk_80 + 166375*uk_81 + 287375*uk_82 + 311575*uk_83 + 96800*uk_84 + 341825*uk_85 + 680625*uk_86 + 311575*uk_87 + 496375*uk_88 + 538175*uk_89 + 2572416961*uk_9 + 167200*uk_90 + 590425*uk_91 + 1175625*uk_92 + 538175*uk_93 + 583495*uk_94 + 181280*uk_95 + 640145*uk_96 + 1274625*uk_97 + 583495*uk_98 + 56320*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 177100*uk_100 + 346500*uk_101 + 146300*uk_102 + 727375*uk_103 + 1423125*uk_104 + 600875*uk_105 + 2784375*uk_106 + 1175625*uk_107 + 496375*uk_108 + 64*uk_109 + 202876*uk_11 + 1520*uk_110 + 448*uk_111 + 1840*uk_112 + 3600*uk_113 + 1520*uk_114 + 36100*uk_115 + 10640*uk_116 + 43700*uk_117 + 85500*uk_118 + 36100*uk_119 + 4818305*uk_12 + 3136*uk_120 + 12880*uk_121 + 25200*uk_122 + 10640*uk_123 + 52900*uk_124 + 103500*uk_125 + 43700*uk_126 + 202500*uk_127 + 85500*uk_128 + 36100*uk_129 + 1420132*uk_13 + 857375*uk_130 + 252700*uk_131 + 1037875*uk_132 + 2030625*uk_133 + 857375*uk_134 + 74480*uk_135 + 305900*uk_136 + 598500*uk_137 + 252700*uk_138 + 1256375*uk_139 + 5832685*uk_14 + 2458125*uk_140 + 1037875*uk_141 + 4809375*uk_142 + 2030625*uk_143 + 857375*uk_144 + 21952*uk_145 + 90160*uk_146 + 176400*uk_147 + 74480*uk_148 + 370300*uk_149 + 11411775*uk_15 + 724500*uk_150 + 305900*uk_151 + 1417500*uk_152 + 598500*uk_153 + 252700*uk_154 + 1520875*uk_155 + 2975625*uk_156 + 1256375*uk_157 + 5821875*uk_158 + 2458125*uk_159 + 4818305*uk_16 + 1037875*uk_160 + 11390625*uk_161 + 4809375*uk_162 + 2030625*uk_163 + 857375*uk_164 + 3025*uk_17 + 220*uk_18 + 5225*uk_19 + 55*uk_2 + 1540*uk_20 + 6325*uk_21 + 12375*uk_22 + 5225*uk_23 + 16*uk_24 + 380*uk_25 + 112*uk_26 + 460*uk_27 + 900*uk_28 + 380*uk_29 + 4*uk_3 + 9025*uk_30 + 2660*uk_31 + 10925*uk_32 + 21375*uk_33 + 9025*uk_34 + 784*uk_35 + 3220*uk_36 + 6300*uk_37 + 2660*uk_38 + 13225*uk_39 + 95*uk_4 + 25875*uk_40 + 10925*uk_41 + 50625*uk_42 + 21375*uk_43 + 9025*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 10289667844*uk_47 + 244379611295*uk_48 + 72027674908*uk_49 + 28*uk_5 + 295827950515*uk_50 + 578793816225*uk_51 + 244379611295*uk_52 + 153424975*uk_53 + 11158180*uk_54 + 265006775*uk_55 + 78107260*uk_56 + 320797675*uk_57 + 627647625*uk_58 + 265006775*uk_59 + 115*uk_6 + 811504*uk_60 + 19273220*uk_61 + 5680528*uk_62 + 23330740*uk_63 + 45647100*uk_64 + 19273220*uk_65 + 457738975*uk_66 + 134912540*uk_67 + 554105075*uk_68 + 1084118625*uk_69 + 225*uk_7 + 457738975*uk_70 + 39763696*uk_71 + 163315180*uk_72 + 319529700*uk_73 + 134912540*uk_74 + 670758775*uk_75 + 1312354125*uk_76 + 554105075*uk_77 + 2567649375*uk_78 + 1084118625*uk_79 + 95*uk_8 + 457738975*uk_80 + 166375*uk_81 + 12100*uk_82 + 287375*uk_83 + 84700*uk_84 + 347875*uk_85 + 680625*uk_86 + 287375*uk_87 + 880*uk_88 + 20900*uk_89 + 2572416961*uk_9 + 6160*uk_90 + 25300*uk_91 + 49500*uk_92 + 20900*uk_93 + 496375*uk_94 + 146300*uk_95 + 600875*uk_96 + 1175625*uk_97 + 496375*uk_98 + 43120*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 205920*uk_100 + 396000*uk_101 + 7040*uk_102 + 752895*uk_103 + 1447875*uk_104 + 25740*uk_105 + 2784375*uk_106 + 49500*uk_107 + 880*uk_108 + 195112*uk_109 + 2941702*uk_11 + 13456*uk_110 + 107648*uk_111 + 393588*uk_112 + 756900*uk_113 + 13456*uk_114 + 928*uk_115 + 7424*uk_116 + 27144*uk_117 + 52200*uk_118 + 928*uk_119 + 202876*uk_12 + 59392*uk_120 + 217152*uk_121 + 417600*uk_122 + 7424*uk_123 + 793962*uk_124 + 1526850*uk_125 + 27144*uk_126 + 2936250*uk_127 + 52200*uk_128 + 928*uk_129 + 1623008*uk_13 + 64*uk_130 + 512*uk_131 + 1872*uk_132 + 3600*uk_133 + 64*uk_134 + 4096*uk_135 + 14976*uk_136 + 28800*uk_137 + 512*uk_138 + 54756*uk_139 + 5934123*uk_14 + 105300*uk_140 + 1872*uk_141 + 202500*uk_142 + 3600*uk_143 + 64*uk_144 + 32768*uk_145 + 119808*uk_146 + 230400*uk_147 + 4096*uk_148 + 438048*uk_149 + 11411775*uk_15 + 842400*uk_150 + 14976*uk_151 + 1620000*uk_152 + 28800*uk_153 + 512*uk_154 + 1601613*uk_155 + 3080025*uk_156 + 54756*uk_157 + 5923125*uk_158 + 105300*uk_159 + 202876*uk_16 + 1872*uk_160 + 11390625*uk_161 + 202500*uk_162 + 3600*uk_163 + 64*uk_164 + 3025*uk_17 + 3190*uk_18 + 220*uk_19 + 55*uk_2 + 1760*uk_20 + 6435*uk_21 + 12375*uk_22 + 220*uk_23 + 3364*uk_24 + 232*uk_25 + 1856*uk_26 + 6786*uk_27 + 13050*uk_28 + 232*uk_29 + 58*uk_3 + 16*uk_30 + 128*uk_31 + 468*uk_32 + 900*uk_33 + 16*uk_34 + 1024*uk_35 + 3744*uk_36 + 7200*uk_37 + 128*uk_38 + 13689*uk_39 + 4*uk_4 + 26325*uk_40 + 468*uk_41 + 50625*uk_42 + 900*uk_43 + 16*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 149200183738*uk_47 + 10289667844*uk_48 + 82317342752*uk_49 + 32*uk_5 + 300972784437*uk_50 + 578793816225*uk_51 + 10289667844*uk_52 + 153424975*uk_53 + 161793610*uk_54 + 11158180*uk_55 + 89265440*uk_56 + 326376765*uk_57 + 627647625*uk_58 + 11158180*uk_59 + 117*uk_6 + 170618716*uk_60 + 11766808*uk_61 + 94134464*uk_62 + 344179134*uk_63 + 661882950*uk_64 + 11766808*uk_65 + 811504*uk_66 + 6492032*uk_67 + 23736492*uk_68 + 45647100*uk_69 + 225*uk_7 + 811504*uk_70 + 51936256*uk_71 + 189891936*uk_72 + 365176800*uk_73 + 6492032*uk_74 + 694292391*uk_75 + 1335177675*uk_76 + 23736492*uk_77 + 2567649375*uk_78 + 45647100*uk_79 + 4*uk_8 + 811504*uk_80 + 166375*uk_81 + 175450*uk_82 + 12100*uk_83 + 96800*uk_84 + 353925*uk_85 + 680625*uk_86 + 12100*uk_87 + 185020*uk_88 + 12760*uk_89 + 2572416961*uk_9 + 102080*uk_90 + 373230*uk_91 + 717750*uk_92 + 12760*uk_93 + 880*uk_94 + 7040*uk_95 + 25740*uk_96 + 49500*uk_97 + 880*uk_98 + 56320*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 183260*uk_100 + 346500*uk_101 + 89320*uk_102 + 778855*uk_103 + 1472625*uk_104 + 379610*uk_105 + 2784375*uk_106 + 717750*uk_107 + 185020*uk_108 + 15625*uk_109 + 1267975*uk_11 + 36250*uk_110 + 17500*uk_111 + 74375*uk_112 + 140625*uk_113 + 36250*uk_114 + 84100*uk_115 + 40600*uk_116 + 172550*uk_117 + 326250*uk_118 + 84100*uk_119 + 2941702*uk_12 + 19600*uk_120 + 83300*uk_121 + 157500*uk_122 + 40600*uk_123 + 354025*uk_124 + 669375*uk_125 + 172550*uk_126 + 1265625*uk_127 + 326250*uk_128 + 84100*uk_129 + 1420132*uk_13 + 195112*uk_130 + 94192*uk_131 + 400316*uk_132 + 756900*uk_133 + 195112*uk_134 + 45472*uk_135 + 193256*uk_136 + 365400*uk_137 + 94192*uk_138 + 821338*uk_139 + 6035561*uk_14 + 1552950*uk_140 + 400316*uk_141 + 2936250*uk_142 + 756900*uk_143 + 195112*uk_144 + 21952*uk_145 + 93296*uk_146 + 176400*uk_147 + 45472*uk_148 + 396508*uk_149 + 11411775*uk_15 + 749700*uk_150 + 193256*uk_151 + 1417500*uk_152 + 365400*uk_153 + 94192*uk_154 + 1685159*uk_155 + 3186225*uk_156 + 821338*uk_157 + 6024375*uk_158 + 1552950*uk_159 + 2941702*uk_16 + 400316*uk_160 + 11390625*uk_161 + 2936250*uk_162 + 756900*uk_163 + 195112*uk_164 + 3025*uk_17 + 1375*uk_18 + 3190*uk_19 + 55*uk_2 + 1540*uk_20 + 6545*uk_21 + 12375*uk_22 + 3190*uk_23 + 625*uk_24 + 1450*uk_25 + 700*uk_26 + 2975*uk_27 + 5625*uk_28 + 1450*uk_29 + 25*uk_3 + 3364*uk_30 + 1624*uk_31 + 6902*uk_32 + 13050*uk_33 + 3364*uk_34 + 784*uk_35 + 3332*uk_36 + 6300*uk_37 + 1624*uk_38 + 14161*uk_39 + 58*uk_4 + 26775*uk_40 + 6902*uk_41 + 50625*uk_42 + 13050*uk_43 + 3364*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 64310424025*uk_47 + 149200183738*uk_48 + 72027674908*uk_49 + 28*uk_5 + 306117618359*uk_50 + 578793816225*uk_51 + 149200183738*uk_52 + 153424975*uk_53 + 69738625*uk_54 + 161793610*uk_55 + 78107260*uk_56 + 331955855*uk_57 + 627647625*uk_58 + 161793610*uk_59 + 119*uk_6 + 31699375*uk_60 + 73542550*uk_61 + 35503300*uk_62 + 150889025*uk_63 + 285294375*uk_64 + 73542550*uk_65 + 170618716*uk_66 + 82367656*uk_67 + 350062538*uk_68 + 661882950*uk_69 + 225*uk_7 + 170618716*uk_70 + 39763696*uk_71 + 168995708*uk_72 + 319529700*uk_73 + 82367656*uk_74 + 718231759*uk_75 + 1358001225*uk_76 + 350062538*uk_77 + 2567649375*uk_78 + 661882950*uk_79 + 58*uk_8 + 170618716*uk_80 + 166375*uk_81 + 75625*uk_82 + 175450*uk_83 + 84700*uk_84 + 359975*uk_85 + 680625*uk_86 + 175450*uk_87 + 34375*uk_88 + 79750*uk_89 + 2572416961*uk_9 + 38500*uk_90 + 163625*uk_91 + 309375*uk_92 + 79750*uk_93 + 185020*uk_94 + 89320*uk_95 + 379610*uk_96 + 717750*uk_97 + 185020*uk_98 + 43120*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 186340*uk_100 + 346500*uk_101 + 38500*uk_102 + 805255*uk_103 + 1497375*uk_104 + 166375*uk_105 + 2784375*uk_106 + 309375*uk_107 + 34375*uk_108 + 8000*uk_109 + 1014380*uk_11 + 10000*uk_110 + 11200*uk_111 + 48400*uk_112 + 90000*uk_113 + 10000*uk_114 + 12500*uk_115 + 14000*uk_116 + 60500*uk_117 + 112500*uk_118 + 12500*uk_119 + 1267975*uk_12 + 15680*uk_120 + 67760*uk_121 + 126000*uk_122 + 14000*uk_123 + 292820*uk_124 + 544500*uk_125 + 60500*uk_126 + 1012500*uk_127 + 112500*uk_128 + 12500*uk_129 + 1420132*uk_13 + 15625*uk_130 + 17500*uk_131 + 75625*uk_132 + 140625*uk_133 + 15625*uk_134 + 19600*uk_135 + 84700*uk_136 + 157500*uk_137 + 17500*uk_138 + 366025*uk_139 + 6136999*uk_14 + 680625*uk_140 + 75625*uk_141 + 1265625*uk_142 + 140625*uk_143 + 15625*uk_144 + 21952*uk_145 + 94864*uk_146 + 176400*uk_147 + 19600*uk_148 + 409948*uk_149 + 11411775*uk_15 + 762300*uk_150 + 84700*uk_151 + 1417500*uk_152 + 157500*uk_153 + 17500*uk_154 + 1771561*uk_155 + 3294225*uk_156 + 366025*uk_157 + 6125625*uk_158 + 680625*uk_159 + 1267975*uk_16 + 75625*uk_160 + 11390625*uk_161 + 1265625*uk_162 + 140625*uk_163 + 15625*uk_164 + 3025*uk_17 + 1100*uk_18 + 1375*uk_19 + 55*uk_2 + 1540*uk_20 + 6655*uk_21 + 12375*uk_22 + 1375*uk_23 + 400*uk_24 + 500*uk_25 + 560*uk_26 + 2420*uk_27 + 4500*uk_28 + 500*uk_29 + 20*uk_3 + 625*uk_30 + 700*uk_31 + 3025*uk_32 + 5625*uk_33 + 625*uk_34 + 784*uk_35 + 3388*uk_36 + 6300*uk_37 + 700*uk_38 + 14641*uk_39 + 25*uk_4 + 27225*uk_40 + 3025*uk_41 + 50625*uk_42 + 5625*uk_43 + 625*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 51448339220*uk_47 + 64310424025*uk_48 + 72027674908*uk_49 + 28*uk_5 + 311262452281*uk_50 + 578793816225*uk_51 + 64310424025*uk_52 + 153424975*uk_53 + 55790900*uk_54 + 69738625*uk_55 + 78107260*uk_56 + 337534945*uk_57 + 627647625*uk_58 + 69738625*uk_59 + 121*uk_6 + 20287600*uk_60 + 25359500*uk_61 + 28402640*uk_62 + 122739980*uk_63 + 228235500*uk_64 + 25359500*uk_65 + 31699375*uk_66 + 35503300*uk_67 + 153424975*uk_68 + 285294375*uk_69 + 225*uk_7 + 31699375*uk_70 + 39763696*uk_71 + 171835972*uk_72 + 319529700*uk_73 + 35503300*uk_74 + 742576879*uk_75 + 1380824775*uk_76 + 153424975*uk_77 + 2567649375*uk_78 + 285294375*uk_79 + 25*uk_8 + 31699375*uk_80 + 166375*uk_81 + 60500*uk_82 + 75625*uk_83 + 84700*uk_84 + 366025*uk_85 + 680625*uk_86 + 75625*uk_87 + 22000*uk_88 + 27500*uk_89 + 2572416961*uk_9 + 30800*uk_90 + 133100*uk_91 + 247500*uk_92 + 27500*uk_93 + 34375*uk_94 + 38500*uk_95 + 166375*uk_96 + 309375*uk_97 + 34375*uk_98 + 43120*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 189420*uk_100 + 346500*uk_101 + 30800*uk_102 + 832095*uk_103 + 1522125*uk_104 + 135300*uk_105 + 2784375*uk_106 + 247500*uk_107 + 22000*uk_108 + 79507*uk_109 + 2180917*uk_11 + 36980*uk_110 + 51772*uk_111 + 227427*uk_112 + 416025*uk_113 + 36980*uk_114 + 17200*uk_115 + 24080*uk_116 + 105780*uk_117 + 193500*uk_118 + 17200*uk_119 + 1014380*uk_12 + 33712*uk_120 + 148092*uk_121 + 270900*uk_122 + 24080*uk_123 + 650547*uk_124 + 1190025*uk_125 + 105780*uk_126 + 2176875*uk_127 + 193500*uk_128 + 17200*uk_129 + 1420132*uk_13 + 8000*uk_130 + 11200*uk_131 + 49200*uk_132 + 90000*uk_133 + 8000*uk_134 + 15680*uk_135 + 68880*uk_136 + 126000*uk_137 + 11200*uk_138 + 302580*uk_139 + 6238437*uk_14 + 553500*uk_140 + 49200*uk_141 + 1012500*uk_142 + 90000*uk_143 + 8000*uk_144 + 21952*uk_145 + 96432*uk_146 + 176400*uk_147 + 15680*uk_148 + 423612*uk_149 + 11411775*uk_15 + 774900*uk_150 + 68880*uk_151 + 1417500*uk_152 + 126000*uk_153 + 11200*uk_154 + 1860867*uk_155 + 3404025*uk_156 + 302580*uk_157 + 6226875*uk_158 + 553500*uk_159 + 1014380*uk_16 + 49200*uk_160 + 11390625*uk_161 + 1012500*uk_162 + 90000*uk_163 + 8000*uk_164 + 3025*uk_17 + 2365*uk_18 + 1100*uk_19 + 55*uk_2 + 1540*uk_20 + 6765*uk_21 + 12375*uk_22 + 1100*uk_23 + 1849*uk_24 + 860*uk_25 + 1204*uk_26 + 5289*uk_27 + 9675*uk_28 + 860*uk_29 + 43*uk_3 + 400*uk_30 + 560*uk_31 + 2460*uk_32 + 4500*uk_33 + 400*uk_34 + 784*uk_35 + 3444*uk_36 + 6300*uk_37 + 560*uk_38 + 15129*uk_39 + 20*uk_4 + 27675*uk_40 + 2460*uk_41 + 50625*uk_42 + 4500*uk_43 + 400*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 110613929323*uk_47 + 51448339220*uk_48 + 72027674908*uk_49 + 28*uk_5 + 316407286203*uk_50 + 578793816225*uk_51 + 51448339220*uk_52 + 153424975*uk_53 + 119950435*uk_54 + 55790900*uk_55 + 78107260*uk_56 + 343114035*uk_57 + 627647625*uk_58 + 55790900*uk_59 + 123*uk_6 + 93779431*uk_60 + 43618340*uk_61 + 61065676*uk_62 + 268252791*uk_63 + 490706325*uk_64 + 43618340*uk_65 + 20287600*uk_66 + 28402640*uk_67 + 124768740*uk_68 + 228235500*uk_69 + 225*uk_7 + 20287600*uk_70 + 39763696*uk_71 + 174676236*uk_72 + 319529700*uk_73 + 28402640*uk_74 + 767327751*uk_75 + 1403648325*uk_76 + 124768740*uk_77 + 2567649375*uk_78 + 228235500*uk_79 + 20*uk_8 + 20287600*uk_80 + 166375*uk_81 + 130075*uk_82 + 60500*uk_83 + 84700*uk_84 + 372075*uk_85 + 680625*uk_86 + 60500*uk_87 + 101695*uk_88 + 47300*uk_89 + 2572416961*uk_9 + 66220*uk_90 + 290895*uk_91 + 532125*uk_92 + 47300*uk_93 + 22000*uk_94 + 30800*uk_95 + 135300*uk_96 + 247500*uk_97 + 22000*uk_98 + 43120*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 192500*uk_100 + 346500*uk_101 + 66220*uk_102 + 859375*uk_103 + 1546875*uk_104 + 295625*uk_105 + 2784375*uk_106 + 532125*uk_107 + 101695*uk_108 + 830584*uk_109 + 4767586*uk_11 + 379948*uk_110 + 247408*uk_111 + 1104500*uk_112 + 1988100*uk_113 + 379948*uk_114 + 173806*uk_115 + 113176*uk_116 + 505250*uk_117 + 909450*uk_118 + 173806*uk_119 + 2180917*uk_12 + 73696*uk_120 + 329000*uk_121 + 592200*uk_122 + 113176*uk_123 + 1468750*uk_124 + 2643750*uk_125 + 505250*uk_126 + 4758750*uk_127 + 909450*uk_128 + 173806*uk_129 + 1420132*uk_13 + 79507*uk_130 + 51772*uk_131 + 231125*uk_132 + 416025*uk_133 + 79507*uk_134 + 33712*uk_135 + 150500*uk_136 + 270900*uk_137 + 51772*uk_138 + 671875*uk_139 + 6339875*uk_14 + 1209375*uk_140 + 231125*uk_141 + 2176875*uk_142 + 416025*uk_143 + 79507*uk_144 + 21952*uk_145 + 98000*uk_146 + 176400*uk_147 + 33712*uk_148 + 437500*uk_149 + 11411775*uk_15 + 787500*uk_150 + 150500*uk_151 + 1417500*uk_152 + 270900*uk_153 + 51772*uk_154 + 1953125*uk_155 + 3515625*uk_156 + 671875*uk_157 + 6328125*uk_158 + 1209375*uk_159 + 2180917*uk_16 + 231125*uk_160 + 11390625*uk_161 + 2176875*uk_162 + 416025*uk_163 + 79507*uk_164 + 3025*uk_17 + 5170*uk_18 + 2365*uk_19 + 55*uk_2 + 1540*uk_20 + 6875*uk_21 + 12375*uk_22 + 2365*uk_23 + 8836*uk_24 + 4042*uk_25 + 2632*uk_26 + 11750*uk_27 + 21150*uk_28 + 4042*uk_29 + 94*uk_3 + 1849*uk_30 + 1204*uk_31 + 5375*uk_32 + 9675*uk_33 + 1849*uk_34 + 784*uk_35 + 3500*uk_36 + 6300*uk_37 + 1204*uk_38 + 15625*uk_39 + 43*uk_4 + 28125*uk_40 + 5375*uk_41 + 50625*uk_42 + 9675*uk_43 + 1849*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 241807194334*uk_47 + 110613929323*uk_48 + 72027674908*uk_49 + 28*uk_5 + 321552120125*uk_50 + 578793816225*uk_51 + 110613929323*uk_52 + 153424975*uk_53 + 262217230*uk_54 + 119950435*uk_55 + 78107260*uk_56 + 348693125*uk_57 + 627647625*uk_58 + 119950435*uk_59 + 125*uk_6 + 448153084*uk_60 + 205006198*uk_61 + 133492408*uk_62 + 595948250*uk_63 + 1072706850*uk_64 + 205006198*uk_65 + 93779431*uk_66 + 61065676*uk_67 + 272614625*uk_68 + 490706325*uk_69 + 225*uk_7 + 93779431*uk_70 + 39763696*uk_71 + 177516500*uk_72 + 319529700*uk_73 + 61065676*uk_74 + 792484375*uk_75 + 1426471875*uk_76 + 272614625*uk_77 + 2567649375*uk_78 + 490706325*uk_79 + 43*uk_8 + 93779431*uk_80 + 166375*uk_81 + 284350*uk_82 + 130075*uk_83 + 84700*uk_84 + 378125*uk_85 + 680625*uk_86 + 130075*uk_87 + 485980*uk_88 + 222310*uk_89 + 2572416961*uk_9 + 144760*uk_90 + 646250*uk_91 + 1163250*uk_92 + 222310*uk_93 + 101695*uk_94 + 66220*uk_95 + 295625*uk_96 + 532125*uk_97 + 101695*uk_98 + 43120*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 167640*uk_100 + 297000*uk_101 + 124080*uk_102 + 887095*uk_103 + 1571625*uk_104 + 656590*uk_105 + 2784375*uk_106 + 1163250*uk_107 + 485980*uk_108 + 97336*uk_109 + 2333074*uk_11 + 198904*uk_110 + 50784*uk_111 + 268732*uk_112 + 476100*uk_113 + 198904*uk_114 + 406456*uk_115 + 103776*uk_116 + 549148*uk_117 + 972900*uk_118 + 406456*uk_119 + 4767586*uk_12 + 26496*uk_120 + 140208*uk_121 + 248400*uk_122 + 103776*uk_123 + 741934*uk_124 + 1314450*uk_125 + 549148*uk_126 + 2328750*uk_127 + 972900*uk_128 + 406456*uk_129 + 1217256*uk_13 + 830584*uk_130 + 212064*uk_131 + 1122172*uk_132 + 1988100*uk_133 + 830584*uk_134 + 54144*uk_135 + 286512*uk_136 + 507600*uk_137 + 212064*uk_138 + 1516126*uk_139 + 6441313*uk_14 + 2686050*uk_140 + 1122172*uk_141 + 4758750*uk_142 + 1988100*uk_143 + 830584*uk_144 + 13824*uk_145 + 73152*uk_146 + 129600*uk_147 + 54144*uk_148 + 387096*uk_149 + 11411775*uk_15 + 685800*uk_150 + 286512*uk_151 + 1215000*uk_152 + 507600*uk_153 + 212064*uk_154 + 2048383*uk_155 + 3629025*uk_156 + 1516126*uk_157 + 6429375*uk_158 + 2686050*uk_159 + 4767586*uk_16 + 1122172*uk_160 + 11390625*uk_161 + 4758750*uk_162 + 1988100*uk_163 + 830584*uk_164 + 3025*uk_17 + 2530*uk_18 + 5170*uk_19 + 55*uk_2 + 1320*uk_20 + 6985*uk_21 + 12375*uk_22 + 5170*uk_23 + 2116*uk_24 + 4324*uk_25 + 1104*uk_26 + 5842*uk_27 + 10350*uk_28 + 4324*uk_29 + 46*uk_3 + 8836*uk_30 + 2256*uk_31 + 11938*uk_32 + 21150*uk_33 + 8836*uk_34 + 576*uk_35 + 3048*uk_36 + 5400*uk_37 + 2256*uk_38 + 16129*uk_39 + 94*uk_4 + 28575*uk_40 + 11938*uk_41 + 50625*uk_42 + 21150*uk_43 + 8836*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 118331180206*uk_47 + 241807194334*uk_48 + 61738007064*uk_49 + 24*uk_5 + 326696954047*uk_50 + 578793816225*uk_51 + 241807194334*uk_52 + 153424975*uk_53 + 128319070*uk_54 + 262217230*uk_55 + 66949080*uk_56 + 354272215*uk_57 + 627647625*uk_58 + 262217230*uk_59 + 127*uk_6 + 107321404*uk_60 + 219308956*uk_61 + 55993776*uk_62 + 296300398*uk_63 + 524941650*uk_64 + 219308956*uk_65 + 448153084*uk_66 + 114422064*uk_67 + 605483422*uk_68 + 1072706850*uk_69 + 225*uk_7 + 448153084*uk_70 + 29214144*uk_71 + 154591512*uk_72 + 273882600*uk_73 + 114422064*uk_74 + 818046751*uk_75 + 1449295425*uk_76 + 605483422*uk_77 + 2567649375*uk_78 + 1072706850*uk_79 + 94*uk_8 + 448153084*uk_80 + 166375*uk_81 + 139150*uk_82 + 284350*uk_83 + 72600*uk_84 + 384175*uk_85 + 680625*uk_86 + 284350*uk_87 + 116380*uk_88 + 237820*uk_89 + 2572416961*uk_9 + 60720*uk_90 + 321310*uk_91 + 569250*uk_92 + 237820*uk_93 + 485980*uk_94 + 124080*uk_95 + 656590*uk_96 + 1163250*uk_97 + 485980*uk_98 + 31680*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 170280*uk_100 + 297000*uk_101 + 60720*uk_102 + 915255*uk_103 + 1596375*uk_104 + 326370*uk_105 + 2784375*uk_106 + 569250*uk_107 + 116380*uk_108 + 10648*uk_109 + 1115818*uk_11 + 22264*uk_110 + 11616*uk_111 + 62436*uk_112 + 108900*uk_113 + 22264*uk_114 + 46552*uk_115 + 24288*uk_116 + 130548*uk_117 + 227700*uk_118 + 46552*uk_119 + 2333074*uk_12 + 12672*uk_120 + 68112*uk_121 + 118800*uk_122 + 24288*uk_123 + 366102*uk_124 + 638550*uk_125 + 130548*uk_126 + 1113750*uk_127 + 227700*uk_128 + 46552*uk_129 + 1217256*uk_13 + 97336*uk_130 + 50784*uk_131 + 272964*uk_132 + 476100*uk_133 + 97336*uk_134 + 26496*uk_135 + 142416*uk_136 + 248400*uk_137 + 50784*uk_138 + 765486*uk_139 + 6542751*uk_14 + 1335150*uk_140 + 272964*uk_141 + 2328750*uk_142 + 476100*uk_143 + 97336*uk_144 + 13824*uk_145 + 74304*uk_146 + 129600*uk_147 + 26496*uk_148 + 399384*uk_149 + 11411775*uk_15 + 696600*uk_150 + 142416*uk_151 + 1215000*uk_152 + 248400*uk_153 + 50784*uk_154 + 2146689*uk_155 + 3744225*uk_156 + 765486*uk_157 + 6530625*uk_158 + 1335150*uk_159 + 2333074*uk_16 + 272964*uk_160 + 11390625*uk_161 + 2328750*uk_162 + 476100*uk_163 + 97336*uk_164 + 3025*uk_17 + 1210*uk_18 + 2530*uk_19 + 55*uk_2 + 1320*uk_20 + 7095*uk_21 + 12375*uk_22 + 2530*uk_23 + 484*uk_24 + 1012*uk_25 + 528*uk_26 + 2838*uk_27 + 4950*uk_28 + 1012*uk_29 + 22*uk_3 + 2116*uk_30 + 1104*uk_31 + 5934*uk_32 + 10350*uk_33 + 2116*uk_34 + 576*uk_35 + 3096*uk_36 + 5400*uk_37 + 1104*uk_38 + 16641*uk_39 + 46*uk_4 + 29025*uk_40 + 5934*uk_41 + 50625*uk_42 + 10350*uk_43 + 2116*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 56593173142*uk_47 + 118331180206*uk_48 + 61738007064*uk_49 + 24*uk_5 + 331841787969*uk_50 + 578793816225*uk_51 + 118331180206*uk_52 + 153424975*uk_53 + 61369990*uk_54 + 128319070*uk_55 + 66949080*uk_56 + 359851305*uk_57 + 627647625*uk_58 + 128319070*uk_59 + 129*uk_6 + 24547996*uk_60 + 51327628*uk_61 + 26779632*uk_62 + 143940522*uk_63 + 251059050*uk_64 + 51327628*uk_65 + 107321404*uk_66 + 55993776*uk_67 + 300966546*uk_68 + 524941650*uk_69 + 225*uk_7 + 107321404*uk_70 + 29214144*uk_71 + 157026024*uk_72 + 273882600*uk_73 + 55993776*uk_74 + 844014879*uk_75 + 1472118975*uk_76 + 300966546*uk_77 + 2567649375*uk_78 + 524941650*uk_79 + 46*uk_8 + 107321404*uk_80 + 166375*uk_81 + 66550*uk_82 + 139150*uk_83 + 72600*uk_84 + 390225*uk_85 + 680625*uk_86 + 139150*uk_87 + 26620*uk_88 + 55660*uk_89 + 2572416961*uk_9 + 29040*uk_90 + 156090*uk_91 + 272250*uk_92 + 55660*uk_93 + 116380*uk_94 + 60720*uk_95 + 326370*uk_96 + 569250*uk_97 + 116380*uk_98 + 31680*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 172920*uk_100 + 297000*uk_101 + 29040*uk_102 + 943855*uk_103 + 1621125*uk_104 + 158510*uk_105 + 2784375*uk_106 + 272250*uk_107 + 26620*uk_108 + 10648*uk_109 + 1115818*uk_11 + 10648*uk_110 + 11616*uk_111 + 63404*uk_112 + 108900*uk_113 + 10648*uk_114 + 10648*uk_115 + 11616*uk_116 + 63404*uk_117 + 108900*uk_118 + 10648*uk_119 + 1115818*uk_12 + 12672*uk_120 + 69168*uk_121 + 118800*uk_122 + 11616*uk_123 + 377542*uk_124 + 648450*uk_125 + 63404*uk_126 + 1113750*uk_127 + 108900*uk_128 + 10648*uk_129 + 1217256*uk_13 + 10648*uk_130 + 11616*uk_131 + 63404*uk_132 + 108900*uk_133 + 10648*uk_134 + 12672*uk_135 + 69168*uk_136 + 118800*uk_137 + 11616*uk_138 + 377542*uk_139 + 6644189*uk_14 + 648450*uk_140 + 63404*uk_141 + 1113750*uk_142 + 108900*uk_143 + 10648*uk_144 + 13824*uk_145 + 75456*uk_146 + 129600*uk_147 + 12672*uk_148 + 411864*uk_149 + 11411775*uk_15 + 707400*uk_150 + 69168*uk_151 + 1215000*uk_152 + 118800*uk_153 + 11616*uk_154 + 2248091*uk_155 + 3861225*uk_156 + 377542*uk_157 + 6631875*uk_158 + 648450*uk_159 + 1115818*uk_16 + 63404*uk_160 + 11390625*uk_161 + 1113750*uk_162 + 108900*uk_163 + 10648*uk_164 + 3025*uk_17 + 1210*uk_18 + 1210*uk_19 + 55*uk_2 + 1320*uk_20 + 7205*uk_21 + 12375*uk_22 + 1210*uk_23 + 484*uk_24 + 484*uk_25 + 528*uk_26 + 2882*uk_27 + 4950*uk_28 + 484*uk_29 + 22*uk_3 + 484*uk_30 + 528*uk_31 + 2882*uk_32 + 4950*uk_33 + 484*uk_34 + 576*uk_35 + 3144*uk_36 + 5400*uk_37 + 528*uk_38 + 17161*uk_39 + 22*uk_4 + 29475*uk_40 + 2882*uk_41 + 50625*uk_42 + 4950*uk_43 + 484*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 56593173142*uk_47 + 56593173142*uk_48 + 61738007064*uk_49 + 24*uk_5 + 336986621891*uk_50 + 578793816225*uk_51 + 56593173142*uk_52 + 153424975*uk_53 + 61369990*uk_54 + 61369990*uk_55 + 66949080*uk_56 + 365430395*uk_57 + 627647625*uk_58 + 61369990*uk_59 + 131*uk_6 + 24547996*uk_60 + 24547996*uk_61 + 26779632*uk_62 + 146172158*uk_63 + 251059050*uk_64 + 24547996*uk_65 + 24547996*uk_66 + 26779632*uk_67 + 146172158*uk_68 + 251059050*uk_69 + 225*uk_7 + 24547996*uk_70 + 29214144*uk_71 + 159460536*uk_72 + 273882600*uk_73 + 26779632*uk_74 + 870388759*uk_75 + 1494942525*uk_76 + 146172158*uk_77 + 2567649375*uk_78 + 251059050*uk_79 + 22*uk_8 + 24547996*uk_80 + 166375*uk_81 + 66550*uk_82 + 66550*uk_83 + 72600*uk_84 + 396275*uk_85 + 680625*uk_86 + 66550*uk_87 + 26620*uk_88 + 26620*uk_89 + 2572416961*uk_9 + 29040*uk_90 + 158510*uk_91 + 272250*uk_92 + 26620*uk_93 + 26620*uk_94 + 29040*uk_95 + 158510*uk_96 + 272250*uk_97 + 26620*uk_98 + 31680*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 175560*uk_100 + 297000*uk_101 + 29040*uk_102 + 972895*uk_103 + 1645875*uk_104 + 160930*uk_105 + 2784375*uk_106 + 272250*uk_107 + 26620*uk_108 + 97336*uk_109 + 2333074*uk_11 + 46552*uk_110 + 50784*uk_111 + 281428*uk_112 + 476100*uk_113 + 46552*uk_114 + 22264*uk_115 + 24288*uk_116 + 134596*uk_117 + 227700*uk_118 + 22264*uk_119 + 1115818*uk_12 + 26496*uk_120 + 146832*uk_121 + 248400*uk_122 + 24288*uk_123 + 813694*uk_124 + 1376550*uk_125 + 134596*uk_126 + 2328750*uk_127 + 227700*uk_128 + 22264*uk_129 + 1217256*uk_13 + 10648*uk_130 + 11616*uk_131 + 64372*uk_132 + 108900*uk_133 + 10648*uk_134 + 12672*uk_135 + 70224*uk_136 + 118800*uk_137 + 11616*uk_138 + 389158*uk_139 + 6745627*uk_14 + 658350*uk_140 + 64372*uk_141 + 1113750*uk_142 + 108900*uk_143 + 10648*uk_144 + 13824*uk_145 + 76608*uk_146 + 129600*uk_147 + 12672*uk_148 + 424536*uk_149 + 11411775*uk_15 + 718200*uk_150 + 70224*uk_151 + 1215000*uk_152 + 118800*uk_153 + 11616*uk_154 + 2352637*uk_155 + 3980025*uk_156 + 389158*uk_157 + 6733125*uk_158 + 658350*uk_159 + 1115818*uk_16 + 64372*uk_160 + 11390625*uk_161 + 1113750*uk_162 + 108900*uk_163 + 10648*uk_164 + 3025*uk_17 + 2530*uk_18 + 1210*uk_19 + 55*uk_2 + 1320*uk_20 + 7315*uk_21 + 12375*uk_22 + 1210*uk_23 + 2116*uk_24 + 1012*uk_25 + 1104*uk_26 + 6118*uk_27 + 10350*uk_28 + 1012*uk_29 + 46*uk_3 + 484*uk_30 + 528*uk_31 + 2926*uk_32 + 4950*uk_33 + 484*uk_34 + 576*uk_35 + 3192*uk_36 + 5400*uk_37 + 528*uk_38 + 17689*uk_39 + 22*uk_4 + 29925*uk_40 + 2926*uk_41 + 50625*uk_42 + 4950*uk_43 + 484*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 118331180206*uk_47 + 56593173142*uk_48 + 61738007064*uk_49 + 24*uk_5 + 342131455813*uk_50 + 578793816225*uk_51 + 56593173142*uk_52 + 153424975*uk_53 + 128319070*uk_54 + 61369990*uk_55 + 66949080*uk_56 + 371009485*uk_57 + 627647625*uk_58 + 61369990*uk_59 + 133*uk_6 + 107321404*uk_60 + 51327628*uk_61 + 55993776*uk_62 + 310298842*uk_63 + 524941650*uk_64 + 51327628*uk_65 + 24547996*uk_66 + 26779632*uk_67 + 148403794*uk_68 + 251059050*uk_69 + 225*uk_7 + 24547996*uk_70 + 29214144*uk_71 + 161895048*uk_72 + 273882600*uk_73 + 26779632*uk_74 + 897168391*uk_75 + 1517766075*uk_76 + 148403794*uk_77 + 2567649375*uk_78 + 251059050*uk_79 + 22*uk_8 + 24547996*uk_80 + 166375*uk_81 + 139150*uk_82 + 66550*uk_83 + 72600*uk_84 + 402325*uk_85 + 680625*uk_86 + 66550*uk_87 + 116380*uk_88 + 55660*uk_89 + 2572416961*uk_9 + 60720*uk_90 + 336490*uk_91 + 569250*uk_92 + 55660*uk_93 + 26620*uk_94 + 29040*uk_95 + 160930*uk_96 + 272250*uk_97 + 26620*uk_98 + 31680*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 178200*uk_100 + 297000*uk_101 + 60720*uk_102 + 1002375*uk_103 + 1670625*uk_104 + 341550*uk_105 + 2784375*uk_106 + 569250*uk_107 + 116380*uk_108 + 830584*uk_109 + 4767586*uk_11 + 406456*uk_110 + 212064*uk_111 + 1192860*uk_112 + 1988100*uk_113 + 406456*uk_114 + 198904*uk_115 + 103776*uk_116 + 583740*uk_117 + 972900*uk_118 + 198904*uk_119 + 2333074*uk_12 + 54144*uk_120 + 304560*uk_121 + 507600*uk_122 + 103776*uk_123 + 1713150*uk_124 + 2855250*uk_125 + 583740*uk_126 + 4758750*uk_127 + 972900*uk_128 + 198904*uk_129 + 1217256*uk_13 + 97336*uk_130 + 50784*uk_131 + 285660*uk_132 + 476100*uk_133 + 97336*uk_134 + 26496*uk_135 + 149040*uk_136 + 248400*uk_137 + 50784*uk_138 + 838350*uk_139 + 6847065*uk_14 + 1397250*uk_140 + 285660*uk_141 + 2328750*uk_142 + 476100*uk_143 + 97336*uk_144 + 13824*uk_145 + 77760*uk_146 + 129600*uk_147 + 26496*uk_148 + 437400*uk_149 + 11411775*uk_15 + 729000*uk_150 + 149040*uk_151 + 1215000*uk_152 + 248400*uk_153 + 50784*uk_154 + 2460375*uk_155 + 4100625*uk_156 + 838350*uk_157 + 6834375*uk_158 + 1397250*uk_159 + 2333074*uk_16 + 285660*uk_160 + 11390625*uk_161 + 2328750*uk_162 + 476100*uk_163 + 97336*uk_164 + 3025*uk_17 + 5170*uk_18 + 2530*uk_19 + 55*uk_2 + 1320*uk_20 + 7425*uk_21 + 12375*uk_22 + 2530*uk_23 + 8836*uk_24 + 4324*uk_25 + 2256*uk_26 + 12690*uk_27 + 21150*uk_28 + 4324*uk_29 + 94*uk_3 + 2116*uk_30 + 1104*uk_31 + 6210*uk_32 + 10350*uk_33 + 2116*uk_34 + 576*uk_35 + 3240*uk_36 + 5400*uk_37 + 1104*uk_38 + 18225*uk_39 + 46*uk_4 + 30375*uk_40 + 6210*uk_41 + 50625*uk_42 + 10350*uk_43 + 2116*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 241807194334*uk_47 + 118331180206*uk_48 + 61738007064*uk_49 + 24*uk_5 + 347276289735*uk_50 + 578793816225*uk_51 + 118331180206*uk_52 + 153424975*uk_53 + 262217230*uk_54 + 128319070*uk_55 + 66949080*uk_56 + 376588575*uk_57 + 627647625*uk_58 + 128319070*uk_59 + 135*uk_6 + 448153084*uk_60 + 219308956*uk_61 + 114422064*uk_62 + 643624110*uk_63 + 1072706850*uk_64 + 219308956*uk_65 + 107321404*uk_66 + 55993776*uk_67 + 314964990*uk_68 + 524941650*uk_69 + 225*uk_7 + 107321404*uk_70 + 29214144*uk_71 + 164329560*uk_72 + 273882600*uk_73 + 55993776*uk_74 + 924353775*uk_75 + 1540589625*uk_76 + 314964990*uk_77 + 2567649375*uk_78 + 524941650*uk_79 + 46*uk_8 + 107321404*uk_80 + 166375*uk_81 + 284350*uk_82 + 139150*uk_83 + 72600*uk_84 + 408375*uk_85 + 680625*uk_86 + 139150*uk_87 + 485980*uk_88 + 237820*uk_89 + 2572416961*uk_9 + 124080*uk_90 + 697950*uk_91 + 1163250*uk_92 + 237820*uk_93 + 116380*uk_94 + 60720*uk_95 + 341550*uk_96 + 569250*uk_97 + 116380*uk_98 + 31680*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 150700*uk_100 + 247500*uk_101 + 103400*uk_102 + 1032295*uk_103 + 1695375*uk_104 + 708290*uk_105 + 2784375*uk_106 + 1163250*uk_107 + 485980*uk_108 + 24389*uk_109 + 1470851*uk_11 + 79054*uk_110 + 16820*uk_111 + 115217*uk_112 + 189225*uk_113 + 79054*uk_114 + 256244*uk_115 + 54520*uk_116 + 373462*uk_117 + 613350*uk_118 + 256244*uk_119 + 4767586*uk_12 + 11600*uk_120 + 79460*uk_121 + 130500*uk_122 + 54520*uk_123 + 544301*uk_124 + 893925*uk_125 + 373462*uk_126 + 1468125*uk_127 + 613350*uk_128 + 256244*uk_129 + 1014380*uk_13 + 830584*uk_130 + 176720*uk_131 + 1210532*uk_132 + 1988100*uk_133 + 830584*uk_134 + 37600*uk_135 + 257560*uk_136 + 423000*uk_137 + 176720*uk_138 + 1764286*uk_139 + 6948503*uk_14 + 2897550*uk_140 + 1210532*uk_141 + 4758750*uk_142 + 1988100*uk_143 + 830584*uk_144 + 8000*uk_145 + 54800*uk_146 + 90000*uk_147 + 37600*uk_148 + 375380*uk_149 + 11411775*uk_15 + 616500*uk_150 + 257560*uk_151 + 1012500*uk_152 + 423000*uk_153 + 176720*uk_154 + 2571353*uk_155 + 4223025*uk_156 + 1764286*uk_157 + 6935625*uk_158 + 2897550*uk_159 + 4767586*uk_16 + 1210532*uk_160 + 11390625*uk_161 + 4758750*uk_162 + 1988100*uk_163 + 830584*uk_164 + 3025*uk_17 + 1595*uk_18 + 5170*uk_19 + 55*uk_2 + 1100*uk_20 + 7535*uk_21 + 12375*uk_22 + 5170*uk_23 + 841*uk_24 + 2726*uk_25 + 580*uk_26 + 3973*uk_27 + 6525*uk_28 + 2726*uk_29 + 29*uk_3 + 8836*uk_30 + 1880*uk_31 + 12878*uk_32 + 21150*uk_33 + 8836*uk_34 + 400*uk_35 + 2740*uk_36 + 4500*uk_37 + 1880*uk_38 + 18769*uk_39 + 94*uk_4 + 30825*uk_40 + 12878*uk_41 + 50625*uk_42 + 21150*uk_43 + 8836*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 74600091869*uk_47 + 241807194334*uk_48 + 51448339220*uk_49 + 20*uk_5 + 352421123657*uk_50 + 578793816225*uk_51 + 241807194334*uk_52 + 153424975*uk_53 + 80896805*uk_54 + 262217230*uk_55 + 55790900*uk_56 + 382167665*uk_57 + 627647625*uk_58 + 262217230*uk_59 + 137*uk_6 + 42654679*uk_60 + 138259994*uk_61 + 29417020*uk_62 + 201506587*uk_63 + 330941475*uk_64 + 138259994*uk_65 + 448153084*uk_66 + 95351720*uk_67 + 653159282*uk_68 + 1072706850*uk_69 + 225*uk_7 + 448153084*uk_70 + 20287600*uk_71 + 138970060*uk_72 + 228235500*uk_73 + 95351720*uk_74 + 951944911*uk_75 + 1563413175*uk_76 + 653159282*uk_77 + 2567649375*uk_78 + 1072706850*uk_79 + 94*uk_8 + 448153084*uk_80 + 166375*uk_81 + 87725*uk_82 + 284350*uk_83 + 60500*uk_84 + 414425*uk_85 + 680625*uk_86 + 284350*uk_87 + 46255*uk_88 + 149930*uk_89 + 2572416961*uk_9 + 31900*uk_90 + 218515*uk_91 + 358875*uk_92 + 149930*uk_93 + 485980*uk_94 + 103400*uk_95 + 708290*uk_96 + 1163250*uk_97 + 485980*uk_98 + 22000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 183480*uk_100 + 297000*uk_101 + 38280*uk_102 + 1062655*uk_103 + 1720125*uk_104 + 221705*uk_105 + 2784375*uk_106 + 358875*uk_107 + 46255*uk_108 + 1860867*uk_109 + 6238437*uk_11 + 438741*uk_110 + 363096*uk_111 + 2102931*uk_112 + 3404025*uk_113 + 438741*uk_114 + 103443*uk_115 + 85608*uk_116 + 495813*uk_117 + 802575*uk_118 + 103443*uk_119 + 1470851*uk_12 + 70848*uk_120 + 410328*uk_121 + 664200*uk_122 + 85608*uk_123 + 2376483*uk_124 + 3846825*uk_125 + 495813*uk_126 + 6226875*uk_127 + 802575*uk_128 + 103443*uk_129 + 1217256*uk_13 + 24389*uk_130 + 20184*uk_131 + 116899*uk_132 + 189225*uk_133 + 24389*uk_134 + 16704*uk_135 + 96744*uk_136 + 156600*uk_137 + 20184*uk_138 + 560309*uk_139 + 7049941*uk_14 + 906975*uk_140 + 116899*uk_141 + 1468125*uk_142 + 189225*uk_143 + 24389*uk_144 + 13824*uk_145 + 80064*uk_146 + 129600*uk_147 + 16704*uk_148 + 463704*uk_149 + 11411775*uk_15 + 750600*uk_150 + 96744*uk_151 + 1215000*uk_152 + 156600*uk_153 + 20184*uk_154 + 2685619*uk_155 + 4347225*uk_156 + 560309*uk_157 + 7036875*uk_158 + 906975*uk_159 + 1470851*uk_16 + 116899*uk_160 + 11390625*uk_161 + 1468125*uk_162 + 189225*uk_163 + 24389*uk_164 + 3025*uk_17 + 6765*uk_18 + 1595*uk_19 + 55*uk_2 + 1320*uk_20 + 7645*uk_21 + 12375*uk_22 + 1595*uk_23 + 15129*uk_24 + 3567*uk_25 + 2952*uk_26 + 17097*uk_27 + 27675*uk_28 + 3567*uk_29 + 123*uk_3 + 841*uk_30 + 696*uk_31 + 4031*uk_32 + 6525*uk_33 + 841*uk_34 + 576*uk_35 + 3336*uk_36 + 5400*uk_37 + 696*uk_38 + 19321*uk_39 + 29*uk_4 + 31275*uk_40 + 4031*uk_41 + 50625*uk_42 + 6525*uk_43 + 841*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 316407286203*uk_47 + 74600091869*uk_48 + 61738007064*uk_49 + 24*uk_5 + 357565957579*uk_50 + 578793816225*uk_51 + 74600091869*uk_52 + 153424975*uk_53 + 343114035*uk_54 + 80896805*uk_55 + 66949080*uk_56 + 387746755*uk_57 + 627647625*uk_58 + 80896805*uk_59 + 139*uk_6 + 767327751*uk_60 + 180914673*uk_61 + 149722488*uk_62 + 867142743*uk_63 + 1403648325*uk_64 + 180914673*uk_65 + 42654679*uk_66 + 35300424*uk_67 + 204448289*uk_68 + 330941475*uk_69 + 225*uk_7 + 42654679*uk_70 + 29214144*uk_71 + 169198584*uk_72 + 273882600*uk_73 + 35300424*uk_74 + 979941799*uk_75 + 1586236725*uk_76 + 204448289*uk_77 + 2567649375*uk_78 + 330941475*uk_79 + 29*uk_8 + 42654679*uk_80 + 166375*uk_81 + 372075*uk_82 + 87725*uk_83 + 72600*uk_84 + 420475*uk_85 + 680625*uk_86 + 87725*uk_87 + 832095*uk_88 + 196185*uk_89 + 2572416961*uk_9 + 162360*uk_90 + 940335*uk_91 + 1522125*uk_92 + 196185*uk_93 + 46255*uk_94 + 38280*uk_95 + 221705*uk_96 + 358875*uk_97 + 46255*uk_98 + 31680*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 155100*uk_100 + 247500*uk_101 + 135300*uk_102 + 1093455*uk_103 + 1744875*uk_104 + 953865*uk_105 + 2784375*uk_106 + 1522125*uk_107 + 832095*uk_108 + 1000000*uk_109 + 5071900*uk_11 + 1230000*uk_110 + 200000*uk_111 + 1410000*uk_112 + 2250000*uk_113 + 1230000*uk_114 + 1512900*uk_115 + 246000*uk_116 + 1734300*uk_117 + 2767500*uk_118 + 1512900*uk_119 + 6238437*uk_12 + 40000*uk_120 + 282000*uk_121 + 450000*uk_122 + 246000*uk_123 + 1988100*uk_124 + 3172500*uk_125 + 1734300*uk_126 + 5062500*uk_127 + 2767500*uk_128 + 1512900*uk_129 + 1014380*uk_13 + 1860867*uk_130 + 302580*uk_131 + 2133189*uk_132 + 3404025*uk_133 + 1860867*uk_134 + 49200*uk_135 + 346860*uk_136 + 553500*uk_137 + 302580*uk_138 + 2445363*uk_139 + 7151379*uk_14 + 3902175*uk_140 + 2133189*uk_141 + 6226875*uk_142 + 3404025*uk_143 + 1860867*uk_144 + 8000*uk_145 + 56400*uk_146 + 90000*uk_147 + 49200*uk_148 + 397620*uk_149 + 11411775*uk_15 + 634500*uk_150 + 346860*uk_151 + 1012500*uk_152 + 553500*uk_153 + 302580*uk_154 + 2803221*uk_155 + 4473225*uk_156 + 2445363*uk_157 + 7138125*uk_158 + 3902175*uk_159 + 6238437*uk_16 + 2133189*uk_160 + 11390625*uk_161 + 6226875*uk_162 + 3404025*uk_163 + 1860867*uk_164 + 3025*uk_17 + 5500*uk_18 + 6765*uk_19 + 55*uk_2 + 1100*uk_20 + 7755*uk_21 + 12375*uk_22 + 6765*uk_23 + 10000*uk_24 + 12300*uk_25 + 2000*uk_26 + 14100*uk_27 + 22500*uk_28 + 12300*uk_29 + 100*uk_3 + 15129*uk_30 + 2460*uk_31 + 17343*uk_32 + 27675*uk_33 + 15129*uk_34 + 400*uk_35 + 2820*uk_36 + 4500*uk_37 + 2460*uk_38 + 19881*uk_39 + 123*uk_4 + 31725*uk_40 + 17343*uk_41 + 50625*uk_42 + 27675*uk_43 + 15129*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 257241696100*uk_47 + 316407286203*uk_48 + 51448339220*uk_49 + 20*uk_5 + 362710791501*uk_50 + 578793816225*uk_51 + 316407286203*uk_52 + 153424975*uk_53 + 278954500*uk_54 + 343114035*uk_55 + 55790900*uk_56 + 393325845*uk_57 + 627647625*uk_58 + 343114035*uk_59 + 141*uk_6 + 507190000*uk_60 + 623843700*uk_61 + 101438000*uk_62 + 715137900*uk_63 + 1141177500*uk_64 + 623843700*uk_65 + 767327751*uk_66 + 124768740*uk_67 + 879619617*uk_68 + 1403648325*uk_69 + 225*uk_7 + 767327751*uk_70 + 20287600*uk_71 + 143027580*uk_72 + 228235500*uk_73 + 124768740*uk_74 + 1008344439*uk_75 + 1609060275*uk_76 + 879619617*uk_77 + 2567649375*uk_78 + 1403648325*uk_79 + 123*uk_8 + 767327751*uk_80 + 166375*uk_81 + 302500*uk_82 + 372075*uk_83 + 60500*uk_84 + 426525*uk_85 + 680625*uk_86 + 372075*uk_87 + 550000*uk_88 + 676500*uk_89 + 2572416961*uk_9 + 110000*uk_90 + 775500*uk_91 + 1237500*uk_92 + 676500*uk_93 + 832095*uk_94 + 135300*uk_95 + 953865*uk_96 + 1522125*uk_97 + 832095*uk_98 + 22000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 157300*uk_100 + 247500*uk_101 + 110000*uk_102 + 1124695*uk_103 + 1769625*uk_104 + 786500*uk_105 + 2784375*uk_106 + 1237500*uk_107 + 550000*uk_108 + 912673*uk_109 + 4919743*uk_11 + 940900*uk_110 + 188180*uk_111 + 1345487*uk_112 + 2117025*uk_113 + 940900*uk_114 + 970000*uk_115 + 194000*uk_116 + 1387100*uk_117 + 2182500*uk_118 + 970000*uk_119 + 5071900*uk_12 + 38800*uk_120 + 277420*uk_121 + 436500*uk_122 + 194000*uk_123 + 1983553*uk_124 + 3120975*uk_125 + 1387100*uk_126 + 4910625*uk_127 + 2182500*uk_128 + 970000*uk_129 + 1014380*uk_13 + 1000000*uk_130 + 200000*uk_131 + 1430000*uk_132 + 2250000*uk_133 + 1000000*uk_134 + 40000*uk_135 + 286000*uk_136 + 450000*uk_137 + 200000*uk_138 + 2044900*uk_139 + 7252817*uk_14 + 3217500*uk_140 + 1430000*uk_141 + 5062500*uk_142 + 2250000*uk_143 + 1000000*uk_144 + 8000*uk_145 + 57200*uk_146 + 90000*uk_147 + 40000*uk_148 + 408980*uk_149 + 11411775*uk_15 + 643500*uk_150 + 286000*uk_151 + 1012500*uk_152 + 450000*uk_153 + 200000*uk_154 + 2924207*uk_155 + 4601025*uk_156 + 2044900*uk_157 + 7239375*uk_158 + 3217500*uk_159 + 5071900*uk_16 + 1430000*uk_160 + 11390625*uk_161 + 5062500*uk_162 + 2250000*uk_163 + 1000000*uk_164 + 3025*uk_17 + 5335*uk_18 + 5500*uk_19 + 55*uk_2 + 1100*uk_20 + 7865*uk_21 + 12375*uk_22 + 5500*uk_23 + 9409*uk_24 + 9700*uk_25 + 1940*uk_26 + 13871*uk_27 + 21825*uk_28 + 9700*uk_29 + 97*uk_3 + 10000*uk_30 + 2000*uk_31 + 14300*uk_32 + 22500*uk_33 + 10000*uk_34 + 400*uk_35 + 2860*uk_36 + 4500*uk_37 + 2000*uk_38 + 20449*uk_39 + 100*uk_4 + 32175*uk_40 + 14300*uk_41 + 50625*uk_42 + 22500*uk_43 + 10000*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 249524445217*uk_47 + 257241696100*uk_48 + 51448339220*uk_49 + 20*uk_5 + 367855625423*uk_50 + 578793816225*uk_51 + 257241696100*uk_52 + 153424975*uk_53 + 270585865*uk_54 + 278954500*uk_55 + 55790900*uk_56 + 398904935*uk_57 + 627647625*uk_58 + 278954500*uk_59 + 143*uk_6 + 477215071*uk_60 + 491974300*uk_61 + 98394860*uk_62 + 703523249*uk_63 + 1106942175*uk_64 + 491974300*uk_65 + 507190000*uk_66 + 101438000*uk_67 + 725281700*uk_68 + 1141177500*uk_69 + 225*uk_7 + 507190000*uk_70 + 20287600*uk_71 + 145056340*uk_72 + 228235500*uk_73 + 101438000*uk_74 + 1037152831*uk_75 + 1631883825*uk_76 + 725281700*uk_77 + 2567649375*uk_78 + 1141177500*uk_79 + 100*uk_8 + 507190000*uk_80 + 166375*uk_81 + 293425*uk_82 + 302500*uk_83 + 60500*uk_84 + 432575*uk_85 + 680625*uk_86 + 302500*uk_87 + 517495*uk_88 + 533500*uk_89 + 2572416961*uk_9 + 106700*uk_90 + 762905*uk_91 + 1200375*uk_92 + 533500*uk_93 + 550000*uk_94 + 110000*uk_95 + 786500*uk_96 + 1237500*uk_97 + 550000*uk_98 + 22000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 159500*uk_100 + 247500*uk_101 + 106700*uk_102 + 1156375*uk_103 + 1794375*uk_104 + 773575*uk_105 + 2784375*uk_106 + 1200375*uk_107 + 517495*uk_108 + 1481544*uk_109 + 5781966*uk_11 + 1260612*uk_110 + 259920*uk_111 + 1884420*uk_112 + 2924100*uk_113 + 1260612*uk_114 + 1072626*uk_115 + 221160*uk_116 + 1603410*uk_117 + 2488050*uk_118 + 1072626*uk_119 + 4919743*uk_12 + 45600*uk_120 + 330600*uk_121 + 513000*uk_122 + 221160*uk_123 + 2396850*uk_124 + 3719250*uk_125 + 1603410*uk_126 + 5771250*uk_127 + 2488050*uk_128 + 1072626*uk_129 + 1014380*uk_13 + 912673*uk_130 + 188180*uk_131 + 1364305*uk_132 + 2117025*uk_133 + 912673*uk_134 + 38800*uk_135 + 281300*uk_136 + 436500*uk_137 + 188180*uk_138 + 2039425*uk_139 + 7354255*uk_14 + 3164625*uk_140 + 1364305*uk_141 + 4910625*uk_142 + 2117025*uk_143 + 912673*uk_144 + 8000*uk_145 + 58000*uk_146 + 90000*uk_147 + 38800*uk_148 + 420500*uk_149 + 11411775*uk_15 + 652500*uk_150 + 281300*uk_151 + 1012500*uk_152 + 436500*uk_153 + 188180*uk_154 + 3048625*uk_155 + 4730625*uk_156 + 2039425*uk_157 + 7340625*uk_158 + 3164625*uk_159 + 4919743*uk_16 + 1364305*uk_160 + 11390625*uk_161 + 4910625*uk_162 + 2117025*uk_163 + 912673*uk_164 + 3025*uk_17 + 6270*uk_18 + 5335*uk_19 + 55*uk_2 + 1100*uk_20 + 7975*uk_21 + 12375*uk_22 + 5335*uk_23 + 12996*uk_24 + 11058*uk_25 + 2280*uk_26 + 16530*uk_27 + 25650*uk_28 + 11058*uk_29 + 114*uk_3 + 9409*uk_30 + 1940*uk_31 + 14065*uk_32 + 21825*uk_33 + 9409*uk_34 + 400*uk_35 + 2900*uk_36 + 4500*uk_37 + 1940*uk_38 + 21025*uk_39 + 97*uk_4 + 32625*uk_40 + 14065*uk_41 + 50625*uk_42 + 21825*uk_43 + 9409*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 293255533554*uk_47 + 249524445217*uk_48 + 51448339220*uk_49 + 20*uk_5 + 373000459345*uk_50 + 578793816225*uk_51 + 249524445217*uk_52 + 153424975*uk_53 + 318008130*uk_54 + 270585865*uk_55 + 55790900*uk_56 + 404484025*uk_57 + 627647625*uk_58 + 270585865*uk_59 + 145*uk_6 + 659144124*uk_60 + 560850702*uk_61 + 115639320*uk_62 + 838385070*uk_63 + 1300942350*uk_64 + 560850702*uk_65 + 477215071*uk_66 + 98394860*uk_67 + 713362735*uk_68 + 1106942175*uk_69 + 225*uk_7 + 477215071*uk_70 + 20287600*uk_71 + 147085100*uk_72 + 228235500*uk_73 + 98394860*uk_74 + 1066366975*uk_75 + 1654707375*uk_76 + 713362735*uk_77 + 2567649375*uk_78 + 1106942175*uk_79 + 97*uk_8 + 477215071*uk_80 + 166375*uk_81 + 344850*uk_82 + 293425*uk_83 + 60500*uk_84 + 438625*uk_85 + 680625*uk_86 + 293425*uk_87 + 714780*uk_88 + 608190*uk_89 + 2572416961*uk_9 + 125400*uk_90 + 909150*uk_91 + 1410750*uk_92 + 608190*uk_93 + 517495*uk_94 + 106700*uk_95 + 773575*uk_96 + 1200375*uk_97 + 517495*uk_98 + 22000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 129360*uk_100 + 198000*uk_101 + 100320*uk_102 + 1188495*uk_103 + 1819125*uk_104 + 921690*uk_105 + 2784375*uk_106 + 1410750*uk_107 + 714780*uk_108 + 64*uk_109 + 202876*uk_11 + 1824*uk_110 + 256*uk_111 + 2352*uk_112 + 3600*uk_113 + 1824*uk_114 + 51984*uk_115 + 7296*uk_116 + 67032*uk_117 + 102600*uk_118 + 51984*uk_119 + 5781966*uk_12 + 1024*uk_120 + 9408*uk_121 + 14400*uk_122 + 7296*uk_123 + 86436*uk_124 + 132300*uk_125 + 67032*uk_126 + 202500*uk_127 + 102600*uk_128 + 51984*uk_129 + 811504*uk_13 + 1481544*uk_130 + 207936*uk_131 + 1910412*uk_132 + 2924100*uk_133 + 1481544*uk_134 + 29184*uk_135 + 268128*uk_136 + 410400*uk_137 + 207936*uk_138 + 2463426*uk_139 + 7455693*uk_14 + 3770550*uk_140 + 1910412*uk_141 + 5771250*uk_142 + 2924100*uk_143 + 1481544*uk_144 + 4096*uk_145 + 37632*uk_146 + 57600*uk_147 + 29184*uk_148 + 345744*uk_149 + 11411775*uk_15 + 529200*uk_150 + 268128*uk_151 + 810000*uk_152 + 410400*uk_153 + 207936*uk_154 + 3176523*uk_155 + 4862025*uk_156 + 2463426*uk_157 + 7441875*uk_158 + 3770550*uk_159 + 5781966*uk_16 + 1910412*uk_160 + 11390625*uk_161 + 5771250*uk_162 + 2924100*uk_163 + 1481544*uk_164 + 3025*uk_17 + 220*uk_18 + 6270*uk_19 + 55*uk_2 + 880*uk_20 + 8085*uk_21 + 12375*uk_22 + 6270*uk_23 + 16*uk_24 + 456*uk_25 + 64*uk_26 + 588*uk_27 + 900*uk_28 + 456*uk_29 + 4*uk_3 + 12996*uk_30 + 1824*uk_31 + 16758*uk_32 + 25650*uk_33 + 12996*uk_34 + 256*uk_35 + 2352*uk_36 + 3600*uk_37 + 1824*uk_38 + 21609*uk_39 + 114*uk_4 + 33075*uk_40 + 16758*uk_41 + 50625*uk_42 + 25650*uk_43 + 12996*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 10289667844*uk_47 + 293255533554*uk_48 + 41158671376*uk_49 + 16*uk_5 + 378145293267*uk_50 + 578793816225*uk_51 + 293255533554*uk_52 + 153424975*uk_53 + 11158180*uk_54 + 318008130*uk_55 + 44632720*uk_56 + 410063115*uk_57 + 627647625*uk_58 + 318008130*uk_59 + 147*uk_6 + 811504*uk_60 + 23127864*uk_61 + 3246016*uk_62 + 29822772*uk_63 + 45647100*uk_64 + 23127864*uk_65 + 659144124*uk_66 + 92511456*uk_67 + 849949002*uk_68 + 1300942350*uk_69 + 225*uk_7 + 659144124*uk_70 + 12984064*uk_71 + 119291088*uk_72 + 182588400*uk_73 + 92511456*uk_74 + 1095986871*uk_75 + 1677530925*uk_76 + 849949002*uk_77 + 2567649375*uk_78 + 1300942350*uk_79 + 114*uk_8 + 659144124*uk_80 + 166375*uk_81 + 12100*uk_82 + 344850*uk_83 + 48400*uk_84 + 444675*uk_85 + 680625*uk_86 + 344850*uk_87 + 880*uk_88 + 25080*uk_89 + 2572416961*uk_9 + 3520*uk_90 + 32340*uk_91 + 49500*uk_92 + 25080*uk_93 + 714780*uk_94 + 100320*uk_95 + 921690*uk_96 + 1410750*uk_97 + 714780*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 163900*uk_100 + 247500*uk_101 + 4400*uk_102 + 1221055*uk_103 + 1843875*uk_104 + 32780*uk_105 + 2784375*uk_106 + 49500*uk_107 + 880*uk_108 + 205379*uk_109 + 2992421*uk_11 + 13924*uk_110 + 69620*uk_111 + 518669*uk_112 + 783225*uk_113 + 13924*uk_114 + 944*uk_115 + 4720*uk_116 + 35164*uk_117 + 53100*uk_118 + 944*uk_119 + 202876*uk_12 + 23600*uk_120 + 175820*uk_121 + 265500*uk_122 + 4720*uk_123 + 1309859*uk_124 + 1977975*uk_125 + 35164*uk_126 + 2986875*uk_127 + 53100*uk_128 + 944*uk_129 + 1014380*uk_13 + 64*uk_130 + 320*uk_131 + 2384*uk_132 + 3600*uk_133 + 64*uk_134 + 1600*uk_135 + 11920*uk_136 + 18000*uk_137 + 320*uk_138 + 88804*uk_139 + 7557131*uk_14 + 134100*uk_140 + 2384*uk_141 + 202500*uk_142 + 3600*uk_143 + 64*uk_144 + 8000*uk_145 + 59600*uk_146 + 90000*uk_147 + 1600*uk_148 + 444020*uk_149 + 11411775*uk_15 + 670500*uk_150 + 11920*uk_151 + 1012500*uk_152 + 18000*uk_153 + 320*uk_154 + 3307949*uk_155 + 4995225*uk_156 + 88804*uk_157 + 7543125*uk_158 + 134100*uk_159 + 202876*uk_16 + 2384*uk_160 + 11390625*uk_161 + 202500*uk_162 + 3600*uk_163 + 64*uk_164 + 3025*uk_17 + 3245*uk_18 + 220*uk_19 + 55*uk_2 + 1100*uk_20 + 8195*uk_21 + 12375*uk_22 + 220*uk_23 + 3481*uk_24 + 236*uk_25 + 1180*uk_26 + 8791*uk_27 + 13275*uk_28 + 236*uk_29 + 59*uk_3 + 16*uk_30 + 80*uk_31 + 596*uk_32 + 900*uk_33 + 16*uk_34 + 400*uk_35 + 2980*uk_36 + 4500*uk_37 + 80*uk_38 + 22201*uk_39 + 4*uk_4 + 33525*uk_40 + 596*uk_41 + 50625*uk_42 + 900*uk_43 + 16*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 151772600699*uk_47 + 10289667844*uk_48 + 51448339220*uk_49 + 20*uk_5 + 383290127189*uk_50 + 578793816225*uk_51 + 10289667844*uk_52 + 153424975*uk_53 + 164583155*uk_54 + 11158180*uk_55 + 55790900*uk_56 + 415642205*uk_57 + 627647625*uk_58 + 11158180*uk_59 + 149*uk_6 + 176552839*uk_60 + 11969684*uk_61 + 59848420*uk_62 + 445870729*uk_63 + 673294725*uk_64 + 11969684*uk_65 + 811504*uk_66 + 4057520*uk_67 + 30228524*uk_68 + 45647100*uk_69 + 225*uk_7 + 811504*uk_70 + 20287600*uk_71 + 151142620*uk_72 + 228235500*uk_73 + 4057520*uk_74 + 1126012519*uk_75 + 1700354475*uk_76 + 30228524*uk_77 + 2567649375*uk_78 + 45647100*uk_79 + 4*uk_8 + 811504*uk_80 + 166375*uk_81 + 178475*uk_82 + 12100*uk_83 + 60500*uk_84 + 450725*uk_85 + 680625*uk_86 + 12100*uk_87 + 191455*uk_88 + 12980*uk_89 + 2572416961*uk_9 + 64900*uk_90 + 483505*uk_91 + 730125*uk_92 + 12980*uk_93 + 880*uk_94 + 4400*uk_95 + 32780*uk_96 + 49500*uk_97 + 880*uk_98 + 22000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 166100*uk_100 + 247500*uk_101 + 64900*uk_102 + 1254055*uk_103 + 1868625*uk_104 + 489995*uk_105 + 2784375*uk_106 + 730125*uk_107 + 191455*uk_108 + 2406104*uk_109 + 6796346*uk_11 + 1059404*uk_110 + 359120*uk_111 + 2711356*uk_112 + 4040100*uk_113 + 1059404*uk_114 + 466454*uk_115 + 158120*uk_116 + 1193806*uk_117 + 1778850*uk_118 + 466454*uk_119 + 2992421*uk_12 + 53600*uk_120 + 404680*uk_121 + 603000*uk_122 + 158120*uk_123 + 3055334*uk_124 + 4552650*uk_125 + 1193806*uk_126 + 6783750*uk_127 + 1778850*uk_128 + 466454*uk_129 + 1014380*uk_13 + 205379*uk_130 + 69620*uk_131 + 525631*uk_132 + 783225*uk_133 + 205379*uk_134 + 23600*uk_135 + 178180*uk_136 + 265500*uk_137 + 69620*uk_138 + 1345259*uk_139 + 7658569*uk_14 + 2004525*uk_140 + 525631*uk_141 + 2986875*uk_142 + 783225*uk_143 + 205379*uk_144 + 8000*uk_145 + 60400*uk_146 + 90000*uk_147 + 23600*uk_148 + 456020*uk_149 + 11411775*uk_15 + 679500*uk_150 + 178180*uk_151 + 1012500*uk_152 + 265500*uk_153 + 69620*uk_154 + 3442951*uk_155 + 5130225*uk_156 + 1345259*uk_157 + 7644375*uk_158 + 2004525*uk_159 + 2992421*uk_16 + 525631*uk_160 + 11390625*uk_161 + 2986875*uk_162 + 783225*uk_163 + 205379*uk_164 + 3025*uk_17 + 7370*uk_18 + 3245*uk_19 + 55*uk_2 + 1100*uk_20 + 8305*uk_21 + 12375*uk_22 + 3245*uk_23 + 17956*uk_24 + 7906*uk_25 + 2680*uk_26 + 20234*uk_27 + 30150*uk_28 + 7906*uk_29 + 134*uk_3 + 3481*uk_30 + 1180*uk_31 + 8909*uk_32 + 13275*uk_33 + 3481*uk_34 + 400*uk_35 + 3020*uk_36 + 4500*uk_37 + 1180*uk_38 + 22801*uk_39 + 59*uk_4 + 33975*uk_40 + 8909*uk_41 + 50625*uk_42 + 13275*uk_43 + 3481*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 344703872774*uk_47 + 151772600699*uk_48 + 51448339220*uk_49 + 20*uk_5 + 388434961111*uk_50 + 578793816225*uk_51 + 151772600699*uk_52 + 153424975*uk_53 + 373799030*uk_54 + 164583155*uk_55 + 55790900*uk_56 + 421221295*uk_57 + 627647625*uk_58 + 164583155*uk_59 + 151*uk_6 + 910710364*uk_60 + 400984414*uk_61 + 135926920*uk_62 + 1026248246*uk_63 + 1529177850*uk_64 + 400984414*uk_65 + 176552839*uk_66 + 59848420*uk_67 + 451855571*uk_68 + 673294725*uk_69 + 225*uk_7 + 176552839*uk_70 + 20287600*uk_71 + 153171380*uk_72 + 228235500*uk_73 + 59848420*uk_74 + 1156443919*uk_75 + 1723178025*uk_76 + 451855571*uk_77 + 2567649375*uk_78 + 673294725*uk_79 + 59*uk_8 + 176552839*uk_80 + 166375*uk_81 + 405350*uk_82 + 178475*uk_83 + 60500*uk_84 + 456775*uk_85 + 680625*uk_86 + 178475*uk_87 + 987580*uk_88 + 434830*uk_89 + 2572416961*uk_9 + 147400*uk_90 + 1112870*uk_91 + 1658250*uk_92 + 434830*uk_93 + 191455*uk_94 + 64900*uk_95 + 489995*uk_96 + 730125*uk_97 + 191455*uk_98 + 22000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 134640*uk_100 + 198000*uk_101 + 117920*uk_102 + 1287495*uk_103 + 1893375*uk_104 + 1127610*uk_105 + 2784375*uk_106 + 1658250*uk_107 + 987580*uk_108 + 438976*uk_109 + 3854644*uk_11 + 773984*uk_110 + 92416*uk_111 + 883728*uk_112 + 1299600*uk_113 + 773984*uk_114 + 1364656*uk_115 + 162944*uk_116 + 1558152*uk_117 + 2291400*uk_118 + 1364656*uk_119 + 6796346*uk_12 + 19456*uk_120 + 186048*uk_121 + 273600*uk_122 + 162944*uk_123 + 1779084*uk_124 + 2616300*uk_125 + 1558152*uk_126 + 3847500*uk_127 + 2291400*uk_128 + 1364656*uk_129 + 811504*uk_13 + 2406104*uk_130 + 287296*uk_131 + 2747268*uk_132 + 4040100*uk_133 + 2406104*uk_134 + 34304*uk_135 + 328032*uk_136 + 482400*uk_137 + 287296*uk_138 + 3136806*uk_139 + 7760007*uk_14 + 4612950*uk_140 + 2747268*uk_141 + 6783750*uk_142 + 4040100*uk_143 + 2406104*uk_144 + 4096*uk_145 + 39168*uk_146 + 57600*uk_147 + 34304*uk_148 + 374544*uk_149 + 11411775*uk_15 + 550800*uk_150 + 328032*uk_151 + 810000*uk_152 + 482400*uk_153 + 287296*uk_154 + 3581577*uk_155 + 5267025*uk_156 + 3136806*uk_157 + 7745625*uk_158 + 4612950*uk_159 + 6796346*uk_16 + 2747268*uk_160 + 11390625*uk_161 + 6783750*uk_162 + 4040100*uk_163 + 2406104*uk_164 + 3025*uk_17 + 4180*uk_18 + 7370*uk_19 + 55*uk_2 + 880*uk_20 + 8415*uk_21 + 12375*uk_22 + 7370*uk_23 + 5776*uk_24 + 10184*uk_25 + 1216*uk_26 + 11628*uk_27 + 17100*uk_28 + 10184*uk_29 + 76*uk_3 + 17956*uk_30 + 2144*uk_31 + 20502*uk_32 + 30150*uk_33 + 17956*uk_34 + 256*uk_35 + 2448*uk_36 + 3600*uk_37 + 2144*uk_38 + 23409*uk_39 + 134*uk_4 + 34425*uk_40 + 20502*uk_41 + 50625*uk_42 + 30150*uk_43 + 17956*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 195503689036*uk_47 + 344703872774*uk_48 + 41158671376*uk_49 + 16*uk_5 + 393579795033*uk_50 + 578793816225*uk_51 + 344703872774*uk_52 + 153424975*uk_53 + 212005420*uk_54 + 373799030*uk_55 + 44632720*uk_56 + 426800385*uk_57 + 627647625*uk_58 + 373799030*uk_59 + 153*uk_6 + 292952944*uk_60 + 516522296*uk_61 + 61674304*uk_62 + 589760532*uk_63 + 867294900*uk_64 + 516522296*uk_65 + 910710364*uk_66 + 108741536*uk_67 + 1039840938*uk_68 + 1529177850*uk_69 + 225*uk_7 + 910710364*uk_70 + 12984064*uk_71 + 124160112*uk_72 + 182588400*uk_73 + 108741536*uk_74 + 1187281071*uk_75 + 1746001575*uk_76 + 1039840938*uk_77 + 2567649375*uk_78 + 1529177850*uk_79 + 134*uk_8 + 910710364*uk_80 + 166375*uk_81 + 229900*uk_82 + 405350*uk_83 + 48400*uk_84 + 462825*uk_85 + 680625*uk_86 + 405350*uk_87 + 317680*uk_88 + 560120*uk_89 + 2572416961*uk_9 + 66880*uk_90 + 639540*uk_91 + 940500*uk_92 + 560120*uk_93 + 987580*uk_94 + 117920*uk_95 + 1127610*uk_96 + 1658250*uk_97 + 987580*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 136400*uk_100 + 198000*uk_101 + 66880*uk_102 + 1321375*uk_103 + 1918125*uk_104 + 647900*uk_105 + 2784375*uk_106 + 940500*uk_107 + 317680*uk_108 + 39304*uk_109 + 1724446*uk_11 + 87856*uk_110 + 18496*uk_111 + 179180*uk_112 + 260100*uk_113 + 87856*uk_114 + 196384*uk_115 + 41344*uk_116 + 400520*uk_117 + 581400*uk_118 + 196384*uk_119 + 3854644*uk_12 + 8704*uk_120 + 84320*uk_121 + 122400*uk_122 + 41344*uk_123 + 816850*uk_124 + 1185750*uk_125 + 400520*uk_126 + 1721250*uk_127 + 581400*uk_128 + 196384*uk_129 + 811504*uk_13 + 438976*uk_130 + 92416*uk_131 + 895280*uk_132 + 1299600*uk_133 + 438976*uk_134 + 19456*uk_135 + 188480*uk_136 + 273600*uk_137 + 92416*uk_138 + 1825900*uk_139 + 7861445*uk_14 + 2650500*uk_140 + 895280*uk_141 + 3847500*uk_142 + 1299600*uk_143 + 438976*uk_144 + 4096*uk_145 + 39680*uk_146 + 57600*uk_147 + 19456*uk_148 + 384400*uk_149 + 11411775*uk_15 + 558000*uk_150 + 188480*uk_151 + 810000*uk_152 + 273600*uk_153 + 92416*uk_154 + 3723875*uk_155 + 5405625*uk_156 + 1825900*uk_157 + 7846875*uk_158 + 2650500*uk_159 + 3854644*uk_16 + 895280*uk_160 + 11390625*uk_161 + 3847500*uk_162 + 1299600*uk_163 + 438976*uk_164 + 3025*uk_17 + 1870*uk_18 + 4180*uk_19 + 55*uk_2 + 880*uk_20 + 8525*uk_21 + 12375*uk_22 + 4180*uk_23 + 1156*uk_24 + 2584*uk_25 + 544*uk_26 + 5270*uk_27 + 7650*uk_28 + 2584*uk_29 + 34*uk_3 + 5776*uk_30 + 1216*uk_31 + 11780*uk_32 + 17100*uk_33 + 5776*uk_34 + 256*uk_35 + 2480*uk_36 + 3600*uk_37 + 1216*uk_38 + 24025*uk_39 + 76*uk_4 + 34875*uk_40 + 11780*uk_41 + 50625*uk_42 + 17100*uk_43 + 5776*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 87462176674*uk_47 + 195503689036*uk_48 + 41158671376*uk_49 + 16*uk_5 + 398724628955*uk_50 + 578793816225*uk_51 + 195503689036*uk_52 + 153424975*uk_53 + 94844530*uk_54 + 212005420*uk_55 + 44632720*uk_56 + 432379475*uk_57 + 627647625*uk_58 + 212005420*uk_59 + 155*uk_6 + 58631164*uk_60 + 131057896*uk_61 + 27591136*uk_62 + 267289130*uk_63 + 388000350*uk_64 + 131057896*uk_65 + 292952944*uk_66 + 61674304*uk_67 + 597469820*uk_68 + 867294900*uk_69 + 225*uk_7 + 292952944*uk_70 + 12984064*uk_71 + 125783120*uk_72 + 182588400*uk_73 + 61674304*uk_74 + 1218523975*uk_75 + 1768825125*uk_76 + 597469820*uk_77 + 2567649375*uk_78 + 867294900*uk_79 + 76*uk_8 + 292952944*uk_80 + 166375*uk_81 + 102850*uk_82 + 229900*uk_83 + 48400*uk_84 + 468875*uk_85 + 680625*uk_86 + 229900*uk_87 + 63580*uk_88 + 142120*uk_89 + 2572416961*uk_9 + 29920*uk_90 + 289850*uk_91 + 420750*uk_92 + 142120*uk_93 + 317680*uk_94 + 66880*uk_95 + 647900*uk_96 + 940500*uk_97 + 317680*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 138160*uk_100 + 198000*uk_101 + 29920*uk_102 + 1355695*uk_103 + 1942875*uk_104 + 293590*uk_105 + 2784375*uk_106 + 420750*uk_107 + 63580*uk_108 + 512*uk_109 + 405752*uk_11 + 2176*uk_110 + 1024*uk_111 + 10048*uk_112 + 14400*uk_113 + 2176*uk_114 + 9248*uk_115 + 4352*uk_116 + 42704*uk_117 + 61200*uk_118 + 9248*uk_119 + 1724446*uk_12 + 2048*uk_120 + 20096*uk_121 + 28800*uk_122 + 4352*uk_123 + 197192*uk_124 + 282600*uk_125 + 42704*uk_126 + 405000*uk_127 + 61200*uk_128 + 9248*uk_129 + 811504*uk_13 + 39304*uk_130 + 18496*uk_131 + 181492*uk_132 + 260100*uk_133 + 39304*uk_134 + 8704*uk_135 + 85408*uk_136 + 122400*uk_137 + 18496*uk_138 + 838066*uk_139 + 7962883*uk_14 + 1201050*uk_140 + 181492*uk_141 + 1721250*uk_142 + 260100*uk_143 + 39304*uk_144 + 4096*uk_145 + 40192*uk_146 + 57600*uk_147 + 8704*uk_148 + 394384*uk_149 + 11411775*uk_15 + 565200*uk_150 + 85408*uk_151 + 810000*uk_152 + 122400*uk_153 + 18496*uk_154 + 3869893*uk_155 + 5546025*uk_156 + 838066*uk_157 + 7948125*uk_158 + 1201050*uk_159 + 1724446*uk_16 + 181492*uk_160 + 11390625*uk_161 + 1721250*uk_162 + 260100*uk_163 + 39304*uk_164 + 3025*uk_17 + 440*uk_18 + 1870*uk_19 + 55*uk_2 + 880*uk_20 + 8635*uk_21 + 12375*uk_22 + 1870*uk_23 + 64*uk_24 + 272*uk_25 + 128*uk_26 + 1256*uk_27 + 1800*uk_28 + 272*uk_29 + 8*uk_3 + 1156*uk_30 + 544*uk_31 + 5338*uk_32 + 7650*uk_33 + 1156*uk_34 + 256*uk_35 + 2512*uk_36 + 3600*uk_37 + 544*uk_38 + 24649*uk_39 + 34*uk_4 + 35325*uk_40 + 5338*uk_41 + 50625*uk_42 + 7650*uk_43 + 1156*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 20579335688*uk_47 + 87462176674*uk_48 + 41158671376*uk_49 + 16*uk_5 + 403869462877*uk_50 + 578793816225*uk_51 + 87462176674*uk_52 + 153424975*uk_53 + 22316360*uk_54 + 94844530*uk_55 + 44632720*uk_56 + 437958565*uk_57 + 627647625*uk_58 + 94844530*uk_59 + 157*uk_6 + 3246016*uk_60 + 13795568*uk_61 + 6492032*uk_62 + 63703064*uk_63 + 91294200*uk_64 + 13795568*uk_65 + 58631164*uk_66 + 27591136*uk_67 + 270738022*uk_68 + 388000350*uk_69 + 225*uk_7 + 58631164*uk_70 + 12984064*uk_71 + 127406128*uk_72 + 182588400*uk_73 + 27591136*uk_74 + 1250172631*uk_75 + 1791648675*uk_76 + 270738022*uk_77 + 2567649375*uk_78 + 388000350*uk_79 + 34*uk_8 + 58631164*uk_80 + 166375*uk_81 + 24200*uk_82 + 102850*uk_83 + 48400*uk_84 + 474925*uk_85 + 680625*uk_86 + 102850*uk_87 + 3520*uk_88 + 14960*uk_89 + 2572416961*uk_9 + 7040*uk_90 + 69080*uk_91 + 99000*uk_92 + 14960*uk_93 + 63580*uk_94 + 29920*uk_95 + 293590*uk_96 + 420750*uk_97 + 63580*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 174900*uk_100 + 247500*uk_101 + 8800*uk_102 + 1390455*uk_103 + 1967625*uk_104 + 69960*uk_105 + 2784375*uk_106 + 99000*uk_107 + 3520*uk_108 + 3869893*uk_109 + 7962883*uk_11 + 197192*uk_110 + 492980*uk_111 + 3919191*uk_112 + 5546025*uk_113 + 197192*uk_114 + 10048*uk_115 + 25120*uk_116 + 199704*uk_117 + 282600*uk_118 + 10048*uk_119 + 405752*uk_12 + 62800*uk_120 + 499260*uk_121 + 706500*uk_122 + 25120*uk_123 + 3969117*uk_124 + 5616675*uk_125 + 199704*uk_126 + 7948125*uk_127 + 282600*uk_128 + 10048*uk_129 + 1014380*uk_13 + 512*uk_130 + 1280*uk_131 + 10176*uk_132 + 14400*uk_133 + 512*uk_134 + 3200*uk_135 + 25440*uk_136 + 36000*uk_137 + 1280*uk_138 + 202248*uk_139 + 8064321*uk_14 + 286200*uk_140 + 10176*uk_141 + 405000*uk_142 + 14400*uk_143 + 512*uk_144 + 8000*uk_145 + 63600*uk_146 + 90000*uk_147 + 3200*uk_148 + 505620*uk_149 + 11411775*uk_15 + 715500*uk_150 + 25440*uk_151 + 1012500*uk_152 + 36000*uk_153 + 1280*uk_154 + 4019679*uk_155 + 5688225*uk_156 + 202248*uk_157 + 8049375*uk_158 + 286200*uk_159 + 405752*uk_16 + 10176*uk_160 + 11390625*uk_161 + 405000*uk_162 + 14400*uk_163 + 512*uk_164 + 3025*uk_17 + 8635*uk_18 + 440*uk_19 + 55*uk_2 + 1100*uk_20 + 8745*uk_21 + 12375*uk_22 + 440*uk_23 + 24649*uk_24 + 1256*uk_25 + 3140*uk_26 + 24963*uk_27 + 35325*uk_28 + 1256*uk_29 + 157*uk_3 + 64*uk_30 + 160*uk_31 + 1272*uk_32 + 1800*uk_33 + 64*uk_34 + 400*uk_35 + 3180*uk_36 + 4500*uk_37 + 160*uk_38 + 25281*uk_39 + 8*uk_4 + 35775*uk_40 + 1272*uk_41 + 50625*uk_42 + 1800*uk_43 + 64*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 403869462877*uk_47 + 20579335688*uk_48 + 51448339220*uk_49 + 20*uk_5 + 409014296799*uk_50 + 578793816225*uk_51 + 20579335688*uk_52 + 153424975*uk_53 + 437958565*uk_54 + 22316360*uk_55 + 55790900*uk_56 + 443537655*uk_57 + 627647625*uk_58 + 22316360*uk_59 + 159*uk_6 + 1250172631*uk_60 + 63703064*uk_61 + 159257660*uk_62 + 1266098397*uk_63 + 1791648675*uk_64 + 63703064*uk_65 + 3246016*uk_66 + 8115040*uk_67 + 64514568*uk_68 + 91294200*uk_69 + 225*uk_7 + 3246016*uk_70 + 20287600*uk_71 + 161286420*uk_72 + 228235500*uk_73 + 8115040*uk_74 + 1282227039*uk_75 + 1814472225*uk_76 + 64514568*uk_77 + 2567649375*uk_78 + 91294200*uk_79 + 8*uk_8 + 3246016*uk_80 + 166375*uk_81 + 474925*uk_82 + 24200*uk_83 + 60500*uk_84 + 480975*uk_85 + 680625*uk_86 + 24200*uk_87 + 1355695*uk_88 + 69080*uk_89 + 2572416961*uk_9 + 172700*uk_90 + 1372965*uk_91 + 1942875*uk_92 + 69080*uk_93 + 3520*uk_94 + 8800*uk_95 + 69960*uk_96 + 99000*uk_97 + 3520*uk_98 + 22000*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 106260*uk_100 + 148500*uk_101 + 103620*uk_102 + 1425655*uk_103 + 1992375*uk_104 + 1390235*uk_105 + 2784375*uk_106 + 1942875*uk_107 + 1355695*uk_108 + 64*uk_109 + 202876*uk_11 + 2512*uk_110 + 192*uk_111 + 2576*uk_112 + 3600*uk_113 + 2512*uk_114 + 98596*uk_115 + 7536*uk_116 + 101108*uk_117 + 141300*uk_118 + 98596*uk_119 + 7962883*uk_12 + 576*uk_120 + 7728*uk_121 + 10800*uk_122 + 7536*uk_123 + 103684*uk_124 + 144900*uk_125 + 101108*uk_126 + 202500*uk_127 + 141300*uk_128 + 98596*uk_129 + 608628*uk_13 + 3869893*uk_130 + 295788*uk_131 + 3968489*uk_132 + 5546025*uk_133 + 3869893*uk_134 + 22608*uk_135 + 303324*uk_136 + 423900*uk_137 + 295788*uk_138 + 4069597*uk_139 + 8165759*uk_14 + 5687325*uk_140 + 3968489*uk_141 + 7948125*uk_142 + 5546025*uk_143 + 3869893*uk_144 + 1728*uk_145 + 23184*uk_146 + 32400*uk_147 + 22608*uk_148 + 311052*uk_149 + 11411775*uk_15 + 434700*uk_150 + 303324*uk_151 + 607500*uk_152 + 423900*uk_153 + 295788*uk_154 + 4173281*uk_155 + 5832225*uk_156 + 4069597*uk_157 + 8150625*uk_158 + 5687325*uk_159 + 7962883*uk_16 + 3968489*uk_160 + 11390625*uk_161 + 7948125*uk_162 + 5546025*uk_163 + 3869893*uk_164 + 3025*uk_17 + 220*uk_18 + 8635*uk_19 + 55*uk_2 + 660*uk_20 + 8855*uk_21 + 12375*uk_22 + 8635*uk_23 + 16*uk_24 + 628*uk_25 + 48*uk_26 + 644*uk_27 + 900*uk_28 + 628*uk_29 + 4*uk_3 + 24649*uk_30 + 1884*uk_31 + 25277*uk_32 + 35325*uk_33 + 24649*uk_34 + 144*uk_35 + 1932*uk_36 + 2700*uk_37 + 1884*uk_38 + 25921*uk_39 + 157*uk_4 + 36225*uk_40 + 25277*uk_41 + 50625*uk_42 + 35325*uk_43 + 24649*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 10289667844*uk_47 + 403869462877*uk_48 + 30869003532*uk_49 + 12*uk_5 + 414159130721*uk_50 + 578793816225*uk_51 + 403869462877*uk_52 + 153424975*uk_53 + 11158180*uk_54 + 437958565*uk_55 + 33474540*uk_56 + 449116745*uk_57 + 627647625*uk_58 + 437958565*uk_59 + 161*uk_6 + 811504*uk_60 + 31851532*uk_61 + 2434512*uk_62 + 32663036*uk_63 + 45647100*uk_64 + 31851532*uk_65 + 1250172631*uk_66 + 95554596*uk_67 + 1282024163*uk_68 + 1791648675*uk_69 + 225*uk_7 + 1250172631*uk_70 + 7303536*uk_71 + 97989108*uk_72 + 136941300*uk_73 + 95554596*uk_74 + 1314687199*uk_75 + 1837295775*uk_76 + 1282024163*uk_77 + 2567649375*uk_78 + 1791648675*uk_79 + 157*uk_8 + 1250172631*uk_80 + 166375*uk_81 + 12100*uk_82 + 474925*uk_83 + 36300*uk_84 + 487025*uk_85 + 680625*uk_86 + 474925*uk_87 + 880*uk_88 + 34540*uk_89 + 2572416961*uk_9 + 2640*uk_90 + 35420*uk_91 + 49500*uk_92 + 34540*uk_93 + 1355695*uk_94 + 103620*uk_95 + 1390235*uk_96 + 1942875*uk_97 + 1355695*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 143440*uk_100 + 198000*uk_101 + 3520*uk_102 + 1461295*uk_103 + 2017125*uk_104 + 35860*uk_105 + 2784375*uk_106 + 49500*uk_107 + 880*uk_108 + 17576*uk_109 + 1318694*uk_11 + 2704*uk_110 + 10816*uk_111 + 110188*uk_112 + 152100*uk_113 + 2704*uk_114 + 416*uk_115 + 1664*uk_116 + 16952*uk_117 + 23400*uk_118 + 416*uk_119 + 202876*uk_12 + 6656*uk_120 + 67808*uk_121 + 93600*uk_122 + 1664*uk_123 + 690794*uk_124 + 953550*uk_125 + 16952*uk_126 + 1316250*uk_127 + 23400*uk_128 + 416*uk_129 + 811504*uk_13 + 64*uk_130 + 256*uk_131 + 2608*uk_132 + 3600*uk_133 + 64*uk_134 + 1024*uk_135 + 10432*uk_136 + 14400*uk_137 + 256*uk_138 + 106276*uk_139 + 8267197*uk_14 + 146700*uk_140 + 2608*uk_141 + 202500*uk_142 + 3600*uk_143 + 64*uk_144 + 4096*uk_145 + 41728*uk_146 + 57600*uk_147 + 1024*uk_148 + 425104*uk_149 + 11411775*uk_15 + 586800*uk_150 + 10432*uk_151 + 810000*uk_152 + 14400*uk_153 + 256*uk_154 + 4330747*uk_155 + 5978025*uk_156 + 106276*uk_157 + 8251875*uk_158 + 146700*uk_159 + 202876*uk_16 + 2608*uk_160 + 11390625*uk_161 + 202500*uk_162 + 3600*uk_163 + 64*uk_164 + 3025*uk_17 + 1430*uk_18 + 220*uk_19 + 55*uk_2 + 880*uk_20 + 8965*uk_21 + 12375*uk_22 + 220*uk_23 + 676*uk_24 + 104*uk_25 + 416*uk_26 + 4238*uk_27 + 5850*uk_28 + 104*uk_29 + 26*uk_3 + 16*uk_30 + 64*uk_31 + 652*uk_32 + 900*uk_33 + 16*uk_34 + 256*uk_35 + 2608*uk_36 + 3600*uk_37 + 64*uk_38 + 26569*uk_39 + 4*uk_4 + 36675*uk_40 + 652*uk_41 + 50625*uk_42 + 900*uk_43 + 16*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 66882840986*uk_47 + 10289667844*uk_48 + 41158671376*uk_49 + 16*uk_5 + 419303964643*uk_50 + 578793816225*uk_51 + 10289667844*uk_52 + 153424975*uk_53 + 72528170*uk_54 + 11158180*uk_55 + 44632720*uk_56 + 454695835*uk_57 + 627647625*uk_58 + 11158180*uk_59 + 163*uk_6 + 34286044*uk_60 + 5274776*uk_61 + 21099104*uk_62 + 214947122*uk_63 + 296706150*uk_64 + 5274776*uk_65 + 811504*uk_66 + 3246016*uk_67 + 33068788*uk_68 + 45647100*uk_69 + 225*uk_7 + 811504*uk_70 + 12984064*uk_71 + 132275152*uk_72 + 182588400*uk_73 + 3246016*uk_74 + 1347553111*uk_75 + 1860119325*uk_76 + 33068788*uk_77 + 2567649375*uk_78 + 45647100*uk_79 + 4*uk_8 + 811504*uk_80 + 166375*uk_81 + 78650*uk_82 + 12100*uk_83 + 48400*uk_84 + 493075*uk_85 + 680625*uk_86 + 12100*uk_87 + 37180*uk_88 + 5720*uk_89 + 2572416961*uk_9 + 22880*uk_90 + 233090*uk_91 + 321750*uk_92 + 5720*uk_93 + 880*uk_94 + 3520*uk_95 + 35860*uk_96 + 49500*uk_97 + 880*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 145200*uk_100 + 198000*uk_101 + 22880*uk_102 + 1497375*uk_103 + 2041875*uk_104 + 235950*uk_105 + 2784375*uk_106 + 321750*uk_107 + 37180*uk_108 + 262144*uk_109 + 3246016*uk_11 + 106496*uk_110 + 65536*uk_111 + 675840*uk_112 + 921600*uk_113 + 106496*uk_114 + 43264*uk_115 + 26624*uk_116 + 274560*uk_117 + 374400*uk_118 + 43264*uk_119 + 1318694*uk_12 + 16384*uk_120 + 168960*uk_121 + 230400*uk_122 + 26624*uk_123 + 1742400*uk_124 + 2376000*uk_125 + 274560*uk_126 + 3240000*uk_127 + 374400*uk_128 + 43264*uk_129 + 811504*uk_13 + 17576*uk_130 + 10816*uk_131 + 111540*uk_132 + 152100*uk_133 + 17576*uk_134 + 6656*uk_135 + 68640*uk_136 + 93600*uk_137 + 10816*uk_138 + 707850*uk_139 + 8368635*uk_14 + 965250*uk_140 + 111540*uk_141 + 1316250*uk_142 + 152100*uk_143 + 17576*uk_144 + 4096*uk_145 + 42240*uk_146 + 57600*uk_147 + 6656*uk_148 + 435600*uk_149 + 11411775*uk_15 + 594000*uk_150 + 68640*uk_151 + 810000*uk_152 + 93600*uk_153 + 10816*uk_154 + 4492125*uk_155 + 6125625*uk_156 + 707850*uk_157 + 8353125*uk_158 + 965250*uk_159 + 1318694*uk_16 + 111540*uk_160 + 11390625*uk_161 + 1316250*uk_162 + 152100*uk_163 + 17576*uk_164 + 3025*uk_17 + 3520*uk_18 + 1430*uk_19 + 55*uk_2 + 880*uk_20 + 9075*uk_21 + 12375*uk_22 + 1430*uk_23 + 4096*uk_24 + 1664*uk_25 + 1024*uk_26 + 10560*uk_27 + 14400*uk_28 + 1664*uk_29 + 64*uk_3 + 676*uk_30 + 416*uk_31 + 4290*uk_32 + 5850*uk_33 + 676*uk_34 + 256*uk_35 + 2640*uk_36 + 3600*uk_37 + 416*uk_38 + 27225*uk_39 + 26*uk_4 + 37125*uk_40 + 4290*uk_41 + 50625*uk_42 + 5850*uk_43 + 676*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 164634685504*uk_47 + 66882840986*uk_48 + 41158671376*uk_49 + 16*uk_5 + 424448798565*uk_50 + 578793816225*uk_51 + 66882840986*uk_52 + 153424975*uk_53 + 178530880*uk_54 + 72528170*uk_55 + 44632720*uk_56 + 460274925*uk_57 + 627647625*uk_58 + 72528170*uk_59 + 165*uk_6 + 207745024*uk_60 + 84396416*uk_61 + 51936256*uk_62 + 535592640*uk_63 + 730353600*uk_64 + 84396416*uk_65 + 34286044*uk_66 + 21099104*uk_67 + 217584510*uk_68 + 296706150*uk_69 + 225*uk_7 + 34286044*uk_70 + 12984064*uk_71 + 133898160*uk_72 + 182588400*uk_73 + 21099104*uk_74 + 1380824775*uk_75 + 1882942875*uk_76 + 217584510*uk_77 + 2567649375*uk_78 + 296706150*uk_79 + 26*uk_8 + 34286044*uk_80 + 166375*uk_81 + 193600*uk_82 + 78650*uk_83 + 48400*uk_84 + 499125*uk_85 + 680625*uk_86 + 78650*uk_87 + 225280*uk_88 + 91520*uk_89 + 2572416961*uk_9 + 56320*uk_90 + 580800*uk_91 + 792000*uk_92 + 91520*uk_93 + 37180*uk_94 + 22880*uk_95 + 235950*uk_96 + 321750*uk_97 + 37180*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 146960*uk_100 + 198000*uk_101 + 56320*uk_102 + 1533895*uk_103 + 2066625*uk_104 + 587840*uk_105 + 2784375*uk_106 + 792000*uk_107 + 225280*uk_108 + 1643032*uk_109 + 5984842*uk_11 + 891136*uk_110 + 222784*uk_111 + 2325308*uk_112 + 3132900*uk_113 + 891136*uk_114 + 483328*uk_115 + 120832*uk_116 + 1261184*uk_117 + 1699200*uk_118 + 483328*uk_119 + 3246016*uk_12 + 30208*uk_120 + 315296*uk_121 + 424800*uk_122 + 120832*uk_123 + 3290902*uk_124 + 4433850*uk_125 + 1261184*uk_126 + 5973750*uk_127 + 1699200*uk_128 + 483328*uk_129 + 811504*uk_13 + 262144*uk_130 + 65536*uk_131 + 684032*uk_132 + 921600*uk_133 + 262144*uk_134 + 16384*uk_135 + 171008*uk_136 + 230400*uk_137 + 65536*uk_138 + 1784896*uk_139 + 8470073*uk_14 + 2404800*uk_140 + 684032*uk_141 + 3240000*uk_142 + 921600*uk_143 + 262144*uk_144 + 4096*uk_145 + 42752*uk_146 + 57600*uk_147 + 16384*uk_148 + 446224*uk_149 + 11411775*uk_15 + 601200*uk_150 + 171008*uk_151 + 810000*uk_152 + 230400*uk_153 + 65536*uk_154 + 4657463*uk_155 + 6275025*uk_156 + 1784896*uk_157 + 8454375*uk_158 + 2404800*uk_159 + 3246016*uk_16 + 684032*uk_160 + 11390625*uk_161 + 3240000*uk_162 + 921600*uk_163 + 262144*uk_164 + 3025*uk_17 + 6490*uk_18 + 3520*uk_19 + 55*uk_2 + 880*uk_20 + 9185*uk_21 + 12375*uk_22 + 3520*uk_23 + 13924*uk_24 + 7552*uk_25 + 1888*uk_26 + 19706*uk_27 + 26550*uk_28 + 7552*uk_29 + 118*uk_3 + 4096*uk_30 + 1024*uk_31 + 10688*uk_32 + 14400*uk_33 + 4096*uk_34 + 256*uk_35 + 2672*uk_36 + 3600*uk_37 + 1024*uk_38 + 27889*uk_39 + 64*uk_4 + 37575*uk_40 + 10688*uk_41 + 50625*uk_42 + 14400*uk_43 + 4096*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 303545201398*uk_47 + 164634685504*uk_48 + 41158671376*uk_49 + 16*uk_5 + 429593632487*uk_50 + 578793816225*uk_51 + 164634685504*uk_52 + 153424975*uk_53 + 329166310*uk_54 + 178530880*uk_55 + 44632720*uk_56 + 465854015*uk_57 + 627647625*uk_58 + 178530880*uk_59 + 167*uk_6 + 706211356*uk_60 + 383029888*uk_61 + 95757472*uk_62 + 999468614*uk_63 + 1346589450*uk_64 + 383029888*uk_65 + 207745024*uk_66 + 51936256*uk_67 + 542084672*uk_68 + 730353600*uk_69 + 225*uk_7 + 207745024*uk_70 + 12984064*uk_71 + 135521168*uk_72 + 182588400*uk_73 + 51936256*uk_74 + 1414502191*uk_75 + 1905766425*uk_76 + 542084672*uk_77 + 2567649375*uk_78 + 730353600*uk_79 + 64*uk_8 + 207745024*uk_80 + 166375*uk_81 + 356950*uk_82 + 193600*uk_83 + 48400*uk_84 + 505175*uk_85 + 680625*uk_86 + 193600*uk_87 + 765820*uk_88 + 415360*uk_89 + 2572416961*uk_9 + 103840*uk_90 + 1083830*uk_91 + 1460250*uk_92 + 415360*uk_93 + 225280*uk_94 + 56320*uk_95 + 587840*uk_96 + 792000*uk_97 + 225280*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 111540*uk_100 + 148500*uk_101 + 77880*uk_102 + 1570855*uk_103 + 2091375*uk_104 + 1096810*uk_105 + 2784375*uk_106 + 1460250*uk_107 + 765820*uk_108 + 6859*uk_109 + 963661*uk_11 + 42598*uk_110 + 4332*uk_111 + 61009*uk_112 + 81225*uk_113 + 42598*uk_114 + 264556*uk_115 + 26904*uk_116 + 378898*uk_117 + 504450*uk_118 + 264556*uk_119 + 5984842*uk_12 + 2736*uk_120 + 38532*uk_121 + 51300*uk_122 + 26904*uk_123 + 542659*uk_124 + 722475*uk_125 + 378898*uk_126 + 961875*uk_127 + 504450*uk_128 + 264556*uk_129 + 608628*uk_13 + 1643032*uk_130 + 167088*uk_131 + 2353156*uk_132 + 3132900*uk_133 + 1643032*uk_134 + 16992*uk_135 + 239304*uk_136 + 318600*uk_137 + 167088*uk_138 + 3370198*uk_139 + 8571511*uk_14 + 4486950*uk_140 + 2353156*uk_141 + 5973750*uk_142 + 3132900*uk_143 + 1643032*uk_144 + 1728*uk_145 + 24336*uk_146 + 32400*uk_147 + 16992*uk_148 + 342732*uk_149 + 11411775*uk_15 + 456300*uk_150 + 239304*uk_151 + 607500*uk_152 + 318600*uk_153 + 167088*uk_154 + 4826809*uk_155 + 6426225*uk_156 + 3370198*uk_157 + 8555625*uk_158 + 4486950*uk_159 + 5984842*uk_16 + 2353156*uk_160 + 11390625*uk_161 + 5973750*uk_162 + 3132900*uk_163 + 1643032*uk_164 + 3025*uk_17 + 1045*uk_18 + 6490*uk_19 + 55*uk_2 + 660*uk_20 + 9295*uk_21 + 12375*uk_22 + 6490*uk_23 + 361*uk_24 + 2242*uk_25 + 228*uk_26 + 3211*uk_27 + 4275*uk_28 + 2242*uk_29 + 19*uk_3 + 13924*uk_30 + 1416*uk_31 + 19942*uk_32 + 26550*uk_33 + 13924*uk_34 + 144*uk_35 + 2028*uk_36 + 2700*uk_37 + 1416*uk_38 + 28561*uk_39 + 118*uk_4 + 38025*uk_40 + 19942*uk_41 + 50625*uk_42 + 26550*uk_43 + 13924*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 48875922259*uk_47 + 303545201398*uk_48 + 30869003532*uk_49 + 12*uk_5 + 434738466409*uk_50 + 578793816225*uk_51 + 303545201398*uk_52 + 153424975*uk_53 + 53001355*uk_54 + 329166310*uk_55 + 33474540*uk_56 + 471433105*uk_57 + 627647625*uk_58 + 329166310*uk_59 + 169*uk_6 + 18309559*uk_60 + 113711998*uk_61 + 11563932*uk_62 + 162858709*uk_63 + 216823725*uk_64 + 113711998*uk_65 + 706211356*uk_66 + 71818104*uk_67 + 1011438298*uk_68 + 1346589450*uk_69 + 225*uk_7 + 706211356*uk_70 + 7303536*uk_71 + 102858132*uk_72 + 136941300*uk_73 + 71818104*uk_74 + 1448585359*uk_75 + 1928589975*uk_76 + 1011438298*uk_77 + 2567649375*uk_78 + 1346589450*uk_79 + 118*uk_8 + 706211356*uk_80 + 166375*uk_81 + 57475*uk_82 + 356950*uk_83 + 36300*uk_84 + 511225*uk_85 + 680625*uk_86 + 356950*uk_87 + 19855*uk_88 + 123310*uk_89 + 2572416961*uk_9 + 12540*uk_90 + 176605*uk_91 + 235125*uk_92 + 123310*uk_93 + 765820*uk_94 + 77880*uk_95 + 1096810*uk_96 + 1460250*uk_97 + 765820*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 150480*uk_100 + 198000*uk_101 + 16720*uk_102 + 1608255*uk_103 + 2116125*uk_104 + 178695*uk_105 + 2784375*uk_106 + 235125*uk_107 + 19855*uk_108 + 1092727*uk_109 + 5224057*uk_11 + 201571*uk_110 + 169744*uk_111 + 1814139*uk_112 + 2387025*uk_113 + 201571*uk_114 + 37183*uk_115 + 31312*uk_116 + 334647*uk_117 + 440325*uk_118 + 37183*uk_119 + 963661*uk_12 + 26368*uk_120 + 281808*uk_121 + 370800*uk_122 + 31312*uk_123 + 3011823*uk_124 + 3962925*uk_125 + 334647*uk_126 + 5214375*uk_127 + 440325*uk_128 + 37183*uk_129 + 811504*uk_13 + 6859*uk_130 + 5776*uk_131 + 61731*uk_132 + 81225*uk_133 + 6859*uk_134 + 4864*uk_135 + 51984*uk_136 + 68400*uk_137 + 5776*uk_138 + 555579*uk_139 + 8672949*uk_14 + 731025*uk_140 + 61731*uk_141 + 961875*uk_142 + 81225*uk_143 + 6859*uk_144 + 4096*uk_145 + 43776*uk_146 + 57600*uk_147 + 4864*uk_148 + 467856*uk_149 + 11411775*uk_15 + 615600*uk_150 + 51984*uk_151 + 810000*uk_152 + 68400*uk_153 + 5776*uk_154 + 5000211*uk_155 + 6579225*uk_156 + 555579*uk_157 + 8656875*uk_158 + 731025*uk_159 + 963661*uk_16 + 61731*uk_160 + 11390625*uk_161 + 961875*uk_162 + 81225*uk_163 + 6859*uk_164 + 3025*uk_17 + 5665*uk_18 + 1045*uk_19 + 55*uk_2 + 880*uk_20 + 9405*uk_21 + 12375*uk_22 + 1045*uk_23 + 10609*uk_24 + 1957*uk_25 + 1648*uk_26 + 17613*uk_27 + 23175*uk_28 + 1957*uk_29 + 103*uk_3 + 361*uk_30 + 304*uk_31 + 3249*uk_32 + 4275*uk_33 + 361*uk_34 + 256*uk_35 + 2736*uk_36 + 3600*uk_37 + 304*uk_38 + 29241*uk_39 + 19*uk_4 + 38475*uk_40 + 3249*uk_41 + 50625*uk_42 + 4275*uk_43 + 361*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 264958946983*uk_47 + 48875922259*uk_48 + 41158671376*uk_49 + 16*uk_5 + 439883300331*uk_50 + 578793816225*uk_51 + 48875922259*uk_52 + 153424975*uk_53 + 287323135*uk_54 + 53001355*uk_55 + 44632720*uk_56 + 477012195*uk_57 + 627647625*uk_58 + 53001355*uk_59 + 171*uk_6 + 538077871*uk_60 + 99257083*uk_61 + 83584912*uk_62 + 893313747*uk_63 + 1175412825*uk_64 + 99257083*uk_65 + 18309559*uk_66 + 15418576*uk_67 + 164786031*uk_68 + 216823725*uk_69 + 225*uk_7 + 18309559*uk_70 + 12984064*uk_71 + 138767184*uk_72 + 182588400*uk_73 + 15418576*uk_74 + 1483074279*uk_75 + 1951413525*uk_76 + 164786031*uk_77 + 2567649375*uk_78 + 216823725*uk_79 + 19*uk_8 + 18309559*uk_80 + 166375*uk_81 + 311575*uk_82 + 57475*uk_83 + 48400*uk_84 + 517275*uk_85 + 680625*uk_86 + 57475*uk_87 + 583495*uk_88 + 107635*uk_89 + 2572416961*uk_9 + 90640*uk_90 + 968715*uk_91 + 1274625*uk_92 + 107635*uk_93 + 19855*uk_94 + 16720*uk_95 + 178695*uk_96 + 235125*uk_97 + 19855*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 114180*uk_100 + 148500*uk_101 + 67980*uk_102 + 1646095*uk_103 + 2140875*uk_104 + 980045*uk_105 + 2784375*uk_106 + 1274625*uk_107 + 583495*uk_108 + 27000*uk_109 + 1521570*uk_11 + 92700*uk_110 + 10800*uk_111 + 155700*uk_112 + 202500*uk_113 + 92700*uk_114 + 318270*uk_115 + 37080*uk_116 + 534570*uk_117 + 695250*uk_118 + 318270*uk_119 + 5224057*uk_12 + 4320*uk_120 + 62280*uk_121 + 81000*uk_122 + 37080*uk_123 + 897870*uk_124 + 1167750*uk_125 + 534570*uk_126 + 1518750*uk_127 + 695250*uk_128 + 318270*uk_129 + 608628*uk_13 + 1092727*uk_130 + 127308*uk_131 + 1835357*uk_132 + 2387025*uk_133 + 1092727*uk_134 + 14832*uk_135 + 213828*uk_136 + 278100*uk_137 + 127308*uk_138 + 3082687*uk_139 + 8774387*uk_14 + 4009275*uk_140 + 1835357*uk_141 + 5214375*uk_142 + 2387025*uk_143 + 1092727*uk_144 + 1728*uk_145 + 24912*uk_146 + 32400*uk_147 + 14832*uk_148 + 359148*uk_149 + 11411775*uk_15 + 467100*uk_150 + 213828*uk_151 + 607500*uk_152 + 278100*uk_153 + 127308*uk_154 + 5177717*uk_155 + 6734025*uk_156 + 3082687*uk_157 + 8758125*uk_158 + 4009275*uk_159 + 5224057*uk_16 + 1835357*uk_160 + 11390625*uk_161 + 5214375*uk_162 + 2387025*uk_163 + 1092727*uk_164 + 3025*uk_17 + 1650*uk_18 + 5665*uk_19 + 55*uk_2 + 660*uk_20 + 9515*uk_21 + 12375*uk_22 + 5665*uk_23 + 900*uk_24 + 3090*uk_25 + 360*uk_26 + 5190*uk_27 + 6750*uk_28 + 3090*uk_29 + 30*uk_3 + 10609*uk_30 + 1236*uk_31 + 17819*uk_32 + 23175*uk_33 + 10609*uk_34 + 144*uk_35 + 2076*uk_36 + 2700*uk_37 + 1236*uk_38 + 29929*uk_39 + 103*uk_4 + 38925*uk_40 + 17819*uk_41 + 50625*uk_42 + 23175*uk_43 + 10609*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 77172508830*uk_47 + 264958946983*uk_48 + 30869003532*uk_49 + 12*uk_5 + 445028134253*uk_50 + 578793816225*uk_51 + 264958946983*uk_52 + 153424975*uk_53 + 83686350*uk_54 + 287323135*uk_55 + 33474540*uk_56 + 482591285*uk_57 + 627647625*uk_58 + 287323135*uk_59 + 173*uk_6 + 45647100*uk_60 + 156721710*uk_61 + 18258840*uk_62 + 263231610*uk_63 + 342353250*uk_64 + 156721710*uk_65 + 538077871*uk_66 + 62688684*uk_67 + 903761861*uk_68 + 1175412825*uk_69 + 225*uk_7 + 538077871*uk_70 + 7303536*uk_71 + 105292644*uk_72 + 136941300*uk_73 + 62688684*uk_74 + 1517968951*uk_75 + 1974237075*uk_76 + 903761861*uk_77 + 2567649375*uk_78 + 1175412825*uk_79 + 103*uk_8 + 538077871*uk_80 + 166375*uk_81 + 90750*uk_82 + 311575*uk_83 + 36300*uk_84 + 523325*uk_85 + 680625*uk_86 + 311575*uk_87 + 49500*uk_88 + 169950*uk_89 + 2572416961*uk_9 + 19800*uk_90 + 285450*uk_91 + 371250*uk_92 + 169950*uk_93 + 583495*uk_94 + 67980*uk_95 + 980045*uk_96 + 1274625*uk_97 + 583495*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 154000*uk_100 + 198000*uk_101 + 26400*uk_102 + 1684375*uk_103 + 2165625*uk_104 + 288750*uk_105 + 2784375*uk_106 + 371250*uk_107 + 49500*uk_108 + 2985984*uk_109 + 7303536*uk_11 + 622080*uk_110 + 331776*uk_111 + 3628800*uk_112 + 4665600*uk_113 + 622080*uk_114 + 129600*uk_115 + 69120*uk_116 + 756000*uk_117 + 972000*uk_118 + 129600*uk_119 + 1521570*uk_12 + 36864*uk_120 + 403200*uk_121 + 518400*uk_122 + 69120*uk_123 + 4410000*uk_124 + 5670000*uk_125 + 756000*uk_126 + 7290000*uk_127 + 972000*uk_128 + 129600*uk_129 + 811504*uk_13 + 27000*uk_130 + 14400*uk_131 + 157500*uk_132 + 202500*uk_133 + 27000*uk_134 + 7680*uk_135 + 84000*uk_136 + 108000*uk_137 + 14400*uk_138 + 918750*uk_139 + 8875825*uk_14 + 1181250*uk_140 + 157500*uk_141 + 1518750*uk_142 + 202500*uk_143 + 27000*uk_144 + 4096*uk_145 + 44800*uk_146 + 57600*uk_147 + 7680*uk_148 + 490000*uk_149 + 11411775*uk_15 + 630000*uk_150 + 84000*uk_151 + 810000*uk_152 + 108000*uk_153 + 14400*uk_154 + 5359375*uk_155 + 6890625*uk_156 + 918750*uk_157 + 8859375*uk_158 + 1181250*uk_159 + 1521570*uk_16 + 157500*uk_160 + 11390625*uk_161 + 1518750*uk_162 + 202500*uk_163 + 27000*uk_164 + 3025*uk_17 + 7920*uk_18 + 1650*uk_19 + 55*uk_2 + 880*uk_20 + 9625*uk_21 + 12375*uk_22 + 1650*uk_23 + 20736*uk_24 + 4320*uk_25 + 2304*uk_26 + 25200*uk_27 + 32400*uk_28 + 4320*uk_29 + 144*uk_3 + 900*uk_30 + 480*uk_31 + 5250*uk_32 + 6750*uk_33 + 900*uk_34 + 256*uk_35 + 2800*uk_36 + 3600*uk_37 + 480*uk_38 + 30625*uk_39 + 30*uk_4 + 39375*uk_40 + 5250*uk_41 + 50625*uk_42 + 6750*uk_43 + 900*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 370428042384*uk_47 + 77172508830*uk_48 + 41158671376*uk_49 + 16*uk_5 + 450172968175*uk_50 + 578793816225*uk_51 + 77172508830*uk_52 + 153424975*uk_53 + 401694480*uk_54 + 83686350*uk_55 + 44632720*uk_56 + 488170375*uk_57 + 627647625*uk_58 + 83686350*uk_59 + 175*uk_6 + 1051709184*uk_60 + 219106080*uk_61 + 116856576*uk_62 + 1278118800*uk_63 + 1643295600*uk_64 + 219106080*uk_65 + 45647100*uk_66 + 24345120*uk_67 + 266274750*uk_68 + 342353250*uk_69 + 225*uk_7 + 45647100*uk_70 + 12984064*uk_71 + 142013200*uk_72 + 182588400*uk_73 + 24345120*uk_74 + 1553269375*uk_75 + 1997060625*uk_76 + 266274750*uk_77 + 2567649375*uk_78 + 342353250*uk_79 + 30*uk_8 + 45647100*uk_80 + 166375*uk_81 + 435600*uk_82 + 90750*uk_83 + 48400*uk_84 + 529375*uk_85 + 680625*uk_86 + 90750*uk_87 + 1140480*uk_88 + 237600*uk_89 + 2572416961*uk_9 + 126720*uk_90 + 1386000*uk_91 + 1782000*uk_92 + 237600*uk_93 + 49500*uk_94 + 26400*uk_95 + 288750*uk_96 + 371250*uk_97 + 49500*uk_98 + 14080*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 116820*uk_100 + 148500*uk_101 + 95040*uk_102 + 1723095*uk_103 + 2190375*uk_104 + 1401840*uk_105 + 2784375*uk_106 + 1782000*uk_107 + 1140480*uk_108 + 912673*uk_109 + 4919743*uk_11 + 1354896*uk_110 + 112908*uk_111 + 1665393*uk_112 + 2117025*uk_113 + 1354896*uk_114 + 2011392*uk_115 + 167616*uk_116 + 2472336*uk_117 + 3142800*uk_118 + 2011392*uk_119 + 7303536*uk_12 + 13968*uk_120 + 206028*uk_121 + 261900*uk_122 + 167616*uk_123 + 3038913*uk_124 + 3863025*uk_125 + 2472336*uk_126 + 4910625*uk_127 + 3142800*uk_128 + 2011392*uk_129 + 608628*uk_13 + 2985984*uk_130 + 248832*uk_131 + 3670272*uk_132 + 4665600*uk_133 + 2985984*uk_134 + 20736*uk_135 + 305856*uk_136 + 388800*uk_137 + 248832*uk_138 + 4511376*uk_139 + 8977263*uk_14 + 5734800*uk_140 + 3670272*uk_141 + 7290000*uk_142 + 4665600*uk_143 + 2985984*uk_144 + 1728*uk_145 + 25488*uk_146 + 32400*uk_147 + 20736*uk_148 + 375948*uk_149 + 11411775*uk_15 + 477900*uk_150 + 305856*uk_151 + 607500*uk_152 + 388800*uk_153 + 248832*uk_154 + 5545233*uk_155 + 7049025*uk_156 + 4511376*uk_157 + 8960625*uk_158 + 5734800*uk_159 + 7303536*uk_16 + 3670272*uk_160 + 11390625*uk_161 + 7290000*uk_162 + 4665600*uk_163 + 2985984*uk_164 + 3025*uk_17 + 5335*uk_18 + 7920*uk_19 + 55*uk_2 + 660*uk_20 + 9735*uk_21 + 12375*uk_22 + 7920*uk_23 + 9409*uk_24 + 13968*uk_25 + 1164*uk_26 + 17169*uk_27 + 21825*uk_28 + 13968*uk_29 + 97*uk_3 + 20736*uk_30 + 1728*uk_31 + 25488*uk_32 + 32400*uk_33 + 20736*uk_34 + 144*uk_35 + 2124*uk_36 + 2700*uk_37 + 1728*uk_38 + 31329*uk_39 + 144*uk_4 + 39825*uk_40 + 25488*uk_41 + 50625*uk_42 + 32400*uk_43 + 20736*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 249524445217*uk_47 + 370428042384*uk_48 + 30869003532*uk_49 + 12*uk_5 + 455317802097*uk_50 + 578793816225*uk_51 + 370428042384*uk_52 + 153424975*uk_53 + 270585865*uk_54 + 401694480*uk_55 + 33474540*uk_56 + 493749465*uk_57 + 627647625*uk_58 + 401694480*uk_59 + 177*uk_6 + 477215071*uk_60 + 708442992*uk_61 + 59036916*uk_62 + 870794511*uk_63 + 1106942175*uk_64 + 708442992*uk_65 + 1051709184*uk_66 + 87642432*uk_67 + 1292725872*uk_68 + 1643295600*uk_69 + 225*uk_7 + 1051709184*uk_70 + 7303536*uk_71 + 107727156*uk_72 + 136941300*uk_73 + 87642432*uk_74 + 1588975551*uk_75 + 2019884175*uk_76 + 1292725872*uk_77 + 2567649375*uk_78 + 1643295600*uk_79 + 144*uk_8 + 1051709184*uk_80 + 166375*uk_81 + 293425*uk_82 + 435600*uk_83 + 36300*uk_84 + 535425*uk_85 + 680625*uk_86 + 435600*uk_87 + 517495*uk_88 + 768240*uk_89 + 2572416961*uk_9 + 64020*uk_90 + 944295*uk_91 + 1200375*uk_92 + 768240*uk_93 + 1140480*uk_94 + 95040*uk_95 + 1401840*uk_96 + 1782000*uk_97 + 1140480*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 118140*uk_100 + 148500*uk_101 + 64020*uk_102 + 1762255*uk_103 + 2215125*uk_104 + 954965*uk_105 + 2784375*uk_106 + 1200375*uk_107 + 517495*uk_108 + 238328*uk_109 + 3144578*uk_11 + 372868*uk_110 + 46128*uk_111 + 688076*uk_112 + 864900*uk_113 + 372868*uk_114 + 583358*uk_115 + 72168*uk_116 + 1076506*uk_117 + 1353150*uk_118 + 583358*uk_119 + 4919743*uk_12 + 8928*uk_120 + 133176*uk_121 + 167400*uk_122 + 72168*uk_123 + 1986542*uk_124 + 2497050*uk_125 + 1076506*uk_126 + 3138750*uk_127 + 1353150*uk_128 + 583358*uk_129 + 608628*uk_13 + 912673*uk_130 + 112908*uk_131 + 1684211*uk_132 + 2117025*uk_133 + 912673*uk_134 + 13968*uk_135 + 208356*uk_136 + 261900*uk_137 + 112908*uk_138 + 3107977*uk_139 + 9078701*uk_14 + 3906675*uk_140 + 1684211*uk_141 + 4910625*uk_142 + 2117025*uk_143 + 912673*uk_144 + 1728*uk_145 + 25776*uk_146 + 32400*uk_147 + 13968*uk_148 + 384492*uk_149 + 11411775*uk_15 + 483300*uk_150 + 208356*uk_151 + 607500*uk_152 + 261900*uk_153 + 112908*uk_154 + 5735339*uk_155 + 7209225*uk_156 + 3107977*uk_157 + 9061875*uk_158 + 3906675*uk_159 + 4919743*uk_16 + 1684211*uk_160 + 11390625*uk_161 + 4910625*uk_162 + 2117025*uk_163 + 912673*uk_164 + 3025*uk_17 + 3410*uk_18 + 5335*uk_19 + 55*uk_2 + 660*uk_20 + 9845*uk_21 + 12375*uk_22 + 5335*uk_23 + 3844*uk_24 + 6014*uk_25 + 744*uk_26 + 11098*uk_27 + 13950*uk_28 + 6014*uk_29 + 62*uk_3 + 9409*uk_30 + 1164*uk_31 + 17363*uk_32 + 21825*uk_33 + 9409*uk_34 + 144*uk_35 + 2148*uk_36 + 2700*uk_37 + 1164*uk_38 + 32041*uk_39 + 97*uk_4 + 40275*uk_40 + 17363*uk_41 + 50625*uk_42 + 21825*uk_43 + 9409*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 159489851582*uk_47 + 249524445217*uk_48 + 30869003532*uk_49 + 12*uk_5 + 460462636019*uk_50 + 578793816225*uk_51 + 249524445217*uk_52 + 153424975*uk_53 + 172951790*uk_54 + 270585865*uk_55 + 33474540*uk_56 + 499328555*uk_57 + 627647625*uk_58 + 270585865*uk_59 + 179*uk_6 + 194963836*uk_60 + 305024066*uk_61 + 37734936*uk_62 + 562879462*uk_63 + 707530050*uk_64 + 305024066*uk_65 + 477215071*uk_66 + 59036916*uk_67 + 880633997*uk_68 + 1106942175*uk_69 + 225*uk_7 + 477215071*uk_70 + 7303536*uk_71 + 108944412*uk_72 + 136941300*uk_73 + 59036916*uk_74 + 1625087479*uk_75 + 2042707725*uk_76 + 880633997*uk_77 + 2567649375*uk_78 + 1106942175*uk_79 + 97*uk_8 + 477215071*uk_80 + 166375*uk_81 + 187550*uk_82 + 293425*uk_83 + 36300*uk_84 + 541475*uk_85 + 680625*uk_86 + 293425*uk_87 + 211420*uk_88 + 330770*uk_89 + 2572416961*uk_9 + 40920*uk_90 + 610390*uk_91 + 767250*uk_92 + 330770*uk_93 + 517495*uk_94 + 64020*uk_95 + 954965*uk_96 + 1200375*uk_97 + 517495*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 119460*uk_100 + 148500*uk_101 + 40920*uk_102 + 1801855*uk_103 + 2239875*uk_104 + 617210*uk_105 + 2784375*uk_106 + 767250*uk_107 + 211420*uk_108 + 59319*uk_109 + 1978041*uk_11 + 94302*uk_110 + 18252*uk_111 + 275301*uk_112 + 342225*uk_113 + 94302*uk_114 + 149916*uk_115 + 29016*uk_116 + 437658*uk_117 + 544050*uk_118 + 149916*uk_119 + 3144578*uk_12 + 5616*uk_120 + 84708*uk_121 + 105300*uk_122 + 29016*uk_123 + 1277679*uk_124 + 1588275*uk_125 + 437658*uk_126 + 1974375*uk_127 + 544050*uk_128 + 149916*uk_129 + 608628*uk_13 + 238328*uk_130 + 46128*uk_131 + 695764*uk_132 + 864900*uk_133 + 238328*uk_134 + 8928*uk_135 + 134664*uk_136 + 167400*uk_137 + 46128*uk_138 + 2031182*uk_139 + 9180139*uk_14 + 2524950*uk_140 + 695764*uk_141 + 3138750*uk_142 + 864900*uk_143 + 238328*uk_144 + 1728*uk_145 + 26064*uk_146 + 32400*uk_147 + 8928*uk_148 + 393132*uk_149 + 11411775*uk_15 + 488700*uk_150 + 134664*uk_151 + 607500*uk_152 + 167400*uk_153 + 46128*uk_154 + 5929741*uk_155 + 7371225*uk_156 + 2031182*uk_157 + 9163125*uk_158 + 2524950*uk_159 + 3144578*uk_16 + 695764*uk_160 + 11390625*uk_161 + 3138750*uk_162 + 864900*uk_163 + 238328*uk_164 + 3025*uk_17 + 2145*uk_18 + 3410*uk_19 + 55*uk_2 + 660*uk_20 + 9955*uk_21 + 12375*uk_22 + 3410*uk_23 + 1521*uk_24 + 2418*uk_25 + 468*uk_26 + 7059*uk_27 + 8775*uk_28 + 2418*uk_29 + 39*uk_3 + 3844*uk_30 + 744*uk_31 + 11222*uk_32 + 13950*uk_33 + 3844*uk_34 + 144*uk_35 + 2172*uk_36 + 2700*uk_37 + 744*uk_38 + 32761*uk_39 + 62*uk_4 + 40725*uk_40 + 11222*uk_41 + 50625*uk_42 + 13950*uk_43 + 3844*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 100324261479*uk_47 + 159489851582*uk_48 + 30869003532*uk_49 + 12*uk_5 + 465607469941*uk_50 + 578793816225*uk_51 + 159489851582*uk_52 + 153424975*uk_53 + 108792255*uk_54 + 172951790*uk_55 + 33474540*uk_56 + 504907645*uk_57 + 627647625*uk_58 + 172951790*uk_59 + 181*uk_6 + 77143599*uk_60 + 122638542*uk_61 + 23736492*uk_62 + 358025421*uk_63 + 445059225*uk_64 + 122638542*uk_65 + 194963836*uk_66 + 37734936*uk_67 + 569168618*uk_68 + 707530050*uk_69 + 225*uk_7 + 194963836*uk_70 + 7303536*uk_71 + 110161668*uk_72 + 136941300*uk_73 + 37734936*uk_74 + 1661605159*uk_75 + 2065531275*uk_76 + 569168618*uk_77 + 2567649375*uk_78 + 707530050*uk_79 + 62*uk_8 + 194963836*uk_80 + 166375*uk_81 + 117975*uk_82 + 187550*uk_83 + 36300*uk_84 + 547525*uk_85 + 680625*uk_86 + 187550*uk_87 + 83655*uk_88 + 132990*uk_89 + 2572416961*uk_9 + 25740*uk_90 + 388245*uk_91 + 482625*uk_92 + 132990*uk_93 + 211420*uk_94 + 40920*uk_95 + 617210*uk_96 + 767250*uk_97 + 211420*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 120780*uk_100 + 148500*uk_101 + 25740*uk_102 + 1841895*uk_103 + 2264625*uk_104 + 392535*uk_105 + 2784375*uk_106 + 482625*uk_107 + 83655*uk_108 + 21952*uk_109 + 1420132*uk_11 + 30576*uk_110 + 9408*uk_111 + 143472*uk_112 + 176400*uk_113 + 30576*uk_114 + 42588*uk_115 + 13104*uk_116 + 199836*uk_117 + 245700*uk_118 + 42588*uk_119 + 1978041*uk_12 + 4032*uk_120 + 61488*uk_121 + 75600*uk_122 + 13104*uk_123 + 937692*uk_124 + 1152900*uk_125 + 199836*uk_126 + 1417500*uk_127 + 245700*uk_128 + 42588*uk_129 + 608628*uk_13 + 59319*uk_130 + 18252*uk_131 + 278343*uk_132 + 342225*uk_133 + 59319*uk_134 + 5616*uk_135 + 85644*uk_136 + 105300*uk_137 + 18252*uk_138 + 1306071*uk_139 + 9281577*uk_14 + 1605825*uk_140 + 278343*uk_141 + 1974375*uk_142 + 342225*uk_143 + 59319*uk_144 + 1728*uk_145 + 26352*uk_146 + 32400*uk_147 + 5616*uk_148 + 401868*uk_149 + 11411775*uk_15 + 494100*uk_150 + 85644*uk_151 + 607500*uk_152 + 105300*uk_153 + 18252*uk_154 + 6128487*uk_155 + 7535025*uk_156 + 1306071*uk_157 + 9264375*uk_158 + 1605825*uk_159 + 1978041*uk_16 + 278343*uk_160 + 11390625*uk_161 + 1974375*uk_162 + 342225*uk_163 + 59319*uk_164 + 3025*uk_17 + 1540*uk_18 + 2145*uk_19 + 55*uk_2 + 660*uk_20 + 10065*uk_21 + 12375*uk_22 + 2145*uk_23 + 784*uk_24 + 1092*uk_25 + 336*uk_26 + 5124*uk_27 + 6300*uk_28 + 1092*uk_29 + 28*uk_3 + 1521*uk_30 + 468*uk_31 + 7137*uk_32 + 8775*uk_33 + 1521*uk_34 + 144*uk_35 + 2196*uk_36 + 2700*uk_37 + 468*uk_38 + 33489*uk_39 + 39*uk_4 + 41175*uk_40 + 7137*uk_41 + 50625*uk_42 + 8775*uk_43 + 1521*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 72027674908*uk_47 + 100324261479*uk_48 + 30869003532*uk_49 + 12*uk_5 + 470752303863*uk_50 + 578793816225*uk_51 + 100324261479*uk_52 + 153424975*uk_53 + 78107260*uk_54 + 108792255*uk_55 + 33474540*uk_56 + 510486735*uk_57 + 627647625*uk_58 + 108792255*uk_59 + 183*uk_6 + 39763696*uk_60 + 55385148*uk_61 + 17041584*uk_62 + 259884156*uk_63 + 319529700*uk_64 + 55385148*uk_65 + 77143599*uk_66 + 23736492*uk_67 + 361981503*uk_68 + 445059225*uk_69 + 225*uk_7 + 77143599*uk_70 + 7303536*uk_71 + 111378924*uk_72 + 136941300*uk_73 + 23736492*uk_74 + 1698528591*uk_75 + 2088354825*uk_76 + 361981503*uk_77 + 2567649375*uk_78 + 445059225*uk_79 + 39*uk_8 + 77143599*uk_80 + 166375*uk_81 + 84700*uk_82 + 117975*uk_83 + 36300*uk_84 + 553575*uk_85 + 680625*uk_86 + 117975*uk_87 + 43120*uk_88 + 60060*uk_89 + 2572416961*uk_9 + 18480*uk_90 + 281820*uk_91 + 346500*uk_92 + 60060*uk_93 + 83655*uk_94 + 25740*uk_95 + 392535*uk_96 + 482625*uk_97 + 83655*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 122100*uk_100 + 148500*uk_101 + 18480*uk_102 + 1882375*uk_103 + 2289375*uk_104 + 284900*uk_105 + 2784375*uk_106 + 346500*uk_107 + 43120*uk_108 + 24389*uk_109 + 1470851*uk_11 + 23548*uk_110 + 10092*uk_111 + 155585*uk_112 + 189225*uk_113 + 23548*uk_114 + 22736*uk_115 + 9744*uk_116 + 150220*uk_117 + 182700*uk_118 + 22736*uk_119 + 1420132*uk_12 + 4176*uk_120 + 64380*uk_121 + 78300*uk_122 + 9744*uk_123 + 992525*uk_124 + 1207125*uk_125 + 150220*uk_126 + 1468125*uk_127 + 182700*uk_128 + 22736*uk_129 + 608628*uk_13 + 21952*uk_130 + 9408*uk_131 + 145040*uk_132 + 176400*uk_133 + 21952*uk_134 + 4032*uk_135 + 62160*uk_136 + 75600*uk_137 + 9408*uk_138 + 958300*uk_139 + 9383015*uk_14 + 1165500*uk_140 + 145040*uk_141 + 1417500*uk_142 + 176400*uk_143 + 21952*uk_144 + 1728*uk_145 + 26640*uk_146 + 32400*uk_147 + 4032*uk_148 + 410700*uk_149 + 11411775*uk_15 + 499500*uk_150 + 62160*uk_151 + 607500*uk_152 + 75600*uk_153 + 9408*uk_154 + 6331625*uk_155 + 7700625*uk_156 + 958300*uk_157 + 9365625*uk_158 + 1165500*uk_159 + 1420132*uk_16 + 145040*uk_160 + 11390625*uk_161 + 1417500*uk_162 + 176400*uk_163 + 21952*uk_164 + 3025*uk_17 + 1595*uk_18 + 1540*uk_19 + 55*uk_2 + 660*uk_20 + 10175*uk_21 + 12375*uk_22 + 1540*uk_23 + 841*uk_24 + 812*uk_25 + 348*uk_26 + 5365*uk_27 + 6525*uk_28 + 812*uk_29 + 29*uk_3 + 784*uk_30 + 336*uk_31 + 5180*uk_32 + 6300*uk_33 + 784*uk_34 + 144*uk_35 + 2220*uk_36 + 2700*uk_37 + 336*uk_38 + 34225*uk_39 + 28*uk_4 + 41625*uk_40 + 5180*uk_41 + 50625*uk_42 + 6300*uk_43 + 784*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 74600091869*uk_47 + 72027674908*uk_48 + 30869003532*uk_49 + 12*uk_5 + 475897137785*uk_50 + 578793816225*uk_51 + 72027674908*uk_52 + 153424975*uk_53 + 80896805*uk_54 + 78107260*uk_55 + 33474540*uk_56 + 516065825*uk_57 + 627647625*uk_58 + 78107260*uk_59 + 185*uk_6 + 42654679*uk_60 + 41183828*uk_61 + 17650212*uk_62 + 272107435*uk_63 + 330941475*uk_64 + 41183828*uk_65 + 39763696*uk_66 + 17041584*uk_67 + 262724420*uk_68 + 319529700*uk_69 + 225*uk_7 + 39763696*uk_70 + 7303536*uk_71 + 112596180*uk_72 + 136941300*uk_73 + 17041584*uk_74 + 1735857775*uk_75 + 2111178375*uk_76 + 262724420*uk_77 + 2567649375*uk_78 + 319529700*uk_79 + 28*uk_8 + 39763696*uk_80 + 166375*uk_81 + 87725*uk_82 + 84700*uk_83 + 36300*uk_84 + 559625*uk_85 + 680625*uk_86 + 84700*uk_87 + 46255*uk_88 + 44660*uk_89 + 2572416961*uk_9 + 19140*uk_90 + 295075*uk_91 + 358875*uk_92 + 44660*uk_93 + 43120*uk_94 + 18480*uk_95 + 284900*uk_96 + 346500*uk_97 + 43120*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 123420*uk_100 + 148500*uk_101 + 19140*uk_102 + 1923295*uk_103 + 2314125*uk_104 + 298265*uk_105 + 2784375*uk_106 + 358875*uk_107 + 46255*uk_108 + 74088*uk_109 + 2130198*uk_11 + 51156*uk_110 + 21168*uk_111 + 329868*uk_112 + 396900*uk_113 + 51156*uk_114 + 35322*uk_115 + 14616*uk_116 + 227766*uk_117 + 274050*uk_118 + 35322*uk_119 + 1470851*uk_12 + 6048*uk_120 + 94248*uk_121 + 113400*uk_122 + 14616*uk_123 + 1468698*uk_124 + 1767150*uk_125 + 227766*uk_126 + 2126250*uk_127 + 274050*uk_128 + 35322*uk_129 + 608628*uk_13 + 24389*uk_130 + 10092*uk_131 + 157267*uk_132 + 189225*uk_133 + 24389*uk_134 + 4176*uk_135 + 65076*uk_136 + 78300*uk_137 + 10092*uk_138 + 1014101*uk_139 + 9484453*uk_14 + 1220175*uk_140 + 157267*uk_141 + 1468125*uk_142 + 189225*uk_143 + 24389*uk_144 + 1728*uk_145 + 26928*uk_146 + 32400*uk_147 + 4176*uk_148 + 419628*uk_149 + 11411775*uk_15 + 504900*uk_150 + 65076*uk_151 + 607500*uk_152 + 78300*uk_153 + 10092*uk_154 + 6539203*uk_155 + 7868025*uk_156 + 1014101*uk_157 + 9466875*uk_158 + 1220175*uk_159 + 1470851*uk_16 + 157267*uk_160 + 11390625*uk_161 + 1468125*uk_162 + 189225*uk_163 + 24389*uk_164 + 3025*uk_17 + 2310*uk_18 + 1595*uk_19 + 55*uk_2 + 660*uk_20 + 10285*uk_21 + 12375*uk_22 + 1595*uk_23 + 1764*uk_24 + 1218*uk_25 + 504*uk_26 + 7854*uk_27 + 9450*uk_28 + 1218*uk_29 + 42*uk_3 + 841*uk_30 + 348*uk_31 + 5423*uk_32 + 6525*uk_33 + 841*uk_34 + 144*uk_35 + 2244*uk_36 + 2700*uk_37 + 348*uk_38 + 34969*uk_39 + 29*uk_4 + 42075*uk_40 + 5423*uk_41 + 50625*uk_42 + 6525*uk_43 + 841*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 108041512362*uk_47 + 74600091869*uk_48 + 30869003532*uk_49 + 12*uk_5 + 481041971707*uk_50 + 578793816225*uk_51 + 74600091869*uk_52 + 153424975*uk_53 + 117160890*uk_54 + 80896805*uk_55 + 33474540*uk_56 + 521644915*uk_57 + 627647625*uk_58 + 80896805*uk_59 + 187*uk_6 + 89468316*uk_60 + 61775742*uk_61 + 25562376*uk_62 + 398347026*uk_63 + 479294550*uk_64 + 61775742*uk_65 + 42654679*uk_66 + 17650212*uk_67 + 275049137*uk_68 + 330941475*uk_69 + 225*uk_7 + 42654679*uk_70 + 7303536*uk_71 + 113813436*uk_72 + 136941300*uk_73 + 17650212*uk_74 + 1773592711*uk_75 + 2134001925*uk_76 + 275049137*uk_77 + 2567649375*uk_78 + 330941475*uk_79 + 29*uk_8 + 42654679*uk_80 + 166375*uk_81 + 127050*uk_82 + 87725*uk_83 + 36300*uk_84 + 565675*uk_85 + 680625*uk_86 + 87725*uk_87 + 97020*uk_88 + 66990*uk_89 + 2572416961*uk_9 + 27720*uk_90 + 431970*uk_91 + 519750*uk_92 + 66990*uk_93 + 46255*uk_94 + 19140*uk_95 + 298265*uk_96 + 358875*uk_97 + 46255*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 124740*uk_100 + 148500*uk_101 + 27720*uk_102 + 1964655*uk_103 + 2338875*uk_104 + 436590*uk_105 + 2784375*uk_106 + 519750*uk_107 + 97020*uk_108 + 300763*uk_109 + 3398173*uk_11 + 188538*uk_110 + 53868*uk_111 + 848421*uk_112 + 1010025*uk_113 + 188538*uk_114 + 118188*uk_115 + 33768*uk_116 + 531846*uk_117 + 633150*uk_118 + 118188*uk_119 + 2130198*uk_12 + 9648*uk_120 + 151956*uk_121 + 180900*uk_122 + 33768*uk_123 + 2393307*uk_124 + 2849175*uk_125 + 531846*uk_126 + 3391875*uk_127 + 633150*uk_128 + 118188*uk_129 + 608628*uk_13 + 74088*uk_130 + 21168*uk_131 + 333396*uk_132 + 396900*uk_133 + 74088*uk_134 + 6048*uk_135 + 95256*uk_136 + 113400*uk_137 + 21168*uk_138 + 1500282*uk_139 + 9585891*uk_14 + 1786050*uk_140 + 333396*uk_141 + 2126250*uk_142 + 396900*uk_143 + 74088*uk_144 + 1728*uk_145 + 27216*uk_146 + 32400*uk_147 + 6048*uk_148 + 428652*uk_149 + 11411775*uk_15 + 510300*uk_150 + 95256*uk_151 + 607500*uk_152 + 113400*uk_153 + 21168*uk_154 + 6751269*uk_155 + 8037225*uk_156 + 1500282*uk_157 + 9568125*uk_158 + 1786050*uk_159 + 2130198*uk_16 + 333396*uk_160 + 11390625*uk_161 + 2126250*uk_162 + 396900*uk_163 + 74088*uk_164 + 3025*uk_17 + 3685*uk_18 + 2310*uk_19 + 55*uk_2 + 660*uk_20 + 10395*uk_21 + 12375*uk_22 + 2310*uk_23 + 4489*uk_24 + 2814*uk_25 + 804*uk_26 + 12663*uk_27 + 15075*uk_28 + 2814*uk_29 + 67*uk_3 + 1764*uk_30 + 504*uk_31 + 7938*uk_32 + 9450*uk_33 + 1764*uk_34 + 144*uk_35 + 2268*uk_36 + 2700*uk_37 + 504*uk_38 + 35721*uk_39 + 42*uk_4 + 42525*uk_40 + 7938*uk_41 + 50625*uk_42 + 9450*uk_43 + 1764*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 172351936387*uk_47 + 108041512362*uk_48 + 30869003532*uk_49 + 12*uk_5 + 486186805629*uk_50 + 578793816225*uk_51 + 108041512362*uk_52 + 153424975*uk_53 + 186899515*uk_54 + 117160890*uk_55 + 33474540*uk_56 + 527224005*uk_57 + 627647625*uk_58 + 117160890*uk_59 + 189*uk_6 + 227677591*uk_60 + 142723266*uk_61 + 40778076*uk_62 + 642254697*uk_63 + 764588925*uk_64 + 142723266*uk_65 + 89468316*uk_66 + 25562376*uk_67 + 402607422*uk_68 + 479294550*uk_69 + 225*uk_7 + 89468316*uk_70 + 7303536*uk_71 + 115030692*uk_72 + 136941300*uk_73 + 25562376*uk_74 + 1811733399*uk_75 + 2156825475*uk_76 + 402607422*uk_77 + 2567649375*uk_78 + 479294550*uk_79 + 42*uk_8 + 89468316*uk_80 + 166375*uk_81 + 202675*uk_82 + 127050*uk_83 + 36300*uk_84 + 571725*uk_85 + 680625*uk_86 + 127050*uk_87 + 246895*uk_88 + 154770*uk_89 + 2572416961*uk_9 + 44220*uk_90 + 696465*uk_91 + 829125*uk_92 + 154770*uk_93 + 97020*uk_94 + 27720*uk_95 + 436590*uk_96 + 519750*uk_97 + 97020*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 126060*uk_100 + 148500*uk_101 + 44220*uk_102 + 2006455*uk_103 + 2363625*uk_104 + 703835*uk_105 + 2784375*uk_106 + 829125*uk_107 + 246895*uk_108 + 1124864*uk_109 + 5274776*uk_11 + 724672*uk_110 + 129792*uk_111 + 2065856*uk_112 + 2433600*uk_113 + 724672*uk_114 + 466856*uk_115 + 83616*uk_116 + 1330888*uk_117 + 1567800*uk_118 + 466856*uk_119 + 3398173*uk_12 + 14976*uk_120 + 238368*uk_121 + 280800*uk_122 + 83616*uk_123 + 3794024*uk_124 + 4469400*uk_125 + 1330888*uk_126 + 5265000*uk_127 + 1567800*uk_128 + 466856*uk_129 + 608628*uk_13 + 300763*uk_130 + 53868*uk_131 + 857399*uk_132 + 1010025*uk_133 + 300763*uk_134 + 9648*uk_135 + 153564*uk_136 + 180900*uk_137 + 53868*uk_138 + 2444227*uk_139 + 9687329*uk_14 + 2879325*uk_140 + 857399*uk_141 + 3391875*uk_142 + 1010025*uk_143 + 300763*uk_144 + 1728*uk_145 + 27504*uk_146 + 32400*uk_147 + 9648*uk_148 + 437772*uk_149 + 11411775*uk_15 + 515700*uk_150 + 153564*uk_151 + 607500*uk_152 + 180900*uk_153 + 53868*uk_154 + 6967871*uk_155 + 8208225*uk_156 + 2444227*uk_157 + 9669375*uk_158 + 2879325*uk_159 + 3398173*uk_16 + 857399*uk_160 + 11390625*uk_161 + 3391875*uk_162 + 1010025*uk_163 + 300763*uk_164 + 3025*uk_17 + 5720*uk_18 + 3685*uk_19 + 55*uk_2 + 660*uk_20 + 10505*uk_21 + 12375*uk_22 + 3685*uk_23 + 10816*uk_24 + 6968*uk_25 + 1248*uk_26 + 19864*uk_27 + 23400*uk_28 + 6968*uk_29 + 104*uk_3 + 4489*uk_30 + 804*uk_31 + 12797*uk_32 + 15075*uk_33 + 4489*uk_34 + 144*uk_35 + 2292*uk_36 + 2700*uk_37 + 804*uk_38 + 36481*uk_39 + 67*uk_4 + 42975*uk_40 + 12797*uk_41 + 50625*uk_42 + 15075*uk_43 + 4489*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 267531363944*uk_47 + 172351936387*uk_48 + 30869003532*uk_49 + 12*uk_5 + 491331639551*uk_50 + 578793816225*uk_51 + 172351936387*uk_52 + 153424975*uk_53 + 290112680*uk_54 + 186899515*uk_55 + 33474540*uk_56 + 532803095*uk_57 + 627647625*uk_58 + 186899515*uk_59 + 191*uk_6 + 548576704*uk_60 + 353409992*uk_61 + 63297312*uk_62 + 1007482216*uk_63 + 1186824600*uk_64 + 353409992*uk_65 + 227677591*uk_66 + 40778076*uk_67 + 649051043*uk_68 + 764588925*uk_69 + 225*uk_7 + 227677591*uk_70 + 7303536*uk_71 + 116247948*uk_72 + 136941300*uk_73 + 40778076*uk_74 + 1850279839*uk_75 + 2179649025*uk_76 + 649051043*uk_77 + 2567649375*uk_78 + 764588925*uk_79 + 67*uk_8 + 227677591*uk_80 + 166375*uk_81 + 314600*uk_82 + 202675*uk_83 + 36300*uk_84 + 577775*uk_85 + 680625*uk_86 + 202675*uk_87 + 594880*uk_88 + 383240*uk_89 + 2572416961*uk_9 + 68640*uk_90 + 1092520*uk_91 + 1287000*uk_92 + 383240*uk_93 + 246895*uk_94 + 44220*uk_95 + 703835*uk_96 + 829125*uk_97 + 246895*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 127380*uk_100 + 148500*uk_101 + 68640*uk_102 + 2048695*uk_103 + 2388375*uk_104 + 1103960*uk_105 + 2784375*uk_106 + 1287000*uk_107 + 594880*uk_108 + 3581577*uk_109 + 7760007*uk_11 + 2434536*uk_110 + 280908*uk_111 + 4517937*uk_112 + 5267025*uk_113 + 2434536*uk_114 + 1654848*uk_115 + 190944*uk_116 + 3071016*uk_117 + 3580200*uk_118 + 1654848*uk_119 + 5274776*uk_12 + 22032*uk_120 + 354348*uk_121 + 413100*uk_122 + 190944*uk_123 + 5699097*uk_124 + 6644025*uk_125 + 3071016*uk_126 + 7745625*uk_127 + 3580200*uk_128 + 1654848*uk_129 + 608628*uk_13 + 1124864*uk_130 + 129792*uk_131 + 2087488*uk_132 + 2433600*uk_133 + 1124864*uk_134 + 14976*uk_135 + 240864*uk_136 + 280800*uk_137 + 129792*uk_138 + 3873896*uk_139 + 9788767*uk_14 + 4516200*uk_140 + 2087488*uk_141 + 5265000*uk_142 + 2433600*uk_143 + 1124864*uk_144 + 1728*uk_145 + 27792*uk_146 + 32400*uk_147 + 14976*uk_148 + 446988*uk_149 + 11411775*uk_15 + 521100*uk_150 + 240864*uk_151 + 607500*uk_152 + 280800*uk_153 + 129792*uk_154 + 7189057*uk_155 + 8381025*uk_156 + 3873896*uk_157 + 9770625*uk_158 + 4516200*uk_159 + 5274776*uk_16 + 2087488*uk_160 + 11390625*uk_161 + 5265000*uk_162 + 2433600*uk_163 + 1124864*uk_164 + 3025*uk_17 + 8415*uk_18 + 5720*uk_19 + 55*uk_2 + 660*uk_20 + 10615*uk_21 + 12375*uk_22 + 5720*uk_23 + 23409*uk_24 + 15912*uk_25 + 1836*uk_26 + 29529*uk_27 + 34425*uk_28 + 15912*uk_29 + 153*uk_3 + 10816*uk_30 + 1248*uk_31 + 20072*uk_32 + 23400*uk_33 + 10816*uk_34 + 144*uk_35 + 2316*uk_36 + 2700*uk_37 + 1248*uk_38 + 37249*uk_39 + 104*uk_4 + 43425*uk_40 + 20072*uk_41 + 50625*uk_42 + 23400*uk_43 + 10816*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 393579795033*uk_47 + 267531363944*uk_48 + 30869003532*uk_49 + 12*uk_5 + 496476473473*uk_50 + 578793816225*uk_51 + 267531363944*uk_52 + 153424975*uk_53 + 426800385*uk_54 + 290112680*uk_55 + 33474540*uk_56 + 538382185*uk_57 + 627647625*uk_58 + 290112680*uk_59 + 193*uk_6 + 1187281071*uk_60 + 807040728*uk_61 + 93120084*uk_62 + 1497681351*uk_63 + 1746001575*uk_64 + 807040728*uk_65 + 548576704*uk_66 + 63297312*uk_67 + 1018031768*uk_68 + 1186824600*uk_69 + 225*uk_7 + 548576704*uk_70 + 7303536*uk_71 + 117465204*uk_72 + 136941300*uk_73 + 63297312*uk_74 + 1889232031*uk_75 + 2202472575*uk_76 + 1018031768*uk_77 + 2567649375*uk_78 + 1186824600*uk_79 + 104*uk_8 + 548576704*uk_80 + 166375*uk_81 + 462825*uk_82 + 314600*uk_83 + 36300*uk_84 + 583825*uk_85 + 680625*uk_86 + 314600*uk_87 + 1287495*uk_88 + 875160*uk_89 + 2572416961*uk_9 + 100980*uk_90 + 1624095*uk_91 + 1893375*uk_92 + 875160*uk_93 + 594880*uk_94 + 68640*uk_95 + 1103960*uk_96 + 1287000*uk_97 + 594880*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 85800*uk_100 + 99000*uk_101 + 67320*uk_102 + 2091375*uk_103 + 2413125*uk_104 + 1640925*uk_105 + 2784375*uk_106 + 1893375*uk_107 + 1287495*uk_108 + 6859*uk_109 + 963661*uk_11 + 55233*uk_110 + 2888*uk_111 + 70395*uk_112 + 81225*uk_113 + 55233*uk_114 + 444771*uk_115 + 23256*uk_116 + 566865*uk_117 + 654075*uk_118 + 444771*uk_119 + 7760007*uk_12 + 1216*uk_120 + 29640*uk_121 + 34200*uk_122 + 23256*uk_123 + 722475*uk_124 + 833625*uk_125 + 566865*uk_126 + 961875*uk_127 + 654075*uk_128 + 444771*uk_129 + 405752*uk_13 + 3581577*uk_130 + 187272*uk_131 + 4564755*uk_132 + 5267025*uk_133 + 3581577*uk_134 + 9792*uk_135 + 238680*uk_136 + 275400*uk_137 + 187272*uk_138 + 5817825*uk_139 + 9890205*uk_14 + 6712875*uk_140 + 4564755*uk_141 + 7745625*uk_142 + 5267025*uk_143 + 3581577*uk_144 + 512*uk_145 + 12480*uk_146 + 14400*uk_147 + 9792*uk_148 + 304200*uk_149 + 11411775*uk_15 + 351000*uk_150 + 238680*uk_151 + 405000*uk_152 + 275400*uk_153 + 187272*uk_154 + 7414875*uk_155 + 8555625*uk_156 + 5817825*uk_157 + 9871875*uk_158 + 6712875*uk_159 + 7760007*uk_16 + 4564755*uk_160 + 11390625*uk_161 + 7745625*uk_162 + 5267025*uk_163 + 3581577*uk_164 + 3025*uk_17 + 1045*uk_18 + 8415*uk_19 + 55*uk_2 + 440*uk_20 + 10725*uk_21 + 12375*uk_22 + 8415*uk_23 + 361*uk_24 + 2907*uk_25 + 152*uk_26 + 3705*uk_27 + 4275*uk_28 + 2907*uk_29 + 19*uk_3 + 23409*uk_30 + 1224*uk_31 + 29835*uk_32 + 34425*uk_33 + 23409*uk_34 + 64*uk_35 + 1560*uk_36 + 1800*uk_37 + 1224*uk_38 + 38025*uk_39 + 153*uk_4 + 43875*uk_40 + 29835*uk_41 + 50625*uk_42 + 34425*uk_43 + 23409*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 48875922259*uk_47 + 393579795033*uk_48 + 20579335688*uk_49 + 8*uk_5 + 501621307395*uk_50 + 578793816225*uk_51 + 393579795033*uk_52 + 153424975*uk_53 + 53001355*uk_54 + 426800385*uk_55 + 22316360*uk_56 + 543961275*uk_57 + 627647625*uk_58 + 426800385*uk_59 + 195*uk_6 + 18309559*uk_60 + 147440133*uk_61 + 7709288*uk_62 + 187913895*uk_63 + 216823725*uk_64 + 147440133*uk_65 + 1187281071*uk_66 + 62080056*uk_67 + 1513201365*uk_68 + 1746001575*uk_69 + 225*uk_7 + 1187281071*uk_70 + 3246016*uk_71 + 79121640*uk_72 + 91294200*uk_73 + 62080056*uk_74 + 1928589975*uk_75 + 2225296125*uk_76 + 1513201365*uk_77 + 2567649375*uk_78 + 1746001575*uk_79 + 153*uk_8 + 1187281071*uk_80 + 166375*uk_81 + 57475*uk_82 + 462825*uk_83 + 24200*uk_84 + 589875*uk_85 + 680625*uk_86 + 462825*uk_87 + 19855*uk_88 + 159885*uk_89 + 2572416961*uk_9 + 8360*uk_90 + 203775*uk_91 + 235125*uk_92 + 159885*uk_93 + 1287495*uk_94 + 67320*uk_95 + 1640925*uk_96 + 1893375*uk_97 + 1287495*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 130020*uk_100 + 148500*uk_101 + 12540*uk_102 + 2134495*uk_103 + 2437875*uk_104 + 205865*uk_105 + 2784375*uk_106 + 235125*uk_107 + 19855*uk_108 + 729000*uk_109 + 4564710*uk_11 + 153900*uk_110 + 97200*uk_111 + 1595700*uk_112 + 1822500*uk_113 + 153900*uk_114 + 32490*uk_115 + 20520*uk_116 + 336870*uk_117 + 384750*uk_118 + 32490*uk_119 + 963661*uk_12 + 12960*uk_120 + 212760*uk_121 + 243000*uk_122 + 20520*uk_123 + 3492810*uk_124 + 3989250*uk_125 + 336870*uk_126 + 4556250*uk_127 + 384750*uk_128 + 32490*uk_129 + 608628*uk_13 + 6859*uk_130 + 4332*uk_131 + 71117*uk_132 + 81225*uk_133 + 6859*uk_134 + 2736*uk_135 + 44916*uk_136 + 51300*uk_137 + 4332*uk_138 + 737371*uk_139 + 9991643*uk_14 + 842175*uk_140 + 71117*uk_141 + 961875*uk_142 + 81225*uk_143 + 6859*uk_144 + 1728*uk_145 + 28368*uk_146 + 32400*uk_147 + 2736*uk_148 + 465708*uk_149 + 11411775*uk_15 + 531900*uk_150 + 44916*uk_151 + 607500*uk_152 + 51300*uk_153 + 4332*uk_154 + 7645373*uk_155 + 8732025*uk_156 + 737371*uk_157 + 9973125*uk_158 + 842175*uk_159 + 963661*uk_16 + 71117*uk_160 + 11390625*uk_161 + 961875*uk_162 + 81225*uk_163 + 6859*uk_164 + 3025*uk_17 + 4950*uk_18 + 1045*uk_19 + 55*uk_2 + 660*uk_20 + 10835*uk_21 + 12375*uk_22 + 1045*uk_23 + 8100*uk_24 + 1710*uk_25 + 1080*uk_26 + 17730*uk_27 + 20250*uk_28 + 1710*uk_29 + 90*uk_3 + 361*uk_30 + 228*uk_31 + 3743*uk_32 + 4275*uk_33 + 361*uk_34 + 144*uk_35 + 2364*uk_36 + 2700*uk_37 + 228*uk_38 + 38809*uk_39 + 19*uk_4 + 44325*uk_40 + 3743*uk_41 + 50625*uk_42 + 4275*uk_43 + 361*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 231517526490*uk_47 + 48875922259*uk_48 + 30869003532*uk_49 + 12*uk_5 + 506766141317*uk_50 + 578793816225*uk_51 + 48875922259*uk_52 + 153424975*uk_53 + 251059050*uk_54 + 53001355*uk_55 + 33474540*uk_56 + 549540365*uk_57 + 627647625*uk_58 + 53001355*uk_59 + 197*uk_6 + 410823900*uk_60 + 86729490*uk_61 + 54776520*uk_62 + 899247870*uk_63 + 1027059750*uk_64 + 86729490*uk_65 + 18309559*uk_66 + 11563932*uk_67 + 189841217*uk_68 + 216823725*uk_69 + 225*uk_7 + 18309559*uk_70 + 7303536*uk_71 + 119899716*uk_72 + 136941300*uk_73 + 11563932*uk_74 + 1968353671*uk_75 + 2248119675*uk_76 + 189841217*uk_77 + 2567649375*uk_78 + 216823725*uk_79 + 19*uk_8 + 18309559*uk_80 + 166375*uk_81 + 272250*uk_82 + 57475*uk_83 + 36300*uk_84 + 595925*uk_85 + 680625*uk_86 + 57475*uk_87 + 445500*uk_88 + 94050*uk_89 + 2572416961*uk_9 + 59400*uk_90 + 975150*uk_91 + 1113750*uk_92 + 94050*uk_93 + 19855*uk_94 + 12540*uk_95 + 205865*uk_96 + 235125*uk_97 + 19855*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 131340*uk_100 + 148500*uk_101 + 59400*uk_102 + 2178055*uk_103 + 2462625*uk_104 + 985050*uk_105 + 2784375*uk_106 + 1113750*uk_107 + 445500*uk_108 + 5177717*uk_109 + 8774387*uk_11 + 2693610*uk_110 + 359148*uk_111 + 5955871*uk_112 + 6734025*uk_113 + 2693610*uk_114 + 1401300*uk_115 + 186840*uk_116 + 3098430*uk_117 + 3503250*uk_118 + 1401300*uk_119 + 4564710*uk_12 + 24912*uk_120 + 413124*uk_121 + 467100*uk_122 + 186840*uk_123 + 6850973*uk_124 + 7746075*uk_125 + 3098430*uk_126 + 8758125*uk_127 + 3503250*uk_128 + 1401300*uk_129 + 608628*uk_13 + 729000*uk_130 + 97200*uk_131 + 1611900*uk_132 + 1822500*uk_133 + 729000*uk_134 + 12960*uk_135 + 214920*uk_136 + 243000*uk_137 + 97200*uk_138 + 3564090*uk_139 + 10093081*uk_14 + 4029750*uk_140 + 1611900*uk_141 + 4556250*uk_142 + 1822500*uk_143 + 729000*uk_144 + 1728*uk_145 + 28656*uk_146 + 32400*uk_147 + 12960*uk_148 + 475212*uk_149 + 11411775*uk_15 + 537300*uk_150 + 214920*uk_151 + 607500*uk_152 + 243000*uk_153 + 97200*uk_154 + 7880599*uk_155 + 8910225*uk_156 + 3564090*uk_157 + 10074375*uk_158 + 4029750*uk_159 + 4564710*uk_16 + 1611900*uk_160 + 11390625*uk_161 + 4556250*uk_162 + 1822500*uk_163 + 729000*uk_164 + 3025*uk_17 + 9515*uk_18 + 4950*uk_19 + 55*uk_2 + 660*uk_20 + 10945*uk_21 + 12375*uk_22 + 4950*uk_23 + 29929*uk_24 + 15570*uk_25 + 2076*uk_26 + 34427*uk_27 + 38925*uk_28 + 15570*uk_29 + 173*uk_3 + 8100*uk_30 + 1080*uk_31 + 17910*uk_32 + 20250*uk_33 + 8100*uk_34 + 144*uk_35 + 2388*uk_36 + 2700*uk_37 + 1080*uk_38 + 39601*uk_39 + 90*uk_4 + 44775*uk_40 + 17910*uk_41 + 50625*uk_42 + 20250*uk_43 + 8100*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 445028134253*uk_47 + 231517526490*uk_48 + 30869003532*uk_49 + 12*uk_5 + 511910975239*uk_50 + 578793816225*uk_51 + 231517526490*uk_52 + 153424975*uk_53 + 482591285*uk_54 + 251059050*uk_55 + 33474540*uk_56 + 555119455*uk_57 + 627647625*uk_58 + 251059050*uk_59 + 199*uk_6 + 1517968951*uk_60 + 789694830*uk_61 + 105292644*uk_62 + 1746103013*uk_63 + 1974237075*uk_64 + 789694830*uk_65 + 410823900*uk_66 + 54776520*uk_67 + 908377290*uk_68 + 1027059750*uk_69 + 225*uk_7 + 410823900*uk_70 + 7303536*uk_71 + 121116972*uk_72 + 136941300*uk_73 + 54776520*uk_74 + 2008523119*uk_75 + 2270943225*uk_76 + 908377290*uk_77 + 2567649375*uk_78 + 1027059750*uk_79 + 90*uk_8 + 410823900*uk_80 + 166375*uk_81 + 523325*uk_82 + 272250*uk_83 + 36300*uk_84 + 601975*uk_85 + 680625*uk_86 + 272250*uk_87 + 1646095*uk_88 + 856350*uk_89 + 2572416961*uk_9 + 114180*uk_90 + 1893485*uk_91 + 2140875*uk_92 + 856350*uk_93 + 445500*uk_94 + 59400*uk_95 + 985050*uk_96 + 1113750*uk_97 + 445500*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 88440*uk_100 + 99000*uk_101 + 76120*uk_102 + 2222055*uk_103 + 2487375*uk_104 + 1912515*uk_105 + 2784375*uk_106 + 2140875*uk_107 + 1646095*uk_108 + 300763*uk_109 + 3398173*uk_11 + 776597*uk_110 + 35912*uk_111 + 902289*uk_112 + 1010025*uk_113 + 776597*uk_114 + 2005243*uk_115 + 92728*uk_116 + 2329791*uk_117 + 2607975*uk_118 + 2005243*uk_119 + 8774387*uk_12 + 4288*uk_120 + 107736*uk_121 + 120600*uk_122 + 92728*uk_123 + 2706867*uk_124 + 3030075*uk_125 + 2329791*uk_126 + 3391875*uk_127 + 2607975*uk_128 + 2005243*uk_129 + 405752*uk_13 + 5177717*uk_130 + 239432*uk_131 + 6015729*uk_132 + 6734025*uk_133 + 5177717*uk_134 + 11072*uk_135 + 278184*uk_136 + 311400*uk_137 + 239432*uk_138 + 6989373*uk_139 + 10194519*uk_14 + 7823925*uk_140 + 6015729*uk_141 + 8758125*uk_142 + 6734025*uk_143 + 5177717*uk_144 + 512*uk_145 + 12864*uk_146 + 14400*uk_147 + 11072*uk_148 + 323208*uk_149 + 11411775*uk_15 + 361800*uk_150 + 278184*uk_151 + 405000*uk_152 + 311400*uk_153 + 239432*uk_154 + 8120601*uk_155 + 9090225*uk_156 + 6989373*uk_157 + 10175625*uk_158 + 7823925*uk_159 + 8774387*uk_16 + 6015729*uk_160 + 11390625*uk_161 + 8758125*uk_162 + 6734025*uk_163 + 5177717*uk_164 + 3025*uk_17 + 3685*uk_18 + 9515*uk_19 + 55*uk_2 + 440*uk_20 + 11055*uk_21 + 12375*uk_22 + 9515*uk_23 + 4489*uk_24 + 11591*uk_25 + 536*uk_26 + 13467*uk_27 + 15075*uk_28 + 11591*uk_29 + 67*uk_3 + 29929*uk_30 + 1384*uk_31 + 34773*uk_32 + 38925*uk_33 + 29929*uk_34 + 64*uk_35 + 1608*uk_36 + 1800*uk_37 + 1384*uk_38 + 40401*uk_39 + 173*uk_4 + 45225*uk_40 + 34773*uk_41 + 50625*uk_42 + 38925*uk_43 + 29929*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 172351936387*uk_47 + 445028134253*uk_48 + 20579335688*uk_49 + 8*uk_5 + 517055809161*uk_50 + 578793816225*uk_51 + 445028134253*uk_52 + 153424975*uk_53 + 186899515*uk_54 + 482591285*uk_55 + 22316360*uk_56 + 560698545*uk_57 + 627647625*uk_58 + 482591285*uk_59 + 201*uk_6 + 227677591*uk_60 + 587883929*uk_61 + 27185384*uk_62 + 683032773*uk_63 + 764588925*uk_64 + 587883929*uk_65 + 1517968951*uk_66 + 70195096*uk_67 + 1763651787*uk_68 + 1974237075*uk_69 + 225*uk_7 + 1517968951*uk_70 + 3246016*uk_71 + 81556152*uk_72 + 91294200*uk_73 + 70195096*uk_74 + 2049098319*uk_75 + 2293766775*uk_76 + 1763651787*uk_77 + 2567649375*uk_78 + 1974237075*uk_79 + 173*uk_8 + 1517968951*uk_80 + 166375*uk_81 + 202675*uk_82 + 523325*uk_83 + 24200*uk_84 + 608025*uk_85 + 680625*uk_86 + 523325*uk_87 + 246895*uk_88 + 637505*uk_89 + 2572416961*uk_9 + 29480*uk_90 + 740685*uk_91 + 829125*uk_92 + 637505*uk_93 + 1646095*uk_94 + 76120*uk_95 + 1912515*uk_96 + 2140875*uk_97 + 1646095*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 133980*uk_100 + 148500*uk_101 + 44220*uk_102 + 2266495*uk_103 + 2512125*uk_104 + 748055*uk_105 + 2784375*uk_106 + 829125*uk_107 + 246895*uk_108 + 5088448*uk_109 + 8723668*uk_11 + 1982128*uk_110 + 355008*uk_111 + 6005552*uk_112 + 6656400*uk_113 + 1982128*uk_114 + 772108*uk_115 + 138288*uk_116 + 2339372*uk_117 + 2592900*uk_118 + 772108*uk_119 + 3398173*uk_12 + 24768*uk_120 + 418992*uk_121 + 464400*uk_122 + 138288*uk_123 + 7087948*uk_124 + 7856100*uk_125 + 2339372*uk_126 + 8707500*uk_127 + 2592900*uk_128 + 772108*uk_129 + 608628*uk_13 + 300763*uk_130 + 53868*uk_131 + 911267*uk_132 + 1010025*uk_133 + 300763*uk_134 + 9648*uk_135 + 163212*uk_136 + 180900*uk_137 + 53868*uk_138 + 2761003*uk_139 + 10295957*uk_14 + 3060225*uk_140 + 911267*uk_141 + 3391875*uk_142 + 1010025*uk_143 + 300763*uk_144 + 1728*uk_145 + 29232*uk_146 + 32400*uk_147 + 9648*uk_148 + 494508*uk_149 + 11411775*uk_15 + 548100*uk_150 + 163212*uk_151 + 607500*uk_152 + 180900*uk_153 + 53868*uk_154 + 8365427*uk_155 + 9272025*uk_156 + 2761003*uk_157 + 10276875*uk_158 + 3060225*uk_159 + 3398173*uk_16 + 911267*uk_160 + 11390625*uk_161 + 3391875*uk_162 + 1010025*uk_163 + 300763*uk_164 + 3025*uk_17 + 9460*uk_18 + 3685*uk_19 + 55*uk_2 + 660*uk_20 + 11165*uk_21 + 12375*uk_22 + 3685*uk_23 + 29584*uk_24 + 11524*uk_25 + 2064*uk_26 + 34916*uk_27 + 38700*uk_28 + 11524*uk_29 + 172*uk_3 + 4489*uk_30 + 804*uk_31 + 13601*uk_32 + 15075*uk_33 + 4489*uk_34 + 144*uk_35 + 2436*uk_36 + 2700*uk_37 + 804*uk_38 + 41209*uk_39 + 67*uk_4 + 45675*uk_40 + 13601*uk_41 + 50625*uk_42 + 15075*uk_43 + 4489*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 442455717292*uk_47 + 172351936387*uk_48 + 30869003532*uk_49 + 12*uk_5 + 522200643083*uk_50 + 578793816225*uk_51 + 172351936387*uk_52 + 153424975*uk_53 + 479801740*uk_54 + 186899515*uk_55 + 33474540*uk_56 + 566277635*uk_57 + 627647625*uk_58 + 186899515*uk_59 + 203*uk_6 + 1500470896*uk_60 + 584485756*uk_61 + 104684016*uk_62 + 1770904604*uk_63 + 1962825300*uk_64 + 584485756*uk_65 + 227677591*uk_66 + 40778076*uk_67 + 689829119*uk_68 + 764588925*uk_69 + 225*uk_7 + 227677591*uk_70 + 7303536*uk_71 + 123551484*uk_72 + 136941300*uk_73 + 40778076*uk_74 + 2090079271*uk_75 + 2316590325*uk_76 + 689829119*uk_77 + 2567649375*uk_78 + 764588925*uk_79 + 67*uk_8 + 227677591*uk_80 + 166375*uk_81 + 520300*uk_82 + 202675*uk_83 + 36300*uk_84 + 614075*uk_85 + 680625*uk_86 + 202675*uk_87 + 1627120*uk_88 + 633820*uk_89 + 2572416961*uk_9 + 113520*uk_90 + 1920380*uk_91 + 2128500*uk_92 + 633820*uk_93 + 246895*uk_94 + 44220*uk_95 + 748055*uk_96 + 829125*uk_97 + 246895*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 90200*uk_100 + 99000*uk_101 + 75680*uk_102 + 2311375*uk_103 + 2536875*uk_104 + 1939300*uk_105 + 2784375*uk_106 + 2128500*uk_107 + 1627120*uk_108 + 592704*uk_109 + 4260396*uk_11 + 1213632*uk_110 + 56448*uk_111 + 1446480*uk_112 + 1587600*uk_113 + 1213632*uk_114 + 2485056*uk_115 + 115584*uk_116 + 2961840*uk_117 + 3250800*uk_118 + 2485056*uk_119 + 8723668*uk_12 + 5376*uk_120 + 137760*uk_121 + 151200*uk_122 + 115584*uk_123 + 3530100*uk_124 + 3874500*uk_125 + 2961840*uk_126 + 4252500*uk_127 + 3250800*uk_128 + 2485056*uk_129 + 405752*uk_13 + 5088448*uk_130 + 236672*uk_131 + 6064720*uk_132 + 6656400*uk_133 + 5088448*uk_134 + 11008*uk_135 + 282080*uk_136 + 309600*uk_137 + 236672*uk_138 + 7228300*uk_139 + 10397395*uk_14 + 7933500*uk_140 + 6064720*uk_141 + 8707500*uk_142 + 6656400*uk_143 + 5088448*uk_144 + 512*uk_145 + 13120*uk_146 + 14400*uk_147 + 11008*uk_148 + 336200*uk_149 + 11411775*uk_15 + 369000*uk_150 + 282080*uk_151 + 405000*uk_152 + 309600*uk_153 + 236672*uk_154 + 8615125*uk_155 + 9455625*uk_156 + 7228300*uk_157 + 10378125*uk_158 + 7933500*uk_159 + 8723668*uk_16 + 6064720*uk_160 + 11390625*uk_161 + 8707500*uk_162 + 6656400*uk_163 + 5088448*uk_164 + 3025*uk_17 + 4620*uk_18 + 9460*uk_19 + 55*uk_2 + 440*uk_20 + 11275*uk_21 + 12375*uk_22 + 9460*uk_23 + 7056*uk_24 + 14448*uk_25 + 672*uk_26 + 17220*uk_27 + 18900*uk_28 + 14448*uk_29 + 84*uk_3 + 29584*uk_30 + 1376*uk_31 + 35260*uk_32 + 38700*uk_33 + 29584*uk_34 + 64*uk_35 + 1640*uk_36 + 1800*uk_37 + 1376*uk_38 + 42025*uk_39 + 172*uk_4 + 46125*uk_40 + 35260*uk_41 + 50625*uk_42 + 38700*uk_43 + 29584*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 216083024724*uk_47 + 442455717292*uk_48 + 20579335688*uk_49 + 8*uk_5 + 527345477005*uk_50 + 578793816225*uk_51 + 442455717292*uk_52 + 153424975*uk_53 + 234321780*uk_54 + 479801740*uk_55 + 22316360*uk_56 + 571856725*uk_57 + 627647625*uk_58 + 479801740*uk_59 + 205*uk_6 + 357873264*uk_60 + 732788112*uk_61 + 34083168*uk_62 + 873381180*uk_63 + 958589100*uk_64 + 732788112*uk_65 + 1500470896*uk_66 + 69789344*uk_67 + 1788351940*uk_68 + 1962825300*uk_69 + 225*uk_7 + 1500470896*uk_70 + 3246016*uk_71 + 83179160*uk_72 + 91294200*uk_73 + 69789344*uk_74 + 2131465975*uk_75 + 2339413875*uk_76 + 1788351940*uk_77 + 2567649375*uk_78 + 1962825300*uk_79 + 172*uk_8 + 1500470896*uk_80 + 166375*uk_81 + 254100*uk_82 + 520300*uk_83 + 24200*uk_84 + 620125*uk_85 + 680625*uk_86 + 520300*uk_87 + 388080*uk_88 + 794640*uk_89 + 2572416961*uk_9 + 36960*uk_90 + 947100*uk_91 + 1039500*uk_92 + 794640*uk_93 + 1627120*uk_94 + 75680*uk_95 + 1939300*uk_96 + 2128500*uk_97 + 1627120*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 91080*uk_100 + 99000*uk_101 + 36960*uk_102 + 2356695*uk_103 + 2561625*uk_104 + 956340*uk_105 + 2784375*uk_106 + 1039500*uk_107 + 388080*uk_108 + 64*uk_109 + 202876*uk_11 + 1344*uk_110 + 128*uk_111 + 3312*uk_112 + 3600*uk_113 + 1344*uk_114 + 28224*uk_115 + 2688*uk_116 + 69552*uk_117 + 75600*uk_118 + 28224*uk_119 + 4260396*uk_12 + 256*uk_120 + 6624*uk_121 + 7200*uk_122 + 2688*uk_123 + 171396*uk_124 + 186300*uk_125 + 69552*uk_126 + 202500*uk_127 + 75600*uk_128 + 28224*uk_129 + 405752*uk_13 + 592704*uk_130 + 56448*uk_131 + 1460592*uk_132 + 1587600*uk_133 + 592704*uk_134 + 5376*uk_135 + 139104*uk_136 + 151200*uk_137 + 56448*uk_138 + 3599316*uk_139 + 10498833*uk_14 + 3912300*uk_140 + 1460592*uk_141 + 4252500*uk_142 + 1587600*uk_143 + 592704*uk_144 + 512*uk_145 + 13248*uk_146 + 14400*uk_147 + 5376*uk_148 + 342792*uk_149 + 11411775*uk_15 + 372600*uk_150 + 139104*uk_151 + 405000*uk_152 + 151200*uk_153 + 56448*uk_154 + 8869743*uk_155 + 9641025*uk_156 + 3599316*uk_157 + 10479375*uk_158 + 3912300*uk_159 + 4260396*uk_16 + 1460592*uk_160 + 11390625*uk_161 + 4252500*uk_162 + 1587600*uk_163 + 592704*uk_164 + 3025*uk_17 + 220*uk_18 + 4620*uk_19 + 55*uk_2 + 440*uk_20 + 11385*uk_21 + 12375*uk_22 + 4620*uk_23 + 16*uk_24 + 336*uk_25 + 32*uk_26 + 828*uk_27 + 900*uk_28 + 336*uk_29 + 4*uk_3 + 7056*uk_30 + 672*uk_31 + 17388*uk_32 + 18900*uk_33 + 7056*uk_34 + 64*uk_35 + 1656*uk_36 + 1800*uk_37 + 672*uk_38 + 42849*uk_39 + 84*uk_4 + 46575*uk_40 + 17388*uk_41 + 50625*uk_42 + 18900*uk_43 + 7056*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 10289667844*uk_47 + 216083024724*uk_48 + 20579335688*uk_49 + 8*uk_5 + 532490310927*uk_50 + 578793816225*uk_51 + 216083024724*uk_52 + 153424975*uk_53 + 11158180*uk_54 + 234321780*uk_55 + 22316360*uk_56 + 577435815*uk_57 + 627647625*uk_58 + 234321780*uk_59 + 207*uk_6 + 811504*uk_60 + 17041584*uk_61 + 1623008*uk_62 + 41995332*uk_63 + 45647100*uk_64 + 17041584*uk_65 + 357873264*uk_66 + 34083168*uk_67 + 881901972*uk_68 + 958589100*uk_69 + 225*uk_7 + 357873264*uk_70 + 3246016*uk_71 + 83990664*uk_72 + 91294200*uk_73 + 34083168*uk_74 + 2173258431*uk_75 + 2362237425*uk_76 + 881901972*uk_77 + 2567649375*uk_78 + 958589100*uk_79 + 84*uk_8 + 357873264*uk_80 + 166375*uk_81 + 12100*uk_82 + 254100*uk_83 + 24200*uk_84 + 626175*uk_85 + 680625*uk_86 + 254100*uk_87 + 880*uk_88 + 18480*uk_89 + 2572416961*uk_9 + 1760*uk_90 + 45540*uk_91 + 49500*uk_92 + 18480*uk_93 + 388080*uk_94 + 36960*uk_95 + 956340*uk_96 + 1039500*uk_97 + 388080*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 137940*uk_100 + 148500*uk_101 + 2640*uk_102 + 2402455*uk_103 + 2586375*uk_104 + 45980*uk_105 + 2784375*uk_106 + 49500*uk_107 + 880*uk_108 + 2803221*uk_109 + 7151379*uk_11 + 79524*uk_110 + 238572*uk_111 + 4155129*uk_112 + 4473225*uk_113 + 79524*uk_114 + 2256*uk_115 + 6768*uk_116 + 117876*uk_117 + 126900*uk_118 + 2256*uk_119 + 202876*uk_12 + 20304*uk_120 + 353628*uk_121 + 380700*uk_122 + 6768*uk_123 + 6159021*uk_124 + 6630525*uk_125 + 117876*uk_126 + 7138125*uk_127 + 126900*uk_128 + 2256*uk_129 + 608628*uk_13 + 64*uk_130 + 192*uk_131 + 3344*uk_132 + 3600*uk_133 + 64*uk_134 + 576*uk_135 + 10032*uk_136 + 10800*uk_137 + 192*uk_138 + 174724*uk_139 + 10600271*uk_14 + 188100*uk_140 + 3344*uk_141 + 202500*uk_142 + 3600*uk_143 + 64*uk_144 + 1728*uk_145 + 30096*uk_146 + 32400*uk_147 + 576*uk_148 + 524172*uk_149 + 11411775*uk_15 + 564300*uk_150 + 10032*uk_151 + 607500*uk_152 + 10800*uk_153 + 192*uk_154 + 9129329*uk_155 + 9828225*uk_156 + 174724*uk_157 + 10580625*uk_158 + 188100*uk_159 + 202876*uk_16 + 3344*uk_160 + 11390625*uk_161 + 202500*uk_162 + 3600*uk_163 + 64*uk_164 + 3025*uk_17 + 7755*uk_18 + 220*uk_19 + 55*uk_2 + 660*uk_20 + 11495*uk_21 + 12375*uk_22 + 220*uk_23 + 19881*uk_24 + 564*uk_25 + 1692*uk_26 + 29469*uk_27 + 31725*uk_28 + 564*uk_29 + 141*uk_3 + 16*uk_30 + 48*uk_31 + 836*uk_32 + 900*uk_33 + 16*uk_34 + 144*uk_35 + 2508*uk_36 + 2700*uk_37 + 48*uk_38 + 43681*uk_39 + 4*uk_4 + 47025*uk_40 + 836*uk_41 + 50625*uk_42 + 900*uk_43 + 16*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 362710791501*uk_47 + 10289667844*uk_48 + 30869003532*uk_49 + 12*uk_5 + 537635144849*uk_50 + 578793816225*uk_51 + 10289667844*uk_52 + 153424975*uk_53 + 393325845*uk_54 + 11158180*uk_55 + 33474540*uk_56 + 583014905*uk_57 + 627647625*uk_58 + 11158180*uk_59 + 209*uk_6 + 1008344439*uk_60 + 28605516*uk_61 + 85816548*uk_62 + 1494638211*uk_63 + 1609060275*uk_64 + 28605516*uk_65 + 811504*uk_66 + 2434512*uk_67 + 42401084*uk_68 + 45647100*uk_69 + 225*uk_7 + 811504*uk_70 + 7303536*uk_71 + 127203252*uk_72 + 136941300*uk_73 + 2434512*uk_74 + 2215456639*uk_75 + 2385060975*uk_76 + 42401084*uk_77 + 2567649375*uk_78 + 45647100*uk_79 + 4*uk_8 + 811504*uk_80 + 166375*uk_81 + 426525*uk_82 + 12100*uk_83 + 36300*uk_84 + 632225*uk_85 + 680625*uk_86 + 12100*uk_87 + 1093455*uk_88 + 31020*uk_89 + 2572416961*uk_9 + 93060*uk_90 + 1620795*uk_91 + 1744875*uk_92 + 31020*uk_93 + 880*uk_94 + 2640*uk_95 + 45980*uk_96 + 49500*uk_97 + 880*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 92840*uk_100 + 99000*uk_101 + 62040*uk_102 + 2448655*uk_103 + 2611125*uk_104 + 1636305*uk_105 + 2784375*uk_106 + 1744875*uk_107 + 1093455*uk_108 + 493039*uk_109 + 4006801*uk_11 + 879981*uk_110 + 49928*uk_111 + 1316851*uk_112 + 1404225*uk_113 + 879981*uk_114 + 1570599*uk_115 + 89112*uk_116 + 2350329*uk_117 + 2506275*uk_118 + 1570599*uk_119 + 7151379*uk_12 + 5056*uk_120 + 133352*uk_121 + 142200*uk_122 + 89112*uk_123 + 3517159*uk_124 + 3750525*uk_125 + 2350329*uk_126 + 3999375*uk_127 + 2506275*uk_128 + 1570599*uk_129 + 405752*uk_13 + 2803221*uk_130 + 159048*uk_131 + 4194891*uk_132 + 4473225*uk_133 + 2803221*uk_134 + 9024*uk_135 + 238008*uk_136 + 253800*uk_137 + 159048*uk_138 + 6277461*uk_139 + 10701709*uk_14 + 6693975*uk_140 + 4194891*uk_141 + 7138125*uk_142 + 4473225*uk_143 + 2803221*uk_144 + 512*uk_145 + 13504*uk_146 + 14400*uk_147 + 9024*uk_148 + 356168*uk_149 + 11411775*uk_15 + 379800*uk_150 + 238008*uk_151 + 405000*uk_152 + 253800*uk_153 + 159048*uk_154 + 9393931*uk_155 + 10017225*uk_156 + 6277461*uk_157 + 10681875*uk_158 + 6693975*uk_159 + 7151379*uk_16 + 4194891*uk_160 + 11390625*uk_161 + 7138125*uk_162 + 4473225*uk_163 + 2803221*uk_164 + 3025*uk_17 + 4345*uk_18 + 7755*uk_19 + 55*uk_2 + 440*uk_20 + 11605*uk_21 + 12375*uk_22 + 7755*uk_23 + 6241*uk_24 + 11139*uk_25 + 632*uk_26 + 16669*uk_27 + 17775*uk_28 + 11139*uk_29 + 79*uk_3 + 19881*uk_30 + 1128*uk_31 + 29751*uk_32 + 31725*uk_33 + 19881*uk_34 + 64*uk_35 + 1688*uk_36 + 1800*uk_37 + 1128*uk_38 + 44521*uk_39 + 141*uk_4 + 47475*uk_40 + 29751*uk_41 + 50625*uk_42 + 31725*uk_43 + 19881*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 203220939919*uk_47 + 362710791501*uk_48 + 20579335688*uk_49 + 8*uk_5 + 542779978771*uk_50 + 578793816225*uk_51 + 362710791501*uk_52 + 153424975*uk_53 + 220374055*uk_54 + 393325845*uk_55 + 22316360*uk_56 + 588593995*uk_57 + 627647625*uk_58 + 393325845*uk_59 + 211*uk_6 + 316537279*uk_60 + 564958941*uk_61 + 32054408*uk_62 + 845435011*uk_63 + 901530225*uk_64 + 564958941*uk_65 + 1008344439*uk_66 + 57211032*uk_67 + 1508940969*uk_68 + 1609060275*uk_69 + 225*uk_7 + 1008344439*uk_70 + 3246016*uk_71 + 85613672*uk_72 + 91294200*uk_73 + 57211032*uk_74 + 2258060599*uk_75 + 2407884525*uk_76 + 1508940969*uk_77 + 2567649375*uk_78 + 1609060275*uk_79 + 141*uk_8 + 1008344439*uk_80 + 166375*uk_81 + 238975*uk_82 + 426525*uk_83 + 24200*uk_84 + 638275*uk_85 + 680625*uk_86 + 426525*uk_87 + 343255*uk_88 + 612645*uk_89 + 2572416961*uk_9 + 34760*uk_90 + 916795*uk_91 + 977625*uk_92 + 612645*uk_93 + 1093455*uk_94 + 62040*uk_95 + 1636305*uk_96 + 1744875*uk_97 + 1093455*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 93720*uk_100 + 99000*uk_101 + 34760*uk_102 + 2495295*uk_103 + 2635875*uk_104 + 925485*uk_105 + 2784375*uk_106 + 977625*uk_107 + 343255*uk_108 + 15625*uk_109 + 1267975*uk_11 + 49375*uk_110 + 5000*uk_111 + 133125*uk_112 + 140625*uk_113 + 49375*uk_114 + 156025*uk_115 + 15800*uk_116 + 420675*uk_117 + 444375*uk_118 + 156025*uk_119 + 4006801*uk_12 + 1600*uk_120 + 42600*uk_121 + 45000*uk_122 + 15800*uk_123 + 1134225*uk_124 + 1198125*uk_125 + 420675*uk_126 + 1265625*uk_127 + 444375*uk_128 + 156025*uk_129 + 405752*uk_13 + 493039*uk_130 + 49928*uk_131 + 1329333*uk_132 + 1404225*uk_133 + 493039*uk_134 + 5056*uk_135 + 134616*uk_136 + 142200*uk_137 + 49928*uk_138 + 3584151*uk_139 + 10803147*uk_14 + 3786075*uk_140 + 1329333*uk_141 + 3999375*uk_142 + 1404225*uk_143 + 493039*uk_144 + 512*uk_145 + 13632*uk_146 + 14400*uk_147 + 5056*uk_148 + 362952*uk_149 + 11411775*uk_15 + 383400*uk_150 + 134616*uk_151 + 405000*uk_152 + 142200*uk_153 + 49928*uk_154 + 9663597*uk_155 + 10208025*uk_156 + 3584151*uk_157 + 10783125*uk_158 + 3786075*uk_159 + 4006801*uk_16 + 1329333*uk_160 + 11390625*uk_161 + 3999375*uk_162 + 1404225*uk_163 + 493039*uk_164 + 3025*uk_17 + 1375*uk_18 + 4345*uk_19 + 55*uk_2 + 440*uk_20 + 11715*uk_21 + 12375*uk_22 + 4345*uk_23 + 625*uk_24 + 1975*uk_25 + 200*uk_26 + 5325*uk_27 + 5625*uk_28 + 1975*uk_29 + 25*uk_3 + 6241*uk_30 + 632*uk_31 + 16827*uk_32 + 17775*uk_33 + 6241*uk_34 + 64*uk_35 + 1704*uk_36 + 1800*uk_37 + 632*uk_38 + 45369*uk_39 + 79*uk_4 + 47925*uk_40 + 16827*uk_41 + 50625*uk_42 + 17775*uk_43 + 6241*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 64310424025*uk_47 + 203220939919*uk_48 + 20579335688*uk_49 + 8*uk_5 + 547924812693*uk_50 + 578793816225*uk_51 + 203220939919*uk_52 + 153424975*uk_53 + 69738625*uk_54 + 220374055*uk_55 + 22316360*uk_56 + 594173085*uk_57 + 627647625*uk_58 + 220374055*uk_59 + 213*uk_6 + 31699375*uk_60 + 100170025*uk_61 + 10143800*uk_62 + 270078675*uk_63 + 285294375*uk_64 + 100170025*uk_65 + 316537279*uk_66 + 32054408*uk_67 + 853448613*uk_68 + 901530225*uk_69 + 225*uk_7 + 316537279*uk_70 + 3246016*uk_71 + 86425176*uk_72 + 91294200*uk_73 + 32054408*uk_74 + 2301070311*uk_75 + 2430708075*uk_76 + 853448613*uk_77 + 2567649375*uk_78 + 901530225*uk_79 + 79*uk_8 + 316537279*uk_80 + 166375*uk_81 + 75625*uk_82 + 238975*uk_83 + 24200*uk_84 + 644325*uk_85 + 680625*uk_86 + 238975*uk_87 + 34375*uk_88 + 108625*uk_89 + 2572416961*uk_9 + 11000*uk_90 + 292875*uk_91 + 309375*uk_92 + 108625*uk_93 + 343255*uk_94 + 34760*uk_95 + 925485*uk_96 + 977625*uk_97 + 343255*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 141900*uk_100 + 148500*uk_101 + 16500*uk_102 + 2542375*uk_103 + 2660625*uk_104 + 295625*uk_105 + 2784375*uk_106 + 309375*uk_107 + 34375*uk_108 + 7301384*uk_109 + 9839486*uk_11 + 940900*uk_110 + 451632*uk_111 + 8091740*uk_112 + 8468100*uk_113 + 940900*uk_114 + 121250*uk_115 + 58200*uk_116 + 1042750*uk_117 + 1091250*uk_118 + 121250*uk_119 + 1267975*uk_12 + 27936*uk_120 + 500520*uk_121 + 523800*uk_122 + 58200*uk_123 + 8967650*uk_124 + 9384750*uk_125 + 1042750*uk_126 + 9821250*uk_127 + 1091250*uk_128 + 121250*uk_129 + 608628*uk_13 + 15625*uk_130 + 7500*uk_131 + 134375*uk_132 + 140625*uk_133 + 15625*uk_134 + 3600*uk_135 + 64500*uk_136 + 67500*uk_137 + 7500*uk_138 + 1155625*uk_139 + 10904585*uk_14 + 1209375*uk_140 + 134375*uk_141 + 1265625*uk_142 + 140625*uk_143 + 15625*uk_144 + 1728*uk_145 + 30960*uk_146 + 32400*uk_147 + 3600*uk_148 + 554700*uk_149 + 11411775*uk_15 + 580500*uk_150 + 64500*uk_151 + 607500*uk_152 + 67500*uk_153 + 7500*uk_154 + 9938375*uk_155 + 10400625*uk_156 + 1155625*uk_157 + 10884375*uk_158 + 1209375*uk_159 + 1267975*uk_16 + 134375*uk_160 + 11390625*uk_161 + 1265625*uk_162 + 140625*uk_163 + 15625*uk_164 + 3025*uk_17 + 10670*uk_18 + 1375*uk_19 + 55*uk_2 + 660*uk_20 + 11825*uk_21 + 12375*uk_22 + 1375*uk_23 + 37636*uk_24 + 4850*uk_25 + 2328*uk_26 + 41710*uk_27 + 43650*uk_28 + 4850*uk_29 + 194*uk_3 + 625*uk_30 + 300*uk_31 + 5375*uk_32 + 5625*uk_33 + 625*uk_34 + 144*uk_35 + 2580*uk_36 + 2700*uk_37 + 300*uk_38 + 46225*uk_39 + 25*uk_4 + 48375*uk_40 + 5375*uk_41 + 50625*uk_42 + 5625*uk_43 + 625*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 499048890434*uk_47 + 64310424025*uk_48 + 30869003532*uk_49 + 12*uk_5 + 553069646615*uk_50 + 578793816225*uk_51 + 64310424025*uk_52 + 153424975*uk_53 + 541171730*uk_54 + 69738625*uk_55 + 33474540*uk_56 + 599752175*uk_57 + 627647625*uk_58 + 69738625*uk_59 + 215*uk_6 + 1908860284*uk_60 + 245987150*uk_61 + 118073832*uk_62 + 2115489490*uk_63 + 2213884350*uk_64 + 245987150*uk_65 + 31699375*uk_66 + 15215700*uk_67 + 272614625*uk_68 + 285294375*uk_69 + 225*uk_7 + 31699375*uk_70 + 7303536*uk_71 + 130855020*uk_72 + 136941300*uk_73 + 15215700*uk_74 + 2344485775*uk_75 + 2453531625*uk_76 + 272614625*uk_77 + 2567649375*uk_78 + 285294375*uk_79 + 25*uk_8 + 31699375*uk_80 + 166375*uk_81 + 586850*uk_82 + 75625*uk_83 + 36300*uk_84 + 650375*uk_85 + 680625*uk_86 + 75625*uk_87 + 2069980*uk_88 + 266750*uk_89 + 2572416961*uk_9 + 128040*uk_90 + 2294050*uk_91 + 2400750*uk_92 + 266750*uk_93 + 34375*uk_94 + 16500*uk_95 + 295625*uk_96 + 309375*uk_97 + 34375*uk_98 + 7920*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 95480*uk_100 + 99000*uk_101 + 85360*uk_102 + 2589895*uk_103 + 2685375*uk_104 + 2315390*uk_105 + 2784375*uk_106 + 2400750*uk_107 + 2069980*uk_108 + 3944312*uk_109 + 8013602*uk_11 + 4843016*uk_110 + 199712*uk_111 + 5417188*uk_112 + 5616900*uk_113 + 4843016*uk_114 + 5946488*uk_115 + 245216*uk_116 + 6651484*uk_117 + 6896700*uk_118 + 5946488*uk_119 + 9839486*uk_12 + 10112*uk_120 + 274288*uk_121 + 284400*uk_122 + 245216*uk_123 + 7440062*uk_124 + 7714350*uk_125 + 6651484*uk_126 + 7998750*uk_127 + 6896700*uk_128 + 5946488*uk_129 + 405752*uk_13 + 7301384*uk_130 + 301088*uk_131 + 8167012*uk_132 + 8468100*uk_133 + 7301384*uk_134 + 12416*uk_135 + 336784*uk_136 + 349200*uk_137 + 301088*uk_138 + 9135266*uk_139 + 11006023*uk_14 + 9472050*uk_140 + 8167012*uk_141 + 9821250*uk_142 + 8468100*uk_143 + 7301384*uk_144 + 512*uk_145 + 13888*uk_146 + 14400*uk_147 + 12416*uk_148 + 376712*uk_149 + 11411775*uk_15 + 390600*uk_150 + 336784*uk_151 + 405000*uk_152 + 349200*uk_153 + 301088*uk_154 + 10218313*uk_155 + 10595025*uk_156 + 9135266*uk_157 + 10985625*uk_158 + 9472050*uk_159 + 9839486*uk_16 + 8167012*uk_160 + 11390625*uk_161 + 9821250*uk_162 + 8468100*uk_163 + 7301384*uk_164 + 3025*uk_17 + 8690*uk_18 + 10670*uk_19 + 55*uk_2 + 440*uk_20 + 11935*uk_21 + 12375*uk_22 + 10670*uk_23 + 24964*uk_24 + 30652*uk_25 + 1264*uk_26 + 34286*uk_27 + 35550*uk_28 + 30652*uk_29 + 158*uk_3 + 37636*uk_30 + 1552*uk_31 + 42098*uk_32 + 43650*uk_33 + 37636*uk_34 + 64*uk_35 + 1736*uk_36 + 1800*uk_37 + 1552*uk_38 + 47089*uk_39 + 194*uk_4 + 48825*uk_40 + 42098*uk_41 + 50625*uk_42 + 43650*uk_43 + 37636*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 406441879838*uk_47 + 499048890434*uk_48 + 20579335688*uk_49 + 8*uk_5 + 558214480537*uk_50 + 578793816225*uk_51 + 499048890434*uk_52 + 153424975*uk_53 + 440748110*uk_54 + 541171730*uk_55 + 22316360*uk_56 + 605331265*uk_57 + 627647625*uk_58 + 541171730*uk_59 + 217*uk_6 + 1266149116*uk_60 + 1554638788*uk_61 + 64108816*uk_62 + 1738951634*uk_63 + 1803060450*uk_64 + 1554638788*uk_65 + 1908860284*uk_66 + 78715888*uk_67 + 2135168462*uk_68 + 2213884350*uk_69 + 225*uk_7 + 1908860284*uk_70 + 3246016*uk_71 + 88048184*uk_72 + 91294200*uk_73 + 78715888*uk_74 + 2388306991*uk_75 + 2476355175*uk_76 + 2135168462*uk_77 + 2567649375*uk_78 + 2213884350*uk_79 + 194*uk_8 + 1908860284*uk_80 + 166375*uk_81 + 477950*uk_82 + 586850*uk_83 + 24200*uk_84 + 656425*uk_85 + 680625*uk_86 + 586850*uk_87 + 1373020*uk_88 + 1685860*uk_89 + 2572416961*uk_9 + 69520*uk_90 + 1885730*uk_91 + 1955250*uk_92 + 1685860*uk_93 + 2069980*uk_94 + 85360*uk_95 + 2315390*uk_96 + 2400750*uk_97 + 2069980*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 96360*uk_100 + 99000*uk_101 + 69520*uk_102 + 2637855*uk_103 + 2710125*uk_104 + 1903110*uk_105 + 2784375*uk_106 + 1955250*uk_107 + 1373020*uk_108 + 2197000*uk_109 + 6593470*uk_11 + 2670200*uk_110 + 135200*uk_111 + 3701100*uk_112 + 3802500*uk_113 + 2670200*uk_114 + 3245320*uk_115 + 164320*uk_116 + 4498260*uk_117 + 4621500*uk_118 + 3245320*uk_119 + 8013602*uk_12 + 8320*uk_120 + 227760*uk_121 + 234000*uk_122 + 164320*uk_123 + 6234930*uk_124 + 6405750*uk_125 + 4498260*uk_126 + 6581250*uk_127 + 4621500*uk_128 + 3245320*uk_129 + 405752*uk_13 + 3944312*uk_130 + 199712*uk_131 + 5467116*uk_132 + 5616900*uk_133 + 3944312*uk_134 + 10112*uk_135 + 276816*uk_136 + 284400*uk_137 + 199712*uk_138 + 7577838*uk_139 + 11107461*uk_14 + 7785450*uk_140 + 5467116*uk_141 + 7998750*uk_142 + 5616900*uk_143 + 3944312*uk_144 + 512*uk_145 + 14016*uk_146 + 14400*uk_147 + 10112*uk_148 + 383688*uk_149 + 11411775*uk_15 + 394200*uk_150 + 276816*uk_151 + 405000*uk_152 + 284400*uk_153 + 199712*uk_154 + 10503459*uk_155 + 10791225*uk_156 + 7577838*uk_157 + 11086875*uk_158 + 7785450*uk_159 + 8013602*uk_16 + 5467116*uk_160 + 11390625*uk_161 + 7998750*uk_162 + 5616900*uk_163 + 3944312*uk_164 + 3025*uk_17 + 7150*uk_18 + 8690*uk_19 + 55*uk_2 + 440*uk_20 + 12045*uk_21 + 12375*uk_22 + 8690*uk_23 + 16900*uk_24 + 20540*uk_25 + 1040*uk_26 + 28470*uk_27 + 29250*uk_28 + 20540*uk_29 + 130*uk_3 + 24964*uk_30 + 1264*uk_31 + 34602*uk_32 + 35550*uk_33 + 24964*uk_34 + 64*uk_35 + 1752*uk_36 + 1800*uk_37 + 1264*uk_38 + 47961*uk_39 + 158*uk_4 + 49275*uk_40 + 34602*uk_41 + 50625*uk_42 + 35550*uk_43 + 24964*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 334414204930*uk_47 + 406441879838*uk_48 + 20579335688*uk_49 + 8*uk_5 + 563359314459*uk_50 + 578793816225*uk_51 + 406441879838*uk_52 + 153424975*uk_53 + 362640850*uk_54 + 440748110*uk_55 + 22316360*uk_56 + 610910355*uk_57 + 627647625*uk_58 + 440748110*uk_59 + 219*uk_6 + 857151100*uk_60 + 1041768260*uk_61 + 52747760*uk_62 + 1443969930*uk_63 + 1483530750*uk_64 + 1041768260*uk_65 + 1266149116*uk_66 + 64108816*uk_67 + 1754978838*uk_68 + 1803060450*uk_69 + 225*uk_7 + 1266149116*uk_70 + 3246016*uk_71 + 88859688*uk_72 + 91294200*uk_73 + 64108816*uk_74 + 2432533959*uk_75 + 2499178725*uk_76 + 1754978838*uk_77 + 2567649375*uk_78 + 1803060450*uk_79 + 158*uk_8 + 1266149116*uk_80 + 166375*uk_81 + 393250*uk_82 + 477950*uk_83 + 24200*uk_84 + 662475*uk_85 + 680625*uk_86 + 477950*uk_87 + 929500*uk_88 + 1129700*uk_89 + 2572416961*uk_9 + 57200*uk_90 + 1565850*uk_91 + 1608750*uk_92 + 1129700*uk_93 + 1373020*uk_94 + 69520*uk_95 + 1903110*uk_96 + 1955250*uk_97 + 1373020*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 97240*uk_100 + 99000*uk_101 + 57200*uk_102 + 2686255*uk_103 + 2734875*uk_104 + 1580150*uk_105 + 2784375*uk_106 + 1608750*uk_107 + 929500*uk_108 + 1331000*uk_109 + 5579090*uk_11 + 1573000*uk_110 + 96800*uk_111 + 2674100*uk_112 + 2722500*uk_113 + 1573000*uk_114 + 1859000*uk_115 + 114400*uk_116 + 3160300*uk_117 + 3217500*uk_118 + 1859000*uk_119 + 6593470*uk_12 + 7040*uk_120 + 194480*uk_121 + 198000*uk_122 + 114400*uk_123 + 5372510*uk_124 + 5469750*uk_125 + 3160300*uk_126 + 5568750*uk_127 + 3217500*uk_128 + 1859000*uk_129 + 405752*uk_13 + 2197000*uk_130 + 135200*uk_131 + 3734900*uk_132 + 3802500*uk_133 + 2197000*uk_134 + 8320*uk_135 + 229840*uk_136 + 234000*uk_137 + 135200*uk_138 + 6349330*uk_139 + 11208899*uk_14 + 6464250*uk_140 + 3734900*uk_141 + 6581250*uk_142 + 3802500*uk_143 + 2197000*uk_144 + 512*uk_145 + 14144*uk_146 + 14400*uk_147 + 8320*uk_148 + 390728*uk_149 + 11411775*uk_15 + 397800*uk_150 + 229840*uk_151 + 405000*uk_152 + 234000*uk_153 + 135200*uk_154 + 10793861*uk_155 + 10989225*uk_156 + 6349330*uk_157 + 11188125*uk_158 + 6464250*uk_159 + 6593470*uk_16 + 3734900*uk_160 + 11390625*uk_161 + 6581250*uk_162 + 3802500*uk_163 + 2197000*uk_164 + 3025*uk_17 + 6050*uk_18 + 7150*uk_19 + 55*uk_2 + 440*uk_20 + 12155*uk_21 + 12375*uk_22 + 7150*uk_23 + 12100*uk_24 + 14300*uk_25 + 880*uk_26 + 24310*uk_27 + 24750*uk_28 + 14300*uk_29 + 110*uk_3 + 16900*uk_30 + 1040*uk_31 + 28730*uk_32 + 29250*uk_33 + 16900*uk_34 + 64*uk_35 + 1768*uk_36 + 1800*uk_37 + 1040*uk_38 + 48841*uk_39 + 130*uk_4 + 49725*uk_40 + 28730*uk_41 + 50625*uk_42 + 29250*uk_43 + 16900*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 282965865710*uk_47 + 334414204930*uk_48 + 20579335688*uk_49 + 8*uk_5 + 568504148381*uk_50 + 578793816225*uk_51 + 334414204930*uk_52 + 153424975*uk_53 + 306849950*uk_54 + 362640850*uk_55 + 22316360*uk_56 + 616489445*uk_57 + 627647625*uk_58 + 362640850*uk_59 + 221*uk_6 + 613699900*uk_60 + 725281700*uk_61 + 44632720*uk_62 + 1232978890*uk_63 + 1255295250*uk_64 + 725281700*uk_65 + 857151100*uk_66 + 52747760*uk_67 + 1457156870*uk_68 + 1483530750*uk_69 + 225*uk_7 + 857151100*uk_70 + 3246016*uk_71 + 89671192*uk_72 + 91294200*uk_73 + 52747760*uk_74 + 2477166679*uk_75 + 2522002275*uk_76 + 1457156870*uk_77 + 2567649375*uk_78 + 1483530750*uk_79 + 130*uk_8 + 857151100*uk_80 + 166375*uk_81 + 332750*uk_82 + 393250*uk_83 + 24200*uk_84 + 668525*uk_85 + 680625*uk_86 + 393250*uk_87 + 665500*uk_88 + 786500*uk_89 + 2572416961*uk_9 + 48400*uk_90 + 1337050*uk_91 + 1361250*uk_92 + 786500*uk_93 + 929500*uk_94 + 57200*uk_95 + 1580150*uk_96 + 1608750*uk_97 + 929500*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 98120*uk_100 + 99000*uk_101 + 48400*uk_102 + 2735095*uk_103 + 2759625*uk_104 + 1349150*uk_105 + 2784375*uk_106 + 1361250*uk_107 + 665500*uk_108 + 941192*uk_109 + 4970462*uk_11 + 1056440*uk_110 + 76832*uk_111 + 2141692*uk_112 + 2160900*uk_113 + 1056440*uk_114 + 1185800*uk_115 + 86240*uk_116 + 2403940*uk_117 + 2425500*uk_118 + 1185800*uk_119 + 5579090*uk_12 + 6272*uk_120 + 174832*uk_121 + 176400*uk_122 + 86240*uk_123 + 4873442*uk_124 + 4917150*uk_125 + 2403940*uk_126 + 4961250*uk_127 + 2425500*uk_128 + 1185800*uk_129 + 405752*uk_13 + 1331000*uk_130 + 96800*uk_131 + 2698300*uk_132 + 2722500*uk_133 + 1331000*uk_134 + 7040*uk_135 + 196240*uk_136 + 198000*uk_137 + 96800*uk_138 + 5470190*uk_139 + 11310337*uk_14 + 5519250*uk_140 + 2698300*uk_141 + 5568750*uk_142 + 2722500*uk_143 + 1331000*uk_144 + 512*uk_145 + 14272*uk_146 + 14400*uk_147 + 7040*uk_148 + 397832*uk_149 + 11411775*uk_15 + 401400*uk_150 + 196240*uk_151 + 405000*uk_152 + 198000*uk_153 + 96800*uk_154 + 11089567*uk_155 + 11189025*uk_156 + 5470190*uk_157 + 11289375*uk_158 + 5519250*uk_159 + 5579090*uk_16 + 2698300*uk_160 + 11390625*uk_161 + 5568750*uk_162 + 2722500*uk_163 + 1331000*uk_164 + 3025*uk_17 + 5390*uk_18 + 6050*uk_19 + 55*uk_2 + 440*uk_20 + 12265*uk_21 + 12375*uk_22 + 6050*uk_23 + 9604*uk_24 + 10780*uk_25 + 784*uk_26 + 21854*uk_27 + 22050*uk_28 + 10780*uk_29 + 98*uk_3 + 12100*uk_30 + 880*uk_31 + 24530*uk_32 + 24750*uk_33 + 12100*uk_34 + 64*uk_35 + 1784*uk_36 + 1800*uk_37 + 880*uk_38 + 49729*uk_39 + 110*uk_4 + 50175*uk_40 + 24530*uk_41 + 50625*uk_42 + 24750*uk_43 + 12100*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 252096862178*uk_47 + 282965865710*uk_48 + 20579335688*uk_49 + 8*uk_5 + 573648982303*uk_50 + 578793816225*uk_51 + 282965865710*uk_52 + 153424975*uk_53 + 273375410*uk_54 + 306849950*uk_55 + 22316360*uk_56 + 622068535*uk_57 + 627647625*uk_58 + 306849950*uk_59 + 223*uk_6 + 487105276*uk_60 + 546750820*uk_61 + 39763696*uk_62 + 1108413026*uk_63 + 1118353950*uk_64 + 546750820*uk_65 + 613699900*uk_66 + 44632720*uk_67 + 1244137070*uk_68 + 1255295250*uk_69 + 225*uk_7 + 613699900*uk_70 + 3246016*uk_71 + 90482696*uk_72 + 91294200*uk_73 + 44632720*uk_74 + 2522205151*uk_75 + 2544825825*uk_76 + 1244137070*uk_77 + 2567649375*uk_78 + 1255295250*uk_79 + 110*uk_8 + 613699900*uk_80 + 166375*uk_81 + 296450*uk_82 + 332750*uk_83 + 24200*uk_84 + 674575*uk_85 + 680625*uk_86 + 332750*uk_87 + 528220*uk_88 + 592900*uk_89 + 2572416961*uk_9 + 43120*uk_90 + 1201970*uk_91 + 1212750*uk_92 + 592900*uk_93 + 665500*uk_94 + 48400*uk_95 + 1349150*uk_96 + 1361250*uk_97 + 665500*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 99000*uk_100 + 99000*uk_101 + 43120*uk_102 + 2784375*uk_103 + 2784375*uk_104 + 1212750*uk_105 + 2784375*uk_106 + 1212750*uk_107 + 528220*uk_108 + 830584*uk_109 + 4767586*uk_11 + 865928*uk_110 + 70688*uk_111 + 1988100*uk_112 + 1988100*uk_113 + 865928*uk_114 + 902776*uk_115 + 73696*uk_116 + 2072700*uk_117 + 2072700*uk_118 + 902776*uk_119 + 4970462*uk_12 + 6016*uk_120 + 169200*uk_121 + 169200*uk_122 + 73696*uk_123 + 4758750*uk_124 + 4758750*uk_125 + 2072700*uk_126 + 4758750*uk_127 + 2072700*uk_128 + 902776*uk_129 + 405752*uk_13 + 941192*uk_130 + 76832*uk_131 + 2160900*uk_132 + 2160900*uk_133 + 941192*uk_134 + 6272*uk_135 + 176400*uk_136 + 176400*uk_137 + 76832*uk_138 + 4961250*uk_139 + 11411775*uk_14 + 4961250*uk_140 + 2160900*uk_141 + 4961250*uk_142 + 2160900*uk_143 + 941192*uk_144 + 512*uk_145 + 14400*uk_146 + 14400*uk_147 + 6272*uk_148 + 405000*uk_149 + 11411775*uk_15 + 405000*uk_150 + 176400*uk_151 + 405000*uk_152 + 176400*uk_153 + 76832*uk_154 + 11390625*uk_155 + 11390625*uk_156 + 4961250*uk_157 + 11390625*uk_158 + 4961250*uk_159 + 4970462*uk_16 + 2160900*uk_160 + 11390625*uk_161 + 4961250*uk_162 + 2160900*uk_163 + 941192*uk_164 + 3025*uk_17 + 5170*uk_18 + 5390*uk_19 + 55*uk_2 + 440*uk_20 + 12375*uk_21 + 12375*uk_22 + 5390*uk_23 + 8836*uk_24 + 9212*uk_25 + 752*uk_26 + 21150*uk_27 + 21150*uk_28 + 9212*uk_29 + 94*uk_3 + 9604*uk_30 + 784*uk_31 + 22050*uk_32 + 22050*uk_33 + 9604*uk_34 + 64*uk_35 + 1800*uk_36 + 1800*uk_37 + 784*uk_38 + 50625*uk_39 + 98*uk_4 + 50625*uk_40 + 22050*uk_41 + 50625*uk_42 + 22050*uk_43 + 9604*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 241807194334*uk_47 + 252096862178*uk_48 + 20579335688*uk_49 + 8*uk_5 + 578793816225*uk_50 + 578793816225*uk_51 + 252096862178*uk_52 + 153424975*uk_53 + 262217230*uk_54 + 273375410*uk_55 + 22316360*uk_56 + 627647625*uk_57 + 627647625*uk_58 + 273375410*uk_59 + 225*uk_6 + 448153084*uk_60 + 467223428*uk_61 + 38140688*uk_62 + 1072706850*uk_63 + 1072706850*uk_64 + 467223428*uk_65 + 487105276*uk_66 + 39763696*uk_67 + 1118353950*uk_68 + 1118353950*uk_69 + 225*uk_7 + 487105276*uk_70 + 3246016*uk_71 + 91294200*uk_72 + 91294200*uk_73 + 39763696*uk_74 + 2567649375*uk_75 + 2567649375*uk_76 + 1118353950*uk_77 + 2567649375*uk_78 + 1118353950*uk_79 + 98*uk_8 + 487105276*uk_80 + 166375*uk_81 + 284350*uk_82 + 296450*uk_83 + 24200*uk_84 + 680625*uk_85 + 680625*uk_86 + 296450*uk_87 + 485980*uk_88 + 506660*uk_89 + 2572416961*uk_9 + 41360*uk_90 + 1163250*uk_91 + 1163250*uk_92 + 506660*uk_93 + 528220*uk_94 + 43120*uk_95 + 1212750*uk_96 + 1212750*uk_97 + 528220*uk_98 + 3520*uk_99, + uk_0 + 50719*uk_1 + 2789545*uk_10 + 99880*uk_100 + 99000*uk_101 + 41360*uk_102 + 2834095*uk_103 + 2809125*uk_104 + 1173590*uk_105 + 2784375*uk_106 + 1163250*uk_107 + 485980*uk_108 + 941192*uk_109 + 4970462*uk_11 + 902776*uk_110 + 76832*uk_111 + 2180108*uk_112 + 2160900*uk_113 + 902776*uk_114 + 865928*uk_115 + 73696*uk_116 + 2091124*uk_117 + 2072700*uk_118 + 865928*uk_119 + 4767586*uk_12 + 6272*uk_120 + 177968*uk_121 + 176400*uk_122 + 73696*uk_123 + 5049842*uk_124 + 5005350*uk_125 + 2091124*uk_126 + 4961250*uk_127 + 2072700*uk_128 + 865928*uk_129 + 405752*uk_13 + 830584*uk_130 + 70688*uk_131 + 2005772*uk_132 + 1988100*uk_133 + 830584*uk_134 + 6016*uk_135 + 170704*uk_136 + 169200*uk_137 + 70688*uk_138 + 4843726*uk_139 + 11513213*uk_14 + 4801050*uk_140 + 2005772*uk_141 + 4758750*uk_142 + 1988100*uk_143 + 830584*uk_144 + 512*uk_145 + 14528*uk_146 + 14400*uk_147 + 6016*uk_148 + 412232*uk_149 + 11411775*uk_15 + 408600*uk_150 + 170704*uk_151 + 405000*uk_152 + 169200*uk_153 + 70688*uk_154 + 11697083*uk_155 + 11594025*uk_156 + 4843726*uk_157 + 11491875*uk_158 + 4801050*uk_159 + 4767586*uk_16 + 2005772*uk_160 + 11390625*uk_161 + 4758750*uk_162 + 1988100*uk_163 + 830584*uk_164 + 3025*uk_17 + 5390*uk_18 + 5170*uk_19 + 55*uk_2 + 440*uk_20 + 12485*uk_21 + 12375*uk_22 + 5170*uk_23 + 9604*uk_24 + 9212*uk_25 + 784*uk_26 + 22246*uk_27 + 22050*uk_28 + 9212*uk_29 + 98*uk_3 + 8836*uk_30 + 752*uk_31 + 21338*uk_32 + 21150*uk_33 + 8836*uk_34 + 64*uk_35 + 1816*uk_36 + 1800*uk_37 + 752*uk_38 + 51529*uk_39 + 94*uk_4 + 51075*uk_40 + 21338*uk_41 + 50625*uk_42 + 21150*uk_43 + 8836*uk_44 + 130470415844959*uk_45 + 141482932855*uk_46 + 252096862178*uk_47 + 241807194334*uk_48 + 20579335688*uk_49 + 8*uk_5 + 583938650147*uk_50 + 578793816225*uk_51 + 241807194334*uk_52 + 153424975*uk_53 + 273375410*uk_54 + 262217230*uk_55 + 22316360*uk_56 + 633226715*uk_57 + 627647625*uk_58 + 262217230*uk_59 + 227*uk_6 + 487105276*uk_60 + 467223428*uk_61 + 39763696*uk_62 + 1128294874*uk_63 + 1118353950*uk_64 + 467223428*uk_65 + 448153084*uk_66 + 38140688*uk_67 + 1082242022*uk_68 + 1072706850*uk_69 + 225*uk_7 + 448153084*uk_70 + 3246016*uk_71 + 92105704*uk_72 + 91294200*uk_73 + 38140688*uk_74 + 2613499351*uk_75 + 2590472925*uk_76 + 1082242022*uk_77 + 2567649375*uk_78 + 1072706850*uk_79 + 94*uk_8 + 448153084*uk_80 + 166375*uk_81 + 296450*uk_82 + 284350*uk_83 + 24200*uk_84 + 686675*uk_85 + 680625*uk_86 + 284350*uk_87 + 528220*uk_88 + 506660*uk_89 + 2572416961*uk_9 + 43120*uk_90 + 1223530*uk_91 + 1212750*uk_92 + 506660*uk_93 + 485980*uk_94 + 41360*uk_95 + 1173590*uk_96 + 1163250*uk_97 + 485980*uk_98 + 3520*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 396900*uk_100 + 1367100*uk_101 + 250047*uk_103 + 861273*uk_104 + 2966607*uk_106 + 64000*uk_109 + 1894120*uk_11 + 27200*uk_110 + 160000*uk_111 + 100800*uk_112 + 347200*uk_113 + 11560*uk_115 + 68000*uk_116 + 42840*uk_117 + 147560*uk_118 + 805001*uk_12 + 400000*uk_120 + 252000*uk_121 + 868000*uk_122 + 158760*uk_124 + 546840*uk_125 + 1883560*uk_127 + 4735300*uk_13 + 4913*uk_130 + 28900*uk_131 + 18207*uk_132 + 62713*uk_133 + 170000*uk_135 + 107100*uk_136 + 368900*uk_137 + 67473*uk_139 + 2983239*uk_14 + 232407*uk_140 + 800513*uk_142 + 1000000*uk_145 + 630000*uk_146 + 2170000*uk_147 + 396900*uk_149 + 10275601*uk_15 + 1367100*uk_150 + 4708900*uk_152 + 250047*uk_155 + 861273*uk_156 + 2966607*uk_158 + 10218313*uk_161 + 3969*uk_17 + 2520*uk_18 + 1071*uk_19 + 63*uk_2 + 6300*uk_20 + 3969*uk_21 + 13671*uk_22 + 1600*uk_24 + 680*uk_25 + 4000*uk_26 + 2520*uk_27 + 8680*uk_28 + 40*uk_3 + 289*uk_30 + 1700*uk_31 + 1071*uk_32 + 3689*uk_33 + 10000*uk_35 + 6300*uk_36 + 21700*uk_37 + 3969*uk_39 + 17*uk_4 + 13671*uk_40 + 47089*uk_42 + 106179944855977*uk_45 + 141265316367*uk_46 + 89692264360*uk_47 + 38119212353*uk_48 + 224230660900*uk_49 + 100*uk_5 + 141265316367*uk_50 + 486580534153*uk_51 + 187944057*uk_53 + 119329560*uk_54 + 50715063*uk_55 + 298323900*uk_56 + 187944057*uk_57 + 647362863*uk_58 + 63*uk_6 + 75764800*uk_60 + 32200040*uk_61 + 189412000*uk_62 + 119329560*uk_63 + 411024040*uk_64 + 13685017*uk_66 + 80500100*uk_67 + 50715063*uk_68 + 174685217*uk_69 + 217*uk_7 + 473530000*uk_71 + 298323900*uk_72 + 1027560100*uk_73 + 187944057*uk_75 + 647362863*uk_76 + 2229805417*uk_78 + 250047*uk_81 + 158760*uk_82 + 67473*uk_83 + 396900*uk_84 + 250047*uk_85 + 861273*uk_86 + 100800*uk_88 + 42840*uk_89 + 2242306609*uk_9 + 252000*uk_90 + 158760*uk_91 + 546840*uk_92 + 18207*uk_94 + 107100*uk_95 + 67473*uk_96 + 232407*uk_97 + 630000*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 376740*uk_100 + 1257732*uk_101 + 231840*uk_102 + 266175*uk_103 + 888615*uk_104 + 163800*uk_105 + 2966607*uk_106 + 546840*uk_107 + 100800*uk_108 + 35937*uk_109 + 1562649*uk_11 + 43560*uk_110 + 100188*uk_111 + 70785*uk_112 + 236313*uk_113 + 43560*uk_114 + 52800*uk_115 + 121440*uk_116 + 85800*uk_117 + 286440*uk_118 + 52800*uk_119 + 1894120*uk_12 + 279312*uk_120 + 197340*uk_121 + 658812*uk_122 + 121440*uk_123 + 139425*uk_124 + 465465*uk_125 + 85800*uk_126 + 1553937*uk_127 + 286440*uk_128 + 52800*uk_129 + 4356476*uk_13 + 64000*uk_130 + 147200*uk_131 + 104000*uk_132 + 347200*uk_133 + 64000*uk_134 + 338560*uk_135 + 239200*uk_136 + 798560*uk_137 + 147200*uk_138 + 169000*uk_139 + 3077945*uk_14 + 564200*uk_140 + 104000*uk_141 + 1883560*uk_142 + 347200*uk_143 + 64000*uk_144 + 778688*uk_145 + 550160*uk_146 + 1836688*uk_147 + 338560*uk_148 + 388700*uk_149 + 10275601*uk_15 + 1297660*uk_150 + 239200*uk_151 + 4332188*uk_152 + 798560*uk_153 + 147200*uk_154 + 274625*uk_155 + 916825*uk_156 + 169000*uk_157 + 3060785*uk_158 + 564200*uk_159 + 1894120*uk_16 + 104000*uk_160 + 10218313*uk_161 + 1883560*uk_162 + 347200*uk_163 + 64000*uk_164 + 3969*uk_17 + 2079*uk_18 + 2520*uk_19 + 63*uk_2 + 5796*uk_20 + 4095*uk_21 + 13671*uk_22 + 2520*uk_23 + 1089*uk_24 + 1320*uk_25 + 3036*uk_26 + 2145*uk_27 + 7161*uk_28 + 1320*uk_29 + 33*uk_3 + 1600*uk_30 + 3680*uk_31 + 2600*uk_32 + 8680*uk_33 + 1600*uk_34 + 8464*uk_35 + 5980*uk_36 + 19964*uk_37 + 3680*uk_38 + 4225*uk_39 + 40*uk_4 + 14105*uk_40 + 2600*uk_41 + 47089*uk_42 + 8680*uk_43 + 1600*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 73996118097*uk_47 + 89692264360*uk_48 + 206292208028*uk_49 + 92*uk_5 + 145749929585*uk_50 + 486580534153*uk_51 + 89692264360*uk_52 + 187944057*uk_53 + 98446887*uk_54 + 119329560*uk_55 + 274457988*uk_56 + 193910535*uk_57 + 647362863*uk_58 + 119329560*uk_59 + 65*uk_6 + 51567417*uk_60 + 62505960*uk_61 + 143763708*uk_62 + 101572185*uk_63 + 339094833*uk_64 + 62505960*uk_65 + 75764800*uk_66 + 174259040*uk_67 + 123117800*uk_68 + 411024040*uk_69 + 217*uk_7 + 75764800*uk_70 + 400795792*uk_71 + 283170940*uk_72 + 945355292*uk_73 + 174259040*uk_74 + 200066425*uk_75 + 667914065*uk_76 + 123117800*uk_77 + 2229805417*uk_78 + 411024040*uk_79 + 40*uk_8 + 75764800*uk_80 + 250047*uk_81 + 130977*uk_82 + 158760*uk_83 + 365148*uk_84 + 257985*uk_85 + 861273*uk_86 + 158760*uk_87 + 68607*uk_88 + 83160*uk_89 + 2242306609*uk_9 + 191268*uk_90 + 135135*uk_91 + 451143*uk_92 + 83160*uk_93 + 100800*uk_94 + 231840*uk_95 + 163800*uk_96 + 546840*uk_97 + 100800*uk_98 + 533232*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 371448*uk_100 + 1203048*uk_101 + 182952*uk_102 + 282807*uk_103 + 915957*uk_104 + 139293*uk_105 + 2966607*uk_106 + 451143*uk_107 + 68607*uk_108 + 132651*uk_109 + 2415003*uk_11 + 85833*uk_110 + 228888*uk_111 + 174267*uk_112 + 564417*uk_113 + 85833*uk_114 + 55539*uk_115 + 148104*uk_116 + 112761*uk_117 + 365211*uk_118 + 55539*uk_119 + 1562649*uk_12 + 394944*uk_120 + 300696*uk_121 + 973896*uk_122 + 148104*uk_123 + 228939*uk_124 + 741489*uk_125 + 112761*uk_126 + 2401539*uk_127 + 365211*uk_128 + 55539*uk_129 + 4167064*uk_13 + 35937*uk_130 + 95832*uk_131 + 72963*uk_132 + 236313*uk_133 + 35937*uk_134 + 255552*uk_135 + 194568*uk_136 + 630168*uk_137 + 95832*uk_138 + 148137*uk_139 + 3172651*uk_14 + 479787*uk_140 + 72963*uk_141 + 1553937*uk_142 + 236313*uk_143 + 35937*uk_144 + 681472*uk_145 + 518848*uk_146 + 1680448*uk_147 + 255552*uk_148 + 395032*uk_149 + 10275601*uk_15 + 1279432*uk_150 + 194568*uk_151 + 4143832*uk_152 + 630168*uk_153 + 95832*uk_154 + 300763*uk_155 + 974113*uk_156 + 148137*uk_157 + 3154963*uk_158 + 479787*uk_159 + 1562649*uk_16 + 72963*uk_160 + 10218313*uk_161 + 1553937*uk_162 + 236313*uk_163 + 35937*uk_164 + 3969*uk_17 + 3213*uk_18 + 2079*uk_19 + 63*uk_2 + 5544*uk_20 + 4221*uk_21 + 13671*uk_22 + 2079*uk_23 + 2601*uk_24 + 1683*uk_25 + 4488*uk_26 + 3417*uk_27 + 11067*uk_28 + 1683*uk_29 + 51*uk_3 + 1089*uk_30 + 2904*uk_31 + 2211*uk_32 + 7161*uk_33 + 1089*uk_34 + 7744*uk_35 + 5896*uk_36 + 19096*uk_37 + 2904*uk_38 + 4489*uk_39 + 33*uk_4 + 14539*uk_40 + 2211*uk_41 + 47089*uk_42 + 7161*uk_43 + 1089*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 114357637059*uk_47 + 73996118097*uk_48 + 197322981592*uk_49 + 88*uk_5 + 150234542803*uk_50 + 486580534153*uk_51 + 73996118097*uk_52 + 187944057*uk_53 + 152145189*uk_54 + 98446887*uk_55 + 262525032*uk_56 + 199877013*uk_57 + 647362863*uk_58 + 98446887*uk_59 + 67*uk_6 + 123165153*uk_60 + 79695099*uk_61 + 212520264*uk_62 + 161805201*uk_63 + 524055651*uk_64 + 79695099*uk_65 + 51567417*uk_66 + 137513112*uk_67 + 104697483*uk_68 + 339094833*uk_69 + 217*uk_7 + 51567417*uk_70 + 366701632*uk_71 + 279193288*uk_72 + 904252888*uk_73 + 137513112*uk_74 + 212567617*uk_75 + 688465267*uk_76 + 104697483*uk_77 + 2229805417*uk_78 + 339094833*uk_79 + 33*uk_8 + 51567417*uk_80 + 250047*uk_81 + 202419*uk_82 + 130977*uk_83 + 349272*uk_84 + 265923*uk_85 + 861273*uk_86 + 130977*uk_87 + 163863*uk_88 + 106029*uk_89 + 2242306609*uk_9 + 282744*uk_90 + 215271*uk_91 + 697221*uk_92 + 106029*uk_93 + 68607*uk_94 + 182952*uk_95 + 139293*uk_96 + 451143*uk_97 + 68607*uk_98 + 487872*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 347760*uk_100 + 1093680*uk_101 + 257040*uk_102 + 299943*uk_103 + 943299*uk_104 + 221697*uk_105 + 2966607*uk_106 + 697221*uk_107 + 163863*uk_108 + 6859*uk_109 + 899707*uk_11 + 18411*uk_110 + 28880*uk_111 + 24909*uk_112 + 78337*uk_113 + 18411*uk_114 + 49419*uk_115 + 77520*uk_116 + 66861*uk_117 + 210273*uk_118 + 49419*uk_119 + 2415003*uk_12 + 121600*uk_120 + 104880*uk_121 + 329840*uk_122 + 77520*uk_123 + 90459*uk_124 + 284487*uk_125 + 66861*uk_126 + 894691*uk_127 + 210273*uk_128 + 49419*uk_129 + 3788240*uk_13 + 132651*uk_130 + 208080*uk_131 + 179469*uk_132 + 564417*uk_133 + 132651*uk_134 + 326400*uk_135 + 281520*uk_136 + 885360*uk_137 + 208080*uk_138 + 242811*uk_139 + 3267357*uk_14 + 763623*uk_140 + 179469*uk_141 + 2401539*uk_142 + 564417*uk_143 + 132651*uk_144 + 512000*uk_145 + 441600*uk_146 + 1388800*uk_147 + 326400*uk_148 + 380880*uk_149 + 10275601*uk_15 + 1197840*uk_150 + 281520*uk_151 + 3767120*uk_152 + 885360*uk_153 + 208080*uk_154 + 328509*uk_155 + 1033137*uk_156 + 242811*uk_157 + 3249141*uk_158 + 763623*uk_159 + 2415003*uk_16 + 179469*uk_160 + 10218313*uk_161 + 2401539*uk_162 + 564417*uk_163 + 132651*uk_164 + 3969*uk_17 + 1197*uk_18 + 3213*uk_19 + 63*uk_2 + 5040*uk_20 + 4347*uk_21 + 13671*uk_22 + 3213*uk_23 + 361*uk_24 + 969*uk_25 + 1520*uk_26 + 1311*uk_27 + 4123*uk_28 + 969*uk_29 + 19*uk_3 + 2601*uk_30 + 4080*uk_31 + 3519*uk_32 + 11067*uk_33 + 2601*uk_34 + 6400*uk_35 + 5520*uk_36 + 17360*uk_37 + 4080*uk_38 + 4761*uk_39 + 51*uk_4 + 14973*uk_40 + 3519*uk_41 + 47089*uk_42 + 11067*uk_43 + 2601*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 42603825571*uk_47 + 114357637059*uk_48 + 179384528720*uk_49 + 80*uk_5 + 154719156021*uk_50 + 486580534153*uk_51 + 114357637059*uk_52 + 187944057*uk_53 + 56681541*uk_54 + 152145189*uk_55 + 238659120*uk_56 + 205843491*uk_57 + 647362863*uk_58 + 152145189*uk_59 + 69*uk_6 + 17094433*uk_60 + 45885057*uk_61 + 71976560*uk_62 + 62079783*uk_63 + 195236419*uk_64 + 45885057*uk_65 + 123165153*uk_66 + 193200240*uk_67 + 166635207*uk_68 + 524055651*uk_69 + 217*uk_7 + 123165153*uk_70 + 303059200*uk_71 + 261388560*uk_72 + 822048080*uk_73 + 193200240*uk_74 + 225447633*uk_75 + 709016469*uk_76 + 166635207*uk_77 + 2229805417*uk_78 + 524055651*uk_79 + 51*uk_8 + 123165153*uk_80 + 250047*uk_81 + 75411*uk_82 + 202419*uk_83 + 317520*uk_84 + 273861*uk_85 + 861273*uk_86 + 202419*uk_87 + 22743*uk_88 + 61047*uk_89 + 2242306609*uk_9 + 95760*uk_90 + 82593*uk_91 + 259749*uk_92 + 61047*uk_93 + 163863*uk_94 + 257040*uk_95 + 221697*uk_96 + 697221*uk_97 + 163863*uk_98 + 403200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 357840*uk_100 + 1093680*uk_101 + 95760*uk_102 + 317583*uk_103 + 970641*uk_104 + 84987*uk_105 + 2966607*uk_106 + 259749*uk_107 + 22743*uk_108 + 300763*uk_109 + 3172651*uk_11 + 85291*uk_110 + 359120*uk_111 + 318719*uk_112 + 974113*uk_113 + 85291*uk_114 + 24187*uk_115 + 101840*uk_116 + 90383*uk_117 + 276241*uk_118 + 24187*uk_119 + 899707*uk_12 + 428800*uk_120 + 380560*uk_121 + 1163120*uk_122 + 101840*uk_123 + 337747*uk_124 + 1032269*uk_125 + 90383*uk_126 + 3154963*uk_127 + 276241*uk_128 + 24187*uk_129 + 3788240*uk_13 + 6859*uk_130 + 28880*uk_131 + 25631*uk_132 + 78337*uk_133 + 6859*uk_134 + 121600*uk_135 + 107920*uk_136 + 329840*uk_137 + 28880*uk_138 + 95779*uk_139 + 3362063*uk_14 + 292733*uk_140 + 25631*uk_141 + 894691*uk_142 + 78337*uk_143 + 6859*uk_144 + 512000*uk_145 + 454400*uk_146 + 1388800*uk_147 + 121600*uk_148 + 403280*uk_149 + 10275601*uk_15 + 1232560*uk_150 + 107920*uk_151 + 3767120*uk_152 + 329840*uk_153 + 28880*uk_154 + 357911*uk_155 + 1093897*uk_156 + 95779*uk_157 + 3343319*uk_158 + 292733*uk_159 + 899707*uk_16 + 25631*uk_160 + 10218313*uk_161 + 894691*uk_162 + 78337*uk_163 + 6859*uk_164 + 3969*uk_17 + 4221*uk_18 + 1197*uk_19 + 63*uk_2 + 5040*uk_20 + 4473*uk_21 + 13671*uk_22 + 1197*uk_23 + 4489*uk_24 + 1273*uk_25 + 5360*uk_26 + 4757*uk_27 + 14539*uk_28 + 1273*uk_29 + 67*uk_3 + 361*uk_30 + 1520*uk_31 + 1349*uk_32 + 4123*uk_33 + 361*uk_34 + 6400*uk_35 + 5680*uk_36 + 17360*uk_37 + 1520*uk_38 + 5041*uk_39 + 19*uk_4 + 15407*uk_40 + 1349*uk_41 + 47089*uk_42 + 4123*uk_43 + 361*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 150234542803*uk_47 + 42603825571*uk_48 + 179384528720*uk_49 + 80*uk_5 + 159203769239*uk_50 + 486580534153*uk_51 + 42603825571*uk_52 + 187944057*uk_53 + 199877013*uk_54 + 56681541*uk_55 + 238659120*uk_56 + 211809969*uk_57 + 647362863*uk_58 + 56681541*uk_59 + 71*uk_6 + 212567617*uk_60 + 60280369*uk_61 + 253812080*uk_62 + 225258221*uk_63 + 688465267*uk_64 + 60280369*uk_65 + 17094433*uk_66 + 71976560*uk_67 + 63879197*uk_68 + 195236419*uk_69 + 217*uk_7 + 17094433*uk_70 + 303059200*uk_71 + 268965040*uk_72 + 822048080*uk_73 + 71976560*uk_74 + 238706473*uk_75 + 729567671*uk_76 + 63879197*uk_77 + 2229805417*uk_78 + 195236419*uk_79 + 19*uk_8 + 17094433*uk_80 + 250047*uk_81 + 265923*uk_82 + 75411*uk_83 + 317520*uk_84 + 281799*uk_85 + 861273*uk_86 + 75411*uk_87 + 282807*uk_88 + 80199*uk_89 + 2242306609*uk_9 + 337680*uk_90 + 299691*uk_91 + 915957*uk_92 + 80199*uk_93 + 22743*uk_94 + 95760*uk_95 + 84987*uk_96 + 259749*uk_97 + 22743*uk_98 + 403200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 331128*uk_100 + 984312*uk_101 + 303912*uk_102 + 335727*uk_103 + 997983*uk_104 + 308133*uk_105 + 2966607*uk_106 + 915957*uk_107 + 282807*uk_108 + 117649*uk_109 + 2320297*uk_11 + 160867*uk_110 + 172872*uk_111 + 175273*uk_112 + 521017*uk_113 + 160867*uk_114 + 219961*uk_115 + 236376*uk_116 + 239659*uk_117 + 712411*uk_118 + 219961*uk_119 + 3172651*uk_12 + 254016*uk_120 + 257544*uk_121 + 765576*uk_122 + 236376*uk_123 + 261121*uk_124 + 776209*uk_125 + 239659*uk_126 + 2307361*uk_127 + 712411*uk_128 + 219961*uk_129 + 3409416*uk_13 + 300763*uk_130 + 323208*uk_131 + 327697*uk_132 + 974113*uk_133 + 300763*uk_134 + 347328*uk_135 + 352152*uk_136 + 1046808*uk_137 + 323208*uk_138 + 357043*uk_139 + 3456769*uk_14 + 1061347*uk_140 + 327697*uk_141 + 3154963*uk_142 + 974113*uk_143 + 300763*uk_144 + 373248*uk_145 + 378432*uk_146 + 1124928*uk_147 + 347328*uk_148 + 383688*uk_149 + 10275601*uk_15 + 1140552*uk_150 + 352152*uk_151 + 3390408*uk_152 + 1046808*uk_153 + 323208*uk_154 + 389017*uk_155 + 1156393*uk_156 + 357043*uk_157 + 3437497*uk_158 + 1061347*uk_159 + 3172651*uk_16 + 327697*uk_160 + 10218313*uk_161 + 3154963*uk_162 + 974113*uk_163 + 300763*uk_164 + 3969*uk_17 + 3087*uk_18 + 4221*uk_19 + 63*uk_2 + 4536*uk_20 + 4599*uk_21 + 13671*uk_22 + 4221*uk_23 + 2401*uk_24 + 3283*uk_25 + 3528*uk_26 + 3577*uk_27 + 10633*uk_28 + 3283*uk_29 + 49*uk_3 + 4489*uk_30 + 4824*uk_31 + 4891*uk_32 + 14539*uk_33 + 4489*uk_34 + 5184*uk_35 + 5256*uk_36 + 15624*uk_37 + 4824*uk_38 + 5329*uk_39 + 67*uk_4 + 15841*uk_40 + 4891*uk_41 + 47089*uk_42 + 14539*uk_43 + 4489*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 109873023841*uk_47 + 150234542803*uk_48 + 161446075848*uk_49 + 72*uk_5 + 163688382457*uk_50 + 486580534153*uk_51 + 150234542803*uk_52 + 187944057*uk_53 + 146178711*uk_54 + 199877013*uk_55 + 214793208*uk_56 + 217776447*uk_57 + 647362863*uk_58 + 199877013*uk_59 + 73*uk_6 + 113694553*uk_60 + 155459899*uk_61 + 167061384*uk_62 + 169381681*uk_63 + 503504449*uk_64 + 155459899*uk_65 + 212567617*uk_66 + 228430872*uk_67 + 231603523*uk_68 + 688465267*uk_69 + 217*uk_7 + 212567617*uk_70 + 245477952*uk_71 + 248887368*uk_72 + 739843272*uk_73 + 228430872*uk_74 + 252344137*uk_75 + 750118873*uk_76 + 231603523*uk_77 + 2229805417*uk_78 + 688465267*uk_79 + 67*uk_8 + 212567617*uk_80 + 250047*uk_81 + 194481*uk_82 + 265923*uk_83 + 285768*uk_84 + 289737*uk_85 + 861273*uk_86 + 265923*uk_87 + 151263*uk_88 + 206829*uk_89 + 2242306609*uk_9 + 222264*uk_90 + 225351*uk_91 + 669879*uk_92 + 206829*uk_93 + 282807*uk_94 + 303912*uk_95 + 308133*uk_96 + 915957*uk_97 + 282807*uk_98 + 326592*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 321300*uk_100 + 929628*uk_101 + 209916*uk_102 + 354375*uk_103 + 1025325*uk_104 + 231525*uk_105 + 2966607*uk_106 + 669879*uk_107 + 151263*uk_108 + 21952*uk_109 + 1325884*uk_11 + 38416*uk_110 + 53312*uk_111 + 58800*uk_112 + 170128*uk_113 + 38416*uk_114 + 67228*uk_115 + 93296*uk_116 + 102900*uk_117 + 297724*uk_118 + 67228*uk_119 + 2320297*uk_12 + 129472*uk_120 + 142800*uk_121 + 413168*uk_122 + 93296*uk_123 + 157500*uk_124 + 455700*uk_125 + 102900*uk_126 + 1318492*uk_127 + 297724*uk_128 + 67228*uk_129 + 3220004*uk_13 + 117649*uk_130 + 163268*uk_131 + 180075*uk_132 + 521017*uk_133 + 117649*uk_134 + 226576*uk_135 + 249900*uk_136 + 723044*uk_137 + 163268*uk_138 + 275625*uk_139 + 3551475*uk_14 + 797475*uk_140 + 180075*uk_141 + 2307361*uk_142 + 521017*uk_143 + 117649*uk_144 + 314432*uk_145 + 346800*uk_146 + 1003408*uk_147 + 226576*uk_148 + 382500*uk_149 + 10275601*uk_15 + 1106700*uk_150 + 249900*uk_151 + 3202052*uk_152 + 723044*uk_153 + 163268*uk_154 + 421875*uk_155 + 1220625*uk_156 + 275625*uk_157 + 3531675*uk_158 + 797475*uk_159 + 2320297*uk_16 + 180075*uk_160 + 10218313*uk_161 + 2307361*uk_162 + 521017*uk_163 + 117649*uk_164 + 3969*uk_17 + 1764*uk_18 + 3087*uk_19 + 63*uk_2 + 4284*uk_20 + 4725*uk_21 + 13671*uk_22 + 3087*uk_23 + 784*uk_24 + 1372*uk_25 + 1904*uk_26 + 2100*uk_27 + 6076*uk_28 + 1372*uk_29 + 28*uk_3 + 2401*uk_30 + 3332*uk_31 + 3675*uk_32 + 10633*uk_33 + 2401*uk_34 + 4624*uk_35 + 5100*uk_36 + 14756*uk_37 + 3332*uk_38 + 5625*uk_39 + 49*uk_4 + 16275*uk_40 + 3675*uk_41 + 47089*uk_42 + 10633*uk_43 + 2401*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 62784585052*uk_47 + 109873023841*uk_48 + 152476849412*uk_49 + 68*uk_5 + 168172995675*uk_50 + 486580534153*uk_51 + 109873023841*uk_52 + 187944057*uk_53 + 83530692*uk_54 + 146178711*uk_55 + 202860252*uk_56 + 223742925*uk_57 + 647362863*uk_58 + 146178711*uk_59 + 75*uk_6 + 37124752*uk_60 + 64968316*uk_61 + 90160112*uk_62 + 99441300*uk_63 + 287716828*uk_64 + 64968316*uk_65 + 113694553*uk_66 + 157780196*uk_67 + 174022275*uk_68 + 503504449*uk_69 + 217*uk_7 + 113694553*uk_70 + 218960272*uk_71 + 241500300*uk_72 + 698740868*uk_73 + 157780196*uk_74 + 266360625*uk_75 + 770670075*uk_76 + 174022275*uk_77 + 2229805417*uk_78 + 503504449*uk_79 + 49*uk_8 + 113694553*uk_80 + 250047*uk_81 + 111132*uk_82 + 194481*uk_83 + 269892*uk_84 + 297675*uk_85 + 861273*uk_86 + 194481*uk_87 + 49392*uk_88 + 86436*uk_89 + 2242306609*uk_9 + 119952*uk_90 + 132300*uk_91 + 382788*uk_92 + 86436*uk_93 + 151263*uk_94 + 209916*uk_95 + 231525*uk_96 + 669879*uk_97 + 151263*uk_98 + 291312*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 329868*uk_100 + 929628*uk_101 + 119952*uk_102 + 373527*uk_103 + 1052667*uk_104 + 135828*uk_105 + 2966607*uk_106 + 382788*uk_107 + 49392*uk_108 + 421875*uk_109 + 3551475*uk_11 + 157500*uk_110 + 382500*uk_111 + 433125*uk_112 + 1220625*uk_113 + 157500*uk_114 + 58800*uk_115 + 142800*uk_116 + 161700*uk_117 + 455700*uk_118 + 58800*uk_119 + 1325884*uk_12 + 346800*uk_120 + 392700*uk_121 + 1106700*uk_122 + 142800*uk_123 + 444675*uk_124 + 1253175*uk_125 + 161700*uk_126 + 3531675*uk_127 + 455700*uk_128 + 58800*uk_129 + 3220004*uk_13 + 21952*uk_130 + 53312*uk_131 + 60368*uk_132 + 170128*uk_133 + 21952*uk_134 + 129472*uk_135 + 146608*uk_136 + 413168*uk_137 + 53312*uk_138 + 166012*uk_139 + 3646181*uk_14 + 467852*uk_140 + 60368*uk_141 + 1318492*uk_142 + 170128*uk_143 + 21952*uk_144 + 314432*uk_145 + 356048*uk_146 + 1003408*uk_147 + 129472*uk_148 + 403172*uk_149 + 10275601*uk_15 + 1136212*uk_150 + 146608*uk_151 + 3202052*uk_152 + 413168*uk_153 + 53312*uk_154 + 456533*uk_155 + 1286593*uk_156 + 166012*uk_157 + 3625853*uk_158 + 467852*uk_159 + 1325884*uk_16 + 60368*uk_160 + 10218313*uk_161 + 1318492*uk_162 + 170128*uk_163 + 21952*uk_164 + 3969*uk_17 + 4725*uk_18 + 1764*uk_19 + 63*uk_2 + 4284*uk_20 + 4851*uk_21 + 13671*uk_22 + 1764*uk_23 + 5625*uk_24 + 2100*uk_25 + 5100*uk_26 + 5775*uk_27 + 16275*uk_28 + 2100*uk_29 + 75*uk_3 + 784*uk_30 + 1904*uk_31 + 2156*uk_32 + 6076*uk_33 + 784*uk_34 + 4624*uk_35 + 5236*uk_36 + 14756*uk_37 + 1904*uk_38 + 5929*uk_39 + 28*uk_4 + 16709*uk_40 + 2156*uk_41 + 47089*uk_42 + 6076*uk_43 + 784*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 168172995675*uk_47 + 62784585052*uk_48 + 152476849412*uk_49 + 68*uk_5 + 172657608893*uk_50 + 486580534153*uk_51 + 62784585052*uk_52 + 187944057*uk_53 + 223742925*uk_54 + 83530692*uk_55 + 202860252*uk_56 + 229709403*uk_57 + 647362863*uk_58 + 83530692*uk_59 + 77*uk_6 + 266360625*uk_60 + 99441300*uk_61 + 241500300*uk_62 + 273463575*uk_63 + 770670075*uk_64 + 99441300*uk_65 + 37124752*uk_66 + 90160112*uk_67 + 102093068*uk_68 + 287716828*uk_69 + 217*uk_7 + 37124752*uk_70 + 218960272*uk_71 + 247940308*uk_72 + 698740868*uk_73 + 90160112*uk_74 + 280755937*uk_75 + 791221277*uk_76 + 102093068*uk_77 + 2229805417*uk_78 + 287716828*uk_79 + 28*uk_8 + 37124752*uk_80 + 250047*uk_81 + 297675*uk_82 + 111132*uk_83 + 269892*uk_84 + 305613*uk_85 + 861273*uk_86 + 111132*uk_87 + 354375*uk_88 + 132300*uk_89 + 2242306609*uk_9 + 321300*uk_90 + 363825*uk_91 + 1025325*uk_92 + 132300*uk_93 + 49392*uk_94 + 119952*uk_95 + 135828*uk_96 + 382788*uk_97 + 49392*uk_98 + 291312*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 298620*uk_100 + 820260*uk_101 + 283500*uk_102 + 393183*uk_103 + 1080009*uk_104 + 373275*uk_105 + 2966607*uk_106 + 1025325*uk_107 + 354375*uk_108 + 32768*uk_109 + 1515296*uk_11 + 76800*uk_110 + 61440*uk_111 + 80896*uk_112 + 222208*uk_113 + 76800*uk_114 + 180000*uk_115 + 144000*uk_116 + 189600*uk_117 + 520800*uk_118 + 180000*uk_119 + 3551475*uk_12 + 115200*uk_120 + 151680*uk_121 + 416640*uk_122 + 144000*uk_123 + 199712*uk_124 + 548576*uk_125 + 189600*uk_126 + 1506848*uk_127 + 520800*uk_128 + 180000*uk_129 + 2841180*uk_13 + 421875*uk_130 + 337500*uk_131 + 444375*uk_132 + 1220625*uk_133 + 421875*uk_134 + 270000*uk_135 + 355500*uk_136 + 976500*uk_137 + 337500*uk_138 + 468075*uk_139 + 3740887*uk_14 + 1285725*uk_140 + 444375*uk_141 + 3531675*uk_142 + 1220625*uk_143 + 421875*uk_144 + 216000*uk_145 + 284400*uk_146 + 781200*uk_147 + 270000*uk_148 + 374460*uk_149 + 10275601*uk_15 + 1028580*uk_150 + 355500*uk_151 + 2825340*uk_152 + 976500*uk_153 + 337500*uk_154 + 493039*uk_155 + 1354297*uk_156 + 468075*uk_157 + 3720031*uk_158 + 1285725*uk_159 + 3551475*uk_16 + 444375*uk_160 + 10218313*uk_161 + 3531675*uk_162 + 1220625*uk_163 + 421875*uk_164 + 3969*uk_17 + 2016*uk_18 + 4725*uk_19 + 63*uk_2 + 3780*uk_20 + 4977*uk_21 + 13671*uk_22 + 4725*uk_23 + 1024*uk_24 + 2400*uk_25 + 1920*uk_26 + 2528*uk_27 + 6944*uk_28 + 2400*uk_29 + 32*uk_3 + 5625*uk_30 + 4500*uk_31 + 5925*uk_32 + 16275*uk_33 + 5625*uk_34 + 3600*uk_35 + 4740*uk_36 + 13020*uk_37 + 4500*uk_38 + 6241*uk_39 + 75*uk_4 + 17143*uk_40 + 5925*uk_41 + 47089*uk_42 + 16275*uk_43 + 5625*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 71753811488*uk_47 + 168172995675*uk_48 + 134538396540*uk_49 + 60*uk_5 + 177142222111*uk_50 + 486580534153*uk_51 + 168172995675*uk_52 + 187944057*uk_53 + 95463648*uk_54 + 223742925*uk_55 + 178994340*uk_56 + 235675881*uk_57 + 647362863*uk_58 + 223742925*uk_59 + 79*uk_6 + 48489472*uk_60 + 113647200*uk_61 + 90917760*uk_62 + 119708384*uk_63 + 328819232*uk_64 + 113647200*uk_65 + 266360625*uk_66 + 213088500*uk_67 + 280566525*uk_68 + 770670075*uk_69 + 217*uk_7 + 266360625*uk_70 + 170470800*uk_71 + 224453220*uk_72 + 616536060*uk_73 + 213088500*uk_74 + 295530073*uk_75 + 811772479*uk_76 + 280566525*uk_77 + 2229805417*uk_78 + 770670075*uk_79 + 75*uk_8 + 266360625*uk_80 + 250047*uk_81 + 127008*uk_82 + 297675*uk_83 + 238140*uk_84 + 313551*uk_85 + 861273*uk_86 + 297675*uk_87 + 64512*uk_88 + 151200*uk_89 + 2242306609*uk_9 + 120960*uk_90 + 159264*uk_91 + 437472*uk_92 + 151200*uk_93 + 354375*uk_94 + 283500*uk_95 + 373275*uk_96 + 1025325*uk_97 + 354375*uk_98 + 226800*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 306180*uk_100 + 820260*uk_101 + 120960*uk_102 + 413343*uk_103 + 1107351*uk_104 + 163296*uk_105 + 2966607*uk_106 + 437472*uk_107 + 64512*uk_108 + 117649*uk_109 + 2320297*uk_11 + 76832*uk_110 + 144060*uk_111 + 194481*uk_112 + 521017*uk_113 + 76832*uk_114 + 50176*uk_115 + 94080*uk_116 + 127008*uk_117 + 340256*uk_118 + 50176*uk_119 + 1515296*uk_12 + 176400*uk_120 + 238140*uk_121 + 637980*uk_122 + 94080*uk_123 + 321489*uk_124 + 861273*uk_125 + 127008*uk_126 + 2307361*uk_127 + 340256*uk_128 + 50176*uk_129 + 2841180*uk_13 + 32768*uk_130 + 61440*uk_131 + 82944*uk_132 + 222208*uk_133 + 32768*uk_134 + 115200*uk_135 + 155520*uk_136 + 416640*uk_137 + 61440*uk_138 + 209952*uk_139 + 3835593*uk_14 + 562464*uk_140 + 82944*uk_141 + 1506848*uk_142 + 222208*uk_143 + 32768*uk_144 + 216000*uk_145 + 291600*uk_146 + 781200*uk_147 + 115200*uk_148 + 393660*uk_149 + 10275601*uk_15 + 1054620*uk_150 + 155520*uk_151 + 2825340*uk_152 + 416640*uk_153 + 61440*uk_154 + 531441*uk_155 + 1423737*uk_156 + 209952*uk_157 + 3814209*uk_158 + 562464*uk_159 + 1515296*uk_16 + 82944*uk_160 + 10218313*uk_161 + 1506848*uk_162 + 222208*uk_163 + 32768*uk_164 + 3969*uk_17 + 3087*uk_18 + 2016*uk_19 + 63*uk_2 + 3780*uk_20 + 5103*uk_21 + 13671*uk_22 + 2016*uk_23 + 2401*uk_24 + 1568*uk_25 + 2940*uk_26 + 3969*uk_27 + 10633*uk_28 + 1568*uk_29 + 49*uk_3 + 1024*uk_30 + 1920*uk_31 + 2592*uk_32 + 6944*uk_33 + 1024*uk_34 + 3600*uk_35 + 4860*uk_36 + 13020*uk_37 + 1920*uk_38 + 6561*uk_39 + 32*uk_4 + 17577*uk_40 + 2592*uk_41 + 47089*uk_42 + 6944*uk_43 + 1024*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 109873023841*uk_47 + 71753811488*uk_48 + 134538396540*uk_49 + 60*uk_5 + 181626835329*uk_50 + 486580534153*uk_51 + 71753811488*uk_52 + 187944057*uk_53 + 146178711*uk_54 + 95463648*uk_55 + 178994340*uk_56 + 241642359*uk_57 + 647362863*uk_58 + 95463648*uk_59 + 81*uk_6 + 113694553*uk_60 + 74249504*uk_61 + 139217820*uk_62 + 187944057*uk_63 + 503504449*uk_64 + 74249504*uk_65 + 48489472*uk_66 + 90917760*uk_67 + 122738976*uk_68 + 328819232*uk_69 + 217*uk_7 + 48489472*uk_70 + 170470800*uk_71 + 230135580*uk_72 + 616536060*uk_73 + 90917760*uk_74 + 310683033*uk_75 + 832323681*uk_76 + 122738976*uk_77 + 2229805417*uk_78 + 328819232*uk_79 + 32*uk_8 + 48489472*uk_80 + 250047*uk_81 + 194481*uk_82 + 127008*uk_83 + 238140*uk_84 + 321489*uk_85 + 861273*uk_86 + 127008*uk_87 + 151263*uk_88 + 98784*uk_89 + 2242306609*uk_9 + 185220*uk_90 + 250047*uk_91 + 669879*uk_92 + 98784*uk_93 + 64512*uk_94 + 120960*uk_95 + 163296*uk_96 + 437472*uk_97 + 64512*uk_98 + 226800*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 292824*uk_100 + 765576*uk_101 + 172872*uk_102 + 434007*uk_103 + 1134693*uk_104 + 256221*uk_105 + 2966607*uk_106 + 669879*uk_107 + 151263*uk_108 + 79507*uk_109 + 2036179*uk_11 + 90601*uk_110 + 103544*uk_111 + 153467*uk_112 + 401233*uk_113 + 90601*uk_114 + 103243*uk_115 + 117992*uk_116 + 174881*uk_117 + 457219*uk_118 + 103243*uk_119 + 2320297*uk_12 + 134848*uk_120 + 199864*uk_121 + 522536*uk_122 + 117992*uk_123 + 296227*uk_124 + 774473*uk_125 + 174881*uk_126 + 2024827*uk_127 + 457219*uk_128 + 103243*uk_129 + 2651768*uk_13 + 117649*uk_130 + 134456*uk_131 + 199283*uk_132 + 521017*uk_133 + 117649*uk_134 + 153664*uk_135 + 227752*uk_136 + 595448*uk_137 + 134456*uk_138 + 337561*uk_139 + 3930299*uk_14 + 882539*uk_140 + 199283*uk_141 + 2307361*uk_142 + 521017*uk_143 + 117649*uk_144 + 175616*uk_145 + 260288*uk_146 + 680512*uk_147 + 153664*uk_148 + 385784*uk_149 + 10275601*uk_15 + 1008616*uk_150 + 227752*uk_151 + 2636984*uk_152 + 595448*uk_153 + 134456*uk_154 + 571787*uk_155 + 1494913*uk_156 + 337561*uk_157 + 3908387*uk_158 + 882539*uk_159 + 2320297*uk_16 + 199283*uk_160 + 10218313*uk_161 + 2307361*uk_162 + 521017*uk_163 + 117649*uk_164 + 3969*uk_17 + 2709*uk_18 + 3087*uk_19 + 63*uk_2 + 3528*uk_20 + 5229*uk_21 + 13671*uk_22 + 3087*uk_23 + 1849*uk_24 + 2107*uk_25 + 2408*uk_26 + 3569*uk_27 + 9331*uk_28 + 2107*uk_29 + 43*uk_3 + 2401*uk_30 + 2744*uk_31 + 4067*uk_32 + 10633*uk_33 + 2401*uk_34 + 3136*uk_35 + 4648*uk_36 + 12152*uk_37 + 2744*uk_38 + 6889*uk_39 + 49*uk_4 + 18011*uk_40 + 4067*uk_41 + 47089*uk_42 + 10633*uk_43 + 2401*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 96419184187*uk_47 + 109873023841*uk_48 + 125569170104*uk_49 + 56*uk_5 + 186111448547*uk_50 + 486580534153*uk_51 + 109873023841*uk_52 + 187944057*uk_53 + 128279277*uk_54 + 146178711*uk_55 + 167061384*uk_56 + 247608837*uk_57 + 647362863*uk_58 + 146178711*uk_59 + 83*uk_6 + 87555697*uk_60 + 99772771*uk_61 + 114026024*uk_62 + 169002857*uk_63 + 441850843*uk_64 + 99772771*uk_65 + 113694553*uk_66 + 129936632*uk_67 + 192584651*uk_68 + 503504449*uk_69 + 217*uk_7 + 113694553*uk_70 + 148499008*uk_71 + 220096744*uk_72 + 575433656*uk_73 + 129936632*uk_74 + 326214817*uk_75 + 852874883*uk_76 + 192584651*uk_77 + 2229805417*uk_78 + 503504449*uk_79 + 49*uk_8 + 113694553*uk_80 + 250047*uk_81 + 170667*uk_82 + 194481*uk_83 + 222264*uk_84 + 329427*uk_85 + 861273*uk_86 + 194481*uk_87 + 116487*uk_88 + 132741*uk_89 + 2242306609*uk_9 + 151704*uk_90 + 224847*uk_91 + 587853*uk_92 + 132741*uk_93 + 151263*uk_94 + 172872*uk_95 + 256221*uk_96 + 669879*uk_97 + 151263*uk_98 + 197568*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 278460*uk_100 + 710892*uk_101 + 140868*uk_102 + 455175*uk_103 + 1162035*uk_104 + 230265*uk_105 + 2966607*uk_106 + 587853*uk_107 + 116487*uk_108 + 512*uk_109 + 378824*uk_11 + 2752*uk_110 + 3328*uk_111 + 5440*uk_112 + 13888*uk_113 + 2752*uk_114 + 14792*uk_115 + 17888*uk_116 + 29240*uk_117 + 74648*uk_118 + 14792*uk_119 + 2036179*uk_12 + 21632*uk_120 + 35360*uk_121 + 90272*uk_122 + 17888*uk_123 + 57800*uk_124 + 147560*uk_125 + 29240*uk_126 + 376712*uk_127 + 74648*uk_128 + 14792*uk_129 + 2462356*uk_13 + 79507*uk_130 + 96148*uk_131 + 157165*uk_132 + 401233*uk_133 + 79507*uk_134 + 116272*uk_135 + 190060*uk_136 + 485212*uk_137 + 96148*uk_138 + 310675*uk_139 + 4025005*uk_14 + 793135*uk_140 + 157165*uk_141 + 2024827*uk_142 + 401233*uk_143 + 79507*uk_144 + 140608*uk_145 + 229840*uk_146 + 586768*uk_147 + 116272*uk_148 + 375700*uk_149 + 10275601*uk_15 + 959140*uk_150 + 190060*uk_151 + 2448628*uk_152 + 485212*uk_153 + 96148*uk_154 + 614125*uk_155 + 1567825*uk_156 + 310675*uk_157 + 4002565*uk_158 + 793135*uk_159 + 2036179*uk_16 + 157165*uk_160 + 10218313*uk_161 + 2024827*uk_162 + 401233*uk_163 + 79507*uk_164 + 3969*uk_17 + 504*uk_18 + 2709*uk_19 + 63*uk_2 + 3276*uk_20 + 5355*uk_21 + 13671*uk_22 + 2709*uk_23 + 64*uk_24 + 344*uk_25 + 416*uk_26 + 680*uk_27 + 1736*uk_28 + 344*uk_29 + 8*uk_3 + 1849*uk_30 + 2236*uk_31 + 3655*uk_32 + 9331*uk_33 + 1849*uk_34 + 2704*uk_35 + 4420*uk_36 + 11284*uk_37 + 2236*uk_38 + 7225*uk_39 + 43*uk_4 + 18445*uk_40 + 3655*uk_41 + 47089*uk_42 + 9331*uk_43 + 1849*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 17938452872*uk_47 + 96419184187*uk_48 + 116599943668*uk_49 + 52*uk_5 + 190596061765*uk_50 + 486580534153*uk_51 + 96419184187*uk_52 + 187944057*uk_53 + 23865912*uk_54 + 128279277*uk_55 + 155128428*uk_56 + 253575315*uk_57 + 647362863*uk_58 + 128279277*uk_59 + 85*uk_6 + 3030592*uk_60 + 16289432*uk_61 + 19698848*uk_62 + 32200040*uk_63 + 82204808*uk_64 + 16289432*uk_65 + 87555697*uk_66 + 105881308*uk_67 + 173075215*uk_68 + 441850843*uk_69 + 217*uk_7 + 87555697*uk_70 + 128042512*uk_71 + 209300260*uk_72 + 534331252*uk_73 + 105881308*uk_74 + 342125425*uk_75 + 873426085*uk_76 + 173075215*uk_77 + 2229805417*uk_78 + 441850843*uk_79 + 43*uk_8 + 87555697*uk_80 + 250047*uk_81 + 31752*uk_82 + 170667*uk_83 + 206388*uk_84 + 337365*uk_85 + 861273*uk_86 + 170667*uk_87 + 4032*uk_88 + 21672*uk_89 + 2242306609*uk_9 + 26208*uk_90 + 42840*uk_91 + 109368*uk_92 + 21672*uk_93 + 116487*uk_94 + 140868*uk_95 + 230265*uk_96 + 587853*uk_97 + 116487*uk_98 + 170352*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 285012*uk_100 + 710892*uk_101 + 26208*uk_102 + 476847*uk_103 + 1189377*uk_104 + 43848*uk_105 + 2966607*uk_106 + 109368*uk_107 + 4032*uk_108 + 15625*uk_109 + 1183825*uk_11 + 5000*uk_110 + 32500*uk_111 + 54375*uk_112 + 135625*uk_113 + 5000*uk_114 + 1600*uk_115 + 10400*uk_116 + 17400*uk_117 + 43400*uk_118 + 1600*uk_119 + 378824*uk_12 + 67600*uk_120 + 113100*uk_121 + 282100*uk_122 + 10400*uk_123 + 189225*uk_124 + 471975*uk_125 + 17400*uk_126 + 1177225*uk_127 + 43400*uk_128 + 1600*uk_129 + 2462356*uk_13 + 512*uk_130 + 3328*uk_131 + 5568*uk_132 + 13888*uk_133 + 512*uk_134 + 21632*uk_135 + 36192*uk_136 + 90272*uk_137 + 3328*uk_138 + 60552*uk_139 + 4119711*uk_14 + 151032*uk_140 + 5568*uk_141 + 376712*uk_142 + 13888*uk_143 + 512*uk_144 + 140608*uk_145 + 235248*uk_146 + 586768*uk_147 + 21632*uk_148 + 393588*uk_149 + 10275601*uk_15 + 981708*uk_150 + 36192*uk_151 + 2448628*uk_152 + 90272*uk_153 + 3328*uk_154 + 658503*uk_155 + 1642473*uk_156 + 60552*uk_157 + 4096743*uk_158 + 151032*uk_159 + 378824*uk_16 + 5568*uk_160 + 10218313*uk_161 + 376712*uk_162 + 13888*uk_163 + 512*uk_164 + 3969*uk_17 + 1575*uk_18 + 504*uk_19 + 63*uk_2 + 3276*uk_20 + 5481*uk_21 + 13671*uk_22 + 504*uk_23 + 625*uk_24 + 200*uk_25 + 1300*uk_26 + 2175*uk_27 + 5425*uk_28 + 200*uk_29 + 25*uk_3 + 64*uk_30 + 416*uk_31 + 696*uk_32 + 1736*uk_33 + 64*uk_34 + 2704*uk_35 + 4524*uk_36 + 11284*uk_37 + 416*uk_38 + 7569*uk_39 + 8*uk_4 + 18879*uk_40 + 696*uk_41 + 47089*uk_42 + 1736*uk_43 + 64*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 56057665225*uk_47 + 17938452872*uk_48 + 116599943668*uk_49 + 52*uk_5 + 195080674983*uk_50 + 486580534153*uk_51 + 17938452872*uk_52 + 187944057*uk_53 + 74580975*uk_54 + 23865912*uk_55 + 155128428*uk_56 + 259541793*uk_57 + 647362863*uk_58 + 23865912*uk_59 + 87*uk_6 + 29595625*uk_60 + 9470600*uk_61 + 61558900*uk_62 + 102992775*uk_63 + 256890025*uk_64 + 9470600*uk_65 + 3030592*uk_66 + 19698848*uk_67 + 32957688*uk_68 + 82204808*uk_69 + 217*uk_7 + 3030592*uk_70 + 128042512*uk_71 + 214224972*uk_72 + 534331252*uk_73 + 19698848*uk_74 + 358414857*uk_75 + 893977287*uk_76 + 32957688*uk_77 + 2229805417*uk_78 + 82204808*uk_79 + 8*uk_8 + 3030592*uk_80 + 250047*uk_81 + 99225*uk_82 + 31752*uk_83 + 206388*uk_84 + 345303*uk_85 + 861273*uk_86 + 31752*uk_87 + 39375*uk_88 + 12600*uk_89 + 2242306609*uk_9 + 81900*uk_90 + 137025*uk_91 + 341775*uk_92 + 12600*uk_93 + 4032*uk_94 + 26208*uk_95 + 43848*uk_96 + 109368*uk_97 + 4032*uk_98 + 170352*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 269136*uk_100 + 656208*uk_101 + 75600*uk_102 + 499023*uk_103 + 1216719*uk_104 + 140175*uk_105 + 2966607*uk_106 + 341775*uk_107 + 39375*uk_108 + 125*uk_109 + 236765*uk_11 + 625*uk_110 + 1200*uk_111 + 2225*uk_112 + 5425*uk_113 + 625*uk_114 + 3125*uk_115 + 6000*uk_116 + 11125*uk_117 + 27125*uk_118 + 3125*uk_119 + 1183825*uk_12 + 11520*uk_120 + 21360*uk_121 + 52080*uk_122 + 6000*uk_123 + 39605*uk_124 + 96565*uk_125 + 11125*uk_126 + 235445*uk_127 + 27125*uk_128 + 3125*uk_129 + 2272944*uk_13 + 15625*uk_130 + 30000*uk_131 + 55625*uk_132 + 135625*uk_133 + 15625*uk_134 + 57600*uk_135 + 106800*uk_136 + 260400*uk_137 + 30000*uk_138 + 198025*uk_139 + 4214417*uk_14 + 482825*uk_140 + 55625*uk_141 + 1177225*uk_142 + 135625*uk_143 + 15625*uk_144 + 110592*uk_145 + 205056*uk_146 + 499968*uk_147 + 57600*uk_148 + 380208*uk_149 + 10275601*uk_15 + 927024*uk_150 + 106800*uk_151 + 2260272*uk_152 + 260400*uk_153 + 30000*uk_154 + 704969*uk_155 + 1718857*uk_156 + 198025*uk_157 + 4190921*uk_158 + 482825*uk_159 + 1183825*uk_16 + 55625*uk_160 + 10218313*uk_161 + 1177225*uk_162 + 135625*uk_163 + 15625*uk_164 + 3969*uk_17 + 315*uk_18 + 1575*uk_19 + 63*uk_2 + 3024*uk_20 + 5607*uk_21 + 13671*uk_22 + 1575*uk_23 + 25*uk_24 + 125*uk_25 + 240*uk_26 + 445*uk_27 + 1085*uk_28 + 125*uk_29 + 5*uk_3 + 625*uk_30 + 1200*uk_31 + 2225*uk_32 + 5425*uk_33 + 625*uk_34 + 2304*uk_35 + 4272*uk_36 + 10416*uk_37 + 1200*uk_38 + 7921*uk_39 + 25*uk_4 + 19313*uk_40 + 2225*uk_41 + 47089*uk_42 + 5425*uk_43 + 625*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 11211533045*uk_47 + 56057665225*uk_48 + 107630717232*uk_49 + 48*uk_5 + 199565288201*uk_50 + 486580534153*uk_51 + 56057665225*uk_52 + 187944057*uk_53 + 14916195*uk_54 + 74580975*uk_55 + 143195472*uk_56 + 265508271*uk_57 + 647362863*uk_58 + 74580975*uk_59 + 89*uk_6 + 1183825*uk_60 + 5919125*uk_61 + 11364720*uk_62 + 21072085*uk_63 + 51378005*uk_64 + 5919125*uk_65 + 29595625*uk_66 + 56823600*uk_67 + 105360425*uk_68 + 256890025*uk_69 + 217*uk_7 + 29595625*uk_70 + 109101312*uk_71 + 202292016*uk_72 + 493228848*uk_73 + 56823600*uk_74 + 375083113*uk_75 + 914528489*uk_76 + 105360425*uk_77 + 2229805417*uk_78 + 256890025*uk_79 + 25*uk_8 + 29595625*uk_80 + 250047*uk_81 + 19845*uk_82 + 99225*uk_83 + 190512*uk_84 + 353241*uk_85 + 861273*uk_86 + 99225*uk_87 + 1575*uk_88 + 7875*uk_89 + 2242306609*uk_9 + 15120*uk_90 + 28035*uk_91 + 68355*uk_92 + 7875*uk_93 + 39375*uk_94 + 75600*uk_95 + 140175*uk_96 + 341775*uk_97 + 39375*uk_98 + 145152*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 275184*uk_100 + 656208*uk_101 + 15120*uk_102 + 521703*uk_103 + 1244061*uk_104 + 28665*uk_105 + 2966607*uk_106 + 68355*uk_107 + 1575*uk_108 + 35937*uk_109 + 1562649*uk_11 + 5445*uk_110 + 52272*uk_111 + 99099*uk_112 + 236313*uk_113 + 5445*uk_114 + 825*uk_115 + 7920*uk_116 + 15015*uk_117 + 35805*uk_118 + 825*uk_119 + 236765*uk_12 + 76032*uk_120 + 144144*uk_121 + 343728*uk_122 + 7920*uk_123 + 273273*uk_124 + 651651*uk_125 + 15015*uk_126 + 1553937*uk_127 + 35805*uk_128 + 825*uk_129 + 2272944*uk_13 + 125*uk_130 + 1200*uk_131 + 2275*uk_132 + 5425*uk_133 + 125*uk_134 + 11520*uk_135 + 21840*uk_136 + 52080*uk_137 + 1200*uk_138 + 41405*uk_139 + 4309123*uk_14 + 98735*uk_140 + 2275*uk_141 + 235445*uk_142 + 5425*uk_143 + 125*uk_144 + 110592*uk_145 + 209664*uk_146 + 499968*uk_147 + 11520*uk_148 + 397488*uk_149 + 10275601*uk_15 + 947856*uk_150 + 21840*uk_151 + 2260272*uk_152 + 52080*uk_153 + 1200*uk_154 + 753571*uk_155 + 1796977*uk_156 + 41405*uk_157 + 4285099*uk_158 + 98735*uk_159 + 236765*uk_16 + 2275*uk_160 + 10218313*uk_161 + 235445*uk_162 + 5425*uk_163 + 125*uk_164 + 3969*uk_17 + 2079*uk_18 + 315*uk_19 + 63*uk_2 + 3024*uk_20 + 5733*uk_21 + 13671*uk_22 + 315*uk_23 + 1089*uk_24 + 165*uk_25 + 1584*uk_26 + 3003*uk_27 + 7161*uk_28 + 165*uk_29 + 33*uk_3 + 25*uk_30 + 240*uk_31 + 455*uk_32 + 1085*uk_33 + 25*uk_34 + 2304*uk_35 + 4368*uk_36 + 10416*uk_37 + 240*uk_38 + 8281*uk_39 + 5*uk_4 + 19747*uk_40 + 455*uk_41 + 47089*uk_42 + 1085*uk_43 + 25*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 73996118097*uk_47 + 11211533045*uk_48 + 107630717232*uk_49 + 48*uk_5 + 204049901419*uk_50 + 486580534153*uk_51 + 11211533045*uk_52 + 187944057*uk_53 + 98446887*uk_54 + 14916195*uk_55 + 143195472*uk_56 + 271474749*uk_57 + 647362863*uk_58 + 14916195*uk_59 + 91*uk_6 + 51567417*uk_60 + 7813245*uk_61 + 75007152*uk_62 + 142201059*uk_63 + 339094833*uk_64 + 7813245*uk_65 + 1183825*uk_66 + 11364720*uk_67 + 21545615*uk_68 + 51378005*uk_69 + 217*uk_7 + 1183825*uk_70 + 109101312*uk_71 + 206837904*uk_72 + 493228848*uk_73 + 11364720*uk_74 + 392130193*uk_75 + 935079691*uk_76 + 21545615*uk_77 + 2229805417*uk_78 + 51378005*uk_79 + 5*uk_8 + 1183825*uk_80 + 250047*uk_81 + 130977*uk_82 + 19845*uk_83 + 190512*uk_84 + 361179*uk_85 + 861273*uk_86 + 19845*uk_87 + 68607*uk_88 + 10395*uk_89 + 2242306609*uk_9 + 99792*uk_90 + 189189*uk_91 + 451143*uk_92 + 10395*uk_93 + 1575*uk_94 + 15120*uk_95 + 28665*uk_96 + 68355*uk_97 + 1575*uk_98 + 145152*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 257796*uk_100 + 601524*uk_101 + 91476*uk_102 + 544887*uk_103 + 1271403*uk_104 + 193347*uk_105 + 2966607*uk_106 + 451143*uk_107 + 68607*uk_108 + 4096*uk_109 + 757648*uk_11 + 8448*uk_110 + 11264*uk_111 + 23808*uk_112 + 55552*uk_113 + 8448*uk_114 + 17424*uk_115 + 23232*uk_116 + 49104*uk_117 + 114576*uk_118 + 17424*uk_119 + 1562649*uk_12 + 30976*uk_120 + 65472*uk_121 + 152768*uk_122 + 23232*uk_123 + 138384*uk_124 + 322896*uk_125 + 49104*uk_126 + 753424*uk_127 + 114576*uk_128 + 17424*uk_129 + 2083532*uk_13 + 35937*uk_130 + 47916*uk_131 + 101277*uk_132 + 236313*uk_133 + 35937*uk_134 + 63888*uk_135 + 135036*uk_136 + 315084*uk_137 + 47916*uk_138 + 285417*uk_139 + 4403829*uk_14 + 665973*uk_140 + 101277*uk_141 + 1553937*uk_142 + 236313*uk_143 + 35937*uk_144 + 85184*uk_145 + 180048*uk_146 + 420112*uk_147 + 63888*uk_148 + 380556*uk_149 + 10275601*uk_15 + 887964*uk_150 + 135036*uk_151 + 2071916*uk_152 + 315084*uk_153 + 47916*uk_154 + 804357*uk_155 + 1876833*uk_156 + 285417*uk_157 + 4379277*uk_158 + 665973*uk_159 + 1562649*uk_16 + 101277*uk_160 + 10218313*uk_161 + 1553937*uk_162 + 236313*uk_163 + 35937*uk_164 + 3969*uk_17 + 1008*uk_18 + 2079*uk_19 + 63*uk_2 + 2772*uk_20 + 5859*uk_21 + 13671*uk_22 + 2079*uk_23 + 256*uk_24 + 528*uk_25 + 704*uk_26 + 1488*uk_27 + 3472*uk_28 + 528*uk_29 + 16*uk_3 + 1089*uk_30 + 1452*uk_31 + 3069*uk_32 + 7161*uk_33 + 1089*uk_34 + 1936*uk_35 + 4092*uk_36 + 9548*uk_37 + 1452*uk_38 + 8649*uk_39 + 33*uk_4 + 20181*uk_40 + 3069*uk_41 + 47089*uk_42 + 7161*uk_43 + 1089*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 35876905744*uk_47 + 73996118097*uk_48 + 98661490796*uk_49 + 44*uk_5 + 208534514637*uk_50 + 486580534153*uk_51 + 73996118097*uk_52 + 187944057*uk_53 + 47731824*uk_54 + 98446887*uk_55 + 131262516*uk_56 + 277441227*uk_57 + 647362863*uk_58 + 98446887*uk_59 + 93*uk_6 + 12122368*uk_60 + 25002384*uk_61 + 33336512*uk_62 + 70461264*uk_63 + 164409616*uk_64 + 25002384*uk_65 + 51567417*uk_66 + 68756556*uk_67 + 145326357*uk_68 + 339094833*uk_69 + 217*uk_7 + 51567417*uk_70 + 91675408*uk_71 + 193768476*uk_72 + 452126444*uk_73 + 68756556*uk_74 + 409556097*uk_75 + 955630893*uk_76 + 145326357*uk_77 + 2229805417*uk_78 + 339094833*uk_79 + 33*uk_8 + 51567417*uk_80 + 250047*uk_81 + 63504*uk_82 + 130977*uk_83 + 174636*uk_84 + 369117*uk_85 + 861273*uk_86 + 130977*uk_87 + 16128*uk_88 + 33264*uk_89 + 2242306609*uk_9 + 44352*uk_90 + 93744*uk_91 + 218736*uk_92 + 33264*uk_93 + 68607*uk_94 + 91476*uk_95 + 193347*uk_96 + 451143*uk_97 + 68607*uk_98 + 121968*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 263340*uk_100 + 601524*uk_101 + 44352*uk_102 + 568575*uk_103 + 1298745*uk_104 + 95760*uk_105 + 2966607*uk_106 + 218736*uk_107 + 16128*uk_108 + 79507*uk_109 + 2036179*uk_11 + 29584*uk_110 + 81356*uk_111 + 175655*uk_112 + 401233*uk_113 + 29584*uk_114 + 11008*uk_115 + 30272*uk_116 + 65360*uk_117 + 149296*uk_118 + 11008*uk_119 + 757648*uk_12 + 83248*uk_120 + 179740*uk_121 + 410564*uk_122 + 30272*uk_123 + 388075*uk_124 + 886445*uk_125 + 65360*uk_126 + 2024827*uk_127 + 149296*uk_128 + 11008*uk_129 + 2083532*uk_13 + 4096*uk_130 + 11264*uk_131 + 24320*uk_132 + 55552*uk_133 + 4096*uk_134 + 30976*uk_135 + 66880*uk_136 + 152768*uk_137 + 11264*uk_138 + 144400*uk_139 + 4498535*uk_14 + 329840*uk_140 + 24320*uk_141 + 753424*uk_142 + 55552*uk_143 + 4096*uk_144 + 85184*uk_145 + 183920*uk_146 + 420112*uk_147 + 30976*uk_148 + 397100*uk_149 + 10275601*uk_15 + 907060*uk_150 + 66880*uk_151 + 2071916*uk_152 + 152768*uk_153 + 11264*uk_154 + 857375*uk_155 + 1958425*uk_156 + 144400*uk_157 + 4473455*uk_158 + 329840*uk_159 + 757648*uk_16 + 24320*uk_160 + 10218313*uk_161 + 753424*uk_162 + 55552*uk_163 + 4096*uk_164 + 3969*uk_17 + 2709*uk_18 + 1008*uk_19 + 63*uk_2 + 2772*uk_20 + 5985*uk_21 + 13671*uk_22 + 1008*uk_23 + 1849*uk_24 + 688*uk_25 + 1892*uk_26 + 4085*uk_27 + 9331*uk_28 + 688*uk_29 + 43*uk_3 + 256*uk_30 + 704*uk_31 + 1520*uk_32 + 3472*uk_33 + 256*uk_34 + 1936*uk_35 + 4180*uk_36 + 9548*uk_37 + 704*uk_38 + 9025*uk_39 + 16*uk_4 + 20615*uk_40 + 1520*uk_41 + 47089*uk_42 + 3472*uk_43 + 256*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 96419184187*uk_47 + 35876905744*uk_48 + 98661490796*uk_49 + 44*uk_5 + 213019127855*uk_50 + 486580534153*uk_51 + 35876905744*uk_52 + 187944057*uk_53 + 128279277*uk_54 + 47731824*uk_55 + 131262516*uk_56 + 283407705*uk_57 + 647362863*uk_58 + 47731824*uk_59 + 95*uk_6 + 87555697*uk_60 + 32578864*uk_61 + 89591876*uk_62 + 193437005*uk_63 + 441850843*uk_64 + 32578864*uk_65 + 12122368*uk_66 + 33336512*uk_67 + 71976560*uk_68 + 164409616*uk_69 + 217*uk_7 + 12122368*uk_70 + 91675408*uk_71 + 197935540*uk_72 + 452126444*uk_73 + 33336512*uk_74 + 427360825*uk_75 + 976182095*uk_76 + 71976560*uk_77 + 2229805417*uk_78 + 164409616*uk_79 + 16*uk_8 + 12122368*uk_80 + 250047*uk_81 + 170667*uk_82 + 63504*uk_83 + 174636*uk_84 + 377055*uk_85 + 861273*uk_86 + 63504*uk_87 + 116487*uk_88 + 43344*uk_89 + 2242306609*uk_9 + 119196*uk_90 + 257355*uk_91 + 587853*uk_92 + 43344*uk_93 + 16128*uk_94 + 44352*uk_95 + 95760*uk_96 + 218736*uk_97 + 16128*uk_98 + 121968*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 244440*uk_100 + 546840*uk_101 + 108360*uk_102 + 592767*uk_103 + 1326087*uk_104 + 262773*uk_105 + 2966607*uk_106 + 587853*uk_107 + 116487*uk_108 + 4913*uk_109 + 805001*uk_11 + 12427*uk_110 + 11560*uk_111 + 28033*uk_112 + 62713*uk_113 + 12427*uk_114 + 31433*uk_115 + 29240*uk_116 + 70907*uk_117 + 158627*uk_118 + 31433*uk_119 + 2036179*uk_12 + 27200*uk_120 + 65960*uk_121 + 147560*uk_122 + 29240*uk_123 + 159953*uk_124 + 357833*uk_125 + 70907*uk_126 + 800513*uk_127 + 158627*uk_128 + 31433*uk_129 + 1894120*uk_13 + 79507*uk_130 + 73960*uk_131 + 179353*uk_132 + 401233*uk_133 + 79507*uk_134 + 68800*uk_135 + 166840*uk_136 + 373240*uk_137 + 73960*uk_138 + 404587*uk_139 + 4593241*uk_14 + 905107*uk_140 + 179353*uk_141 + 2024827*uk_142 + 401233*uk_143 + 79507*uk_144 + 64000*uk_145 + 155200*uk_146 + 347200*uk_147 + 68800*uk_148 + 376360*uk_149 + 10275601*uk_15 + 841960*uk_150 + 166840*uk_151 + 1883560*uk_152 + 373240*uk_153 + 73960*uk_154 + 912673*uk_155 + 2041753*uk_156 + 404587*uk_157 + 4567633*uk_158 + 905107*uk_159 + 2036179*uk_16 + 179353*uk_160 + 10218313*uk_161 + 2024827*uk_162 + 401233*uk_163 + 79507*uk_164 + 3969*uk_17 + 1071*uk_18 + 2709*uk_19 + 63*uk_2 + 2520*uk_20 + 6111*uk_21 + 13671*uk_22 + 2709*uk_23 + 289*uk_24 + 731*uk_25 + 680*uk_26 + 1649*uk_27 + 3689*uk_28 + 731*uk_29 + 17*uk_3 + 1849*uk_30 + 1720*uk_31 + 4171*uk_32 + 9331*uk_33 + 1849*uk_34 + 1600*uk_35 + 3880*uk_36 + 8680*uk_37 + 1720*uk_38 + 9409*uk_39 + 43*uk_4 + 21049*uk_40 + 4171*uk_41 + 47089*uk_42 + 9331*uk_43 + 1849*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 38119212353*uk_47 + 96419184187*uk_48 + 89692264360*uk_49 + 40*uk_5 + 217503741073*uk_50 + 486580534153*uk_51 + 96419184187*uk_52 + 187944057*uk_53 + 50715063*uk_54 + 128279277*uk_55 + 119329560*uk_56 + 289374183*uk_57 + 647362863*uk_58 + 128279277*uk_59 + 97*uk_6 + 13685017*uk_60 + 34615043*uk_61 + 32200040*uk_62 + 78085097*uk_63 + 174685217*uk_64 + 34615043*uk_65 + 87555697*uk_66 + 81447160*uk_67 + 197509363*uk_68 + 441850843*uk_69 + 217*uk_7 + 87555697*uk_70 + 75764800*uk_71 + 183729640*uk_72 + 411024040*uk_73 + 81447160*uk_74 + 445544377*uk_75 + 996733297*uk_76 + 197509363*uk_77 + 2229805417*uk_78 + 441850843*uk_79 + 43*uk_8 + 87555697*uk_80 + 250047*uk_81 + 67473*uk_82 + 170667*uk_83 + 158760*uk_84 + 384993*uk_85 + 861273*uk_86 + 170667*uk_87 + 18207*uk_88 + 46053*uk_89 + 2242306609*uk_9 + 42840*uk_90 + 103887*uk_91 + 232407*uk_92 + 46053*uk_93 + 116487*uk_94 + 108360*uk_95 + 262773*uk_96 + 587853*uk_97 + 116487*uk_98 + 100800*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 249480*uk_100 + 546840*uk_101 + 42840*uk_102 + 617463*uk_103 + 1353429*uk_104 + 106029*uk_105 + 2966607*uk_106 + 232407*uk_107 + 18207*uk_108 + 29791*uk_109 + 1467943*uk_11 + 16337*uk_110 + 38440*uk_111 + 95139*uk_112 + 208537*uk_113 + 16337*uk_114 + 8959*uk_115 + 21080*uk_116 + 52173*uk_117 + 114359*uk_118 + 8959*uk_119 + 805001*uk_12 + 49600*uk_120 + 122760*uk_121 + 269080*uk_122 + 21080*uk_123 + 303831*uk_124 + 665973*uk_125 + 52173*uk_126 + 1459759*uk_127 + 114359*uk_128 + 8959*uk_129 + 1894120*uk_13 + 4913*uk_130 + 11560*uk_131 + 28611*uk_132 + 62713*uk_133 + 4913*uk_134 + 27200*uk_135 + 67320*uk_136 + 147560*uk_137 + 11560*uk_138 + 166617*uk_139 + 4687947*uk_14 + 365211*uk_140 + 28611*uk_141 + 800513*uk_142 + 62713*uk_143 + 4913*uk_144 + 64000*uk_145 + 158400*uk_146 + 347200*uk_147 + 27200*uk_148 + 392040*uk_149 + 10275601*uk_15 + 859320*uk_150 + 67320*uk_151 + 1883560*uk_152 + 147560*uk_153 + 11560*uk_154 + 970299*uk_155 + 2126817*uk_156 + 166617*uk_157 + 4661811*uk_158 + 365211*uk_159 + 805001*uk_16 + 28611*uk_160 + 10218313*uk_161 + 800513*uk_162 + 62713*uk_163 + 4913*uk_164 + 3969*uk_17 + 1953*uk_18 + 1071*uk_19 + 63*uk_2 + 2520*uk_20 + 6237*uk_21 + 13671*uk_22 + 1071*uk_23 + 961*uk_24 + 527*uk_25 + 1240*uk_26 + 3069*uk_27 + 6727*uk_28 + 527*uk_29 + 31*uk_3 + 289*uk_30 + 680*uk_31 + 1683*uk_32 + 3689*uk_33 + 289*uk_34 + 1600*uk_35 + 3960*uk_36 + 8680*uk_37 + 680*uk_38 + 9801*uk_39 + 17*uk_4 + 21483*uk_40 + 1683*uk_41 + 47089*uk_42 + 3689*uk_43 + 289*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 69511504879*uk_47 + 38119212353*uk_48 + 89692264360*uk_49 + 40*uk_5 + 221988354291*uk_50 + 486580534153*uk_51 + 38119212353*uk_52 + 187944057*uk_53 + 92480409*uk_54 + 50715063*uk_55 + 119329560*uk_56 + 295340661*uk_57 + 647362863*uk_58 + 50715063*uk_59 + 99*uk_6 + 45506233*uk_60 + 24955031*uk_61 + 58717720*uk_62 + 145326357*uk_63 + 318543631*uk_64 + 24955031*uk_65 + 13685017*uk_66 + 32200040*uk_67 + 79695099*uk_68 + 174685217*uk_69 + 217*uk_7 + 13685017*uk_70 + 75764800*uk_71 + 187517880*uk_72 + 411024040*uk_73 + 32200040*uk_74 + 464106753*uk_75 + 1017284499*uk_76 + 79695099*uk_77 + 2229805417*uk_78 + 174685217*uk_79 + 17*uk_8 + 13685017*uk_80 + 250047*uk_81 + 123039*uk_82 + 67473*uk_83 + 158760*uk_84 + 392931*uk_85 + 861273*uk_86 + 67473*uk_87 + 60543*uk_88 + 33201*uk_89 + 2242306609*uk_9 + 78120*uk_90 + 193347*uk_91 + 423801*uk_92 + 33201*uk_93 + 18207*uk_94 + 42840*uk_95 + 106029*uk_96 + 232407*uk_97 + 18207*uk_98 + 100800*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 254520*uk_100 + 546840*uk_101 + 78120*uk_102 + 642663*uk_103 + 1380771*uk_104 + 197253*uk_105 + 2966607*uk_106 + 423801*uk_107 + 60543*uk_108 + 614125*uk_109 + 4025005*uk_11 + 223975*uk_110 + 289000*uk_111 + 729725*uk_112 + 1567825*uk_113 + 223975*uk_114 + 81685*uk_115 + 105400*uk_116 + 266135*uk_117 + 571795*uk_118 + 81685*uk_119 + 1467943*uk_12 + 136000*uk_120 + 343400*uk_121 + 737800*uk_122 + 105400*uk_123 + 867085*uk_124 + 1862945*uk_125 + 266135*uk_126 + 4002565*uk_127 + 571795*uk_128 + 81685*uk_129 + 1894120*uk_13 + 29791*uk_130 + 38440*uk_131 + 97061*uk_132 + 208537*uk_133 + 29791*uk_134 + 49600*uk_135 + 125240*uk_136 + 269080*uk_137 + 38440*uk_138 + 316231*uk_139 + 4782653*uk_14 + 679427*uk_140 + 97061*uk_141 + 1459759*uk_142 + 208537*uk_143 + 29791*uk_144 + 64000*uk_145 + 161600*uk_146 + 347200*uk_147 + 49600*uk_148 + 408040*uk_149 + 10275601*uk_15 + 876680*uk_150 + 125240*uk_151 + 1883560*uk_152 + 269080*uk_153 + 38440*uk_154 + 1030301*uk_155 + 2213617*uk_156 + 316231*uk_157 + 4755989*uk_158 + 679427*uk_159 + 1467943*uk_16 + 97061*uk_160 + 10218313*uk_161 + 1459759*uk_162 + 208537*uk_163 + 29791*uk_164 + 3969*uk_17 + 5355*uk_18 + 1953*uk_19 + 63*uk_2 + 2520*uk_20 + 6363*uk_21 + 13671*uk_22 + 1953*uk_23 + 7225*uk_24 + 2635*uk_25 + 3400*uk_26 + 8585*uk_27 + 18445*uk_28 + 2635*uk_29 + 85*uk_3 + 961*uk_30 + 1240*uk_31 + 3131*uk_32 + 6727*uk_33 + 961*uk_34 + 1600*uk_35 + 4040*uk_36 + 8680*uk_37 + 1240*uk_38 + 10201*uk_39 + 31*uk_4 + 21917*uk_40 + 3131*uk_41 + 47089*uk_42 + 6727*uk_43 + 961*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 190596061765*uk_47 + 69511504879*uk_48 + 89692264360*uk_49 + 40*uk_5 + 226472967509*uk_50 + 486580534153*uk_51 + 69511504879*uk_52 + 187944057*uk_53 + 253575315*uk_54 + 92480409*uk_55 + 119329560*uk_56 + 301307139*uk_57 + 647362863*uk_58 + 92480409*uk_59 + 101*uk_6 + 342125425*uk_60 + 124775155*uk_61 + 161000200*uk_62 + 406525505*uk_63 + 873426085*uk_64 + 124775155*uk_65 + 45506233*uk_66 + 58717720*uk_67 + 148262243*uk_68 + 318543631*uk_69 + 217*uk_7 + 45506233*uk_70 + 75764800*uk_71 + 191306120*uk_72 + 411024040*uk_73 + 58717720*uk_74 + 483047953*uk_75 + 1037835701*uk_76 + 148262243*uk_77 + 2229805417*uk_78 + 318543631*uk_79 + 31*uk_8 + 45506233*uk_80 + 250047*uk_81 + 337365*uk_82 + 123039*uk_83 + 158760*uk_84 + 400869*uk_85 + 861273*uk_86 + 123039*uk_87 + 455175*uk_88 + 166005*uk_89 + 2242306609*uk_9 + 214200*uk_90 + 540855*uk_91 + 1162035*uk_92 + 166005*uk_93 + 60543*uk_94 + 78120*uk_95 + 197253*uk_96 + 423801*uk_97 + 60543*uk_98 + 100800*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 233604*uk_100 + 492156*uk_101 + 192780*uk_102 + 668367*uk_103 + 1408113*uk_104 + 551565*uk_105 + 2966607*uk_106 + 1162035*uk_107 + 455175*uk_108 + 438976*uk_109 + 3598828*uk_11 + 490960*uk_110 + 207936*uk_111 + 594928*uk_112 + 1253392*uk_113 + 490960*uk_114 + 549100*uk_115 + 232560*uk_116 + 665380*uk_117 + 1401820*uk_118 + 549100*uk_119 + 4025005*uk_12 + 98496*uk_120 + 281808*uk_121 + 593712*uk_122 + 232560*uk_123 + 806284*uk_124 + 1698676*uk_125 + 665380*uk_126 + 3578764*uk_127 + 1401820*uk_128 + 549100*uk_129 + 1704708*uk_13 + 614125*uk_130 + 260100*uk_131 + 744175*uk_132 + 1567825*uk_133 + 614125*uk_134 + 110160*uk_135 + 315180*uk_136 + 664020*uk_137 + 260100*uk_138 + 901765*uk_139 + 4877359*uk_14 + 1899835*uk_140 + 744175*uk_141 + 4002565*uk_142 + 1567825*uk_143 + 614125*uk_144 + 46656*uk_145 + 133488*uk_146 + 281232*uk_147 + 110160*uk_148 + 381924*uk_149 + 10275601*uk_15 + 804636*uk_150 + 315180*uk_151 + 1695204*uk_152 + 664020*uk_153 + 260100*uk_154 + 1092727*uk_155 + 2302153*uk_156 + 901765*uk_157 + 4850167*uk_158 + 1899835*uk_159 + 4025005*uk_16 + 744175*uk_160 + 10218313*uk_161 + 4002565*uk_162 + 1567825*uk_163 + 614125*uk_164 + 3969*uk_17 + 4788*uk_18 + 5355*uk_19 + 63*uk_2 + 2268*uk_20 + 6489*uk_21 + 13671*uk_22 + 5355*uk_23 + 5776*uk_24 + 6460*uk_25 + 2736*uk_26 + 7828*uk_27 + 16492*uk_28 + 6460*uk_29 + 76*uk_3 + 7225*uk_30 + 3060*uk_31 + 8755*uk_32 + 18445*uk_33 + 7225*uk_34 + 1296*uk_35 + 3708*uk_36 + 7812*uk_37 + 3060*uk_38 + 10609*uk_39 + 85*uk_4 + 22351*uk_40 + 8755*uk_41 + 47089*uk_42 + 18445*uk_43 + 7225*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 170415302284*uk_47 + 190596061765*uk_48 + 80723037924*uk_49 + 36*uk_5 + 230957580727*uk_50 + 486580534153*uk_51 + 190596061765*uk_52 + 187944057*uk_53 + 226726164*uk_54 + 253575315*uk_55 + 107396604*uk_56 + 307273617*uk_57 + 647362863*uk_58 + 253575315*uk_59 + 103*uk_6 + 273510928*uk_60 + 305900380*uk_61 + 129557808*uk_62 + 370679284*uk_63 + 780945676*uk_64 + 305900380*uk_65 + 342125425*uk_66 + 144900180*uk_67 + 414575515*uk_68 + 873426085*uk_69 + 217*uk_7 + 342125425*uk_70 + 61369488*uk_71 + 175584924*uk_72 + 369921636*uk_73 + 144900180*uk_74 + 502367977*uk_75 + 1058386903*uk_76 + 414575515*uk_77 + 2229805417*uk_78 + 873426085*uk_79 + 85*uk_8 + 342125425*uk_80 + 250047*uk_81 + 301644*uk_82 + 337365*uk_83 + 142884*uk_84 + 408807*uk_85 + 861273*uk_86 + 337365*uk_87 + 363888*uk_88 + 406980*uk_89 + 2242306609*uk_9 + 172368*uk_90 + 493164*uk_91 + 1038996*uk_92 + 406980*uk_93 + 455175*uk_94 + 192780*uk_95 + 551565*uk_96 + 1162035*uk_97 + 455175*uk_98 + 81648*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 238140*uk_100 + 492156*uk_101 + 172368*uk_102 + 694575*uk_103 + 1435455*uk_104 + 502740*uk_105 + 2966607*uk_106 + 1038996*uk_107 + 363888*uk_108 + 1092727*uk_109 + 4877359*uk_11 + 806284*uk_110 + 381924*uk_111 + 1113945*uk_112 + 2302153*uk_113 + 806284*uk_114 + 594928*uk_115 + 281808*uk_116 + 821940*uk_117 + 1698676*uk_118 + 594928*uk_119 + 3598828*uk_12 + 133488*uk_120 + 389340*uk_121 + 804636*uk_122 + 281808*uk_123 + 1135575*uk_124 + 2346855*uk_125 + 821940*uk_126 + 4850167*uk_127 + 1698676*uk_128 + 594928*uk_129 + 1704708*uk_13 + 438976*uk_130 + 207936*uk_131 + 606480*uk_132 + 1253392*uk_133 + 438976*uk_134 + 98496*uk_135 + 287280*uk_136 + 593712*uk_137 + 207936*uk_138 + 837900*uk_139 + 4972065*uk_14 + 1731660*uk_140 + 606480*uk_141 + 3578764*uk_142 + 1253392*uk_143 + 438976*uk_144 + 46656*uk_145 + 136080*uk_146 + 281232*uk_147 + 98496*uk_148 + 396900*uk_149 + 10275601*uk_15 + 820260*uk_150 + 287280*uk_151 + 1695204*uk_152 + 593712*uk_153 + 207936*uk_154 + 1157625*uk_155 + 2392425*uk_156 + 837900*uk_157 + 4944345*uk_158 + 1731660*uk_159 + 3598828*uk_16 + 606480*uk_160 + 10218313*uk_161 + 3578764*uk_162 + 1253392*uk_163 + 438976*uk_164 + 3969*uk_17 + 6489*uk_18 + 4788*uk_19 + 63*uk_2 + 2268*uk_20 + 6615*uk_21 + 13671*uk_22 + 4788*uk_23 + 10609*uk_24 + 7828*uk_25 + 3708*uk_26 + 10815*uk_27 + 22351*uk_28 + 7828*uk_29 + 103*uk_3 + 5776*uk_30 + 2736*uk_31 + 7980*uk_32 + 16492*uk_33 + 5776*uk_34 + 1296*uk_35 + 3780*uk_36 + 7812*uk_37 + 2736*uk_38 + 11025*uk_39 + 76*uk_4 + 22785*uk_40 + 7980*uk_41 + 47089*uk_42 + 16492*uk_43 + 5776*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 230957580727*uk_47 + 170415302284*uk_48 + 80723037924*uk_49 + 36*uk_5 + 235442193945*uk_50 + 486580534153*uk_51 + 170415302284*uk_52 + 187944057*uk_53 + 307273617*uk_54 + 226726164*uk_55 + 107396604*uk_56 + 313240095*uk_57 + 647362863*uk_58 + 226726164*uk_59 + 105*uk_6 + 502367977*uk_60 + 370679284*uk_61 + 175584924*uk_62 + 512122695*uk_63 + 1058386903*uk_64 + 370679284*uk_65 + 273510928*uk_66 + 129557808*uk_67 + 377876940*uk_68 + 780945676*uk_69 + 217*uk_7 + 273510928*uk_70 + 61369488*uk_71 + 178994340*uk_72 + 369921636*uk_73 + 129557808*uk_74 + 522066825*uk_75 + 1078938105*uk_76 + 377876940*uk_77 + 2229805417*uk_78 + 780945676*uk_79 + 76*uk_8 + 273510928*uk_80 + 250047*uk_81 + 408807*uk_82 + 301644*uk_83 + 142884*uk_84 + 416745*uk_85 + 861273*uk_86 + 301644*uk_87 + 668367*uk_88 + 493164*uk_89 + 2242306609*uk_9 + 233604*uk_90 + 681345*uk_91 + 1408113*uk_92 + 493164*uk_93 + 363888*uk_94 + 172368*uk_95 + 502740*uk_96 + 1038996*uk_97 + 363888*uk_98 + 81648*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 215712*uk_100 + 437472*uk_101 + 207648*uk_102 + 721287*uk_103 + 1462797*uk_104 + 694323*uk_105 + 2966607*uk_106 + 1408113*uk_107 + 668367*uk_108 + 205379*uk_109 + 2793827*uk_11 + 358543*uk_110 + 111392*uk_111 + 372467*uk_112 + 755377*uk_113 + 358543*uk_114 + 625931*uk_115 + 194464*uk_116 + 650239*uk_117 + 1318709*uk_118 + 625931*uk_119 + 4877359*uk_12 + 60416*uk_120 + 202016*uk_121 + 409696*uk_122 + 194464*uk_123 + 675491*uk_124 + 1369921*uk_125 + 650239*uk_126 + 2778251*uk_127 + 1318709*uk_128 + 625931*uk_129 + 1515296*uk_13 + 1092727*uk_130 + 339488*uk_131 + 1135163*uk_132 + 2302153*uk_133 + 1092727*uk_134 + 105472*uk_135 + 352672*uk_136 + 715232*uk_137 + 339488*uk_138 + 1179247*uk_139 + 5066771*uk_14 + 2391557*uk_140 + 1135163*uk_141 + 4850167*uk_142 + 2302153*uk_143 + 1092727*uk_144 + 32768*uk_145 + 109568*uk_146 + 222208*uk_147 + 105472*uk_148 + 366368*uk_149 + 10275601*uk_15 + 743008*uk_150 + 352672*uk_151 + 1506848*uk_152 + 715232*uk_153 + 339488*uk_154 + 1225043*uk_155 + 2484433*uk_156 + 1179247*uk_157 + 5038523*uk_158 + 2391557*uk_159 + 4877359*uk_16 + 1135163*uk_160 + 10218313*uk_161 + 4850167*uk_162 + 2302153*uk_163 + 1092727*uk_164 + 3969*uk_17 + 3717*uk_18 + 6489*uk_19 + 63*uk_2 + 2016*uk_20 + 6741*uk_21 + 13671*uk_22 + 6489*uk_23 + 3481*uk_24 + 6077*uk_25 + 1888*uk_26 + 6313*uk_27 + 12803*uk_28 + 6077*uk_29 + 59*uk_3 + 10609*uk_30 + 3296*uk_31 + 11021*uk_32 + 22351*uk_33 + 10609*uk_34 + 1024*uk_35 + 3424*uk_36 + 6944*uk_37 + 3296*uk_38 + 11449*uk_39 + 103*uk_4 + 23219*uk_40 + 11021*uk_41 + 47089*uk_42 + 22351*uk_43 + 10609*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 132296089931*uk_47 + 230957580727*uk_48 + 71753811488*uk_49 + 32*uk_5 + 239926807163*uk_50 + 486580534153*uk_51 + 230957580727*uk_52 + 187944057*uk_53 + 176011101*uk_54 + 307273617*uk_55 + 95463648*uk_56 + 319206573*uk_57 + 647362863*uk_58 + 307273617*uk_59 + 107*uk_6 + 164835793*uk_60 + 287764181*uk_61 + 89402464*uk_62 + 298939489*uk_63 + 606260459*uk_64 + 287764181*uk_65 + 502367977*uk_66 + 156075488*uk_67 + 521877413*uk_68 + 1058386903*uk_69 + 217*uk_7 + 502367977*uk_70 + 48489472*uk_71 + 162136672*uk_72 + 328819232*uk_73 + 156075488*uk_74 + 542144497*uk_75 + 1099489307*uk_76 + 521877413*uk_77 + 2229805417*uk_78 + 1058386903*uk_79 + 103*uk_8 + 502367977*uk_80 + 250047*uk_81 + 234171*uk_82 + 408807*uk_83 + 127008*uk_84 + 424683*uk_85 + 861273*uk_86 + 408807*uk_87 + 219303*uk_88 + 382851*uk_89 + 2242306609*uk_9 + 118944*uk_90 + 397719*uk_91 + 806589*uk_92 + 382851*uk_93 + 668367*uk_94 + 207648*uk_95 + 694323*uk_96 + 1408113*uk_97 + 668367*uk_98 + 64512*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 219744*uk_100 + 437472*uk_101 + 118944*uk_102 + 748503*uk_103 + 1490139*uk_104 + 405153*uk_105 + 2966607*uk_106 + 806589*uk_107 + 219303*uk_108 + 103823*uk_109 + 2225591*uk_11 + 130331*uk_110 + 70688*uk_111 + 240781*uk_112 + 479353*uk_113 + 130331*uk_114 + 163607*uk_115 + 88736*uk_116 + 302257*uk_117 + 601741*uk_118 + 163607*uk_119 + 2793827*uk_12 + 48128*uk_120 + 163936*uk_121 + 326368*uk_122 + 88736*uk_123 + 558407*uk_124 + 1111691*uk_125 + 302257*uk_126 + 2213183*uk_127 + 601741*uk_128 + 163607*uk_129 + 1515296*uk_13 + 205379*uk_130 + 111392*uk_131 + 379429*uk_132 + 755377*uk_133 + 205379*uk_134 + 60416*uk_135 + 205792*uk_136 + 409696*uk_137 + 111392*uk_138 + 700979*uk_139 + 5161477*uk_14 + 1395527*uk_140 + 379429*uk_141 + 2778251*uk_142 + 755377*uk_143 + 205379*uk_144 + 32768*uk_145 + 111616*uk_146 + 222208*uk_147 + 60416*uk_148 + 380192*uk_149 + 10275601*uk_15 + 756896*uk_150 + 205792*uk_151 + 1506848*uk_152 + 409696*uk_153 + 111392*uk_154 + 1295029*uk_155 + 2578177*uk_156 + 700979*uk_157 + 5132701*uk_158 + 1395527*uk_159 + 2793827*uk_16 + 379429*uk_160 + 10218313*uk_161 + 2778251*uk_162 + 755377*uk_163 + 205379*uk_164 + 3969*uk_17 + 2961*uk_18 + 3717*uk_19 + 63*uk_2 + 2016*uk_20 + 6867*uk_21 + 13671*uk_22 + 3717*uk_23 + 2209*uk_24 + 2773*uk_25 + 1504*uk_26 + 5123*uk_27 + 10199*uk_28 + 2773*uk_29 + 47*uk_3 + 3481*uk_30 + 1888*uk_31 + 6431*uk_32 + 12803*uk_33 + 3481*uk_34 + 1024*uk_35 + 3488*uk_36 + 6944*uk_37 + 1888*uk_38 + 11881*uk_39 + 59*uk_4 + 23653*uk_40 + 6431*uk_41 + 47089*uk_42 + 12803*uk_43 + 3481*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 105388410623*uk_47 + 132296089931*uk_48 + 71753811488*uk_49 + 32*uk_5 + 244411420381*uk_50 + 486580534153*uk_51 + 132296089931*uk_52 + 187944057*uk_53 + 140212233*uk_54 + 176011101*uk_55 + 95463648*uk_56 + 325173051*uk_57 + 647362863*uk_58 + 176011101*uk_59 + 109*uk_6 + 104602777*uk_60 + 131309869*uk_61 + 71218912*uk_62 + 242589419*uk_63 + 482953247*uk_64 + 131309869*uk_65 + 164835793*uk_66 + 89402464*uk_67 + 304527143*uk_68 + 606260459*uk_69 + 217*uk_7 + 164835793*uk_70 + 48489472*uk_71 + 165167264*uk_72 + 328819232*uk_73 + 89402464*uk_74 + 562600993*uk_75 + 1120040509*uk_76 + 304527143*uk_77 + 2229805417*uk_78 + 606260459*uk_79 + 59*uk_8 + 164835793*uk_80 + 250047*uk_81 + 186543*uk_82 + 234171*uk_83 + 127008*uk_84 + 432621*uk_85 + 861273*uk_86 + 234171*uk_87 + 139167*uk_88 + 174699*uk_89 + 2242306609*uk_9 + 94752*uk_90 + 322749*uk_91 + 642537*uk_92 + 174699*uk_93 + 219303*uk_94 + 118944*uk_95 + 405153*uk_96 + 806589*uk_97 + 219303*uk_98 + 64512*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 223776*uk_100 + 437472*uk_101 + 94752*uk_102 + 776223*uk_103 + 1517481*uk_104 + 328671*uk_105 + 2966607*uk_106 + 642537*uk_107 + 139167*uk_108 + 300763*uk_109 + 3172651*uk_11 + 210983*uk_110 + 143648*uk_111 + 498279*uk_112 + 974113*uk_113 + 210983*uk_114 + 148003*uk_115 + 100768*uk_116 + 349539*uk_117 + 683333*uk_118 + 148003*uk_119 + 2225591*uk_12 + 68608*uk_120 + 237984*uk_121 + 465248*uk_122 + 100768*uk_123 + 825507*uk_124 + 1613829*uk_125 + 349539*uk_126 + 3154963*uk_127 + 683333*uk_128 + 148003*uk_129 + 1515296*uk_13 + 103823*uk_130 + 70688*uk_131 + 245199*uk_132 + 479353*uk_133 + 103823*uk_134 + 48128*uk_135 + 166944*uk_136 + 326368*uk_137 + 70688*uk_138 + 579087*uk_139 + 5256183*uk_14 + 1132089*uk_140 + 245199*uk_141 + 2213183*uk_142 + 479353*uk_143 + 103823*uk_144 + 32768*uk_145 + 113664*uk_146 + 222208*uk_147 + 48128*uk_148 + 394272*uk_149 + 10275601*uk_15 + 770784*uk_150 + 166944*uk_151 + 1506848*uk_152 + 326368*uk_153 + 70688*uk_154 + 1367631*uk_155 + 2673657*uk_156 + 579087*uk_157 + 5226879*uk_158 + 1132089*uk_159 + 2225591*uk_16 + 245199*uk_160 + 10218313*uk_161 + 2213183*uk_162 + 479353*uk_163 + 103823*uk_164 + 3969*uk_17 + 4221*uk_18 + 2961*uk_19 + 63*uk_2 + 2016*uk_20 + 6993*uk_21 + 13671*uk_22 + 2961*uk_23 + 4489*uk_24 + 3149*uk_25 + 2144*uk_26 + 7437*uk_27 + 14539*uk_28 + 3149*uk_29 + 67*uk_3 + 2209*uk_30 + 1504*uk_31 + 5217*uk_32 + 10199*uk_33 + 2209*uk_34 + 1024*uk_35 + 3552*uk_36 + 6944*uk_37 + 1504*uk_38 + 12321*uk_39 + 47*uk_4 + 24087*uk_40 + 5217*uk_41 + 47089*uk_42 + 10199*uk_43 + 2209*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 150234542803*uk_47 + 105388410623*uk_48 + 71753811488*uk_49 + 32*uk_5 + 248896033599*uk_50 + 486580534153*uk_51 + 105388410623*uk_52 + 187944057*uk_53 + 199877013*uk_54 + 140212233*uk_55 + 95463648*uk_56 + 331139529*uk_57 + 647362863*uk_58 + 140212233*uk_59 + 111*uk_6 + 212567617*uk_60 + 149114597*uk_61 + 101524832*uk_62 + 352164261*uk_63 + 688465267*uk_64 + 149114597*uk_65 + 104602777*uk_66 + 71218912*uk_67 + 247040601*uk_68 + 482953247*uk_69 + 217*uk_7 + 104602777*uk_70 + 48489472*uk_71 + 168197856*uk_72 + 328819232*uk_73 + 71218912*uk_74 + 583436313*uk_75 + 1140591711*uk_76 + 247040601*uk_77 + 2229805417*uk_78 + 482953247*uk_79 + 47*uk_8 + 104602777*uk_80 + 250047*uk_81 + 265923*uk_82 + 186543*uk_83 + 127008*uk_84 + 440559*uk_85 + 861273*uk_86 + 186543*uk_87 + 282807*uk_88 + 198387*uk_89 + 2242306609*uk_9 + 135072*uk_90 + 468531*uk_91 + 915957*uk_92 + 198387*uk_93 + 139167*uk_94 + 94752*uk_95 + 328671*uk_96 + 642537*uk_97 + 139167*uk_98 + 64512*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 199332*uk_100 + 382788*uk_101 + 118188*uk_102 + 804447*uk_103 + 1544823*uk_104 + 476973*uk_105 + 2966607*uk_106 + 915957*uk_107 + 282807*uk_108 + 216*uk_109 + 284118*uk_11 + 2412*uk_110 + 1008*uk_111 + 4068*uk_112 + 7812*uk_113 + 2412*uk_114 + 26934*uk_115 + 11256*uk_116 + 45426*uk_117 + 87234*uk_118 + 26934*uk_119 + 3172651*uk_12 + 4704*uk_120 + 18984*uk_121 + 36456*uk_122 + 11256*uk_123 + 76614*uk_124 + 147126*uk_125 + 45426*uk_126 + 282534*uk_127 + 87234*uk_128 + 26934*uk_129 + 1325884*uk_13 + 300763*uk_130 + 125692*uk_131 + 507257*uk_132 + 974113*uk_133 + 300763*uk_134 + 52528*uk_135 + 211988*uk_136 + 407092*uk_137 + 125692*uk_138 + 855523*uk_139 + 5350889*uk_14 + 1642907*uk_140 + 507257*uk_141 + 3154963*uk_142 + 974113*uk_143 + 300763*uk_144 + 21952*uk_145 + 88592*uk_146 + 170128*uk_147 + 52528*uk_148 + 357532*uk_149 + 10275601*uk_15 + 686588*uk_150 + 211988*uk_151 + 1318492*uk_152 + 407092*uk_153 + 125692*uk_154 + 1442897*uk_155 + 2770873*uk_156 + 855523*uk_157 + 5321057*uk_158 + 1642907*uk_159 + 3172651*uk_16 + 507257*uk_160 + 10218313*uk_161 + 3154963*uk_162 + 974113*uk_163 + 300763*uk_164 + 3969*uk_17 + 378*uk_18 + 4221*uk_19 + 63*uk_2 + 1764*uk_20 + 7119*uk_21 + 13671*uk_22 + 4221*uk_23 + 36*uk_24 + 402*uk_25 + 168*uk_26 + 678*uk_27 + 1302*uk_28 + 402*uk_29 + 6*uk_3 + 4489*uk_30 + 1876*uk_31 + 7571*uk_32 + 14539*uk_33 + 4489*uk_34 + 784*uk_35 + 3164*uk_36 + 6076*uk_37 + 1876*uk_38 + 12769*uk_39 + 67*uk_4 + 24521*uk_40 + 7571*uk_41 + 47089*uk_42 + 14539*uk_43 + 4489*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 13453839654*uk_47 + 150234542803*uk_48 + 62784585052*uk_49 + 28*uk_5 + 253380646817*uk_50 + 486580534153*uk_51 + 150234542803*uk_52 + 187944057*uk_53 + 17899434*uk_54 + 199877013*uk_55 + 83530692*uk_56 + 337106007*uk_57 + 647362863*uk_58 + 199877013*uk_59 + 113*uk_6 + 1704708*uk_60 + 19035906*uk_61 + 7955304*uk_62 + 32105334*uk_63 + 61653606*uk_64 + 19035906*uk_65 + 212567617*uk_66 + 88834228*uk_67 + 358509563*uk_68 + 688465267*uk_69 + 217*uk_7 + 212567617*uk_70 + 37124752*uk_71 + 149824892*uk_72 + 287716828*uk_73 + 88834228*uk_74 + 604650457*uk_75 + 1161142913*uk_76 + 358509563*uk_77 + 2229805417*uk_78 + 688465267*uk_79 + 67*uk_8 + 212567617*uk_80 + 250047*uk_81 + 23814*uk_82 + 265923*uk_83 + 111132*uk_84 + 448497*uk_85 + 861273*uk_86 + 265923*uk_87 + 2268*uk_88 + 25326*uk_89 + 2242306609*uk_9 + 10584*uk_90 + 42714*uk_91 + 82026*uk_92 + 25326*uk_93 + 282807*uk_94 + 118188*uk_95 + 476973*uk_96 + 915957*uk_97 + 282807*uk_98 + 49392*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 231840*uk_100 + 437472*uk_101 + 12096*uk_102 + 833175*uk_103 + 1572165*uk_104 + 43470*uk_105 + 2966607*uk_106 + 82026*uk_107 + 2268*uk_108 + 681472*uk_109 + 4167064*uk_11 + 46464*uk_110 + 247808*uk_111 + 890560*uk_112 + 1680448*uk_113 + 46464*uk_114 + 3168*uk_115 + 16896*uk_116 + 60720*uk_117 + 114576*uk_118 + 3168*uk_119 + 284118*uk_12 + 90112*uk_120 + 323840*uk_121 + 611072*uk_122 + 16896*uk_123 + 1163800*uk_124 + 2196040*uk_125 + 60720*uk_126 + 4143832*uk_127 + 114576*uk_128 + 3168*uk_129 + 1515296*uk_13 + 216*uk_130 + 1152*uk_131 + 4140*uk_132 + 7812*uk_133 + 216*uk_134 + 6144*uk_135 + 22080*uk_136 + 41664*uk_137 + 1152*uk_138 + 79350*uk_139 + 5445595*uk_14 + 149730*uk_140 + 4140*uk_141 + 282534*uk_142 + 7812*uk_143 + 216*uk_144 + 32768*uk_145 + 117760*uk_146 + 222208*uk_147 + 6144*uk_148 + 423200*uk_149 + 10275601*uk_15 + 798560*uk_150 + 22080*uk_151 + 1506848*uk_152 + 41664*uk_153 + 1152*uk_154 + 1520875*uk_155 + 2869825*uk_156 + 79350*uk_157 + 5415235*uk_158 + 149730*uk_159 + 284118*uk_16 + 4140*uk_160 + 10218313*uk_161 + 282534*uk_162 + 7812*uk_163 + 216*uk_164 + 3969*uk_17 + 5544*uk_18 + 378*uk_19 + 63*uk_2 + 2016*uk_20 + 7245*uk_21 + 13671*uk_22 + 378*uk_23 + 7744*uk_24 + 528*uk_25 + 2816*uk_26 + 10120*uk_27 + 19096*uk_28 + 528*uk_29 + 88*uk_3 + 36*uk_30 + 192*uk_31 + 690*uk_32 + 1302*uk_33 + 36*uk_34 + 1024*uk_35 + 3680*uk_36 + 6944*uk_37 + 192*uk_38 + 13225*uk_39 + 6*uk_4 + 24955*uk_40 + 690*uk_41 + 47089*uk_42 + 1302*uk_43 + 36*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 197322981592*uk_47 + 13453839654*uk_48 + 71753811488*uk_49 + 32*uk_5 + 257865260035*uk_50 + 486580534153*uk_51 + 13453839654*uk_52 + 187944057*uk_53 + 262525032*uk_54 + 17899434*uk_55 + 95463648*uk_56 + 343072485*uk_57 + 647362863*uk_58 + 17899434*uk_59 + 115*uk_6 + 366701632*uk_60 + 25002384*uk_61 + 133346048*uk_62 + 479212360*uk_63 + 904252888*uk_64 + 25002384*uk_65 + 1704708*uk_66 + 9091776*uk_67 + 32673570*uk_68 + 61653606*uk_69 + 217*uk_7 + 1704708*uk_70 + 48489472*uk_71 + 174259040*uk_72 + 328819232*uk_73 + 9091776*uk_74 + 626243425*uk_75 + 1181694115*uk_76 + 32673570*uk_77 + 2229805417*uk_78 + 61653606*uk_79 + 6*uk_8 + 1704708*uk_80 + 250047*uk_81 + 349272*uk_82 + 23814*uk_83 + 127008*uk_84 + 456435*uk_85 + 861273*uk_86 + 23814*uk_87 + 487872*uk_88 + 33264*uk_89 + 2242306609*uk_9 + 177408*uk_90 + 637560*uk_91 + 1203048*uk_92 + 33264*uk_93 + 2268*uk_94 + 12096*uk_95 + 43470*uk_96 + 82026*uk_97 + 2268*uk_98 + 64512*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 206388*uk_100 + 382788*uk_101 + 155232*uk_102 + 862407*uk_103 + 1599507*uk_104 + 648648*uk_105 + 2966607*uk_106 + 1203048*uk_107 + 487872*uk_108 + 614125*uk_109 + 4025005*uk_11 + 635800*uk_110 + 202300*uk_111 + 845325*uk_112 + 1567825*uk_113 + 635800*uk_114 + 658240*uk_115 + 209440*uk_116 + 875160*uk_117 + 1623160*uk_118 + 658240*uk_119 + 4167064*uk_12 + 66640*uk_120 + 278460*uk_121 + 516460*uk_122 + 209440*uk_123 + 1163565*uk_124 + 2158065*uk_125 + 875160*uk_126 + 4002565*uk_127 + 1623160*uk_128 + 658240*uk_129 + 1325884*uk_13 + 681472*uk_130 + 216832*uk_131 + 906048*uk_132 + 1680448*uk_133 + 681472*uk_134 + 68992*uk_135 + 288288*uk_136 + 534688*uk_137 + 216832*uk_138 + 1204632*uk_139 + 5540301*uk_14 + 2234232*uk_140 + 906048*uk_141 + 4143832*uk_142 + 1680448*uk_143 + 681472*uk_144 + 21952*uk_145 + 91728*uk_146 + 170128*uk_147 + 68992*uk_148 + 383292*uk_149 + 10275601*uk_15 + 710892*uk_150 + 288288*uk_151 + 1318492*uk_152 + 534688*uk_153 + 216832*uk_154 + 1601613*uk_155 + 2970513*uk_156 + 1204632*uk_157 + 5509413*uk_158 + 2234232*uk_159 + 4167064*uk_16 + 906048*uk_160 + 10218313*uk_161 + 4143832*uk_162 + 1680448*uk_163 + 681472*uk_164 + 3969*uk_17 + 5355*uk_18 + 5544*uk_19 + 63*uk_2 + 1764*uk_20 + 7371*uk_21 + 13671*uk_22 + 5544*uk_23 + 7225*uk_24 + 7480*uk_25 + 2380*uk_26 + 9945*uk_27 + 18445*uk_28 + 7480*uk_29 + 85*uk_3 + 7744*uk_30 + 2464*uk_31 + 10296*uk_32 + 19096*uk_33 + 7744*uk_34 + 784*uk_35 + 3276*uk_36 + 6076*uk_37 + 2464*uk_38 + 13689*uk_39 + 88*uk_4 + 25389*uk_40 + 10296*uk_41 + 47089*uk_42 + 19096*uk_43 + 7744*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 190596061765*uk_47 + 197322981592*uk_48 + 62784585052*uk_49 + 28*uk_5 + 262349873253*uk_50 + 486580534153*uk_51 + 197322981592*uk_52 + 187944057*uk_53 + 253575315*uk_54 + 262525032*uk_55 + 83530692*uk_56 + 349038963*uk_57 + 647362863*uk_58 + 262525032*uk_59 + 117*uk_6 + 342125425*uk_60 + 354200440*uk_61 + 112700140*uk_62 + 470925585*uk_63 + 873426085*uk_64 + 354200440*uk_65 + 366701632*uk_66 + 116677792*uk_67 + 487546488*uk_68 + 904252888*uk_69 + 217*uk_7 + 366701632*uk_70 + 37124752*uk_71 + 155128428*uk_72 + 287716828*uk_73 + 116677792*uk_74 + 648215217*uk_75 + 1202245317*uk_76 + 487546488*uk_77 + 2229805417*uk_78 + 904252888*uk_79 + 88*uk_8 + 366701632*uk_80 + 250047*uk_81 + 337365*uk_82 + 349272*uk_83 + 111132*uk_84 + 464373*uk_85 + 861273*uk_86 + 349272*uk_87 + 455175*uk_88 + 471240*uk_89 + 2242306609*uk_9 + 149940*uk_90 + 626535*uk_91 + 1162035*uk_92 + 471240*uk_93 + 487872*uk_94 + 155232*uk_95 + 648648*uk_96 + 1203048*uk_97 + 487872*uk_98 + 49392*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 209916*uk_100 + 382788*uk_101 + 149940*uk_102 + 892143*uk_103 + 1626849*uk_104 + 637245*uk_105 + 2966607*uk_106 + 1162035*uk_107 + 455175*uk_108 + 1331000*uk_109 + 5208830*uk_11 + 1028500*uk_110 + 338800*uk_111 + 1439900*uk_112 + 2625700*uk_113 + 1028500*uk_114 + 794750*uk_115 + 261800*uk_116 + 1112650*uk_117 + 2028950*uk_118 + 794750*uk_119 + 4025005*uk_12 + 86240*uk_120 + 366520*uk_121 + 668360*uk_122 + 261800*uk_123 + 1557710*uk_124 + 2840530*uk_125 + 1112650*uk_126 + 5179790*uk_127 + 2028950*uk_128 + 794750*uk_129 + 1325884*uk_13 + 614125*uk_130 + 202300*uk_131 + 859775*uk_132 + 1567825*uk_133 + 614125*uk_134 + 66640*uk_135 + 283220*uk_136 + 516460*uk_137 + 202300*uk_138 + 1203685*uk_139 + 5635007*uk_14 + 2194955*uk_140 + 859775*uk_141 + 4002565*uk_142 + 1567825*uk_143 + 614125*uk_144 + 21952*uk_145 + 93296*uk_146 + 170128*uk_147 + 66640*uk_148 + 396508*uk_149 + 10275601*uk_15 + 723044*uk_150 + 283220*uk_151 + 1318492*uk_152 + 516460*uk_153 + 202300*uk_154 + 1685159*uk_155 + 3072937*uk_156 + 1203685*uk_157 + 5603591*uk_158 + 2194955*uk_159 + 4025005*uk_16 + 859775*uk_160 + 10218313*uk_161 + 4002565*uk_162 + 1567825*uk_163 + 614125*uk_164 + 3969*uk_17 + 6930*uk_18 + 5355*uk_19 + 63*uk_2 + 1764*uk_20 + 7497*uk_21 + 13671*uk_22 + 5355*uk_23 + 12100*uk_24 + 9350*uk_25 + 3080*uk_26 + 13090*uk_27 + 23870*uk_28 + 9350*uk_29 + 110*uk_3 + 7225*uk_30 + 2380*uk_31 + 10115*uk_32 + 18445*uk_33 + 7225*uk_34 + 784*uk_35 + 3332*uk_36 + 6076*uk_37 + 2380*uk_38 + 14161*uk_39 + 85*uk_4 + 25823*uk_40 + 10115*uk_41 + 47089*uk_42 + 18445*uk_43 + 7225*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 246653726990*uk_47 + 190596061765*uk_48 + 62784585052*uk_49 + 28*uk_5 + 266834486471*uk_50 + 486580534153*uk_51 + 190596061765*uk_52 + 187944057*uk_53 + 328156290*uk_54 + 253575315*uk_55 + 83530692*uk_56 + 355005441*uk_57 + 647362863*uk_58 + 253575315*uk_59 + 119*uk_6 + 572971300*uk_60 + 442750550*uk_61 + 145847240*uk_62 + 619850770*uk_63 + 1130316110*uk_64 + 442750550*uk_65 + 342125425*uk_66 + 112700140*uk_67 + 478975595*uk_68 + 873426085*uk_69 + 217*uk_7 + 342125425*uk_70 + 37124752*uk_71 + 157780196*uk_72 + 287716828*uk_73 + 112700140*uk_74 + 670565833*uk_75 + 1222796519*uk_76 + 478975595*uk_77 + 2229805417*uk_78 + 873426085*uk_79 + 85*uk_8 + 342125425*uk_80 + 250047*uk_81 + 436590*uk_82 + 337365*uk_83 + 111132*uk_84 + 472311*uk_85 + 861273*uk_86 + 337365*uk_87 + 762300*uk_88 + 589050*uk_89 + 2242306609*uk_9 + 194040*uk_90 + 824670*uk_91 + 1503810*uk_92 + 589050*uk_93 + 455175*uk_94 + 149940*uk_95 + 637245*uk_96 + 1162035*uk_97 + 455175*uk_98 + 49392*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 182952*uk_100 + 328104*uk_101 + 166320*uk_102 + 922383*uk_103 + 1654191*uk_104 + 838530*uk_105 + 2966607*uk_106 + 1503810*uk_107 + 762300*uk_108 + 74088*uk_109 + 1988826*uk_11 + 194040*uk_110 + 42336*uk_111 + 213444*uk_112 + 382788*uk_113 + 194040*uk_114 + 508200*uk_115 + 110880*uk_116 + 559020*uk_117 + 1002540*uk_118 + 508200*uk_119 + 5208830*uk_12 + 24192*uk_120 + 121968*uk_121 + 218736*uk_122 + 110880*uk_123 + 614922*uk_124 + 1102794*uk_125 + 559020*uk_126 + 1977738*uk_127 + 1002540*uk_128 + 508200*uk_129 + 1136472*uk_13 + 1331000*uk_130 + 290400*uk_131 + 1464100*uk_132 + 2625700*uk_133 + 1331000*uk_134 + 63360*uk_135 + 319440*uk_136 + 572880*uk_137 + 290400*uk_138 + 1610510*uk_139 + 5729713*uk_14 + 2888270*uk_140 + 1464100*uk_141 + 5179790*uk_142 + 2625700*uk_143 + 1331000*uk_144 + 13824*uk_145 + 69696*uk_146 + 124992*uk_147 + 63360*uk_148 + 351384*uk_149 + 10275601*uk_15 + 630168*uk_150 + 319440*uk_151 + 1130136*uk_152 + 572880*uk_153 + 290400*uk_154 + 1771561*uk_155 + 3177097*uk_156 + 1610510*uk_157 + 5697769*uk_158 + 2888270*uk_159 + 5208830*uk_16 + 1464100*uk_160 + 10218313*uk_161 + 5179790*uk_162 + 2625700*uk_163 + 1331000*uk_164 + 3969*uk_17 + 2646*uk_18 + 6930*uk_19 + 63*uk_2 + 1512*uk_20 + 7623*uk_21 + 13671*uk_22 + 6930*uk_23 + 1764*uk_24 + 4620*uk_25 + 1008*uk_26 + 5082*uk_27 + 9114*uk_28 + 4620*uk_29 + 42*uk_3 + 12100*uk_30 + 2640*uk_31 + 13310*uk_32 + 23870*uk_33 + 12100*uk_34 + 576*uk_35 + 2904*uk_36 + 5208*uk_37 + 2640*uk_38 + 14641*uk_39 + 110*uk_4 + 26257*uk_40 + 13310*uk_41 + 47089*uk_42 + 23870*uk_43 + 12100*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 94176877578*uk_47 + 246653726990*uk_48 + 53815358616*uk_49 + 24*uk_5 + 271319099689*uk_50 + 486580534153*uk_51 + 246653726990*uk_52 + 187944057*uk_53 + 125296038*uk_54 + 328156290*uk_55 + 71597736*uk_56 + 360971919*uk_57 + 647362863*uk_58 + 328156290*uk_59 + 121*uk_6 + 83530692*uk_60 + 218770860*uk_61 + 47731824*uk_62 + 240647946*uk_63 + 431575242*uk_64 + 218770860*uk_65 + 572971300*uk_66 + 125011920*uk_67 + 630268430*uk_68 + 1130316110*uk_69 + 217*uk_7 + 572971300*uk_70 + 27275328*uk_71 + 137513112*uk_72 + 246614424*uk_73 + 125011920*uk_74 + 693295273*uk_75 + 1243347721*uk_76 + 630268430*uk_77 + 2229805417*uk_78 + 1130316110*uk_79 + 110*uk_8 + 572971300*uk_80 + 250047*uk_81 + 166698*uk_82 + 436590*uk_83 + 95256*uk_84 + 480249*uk_85 + 861273*uk_86 + 436590*uk_87 + 111132*uk_88 + 291060*uk_89 + 2242306609*uk_9 + 63504*uk_90 + 320166*uk_91 + 574182*uk_92 + 291060*uk_93 + 762300*uk_94 + 166320*uk_95 + 838530*uk_96 + 1503810*uk_97 + 762300*uk_98 + 36288*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 216972*uk_100 + 382788*uk_101 + 74088*uk_102 + 953127*uk_103 + 1681533*uk_104 + 325458*uk_105 + 2966607*uk_106 + 574182*uk_107 + 111132*uk_108 + 1771561*uk_109 + 5729713*uk_11 + 614922*uk_110 + 409948*uk_111 + 1800843*uk_112 + 3177097*uk_113 + 614922*uk_114 + 213444*uk_115 + 142296*uk_116 + 625086*uk_117 + 1102794*uk_118 + 213444*uk_119 + 1988826*uk_12 + 94864*uk_120 + 416724*uk_121 + 735196*uk_122 + 142296*uk_123 + 1830609*uk_124 + 3229611*uk_125 + 625086*uk_126 + 5697769*uk_127 + 1102794*uk_128 + 213444*uk_129 + 1325884*uk_13 + 74088*uk_130 + 49392*uk_131 + 216972*uk_132 + 382788*uk_133 + 74088*uk_134 + 32928*uk_135 + 144648*uk_136 + 255192*uk_137 + 49392*uk_138 + 635418*uk_139 + 5824419*uk_14 + 1121022*uk_140 + 216972*uk_141 + 1977738*uk_142 + 382788*uk_143 + 74088*uk_144 + 21952*uk_145 + 96432*uk_146 + 170128*uk_147 + 32928*uk_148 + 423612*uk_149 + 10275601*uk_15 + 747348*uk_150 + 144648*uk_151 + 1318492*uk_152 + 255192*uk_153 + 49392*uk_154 + 1860867*uk_155 + 3282993*uk_156 + 635418*uk_157 + 5791947*uk_158 + 1121022*uk_159 + 1988826*uk_16 + 216972*uk_160 + 10218313*uk_161 + 1977738*uk_162 + 382788*uk_163 + 74088*uk_164 + 3969*uk_17 + 7623*uk_18 + 2646*uk_19 + 63*uk_2 + 1764*uk_20 + 7749*uk_21 + 13671*uk_22 + 2646*uk_23 + 14641*uk_24 + 5082*uk_25 + 3388*uk_26 + 14883*uk_27 + 26257*uk_28 + 5082*uk_29 + 121*uk_3 + 1764*uk_30 + 1176*uk_31 + 5166*uk_32 + 9114*uk_33 + 1764*uk_34 + 784*uk_35 + 3444*uk_36 + 6076*uk_37 + 1176*uk_38 + 15129*uk_39 + 42*uk_4 + 26691*uk_40 + 5166*uk_41 + 47089*uk_42 + 9114*uk_43 + 1764*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 271319099689*uk_47 + 94176877578*uk_48 + 62784585052*uk_49 + 28*uk_5 + 275803712907*uk_50 + 486580534153*uk_51 + 94176877578*uk_52 + 187944057*uk_53 + 360971919*uk_54 + 125296038*uk_55 + 83530692*uk_56 + 366938397*uk_57 + 647362863*uk_58 + 125296038*uk_59 + 123*uk_6 + 693295273*uk_60 + 240647946*uk_61 + 160431964*uk_62 + 704754699*uk_63 + 1243347721*uk_64 + 240647946*uk_65 + 83530692*uk_66 + 55687128*uk_67 + 244625598*uk_68 + 431575242*uk_69 + 217*uk_7 + 83530692*uk_70 + 37124752*uk_71 + 163083732*uk_72 + 287716828*uk_73 + 55687128*uk_74 + 716403537*uk_75 + 1263898923*uk_76 + 244625598*uk_77 + 2229805417*uk_78 + 431575242*uk_79 + 42*uk_8 + 83530692*uk_80 + 250047*uk_81 + 480249*uk_82 + 166698*uk_83 + 111132*uk_84 + 488187*uk_85 + 861273*uk_86 + 166698*uk_87 + 922383*uk_88 + 320166*uk_89 + 2242306609*uk_9 + 213444*uk_90 + 937629*uk_91 + 1654191*uk_92 + 320166*uk_93 + 111132*uk_94 + 74088*uk_95 + 325458*uk_96 + 574182*uk_97 + 111132*uk_98 + 49392*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 189000*uk_100 + 328104*uk_101 + 182952*uk_102 + 984375*uk_103 + 1708875*uk_104 + 952875*uk_105 + 2966607*uk_106 + 1654191*uk_107 + 922383*uk_108 + 1092727*uk_109 + 4877359*uk_11 + 1283689*uk_110 + 254616*uk_111 + 1326125*uk_112 + 2302153*uk_113 + 1283689*uk_114 + 1508023*uk_115 + 299112*uk_116 + 1557875*uk_117 + 2704471*uk_118 + 1508023*uk_119 + 5729713*uk_12 + 59328*uk_120 + 309000*uk_121 + 536424*uk_122 + 299112*uk_123 + 1609375*uk_124 + 2793875*uk_125 + 1557875*uk_126 + 4850167*uk_127 + 2704471*uk_128 + 1508023*uk_129 + 1136472*uk_13 + 1771561*uk_130 + 351384*uk_131 + 1830125*uk_132 + 3177097*uk_133 + 1771561*uk_134 + 69696*uk_135 + 363000*uk_136 + 630168*uk_137 + 351384*uk_138 + 1890625*uk_139 + 5919125*uk_14 + 3282125*uk_140 + 1830125*uk_141 + 5697769*uk_142 + 3177097*uk_143 + 1771561*uk_144 + 13824*uk_145 + 72000*uk_146 + 124992*uk_147 + 69696*uk_148 + 375000*uk_149 + 10275601*uk_15 + 651000*uk_150 + 363000*uk_151 + 1130136*uk_152 + 630168*uk_153 + 351384*uk_154 + 1953125*uk_155 + 3390625*uk_156 + 1890625*uk_157 + 5886125*uk_158 + 3282125*uk_159 + 5729713*uk_16 + 1830125*uk_160 + 10218313*uk_161 + 5697769*uk_162 + 3177097*uk_163 + 1771561*uk_164 + 3969*uk_17 + 6489*uk_18 + 7623*uk_19 + 63*uk_2 + 1512*uk_20 + 7875*uk_21 + 13671*uk_22 + 7623*uk_23 + 10609*uk_24 + 12463*uk_25 + 2472*uk_26 + 12875*uk_27 + 22351*uk_28 + 12463*uk_29 + 103*uk_3 + 14641*uk_30 + 2904*uk_31 + 15125*uk_32 + 26257*uk_33 + 14641*uk_34 + 576*uk_35 + 3000*uk_36 + 5208*uk_37 + 2904*uk_38 + 15625*uk_39 + 121*uk_4 + 27125*uk_40 + 15125*uk_41 + 47089*uk_42 + 26257*uk_43 + 14641*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 230957580727*uk_47 + 271319099689*uk_48 + 53815358616*uk_49 + 24*uk_5 + 280288326125*uk_50 + 486580534153*uk_51 + 271319099689*uk_52 + 187944057*uk_53 + 307273617*uk_54 + 360971919*uk_55 + 71597736*uk_56 + 372904875*uk_57 + 647362863*uk_58 + 360971919*uk_59 + 125*uk_6 + 502367977*uk_60 + 590160439*uk_61 + 117056616*uk_62 + 609669875*uk_63 + 1058386903*uk_64 + 590160439*uk_65 + 693295273*uk_66 + 137513112*uk_67 + 716214125*uk_68 + 1243347721*uk_69 + 217*uk_7 + 693295273*uk_70 + 27275328*uk_71 + 142059000*uk_72 + 246614424*uk_73 + 137513112*uk_74 + 739890625*uk_75 + 1284450125*uk_76 + 716214125*uk_77 + 2229805417*uk_78 + 1243347721*uk_79 + 121*uk_8 + 693295273*uk_80 + 250047*uk_81 + 408807*uk_82 + 480249*uk_83 + 95256*uk_84 + 496125*uk_85 + 861273*uk_86 + 480249*uk_87 + 668367*uk_88 + 785169*uk_89 + 2242306609*uk_9 + 155736*uk_90 + 811125*uk_91 + 1408113*uk_92 + 785169*uk_93 + 922383*uk_94 + 182952*uk_95 + 952875*uk_96 + 1654191*uk_97 + 922383*uk_98 + 36288*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 192024*uk_100 + 328104*uk_101 + 155736*uk_102 + 1016127*uk_103 + 1736217*uk_104 + 824103*uk_105 + 2966607*uk_106 + 1408113*uk_107 + 668367*uk_108 + 1295029*uk_109 + 5161477*uk_11 + 1223743*uk_110 + 285144*uk_111 + 1508887*uk_112 + 2578177*uk_113 + 1223743*uk_114 + 1156381*uk_115 + 269448*uk_116 + 1425829*uk_117 + 2436259*uk_118 + 1156381*uk_119 + 4877359*uk_12 + 62784*uk_120 + 332232*uk_121 + 567672*uk_122 + 269448*uk_123 + 1758061*uk_124 + 3003931*uk_125 + 1425829*uk_126 + 5132701*uk_127 + 2436259*uk_128 + 1156381*uk_129 + 1136472*uk_13 + 1092727*uk_130 + 254616*uk_131 + 1347343*uk_132 + 2302153*uk_133 + 1092727*uk_134 + 59328*uk_135 + 313944*uk_136 + 536424*uk_137 + 254616*uk_138 + 1661287*uk_139 + 6013831*uk_14 + 2838577*uk_140 + 1347343*uk_141 + 4850167*uk_142 + 2302153*uk_143 + 1092727*uk_144 + 13824*uk_145 + 73152*uk_146 + 124992*uk_147 + 59328*uk_148 + 387096*uk_149 + 10275601*uk_15 + 661416*uk_150 + 313944*uk_151 + 1130136*uk_152 + 536424*uk_153 + 254616*uk_154 + 2048383*uk_155 + 3499993*uk_156 + 1661287*uk_157 + 5980303*uk_158 + 2838577*uk_159 + 4877359*uk_16 + 1347343*uk_160 + 10218313*uk_161 + 4850167*uk_162 + 2302153*uk_163 + 1092727*uk_164 + 3969*uk_17 + 6867*uk_18 + 6489*uk_19 + 63*uk_2 + 1512*uk_20 + 8001*uk_21 + 13671*uk_22 + 6489*uk_23 + 11881*uk_24 + 11227*uk_25 + 2616*uk_26 + 13843*uk_27 + 23653*uk_28 + 11227*uk_29 + 109*uk_3 + 10609*uk_30 + 2472*uk_31 + 13081*uk_32 + 22351*uk_33 + 10609*uk_34 + 576*uk_35 + 3048*uk_36 + 5208*uk_37 + 2472*uk_38 + 16129*uk_39 + 103*uk_4 + 27559*uk_40 + 13081*uk_41 + 47089*uk_42 + 22351*uk_43 + 10609*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 244411420381*uk_47 + 230957580727*uk_48 + 53815358616*uk_49 + 24*uk_5 + 284772939343*uk_50 + 486580534153*uk_51 + 230957580727*uk_52 + 187944057*uk_53 + 325173051*uk_54 + 307273617*uk_55 + 71597736*uk_56 + 378871353*uk_57 + 647362863*uk_58 + 307273617*uk_59 + 127*uk_6 + 562600993*uk_60 + 531632131*uk_61 + 123875448*uk_62 + 655507579*uk_63 + 1120040509*uk_64 + 531632131*uk_65 + 502367977*uk_66 + 117056616*uk_67 + 619424593*uk_68 + 1058386903*uk_69 + 217*uk_7 + 502367977*uk_70 + 27275328*uk_71 + 144331944*uk_72 + 246614424*uk_73 + 117056616*uk_74 + 763756537*uk_75 + 1305001327*uk_76 + 619424593*uk_77 + 2229805417*uk_78 + 1058386903*uk_79 + 103*uk_8 + 502367977*uk_80 + 250047*uk_81 + 432621*uk_82 + 408807*uk_83 + 95256*uk_84 + 504063*uk_85 + 861273*uk_86 + 408807*uk_87 + 748503*uk_88 + 707301*uk_89 + 2242306609*uk_9 + 164808*uk_90 + 872109*uk_91 + 1490139*uk_92 + 707301*uk_93 + 668367*uk_94 + 155736*uk_95 + 824103*uk_96 + 1408113*uk_97 + 668367*uk_98 + 36288*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 162540*uk_100 + 273420*uk_101 + 137340*uk_102 + 1048383*uk_103 + 1763559*uk_104 + 885843*uk_105 + 2966607*uk_106 + 1490139*uk_107 + 748503*uk_108 + 1000*uk_109 + 473530*uk_11 + 10900*uk_110 + 2000*uk_111 + 12900*uk_112 + 21700*uk_113 + 10900*uk_114 + 118810*uk_115 + 21800*uk_116 + 140610*uk_117 + 236530*uk_118 + 118810*uk_119 + 5161477*uk_12 + 4000*uk_120 + 25800*uk_121 + 43400*uk_122 + 21800*uk_123 + 166410*uk_124 + 279930*uk_125 + 140610*uk_126 + 470890*uk_127 + 236530*uk_128 + 118810*uk_129 + 947060*uk_13 + 1295029*uk_130 + 237620*uk_131 + 1532649*uk_132 + 2578177*uk_133 + 1295029*uk_134 + 43600*uk_135 + 281220*uk_136 + 473060*uk_137 + 237620*uk_138 + 1813869*uk_139 + 6108537*uk_14 + 3051237*uk_140 + 1532649*uk_141 + 5132701*uk_142 + 2578177*uk_143 + 1295029*uk_144 + 8000*uk_145 + 51600*uk_146 + 86800*uk_147 + 43600*uk_148 + 332820*uk_149 + 10275601*uk_15 + 559860*uk_150 + 281220*uk_151 + 941780*uk_152 + 473060*uk_153 + 237620*uk_154 + 2146689*uk_155 + 3611097*uk_156 + 1813869*uk_157 + 6074481*uk_158 + 3051237*uk_159 + 5161477*uk_16 + 1532649*uk_160 + 10218313*uk_161 + 5132701*uk_162 + 2578177*uk_163 + 1295029*uk_164 + 3969*uk_17 + 630*uk_18 + 6867*uk_19 + 63*uk_2 + 1260*uk_20 + 8127*uk_21 + 13671*uk_22 + 6867*uk_23 + 100*uk_24 + 1090*uk_25 + 200*uk_26 + 1290*uk_27 + 2170*uk_28 + 1090*uk_29 + 10*uk_3 + 11881*uk_30 + 2180*uk_31 + 14061*uk_32 + 23653*uk_33 + 11881*uk_34 + 400*uk_35 + 2580*uk_36 + 4340*uk_37 + 2180*uk_38 + 16641*uk_39 + 109*uk_4 + 27993*uk_40 + 14061*uk_41 + 47089*uk_42 + 23653*uk_43 + 11881*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 22423066090*uk_47 + 244411420381*uk_48 + 44846132180*uk_49 + 20*uk_5 + 289257552561*uk_50 + 486580534153*uk_51 + 244411420381*uk_52 + 187944057*uk_53 + 29832390*uk_54 + 325173051*uk_55 + 59664780*uk_56 + 384837831*uk_57 + 647362863*uk_58 + 325173051*uk_59 + 129*uk_6 + 4735300*uk_60 + 51614770*uk_61 + 9470600*uk_62 + 61085370*uk_63 + 102756010*uk_64 + 51614770*uk_65 + 562600993*uk_66 + 103229540*uk_67 + 665830533*uk_68 + 1120040509*uk_69 + 217*uk_7 + 562600993*uk_70 + 18941200*uk_71 + 122170740*uk_72 + 205512020*uk_73 + 103229540*uk_74 + 788001273*uk_75 + 1325552529*uk_76 + 665830533*uk_77 + 2229805417*uk_78 + 1120040509*uk_79 + 109*uk_8 + 562600993*uk_80 + 250047*uk_81 + 39690*uk_82 + 432621*uk_83 + 79380*uk_84 + 512001*uk_85 + 861273*uk_86 + 432621*uk_87 + 6300*uk_88 + 68670*uk_89 + 2242306609*uk_9 + 12600*uk_90 + 81270*uk_91 + 136710*uk_92 + 68670*uk_93 + 748503*uk_94 + 137340*uk_95 + 885843*uk_96 + 1490139*uk_97 + 748503*uk_98 + 25200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 198072*uk_100 + 328104*uk_101 + 15120*uk_102 + 1081143*uk_103 + 1790901*uk_104 + 82530*uk_105 + 2966607*uk_106 + 136710*uk_107 + 6300*uk_108 + 238328*uk_109 + 2935886*uk_11 + 38440*uk_110 + 92256*uk_111 + 503564*uk_112 + 834148*uk_113 + 38440*uk_114 + 6200*uk_115 + 14880*uk_116 + 81220*uk_117 + 134540*uk_118 + 6200*uk_119 + 473530*uk_12 + 35712*uk_120 + 194928*uk_121 + 322896*uk_122 + 14880*uk_123 + 1063982*uk_124 + 1762474*uk_125 + 81220*uk_126 + 2919518*uk_127 + 134540*uk_128 + 6200*uk_129 + 1136472*uk_13 + 1000*uk_130 + 2400*uk_131 + 13100*uk_132 + 21700*uk_133 + 1000*uk_134 + 5760*uk_135 + 31440*uk_136 + 52080*uk_137 + 2400*uk_138 + 171610*uk_139 + 6203243*uk_14 + 284270*uk_140 + 13100*uk_141 + 470890*uk_142 + 21700*uk_143 + 1000*uk_144 + 13824*uk_145 + 75456*uk_146 + 124992*uk_147 + 5760*uk_148 + 411864*uk_149 + 10275601*uk_15 + 682248*uk_150 + 31440*uk_151 + 1130136*uk_152 + 52080*uk_153 + 2400*uk_154 + 2248091*uk_155 + 3723937*uk_156 + 171610*uk_157 + 6168659*uk_158 + 284270*uk_159 + 473530*uk_16 + 13100*uk_160 + 10218313*uk_161 + 470890*uk_162 + 21700*uk_163 + 1000*uk_164 + 3969*uk_17 + 3906*uk_18 + 630*uk_19 + 63*uk_2 + 1512*uk_20 + 8253*uk_21 + 13671*uk_22 + 630*uk_23 + 3844*uk_24 + 620*uk_25 + 1488*uk_26 + 8122*uk_27 + 13454*uk_28 + 620*uk_29 + 62*uk_3 + 100*uk_30 + 240*uk_31 + 1310*uk_32 + 2170*uk_33 + 100*uk_34 + 576*uk_35 + 3144*uk_36 + 5208*uk_37 + 240*uk_38 + 17161*uk_39 + 10*uk_4 + 28427*uk_40 + 1310*uk_41 + 47089*uk_42 + 2170*uk_43 + 100*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 139023009758*uk_47 + 22423066090*uk_48 + 53815358616*uk_49 + 24*uk_5 + 293742165779*uk_50 + 486580534153*uk_51 + 22423066090*uk_52 + 187944057*uk_53 + 184960818*uk_54 + 29832390*uk_55 + 71597736*uk_56 + 390804309*uk_57 + 647362863*uk_58 + 29832390*uk_59 + 131*uk_6 + 182024932*uk_60 + 29358860*uk_61 + 70461264*uk_62 + 384601066*uk_63 + 637087262*uk_64 + 29358860*uk_65 + 4735300*uk_66 + 11364720*uk_67 + 62032430*uk_68 + 102756010*uk_69 + 217*uk_7 + 4735300*uk_70 + 27275328*uk_71 + 148877832*uk_72 + 246614424*uk_73 + 11364720*uk_74 + 812624833*uk_75 + 1346103731*uk_76 + 62032430*uk_77 + 2229805417*uk_78 + 102756010*uk_79 + 10*uk_8 + 4735300*uk_80 + 250047*uk_81 + 246078*uk_82 + 39690*uk_83 + 95256*uk_84 + 519939*uk_85 + 861273*uk_86 + 39690*uk_87 + 242172*uk_88 + 39060*uk_89 + 2242306609*uk_9 + 93744*uk_90 + 511686*uk_91 + 847602*uk_92 + 39060*uk_93 + 6300*uk_94 + 15120*uk_95 + 82530*uk_96 + 136710*uk_97 + 6300*uk_98 + 36288*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 167580*uk_100 + 273420*uk_101 + 78120*uk_102 + 1114407*uk_103 + 1818243*uk_104 + 519498*uk_105 + 2966607*uk_106 + 847602*uk_107 + 242172*uk_108 + 125*uk_109 + 236765*uk_11 + 1550*uk_110 + 500*uk_111 + 3325*uk_112 + 5425*uk_113 + 1550*uk_114 + 19220*uk_115 + 6200*uk_116 + 41230*uk_117 + 67270*uk_118 + 19220*uk_119 + 2935886*uk_12 + 2000*uk_120 + 13300*uk_121 + 21700*uk_122 + 6200*uk_123 + 88445*uk_124 + 144305*uk_125 + 41230*uk_126 + 235445*uk_127 + 67270*uk_128 + 19220*uk_129 + 947060*uk_13 + 238328*uk_130 + 76880*uk_131 + 511252*uk_132 + 834148*uk_133 + 238328*uk_134 + 24800*uk_135 + 164920*uk_136 + 269080*uk_137 + 76880*uk_138 + 1096718*uk_139 + 6297949*uk_14 + 1789382*uk_140 + 511252*uk_141 + 2919518*uk_142 + 834148*uk_143 + 238328*uk_144 + 8000*uk_145 + 53200*uk_146 + 86800*uk_147 + 24800*uk_148 + 353780*uk_149 + 10275601*uk_15 + 577220*uk_150 + 164920*uk_151 + 941780*uk_152 + 269080*uk_153 + 76880*uk_154 + 2352637*uk_155 + 3838513*uk_156 + 1096718*uk_157 + 6262837*uk_158 + 1789382*uk_159 + 2935886*uk_16 + 511252*uk_160 + 10218313*uk_161 + 2919518*uk_162 + 834148*uk_163 + 238328*uk_164 + 3969*uk_17 + 315*uk_18 + 3906*uk_19 + 63*uk_2 + 1260*uk_20 + 8379*uk_21 + 13671*uk_22 + 3906*uk_23 + 25*uk_24 + 310*uk_25 + 100*uk_26 + 665*uk_27 + 1085*uk_28 + 310*uk_29 + 5*uk_3 + 3844*uk_30 + 1240*uk_31 + 8246*uk_32 + 13454*uk_33 + 3844*uk_34 + 400*uk_35 + 2660*uk_36 + 4340*uk_37 + 1240*uk_38 + 17689*uk_39 + 62*uk_4 + 28861*uk_40 + 8246*uk_41 + 47089*uk_42 + 13454*uk_43 + 3844*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 11211533045*uk_47 + 139023009758*uk_48 + 44846132180*uk_49 + 20*uk_5 + 298226778997*uk_50 + 486580534153*uk_51 + 139023009758*uk_52 + 187944057*uk_53 + 14916195*uk_54 + 184960818*uk_55 + 59664780*uk_56 + 396770787*uk_57 + 647362863*uk_58 + 184960818*uk_59 + 133*uk_6 + 1183825*uk_60 + 14679430*uk_61 + 4735300*uk_62 + 31489745*uk_63 + 51378005*uk_64 + 14679430*uk_65 + 182024932*uk_66 + 58717720*uk_67 + 390472838*uk_68 + 637087262*uk_69 + 217*uk_7 + 182024932*uk_70 + 18941200*uk_71 + 125958980*uk_72 + 205512020*uk_73 + 58717720*uk_74 + 837627217*uk_75 + 1366654933*uk_76 + 390472838*uk_77 + 2229805417*uk_78 + 637087262*uk_79 + 62*uk_8 + 182024932*uk_80 + 250047*uk_81 + 19845*uk_82 + 246078*uk_83 + 79380*uk_84 + 527877*uk_85 + 861273*uk_86 + 246078*uk_87 + 1575*uk_88 + 19530*uk_89 + 2242306609*uk_9 + 6300*uk_90 + 41895*uk_91 + 68355*uk_92 + 19530*uk_93 + 242172*uk_94 + 78120*uk_95 + 519498*uk_96 + 847602*uk_97 + 242172*uk_98 + 25200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 204120*uk_100 + 328104*uk_101 + 7560*uk_102 + 1148175*uk_103 + 1845585*uk_104 + 42525*uk_105 + 2966607*uk_106 + 68355*uk_107 + 1575*uk_108 + 1092727*uk_109 + 4877359*uk_11 + 53045*uk_110 + 254616*uk_111 + 1432215*uk_112 + 2302153*uk_113 + 53045*uk_114 + 2575*uk_115 + 12360*uk_116 + 69525*uk_117 + 111755*uk_118 + 2575*uk_119 + 236765*uk_12 + 59328*uk_120 + 333720*uk_121 + 536424*uk_122 + 12360*uk_123 + 1877175*uk_124 + 3017385*uk_125 + 69525*uk_126 + 4850167*uk_127 + 111755*uk_128 + 2575*uk_129 + 1136472*uk_13 + 125*uk_130 + 600*uk_131 + 3375*uk_132 + 5425*uk_133 + 125*uk_134 + 2880*uk_135 + 16200*uk_136 + 26040*uk_137 + 600*uk_138 + 91125*uk_139 + 6392655*uk_14 + 146475*uk_140 + 3375*uk_141 + 235445*uk_142 + 5425*uk_143 + 125*uk_144 + 13824*uk_145 + 77760*uk_146 + 124992*uk_147 + 2880*uk_148 + 437400*uk_149 + 10275601*uk_15 + 703080*uk_150 + 16200*uk_151 + 1130136*uk_152 + 26040*uk_153 + 600*uk_154 + 2460375*uk_155 + 3954825*uk_156 + 91125*uk_157 + 6357015*uk_158 + 146475*uk_159 + 236765*uk_16 + 3375*uk_160 + 10218313*uk_161 + 235445*uk_162 + 5425*uk_163 + 125*uk_164 + 3969*uk_17 + 6489*uk_18 + 315*uk_19 + 63*uk_2 + 1512*uk_20 + 8505*uk_21 + 13671*uk_22 + 315*uk_23 + 10609*uk_24 + 515*uk_25 + 2472*uk_26 + 13905*uk_27 + 22351*uk_28 + 515*uk_29 + 103*uk_3 + 25*uk_30 + 120*uk_31 + 675*uk_32 + 1085*uk_33 + 25*uk_34 + 576*uk_35 + 3240*uk_36 + 5208*uk_37 + 120*uk_38 + 18225*uk_39 + 5*uk_4 + 29295*uk_40 + 675*uk_41 + 47089*uk_42 + 1085*uk_43 + 25*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 230957580727*uk_47 + 11211533045*uk_48 + 53815358616*uk_49 + 24*uk_5 + 302711392215*uk_50 + 486580534153*uk_51 + 11211533045*uk_52 + 187944057*uk_53 + 307273617*uk_54 + 14916195*uk_55 + 71597736*uk_56 + 402737265*uk_57 + 647362863*uk_58 + 14916195*uk_59 + 135*uk_6 + 502367977*uk_60 + 24386795*uk_61 + 117056616*uk_62 + 658443465*uk_63 + 1058386903*uk_64 + 24386795*uk_65 + 1183825*uk_66 + 5682360*uk_67 + 31963275*uk_68 + 51378005*uk_69 + 217*uk_7 + 1183825*uk_70 + 27275328*uk_71 + 153423720*uk_72 + 246614424*uk_73 + 5682360*uk_74 + 863008425*uk_75 + 1387206135*uk_76 + 31963275*uk_77 + 2229805417*uk_78 + 51378005*uk_79 + 5*uk_8 + 1183825*uk_80 + 250047*uk_81 + 408807*uk_82 + 19845*uk_83 + 95256*uk_84 + 535815*uk_85 + 861273*uk_86 + 19845*uk_87 + 668367*uk_88 + 32445*uk_89 + 2242306609*uk_9 + 155736*uk_90 + 876015*uk_91 + 1408113*uk_92 + 32445*uk_93 + 1575*uk_94 + 7560*uk_95 + 42525*uk_96 + 68355*uk_97 + 1575*uk_98 + 36288*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 172620*uk_100 + 273420*uk_101 + 129780*uk_102 + 1182447*uk_103 + 1872927*uk_104 + 888993*uk_105 + 2966607*uk_106 + 1408113*uk_107 + 668367*uk_108 + 681472*uk_109 + 4167064*uk_11 + 797632*uk_110 + 154880*uk_111 + 1060928*uk_112 + 1680448*uk_113 + 797632*uk_114 + 933592*uk_115 + 181280*uk_116 + 1241768*uk_117 + 1966888*uk_118 + 933592*uk_119 + 4877359*uk_12 + 35200*uk_120 + 241120*uk_121 + 381920*uk_122 + 181280*uk_123 + 1651672*uk_124 + 2616152*uk_125 + 1241768*uk_126 + 4143832*uk_127 + 1966888*uk_128 + 933592*uk_129 + 947060*uk_13 + 1092727*uk_130 + 212180*uk_131 + 1453433*uk_132 + 2302153*uk_133 + 1092727*uk_134 + 41200*uk_135 + 282220*uk_136 + 447020*uk_137 + 212180*uk_138 + 1933207*uk_139 + 6487361*uk_14 + 3062087*uk_140 + 1453433*uk_141 + 4850167*uk_142 + 2302153*uk_143 + 1092727*uk_144 + 8000*uk_145 + 54800*uk_146 + 86800*uk_147 + 41200*uk_148 + 375380*uk_149 + 10275601*uk_15 + 594580*uk_150 + 282220*uk_151 + 941780*uk_152 + 447020*uk_153 + 212180*uk_154 + 2571353*uk_155 + 4072873*uk_156 + 1933207*uk_157 + 6451193*uk_158 + 3062087*uk_159 + 4877359*uk_16 + 1453433*uk_160 + 10218313*uk_161 + 4850167*uk_162 + 2302153*uk_163 + 1092727*uk_164 + 3969*uk_17 + 5544*uk_18 + 6489*uk_19 + 63*uk_2 + 1260*uk_20 + 8631*uk_21 + 13671*uk_22 + 6489*uk_23 + 7744*uk_24 + 9064*uk_25 + 1760*uk_26 + 12056*uk_27 + 19096*uk_28 + 9064*uk_29 + 88*uk_3 + 10609*uk_30 + 2060*uk_31 + 14111*uk_32 + 22351*uk_33 + 10609*uk_34 + 400*uk_35 + 2740*uk_36 + 4340*uk_37 + 2060*uk_38 + 18769*uk_39 + 103*uk_4 + 29729*uk_40 + 14111*uk_41 + 47089*uk_42 + 22351*uk_43 + 10609*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 197322981592*uk_47 + 230957580727*uk_48 + 44846132180*uk_49 + 20*uk_5 + 307196005433*uk_50 + 486580534153*uk_51 + 230957580727*uk_52 + 187944057*uk_53 + 262525032*uk_54 + 307273617*uk_55 + 59664780*uk_56 + 408703743*uk_57 + 647362863*uk_58 + 307273617*uk_59 + 137*uk_6 + 366701632*uk_60 + 429207592*uk_61 + 83341280*uk_62 + 570887768*uk_63 + 904252888*uk_64 + 429207592*uk_65 + 502367977*uk_66 + 97547180*uk_67 + 668198183*uk_68 + 1058386903*uk_69 + 217*uk_7 + 502367977*uk_70 + 18941200*uk_71 + 129747220*uk_72 + 205512020*uk_73 + 97547180*uk_74 + 888768457*uk_75 + 1407757337*uk_76 + 668198183*uk_77 + 2229805417*uk_78 + 1058386903*uk_79 + 103*uk_8 + 502367977*uk_80 + 250047*uk_81 + 349272*uk_82 + 408807*uk_83 + 79380*uk_84 + 543753*uk_85 + 861273*uk_86 + 408807*uk_87 + 487872*uk_88 + 571032*uk_89 + 2242306609*uk_9 + 110880*uk_90 + 759528*uk_91 + 1203048*uk_92 + 571032*uk_93 + 668367*uk_94 + 129780*uk_95 + 888993*uk_96 + 1408113*uk_97 + 668367*uk_98 + 25200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 175140*uk_100 + 273420*uk_101 + 110880*uk_102 + 1217223*uk_103 + 1900269*uk_104 + 770616*uk_105 + 2966607*uk_106 + 1203048*uk_107 + 487872*uk_108 + 804357*uk_109 + 4403829*uk_11 + 761112*uk_110 + 172980*uk_111 + 1202211*uk_112 + 1876833*uk_113 + 761112*uk_114 + 720192*uk_115 + 163680*uk_116 + 1137576*uk_117 + 1775928*uk_118 + 720192*uk_119 + 4167064*uk_12 + 37200*uk_120 + 258540*uk_121 + 403620*uk_122 + 163680*uk_123 + 1796853*uk_124 + 2805159*uk_125 + 1137576*uk_126 + 4379277*uk_127 + 1775928*uk_128 + 720192*uk_129 + 947060*uk_13 + 681472*uk_130 + 154880*uk_131 + 1076416*uk_132 + 1680448*uk_133 + 681472*uk_134 + 35200*uk_135 + 244640*uk_136 + 381920*uk_137 + 154880*uk_138 + 1700248*uk_139 + 6582067*uk_14 + 2654344*uk_140 + 1076416*uk_141 + 4143832*uk_142 + 1680448*uk_143 + 681472*uk_144 + 8000*uk_145 + 55600*uk_146 + 86800*uk_147 + 35200*uk_148 + 386420*uk_149 + 10275601*uk_15 + 603260*uk_150 + 244640*uk_151 + 941780*uk_152 + 381920*uk_153 + 154880*uk_154 + 2685619*uk_155 + 4192657*uk_156 + 1700248*uk_157 + 6545371*uk_158 + 2654344*uk_159 + 4167064*uk_16 + 1076416*uk_160 + 10218313*uk_161 + 4143832*uk_162 + 1680448*uk_163 + 681472*uk_164 + 3969*uk_17 + 5859*uk_18 + 5544*uk_19 + 63*uk_2 + 1260*uk_20 + 8757*uk_21 + 13671*uk_22 + 5544*uk_23 + 8649*uk_24 + 8184*uk_25 + 1860*uk_26 + 12927*uk_27 + 20181*uk_28 + 8184*uk_29 + 93*uk_3 + 7744*uk_30 + 1760*uk_31 + 12232*uk_32 + 19096*uk_33 + 7744*uk_34 + 400*uk_35 + 2780*uk_36 + 4340*uk_37 + 1760*uk_38 + 19321*uk_39 + 88*uk_4 + 30163*uk_40 + 12232*uk_41 + 47089*uk_42 + 19096*uk_43 + 7744*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 208534514637*uk_47 + 197322981592*uk_48 + 44846132180*uk_49 + 20*uk_5 + 311680618651*uk_50 + 486580534153*uk_51 + 197322981592*uk_52 + 187944057*uk_53 + 277441227*uk_54 + 262525032*uk_55 + 59664780*uk_56 + 414670221*uk_57 + 647362863*uk_58 + 262525032*uk_59 + 139*uk_6 + 409556097*uk_60 + 387536952*uk_61 + 88076580*uk_62 + 612132231*uk_63 + 955630893*uk_64 + 387536952*uk_65 + 366701632*uk_66 + 83341280*uk_67 + 579221896*uk_68 + 904252888*uk_69 + 217*uk_7 + 366701632*uk_70 + 18941200*uk_71 + 131641340*uk_72 + 205512020*uk_73 + 83341280*uk_74 + 914907313*uk_75 + 1428308539*uk_76 + 579221896*uk_77 + 2229805417*uk_78 + 904252888*uk_79 + 88*uk_8 + 366701632*uk_80 + 250047*uk_81 + 369117*uk_82 + 349272*uk_83 + 79380*uk_84 + 551691*uk_85 + 861273*uk_86 + 349272*uk_87 + 544887*uk_88 + 515592*uk_89 + 2242306609*uk_9 + 117180*uk_90 + 814401*uk_91 + 1271403*uk_92 + 515592*uk_93 + 487872*uk_94 + 110880*uk_95 + 770616*uk_96 + 1203048*uk_97 + 487872*uk_98 + 25200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 177660*uk_100 + 273420*uk_101 + 117180*uk_102 + 1252503*uk_103 + 1927611*uk_104 + 826119*uk_105 + 2966607*uk_106 + 1271403*uk_107 + 544887*uk_108 + 1643032*uk_109 + 5587654*uk_11 + 1294932*uk_110 + 278480*uk_111 + 1963284*uk_112 + 3021508*uk_113 + 1294932*uk_114 + 1020582*uk_115 + 219480*uk_116 + 1547334*uk_117 + 2381358*uk_118 + 1020582*uk_119 + 4403829*uk_12 + 47200*uk_120 + 332760*uk_121 + 512120*uk_122 + 219480*uk_123 + 2345958*uk_124 + 3610446*uk_125 + 1547334*uk_126 + 5556502*uk_127 + 2381358*uk_128 + 1020582*uk_129 + 947060*uk_13 + 804357*uk_130 + 172980*uk_131 + 1219509*uk_132 + 1876833*uk_133 + 804357*uk_134 + 37200*uk_135 + 262260*uk_136 + 403620*uk_137 + 172980*uk_138 + 1848933*uk_139 + 6676773*uk_14 + 2845521*uk_140 + 1219509*uk_141 + 4379277*uk_142 + 1876833*uk_143 + 804357*uk_144 + 8000*uk_145 + 56400*uk_146 + 86800*uk_147 + 37200*uk_148 + 397620*uk_149 + 10275601*uk_15 + 611940*uk_150 + 262260*uk_151 + 941780*uk_152 + 403620*uk_153 + 172980*uk_154 + 2803221*uk_155 + 4314177*uk_156 + 1848933*uk_157 + 6639549*uk_158 + 2845521*uk_159 + 4403829*uk_16 + 1219509*uk_160 + 10218313*uk_161 + 4379277*uk_162 + 1876833*uk_163 + 804357*uk_164 + 3969*uk_17 + 7434*uk_18 + 5859*uk_19 + 63*uk_2 + 1260*uk_20 + 8883*uk_21 + 13671*uk_22 + 5859*uk_23 + 13924*uk_24 + 10974*uk_25 + 2360*uk_26 + 16638*uk_27 + 25606*uk_28 + 10974*uk_29 + 118*uk_3 + 8649*uk_30 + 1860*uk_31 + 13113*uk_32 + 20181*uk_33 + 8649*uk_34 + 400*uk_35 + 2820*uk_36 + 4340*uk_37 + 1860*uk_38 + 19881*uk_39 + 93*uk_4 + 30597*uk_40 + 13113*uk_41 + 47089*uk_42 + 20181*uk_43 + 8649*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 264592179862*uk_47 + 208534514637*uk_48 + 44846132180*uk_49 + 20*uk_5 + 316165231869*uk_50 + 486580534153*uk_51 + 208534514637*uk_52 + 187944057*uk_53 + 352022202*uk_54 + 277441227*uk_55 + 59664780*uk_56 + 420636699*uk_57 + 647362863*uk_58 + 277441227*uk_59 + 141*uk_6 + 659343172*uk_60 + 519651822*uk_61 + 111753080*uk_62 + 787859214*uk_63 + 1212520918*uk_64 + 519651822*uk_65 + 409556097*uk_66 + 88076580*uk_67 + 620939889*uk_68 + 955630893*uk_69 + 217*uk_7 + 409556097*uk_70 + 18941200*uk_71 + 133535460*uk_72 + 205512020*uk_73 + 88076580*uk_74 + 941424993*uk_75 + 1448859741*uk_76 + 620939889*uk_77 + 2229805417*uk_78 + 955630893*uk_79 + 93*uk_8 + 409556097*uk_80 + 250047*uk_81 + 468342*uk_82 + 369117*uk_83 + 79380*uk_84 + 559629*uk_85 + 861273*uk_86 + 369117*uk_87 + 877212*uk_88 + 691362*uk_89 + 2242306609*uk_9 + 148680*uk_90 + 1048194*uk_91 + 1613178*uk_92 + 691362*uk_93 + 544887*uk_94 + 117180*uk_95 + 826119*uk_96 + 1271403*uk_97 + 544887*uk_98 + 25200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 144144*uk_100 + 218736*uk_101 + 118944*uk_102 + 1288287*uk_103 + 1954953*uk_104 + 1063062*uk_105 + 2966607*uk_106 + 1613178*uk_107 + 877212*uk_108 + 8000*uk_109 + 947060*uk_11 + 47200*uk_110 + 6400*uk_111 + 57200*uk_112 + 86800*uk_113 + 47200*uk_114 + 278480*uk_115 + 37760*uk_116 + 337480*uk_117 + 512120*uk_118 + 278480*uk_119 + 5587654*uk_12 + 5120*uk_120 + 45760*uk_121 + 69440*uk_122 + 37760*uk_123 + 408980*uk_124 + 620620*uk_125 + 337480*uk_126 + 941780*uk_127 + 512120*uk_128 + 278480*uk_129 + 757648*uk_13 + 1643032*uk_130 + 222784*uk_131 + 1991132*uk_132 + 3021508*uk_133 + 1643032*uk_134 + 30208*uk_135 + 269984*uk_136 + 409696*uk_137 + 222784*uk_138 + 2412982*uk_139 + 6771479*uk_14 + 3661658*uk_140 + 1991132*uk_141 + 5556502*uk_142 + 3021508*uk_143 + 1643032*uk_144 + 4096*uk_145 + 36608*uk_146 + 55552*uk_147 + 30208*uk_148 + 327184*uk_149 + 10275601*uk_15 + 496496*uk_150 + 269984*uk_151 + 753424*uk_152 + 409696*uk_153 + 222784*uk_154 + 2924207*uk_155 + 4437433*uk_156 + 2412982*uk_157 + 6733727*uk_158 + 3661658*uk_159 + 5587654*uk_16 + 1991132*uk_160 + 10218313*uk_161 + 5556502*uk_162 + 3021508*uk_163 + 1643032*uk_164 + 3969*uk_17 + 1260*uk_18 + 7434*uk_19 + 63*uk_2 + 1008*uk_20 + 9009*uk_21 + 13671*uk_22 + 7434*uk_23 + 400*uk_24 + 2360*uk_25 + 320*uk_26 + 2860*uk_27 + 4340*uk_28 + 2360*uk_29 + 20*uk_3 + 13924*uk_30 + 1888*uk_31 + 16874*uk_32 + 25606*uk_33 + 13924*uk_34 + 256*uk_35 + 2288*uk_36 + 3472*uk_37 + 1888*uk_38 + 20449*uk_39 + 118*uk_4 + 31031*uk_40 + 16874*uk_41 + 47089*uk_42 + 25606*uk_43 + 13924*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 44846132180*uk_47 + 264592179862*uk_48 + 35876905744*uk_49 + 16*uk_5 + 320649845087*uk_50 + 486580534153*uk_51 + 264592179862*uk_52 + 187944057*uk_53 + 59664780*uk_54 + 352022202*uk_55 + 47731824*uk_56 + 426603177*uk_57 + 647362863*uk_58 + 352022202*uk_59 + 143*uk_6 + 18941200*uk_60 + 111753080*uk_61 + 15152960*uk_62 + 135429580*uk_63 + 205512020*uk_64 + 111753080*uk_65 + 659343172*uk_66 + 89402464*uk_67 + 799034522*uk_68 + 1212520918*uk_69 + 217*uk_7 + 659343172*uk_70 + 12122368*uk_71 + 108343664*uk_72 + 164409616*uk_73 + 89402464*uk_74 + 968321497*uk_75 + 1469410943*uk_76 + 799034522*uk_77 + 2229805417*uk_78 + 1212520918*uk_79 + 118*uk_8 + 659343172*uk_80 + 250047*uk_81 + 79380*uk_82 + 468342*uk_83 + 63504*uk_84 + 567567*uk_85 + 861273*uk_86 + 468342*uk_87 + 25200*uk_88 + 148680*uk_89 + 2242306609*uk_9 + 20160*uk_90 + 180180*uk_91 + 273420*uk_92 + 148680*uk_93 + 877212*uk_94 + 118944*uk_95 + 1063062*uk_96 + 1613178*uk_97 + 877212*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 182700*uk_100 + 273420*uk_101 + 25200*uk_102 + 1324575*uk_103 + 1982295*uk_104 + 182700*uk_105 + 2966607*uk_106 + 273420*uk_107 + 25200*uk_108 + 571787*uk_109 + 3930299*uk_11 + 137780*uk_110 + 137780*uk_111 + 998905*uk_112 + 1494913*uk_113 + 137780*uk_114 + 33200*uk_115 + 33200*uk_116 + 240700*uk_117 + 360220*uk_118 + 33200*uk_119 + 947060*uk_12 + 33200*uk_120 + 240700*uk_121 + 360220*uk_122 + 33200*uk_123 + 1745075*uk_124 + 2611595*uk_125 + 240700*uk_126 + 3908387*uk_127 + 360220*uk_128 + 33200*uk_129 + 947060*uk_13 + 8000*uk_130 + 8000*uk_131 + 58000*uk_132 + 86800*uk_133 + 8000*uk_134 + 8000*uk_135 + 58000*uk_136 + 86800*uk_137 + 8000*uk_138 + 420500*uk_139 + 6866185*uk_14 + 629300*uk_140 + 58000*uk_141 + 941780*uk_142 + 86800*uk_143 + 8000*uk_144 + 8000*uk_145 + 58000*uk_146 + 86800*uk_147 + 8000*uk_148 + 420500*uk_149 + 10275601*uk_15 + 629300*uk_150 + 58000*uk_151 + 941780*uk_152 + 86800*uk_153 + 8000*uk_154 + 3048625*uk_155 + 4562425*uk_156 + 420500*uk_157 + 6827905*uk_158 + 629300*uk_159 + 947060*uk_16 + 58000*uk_160 + 10218313*uk_161 + 941780*uk_162 + 86800*uk_163 + 8000*uk_164 + 3969*uk_17 + 5229*uk_18 + 1260*uk_19 + 63*uk_2 + 1260*uk_20 + 9135*uk_21 + 13671*uk_22 + 1260*uk_23 + 6889*uk_24 + 1660*uk_25 + 1660*uk_26 + 12035*uk_27 + 18011*uk_28 + 1660*uk_29 + 83*uk_3 + 400*uk_30 + 400*uk_31 + 2900*uk_32 + 4340*uk_33 + 400*uk_34 + 400*uk_35 + 2900*uk_36 + 4340*uk_37 + 400*uk_38 + 21025*uk_39 + 20*uk_4 + 31465*uk_40 + 2900*uk_41 + 47089*uk_42 + 4340*uk_43 + 400*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 186111448547*uk_47 + 44846132180*uk_48 + 44846132180*uk_49 + 20*uk_5 + 325134458305*uk_50 + 486580534153*uk_51 + 44846132180*uk_52 + 187944057*uk_53 + 247608837*uk_54 + 59664780*uk_55 + 59664780*uk_56 + 432569655*uk_57 + 647362863*uk_58 + 59664780*uk_59 + 145*uk_6 + 326214817*uk_60 + 78605980*uk_61 + 78605980*uk_62 + 569893355*uk_63 + 852874883*uk_64 + 78605980*uk_65 + 18941200*uk_66 + 18941200*uk_67 + 137323700*uk_68 + 205512020*uk_69 + 217*uk_7 + 18941200*uk_70 + 18941200*uk_71 + 137323700*uk_72 + 205512020*uk_73 + 18941200*uk_74 + 995596825*uk_75 + 1489962145*uk_76 + 137323700*uk_77 + 2229805417*uk_78 + 205512020*uk_79 + 20*uk_8 + 18941200*uk_80 + 250047*uk_81 + 329427*uk_82 + 79380*uk_83 + 79380*uk_84 + 575505*uk_85 + 861273*uk_86 + 79380*uk_87 + 434007*uk_88 + 104580*uk_89 + 2242306609*uk_9 + 104580*uk_90 + 758205*uk_91 + 1134693*uk_92 + 104580*uk_93 + 25200*uk_94 + 25200*uk_95 + 182700*uk_96 + 273420*uk_97 + 25200*uk_98 + 25200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 148176*uk_100 + 218736*uk_101 + 83664*uk_102 + 1361367*uk_103 + 2009637*uk_104 + 768663*uk_105 + 2966607*uk_106 + 1134693*uk_107 + 434007*uk_108 + 6859*uk_109 + 899707*uk_11 + 29963*uk_110 + 5776*uk_111 + 53067*uk_112 + 78337*uk_113 + 29963*uk_114 + 130891*uk_115 + 25232*uk_116 + 231819*uk_117 + 342209*uk_118 + 130891*uk_119 + 3930299*uk_12 + 4864*uk_120 + 44688*uk_121 + 65968*uk_122 + 25232*uk_123 + 410571*uk_124 + 606081*uk_125 + 231819*uk_126 + 894691*uk_127 + 342209*uk_128 + 130891*uk_129 + 757648*uk_13 + 571787*uk_130 + 110224*uk_131 + 1012683*uk_132 + 1494913*uk_133 + 571787*uk_134 + 21248*uk_135 + 195216*uk_136 + 288176*uk_137 + 110224*uk_138 + 1793547*uk_139 + 6960891*uk_14 + 2647617*uk_140 + 1012683*uk_141 + 3908387*uk_142 + 1494913*uk_143 + 571787*uk_144 + 4096*uk_145 + 37632*uk_146 + 55552*uk_147 + 21248*uk_148 + 345744*uk_149 + 10275601*uk_15 + 510384*uk_150 + 195216*uk_151 + 753424*uk_152 + 288176*uk_153 + 110224*uk_154 + 3176523*uk_155 + 4689153*uk_156 + 1793547*uk_157 + 6922083*uk_158 + 2647617*uk_159 + 3930299*uk_16 + 1012683*uk_160 + 10218313*uk_161 + 3908387*uk_162 + 1494913*uk_163 + 571787*uk_164 + 3969*uk_17 + 1197*uk_18 + 5229*uk_19 + 63*uk_2 + 1008*uk_20 + 9261*uk_21 + 13671*uk_22 + 5229*uk_23 + 361*uk_24 + 1577*uk_25 + 304*uk_26 + 2793*uk_27 + 4123*uk_28 + 1577*uk_29 + 19*uk_3 + 6889*uk_30 + 1328*uk_31 + 12201*uk_32 + 18011*uk_33 + 6889*uk_34 + 256*uk_35 + 2352*uk_36 + 3472*uk_37 + 1328*uk_38 + 21609*uk_39 + 83*uk_4 + 31899*uk_40 + 12201*uk_41 + 47089*uk_42 + 18011*uk_43 + 6889*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 42603825571*uk_47 + 186111448547*uk_48 + 35876905744*uk_49 + 16*uk_5 + 329619071523*uk_50 + 486580534153*uk_51 + 186111448547*uk_52 + 187944057*uk_53 + 56681541*uk_54 + 247608837*uk_55 + 47731824*uk_56 + 438536133*uk_57 + 647362863*uk_58 + 247608837*uk_59 + 147*uk_6 + 17094433*uk_60 + 74675681*uk_61 + 14395312*uk_62 + 132256929*uk_63 + 195236419*uk_64 + 74675681*uk_65 + 326214817*uk_66 + 62884784*uk_67 + 577753953*uk_68 + 852874883*uk_69 + 217*uk_7 + 326214817*uk_70 + 12122368*uk_71 + 111374256*uk_72 + 164409616*uk_73 + 62884784*uk_74 + 1023250977*uk_75 + 1510513347*uk_76 + 577753953*uk_77 + 2229805417*uk_78 + 852874883*uk_79 + 83*uk_8 + 326214817*uk_80 + 250047*uk_81 + 75411*uk_82 + 329427*uk_83 + 63504*uk_84 + 583443*uk_85 + 861273*uk_86 + 329427*uk_87 + 22743*uk_88 + 99351*uk_89 + 2242306609*uk_9 + 19152*uk_90 + 175959*uk_91 + 259749*uk_92 + 99351*uk_93 + 434007*uk_94 + 83664*uk_95 + 768663*uk_96 + 1134693*uk_97 + 434007*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 187740*uk_100 + 273420*uk_101 + 23940*uk_102 + 1398663*uk_103 + 2036979*uk_104 + 178353*uk_105 + 2966607*uk_106 + 259749*uk_107 + 22743*uk_108 + 1728000*uk_109 + 5682360*uk_11 + 273600*uk_110 + 288000*uk_111 + 2145600*uk_112 + 3124800*uk_113 + 273600*uk_114 + 43320*uk_115 + 45600*uk_116 + 339720*uk_117 + 494760*uk_118 + 43320*uk_119 + 899707*uk_12 + 48000*uk_120 + 357600*uk_121 + 520800*uk_122 + 45600*uk_123 + 2664120*uk_124 + 3879960*uk_125 + 339720*uk_126 + 5650680*uk_127 + 494760*uk_128 + 43320*uk_129 + 947060*uk_13 + 6859*uk_130 + 7220*uk_131 + 53789*uk_132 + 78337*uk_133 + 6859*uk_134 + 7600*uk_135 + 56620*uk_136 + 82460*uk_137 + 7220*uk_138 + 421819*uk_139 + 7055597*uk_14 + 614327*uk_140 + 53789*uk_141 + 894691*uk_142 + 78337*uk_143 + 6859*uk_144 + 8000*uk_145 + 59600*uk_146 + 86800*uk_147 + 7600*uk_148 + 444020*uk_149 + 10275601*uk_15 + 646660*uk_150 + 56620*uk_151 + 941780*uk_152 + 82460*uk_153 + 7220*uk_154 + 3307949*uk_155 + 4817617*uk_156 + 421819*uk_157 + 7016261*uk_158 + 614327*uk_159 + 899707*uk_16 + 53789*uk_160 + 10218313*uk_161 + 894691*uk_162 + 78337*uk_163 + 6859*uk_164 + 3969*uk_17 + 7560*uk_18 + 1197*uk_19 + 63*uk_2 + 1260*uk_20 + 9387*uk_21 + 13671*uk_22 + 1197*uk_23 + 14400*uk_24 + 2280*uk_25 + 2400*uk_26 + 17880*uk_27 + 26040*uk_28 + 2280*uk_29 + 120*uk_3 + 361*uk_30 + 380*uk_31 + 2831*uk_32 + 4123*uk_33 + 361*uk_34 + 400*uk_35 + 2980*uk_36 + 4340*uk_37 + 380*uk_38 + 22201*uk_39 + 19*uk_4 + 32333*uk_40 + 2831*uk_41 + 47089*uk_42 + 4123*uk_43 + 361*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 269076793080*uk_47 + 42603825571*uk_48 + 44846132180*uk_49 + 20*uk_5 + 334103684741*uk_50 + 486580534153*uk_51 + 42603825571*uk_52 + 187944057*uk_53 + 357988680*uk_54 + 56681541*uk_55 + 59664780*uk_56 + 444502611*uk_57 + 647362863*uk_58 + 56681541*uk_59 + 149*uk_6 + 681883200*uk_60 + 107964840*uk_61 + 113647200*uk_62 + 846671640*uk_63 + 1233072120*uk_64 + 107964840*uk_65 + 17094433*uk_66 + 17994140*uk_67 + 134056343*uk_68 + 195236419*uk_69 + 217*uk_7 + 17094433*uk_70 + 18941200*uk_71 + 141111940*uk_72 + 205512020*uk_73 + 17994140*uk_74 + 1051283953*uk_75 + 1531064549*uk_76 + 134056343*uk_77 + 2229805417*uk_78 + 195236419*uk_79 + 19*uk_8 + 17094433*uk_80 + 250047*uk_81 + 476280*uk_82 + 75411*uk_83 + 79380*uk_84 + 591381*uk_85 + 861273*uk_86 + 75411*uk_87 + 907200*uk_88 + 143640*uk_89 + 2242306609*uk_9 + 151200*uk_90 + 1126440*uk_91 + 1640520*uk_92 + 143640*uk_93 + 22743*uk_94 + 23940*uk_95 + 178353*uk_96 + 259749*uk_97 + 22743*uk_98 + 25200*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 152208*uk_100 + 218736*uk_101 + 120960*uk_102 + 1436463*uk_103 + 2064321*uk_104 + 1141560*uk_105 + 2966607*uk_106 + 1640520*uk_107 + 907200*uk_108 + 729000*uk_109 + 4261770*uk_11 + 972000*uk_110 + 129600*uk_111 + 1223100*uk_112 + 1757700*uk_113 + 972000*uk_114 + 1296000*uk_115 + 172800*uk_116 + 1630800*uk_117 + 2343600*uk_118 + 1296000*uk_119 + 5682360*uk_12 + 23040*uk_120 + 217440*uk_121 + 312480*uk_122 + 172800*uk_123 + 2052090*uk_124 + 2949030*uk_125 + 1630800*uk_126 + 4238010*uk_127 + 2343600*uk_128 + 1296000*uk_129 + 757648*uk_13 + 1728000*uk_130 + 230400*uk_131 + 2174400*uk_132 + 3124800*uk_133 + 1728000*uk_134 + 30720*uk_135 + 289920*uk_136 + 416640*uk_137 + 230400*uk_138 + 2736120*uk_139 + 7150303*uk_14 + 3932040*uk_140 + 2174400*uk_141 + 5650680*uk_142 + 3124800*uk_143 + 1728000*uk_144 + 4096*uk_145 + 38656*uk_146 + 55552*uk_147 + 30720*uk_148 + 364816*uk_149 + 10275601*uk_15 + 524272*uk_150 + 289920*uk_151 + 753424*uk_152 + 416640*uk_153 + 230400*uk_154 + 3442951*uk_155 + 4947817*uk_156 + 2736120*uk_157 + 7110439*uk_158 + 3932040*uk_159 + 5682360*uk_16 + 2174400*uk_160 + 10218313*uk_161 + 5650680*uk_162 + 3124800*uk_163 + 1728000*uk_164 + 3969*uk_17 + 5670*uk_18 + 7560*uk_19 + 63*uk_2 + 1008*uk_20 + 9513*uk_21 + 13671*uk_22 + 7560*uk_23 + 8100*uk_24 + 10800*uk_25 + 1440*uk_26 + 13590*uk_27 + 19530*uk_28 + 10800*uk_29 + 90*uk_3 + 14400*uk_30 + 1920*uk_31 + 18120*uk_32 + 26040*uk_33 + 14400*uk_34 + 256*uk_35 + 2416*uk_36 + 3472*uk_37 + 1920*uk_38 + 22801*uk_39 + 120*uk_4 + 32767*uk_40 + 18120*uk_41 + 47089*uk_42 + 26040*uk_43 + 14400*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 201807594810*uk_47 + 269076793080*uk_48 + 35876905744*uk_49 + 16*uk_5 + 338588297959*uk_50 + 486580534153*uk_51 + 269076793080*uk_52 + 187944057*uk_53 + 268491510*uk_54 + 357988680*uk_55 + 47731824*uk_56 + 450469089*uk_57 + 647362863*uk_58 + 357988680*uk_59 + 151*uk_6 + 383559300*uk_60 + 511412400*uk_61 + 68188320*uk_62 + 643527270*uk_63 + 924804090*uk_64 + 511412400*uk_65 + 681883200*uk_66 + 90917760*uk_67 + 858036360*uk_68 + 1233072120*uk_69 + 217*uk_7 + 681883200*uk_70 + 12122368*uk_71 + 114404848*uk_72 + 164409616*uk_73 + 90917760*uk_74 + 1079695753*uk_75 + 1551615751*uk_76 + 858036360*uk_77 + 2229805417*uk_78 + 1233072120*uk_79 + 120*uk_8 + 681883200*uk_80 + 250047*uk_81 + 357210*uk_82 + 476280*uk_83 + 63504*uk_84 + 599319*uk_85 + 861273*uk_86 + 476280*uk_87 + 510300*uk_88 + 680400*uk_89 + 2242306609*uk_9 + 90720*uk_90 + 856170*uk_91 + 1230390*uk_92 + 680400*uk_93 + 907200*uk_94 + 120960*uk_95 + 1141560*uk_96 + 1640520*uk_97 + 907200*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 154224*uk_100 + 218736*uk_101 + 90720*uk_102 + 1474767*uk_103 + 2091663*uk_104 + 867510*uk_105 + 2966607*uk_106 + 1230390*uk_107 + 510300*uk_108 + 438976*uk_109 + 3598828*uk_11 + 519840*uk_110 + 92416*uk_111 + 883728*uk_112 + 1253392*uk_113 + 519840*uk_114 + 615600*uk_115 + 109440*uk_116 + 1046520*uk_117 + 1484280*uk_118 + 615600*uk_119 + 4261770*uk_12 + 19456*uk_120 + 186048*uk_121 + 263872*uk_122 + 109440*uk_123 + 1779084*uk_124 + 2523276*uk_125 + 1046520*uk_126 + 3578764*uk_127 + 1484280*uk_128 + 615600*uk_129 + 757648*uk_13 + 729000*uk_130 + 129600*uk_131 + 1239300*uk_132 + 1757700*uk_133 + 729000*uk_134 + 23040*uk_135 + 220320*uk_136 + 312480*uk_137 + 129600*uk_138 + 2106810*uk_139 + 7245009*uk_14 + 2988090*uk_140 + 1239300*uk_141 + 4238010*uk_142 + 1757700*uk_143 + 729000*uk_144 + 4096*uk_145 + 39168*uk_146 + 55552*uk_147 + 23040*uk_148 + 374544*uk_149 + 10275601*uk_15 + 531216*uk_150 + 220320*uk_151 + 753424*uk_152 + 312480*uk_153 + 129600*uk_154 + 3581577*uk_155 + 5079753*uk_156 + 2106810*uk_157 + 7204617*uk_158 + 2988090*uk_159 + 4261770*uk_16 + 1239300*uk_160 + 10218313*uk_161 + 4238010*uk_162 + 1757700*uk_163 + 729000*uk_164 + 3969*uk_17 + 4788*uk_18 + 5670*uk_19 + 63*uk_2 + 1008*uk_20 + 9639*uk_21 + 13671*uk_22 + 5670*uk_23 + 5776*uk_24 + 6840*uk_25 + 1216*uk_26 + 11628*uk_27 + 16492*uk_28 + 6840*uk_29 + 76*uk_3 + 8100*uk_30 + 1440*uk_31 + 13770*uk_32 + 19530*uk_33 + 8100*uk_34 + 256*uk_35 + 2448*uk_36 + 3472*uk_37 + 1440*uk_38 + 23409*uk_39 + 90*uk_4 + 33201*uk_40 + 13770*uk_41 + 47089*uk_42 + 19530*uk_43 + 8100*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 170415302284*uk_47 + 201807594810*uk_48 + 35876905744*uk_49 + 16*uk_5 + 343072911177*uk_50 + 486580534153*uk_51 + 201807594810*uk_52 + 187944057*uk_53 + 226726164*uk_54 + 268491510*uk_55 + 47731824*uk_56 + 456435567*uk_57 + 647362863*uk_58 + 268491510*uk_59 + 153*uk_6 + 273510928*uk_60 + 323894520*uk_61 + 57581248*uk_62 + 550620684*uk_63 + 780945676*uk_64 + 323894520*uk_65 + 383559300*uk_66 + 68188320*uk_67 + 652050810*uk_68 + 924804090*uk_69 + 217*uk_7 + 383559300*uk_70 + 12122368*uk_71 + 115920144*uk_72 + 164409616*uk_73 + 68188320*uk_74 + 1108486377*uk_75 + 1572166953*uk_76 + 652050810*uk_77 + 2229805417*uk_78 + 924804090*uk_79 + 90*uk_8 + 383559300*uk_80 + 250047*uk_81 + 301644*uk_82 + 357210*uk_83 + 63504*uk_84 + 607257*uk_85 + 861273*uk_86 + 357210*uk_87 + 363888*uk_88 + 430920*uk_89 + 2242306609*uk_9 + 76608*uk_90 + 732564*uk_91 + 1038996*uk_92 + 430920*uk_93 + 510300*uk_94 + 90720*uk_95 + 867510*uk_96 + 1230390*uk_97 + 510300*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 156240*uk_100 + 218736*uk_101 + 76608*uk_102 + 1513575*uk_103 + 2119005*uk_104 + 742140*uk_105 + 2966607*uk_106 + 1038996*uk_107 + 363888*uk_108 + 474552*uk_109 + 3693534*uk_11 + 462384*uk_110 + 97344*uk_111 + 943020*uk_112 + 1320228*uk_113 + 462384*uk_114 + 450528*uk_115 + 94848*uk_116 + 918840*uk_117 + 1286376*uk_118 + 450528*uk_119 + 3598828*uk_12 + 19968*uk_120 + 193440*uk_121 + 270816*uk_122 + 94848*uk_123 + 1873950*uk_124 + 2623530*uk_125 + 918840*uk_126 + 3672942*uk_127 + 1286376*uk_128 + 450528*uk_129 + 757648*uk_13 + 438976*uk_130 + 92416*uk_131 + 895280*uk_132 + 1253392*uk_133 + 438976*uk_134 + 19456*uk_135 + 188480*uk_136 + 263872*uk_137 + 92416*uk_138 + 1825900*uk_139 + 7339715*uk_14 + 2556260*uk_140 + 895280*uk_141 + 3578764*uk_142 + 1253392*uk_143 + 438976*uk_144 + 4096*uk_145 + 39680*uk_146 + 55552*uk_147 + 19456*uk_148 + 384400*uk_149 + 10275601*uk_15 + 538160*uk_150 + 188480*uk_151 + 753424*uk_152 + 263872*uk_153 + 92416*uk_154 + 3723875*uk_155 + 5213425*uk_156 + 1825900*uk_157 + 7298795*uk_158 + 2556260*uk_159 + 3598828*uk_16 + 895280*uk_160 + 10218313*uk_161 + 3578764*uk_162 + 1253392*uk_163 + 438976*uk_164 + 3969*uk_17 + 4914*uk_18 + 4788*uk_19 + 63*uk_2 + 1008*uk_20 + 9765*uk_21 + 13671*uk_22 + 4788*uk_23 + 6084*uk_24 + 5928*uk_25 + 1248*uk_26 + 12090*uk_27 + 16926*uk_28 + 5928*uk_29 + 78*uk_3 + 5776*uk_30 + 1216*uk_31 + 11780*uk_32 + 16492*uk_33 + 5776*uk_34 + 256*uk_35 + 2480*uk_36 + 3472*uk_37 + 1216*uk_38 + 24025*uk_39 + 76*uk_4 + 33635*uk_40 + 11780*uk_41 + 47089*uk_42 + 16492*uk_43 + 5776*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 174899915502*uk_47 + 170415302284*uk_48 + 35876905744*uk_49 + 16*uk_5 + 347557524395*uk_50 + 486580534153*uk_51 + 170415302284*uk_52 + 187944057*uk_53 + 232692642*uk_54 + 226726164*uk_55 + 47731824*uk_56 + 462402045*uk_57 + 647362863*uk_58 + 226726164*uk_59 + 155*uk_6 + 288095652*uk_60 + 280708584*uk_61 + 59096544*uk_62 + 572497770*uk_63 + 801496878*uk_64 + 280708584*uk_65 + 273510928*uk_66 + 57581248*uk_67 + 557818340*uk_68 + 780945676*uk_69 + 217*uk_7 + 273510928*uk_70 + 12122368*uk_71 + 117435440*uk_72 + 164409616*uk_73 + 57581248*uk_74 + 1137655825*uk_75 + 1592718155*uk_76 + 557818340*uk_77 + 2229805417*uk_78 + 780945676*uk_79 + 76*uk_8 + 273510928*uk_80 + 250047*uk_81 + 309582*uk_82 + 301644*uk_83 + 63504*uk_84 + 615195*uk_85 + 861273*uk_86 + 301644*uk_87 + 383292*uk_88 + 373464*uk_89 + 2242306609*uk_9 + 78624*uk_90 + 761670*uk_91 + 1066338*uk_92 + 373464*uk_93 + 363888*uk_94 + 76608*uk_95 + 742140*uk_96 + 1038996*uk_97 + 363888*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 158256*uk_100 + 218736*uk_101 + 78624*uk_102 + 1552887*uk_103 + 2146347*uk_104 + 771498*uk_105 + 2966607*uk_106 + 1066338*uk_107 + 383292*uk_108 + 884736*uk_109 + 4545888*uk_11 + 718848*uk_110 + 147456*uk_111 + 1446912*uk_112 + 1999872*uk_113 + 718848*uk_114 + 584064*uk_115 + 119808*uk_116 + 1175616*uk_117 + 1624896*uk_118 + 584064*uk_119 + 3693534*uk_12 + 24576*uk_120 + 241152*uk_121 + 333312*uk_122 + 119808*uk_123 + 2366304*uk_124 + 3270624*uk_125 + 1175616*uk_126 + 4520544*uk_127 + 1624896*uk_128 + 584064*uk_129 + 757648*uk_13 + 474552*uk_130 + 97344*uk_131 + 955188*uk_132 + 1320228*uk_133 + 474552*uk_134 + 19968*uk_135 + 195936*uk_136 + 270816*uk_137 + 97344*uk_138 + 1922622*uk_139 + 7434421*uk_14 + 2657382*uk_140 + 955188*uk_141 + 3672942*uk_142 + 1320228*uk_143 + 474552*uk_144 + 4096*uk_145 + 40192*uk_146 + 55552*uk_147 + 19968*uk_148 + 394384*uk_149 + 10275601*uk_15 + 545104*uk_150 + 195936*uk_151 + 753424*uk_152 + 270816*uk_153 + 97344*uk_154 + 3869893*uk_155 + 5348833*uk_156 + 1922622*uk_157 + 7392973*uk_158 + 2657382*uk_159 + 3693534*uk_16 + 955188*uk_160 + 10218313*uk_161 + 3672942*uk_162 + 1320228*uk_163 + 474552*uk_164 + 3969*uk_17 + 6048*uk_18 + 4914*uk_19 + 63*uk_2 + 1008*uk_20 + 9891*uk_21 + 13671*uk_22 + 4914*uk_23 + 9216*uk_24 + 7488*uk_25 + 1536*uk_26 + 15072*uk_27 + 20832*uk_28 + 7488*uk_29 + 96*uk_3 + 6084*uk_30 + 1248*uk_31 + 12246*uk_32 + 16926*uk_33 + 6084*uk_34 + 256*uk_35 + 2512*uk_36 + 3472*uk_37 + 1248*uk_38 + 24649*uk_39 + 78*uk_4 + 34069*uk_40 + 12246*uk_41 + 47089*uk_42 + 16926*uk_43 + 6084*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 215261434464*uk_47 + 174899915502*uk_48 + 35876905744*uk_49 + 16*uk_5 + 352042137613*uk_50 + 486580534153*uk_51 + 174899915502*uk_52 + 187944057*uk_53 + 286390944*uk_54 + 232692642*uk_55 + 47731824*uk_56 + 468368523*uk_57 + 647362863*uk_58 + 232692642*uk_59 + 157*uk_6 + 436405248*uk_60 + 354579264*uk_61 + 72734208*uk_62 + 713704416*uk_63 + 986457696*uk_64 + 354579264*uk_65 + 288095652*uk_66 + 59096544*uk_67 + 579884838*uk_68 + 801496878*uk_69 + 217*uk_7 + 288095652*uk_70 + 12122368*uk_71 + 118950736*uk_72 + 164409616*uk_73 + 59096544*uk_74 + 1167204097*uk_75 + 1613269357*uk_76 + 579884838*uk_77 + 2229805417*uk_78 + 801496878*uk_79 + 78*uk_8 + 288095652*uk_80 + 250047*uk_81 + 381024*uk_82 + 309582*uk_83 + 63504*uk_84 + 623133*uk_85 + 861273*uk_86 + 309582*uk_87 + 580608*uk_88 + 471744*uk_89 + 2242306609*uk_9 + 96768*uk_90 + 949536*uk_91 + 1312416*uk_92 + 471744*uk_93 + 383292*uk_94 + 78624*uk_95 + 771498*uk_96 + 1066338*uk_97 + 383292*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 160272*uk_100 + 218736*uk_101 + 96768*uk_102 + 1592703*uk_103 + 2173689*uk_104 + 961632*uk_105 + 2966607*uk_106 + 1312416*uk_107 + 580608*uk_108 + 2197000*uk_109 + 6155890*uk_11 + 1622400*uk_110 + 270400*uk_111 + 2687100*uk_112 + 3667300*uk_113 + 1622400*uk_114 + 1198080*uk_115 + 199680*uk_116 + 1984320*uk_117 + 2708160*uk_118 + 1198080*uk_119 + 4545888*uk_12 + 33280*uk_120 + 330720*uk_121 + 451360*uk_122 + 199680*uk_123 + 3286530*uk_124 + 4485390*uk_125 + 1984320*uk_126 + 6121570*uk_127 + 2708160*uk_128 + 1198080*uk_129 + 757648*uk_13 + 884736*uk_130 + 147456*uk_131 + 1465344*uk_132 + 1999872*uk_133 + 884736*uk_134 + 24576*uk_135 + 244224*uk_136 + 333312*uk_137 + 147456*uk_138 + 2426976*uk_139 + 7529127*uk_14 + 3312288*uk_140 + 1465344*uk_141 + 4520544*uk_142 + 1999872*uk_143 + 884736*uk_144 + 4096*uk_145 + 40704*uk_146 + 55552*uk_147 + 24576*uk_148 + 404496*uk_149 + 10275601*uk_15 + 552048*uk_150 + 244224*uk_151 + 753424*uk_152 + 333312*uk_153 + 147456*uk_154 + 4019679*uk_155 + 5485977*uk_156 + 2426976*uk_157 + 7487151*uk_158 + 3312288*uk_159 + 4545888*uk_16 + 1465344*uk_160 + 10218313*uk_161 + 4520544*uk_162 + 1999872*uk_163 + 884736*uk_164 + 3969*uk_17 + 8190*uk_18 + 6048*uk_19 + 63*uk_2 + 1008*uk_20 + 10017*uk_21 + 13671*uk_22 + 6048*uk_23 + 16900*uk_24 + 12480*uk_25 + 2080*uk_26 + 20670*uk_27 + 28210*uk_28 + 12480*uk_29 + 130*uk_3 + 9216*uk_30 + 1536*uk_31 + 15264*uk_32 + 20832*uk_33 + 9216*uk_34 + 256*uk_35 + 2544*uk_36 + 3472*uk_37 + 1536*uk_38 + 25281*uk_39 + 96*uk_4 + 34503*uk_40 + 15264*uk_41 + 47089*uk_42 + 20832*uk_43 + 9216*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 291499859170*uk_47 + 215261434464*uk_48 + 35876905744*uk_49 + 16*uk_5 + 356526750831*uk_50 + 486580534153*uk_51 + 215261434464*uk_52 + 187944057*uk_53 + 387821070*uk_54 + 286390944*uk_55 + 47731824*uk_56 + 474335001*uk_57 + 647362863*uk_58 + 286390944*uk_59 + 159*uk_6 + 800265700*uk_60 + 590965440*uk_61 + 98494240*uk_62 + 978786510*uk_63 + 1335828130*uk_64 + 590965440*uk_65 + 436405248*uk_66 + 72734208*uk_67 + 722796192*uk_68 + 986457696*uk_69 + 217*uk_7 + 436405248*uk_70 + 12122368*uk_71 + 120466032*uk_72 + 164409616*uk_73 + 72734208*uk_74 + 1197131193*uk_75 + 1633820559*uk_76 + 722796192*uk_77 + 2229805417*uk_78 + 986457696*uk_79 + 96*uk_8 + 436405248*uk_80 + 250047*uk_81 + 515970*uk_82 + 381024*uk_83 + 63504*uk_84 + 631071*uk_85 + 861273*uk_86 + 381024*uk_87 + 1064700*uk_88 + 786240*uk_89 + 2242306609*uk_9 + 131040*uk_90 + 1302210*uk_91 + 1777230*uk_92 + 786240*uk_93 + 580608*uk_94 + 96768*uk_95 + 961632*uk_96 + 1312416*uk_97 + 580608*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 121716*uk_100 + 164052*uk_101 + 98280*uk_102 + 1633023*uk_103 + 2201031*uk_104 + 1318590*uk_105 + 2966607*uk_106 + 1777230*uk_107 + 1064700*uk_108 + 6859*uk_109 + 899707*uk_11 + 46930*uk_110 + 4332*uk_111 + 58121*uk_112 + 78337*uk_113 + 46930*uk_114 + 321100*uk_115 + 29640*uk_116 + 397670*uk_117 + 535990*uk_118 + 321100*uk_119 + 6155890*uk_12 + 2736*uk_120 + 36708*uk_121 + 49476*uk_122 + 29640*uk_123 + 492499*uk_124 + 663803*uk_125 + 397670*uk_126 + 894691*uk_127 + 535990*uk_128 + 321100*uk_129 + 568236*uk_13 + 2197000*uk_130 + 202800*uk_131 + 2720900*uk_132 + 3667300*uk_133 + 2197000*uk_134 + 18720*uk_135 + 251160*uk_136 + 338520*uk_137 + 202800*uk_138 + 3369730*uk_139 + 7623833*uk_14 + 4541810*uk_140 + 2720900*uk_141 + 6121570*uk_142 + 3667300*uk_143 + 2197000*uk_144 + 1728*uk_145 + 23184*uk_146 + 31248*uk_147 + 18720*uk_148 + 311052*uk_149 + 10275601*uk_15 + 419244*uk_150 + 251160*uk_151 + 565068*uk_152 + 338520*uk_153 + 202800*uk_154 + 4173281*uk_155 + 5624857*uk_156 + 3369730*uk_157 + 7581329*uk_158 + 4541810*uk_159 + 6155890*uk_16 + 2720900*uk_160 + 10218313*uk_161 + 6121570*uk_162 + 3667300*uk_163 + 2197000*uk_164 + 3969*uk_17 + 1197*uk_18 + 8190*uk_19 + 63*uk_2 + 756*uk_20 + 10143*uk_21 + 13671*uk_22 + 8190*uk_23 + 361*uk_24 + 2470*uk_25 + 228*uk_26 + 3059*uk_27 + 4123*uk_28 + 2470*uk_29 + 19*uk_3 + 16900*uk_30 + 1560*uk_31 + 20930*uk_32 + 28210*uk_33 + 16900*uk_34 + 144*uk_35 + 1932*uk_36 + 2604*uk_37 + 1560*uk_38 + 25921*uk_39 + 130*uk_4 + 34937*uk_40 + 20930*uk_41 + 47089*uk_42 + 28210*uk_43 + 16900*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 42603825571*uk_47 + 291499859170*uk_48 + 26907679308*uk_49 + 12*uk_5 + 361011364049*uk_50 + 486580534153*uk_51 + 291499859170*uk_52 + 187944057*uk_53 + 56681541*uk_54 + 387821070*uk_55 + 35798868*uk_56 + 480301479*uk_57 + 647362863*uk_58 + 387821070*uk_59 + 161*uk_6 + 17094433*uk_60 + 116961910*uk_61 + 10796484*uk_62 + 144852827*uk_63 + 195236419*uk_64 + 116961910*uk_65 + 800265700*uk_66 + 73870680*uk_67 + 991098290*uk_68 + 1335828130*uk_69 + 217*uk_7 + 800265700*uk_70 + 6818832*uk_71 + 91485996*uk_72 + 123307212*uk_73 + 73870680*uk_74 + 1227437113*uk_75 + 1654371761*uk_76 + 991098290*uk_77 + 2229805417*uk_78 + 1335828130*uk_79 + 130*uk_8 + 800265700*uk_80 + 250047*uk_81 + 75411*uk_82 + 515970*uk_83 + 47628*uk_84 + 639009*uk_85 + 861273*uk_86 + 515970*uk_87 + 22743*uk_88 + 155610*uk_89 + 2242306609*uk_9 + 14364*uk_90 + 192717*uk_91 + 259749*uk_92 + 155610*uk_93 + 1064700*uk_94 + 98280*uk_95 + 1318590*uk_96 + 1777230*uk_97 + 1064700*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 164304*uk_100 + 218736*uk_101 + 19152*uk_102 + 1673847*uk_103 + 2228373*uk_104 + 195111*uk_105 + 2966607*uk_106 + 259749*uk_107 + 22743*uk_108 + 571787*uk_109 + 3930299*uk_11 + 130891*uk_110 + 110224*uk_111 + 1122907*uk_112 + 1494913*uk_113 + 130891*uk_114 + 29963*uk_115 + 25232*uk_116 + 257051*uk_117 + 342209*uk_118 + 29963*uk_119 + 899707*uk_12 + 21248*uk_120 + 216464*uk_121 + 288176*uk_122 + 25232*uk_123 + 2205227*uk_124 + 2935793*uk_125 + 257051*uk_126 + 3908387*uk_127 + 342209*uk_128 + 29963*uk_129 + 757648*uk_13 + 6859*uk_130 + 5776*uk_131 + 58843*uk_132 + 78337*uk_133 + 6859*uk_134 + 4864*uk_135 + 49552*uk_136 + 65968*uk_137 + 5776*uk_138 + 504811*uk_139 + 7718539*uk_14 + 672049*uk_140 + 58843*uk_141 + 894691*uk_142 + 78337*uk_143 + 6859*uk_144 + 4096*uk_145 + 41728*uk_146 + 55552*uk_147 + 4864*uk_148 + 425104*uk_149 + 10275601*uk_15 + 565936*uk_150 + 49552*uk_151 + 753424*uk_152 + 65968*uk_153 + 5776*uk_154 + 4330747*uk_155 + 5765473*uk_156 + 504811*uk_157 + 7675507*uk_158 + 672049*uk_159 + 899707*uk_16 + 58843*uk_160 + 10218313*uk_161 + 894691*uk_162 + 78337*uk_163 + 6859*uk_164 + 3969*uk_17 + 5229*uk_18 + 1197*uk_19 + 63*uk_2 + 1008*uk_20 + 10269*uk_21 + 13671*uk_22 + 1197*uk_23 + 6889*uk_24 + 1577*uk_25 + 1328*uk_26 + 13529*uk_27 + 18011*uk_28 + 1577*uk_29 + 83*uk_3 + 361*uk_30 + 304*uk_31 + 3097*uk_32 + 4123*uk_33 + 361*uk_34 + 256*uk_35 + 2608*uk_36 + 3472*uk_37 + 304*uk_38 + 26569*uk_39 + 19*uk_4 + 35371*uk_40 + 3097*uk_41 + 47089*uk_42 + 4123*uk_43 + 361*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 186111448547*uk_47 + 42603825571*uk_48 + 35876905744*uk_49 + 16*uk_5 + 365495977267*uk_50 + 486580534153*uk_51 + 42603825571*uk_52 + 187944057*uk_53 + 247608837*uk_54 + 56681541*uk_55 + 47731824*uk_56 + 486267957*uk_57 + 647362863*uk_58 + 56681541*uk_59 + 163*uk_6 + 326214817*uk_60 + 74675681*uk_61 + 62884784*uk_62 + 640638737*uk_63 + 852874883*uk_64 + 74675681*uk_65 + 17094433*uk_66 + 14395312*uk_67 + 146652241*uk_68 + 195236419*uk_69 + 217*uk_7 + 17094433*uk_70 + 12122368*uk_71 + 123496624*uk_72 + 164409616*uk_73 + 14395312*uk_74 + 1258121857*uk_75 + 1674922963*uk_76 + 146652241*uk_77 + 2229805417*uk_78 + 195236419*uk_79 + 19*uk_8 + 17094433*uk_80 + 250047*uk_81 + 329427*uk_82 + 75411*uk_83 + 63504*uk_84 + 646947*uk_85 + 861273*uk_86 + 75411*uk_87 + 434007*uk_88 + 99351*uk_89 + 2242306609*uk_9 + 83664*uk_90 + 852327*uk_91 + 1134693*uk_92 + 99351*uk_93 + 22743*uk_94 + 19152*uk_95 + 195111*uk_96 + 259749*uk_97 + 22743*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 166320*uk_100 + 218736*uk_101 + 83664*uk_102 + 1715175*uk_103 + 2255715*uk_104 + 862785*uk_105 + 2966607*uk_106 + 1134693*uk_107 + 434007*uk_108 + 4330747*uk_109 + 7718539*uk_11 + 2205227*uk_110 + 425104*uk_111 + 4383885*uk_112 + 5765473*uk_113 + 2205227*uk_114 + 1122907*uk_115 + 216464*uk_116 + 2232285*uk_117 + 2935793*uk_118 + 1122907*uk_119 + 3930299*uk_12 + 41728*uk_120 + 430320*uk_121 + 565936*uk_122 + 216464*uk_123 + 4437675*uk_124 + 5836215*uk_125 + 2232285*uk_126 + 7675507*uk_127 + 2935793*uk_128 + 1122907*uk_129 + 757648*uk_13 + 571787*uk_130 + 110224*uk_131 + 1136685*uk_132 + 1494913*uk_133 + 571787*uk_134 + 21248*uk_135 + 219120*uk_136 + 288176*uk_137 + 110224*uk_138 + 2259675*uk_139 + 7813245*uk_14 + 2971815*uk_140 + 1136685*uk_141 + 3908387*uk_142 + 1494913*uk_143 + 571787*uk_144 + 4096*uk_145 + 42240*uk_146 + 55552*uk_147 + 21248*uk_148 + 435600*uk_149 + 10275601*uk_15 + 572880*uk_150 + 219120*uk_151 + 753424*uk_152 + 288176*uk_153 + 110224*uk_154 + 4492125*uk_155 + 5907825*uk_156 + 2259675*uk_157 + 7769685*uk_158 + 2971815*uk_159 + 3930299*uk_16 + 1136685*uk_160 + 10218313*uk_161 + 3908387*uk_162 + 1494913*uk_163 + 571787*uk_164 + 3969*uk_17 + 10269*uk_18 + 5229*uk_19 + 63*uk_2 + 1008*uk_20 + 10395*uk_21 + 13671*uk_22 + 5229*uk_23 + 26569*uk_24 + 13529*uk_25 + 2608*uk_26 + 26895*uk_27 + 35371*uk_28 + 13529*uk_29 + 163*uk_3 + 6889*uk_30 + 1328*uk_31 + 13695*uk_32 + 18011*uk_33 + 6889*uk_34 + 256*uk_35 + 2640*uk_36 + 3472*uk_37 + 1328*uk_38 + 27225*uk_39 + 83*uk_4 + 35805*uk_40 + 13695*uk_41 + 47089*uk_42 + 18011*uk_43 + 6889*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 365495977267*uk_47 + 186111448547*uk_48 + 35876905744*uk_49 + 16*uk_5 + 369980590485*uk_50 + 486580534153*uk_51 + 186111448547*uk_52 + 187944057*uk_53 + 486267957*uk_54 + 247608837*uk_55 + 47731824*uk_56 + 492234435*uk_57 + 647362863*uk_58 + 247608837*uk_59 + 165*uk_6 + 1258121857*uk_60 + 640638737*uk_61 + 123496624*uk_62 + 1273558935*uk_63 + 1674922963*uk_64 + 640638737*uk_65 + 326214817*uk_66 + 62884784*uk_67 + 648499335*uk_68 + 852874883*uk_69 + 217*uk_7 + 326214817*uk_70 + 12122368*uk_71 + 125011920*uk_72 + 164409616*uk_73 + 62884784*uk_74 + 1289185425*uk_75 + 1695474165*uk_76 + 648499335*uk_77 + 2229805417*uk_78 + 852874883*uk_79 + 83*uk_8 + 326214817*uk_80 + 250047*uk_81 + 646947*uk_82 + 329427*uk_83 + 63504*uk_84 + 654885*uk_85 + 861273*uk_86 + 329427*uk_87 + 1673847*uk_88 + 852327*uk_89 + 2242306609*uk_9 + 164304*uk_90 + 1694385*uk_91 + 2228373*uk_92 + 852327*uk_93 + 434007*uk_94 + 83664*uk_95 + 862785*uk_96 + 1134693*uk_97 + 434007*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 126252*uk_100 + 164052*uk_101 + 123228*uk_102 + 1757007*uk_103 + 2283057*uk_104 + 1714923*uk_105 + 2966607*uk_106 + 2228373*uk_107 + 1673847*uk_108 + 778688*uk_109 + 4356476*uk_11 + 1379632*uk_110 + 101568*uk_111 + 1413488*uk_112 + 1836688*uk_113 + 1379632*uk_114 + 2444348*uk_115 + 179952*uk_116 + 2504332*uk_117 + 3254132*uk_118 + 2444348*uk_119 + 7718539*uk_12 + 13248*uk_120 + 184368*uk_121 + 239568*uk_122 + 179952*uk_123 + 2565788*uk_124 + 3333988*uk_125 + 2504332*uk_126 + 4332188*uk_127 + 3254132*uk_128 + 2444348*uk_129 + 568236*uk_13 + 4330747*uk_130 + 318828*uk_131 + 4437023*uk_132 + 5765473*uk_133 + 4330747*uk_134 + 23472*uk_135 + 326652*uk_136 + 424452*uk_137 + 318828*uk_138 + 4545907*uk_139 + 7907951*uk_14 + 5906957*uk_140 + 4437023*uk_141 + 7675507*uk_142 + 5765473*uk_143 + 4330747*uk_144 + 1728*uk_145 + 24048*uk_146 + 31248*uk_147 + 23472*uk_148 + 334668*uk_149 + 10275601*uk_15 + 434868*uk_150 + 326652*uk_151 + 565068*uk_152 + 424452*uk_153 + 318828*uk_154 + 4657463*uk_155 + 6051913*uk_156 + 4545907*uk_157 + 7863863*uk_158 + 5906957*uk_159 + 7718539*uk_16 + 4437023*uk_160 + 10218313*uk_161 + 7675507*uk_162 + 5765473*uk_163 + 4330747*uk_164 + 3969*uk_17 + 5796*uk_18 + 10269*uk_19 + 63*uk_2 + 756*uk_20 + 10521*uk_21 + 13671*uk_22 + 10269*uk_23 + 8464*uk_24 + 14996*uk_25 + 1104*uk_26 + 15364*uk_27 + 19964*uk_28 + 14996*uk_29 + 92*uk_3 + 26569*uk_30 + 1956*uk_31 + 27221*uk_32 + 35371*uk_33 + 26569*uk_34 + 144*uk_35 + 2004*uk_36 + 2604*uk_37 + 1956*uk_38 + 27889*uk_39 + 163*uk_4 + 36239*uk_40 + 27221*uk_41 + 47089*uk_42 + 35371*uk_43 + 26569*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 206292208028*uk_47 + 365495977267*uk_48 + 26907679308*uk_49 + 12*uk_5 + 374465203703*uk_50 + 486580534153*uk_51 + 365495977267*uk_52 + 187944057*uk_53 + 274457988*uk_54 + 486267957*uk_55 + 35798868*uk_56 + 498200913*uk_57 + 647362863*uk_58 + 486267957*uk_59 + 167*uk_6 + 400795792*uk_60 + 710105588*uk_61 + 52277712*uk_62 + 727531492*uk_63 + 945355292*uk_64 + 710105588*uk_65 + 1258121857*uk_66 + 92622468*uk_67 + 1288996013*uk_68 + 1674922963*uk_69 + 217*uk_7 + 1258121857*uk_70 + 6818832*uk_71 + 94895412*uk_72 + 123307212*uk_73 + 92622468*uk_74 + 1320627817*uk_75 + 1716025367*uk_76 + 1288996013*uk_77 + 2229805417*uk_78 + 1674922963*uk_79 + 163*uk_8 + 1258121857*uk_80 + 250047*uk_81 + 365148*uk_82 + 646947*uk_83 + 47628*uk_84 + 662823*uk_85 + 861273*uk_86 + 646947*uk_87 + 533232*uk_88 + 944748*uk_89 + 2242306609*uk_9 + 69552*uk_90 + 967932*uk_91 + 1257732*uk_92 + 944748*uk_93 + 1673847*uk_94 + 123228*uk_95 + 1714923*uk_96 + 2228373*uk_97 + 1673847*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 127764*uk_100 + 164052*uk_101 + 69552*uk_102 + 1799343*uk_103 + 2310399*uk_104 + 979524*uk_105 + 2966607*uk_106 + 1257732*uk_107 + 533232*uk_108 + 35937*uk_109 + 1562649*uk_11 + 100188*uk_110 + 13068*uk_111 + 184041*uk_112 + 236313*uk_113 + 100188*uk_114 + 279312*uk_115 + 36432*uk_116 + 513084*uk_117 + 658812*uk_118 + 279312*uk_119 + 4356476*uk_12 + 4752*uk_120 + 66924*uk_121 + 85932*uk_122 + 36432*uk_123 + 942513*uk_124 + 1210209*uk_125 + 513084*uk_126 + 1553937*uk_127 + 658812*uk_128 + 279312*uk_129 + 568236*uk_13 + 778688*uk_130 + 101568*uk_131 + 1430416*uk_132 + 1836688*uk_133 + 778688*uk_134 + 13248*uk_135 + 186576*uk_136 + 239568*uk_137 + 101568*uk_138 + 2627612*uk_139 + 8002657*uk_14 + 3373916*uk_140 + 1430416*uk_141 + 4332188*uk_142 + 1836688*uk_143 + 778688*uk_144 + 1728*uk_145 + 24336*uk_146 + 31248*uk_147 + 13248*uk_148 + 342732*uk_149 + 10275601*uk_15 + 440076*uk_150 + 186576*uk_151 + 565068*uk_152 + 239568*uk_153 + 101568*uk_154 + 4826809*uk_155 + 6197737*uk_156 + 2627612*uk_157 + 7958041*uk_158 + 3373916*uk_159 + 4356476*uk_16 + 1430416*uk_160 + 10218313*uk_161 + 4332188*uk_162 + 1836688*uk_163 + 778688*uk_164 + 3969*uk_17 + 2079*uk_18 + 5796*uk_19 + 63*uk_2 + 756*uk_20 + 10647*uk_21 + 13671*uk_22 + 5796*uk_23 + 1089*uk_24 + 3036*uk_25 + 396*uk_26 + 5577*uk_27 + 7161*uk_28 + 3036*uk_29 + 33*uk_3 + 8464*uk_30 + 1104*uk_31 + 15548*uk_32 + 19964*uk_33 + 8464*uk_34 + 144*uk_35 + 2028*uk_36 + 2604*uk_37 + 1104*uk_38 + 28561*uk_39 + 92*uk_4 + 36673*uk_40 + 15548*uk_41 + 47089*uk_42 + 19964*uk_43 + 8464*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 73996118097*uk_47 + 206292208028*uk_48 + 26907679308*uk_49 + 12*uk_5 + 378949816921*uk_50 + 486580534153*uk_51 + 206292208028*uk_52 + 187944057*uk_53 + 98446887*uk_54 + 274457988*uk_55 + 35798868*uk_56 + 504167391*uk_57 + 647362863*uk_58 + 274457988*uk_59 + 169*uk_6 + 51567417*uk_60 + 143763708*uk_61 + 18751788*uk_62 + 264087681*uk_63 + 339094833*uk_64 + 143763708*uk_65 + 400795792*uk_66 + 52277712*uk_67 + 736244444*uk_68 + 945355292*uk_69 + 217*uk_7 + 400795792*uk_70 + 6818832*uk_71 + 96031884*uk_72 + 123307212*uk_73 + 52277712*uk_74 + 1352449033*uk_75 + 1736576569*uk_76 + 736244444*uk_77 + 2229805417*uk_78 + 945355292*uk_79 + 92*uk_8 + 400795792*uk_80 + 250047*uk_81 + 130977*uk_82 + 365148*uk_83 + 47628*uk_84 + 670761*uk_85 + 861273*uk_86 + 365148*uk_87 + 68607*uk_88 + 191268*uk_89 + 2242306609*uk_9 + 24948*uk_90 + 351351*uk_91 + 451143*uk_92 + 191268*uk_93 + 533232*uk_94 + 69552*uk_95 + 979524*uk_96 + 1257732*uk_97 + 533232*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 172368*uk_100 + 218736*uk_101 + 33264*uk_102 + 1842183*uk_103 + 2337741*uk_104 + 355509*uk_105 + 2966607*uk_106 + 451143*uk_107 + 68607*uk_108 + 3869893*uk_109 + 7434421*uk_11 + 813417*uk_110 + 394384*uk_111 + 4214979*uk_112 + 5348833*uk_113 + 813417*uk_114 + 170973*uk_115 + 82896*uk_116 + 885951*uk_117 + 1124277*uk_118 + 170973*uk_119 + 1562649*uk_12 + 40192*uk_120 + 429552*uk_121 + 545104*uk_122 + 82896*uk_123 + 4590837*uk_124 + 5825799*uk_125 + 885951*uk_126 + 7392973*uk_127 + 1124277*uk_128 + 170973*uk_129 + 757648*uk_13 + 35937*uk_130 + 17424*uk_131 + 186219*uk_132 + 236313*uk_133 + 35937*uk_134 + 8448*uk_135 + 90288*uk_136 + 114576*uk_137 + 17424*uk_138 + 964953*uk_139 + 8097363*uk_14 + 1224531*uk_140 + 186219*uk_141 + 1553937*uk_142 + 236313*uk_143 + 35937*uk_144 + 4096*uk_145 + 43776*uk_146 + 55552*uk_147 + 8448*uk_148 + 467856*uk_149 + 10275601*uk_15 + 593712*uk_150 + 90288*uk_151 + 753424*uk_152 + 114576*uk_153 + 17424*uk_154 + 5000211*uk_155 + 6345297*uk_156 + 964953*uk_157 + 8052219*uk_158 + 1224531*uk_159 + 1562649*uk_16 + 186219*uk_160 + 10218313*uk_161 + 1553937*uk_162 + 236313*uk_163 + 35937*uk_164 + 3969*uk_17 + 9891*uk_18 + 2079*uk_19 + 63*uk_2 + 1008*uk_20 + 10773*uk_21 + 13671*uk_22 + 2079*uk_23 + 24649*uk_24 + 5181*uk_25 + 2512*uk_26 + 26847*uk_27 + 34069*uk_28 + 5181*uk_29 + 157*uk_3 + 1089*uk_30 + 528*uk_31 + 5643*uk_32 + 7161*uk_33 + 1089*uk_34 + 256*uk_35 + 2736*uk_36 + 3472*uk_37 + 528*uk_38 + 29241*uk_39 + 33*uk_4 + 37107*uk_40 + 5643*uk_41 + 47089*uk_42 + 7161*uk_43 + 1089*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 352042137613*uk_47 + 73996118097*uk_48 + 35876905744*uk_49 + 16*uk_5 + 383434430139*uk_50 + 486580534153*uk_51 + 73996118097*uk_52 + 187944057*uk_53 + 468368523*uk_54 + 98446887*uk_55 + 47731824*uk_56 + 510133869*uk_57 + 647362863*uk_58 + 98446887*uk_59 + 171*uk_6 + 1167204097*uk_60 + 245335893*uk_61 + 118950736*uk_62 + 1271285991*uk_63 + 1613269357*uk_64 + 245335893*uk_65 + 51567417*uk_66 + 25002384*uk_67 + 267212979*uk_68 + 339094833*uk_69 + 217*uk_7 + 51567417*uk_70 + 12122368*uk_71 + 129557808*uk_72 + 164409616*uk_73 + 25002384*uk_74 + 1384649073*uk_75 + 1757127771*uk_76 + 267212979*uk_77 + 2229805417*uk_78 + 339094833*uk_79 + 33*uk_8 + 51567417*uk_80 + 250047*uk_81 + 623133*uk_82 + 130977*uk_83 + 63504*uk_84 + 678699*uk_85 + 861273*uk_86 + 130977*uk_87 + 1552887*uk_88 + 326403*uk_89 + 2242306609*uk_9 + 158256*uk_90 + 1691361*uk_91 + 2146347*uk_92 + 326403*uk_93 + 68607*uk_94 + 33264*uk_95 + 355509*uk_96 + 451143*uk_97 + 68607*uk_98 + 16128*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 130788*uk_100 + 164052*uk_101 + 118692*uk_102 + 1885527*uk_103 + 2365083*uk_104 + 1711143*uk_105 + 2966607*uk_106 + 2146347*uk_107 + 1552887*uk_108 + 1906624*uk_109 + 5871772*uk_11 + 2414032*uk_110 + 184512*uk_111 + 2660048*uk_112 + 3336592*uk_113 + 2414032*uk_114 + 3056476*uk_115 + 233616*uk_116 + 3367964*uk_117 + 4224556*uk_118 + 3056476*uk_119 + 7434421*uk_12 + 17856*uk_120 + 257424*uk_121 + 322896*uk_122 + 233616*uk_123 + 3711196*uk_124 + 4655084*uk_125 + 3367964*uk_126 + 5839036*uk_127 + 4224556*uk_128 + 3056476*uk_129 + 568236*uk_13 + 3869893*uk_130 + 295788*uk_131 + 4264277*uk_132 + 5348833*uk_133 + 3869893*uk_134 + 22608*uk_135 + 325932*uk_136 + 408828*uk_137 + 295788*uk_138 + 4698853*uk_139 + 8192069*uk_14 + 5893937*uk_140 + 4264277*uk_141 + 7392973*uk_142 + 5348833*uk_143 + 3869893*uk_144 + 1728*uk_145 + 24912*uk_146 + 31248*uk_147 + 22608*uk_148 + 359148*uk_149 + 10275601*uk_15 + 450492*uk_150 + 325932*uk_151 + 565068*uk_152 + 408828*uk_153 + 295788*uk_154 + 5177717*uk_155 + 6494593*uk_156 + 4698853*uk_157 + 8146397*uk_158 + 5893937*uk_159 + 7434421*uk_16 + 4264277*uk_160 + 10218313*uk_161 + 7392973*uk_162 + 5348833*uk_163 + 3869893*uk_164 + 3969*uk_17 + 7812*uk_18 + 9891*uk_19 + 63*uk_2 + 756*uk_20 + 10899*uk_21 + 13671*uk_22 + 9891*uk_23 + 15376*uk_24 + 19468*uk_25 + 1488*uk_26 + 21452*uk_27 + 26908*uk_28 + 19468*uk_29 + 124*uk_3 + 24649*uk_30 + 1884*uk_31 + 27161*uk_32 + 34069*uk_33 + 24649*uk_34 + 144*uk_35 + 2076*uk_36 + 2604*uk_37 + 1884*uk_38 + 29929*uk_39 + 157*uk_4 + 37541*uk_40 + 27161*uk_41 + 47089*uk_42 + 34069*uk_43 + 24649*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 278046019516*uk_47 + 352042137613*uk_48 + 26907679308*uk_49 + 12*uk_5 + 387919043357*uk_50 + 486580534153*uk_51 + 352042137613*uk_52 + 187944057*uk_53 + 369921636*uk_54 + 468368523*uk_55 + 35798868*uk_56 + 516100347*uk_57 + 647362863*uk_58 + 468368523*uk_59 + 173*uk_6 + 728099728*uk_60 + 921868204*uk_61 + 70461264*uk_62 + 1015816556*uk_63 + 1274174524*uk_64 + 921868204*uk_65 + 1167204097*uk_66 + 89213052*uk_67 + 1286154833*uk_68 + 1613269357*uk_69 + 217*uk_7 + 1167204097*uk_70 + 6818832*uk_71 + 98304828*uk_72 + 123307212*uk_73 + 89213052*uk_74 + 1417227937*uk_75 + 1777678973*uk_76 + 1286154833*uk_77 + 2229805417*uk_78 + 1613269357*uk_79 + 157*uk_8 + 1167204097*uk_80 + 250047*uk_81 + 492156*uk_82 + 623133*uk_83 + 47628*uk_84 + 686637*uk_85 + 861273*uk_86 + 623133*uk_87 + 968688*uk_88 + 1226484*uk_89 + 2242306609*uk_9 + 93744*uk_90 + 1351476*uk_91 + 1695204*uk_92 + 1226484*uk_93 + 1552887*uk_94 + 118692*uk_95 + 1711143*uk_96 + 2146347*uk_97 + 1552887*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 132300*uk_100 + 164052*uk_101 + 93744*uk_102 + 1929375*uk_103 + 2392425*uk_104 + 1367100*uk_105 + 2966607*uk_106 + 1695204*uk_107 + 968688*uk_108 + 1092727*uk_109 + 4877359*uk_11 + 1315516*uk_110 + 127308*uk_111 + 1856575*uk_112 + 2302153*uk_113 + 1315516*uk_114 + 1583728*uk_115 + 153264*uk_116 + 2235100*uk_117 + 2771524*uk_118 + 1583728*uk_119 + 5871772*uk_12 + 14832*uk_120 + 216300*uk_121 + 268212*uk_122 + 153264*uk_123 + 3154375*uk_124 + 3911425*uk_125 + 2235100*uk_126 + 4850167*uk_127 + 2771524*uk_128 + 1583728*uk_129 + 568236*uk_13 + 1906624*uk_130 + 184512*uk_131 + 2690800*uk_132 + 3336592*uk_133 + 1906624*uk_134 + 17856*uk_135 + 260400*uk_136 + 322896*uk_137 + 184512*uk_138 + 3797500*uk_139 + 8286775*uk_14 + 4708900*uk_140 + 2690800*uk_141 + 5839036*uk_142 + 3336592*uk_143 + 1906624*uk_144 + 1728*uk_145 + 25200*uk_146 + 31248*uk_147 + 17856*uk_148 + 367500*uk_149 + 10275601*uk_15 + 455700*uk_150 + 260400*uk_151 + 565068*uk_152 + 322896*uk_153 + 184512*uk_154 + 5359375*uk_155 + 6645625*uk_156 + 3797500*uk_157 + 8240575*uk_158 + 4708900*uk_159 + 5871772*uk_16 + 2690800*uk_160 + 10218313*uk_161 + 5839036*uk_162 + 3336592*uk_163 + 1906624*uk_164 + 3969*uk_17 + 6489*uk_18 + 7812*uk_19 + 63*uk_2 + 756*uk_20 + 11025*uk_21 + 13671*uk_22 + 7812*uk_23 + 10609*uk_24 + 12772*uk_25 + 1236*uk_26 + 18025*uk_27 + 22351*uk_28 + 12772*uk_29 + 103*uk_3 + 15376*uk_30 + 1488*uk_31 + 21700*uk_32 + 26908*uk_33 + 15376*uk_34 + 144*uk_35 + 2100*uk_36 + 2604*uk_37 + 1488*uk_38 + 30625*uk_39 + 124*uk_4 + 37975*uk_40 + 21700*uk_41 + 47089*uk_42 + 26908*uk_43 + 15376*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 230957580727*uk_47 + 278046019516*uk_48 + 26907679308*uk_49 + 12*uk_5 + 392403656575*uk_50 + 486580534153*uk_51 + 278046019516*uk_52 + 187944057*uk_53 + 307273617*uk_54 + 369921636*uk_55 + 35798868*uk_56 + 522066825*uk_57 + 647362863*uk_58 + 369921636*uk_59 + 175*uk_6 + 502367977*uk_60 + 604792516*uk_61 + 58528308*uk_62 + 853537825*uk_63 + 1058386903*uk_64 + 604792516*uk_65 + 728099728*uk_66 + 70461264*uk_67 + 1027560100*uk_68 + 1274174524*uk_69 + 217*uk_7 + 728099728*uk_70 + 6818832*uk_71 + 99441300*uk_72 + 123307212*uk_73 + 70461264*uk_74 + 1450185625*uk_75 + 1798230175*uk_76 + 1027560100*uk_77 + 2229805417*uk_78 + 1274174524*uk_79 + 124*uk_8 + 728099728*uk_80 + 250047*uk_81 + 408807*uk_82 + 492156*uk_83 + 47628*uk_84 + 694575*uk_85 + 861273*uk_86 + 492156*uk_87 + 668367*uk_88 + 804636*uk_89 + 2242306609*uk_9 + 77868*uk_90 + 1135575*uk_91 + 1408113*uk_92 + 804636*uk_93 + 968688*uk_94 + 93744*uk_95 + 1367100*uk_96 + 1695204*uk_97 + 968688*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 133812*uk_100 + 164052*uk_101 + 77868*uk_102 + 1973727*uk_103 + 2419767*uk_104 + 1148553*uk_105 + 2966607*uk_106 + 1408113*uk_107 + 668367*uk_108 + 830584*uk_109 + 4451182*uk_11 + 910108*uk_110 + 106032*uk_111 + 1563972*uk_112 + 1917412*uk_113 + 910108*uk_114 + 997246*uk_115 + 116184*uk_116 + 1713714*uk_117 + 2100994*uk_118 + 997246*uk_119 + 4877359*uk_12 + 13536*uk_120 + 199656*uk_121 + 244776*uk_122 + 116184*uk_123 + 2944926*uk_124 + 3610446*uk_125 + 1713714*uk_126 + 4426366*uk_127 + 2100994*uk_128 + 997246*uk_129 + 568236*uk_13 + 1092727*uk_130 + 127308*uk_131 + 1877793*uk_132 + 2302153*uk_133 + 1092727*uk_134 + 14832*uk_135 + 218772*uk_136 + 268212*uk_137 + 127308*uk_138 + 3226887*uk_139 + 8381481*uk_14 + 3956127*uk_140 + 1877793*uk_141 + 4850167*uk_142 + 2302153*uk_143 + 1092727*uk_144 + 1728*uk_145 + 25488*uk_146 + 31248*uk_147 + 14832*uk_148 + 375948*uk_149 + 10275601*uk_15 + 460908*uk_150 + 218772*uk_151 + 565068*uk_152 + 268212*uk_153 + 127308*uk_154 + 5545233*uk_155 + 6798393*uk_156 + 3226887*uk_157 + 8334753*uk_158 + 3956127*uk_159 + 4877359*uk_16 + 1877793*uk_160 + 10218313*uk_161 + 4850167*uk_162 + 2302153*uk_163 + 1092727*uk_164 + 3969*uk_17 + 5922*uk_18 + 6489*uk_19 + 63*uk_2 + 756*uk_20 + 11151*uk_21 + 13671*uk_22 + 6489*uk_23 + 8836*uk_24 + 9682*uk_25 + 1128*uk_26 + 16638*uk_27 + 20398*uk_28 + 9682*uk_29 + 94*uk_3 + 10609*uk_30 + 1236*uk_31 + 18231*uk_32 + 22351*uk_33 + 10609*uk_34 + 144*uk_35 + 2124*uk_36 + 2604*uk_37 + 1236*uk_38 + 31329*uk_39 + 103*uk_4 + 38409*uk_40 + 18231*uk_41 + 47089*uk_42 + 22351*uk_43 + 10609*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 210776821246*uk_47 + 230957580727*uk_48 + 26907679308*uk_49 + 12*uk_5 + 396888269793*uk_50 + 486580534153*uk_51 + 230957580727*uk_52 + 187944057*uk_53 + 280424466*uk_54 + 307273617*uk_55 + 35798868*uk_56 + 528033303*uk_57 + 647362863*uk_58 + 307273617*uk_59 + 177*uk_6 + 418411108*uk_60 + 458471746*uk_61 + 53414184*uk_62 + 787859214*uk_63 + 965906494*uk_64 + 458471746*uk_65 + 502367977*uk_66 + 58528308*uk_67 + 863292543*uk_68 + 1058386903*uk_69 + 217*uk_7 + 502367977*uk_70 + 6818832*uk_71 + 100577772*uk_72 + 123307212*uk_73 + 58528308*uk_74 + 1483522137*uk_75 + 1818781377*uk_76 + 863292543*uk_77 + 2229805417*uk_78 + 1058386903*uk_79 + 103*uk_8 + 502367977*uk_80 + 250047*uk_81 + 373086*uk_82 + 408807*uk_83 + 47628*uk_84 + 702513*uk_85 + 861273*uk_86 + 408807*uk_87 + 556668*uk_88 + 609966*uk_89 + 2242306609*uk_9 + 71064*uk_90 + 1048194*uk_91 + 1285074*uk_92 + 609966*uk_93 + 668367*uk_94 + 77868*uk_95 + 1148553*uk_96 + 1408113*uk_97 + 668367*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 135324*uk_100 + 164052*uk_101 + 71064*uk_102 + 2018583*uk_103 + 2447109*uk_104 + 1060038*uk_105 + 2966607*uk_106 + 1285074*uk_107 + 556668*uk_108 + 912673*uk_109 + 4593241*uk_11 + 884446*uk_110 + 112908*uk_111 + 1684211*uk_112 + 2041753*uk_113 + 884446*uk_114 + 857092*uk_115 + 109416*uk_116 + 1632122*uk_117 + 1978606*uk_118 + 857092*uk_119 + 4451182*uk_12 + 13968*uk_120 + 208356*uk_121 + 252588*uk_122 + 109416*uk_123 + 3107977*uk_124 + 3767771*uk_125 + 1632122*uk_126 + 4567633*uk_127 + 1978606*uk_128 + 857092*uk_129 + 568236*uk_13 + 830584*uk_130 + 106032*uk_131 + 1581644*uk_132 + 1917412*uk_133 + 830584*uk_134 + 13536*uk_135 + 201912*uk_136 + 244776*uk_137 + 106032*uk_138 + 3011854*uk_139 + 8476187*uk_14 + 3651242*uk_140 + 1581644*uk_141 + 4426366*uk_142 + 1917412*uk_143 + 830584*uk_144 + 1728*uk_145 + 25776*uk_146 + 31248*uk_147 + 13536*uk_148 + 384492*uk_149 + 10275601*uk_15 + 466116*uk_150 + 201912*uk_151 + 565068*uk_152 + 244776*uk_153 + 106032*uk_154 + 5735339*uk_155 + 6952897*uk_156 + 3011854*uk_157 + 8428931*uk_158 + 3651242*uk_159 + 4451182*uk_16 + 1581644*uk_160 + 10218313*uk_161 + 4426366*uk_162 + 1917412*uk_163 + 830584*uk_164 + 3969*uk_17 + 6111*uk_18 + 5922*uk_19 + 63*uk_2 + 756*uk_20 + 11277*uk_21 + 13671*uk_22 + 5922*uk_23 + 9409*uk_24 + 9118*uk_25 + 1164*uk_26 + 17363*uk_27 + 21049*uk_28 + 9118*uk_29 + 97*uk_3 + 8836*uk_30 + 1128*uk_31 + 16826*uk_32 + 20398*uk_33 + 8836*uk_34 + 144*uk_35 + 2148*uk_36 + 2604*uk_37 + 1128*uk_38 + 32041*uk_39 + 94*uk_4 + 38843*uk_40 + 16826*uk_41 + 47089*uk_42 + 20398*uk_43 + 8836*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 217503741073*uk_47 + 210776821246*uk_48 + 26907679308*uk_49 + 12*uk_5 + 401372883011*uk_50 + 486580534153*uk_51 + 210776821246*uk_52 + 187944057*uk_53 + 289374183*uk_54 + 280424466*uk_55 + 35798868*uk_56 + 533999781*uk_57 + 647362863*uk_58 + 280424466*uk_59 + 179*uk_6 + 445544377*uk_60 + 431764654*uk_61 + 55118892*uk_62 + 822190139*uk_63 + 996733297*uk_64 + 431764654*uk_65 + 418411108*uk_66 + 53414184*uk_67 + 796761578*uk_68 + 965906494*uk_69 + 217*uk_7 + 418411108*uk_70 + 6818832*uk_71 + 101714244*uk_72 + 123307212*uk_73 + 53414184*uk_74 + 1517237473*uk_75 + 1839332579*uk_76 + 796761578*uk_77 + 2229805417*uk_78 + 965906494*uk_79 + 94*uk_8 + 418411108*uk_80 + 250047*uk_81 + 384993*uk_82 + 373086*uk_83 + 47628*uk_84 + 710451*uk_85 + 861273*uk_86 + 373086*uk_87 + 592767*uk_88 + 574434*uk_89 + 2242306609*uk_9 + 73332*uk_90 + 1093869*uk_91 + 1326087*uk_92 + 574434*uk_93 + 556668*uk_94 + 71064*uk_95 + 1060038*uk_96 + 1285074*uk_97 + 556668*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 136836*uk_100 + 164052*uk_101 + 73332*uk_102 + 2063943*uk_103 + 2474451*uk_104 + 1106091*uk_105 + 2966607*uk_106 + 1326087*uk_107 + 592767*uk_108 + 1404928*uk_109 + 5303536*uk_11 + 1216768*uk_110 + 150528*uk_111 + 2270464*uk_112 + 2722048*uk_113 + 1216768*uk_114 + 1053808*uk_115 + 130368*uk_116 + 1966384*uk_117 + 2357488*uk_118 + 1053808*uk_119 + 4593241*uk_12 + 16128*uk_120 + 243264*uk_121 + 291648*uk_122 + 130368*uk_123 + 3669232*uk_124 + 4399024*uk_125 + 1966384*uk_126 + 5273968*uk_127 + 2357488*uk_128 + 1053808*uk_129 + 568236*uk_13 + 912673*uk_130 + 112908*uk_131 + 1703029*uk_132 + 2041753*uk_133 + 912673*uk_134 + 13968*uk_135 + 210684*uk_136 + 252588*uk_137 + 112908*uk_138 + 3177817*uk_139 + 8570893*uk_14 + 3809869*uk_140 + 1703029*uk_141 + 4567633*uk_142 + 2041753*uk_143 + 912673*uk_144 + 1728*uk_145 + 26064*uk_146 + 31248*uk_147 + 13968*uk_148 + 393132*uk_149 + 10275601*uk_15 + 471324*uk_150 + 210684*uk_151 + 565068*uk_152 + 252588*uk_153 + 112908*uk_154 + 5929741*uk_155 + 7109137*uk_156 + 3177817*uk_157 + 8523109*uk_158 + 3809869*uk_159 + 4593241*uk_16 + 1703029*uk_160 + 10218313*uk_161 + 4567633*uk_162 + 2041753*uk_163 + 912673*uk_164 + 3969*uk_17 + 7056*uk_18 + 6111*uk_19 + 63*uk_2 + 756*uk_20 + 11403*uk_21 + 13671*uk_22 + 6111*uk_23 + 12544*uk_24 + 10864*uk_25 + 1344*uk_26 + 20272*uk_27 + 24304*uk_28 + 10864*uk_29 + 112*uk_3 + 9409*uk_30 + 1164*uk_31 + 17557*uk_32 + 21049*uk_33 + 9409*uk_34 + 144*uk_35 + 2172*uk_36 + 2604*uk_37 + 1164*uk_38 + 32761*uk_39 + 97*uk_4 + 39277*uk_40 + 17557*uk_41 + 47089*uk_42 + 21049*uk_43 + 9409*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 251138340208*uk_47 + 217503741073*uk_48 + 26907679308*uk_49 + 12*uk_5 + 405857496229*uk_50 + 486580534153*uk_51 + 217503741073*uk_52 + 187944057*uk_53 + 334122768*uk_54 + 289374183*uk_55 + 35798868*uk_56 + 539966259*uk_57 + 647362863*uk_58 + 289374183*uk_59 + 181*uk_6 + 593996032*uk_60 + 514442992*uk_61 + 63642432*uk_62 + 959940016*uk_63 + 1150867312*uk_64 + 514442992*uk_65 + 445544377*uk_66 + 55118892*uk_67 + 831376621*uk_68 + 996733297*uk_69 + 217*uk_7 + 445544377*uk_70 + 6818832*uk_71 + 102850716*uk_72 + 123307212*uk_73 + 55118892*uk_74 + 1551331633*uk_75 + 1859883781*uk_76 + 831376621*uk_77 + 2229805417*uk_78 + 996733297*uk_79 + 97*uk_8 + 445544377*uk_80 + 250047*uk_81 + 444528*uk_82 + 384993*uk_83 + 47628*uk_84 + 718389*uk_85 + 861273*uk_86 + 384993*uk_87 + 790272*uk_88 + 684432*uk_89 + 2242306609*uk_9 + 84672*uk_90 + 1277136*uk_91 + 1531152*uk_92 + 684432*uk_93 + 592767*uk_94 + 73332*uk_95 + 1106091*uk_96 + 1326087*uk_97 + 592767*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 138348*uk_100 + 164052*uk_101 + 84672*uk_102 + 2109807*uk_103 + 2501793*uk_104 + 1291248*uk_105 + 2966607*uk_106 + 1531152*uk_107 + 790272*uk_108 + 2685619*uk_109 + 6582067*uk_11 + 2163952*uk_110 + 231852*uk_111 + 3535743*uk_112 + 4192657*uk_113 + 2163952*uk_114 + 1743616*uk_115 + 186816*uk_116 + 2848944*uk_117 + 3378256*uk_118 + 1743616*uk_119 + 5303536*uk_12 + 20016*uk_120 + 305244*uk_121 + 361956*uk_122 + 186816*uk_123 + 4654971*uk_124 + 5519829*uk_125 + 2848944*uk_126 + 6545371*uk_127 + 3378256*uk_128 + 1743616*uk_129 + 568236*uk_13 + 1404928*uk_130 + 150528*uk_131 + 2295552*uk_132 + 2722048*uk_133 + 1404928*uk_134 + 16128*uk_135 + 245952*uk_136 + 291648*uk_137 + 150528*uk_138 + 3750768*uk_139 + 8665599*uk_14 + 4447632*uk_140 + 2295552*uk_141 + 5273968*uk_142 + 2722048*uk_143 + 1404928*uk_144 + 1728*uk_145 + 26352*uk_146 + 31248*uk_147 + 16128*uk_148 + 401868*uk_149 + 10275601*uk_15 + 476532*uk_150 + 245952*uk_151 + 565068*uk_152 + 291648*uk_153 + 150528*uk_154 + 6128487*uk_155 + 7267113*uk_156 + 3750768*uk_157 + 8617287*uk_158 + 4447632*uk_159 + 5303536*uk_16 + 2295552*uk_160 + 10218313*uk_161 + 5273968*uk_162 + 2722048*uk_163 + 1404928*uk_164 + 3969*uk_17 + 8757*uk_18 + 7056*uk_19 + 63*uk_2 + 756*uk_20 + 11529*uk_21 + 13671*uk_22 + 7056*uk_23 + 19321*uk_24 + 15568*uk_25 + 1668*uk_26 + 25437*uk_27 + 30163*uk_28 + 15568*uk_29 + 139*uk_3 + 12544*uk_30 + 1344*uk_31 + 20496*uk_32 + 24304*uk_33 + 12544*uk_34 + 144*uk_35 + 2196*uk_36 + 2604*uk_37 + 1344*uk_38 + 33489*uk_39 + 112*uk_4 + 39711*uk_40 + 20496*uk_41 + 47089*uk_42 + 24304*uk_43 + 12544*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 311680618651*uk_47 + 251138340208*uk_48 + 26907679308*uk_49 + 12*uk_5 + 410342109447*uk_50 + 486580534153*uk_51 + 251138340208*uk_52 + 187944057*uk_53 + 414670221*uk_54 + 334122768*uk_55 + 35798868*uk_56 + 545932737*uk_57 + 647362863*uk_58 + 334122768*uk_59 + 183*uk_6 + 914907313*uk_60 + 737191504*uk_61 + 78984804*uk_62 + 1204518261*uk_63 + 1428308539*uk_64 + 737191504*uk_65 + 593996032*uk_66 + 63642432*uk_67 + 970547088*uk_68 + 1150867312*uk_69 + 217*uk_7 + 593996032*uk_70 + 6818832*uk_71 + 103987188*uk_72 + 123307212*uk_73 + 63642432*uk_74 + 1585804617*uk_75 + 1880434983*uk_76 + 970547088*uk_77 + 2229805417*uk_78 + 1150867312*uk_79 + 112*uk_8 + 593996032*uk_80 + 250047*uk_81 + 551691*uk_82 + 444528*uk_83 + 47628*uk_84 + 726327*uk_85 + 861273*uk_86 + 444528*uk_87 + 1217223*uk_88 + 980784*uk_89 + 2242306609*uk_9 + 105084*uk_90 + 1602531*uk_91 + 1900269*uk_92 + 980784*uk_93 + 790272*uk_94 + 84672*uk_95 + 1291248*uk_96 + 1531152*uk_97 + 790272*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 139860*uk_100 + 164052*uk_101 + 105084*uk_102 + 2156175*uk_103 + 2529135*uk_104 + 1620045*uk_105 + 2966607*uk_106 + 1900269*uk_107 + 1217223*uk_108 + 5639752*uk_109 + 8428834*uk_11 + 4404076*uk_110 + 380208*uk_111 + 5861540*uk_112 + 6875428*uk_113 + 4404076*uk_114 + 3439138*uk_115 + 296904*uk_116 + 4577270*uk_117 + 5369014*uk_118 + 3439138*uk_119 + 6582067*uk_12 + 25632*uk_120 + 395160*uk_121 + 463512*uk_122 + 296904*uk_123 + 6092050*uk_124 + 7145810*uk_125 + 4577270*uk_126 + 8381842*uk_127 + 5369014*uk_128 + 3439138*uk_129 + 568236*uk_13 + 2685619*uk_130 + 231852*uk_131 + 3574385*uk_132 + 4192657*uk_133 + 2685619*uk_134 + 20016*uk_135 + 308580*uk_136 + 361956*uk_137 + 231852*uk_138 + 4757275*uk_139 + 8760305*uk_14 + 5580155*uk_140 + 3574385*uk_141 + 6545371*uk_142 + 4192657*uk_143 + 2685619*uk_144 + 1728*uk_145 + 26640*uk_146 + 31248*uk_147 + 20016*uk_148 + 410700*uk_149 + 10275601*uk_15 + 481740*uk_150 + 308580*uk_151 + 565068*uk_152 + 361956*uk_153 + 231852*uk_154 + 6331625*uk_155 + 7426825*uk_156 + 4757275*uk_157 + 8711465*uk_158 + 5580155*uk_159 + 6582067*uk_16 + 3574385*uk_160 + 10218313*uk_161 + 6545371*uk_162 + 4192657*uk_163 + 2685619*uk_164 + 3969*uk_17 + 11214*uk_18 + 8757*uk_19 + 63*uk_2 + 756*uk_20 + 11655*uk_21 + 13671*uk_22 + 8757*uk_23 + 31684*uk_24 + 24742*uk_25 + 2136*uk_26 + 32930*uk_27 + 38626*uk_28 + 24742*uk_29 + 178*uk_3 + 19321*uk_30 + 1668*uk_31 + 25715*uk_32 + 30163*uk_33 + 19321*uk_34 + 144*uk_35 + 2220*uk_36 + 2604*uk_37 + 1668*uk_38 + 34225*uk_39 + 139*uk_4 + 40145*uk_40 + 25715*uk_41 + 47089*uk_42 + 30163*uk_43 + 19321*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 399130576402*uk_47 + 311680618651*uk_48 + 26907679308*uk_49 + 12*uk_5 + 414826722665*uk_50 + 486580534153*uk_51 + 311680618651*uk_52 + 187944057*uk_53 + 531016542*uk_54 + 414670221*uk_55 + 35798868*uk_56 + 551899215*uk_57 + 647362863*uk_58 + 414670221*uk_59 + 185*uk_6 + 1500332452*uk_60 + 1171607926*uk_61 + 101146008*uk_62 + 1559334290*uk_63 + 1829056978*uk_64 + 1171607926*uk_65 + 914907313*uk_66 + 78984804*uk_67 + 1217682395*uk_68 + 1428308539*uk_69 + 217*uk_7 + 914907313*uk_70 + 6818832*uk_71 + 105123660*uk_72 + 123307212*uk_73 + 78984804*uk_74 + 1620656425*uk_75 + 1900986185*uk_76 + 1217682395*uk_77 + 2229805417*uk_78 + 1428308539*uk_79 + 139*uk_8 + 914907313*uk_80 + 250047*uk_81 + 706482*uk_82 + 551691*uk_83 + 47628*uk_84 + 734265*uk_85 + 861273*uk_86 + 551691*uk_87 + 1996092*uk_88 + 1558746*uk_89 + 2242306609*uk_9 + 134568*uk_90 + 2074590*uk_91 + 2433438*uk_92 + 1558746*uk_93 + 1217223*uk_94 + 105084*uk_95 + 1620045*uk_96 + 1900269*uk_97 + 1217223*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 94248*uk_100 + 109368*uk_101 + 89712*uk_102 + 2203047*uk_103 + 2556477*uk_104 + 2097018*uk_105 + 2966607*uk_106 + 2433438*uk_107 + 1996092*uk_108 + 74088*uk_109 + 1988826*uk_11 + 313992*uk_110 + 14112*uk_111 + 329868*uk_112 + 382788*uk_113 + 313992*uk_114 + 1330728*uk_115 + 59808*uk_116 + 1398012*uk_117 + 1622292*uk_118 + 1330728*uk_119 + 8428834*uk_12 + 2688*uk_120 + 62832*uk_121 + 72912*uk_122 + 59808*uk_123 + 1468698*uk_124 + 1704318*uk_125 + 1398012*uk_126 + 1977738*uk_127 + 1622292*uk_128 + 1330728*uk_129 + 378824*uk_13 + 5639752*uk_130 + 253472*uk_131 + 5924908*uk_132 + 6875428*uk_133 + 5639752*uk_134 + 11392*uk_135 + 266288*uk_136 + 309008*uk_137 + 253472*uk_138 + 6224482*uk_139 + 8855011*uk_14 + 7223062*uk_140 + 5924908*uk_141 + 8381842*uk_142 + 6875428*uk_143 + 5639752*uk_144 + 512*uk_145 + 11968*uk_146 + 13888*uk_147 + 11392*uk_148 + 279752*uk_149 + 10275601*uk_15 + 324632*uk_150 + 266288*uk_151 + 376712*uk_152 + 309008*uk_153 + 253472*uk_154 + 6539203*uk_155 + 7588273*uk_156 + 6224482*uk_157 + 8805643*uk_158 + 7223062*uk_159 + 8428834*uk_16 + 5924908*uk_160 + 10218313*uk_161 + 8381842*uk_162 + 6875428*uk_163 + 5639752*uk_164 + 3969*uk_17 + 2646*uk_18 + 11214*uk_19 + 63*uk_2 + 504*uk_20 + 11781*uk_21 + 13671*uk_22 + 11214*uk_23 + 1764*uk_24 + 7476*uk_25 + 336*uk_26 + 7854*uk_27 + 9114*uk_28 + 7476*uk_29 + 42*uk_3 + 31684*uk_30 + 1424*uk_31 + 33286*uk_32 + 38626*uk_33 + 31684*uk_34 + 64*uk_35 + 1496*uk_36 + 1736*uk_37 + 1424*uk_38 + 34969*uk_39 + 178*uk_4 + 40579*uk_40 + 33286*uk_41 + 47089*uk_42 + 38626*uk_43 + 31684*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 94176877578*uk_47 + 399130576402*uk_48 + 17938452872*uk_49 + 8*uk_5 + 419311335883*uk_50 + 486580534153*uk_51 + 399130576402*uk_52 + 187944057*uk_53 + 125296038*uk_54 + 531016542*uk_55 + 23865912*uk_56 + 557865693*uk_57 + 647362863*uk_58 + 531016542*uk_59 + 187*uk_6 + 83530692*uk_60 + 354011028*uk_61 + 15910608*uk_62 + 371910462*uk_63 + 431575242*uk_64 + 354011028*uk_65 + 1500332452*uk_66 + 67430672*uk_67 + 1576191958*uk_68 + 1829056978*uk_69 + 217*uk_7 + 1500332452*uk_70 + 3030592*uk_71 + 70840088*uk_72 + 82204808*uk_73 + 67430672*uk_74 + 1655887057*uk_75 + 1921537387*uk_76 + 1576191958*uk_77 + 2229805417*uk_78 + 1829056978*uk_79 + 178*uk_8 + 1500332452*uk_80 + 250047*uk_81 + 166698*uk_82 + 706482*uk_83 + 31752*uk_84 + 742203*uk_85 + 861273*uk_86 + 706482*uk_87 + 111132*uk_88 + 470988*uk_89 + 2242306609*uk_9 + 21168*uk_90 + 494802*uk_91 + 574182*uk_92 + 470988*uk_93 + 1996092*uk_94 + 89712*uk_95 + 2097018*uk_96 + 2433438*uk_97 + 1996092*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 142884*uk_100 + 164052*uk_101 + 31752*uk_102 + 2250423*uk_103 + 2583819*uk_104 + 500094*uk_105 + 2966607*uk_106 + 574182*uk_107 + 111132*uk_108 + 1092727*uk_109 + 4877359*uk_11 + 445578*uk_110 + 127308*uk_111 + 2005101*uk_112 + 2302153*uk_113 + 445578*uk_114 + 181692*uk_115 + 51912*uk_116 + 817614*uk_117 + 938742*uk_118 + 181692*uk_119 + 1988826*uk_12 + 14832*uk_120 + 233604*uk_121 + 268212*uk_122 + 51912*uk_123 + 3679263*uk_124 + 4224339*uk_125 + 817614*uk_126 + 4850167*uk_127 + 938742*uk_128 + 181692*uk_129 + 568236*uk_13 + 74088*uk_130 + 21168*uk_131 + 333396*uk_132 + 382788*uk_133 + 74088*uk_134 + 6048*uk_135 + 95256*uk_136 + 109368*uk_137 + 21168*uk_138 + 1500282*uk_139 + 8949717*uk_14 + 1722546*uk_140 + 333396*uk_141 + 1977738*uk_142 + 382788*uk_143 + 74088*uk_144 + 1728*uk_145 + 27216*uk_146 + 31248*uk_147 + 6048*uk_148 + 428652*uk_149 + 10275601*uk_15 + 492156*uk_150 + 95256*uk_151 + 565068*uk_152 + 109368*uk_153 + 21168*uk_154 + 6751269*uk_155 + 7751457*uk_156 + 1500282*uk_157 + 8899821*uk_158 + 1722546*uk_159 + 1988826*uk_16 + 333396*uk_160 + 10218313*uk_161 + 1977738*uk_162 + 382788*uk_163 + 74088*uk_164 + 3969*uk_17 + 6489*uk_18 + 2646*uk_19 + 63*uk_2 + 756*uk_20 + 11907*uk_21 + 13671*uk_22 + 2646*uk_23 + 10609*uk_24 + 4326*uk_25 + 1236*uk_26 + 19467*uk_27 + 22351*uk_28 + 4326*uk_29 + 103*uk_3 + 1764*uk_30 + 504*uk_31 + 7938*uk_32 + 9114*uk_33 + 1764*uk_34 + 144*uk_35 + 2268*uk_36 + 2604*uk_37 + 504*uk_38 + 35721*uk_39 + 42*uk_4 + 41013*uk_40 + 7938*uk_41 + 47089*uk_42 + 9114*uk_43 + 1764*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 230957580727*uk_47 + 94176877578*uk_48 + 26907679308*uk_49 + 12*uk_5 + 423795949101*uk_50 + 486580534153*uk_51 + 94176877578*uk_52 + 187944057*uk_53 + 307273617*uk_54 + 125296038*uk_55 + 35798868*uk_56 + 563832171*uk_57 + 647362863*uk_58 + 125296038*uk_59 + 189*uk_6 + 502367977*uk_60 + 204849078*uk_61 + 58528308*uk_62 + 921820851*uk_63 + 1058386903*uk_64 + 204849078*uk_65 + 83530692*uk_66 + 23865912*uk_67 + 375888114*uk_68 + 431575242*uk_69 + 217*uk_7 + 83530692*uk_70 + 6818832*uk_71 + 107396604*uk_72 + 123307212*uk_73 + 23865912*uk_74 + 1691496513*uk_75 + 1942088589*uk_76 + 375888114*uk_77 + 2229805417*uk_78 + 431575242*uk_79 + 42*uk_8 + 83530692*uk_80 + 250047*uk_81 + 408807*uk_82 + 166698*uk_83 + 47628*uk_84 + 750141*uk_85 + 861273*uk_86 + 166698*uk_87 + 668367*uk_88 + 272538*uk_89 + 2242306609*uk_9 + 77868*uk_90 + 1226421*uk_91 + 1408113*uk_92 + 272538*uk_93 + 111132*uk_94 + 31752*uk_95 + 500094*uk_96 + 574182*uk_97 + 111132*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 144396*uk_100 + 164052*uk_101 + 77868*uk_102 + 2298303*uk_103 + 2611161*uk_104 + 1239399*uk_105 + 2966607*uk_106 + 1408113*uk_107 + 668367*uk_108 + 5451776*uk_109 + 8334128*uk_11 + 3190528*uk_110 + 371712*uk_111 + 5916416*uk_112 + 6721792*uk_113 + 3190528*uk_114 + 1867184*uk_115 + 217536*uk_116 + 3462448*uk_117 + 3933776*uk_118 + 1867184*uk_119 + 4877359*uk_12 + 25344*uk_120 + 403392*uk_121 + 458304*uk_122 + 217536*uk_123 + 6420656*uk_124 + 7294672*uk_125 + 3462448*uk_126 + 8287664*uk_127 + 3933776*uk_128 + 1867184*uk_129 + 568236*uk_13 + 1092727*uk_130 + 127308*uk_131 + 2026319*uk_132 + 2302153*uk_133 + 1092727*uk_134 + 14832*uk_135 + 236076*uk_136 + 268212*uk_137 + 127308*uk_138 + 3757543*uk_139 + 9044423*uk_14 + 4269041*uk_140 + 2026319*uk_141 + 4850167*uk_142 + 2302153*uk_143 + 1092727*uk_144 + 1728*uk_145 + 27504*uk_146 + 31248*uk_147 + 14832*uk_148 + 437772*uk_149 + 10275601*uk_15 + 497364*uk_150 + 236076*uk_151 + 565068*uk_152 + 268212*uk_153 + 127308*uk_154 + 6967871*uk_155 + 7916377*uk_156 + 3757543*uk_157 + 8993999*uk_158 + 4269041*uk_159 + 4877359*uk_16 + 2026319*uk_160 + 10218313*uk_161 + 4850167*uk_162 + 2302153*uk_163 + 1092727*uk_164 + 3969*uk_17 + 11088*uk_18 + 6489*uk_19 + 63*uk_2 + 756*uk_20 + 12033*uk_21 + 13671*uk_22 + 6489*uk_23 + 30976*uk_24 + 18128*uk_25 + 2112*uk_26 + 33616*uk_27 + 38192*uk_28 + 18128*uk_29 + 176*uk_3 + 10609*uk_30 + 1236*uk_31 + 19673*uk_32 + 22351*uk_33 + 10609*uk_34 + 144*uk_35 + 2292*uk_36 + 2604*uk_37 + 1236*uk_38 + 36481*uk_39 + 103*uk_4 + 41447*uk_40 + 19673*uk_41 + 47089*uk_42 + 22351*uk_43 + 10609*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 394645963184*uk_47 + 230957580727*uk_48 + 26907679308*uk_49 + 12*uk_5 + 428280562319*uk_50 + 486580534153*uk_51 + 230957580727*uk_52 + 187944057*uk_53 + 525050064*uk_54 + 307273617*uk_55 + 35798868*uk_56 + 569798649*uk_57 + 647362863*uk_58 + 307273617*uk_59 + 191*uk_6 + 1466806528*uk_60 + 858415184*uk_61 + 100009536*uk_62 + 1591818448*uk_63 + 1808505776*uk_64 + 858415184*uk_65 + 502367977*uk_66 + 58528308*uk_67 + 931575569*uk_68 + 1058386903*uk_69 + 217*uk_7 + 502367977*uk_70 + 6818832*uk_71 + 108533076*uk_72 + 123307212*uk_73 + 58528308*uk_74 + 1727484793*uk_75 + 1962639791*uk_76 + 931575569*uk_77 + 2229805417*uk_78 + 1058386903*uk_79 + 103*uk_8 + 502367977*uk_80 + 250047*uk_81 + 698544*uk_82 + 408807*uk_83 + 47628*uk_84 + 758079*uk_85 + 861273*uk_86 + 408807*uk_87 + 1951488*uk_88 + 1142064*uk_89 + 2242306609*uk_9 + 133056*uk_90 + 2117808*uk_91 + 2406096*uk_92 + 1142064*uk_93 + 668367*uk_94 + 77868*uk_95 + 1239399*uk_96 + 1408113*uk_97 + 668367*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 97272*uk_100 + 109368*uk_101 + 88704*uk_102 + 2346687*uk_103 + 2638503*uk_104 + 2139984*uk_105 + 2966607*uk_106 + 2406096*uk_107 + 1951488*uk_108 + 314432*uk_109 + 3220004*uk_11 + 813824*uk_110 + 36992*uk_111 + 892432*uk_112 + 1003408*uk_113 + 813824*uk_114 + 2106368*uk_115 + 95744*uk_116 + 2309824*uk_117 + 2597056*uk_118 + 2106368*uk_119 + 8334128*uk_12 + 4352*uk_120 + 104992*uk_121 + 118048*uk_122 + 95744*uk_123 + 2532932*uk_124 + 2847908*uk_125 + 2309824*uk_126 + 3202052*uk_127 + 2597056*uk_128 + 2106368*uk_129 + 378824*uk_13 + 5451776*uk_130 + 247808*uk_131 + 5978368*uk_132 + 6721792*uk_133 + 5451776*uk_134 + 11264*uk_135 + 271744*uk_136 + 305536*uk_137 + 247808*uk_138 + 6555824*uk_139 + 9139129*uk_14 + 7371056*uk_140 + 5978368*uk_141 + 8287664*uk_142 + 6721792*uk_143 + 5451776*uk_144 + 512*uk_145 + 12352*uk_146 + 13888*uk_147 + 11264*uk_148 + 297992*uk_149 + 10275601*uk_15 + 335048*uk_150 + 271744*uk_151 + 376712*uk_152 + 305536*uk_153 + 247808*uk_154 + 7189057*uk_155 + 8083033*uk_156 + 6555824*uk_157 + 9088177*uk_158 + 7371056*uk_159 + 8334128*uk_16 + 5978368*uk_160 + 10218313*uk_161 + 8287664*uk_162 + 6721792*uk_163 + 5451776*uk_164 + 3969*uk_17 + 4284*uk_18 + 11088*uk_19 + 63*uk_2 + 504*uk_20 + 12159*uk_21 + 13671*uk_22 + 11088*uk_23 + 4624*uk_24 + 11968*uk_25 + 544*uk_26 + 13124*uk_27 + 14756*uk_28 + 11968*uk_29 + 68*uk_3 + 30976*uk_30 + 1408*uk_31 + 33968*uk_32 + 38192*uk_33 + 30976*uk_34 + 64*uk_35 + 1544*uk_36 + 1736*uk_37 + 1408*uk_38 + 37249*uk_39 + 176*uk_4 + 41881*uk_40 + 33968*uk_41 + 47089*uk_42 + 38192*uk_43 + 30976*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 152476849412*uk_47 + 394645963184*uk_48 + 17938452872*uk_49 + 8*uk_5 + 432765175537*uk_50 + 486580534153*uk_51 + 394645963184*uk_52 + 187944057*uk_53 + 202860252*uk_54 + 525050064*uk_55 + 23865912*uk_56 + 575765127*uk_57 + 647362863*uk_58 + 525050064*uk_59 + 193*uk_6 + 218960272*uk_60 + 566720704*uk_61 + 25760032*uk_62 + 621460772*uk_63 + 698740868*uk_64 + 566720704*uk_65 + 1466806528*uk_66 + 66673024*uk_67 + 1608486704*uk_68 + 1808505776*uk_69 + 217*uk_7 + 1466806528*uk_70 + 3030592*uk_71 + 73113032*uk_72 + 82204808*uk_73 + 66673024*uk_74 + 1763851897*uk_75 + 1983190993*uk_76 + 1608486704*uk_77 + 2229805417*uk_78 + 1808505776*uk_79 + 176*uk_8 + 1466806528*uk_80 + 250047*uk_81 + 269892*uk_82 + 698544*uk_83 + 31752*uk_84 + 766017*uk_85 + 861273*uk_86 + 698544*uk_87 + 291312*uk_88 + 753984*uk_89 + 2242306609*uk_9 + 34272*uk_90 + 826812*uk_91 + 929628*uk_92 + 753984*uk_93 + 1951488*uk_94 + 88704*uk_95 + 2139984*uk_96 + 2406096*uk_97 + 1951488*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 147420*uk_100 + 164052*uk_101 + 51408*uk_102 + 2395575*uk_103 + 2665845*uk_104 + 835380*uk_105 + 2966607*uk_106 + 929628*uk_107 + 291312*uk_108 + 4330747*uk_109 + 7718539*uk_11 + 1806692*uk_110 + 318828*uk_111 + 5180955*uk_112 + 5765473*uk_113 + 1806692*uk_114 + 753712*uk_115 + 133008*uk_116 + 2161380*uk_117 + 2405228*uk_118 + 753712*uk_119 + 3220004*uk_12 + 23472*uk_120 + 381420*uk_121 + 424452*uk_122 + 133008*uk_123 + 6198075*uk_124 + 6897345*uk_125 + 2161380*uk_126 + 7675507*uk_127 + 2405228*uk_128 + 753712*uk_129 + 568236*uk_13 + 314432*uk_130 + 55488*uk_131 + 901680*uk_132 + 1003408*uk_133 + 314432*uk_134 + 9792*uk_135 + 159120*uk_136 + 177072*uk_137 + 55488*uk_138 + 2585700*uk_139 + 9233835*uk_14 + 2877420*uk_140 + 901680*uk_141 + 3202052*uk_142 + 1003408*uk_143 + 314432*uk_144 + 1728*uk_145 + 28080*uk_146 + 31248*uk_147 + 9792*uk_148 + 456300*uk_149 + 10275601*uk_15 + 507780*uk_150 + 159120*uk_151 + 565068*uk_152 + 177072*uk_153 + 55488*uk_154 + 7414875*uk_155 + 8251425*uk_156 + 2585700*uk_157 + 9182355*uk_158 + 2877420*uk_159 + 3220004*uk_16 + 901680*uk_160 + 10218313*uk_161 + 3202052*uk_162 + 1003408*uk_163 + 314432*uk_164 + 3969*uk_17 + 10269*uk_18 + 4284*uk_19 + 63*uk_2 + 756*uk_20 + 12285*uk_21 + 13671*uk_22 + 4284*uk_23 + 26569*uk_24 + 11084*uk_25 + 1956*uk_26 + 31785*uk_27 + 35371*uk_28 + 11084*uk_29 + 163*uk_3 + 4624*uk_30 + 816*uk_31 + 13260*uk_32 + 14756*uk_33 + 4624*uk_34 + 144*uk_35 + 2340*uk_36 + 2604*uk_37 + 816*uk_38 + 38025*uk_39 + 68*uk_4 + 42315*uk_40 + 13260*uk_41 + 47089*uk_42 + 14756*uk_43 + 4624*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 365495977267*uk_47 + 152476849412*uk_48 + 26907679308*uk_49 + 12*uk_5 + 437249788755*uk_50 + 486580534153*uk_51 + 152476849412*uk_52 + 187944057*uk_53 + 486267957*uk_54 + 202860252*uk_55 + 35798868*uk_56 + 581731605*uk_57 + 647362863*uk_58 + 202860252*uk_59 + 195*uk_6 + 1258121857*uk_60 + 524860652*uk_61 + 92622468*uk_62 + 1505115105*uk_63 + 1674922963*uk_64 + 524860652*uk_65 + 218960272*uk_66 + 38640048*uk_67 + 627900780*uk_68 + 698740868*uk_69 + 217*uk_7 + 218960272*uk_70 + 6818832*uk_71 + 110806020*uk_72 + 123307212*uk_73 + 38640048*uk_74 + 1800597825*uk_75 + 2003742195*uk_76 + 627900780*uk_77 + 2229805417*uk_78 + 698740868*uk_79 + 68*uk_8 + 218960272*uk_80 + 250047*uk_81 + 646947*uk_82 + 269892*uk_83 + 47628*uk_84 + 773955*uk_85 + 861273*uk_86 + 269892*uk_87 + 1673847*uk_88 + 698292*uk_89 + 2242306609*uk_9 + 123228*uk_90 + 2002455*uk_91 + 2228373*uk_92 + 698292*uk_93 + 291312*uk_94 + 51408*uk_95 + 835380*uk_96 + 929628*uk_97 + 291312*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 99288*uk_100 + 109368*uk_101 + 82152*uk_102 + 2444967*uk_103 + 2693187*uk_104 + 2022993*uk_105 + 2966607*uk_106 + 2228373*uk_107 + 1673847*uk_108 + 389017*uk_109 + 3456769*uk_11 + 868627*uk_110 + 42632*uk_111 + 1049813*uk_112 + 1156393*uk_113 + 868627*uk_114 + 1939537*uk_115 + 95192*uk_116 + 2344103*uk_117 + 2582083*uk_118 + 1939537*uk_119 + 7718539*uk_12 + 4672*uk_120 + 115048*uk_121 + 126728*uk_122 + 95192*uk_123 + 2833057*uk_124 + 3120677*uk_125 + 2344103*uk_126 + 3437497*uk_127 + 2582083*uk_128 + 1939537*uk_129 + 378824*uk_13 + 4330747*uk_130 + 212552*uk_131 + 5234093*uk_132 + 5765473*uk_133 + 4330747*uk_134 + 10432*uk_135 + 256888*uk_136 + 282968*uk_137 + 212552*uk_138 + 6325867*uk_139 + 9328541*uk_14 + 6968087*uk_140 + 5234093*uk_141 + 7675507*uk_142 + 5765473*uk_143 + 4330747*uk_144 + 512*uk_145 + 12608*uk_146 + 13888*uk_147 + 10432*uk_148 + 310472*uk_149 + 10275601*uk_15 + 341992*uk_150 + 256888*uk_151 + 376712*uk_152 + 282968*uk_153 + 212552*uk_154 + 7645373*uk_155 + 8421553*uk_156 + 6325867*uk_157 + 9276533*uk_158 + 6968087*uk_159 + 7718539*uk_16 + 5234093*uk_160 + 10218313*uk_161 + 7675507*uk_162 + 5765473*uk_163 + 4330747*uk_164 + 3969*uk_17 + 4599*uk_18 + 10269*uk_19 + 63*uk_2 + 504*uk_20 + 12411*uk_21 + 13671*uk_22 + 10269*uk_23 + 5329*uk_24 + 11899*uk_25 + 584*uk_26 + 14381*uk_27 + 15841*uk_28 + 11899*uk_29 + 73*uk_3 + 26569*uk_30 + 1304*uk_31 + 32111*uk_32 + 35371*uk_33 + 26569*uk_34 + 64*uk_35 + 1576*uk_36 + 1736*uk_37 + 1304*uk_38 + 38809*uk_39 + 163*uk_4 + 42749*uk_40 + 32111*uk_41 + 47089*uk_42 + 35371*uk_43 + 26569*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 163688382457*uk_47 + 365495977267*uk_48 + 17938452872*uk_49 + 8*uk_5 + 441734401973*uk_50 + 486580534153*uk_51 + 365495977267*uk_52 + 187944057*uk_53 + 217776447*uk_54 + 486267957*uk_55 + 23865912*uk_56 + 587698083*uk_57 + 647362863*uk_58 + 486267957*uk_59 + 197*uk_6 + 252344137*uk_60 + 563453347*uk_61 + 27654152*uk_62 + 680983493*uk_63 + 750118873*uk_64 + 563453347*uk_65 + 1258121857*uk_66 + 61748312*uk_67 + 1520552183*uk_68 + 1674922963*uk_69 + 217*uk_7 + 1258121857*uk_70 + 3030592*uk_71 + 74628328*uk_72 + 82204808*uk_73 + 61748312*uk_74 + 1837722577*uk_75 + 2024293397*uk_76 + 1520552183*uk_77 + 2229805417*uk_78 + 1674922963*uk_79 + 163*uk_8 + 1258121857*uk_80 + 250047*uk_81 + 289737*uk_82 + 646947*uk_83 + 31752*uk_84 + 781893*uk_85 + 861273*uk_86 + 646947*uk_87 + 335727*uk_88 + 749637*uk_89 + 2242306609*uk_9 + 36792*uk_90 + 906003*uk_91 + 997983*uk_92 + 749637*uk_93 + 1673847*uk_94 + 82152*uk_95 + 2022993*uk_96 + 2228373*uk_97 + 1673847*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 150444*uk_100 + 164052*uk_101 + 55188*uk_102 + 2494863*uk_103 + 2720529*uk_104 + 915201*uk_105 + 2966607*uk_106 + 997983*uk_107 + 335727*uk_108 + 6859000*uk_109 + 8997070*uk_11 + 2635300*uk_110 + 433200*uk_111 + 7183900*uk_112 + 7833700*uk_113 + 2635300*uk_114 + 1012510*uk_115 + 166440*uk_116 + 2760130*uk_117 + 3009790*uk_118 + 1012510*uk_119 + 3456769*uk_12 + 27360*uk_120 + 453720*uk_121 + 494760*uk_122 + 166440*uk_123 + 7524190*uk_124 + 8204770*uk_125 + 2760130*uk_126 + 8946910*uk_127 + 3009790*uk_128 + 1012510*uk_129 + 568236*uk_13 + 389017*uk_130 + 63948*uk_131 + 1060471*uk_132 + 1156393*uk_133 + 389017*uk_134 + 10512*uk_135 + 174324*uk_136 + 190092*uk_137 + 63948*uk_138 + 2890873*uk_139 + 9423247*uk_14 + 3152359*uk_140 + 1060471*uk_141 + 3437497*uk_142 + 1156393*uk_143 + 389017*uk_144 + 1728*uk_145 + 28656*uk_146 + 31248*uk_147 + 10512*uk_148 + 475212*uk_149 + 10275601*uk_15 + 518196*uk_150 + 174324*uk_151 + 565068*uk_152 + 190092*uk_153 + 63948*uk_154 + 7880599*uk_155 + 8593417*uk_156 + 2890873*uk_157 + 9370711*uk_158 + 3152359*uk_159 + 3456769*uk_16 + 1060471*uk_160 + 10218313*uk_161 + 3437497*uk_162 + 1156393*uk_163 + 389017*uk_164 + 3969*uk_17 + 11970*uk_18 + 4599*uk_19 + 63*uk_2 + 756*uk_20 + 12537*uk_21 + 13671*uk_22 + 4599*uk_23 + 36100*uk_24 + 13870*uk_25 + 2280*uk_26 + 37810*uk_27 + 41230*uk_28 + 13870*uk_29 + 190*uk_3 + 5329*uk_30 + 876*uk_31 + 14527*uk_32 + 15841*uk_33 + 5329*uk_34 + 144*uk_35 + 2388*uk_36 + 2604*uk_37 + 876*uk_38 + 39601*uk_39 + 73*uk_4 + 43183*uk_40 + 14527*uk_41 + 47089*uk_42 + 15841*uk_43 + 5329*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 426038255710*uk_47 + 163688382457*uk_48 + 26907679308*uk_49 + 12*uk_5 + 446219015191*uk_50 + 486580534153*uk_51 + 163688382457*uk_52 + 187944057*uk_53 + 566815410*uk_54 + 217776447*uk_55 + 35798868*uk_56 + 593664561*uk_57 + 647362863*uk_58 + 217776447*uk_59 + 199*uk_6 + 1709443300*uk_60 + 656786110*uk_61 + 107964840*uk_62 + 1790416930*uk_63 + 1952364190*uk_64 + 656786110*uk_65 + 252344137*uk_66 + 41481228*uk_67 + 687897031*uk_68 + 750118873*uk_69 + 217*uk_7 + 252344137*uk_70 + 6818832*uk_71 + 113078964*uk_72 + 123307212*uk_73 + 41481228*uk_74 + 1875226153*uk_75 + 2044844599*uk_76 + 687897031*uk_77 + 2229805417*uk_78 + 750118873*uk_79 + 73*uk_8 + 252344137*uk_80 + 250047*uk_81 + 754110*uk_82 + 289737*uk_83 + 47628*uk_84 + 789831*uk_85 + 861273*uk_86 + 289737*uk_87 + 2274300*uk_88 + 873810*uk_89 + 2242306609*uk_9 + 143640*uk_90 + 2382030*uk_91 + 2597490*uk_92 + 873810*uk_93 + 335727*uk_94 + 55188*uk_95 + 915201*uk_96 + 997983*uk_97 + 335727*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 101304*uk_100 + 109368*uk_101 + 95760*uk_102 + 2545263*uk_103 + 2747871*uk_104 + 2405970*uk_105 + 2966607*uk_106 + 2597490*uk_107 + 2274300*uk_108 + 1643032*uk_109 + 5587654*uk_11 + 2645560*uk_110 + 111392*uk_111 + 2798724*uk_112 + 3021508*uk_113 + 2645560*uk_114 + 4259800*uk_115 + 179360*uk_116 + 4506420*uk_117 + 4865140*uk_118 + 4259800*uk_119 + 8997070*uk_12 + 7552*uk_120 + 189744*uk_121 + 204848*uk_122 + 179360*uk_123 + 4767318*uk_124 + 5146806*uk_125 + 4506420*uk_126 + 5556502*uk_127 + 4865140*uk_128 + 4259800*uk_129 + 378824*uk_13 + 6859000*uk_130 + 288800*uk_131 + 7256100*uk_132 + 7833700*uk_133 + 6859000*uk_134 + 12160*uk_135 + 305520*uk_136 + 329840*uk_137 + 288800*uk_138 + 7676190*uk_139 + 9517953*uk_14 + 8287230*uk_140 + 7256100*uk_141 + 8946910*uk_142 + 7833700*uk_143 + 6859000*uk_144 + 512*uk_145 + 12864*uk_146 + 13888*uk_147 + 12160*uk_148 + 323208*uk_149 + 10275601*uk_15 + 348936*uk_150 + 305520*uk_151 + 376712*uk_152 + 329840*uk_153 + 288800*uk_154 + 8120601*uk_155 + 8767017*uk_156 + 7676190*uk_157 + 9464889*uk_158 + 8287230*uk_159 + 8997070*uk_16 + 7256100*uk_160 + 10218313*uk_161 + 8946910*uk_162 + 7833700*uk_163 + 6859000*uk_164 + 3969*uk_17 + 7434*uk_18 + 11970*uk_19 + 63*uk_2 + 504*uk_20 + 12663*uk_21 + 13671*uk_22 + 11970*uk_23 + 13924*uk_24 + 22420*uk_25 + 944*uk_26 + 23718*uk_27 + 25606*uk_28 + 22420*uk_29 + 118*uk_3 + 36100*uk_30 + 1520*uk_31 + 38190*uk_32 + 41230*uk_33 + 36100*uk_34 + 64*uk_35 + 1608*uk_36 + 1736*uk_37 + 1520*uk_38 + 40401*uk_39 + 190*uk_4 + 43617*uk_40 + 38190*uk_41 + 47089*uk_42 + 41230*uk_43 + 36100*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 264592179862*uk_47 + 426038255710*uk_48 + 17938452872*uk_49 + 8*uk_5 + 450703628409*uk_50 + 486580534153*uk_51 + 426038255710*uk_52 + 187944057*uk_53 + 352022202*uk_54 + 566815410*uk_55 + 23865912*uk_56 + 599631039*uk_57 + 647362863*uk_58 + 566815410*uk_59 + 201*uk_6 + 659343172*uk_60 + 1061654260*uk_61 + 44701232*uk_62 + 1123118454*uk_63 + 1212520918*uk_64 + 1061654260*uk_65 + 1709443300*uk_66 + 71976560*uk_67 + 1808411070*uk_68 + 1952364190*uk_69 + 217*uk_7 + 1709443300*uk_70 + 3030592*uk_71 + 76143624*uk_72 + 82204808*uk_73 + 71976560*uk_74 + 1913108553*uk_75 + 2065395801*uk_76 + 1808411070*uk_77 + 2229805417*uk_78 + 1952364190*uk_79 + 190*uk_8 + 1709443300*uk_80 + 250047*uk_81 + 468342*uk_82 + 754110*uk_83 + 31752*uk_84 + 797769*uk_85 + 861273*uk_86 + 754110*uk_87 + 877212*uk_88 + 1412460*uk_89 + 2242306609*uk_9 + 59472*uk_90 + 1494234*uk_91 + 1613178*uk_92 + 1412460*uk_93 + 2274300*uk_94 + 95760*uk_95 + 2405970*uk_96 + 2597490*uk_97 + 2274300*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 102312*uk_100 + 109368*uk_101 + 59472*uk_102 + 2596167*uk_103 + 2775213*uk_104 + 1509102*uk_105 + 2966607*uk_106 + 1613178*uk_107 + 877212*uk_108 + 157464*uk_109 + 2557062*uk_11 + 344088*uk_110 + 23328*uk_111 + 591948*uk_112 + 632772*uk_113 + 344088*uk_114 + 751896*uk_115 + 50976*uk_116 + 1293516*uk_117 + 1382724*uk_118 + 751896*uk_119 + 5587654*uk_12 + 3456*uk_120 + 87696*uk_121 + 93744*uk_122 + 50976*uk_123 + 2225286*uk_124 + 2378754*uk_125 + 1293516*uk_126 + 2542806*uk_127 + 1382724*uk_128 + 751896*uk_129 + 378824*uk_13 + 1643032*uk_130 + 111392*uk_131 + 2826572*uk_132 + 3021508*uk_133 + 1643032*uk_134 + 7552*uk_135 + 191632*uk_136 + 204848*uk_137 + 111392*uk_138 + 4862662*uk_139 + 9612659*uk_14 + 5198018*uk_140 + 2826572*uk_141 + 5556502*uk_142 + 3021508*uk_143 + 1643032*uk_144 + 512*uk_145 + 12992*uk_146 + 13888*uk_147 + 7552*uk_148 + 329672*uk_149 + 10275601*uk_15 + 352408*uk_150 + 191632*uk_151 + 376712*uk_152 + 204848*uk_153 + 111392*uk_154 + 8365427*uk_155 + 8942353*uk_156 + 4862662*uk_157 + 9559067*uk_158 + 5198018*uk_159 + 5587654*uk_16 + 2826572*uk_160 + 10218313*uk_161 + 5556502*uk_162 + 3021508*uk_163 + 1643032*uk_164 + 3969*uk_17 + 3402*uk_18 + 7434*uk_19 + 63*uk_2 + 504*uk_20 + 12789*uk_21 + 13671*uk_22 + 7434*uk_23 + 2916*uk_24 + 6372*uk_25 + 432*uk_26 + 10962*uk_27 + 11718*uk_28 + 6372*uk_29 + 54*uk_3 + 13924*uk_30 + 944*uk_31 + 23954*uk_32 + 25606*uk_33 + 13924*uk_34 + 64*uk_35 + 1624*uk_36 + 1736*uk_37 + 944*uk_38 + 41209*uk_39 + 118*uk_4 + 44051*uk_40 + 23954*uk_41 + 47089*uk_42 + 25606*uk_43 + 13924*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 121084556886*uk_47 + 264592179862*uk_48 + 17938452872*uk_49 + 8*uk_5 + 455188241627*uk_50 + 486580534153*uk_51 + 264592179862*uk_52 + 187944057*uk_53 + 161094906*uk_54 + 352022202*uk_55 + 23865912*uk_56 + 605597517*uk_57 + 647362863*uk_58 + 352022202*uk_59 + 203*uk_6 + 138081348*uk_60 + 301733316*uk_61 + 20456496*uk_62 + 519083586*uk_63 + 554882454*uk_64 + 301733316*uk_65 + 659343172*uk_66 + 44701232*uk_67 + 1134293762*uk_68 + 1212520918*uk_69 + 217*uk_7 + 659343172*uk_70 + 3030592*uk_71 + 76901272*uk_72 + 82204808*uk_73 + 44701232*uk_74 + 1951369777*uk_75 + 2085947003*uk_76 + 1134293762*uk_77 + 2229805417*uk_78 + 1212520918*uk_79 + 118*uk_8 + 659343172*uk_80 + 250047*uk_81 + 214326*uk_82 + 468342*uk_83 + 31752*uk_84 + 805707*uk_85 + 861273*uk_86 + 468342*uk_87 + 183708*uk_88 + 401436*uk_89 + 2242306609*uk_9 + 27216*uk_90 + 690606*uk_91 + 738234*uk_92 + 401436*uk_93 + 877212*uk_94 + 59472*uk_95 + 1509102*uk_96 + 1613178*uk_97 + 877212*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 154980*uk_100 + 164052*uk_101 + 40824*uk_102 + 2647575*uk_103 + 2802555*uk_104 + 697410*uk_105 + 2966607*uk_106 + 738234*uk_107 + 183708*uk_108 + 8365427*uk_109 + 9612659*uk_11 + 2225286*uk_110 + 494508*uk_111 + 8447845*uk_112 + 8942353*uk_113 + 2225286*uk_114 + 591948*uk_115 + 131544*uk_116 + 2247210*uk_117 + 2378754*uk_118 + 591948*uk_119 + 2557062*uk_12 + 29232*uk_120 + 499380*uk_121 + 528612*uk_122 + 131544*uk_123 + 8531075*uk_124 + 9030455*uk_125 + 2247210*uk_126 + 9559067*uk_127 + 2378754*uk_128 + 591948*uk_129 + 568236*uk_13 + 157464*uk_130 + 34992*uk_131 + 597780*uk_132 + 632772*uk_133 + 157464*uk_134 + 7776*uk_135 + 132840*uk_136 + 140616*uk_137 + 34992*uk_138 + 2269350*uk_139 + 9707365*uk_14 + 2402190*uk_140 + 597780*uk_141 + 2542806*uk_142 + 632772*uk_143 + 157464*uk_144 + 1728*uk_145 + 29520*uk_146 + 31248*uk_147 + 7776*uk_148 + 504300*uk_149 + 10275601*uk_15 + 533820*uk_150 + 132840*uk_151 + 565068*uk_152 + 140616*uk_153 + 34992*uk_154 + 8615125*uk_155 + 9119425*uk_156 + 2269350*uk_157 + 9653245*uk_158 + 2402190*uk_159 + 2557062*uk_16 + 597780*uk_160 + 10218313*uk_161 + 2542806*uk_162 + 632772*uk_163 + 157464*uk_164 + 3969*uk_17 + 12789*uk_18 + 3402*uk_19 + 63*uk_2 + 756*uk_20 + 12915*uk_21 + 13671*uk_22 + 3402*uk_23 + 41209*uk_24 + 10962*uk_25 + 2436*uk_26 + 41615*uk_27 + 44051*uk_28 + 10962*uk_29 + 203*uk_3 + 2916*uk_30 + 648*uk_31 + 11070*uk_32 + 11718*uk_33 + 2916*uk_34 + 144*uk_35 + 2460*uk_36 + 2604*uk_37 + 648*uk_38 + 42025*uk_39 + 54*uk_4 + 44485*uk_40 + 11070*uk_41 + 47089*uk_42 + 11718*uk_43 + 2916*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 455188241627*uk_47 + 121084556886*uk_48 + 26907679308*uk_49 + 12*uk_5 + 459672854845*uk_50 + 486580534153*uk_51 + 121084556886*uk_52 + 187944057*uk_53 + 605597517*uk_54 + 161094906*uk_55 + 35798868*uk_56 + 611563995*uk_57 + 647362863*uk_58 + 161094906*uk_59 + 205*uk_6 + 1951369777*uk_60 + 519083586*uk_61 + 115351908*uk_62 + 1970595095*uk_63 + 2085947003*uk_64 + 519083586*uk_65 + 138081348*uk_66 + 30684744*uk_67 + 524197710*uk_68 + 554882454*uk_69 + 217*uk_7 + 138081348*uk_70 + 6818832*uk_71 + 116488380*uk_72 + 123307212*uk_73 + 30684744*uk_74 + 1990009825*uk_75 + 2106498205*uk_76 + 524197710*uk_77 + 2229805417*uk_78 + 554882454*uk_79 + 54*uk_8 + 138081348*uk_80 + 250047*uk_81 + 805707*uk_82 + 214326*uk_83 + 47628*uk_84 + 813645*uk_85 + 861273*uk_86 + 214326*uk_87 + 2596167*uk_88 + 690606*uk_89 + 2242306609*uk_9 + 153468*uk_90 + 2621745*uk_91 + 2775213*uk_92 + 690606*uk_93 + 183708*uk_94 + 40824*uk_95 + 697410*uk_96 + 738234*uk_97 + 183708*uk_98 + 9072*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 104328*uk_100 + 109368*uk_101 + 102312*uk_102 + 2699487*uk_103 + 2829897*uk_104 + 2647323*uk_105 + 2966607*uk_106 + 2775213*uk_107 + 2596167*uk_108 + 3869893*uk_109 + 7434421*uk_11 + 5003747*uk_110 + 197192*uk_111 + 5102343*uk_112 + 5348833*uk_113 + 5003747*uk_114 + 6469813*uk_115 + 254968*uk_116 + 6597297*uk_117 + 6916007*uk_118 + 6469813*uk_119 + 9612659*uk_12 + 10048*uk_120 + 259992*uk_121 + 272552*uk_122 + 254968*uk_123 + 6727293*uk_124 + 7052283*uk_125 + 6597297*uk_126 + 7392973*uk_127 + 6916007*uk_128 + 6469813*uk_129 + 378824*uk_13 + 8365427*uk_130 + 329672*uk_131 + 8530263*uk_132 + 8942353*uk_133 + 8365427*uk_134 + 12992*uk_135 + 336168*uk_136 + 352408*uk_137 + 329672*uk_138 + 8698347*uk_139 + 9802071*uk_14 + 9118557*uk_140 + 8530263*uk_141 + 9559067*uk_142 + 8942353*uk_143 + 8365427*uk_144 + 512*uk_145 + 13248*uk_146 + 13888*uk_147 + 12992*uk_148 + 342792*uk_149 + 10275601*uk_15 + 359352*uk_150 + 336168*uk_151 + 376712*uk_152 + 352408*uk_153 + 329672*uk_154 + 8869743*uk_155 + 9298233*uk_156 + 8698347*uk_157 + 9747423*uk_158 + 9118557*uk_159 + 9612659*uk_16 + 8530263*uk_160 + 10218313*uk_161 + 9559067*uk_162 + 8942353*uk_163 + 8365427*uk_164 + 3969*uk_17 + 9891*uk_18 + 12789*uk_19 + 63*uk_2 + 504*uk_20 + 13041*uk_21 + 13671*uk_22 + 12789*uk_23 + 24649*uk_24 + 31871*uk_25 + 1256*uk_26 + 32499*uk_27 + 34069*uk_28 + 31871*uk_29 + 157*uk_3 + 41209*uk_30 + 1624*uk_31 + 42021*uk_32 + 44051*uk_33 + 41209*uk_34 + 64*uk_35 + 1656*uk_36 + 1736*uk_37 + 1624*uk_38 + 42849*uk_39 + 203*uk_4 + 44919*uk_40 + 42021*uk_41 + 47089*uk_42 + 44051*uk_43 + 41209*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 352042137613*uk_47 + 455188241627*uk_48 + 17938452872*uk_49 + 8*uk_5 + 464157468063*uk_50 + 486580534153*uk_51 + 455188241627*uk_52 + 187944057*uk_53 + 468368523*uk_54 + 605597517*uk_55 + 23865912*uk_56 + 617530473*uk_57 + 647362863*uk_58 + 605597517*uk_59 + 207*uk_6 + 1167204097*uk_60 + 1509187463*uk_61 + 59475368*uk_62 + 1538925147*uk_63 + 1613269357*uk_64 + 1509187463*uk_65 + 1951369777*uk_66 + 76901272*uk_67 + 1989820413*uk_68 + 2085947003*uk_69 + 217*uk_7 + 1951369777*uk_70 + 3030592*uk_71 + 78416568*uk_72 + 82204808*uk_73 + 76901272*uk_74 + 2029028697*uk_75 + 2127049407*uk_76 + 1989820413*uk_77 + 2229805417*uk_78 + 2085947003*uk_79 + 203*uk_8 + 1951369777*uk_80 + 250047*uk_81 + 623133*uk_82 + 805707*uk_83 + 31752*uk_84 + 821583*uk_85 + 861273*uk_86 + 805707*uk_87 + 1552887*uk_88 + 2007873*uk_89 + 2242306609*uk_9 + 79128*uk_90 + 2047437*uk_91 + 2146347*uk_92 + 2007873*uk_93 + 2596167*uk_94 + 102312*uk_95 + 2647323*uk_96 + 2775213*uk_97 + 2596167*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 105336*uk_100 + 109368*uk_101 + 79128*uk_102 + 2751903*uk_103 + 2857239*uk_104 + 2067219*uk_105 + 2966607*uk_106 + 2146347*uk_107 + 1552887*uk_108 + 1685159*uk_109 + 5635007*uk_11 + 2223277*uk_110 + 113288*uk_111 + 2959649*uk_112 + 3072937*uk_113 + 2223277*uk_114 + 2933231*uk_115 + 149464*uk_116 + 3904747*uk_117 + 4054211*uk_118 + 2933231*uk_119 + 7434421*uk_12 + 7616*uk_120 + 198968*uk_121 + 206584*uk_122 + 149464*uk_123 + 5198039*uk_124 + 5397007*uk_125 + 3904747*uk_126 + 5603591*uk_127 + 4054211*uk_128 + 2933231*uk_129 + 378824*uk_13 + 3869893*uk_130 + 197192*uk_131 + 5151641*uk_132 + 5348833*uk_133 + 3869893*uk_134 + 10048*uk_135 + 262504*uk_136 + 272552*uk_137 + 197192*uk_138 + 6857917*uk_139 + 9896777*uk_14 + 7120421*uk_140 + 5151641*uk_141 + 7392973*uk_142 + 5348833*uk_143 + 3869893*uk_144 + 512*uk_145 + 13376*uk_146 + 13888*uk_147 + 10048*uk_148 + 349448*uk_149 + 10275601*uk_15 + 362824*uk_150 + 262504*uk_151 + 376712*uk_152 + 272552*uk_153 + 197192*uk_154 + 9129329*uk_155 + 9478777*uk_156 + 6857917*uk_157 + 9841601*uk_158 + 7120421*uk_159 + 7434421*uk_16 + 5151641*uk_160 + 10218313*uk_161 + 7392973*uk_162 + 5348833*uk_163 + 3869893*uk_164 + 3969*uk_17 + 7497*uk_18 + 9891*uk_19 + 63*uk_2 + 504*uk_20 + 13167*uk_21 + 13671*uk_22 + 9891*uk_23 + 14161*uk_24 + 18683*uk_25 + 952*uk_26 + 24871*uk_27 + 25823*uk_28 + 18683*uk_29 + 119*uk_3 + 24649*uk_30 + 1256*uk_31 + 32813*uk_32 + 34069*uk_33 + 24649*uk_34 + 64*uk_35 + 1672*uk_36 + 1736*uk_37 + 1256*uk_38 + 43681*uk_39 + 157*uk_4 + 45353*uk_40 + 32813*uk_41 + 47089*uk_42 + 34069*uk_43 + 24649*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 266834486471*uk_47 + 352042137613*uk_48 + 17938452872*uk_49 + 8*uk_5 + 468642081281*uk_50 + 486580534153*uk_51 + 352042137613*uk_52 + 187944057*uk_53 + 355005441*uk_54 + 468368523*uk_55 + 23865912*uk_56 + 623496951*uk_57 + 647362863*uk_58 + 468368523*uk_59 + 209*uk_6 + 670565833*uk_60 + 884696099*uk_61 + 45080056*uk_62 + 1177716463*uk_63 + 1222796519*uk_64 + 884696099*uk_65 + 1167204097*uk_66 + 59475368*uk_67 + 1553793989*uk_68 + 1613269357*uk_69 + 217*uk_7 + 1167204097*uk_70 + 3030592*uk_71 + 79174216*uk_72 + 82204808*uk_73 + 59475368*uk_74 + 2068426393*uk_75 + 2147600609*uk_76 + 1553793989*uk_77 + 2229805417*uk_78 + 1613269357*uk_79 + 157*uk_8 + 1167204097*uk_80 + 250047*uk_81 + 472311*uk_82 + 623133*uk_83 + 31752*uk_84 + 829521*uk_85 + 861273*uk_86 + 623133*uk_87 + 892143*uk_88 + 1177029*uk_89 + 2242306609*uk_9 + 59976*uk_90 + 1566873*uk_91 + 1626849*uk_92 + 1177029*uk_93 + 1552887*uk_94 + 79128*uk_95 + 2067219*uk_96 + 2146347*uk_97 + 1552887*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 106344*uk_100 + 109368*uk_101 + 59976*uk_102 + 2804823*uk_103 + 2884581*uk_104 + 1581867*uk_105 + 2966607*uk_106 + 1626849*uk_107 + 892143*uk_108 + 704969*uk_109 + 4214417*uk_11 + 942599*uk_110 + 63368*uk_111 + 1671331*uk_112 + 1718857*uk_113 + 942599*uk_114 + 1260329*uk_115 + 84728*uk_116 + 2234701*uk_117 + 2298247*uk_118 + 1260329*uk_119 + 5635007*uk_12 + 5696*uk_120 + 150232*uk_121 + 154504*uk_122 + 84728*uk_123 + 3962369*uk_124 + 4075043*uk_125 + 2234701*uk_126 + 4190921*uk_127 + 2298247*uk_128 + 1260329*uk_129 + 378824*uk_13 + 1685159*uk_130 + 113288*uk_131 + 2987971*uk_132 + 3072937*uk_133 + 1685159*uk_134 + 7616*uk_135 + 200872*uk_136 + 206584*uk_137 + 113288*uk_138 + 5297999*uk_139 + 9991483*uk_14 + 5448653*uk_140 + 2987971*uk_141 + 5603591*uk_142 + 3072937*uk_143 + 1685159*uk_144 + 512*uk_145 + 13504*uk_146 + 13888*uk_147 + 7616*uk_148 + 356168*uk_149 + 10275601*uk_15 + 366296*uk_150 + 200872*uk_151 + 376712*uk_152 + 206584*uk_153 + 113288*uk_154 + 9393931*uk_155 + 9661057*uk_156 + 5297999*uk_157 + 9935779*uk_158 + 5448653*uk_159 + 5635007*uk_16 + 2987971*uk_160 + 10218313*uk_161 + 5603591*uk_162 + 3072937*uk_163 + 1685159*uk_164 + 3969*uk_17 + 5607*uk_18 + 7497*uk_19 + 63*uk_2 + 504*uk_20 + 13293*uk_21 + 13671*uk_22 + 7497*uk_23 + 7921*uk_24 + 10591*uk_25 + 712*uk_26 + 18779*uk_27 + 19313*uk_28 + 10591*uk_29 + 89*uk_3 + 14161*uk_30 + 952*uk_31 + 25109*uk_32 + 25823*uk_33 + 14161*uk_34 + 64*uk_35 + 1688*uk_36 + 1736*uk_37 + 952*uk_38 + 44521*uk_39 + 119*uk_4 + 45787*uk_40 + 25109*uk_41 + 47089*uk_42 + 25823*uk_43 + 14161*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 199565288201*uk_47 + 266834486471*uk_48 + 17938452872*uk_49 + 8*uk_5 + 473126694499*uk_50 + 486580534153*uk_51 + 266834486471*uk_52 + 187944057*uk_53 + 265508271*uk_54 + 355005441*uk_55 + 23865912*uk_56 + 629463429*uk_57 + 647362863*uk_58 + 355005441*uk_59 + 211*uk_6 + 375083113*uk_60 + 501515623*uk_61 + 33715336*uk_62 + 889241987*uk_63 + 914528489*uk_64 + 501515623*uk_65 + 670565833*uk_66 + 45080056*uk_67 + 1188986477*uk_68 + 1222796519*uk_69 + 217*uk_7 + 670565833*uk_70 + 3030592*uk_71 + 79931864*uk_72 + 82204808*uk_73 + 45080056*uk_74 + 2108202913*uk_75 + 2168151811*uk_76 + 1188986477*uk_77 + 2229805417*uk_78 + 1222796519*uk_79 + 119*uk_8 + 670565833*uk_80 + 250047*uk_81 + 353241*uk_82 + 472311*uk_83 + 31752*uk_84 + 837459*uk_85 + 861273*uk_86 + 472311*uk_87 + 499023*uk_88 + 667233*uk_89 + 2242306609*uk_9 + 44856*uk_90 + 1183077*uk_91 + 1216719*uk_92 + 667233*uk_93 + 892143*uk_94 + 59976*uk_95 + 1581867*uk_96 + 1626849*uk_97 + 892143*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 107352*uk_100 + 109368*uk_101 + 44856*uk_102 + 2858247*uk_103 + 2911923*uk_104 + 1194291*uk_105 + 2966607*uk_106 + 1216719*uk_107 + 499023*uk_108 + 300763*uk_109 + 3172651*uk_11 + 399521*uk_110 + 35912*uk_111 + 956157*uk_112 + 974113*uk_113 + 399521*uk_114 + 530707*uk_115 + 47704*uk_116 + 1270119*uk_117 + 1293971*uk_118 + 530707*uk_119 + 4214417*uk_12 + 4288*uk_120 + 114168*uk_121 + 116312*uk_122 + 47704*uk_123 + 3039723*uk_124 + 3096807*uk_125 + 1270119*uk_126 + 3154963*uk_127 + 1293971*uk_128 + 530707*uk_129 + 378824*uk_13 + 704969*uk_130 + 63368*uk_131 + 1687173*uk_132 + 1718857*uk_133 + 704969*uk_134 + 5696*uk_135 + 151656*uk_136 + 154504*uk_137 + 63368*uk_138 + 4037841*uk_139 + 10086189*uk_14 + 4113669*uk_140 + 1687173*uk_141 + 4190921*uk_142 + 1718857*uk_143 + 704969*uk_144 + 512*uk_145 + 13632*uk_146 + 13888*uk_147 + 5696*uk_148 + 362952*uk_149 + 10275601*uk_15 + 369768*uk_150 + 151656*uk_151 + 376712*uk_152 + 154504*uk_153 + 63368*uk_154 + 9663597*uk_155 + 9845073*uk_156 + 4037841*uk_157 + 10029957*uk_158 + 4113669*uk_159 + 4214417*uk_16 + 1687173*uk_160 + 10218313*uk_161 + 4190921*uk_162 + 1718857*uk_163 + 704969*uk_164 + 3969*uk_17 + 4221*uk_18 + 5607*uk_19 + 63*uk_2 + 504*uk_20 + 13419*uk_21 + 13671*uk_22 + 5607*uk_23 + 4489*uk_24 + 5963*uk_25 + 536*uk_26 + 14271*uk_27 + 14539*uk_28 + 5963*uk_29 + 67*uk_3 + 7921*uk_30 + 712*uk_31 + 18957*uk_32 + 19313*uk_33 + 7921*uk_34 + 64*uk_35 + 1704*uk_36 + 1736*uk_37 + 712*uk_38 + 45369*uk_39 + 89*uk_4 + 46221*uk_40 + 18957*uk_41 + 47089*uk_42 + 19313*uk_43 + 7921*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 150234542803*uk_47 + 199565288201*uk_48 + 17938452872*uk_49 + 8*uk_5 + 477611307717*uk_50 + 486580534153*uk_51 + 199565288201*uk_52 + 187944057*uk_53 + 199877013*uk_54 + 265508271*uk_55 + 23865912*uk_56 + 635429907*uk_57 + 647362863*uk_58 + 265508271*uk_59 + 213*uk_6 + 212567617*uk_60 + 282365939*uk_61 + 25381208*uk_62 + 675774663*uk_63 + 688465267*uk_64 + 282365939*uk_65 + 375083113*uk_66 + 33715336*uk_67 + 897670821*uk_68 + 914528489*uk_69 + 217*uk_7 + 375083113*uk_70 + 3030592*uk_71 + 80689512*uk_72 + 82204808*uk_73 + 33715336*uk_74 + 2148358257*uk_75 + 2188703013*uk_76 + 897670821*uk_77 + 2229805417*uk_78 + 914528489*uk_79 + 89*uk_8 + 375083113*uk_80 + 250047*uk_81 + 265923*uk_82 + 353241*uk_83 + 31752*uk_84 + 845397*uk_85 + 861273*uk_86 + 353241*uk_87 + 282807*uk_88 + 375669*uk_89 + 2242306609*uk_9 + 33768*uk_90 + 899073*uk_91 + 915957*uk_92 + 375669*uk_93 + 499023*uk_94 + 44856*uk_95 + 1194291*uk_96 + 1216719*uk_97 + 499023*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 108360*uk_100 + 109368*uk_101 + 33768*uk_102 + 2912175*uk_103 + 2939265*uk_104 + 907515*uk_105 + 2966607*uk_106 + 915957*uk_107 + 282807*uk_108 + 148877*uk_109 + 2509709*uk_11 + 188203*uk_110 + 22472*uk_111 + 603935*uk_112 + 609553*uk_113 + 188203*uk_114 + 237917*uk_115 + 28408*uk_116 + 763465*uk_117 + 770567*uk_118 + 237917*uk_119 + 3172651*uk_12 + 3392*uk_120 + 91160*uk_121 + 92008*uk_122 + 28408*uk_123 + 2449925*uk_124 + 2472715*uk_125 + 763465*uk_126 + 2495717*uk_127 + 770567*uk_128 + 237917*uk_129 + 378824*uk_13 + 300763*uk_130 + 35912*uk_131 + 965135*uk_132 + 974113*uk_133 + 300763*uk_134 + 4288*uk_135 + 115240*uk_136 + 116312*uk_137 + 35912*uk_138 + 3097075*uk_139 + 10180895*uk_14 + 3125885*uk_140 + 965135*uk_141 + 3154963*uk_142 + 974113*uk_143 + 300763*uk_144 + 512*uk_145 + 13760*uk_146 + 13888*uk_147 + 4288*uk_148 + 369800*uk_149 + 10275601*uk_15 + 373240*uk_150 + 115240*uk_151 + 376712*uk_152 + 116312*uk_153 + 35912*uk_154 + 9938375*uk_155 + 10030825*uk_156 + 3097075*uk_157 + 10124135*uk_158 + 3125885*uk_159 + 3172651*uk_16 + 965135*uk_160 + 10218313*uk_161 + 3154963*uk_162 + 974113*uk_163 + 300763*uk_164 + 3969*uk_17 + 3339*uk_18 + 4221*uk_19 + 63*uk_2 + 504*uk_20 + 13545*uk_21 + 13671*uk_22 + 4221*uk_23 + 2809*uk_24 + 3551*uk_25 + 424*uk_26 + 11395*uk_27 + 11501*uk_28 + 3551*uk_29 + 53*uk_3 + 4489*uk_30 + 536*uk_31 + 14405*uk_32 + 14539*uk_33 + 4489*uk_34 + 64*uk_35 + 1720*uk_36 + 1736*uk_37 + 536*uk_38 + 46225*uk_39 + 67*uk_4 + 46655*uk_40 + 14405*uk_41 + 47089*uk_42 + 14539*uk_43 + 4489*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 118842250277*uk_47 + 150234542803*uk_48 + 17938452872*uk_49 + 8*uk_5 + 482095920935*uk_50 + 486580534153*uk_51 + 150234542803*uk_52 + 187944057*uk_53 + 158111667*uk_54 + 199877013*uk_55 + 23865912*uk_56 + 641396385*uk_57 + 647362863*uk_58 + 199877013*uk_59 + 215*uk_6 + 133014577*uk_60 + 168150503*uk_61 + 20077672*uk_62 + 539587435*uk_63 + 544606853*uk_64 + 168150503*uk_65 + 212567617*uk_66 + 25381208*uk_67 + 682119965*uk_68 + 688465267*uk_69 + 217*uk_7 + 212567617*uk_70 + 3030592*uk_71 + 81447160*uk_72 + 82204808*uk_73 + 25381208*uk_74 + 2188892425*uk_75 + 2209254215*uk_76 + 682119965*uk_77 + 2229805417*uk_78 + 688465267*uk_79 + 67*uk_8 + 212567617*uk_80 + 250047*uk_81 + 210357*uk_82 + 265923*uk_83 + 31752*uk_84 + 853335*uk_85 + 861273*uk_86 + 265923*uk_87 + 176967*uk_88 + 223713*uk_89 + 2242306609*uk_9 + 26712*uk_90 + 717885*uk_91 + 724563*uk_92 + 223713*uk_93 + 282807*uk_94 + 33768*uk_95 + 907515*uk_96 + 915957*uk_97 + 282807*uk_98 + 4032*uk_99, + uk_0 + 47353*uk_1 + 2983239*uk_10 + 109368*uk_100 + 109368*uk_101 + 26712*uk_102 + 2966607*uk_103 + 2966607*uk_104 + 724563*uk_105 + 2966607*uk_106 + 724563*uk_107 + 176967*uk_108 + 103823*uk_109 + 2225591*uk_11 + 117077*uk_110 + 17672*uk_111 + 479353*uk_112 + 479353*uk_113 + 117077*uk_114 + 132023*uk_115 + 19928*uk_116 + 540547*uk_117 + 540547*uk_118 + 132023*uk_119 + 2509709*uk_12 + 3008*uk_120 + 81592*uk_121 + 81592*uk_122 + 19928*uk_123 + 2213183*uk_124 + 2213183*uk_125 + 540547*uk_126 + 2213183*uk_127 + 540547*uk_128 + 132023*uk_129 + 378824*uk_13 + 148877*uk_130 + 22472*uk_131 + 609553*uk_132 + 609553*uk_133 + 148877*uk_134 + 3392*uk_135 + 92008*uk_136 + 92008*uk_137 + 22472*uk_138 + 2495717*uk_139 + 10275601*uk_14 + 2495717*uk_140 + 609553*uk_141 + 2495717*uk_142 + 609553*uk_143 + 148877*uk_144 + 512*uk_145 + 13888*uk_146 + 13888*uk_147 + 3392*uk_148 + 376712*uk_149 + 10275601*uk_15 + 376712*uk_150 + 92008*uk_151 + 376712*uk_152 + 92008*uk_153 + 22472*uk_154 + 10218313*uk_155 + 10218313*uk_156 + 2495717*uk_157 + 10218313*uk_158 + 2495717*uk_159 + 2509709*uk_16 + 609553*uk_160 + 10218313*uk_161 + 2495717*uk_162 + 609553*uk_163 + 148877*uk_164 + 3969*uk_17 + 2961*uk_18 + 3339*uk_19 + 63*uk_2 + 504*uk_20 + 13671*uk_21 + 13671*uk_22 + 3339*uk_23 + 2209*uk_24 + 2491*uk_25 + 376*uk_26 + 10199*uk_27 + 10199*uk_28 + 2491*uk_29 + 47*uk_3 + 2809*uk_30 + 424*uk_31 + 11501*uk_32 + 11501*uk_33 + 2809*uk_34 + 64*uk_35 + 1736*uk_36 + 1736*uk_37 + 424*uk_38 + 47089*uk_39 + 53*uk_4 + 47089*uk_40 + 11501*uk_41 + 47089*uk_42 + 11501*uk_43 + 2809*uk_44 + 106179944855977*uk_45 + 141265316367*uk_46 + 105388410623*uk_47 + 118842250277*uk_48 + 17938452872*uk_49 + 8*uk_5 + 486580534153*uk_50 + 486580534153*uk_51 + 118842250277*uk_52 + 187944057*uk_53 + 140212233*uk_54 + 158111667*uk_55 + 23865912*uk_56 + 647362863*uk_57 + 647362863*uk_58 + 158111667*uk_59 + 217*uk_6 + 104602777*uk_60 + 117956323*uk_61 + 17804728*uk_62 + 482953247*uk_63 + 482953247*uk_64 + 117956323*uk_65 + 133014577*uk_66 + 20077672*uk_67 + 544606853*uk_68 + 544606853*uk_69 + 217*uk_7 + 133014577*uk_70 + 3030592*uk_71 + 82204808*uk_72 + 82204808*uk_73 + 20077672*uk_74 + 2229805417*uk_75 + 2229805417*uk_76 + 544606853*uk_77 + 2229805417*uk_78 + 544606853*uk_79 + 53*uk_8 + 133014577*uk_80 + 250047*uk_81 + 186543*uk_82 + 210357*uk_83 + 31752*uk_84 + 861273*uk_85 + 861273*uk_86 + 210357*uk_87 + 139167*uk_88 + 156933*uk_89 + 2242306609*uk_9 + 23688*uk_90 + 642537*uk_91 + 642537*uk_92 + 156933*uk_93 + 176967*uk_94 + 26712*uk_95 + 724563*uk_96 + 724563*uk_97 + 176967*uk_98 + 4032*uk_99, + ] + +def sol_165x165(): + return { + uk_0: -QQ(295441,1683)*uk_2 - QQ(175799,1683)*uk_7 + QQ(2401696807,1)*uk_9 - QQ(9606787228,1683)*uk_10 + QQ(9606787228,1683)*uk_15 - QQ(29030443,1683)*uk_17 - QQ(5965893,187)*uk_22 + QQ(262901,99)*uk_42 + QQ(235539209256104,1)*uk_45 - QQ(232597130667529,1683)*uk_46 + QQ(1364372733998209,1683)*uk_51 - QQ(1133600892904,1683)*uk_53 - QQ(172922170104,187)*uk_58 + QQ(249776467928,99)*uk_78 - QQ(2401889209,1683)*uk_81 - QQ(636292759,187)*uk_86 - QQ(1034157281,187)*uk_106 + QQ(10558824289,1683)*uk_161, + uk_1: QQ(4,1683)*uk_2 - QQ(4,1683)*uk_7 - QQ(98072,1)*uk_9 + QQ(96847,1683)*uk_10 - QQ(568087,1683)*uk_15 + QQ(472,1683)*uk_17 + QQ(72,187)*uk_22 - QQ(104,99)*uk_42 - QQ(7216420377,1)*uk_45 - QQ(108808244,1683)*uk_46 - QQ(46106641036,1683)*uk_51 + QQ(17259541,1683)*uk_53 + QQ(1095291,187)*uk_58 - QQ(9936587,99)*uk_78 + QQ(41836,1683)*uk_81 + QQ(10036,187)*uk_86 + QQ(10124,187)*uk_106 - QQ(8,1)*uk_149 - QQ(586156,1683)*uk_161, + uk_3: -QQ(295441,1683)*uk_18 - QQ(175799,1683)*uk_28 + QQ(2401696807,1)*uk_47 - QQ(9606787228,1683)*uk_54 + QQ(9606787228,1683)*uk_64 - QQ(29030443,1683)*uk_82 - QQ(5965893,187)*uk_92 + QQ(262901,99)*uk_127 + QQ(8,1)*uk_149, + uk_4: -QQ(295441,1683)*uk_19 + QQ(1602583,3366)*uk_29 - QQ(175799,1683)*uk_33 - QQ(45670,99)*uk_34 - QQ(76006,187)*uk_38 + QQ(295441,1683)*uk_41 - QQ(45670,99)*uk_44 + QQ(2401696807,1)*uk_48 - QQ(9606787228,1683)*uk_55 + QQ(74452601017,3366)*uk_65 + QQ(9606787228,1683)*uk_69 - QQ(2401696807,99)*uk_70 - QQ(4803393614,187)*uk_74 + QQ(9606787228,1683)*uk_77 - QQ(2401696807,99)*uk_80 - QQ(29030443,1683)*uk_83 + QQ(11596905,374)*uk_93 - QQ(5965893,187)*uk_97 - QQ(769658,33)*uk_98 - QQ(17335370,1683)*uk_102 + QQ(29030443,1683)*uk_105 - QQ(769658,33)*uk_108 + QQ(77314807,3366)*uk_114 + QQ(750229,198)*uk_119 + QQ(72457964,1683)*uk_123 + QQ(11596905,374)*uk_126 + QQ(31304645,306)*uk_128 + QQ(750229,198)*uk_129 - QQ(3191393,99)*uk_134 - QQ(647642,9)*uk_138 - QQ(769658,33)*uk_141 + QQ(262901,99)*uk_142 - QQ(10478626,99)*uk_143 - QQ(3191393,99)*uk_144 - QQ(20480616,187)*uk_148 - QQ(17335370,1683)*uk_151 - QQ(174199750,1683)*uk_153 - QQ(647642,9)*uk_154 + QQ(29030443,1683)*uk_157 + QQ(5965893,187)*uk_159 - QQ(769658,33)*uk_160 - QQ(10478626,99)*uk_163 - QQ(3191393,99)*uk_164, + uk_5: -QQ(295441,1683)*uk_20 - QQ(175799,1683)*uk_37 + QQ(2401696807,1)*uk_49 - QQ(9606787228,1683)*uk_56 + QQ(9606787228,1683)*uk_73 - QQ(29030443,1683)*uk_84 - QQ(5965893,187)*uk_101 + QQ(262901,99)*uk_152, + uk_6: -QQ(295441,1683)*uk_21 - QQ(175799,1683)*uk_40 + QQ(2401696807,1)*uk_50 - QQ(9606787228,1683)*uk_57 + QQ(9606787228,1683)*uk_76 - QQ(29030443,1683)*uk_85 - QQ(5965893,187)*uk_104 + QQ(262901,99)*uk_158, + uk_8: -QQ(295441,1683)*uk_23 - QQ(1602583,3366)*uk_29 + QQ(45670,99)*uk_34 + QQ(76006,187)*uk_38 - QQ(295441,1683)*uk_41 - QQ(175799,1683)*uk_43 + QQ(45670,99)*uk_44 + QQ(2401696807,1)*uk_52 - QQ(9606787228,1683)*uk_59 - QQ(74452601017,3366)*uk_65 + QQ(2401696807,99)*uk_70 + QQ(4803393614,187)*uk_74 - QQ(9606787228,1683)*uk_77 + QQ(9606787228,1683)*uk_79 + QQ(2401696807,99)*uk_80 - QQ(29030443,1683)*uk_87 - QQ(11596905,374)*uk_93 + QQ(769658,33)*uk_98 + QQ(17335370,1683)*uk_102 - QQ(29030443,1683)*uk_105 - QQ(5965893,187)*uk_107 + QQ(769658,33)*uk_108 - QQ(77314807,3366)*uk_114 - QQ(750229,198)*uk_119 - QQ(72457964,1683)*uk_123 - QQ(11596905,374)*uk_126 - QQ(31304645,306)*uk_128 - QQ(750229,198)*uk_129 + QQ(3191393,99)*uk_134 + QQ(647642,9)*uk_138 + QQ(769658,33)*uk_141 + QQ(10478626,99)*uk_143 + QQ(3191393,99)*uk_144 + QQ(20480616,187)*uk_148 + QQ(17335370,1683)*uk_151 + QQ(174199750,1683)*uk_153 + QQ(647642,9)*uk_154 - QQ(29030443,1683)*uk_157 - QQ(5965893,187)*uk_159 + QQ(769658,33)*uk_160 + QQ(262901,99)*uk_162 + QQ(10478626,99)*uk_163 + QQ(3191393,99)*uk_164, + uk_11: QQ(4,1683)*uk_18 - QQ(4,1683)*uk_28 - QQ(98072,1)*uk_47 + QQ(96847,1683)*uk_54 - QQ(568087,1683)*uk_64 + QQ(472,1683)*uk_82 + QQ(72,187)*uk_92 - QQ(104,99)*uk_127, + uk_12: QQ(4,1683)*uk_19 - QQ(31,3366)*uk_29 - QQ(4,1683)*uk_33 + QQ(1,99)*uk_34 + QQ(2,187)*uk_38 - QQ(4,1683)*uk_41 + QQ(1,99)*uk_44 - QQ(98072,1)*uk_48 + QQ(96847,1683)*uk_55 - QQ(1437649,3366)*uk_65 - QQ(568087,1683)*uk_69 + QQ(52402,99)*uk_70 + QQ(120138,187)*uk_74 - QQ(96847,1683)*uk_77 + QQ(52402,99)*uk_80 + QQ(472,1683)*uk_83 - QQ(225,374)*uk_93 + QQ(72,187)*uk_97 + QQ(17,33)*uk_98 + QQ(590,1683)*uk_102 - QQ(472,1683)*uk_105 + QQ(17,33)*uk_108 - QQ(1519,3366)*uk_114 - QQ(13,198)*uk_119 - QQ(1388,1683)*uk_123 - QQ(225,374)*uk_126 - QQ(605,306)*uk_128 - QQ(13,198)*uk_129 + QQ(68,99)*uk_134 + QQ(14,9)*uk_138 + QQ(17,33)*uk_141 - QQ(104,99)*uk_142 + QQ(229,99)*uk_143 + QQ(68,99)*uk_144 + QQ(472,187)*uk_148 + QQ(590,1683)*uk_151 + QQ(4450,1683)*uk_153 + QQ(14,9)*uk_154 - QQ(472,1683)*uk_157 - QQ(72,187)*uk_159 + QQ(17,33)*uk_160 + QQ(229,99)*uk_163 + QQ(68,99)*uk_164, + uk_13: QQ(4,1683)*uk_20 - QQ(4,1683)*uk_37 - QQ(98072,1)*uk_49 + QQ(96847,1683)*uk_56 - QQ(568087,1683)*uk_73 + QQ(472,1683)*uk_84 + QQ(72,187)*uk_101 - QQ(104,99)*uk_152, + uk_14: QQ(4,1683)*uk_21 - QQ(4,1683)*uk_40 - QQ(98072,1)*uk_50 + QQ(96847,1683)*uk_57 - QQ(568087,1683)*uk_76 + QQ(472,1683)*uk_85 + QQ(72,187)*uk_104 - QQ(104,99)*uk_158, + uk_16: QQ(4,1683)*uk_23 + QQ(31,3366)*uk_29 - QQ(1,99)*uk_34 - QQ(2,187)*uk_38 + QQ(4,1683)*uk_41 - QQ(4,1683)*uk_43 - QQ(1,99)*uk_44 - QQ(98072,1)*uk_52 + QQ(96847,1683)*uk_59 + QQ(1437649,3366)*uk_65 - QQ(52402,99)*uk_70 - QQ(120138,187)*uk_74 + QQ(96847,1683)*uk_77 - QQ(568087,1683)*uk_79 - QQ(52402,99)*uk_80 + QQ(472,1683)*uk_87 + QQ(225,374)*uk_93 - QQ(17,33)*uk_98 - QQ(590,1683)*uk_102 + QQ(472,1683)*uk_105 + QQ(72,187)*uk_107 - QQ(17,33)*uk_108 + QQ(1519,3366)*uk_114 + QQ(13,198)*uk_119 + QQ(1388,1683)*uk_123 + QQ(225,374)*uk_126 + QQ(605,306)*uk_128 + QQ(13,198)*uk_129 - QQ(68,99)*uk_134 - QQ(14,9)*uk_138 - QQ(17,33)*uk_141 - QQ(229,99)*uk_143 - QQ(68,99)*uk_144 - QQ(472,187)*uk_148 - QQ(590,1683)*uk_151 - QQ(4450,1683)*uk_153 - QQ(14,9)*uk_154 + QQ(472,1683)*uk_157 + QQ(72,187)*uk_159 - QQ(17,33)*uk_160 - QQ(104,99)*uk_162 - QQ(229,99)*uk_163 - QQ(68,99)*uk_164, + uk_24: -QQ(295441,1683)*uk_88 - QQ(175799,1683)*uk_113, + uk_26: -QQ(295441,1683)*uk_90 - QQ(175799,1683)*uk_122, uk_25: -uk_29 - QQ(295441,1683)*uk_89 - QQ(295441,1683)*uk_93 - QQ(175799,1683)*uk_118 - QQ(175799,1683)*uk_128, + uk_27: -QQ(295441,1683)*uk_91 - QQ(175799,1683)*uk_125 - QQ(4,1)*uk_149, + uk_30: -uk_34 - uk_44 - QQ(295441,1683)*uk_94 - QQ(295441,1683)*uk_98 - QQ(295441,1683)*uk_108 - QQ(175799,1683)*uk_133 - QQ(175799,1683)*uk_143 - QQ(175799,1683)*uk_163, + uk_31: -uk_38 - QQ(295441,1683)*uk_95 - QQ(295441,1683)*uk_102 - QQ(175799,1683)*uk_137 - QQ(175799,1683)*uk_153, + uk_32: -uk_41 - QQ(295441,1683)*uk_96 - QQ(295441,1683)*uk_105 - QQ(175799,1683)*uk_140 + QQ(4,1)*uk_149 - QQ(175799,1683)*uk_159, + uk_35: -QQ(295441,1683)*uk_99 - QQ(175799,1683)*uk_147, + uk_36: -QQ(295441,1683)*uk_100 - QQ(2,1)*uk_149 - QQ(175799,1683)*uk_150, + uk_39: -QQ(295441,1683)*uk_103 - QQ(175799,1683)*uk_156, + uk_60: QQ(4,1683)*uk_88 - QQ(4,1683)*uk_113, + uk_61: -uk_65 + QQ(4,1683)*uk_89 + QQ(4,1683)*uk_93 - QQ(4,1683)*uk_118 - QQ(4,1683)*uk_128, + uk_62: QQ(4,1683)*uk_90 - QQ(4,1683)*uk_122, + uk_63: QQ(4,1683)*uk_91 - QQ(4,1683)*uk_125, + uk_66: -uk_70 - uk_80 + QQ(4,1683)*uk_94 + QQ(4,1683)*uk_98 + QQ(4,1683)*uk_108 - QQ(4,1683)*uk_133 - QQ(4,1683)*uk_143 - QQ(4,1683)*uk_163, + uk_67: -uk_74 + QQ(4,1683)*uk_95 + QQ(4,1683)*uk_102 - QQ(4,1683)*uk_137 - QQ(4,1683)*uk_153, + uk_68: -uk_77 + QQ(4,1683)*uk_96 + QQ(4,1683)*uk_105 - QQ(4,1683)*uk_140 - QQ(4,1683)*uk_159, + uk_71: QQ(4,1683)*uk_99 - QQ(4,1683)*uk_147, + uk_72: QQ(4,1683)*uk_100 - QQ(4,1683)*uk_150, + uk_75: QQ(4,1683)*uk_103 - QQ(4,1683)*uk_156, + uk_109: 0, + uk_110: -uk_114, + uk_111: 0, + uk_112: 0, + uk_115: -uk_119 - uk_129, + uk_116: -uk_123, + uk_117: -uk_126, + uk_120: 0, + uk_121: 0, + uk_124: 0, + uk_130: -uk_134 - uk_144 - uk_164, + uk_131: -uk_138 - uk_154, + uk_132: -uk_141 - uk_160, + uk_135: -uk_148, + uk_136: -uk_151, + uk_139: -uk_157, + uk_145: 0, + uk_146: 0, + uk_155: 0, + } + +def time_eqs_165x165(): + if len(eqs_165x165()) != 165: + raise ValueError("length should be 165") + +def time_solve_lin_sys_165x165(): + eqs = eqs_165x165() + sol = solve_lin_sys(eqs, R_165) + if sol != sol_165x165(): + raise ValueError("Value should be equal") + +def time_verify_sol_165x165(): + eqs = eqs_165x165() + sol = sol_165x165() + zeros = [ eq.compose(sol) for eq in eqs ] + if not all(zero == 0 for zero in zeros): + raise ValueError("All should be 0") + +def time_to_expr_eqs_165x165(): + eqs = eqs_165x165() + assert [ R_165.from_expr(eq.as_expr()) for eq in eqs ] == eqs + +# Benchmark R_49: shows how fast are arithmetics in rational function fields. +F_abc, a, b, c = field("a,b,c", ZZ) +R_49, k1, k2, k3, k4, k5, k6, k7, k8, k9, k10, k11, k12, k13, k14, k15, k16, k17, k18, k19, k20, k21, k22, k23, k24, k25, k26, k27, k28, k29, k30, k31, k32, k33, k34, k35, k36, k37, k38, k39, k40, k41, k42, k43, k44, k45, k46, k47, k48, k49 = ring("k1:50", F_abc) + +def eqs_189x49(): + return [ + -b*k8/a+c*k8/a, + -b*k11/a+c*k11/a, + -b*k10/a+c*k10/a+k2, + -k3-b*k9/a+c*k9/a, + -b*k14/a+c*k14/a, + -b*k15/a+c*k15/a, + -b*k18/a+c*k18/a-k2, + -b*k17/a+c*k17/a, + -b*k16/a+c*k16/a+k4, + -b*k13/a+c*k13/a-b*k21/a+c*k21/a+b*k5/a-c*k5/a, + b*k44/a-c*k44/a, + -b*k45/a+c*k45/a, + -b*k20/a+c*k20/a, + -b*k44/a+c*k44/a, + b*k46/a-c*k46/a, + b**2*k47/a**2-2*b*c*k47/a**2+c**2*k47/a**2, + k3, + -k4, + -b*k12/a+c*k12/a-a*k6/b+c*k6/b, + -b*k19/a+c*k19/a+a*k7/c-b*k7/c, + b*k45/a-c*k45/a, + -b*k46/a+c*k46/a, + -k48+c*k48/a+c*k48/b-c**2*k48/(a*b), + -k49+b*k49/a+b*k49/c-b**2*k49/(a*c), + a*k1/b-c*k1/b, + a*k4/b-c*k4/b, + a*k3/b-c*k3/b+k9, + -k10+a*k2/b-c*k2/b, + a*k7/b-c*k7/b, + -k9, + k11, + b*k12/a-c*k12/a+a*k6/b-c*k6/b, + a*k15/b-c*k15/b, + k10+a*k18/b-c*k18/b, + -k11+a*k17/b-c*k17/b, + a*k16/b-c*k16/b, + -a*k13/b+c*k13/b+a*k21/b-c*k21/b+a*k5/b-c*k5/b, + -a*k44/b+c*k44/b, + a*k45/b-c*k45/b, + a*k14/c-b*k14/c+a*k20/b-c*k20/b, + a*k44/b-c*k44/b, + -a*k46/b+c*k46/b, + -k47+c*k47/a+c*k47/b-c**2*k47/(a*b), + a*k19/b-c*k19/b, + -a*k45/b+c*k45/b, + a*k46/b-c*k46/b, + a**2*k48/b**2-2*a*c*k48/b**2+c**2*k48/b**2, + -k49+a*k49/b+a*k49/c-a**2*k49/(b*c), + k16, + -k17, + -a*k1/c+b*k1/c, + -k16-a*k4/c+b*k4/c, + -a*k3/c+b*k3/c, + k18-a*k2/c+b*k2/c, + b*k19/a-c*k19/a-a*k7/c+b*k7/c, + -a*k6/c+b*k6/c, + -a*k8/c+b*k8/c, + -a*k11/c+b*k11/c+k17, + -a*k10/c+b*k10/c-k18, + -a*k9/c+b*k9/c, + -a*k14/c+b*k14/c-a*k20/b+c*k20/b, + -a*k13/c+b*k13/c+a*k21/c-b*k21/c-a*k5/c+b*k5/c, + a*k44/c-b*k44/c, + -a*k45/c+b*k45/c, + -a*k44/c+b*k44/c, + a*k46/c-b*k46/c, + -k47+b*k47/a+b*k47/c-b**2*k47/(a*c), + -a*k12/c+b*k12/c, + a*k45/c-b*k45/c, + -a*k46/c+b*k46/c, + -k48+a*k48/b+a*k48/c-a**2*k48/(b*c), + a**2*k49/c**2-2*a*b*k49/c**2+b**2*k49/c**2, + k8, + k11, + -k15, + k10-k18, + -k17, + k9, + -k16, + -k29, + k14-k32, + -k21+k23-k31, + -k24-k30, + -k35, + k44, + -k45, + k36, + k13-k23+k39, + -k20+k38, + k25+k37, + b*k26/a-c*k26/a-k34+k42, + -2*k44, + k45, + k46, + b*k47/a-c*k47/a, + k41, + k44, + -k46, + -b*k47/a+c*k47/a, + k12+k24, + -k19-k25, + -a*k27/b+c*k27/b-k33, + k45, + -k46, + -a*k48/b+c*k48/b, + a*k28/c-b*k28/c+k40, + -k45, + k46, + a*k48/b-c*k48/b, + a*k49/c-b*k49/c, + -a*k49/c+b*k49/c, + -k1, + -k4, + -k3, + k15, + k18-k2, + k17, + k16, + k22, + k25-k7, + k24+k30, + k21+k23-k31, + k28, + -k44, + k45, + -k30-k6, + k20+k32, + k27+b*k33/a-c*k33/a, + k44, + -k46, + -b*k47/a+c*k47/a, + -k36, + k31-k39-k5, + -k32-k38, + k19-k37, + k26-a*k34/b+c*k34/b-k42, + k44, + -2*k45, + k46, + a*k48/b-c*k48/b, + a*k35/c-b*k35/c-k41, + -k44, + k46, + b*k47/a-c*k47/a, + -a*k49/c+b*k49/c, + -k40, + k45, + -k46, + -a*k48/b+c*k48/b, + a*k49/c-b*k49/c, + k1, + k4, + k3, + -k8, + -k11, + -k10+k2, + -k9, + k37+k7, + -k14-k38, + -k22, + -k25-k37, + -k24+k6, + -k13-k23+k39, + -k28+b*k40/a-c*k40/a, + k44, + -k45, + -k27, + -k44, + k46, + b*k47/a-c*k47/a, + k29, + k32+k38, + k31-k39+k5, + -k12+k30, + k35-a*k41/b+c*k41/b, + -k44, + k45, + -k26+k34+a*k42/c-b*k42/c, + k44, + k45, + -2*k46, + -b*k47/a+c*k47/a, + -a*k48/b+c*k48/b, + a*k49/c-b*k49/c, + k33, + -k45, + k46, + a*k48/b-c*k48/b, + -a*k49/c+b*k49/c, + ] + +def sol_189x49(): + return { + k49: 0, k48: 0, k47: 0, k46: 0, k45: 0, k44: 0, k41: 0, k40: 0, + k38: 0, k37: 0, k36: 0, k35: 0, k33: 0, k32: 0, k30: 0, k29: 0, + k28: 0, k27: 0, k25: 0, k24: 0, k22: 0, k21: 0, k20: 0, k19: 0, + k18: 0, k17: 0, k16: 0, k15: 0, k14: 0, k13: 0, k12: 0, k11: 0, + k10: 0, k9: 0, k8: 0, k7: 0, k6: 0, k5: 0, k4: 0, k3: 0, + k2: 0, k1: 0, + k34: b/c*k42, + k31: k39, + k26: a/c*k42, + k23: k39, + } + +def time_eqs_189x49(): + if len(eqs_189x49()) != 189: + raise ValueError("Length should be equal to 189") + +def time_solve_lin_sys_189x49(): + eqs = eqs_189x49() + sol = solve_lin_sys(eqs, R_49) + if sol != sol_189x49(): + raise ValueError("Values should be equal") + +def time_verify_sol_189x49(): + eqs = eqs_189x49() + sol = sol_189x49() + zeros = [ eq.compose(sol) for eq in eqs ] + assert all(zero == 0 for zero in zeros) + +def time_to_expr_eqs_189x49(): + eqs = eqs_189x49() + assert [ R_49.from_expr(eq.as_expr()) for eq in eqs ] == eqs + +# Benchmark R_8: shows how fast polynomial GCDs are computed. + +F_a5_5, a_11, a_12, a_13, a_14, a_21, a_22, a_23, a_24, a_31, a_32, a_33, a_34, a_41, a_42, a_43, a_44 = field("a_(1:5)(1:5)", ZZ) +R_8, x0, x1, x2, x3, x4, x5, x6, x7 = ring("x:8", F_a5_5) + +def eqs_10x8(): + return [ + (a_33*a_34 + a_33*a_44 + a_43*a_44)*x3 + (a_33*a_34 + a_33*a_44 + a_43*a_44)*x4 + (a_12*a_34 + a_12*a_44 + a_22*a_34 + a_22*a_44)*x5 + (a_12*a_44 + a_22*a_44)*x6 + (a_12*a_33 + a_22*a_33)*x7 - a_12*a_33 - a_12*a_43 - a_22*a_33 - a_22*a_43, + (a_33 + a_34 + a_43 + a_44)*x3 + (a_33 + a_34 + a_43 + a_44)*x4 + (a_12 + a_22 + a_34 + a_44)*x5 + (a_12 + a_22 + a_44)*x6 + (a_12 + a_22 + a_33)*x7 - a_12 - a_22 - a_33 - a_43, + x3 + x4 + x5 + x6 + x7 - 1, + (a_12*a_33*a_34 + a_12*a_33*a_44 + a_12*a_43*a_44 + a_22*a_33*a_34 + a_22*a_33*a_44 + a_22*a_43*a_44)*x0 + (a_22*a_33*a_34 + a_22*a_33*a_44 + a_22*a_43*a_44)*x1 + (a_12*a_33*a_34 + a_12*a_33*a_44 + a_12*a_43*a_44 + a_22*a_33*a_34 + a_22*a_33*a_44 + a_22*a_43*a_44)*x2 + (a_11*a_33*a_34 + a_11*a_33*a_44 + a_11*a_43*a_44 + a_31*a_33*a_34 + a_31*a_33*a_44 + a_31*a_43*a_44)*x3 + (a_11*a_33*a_34 + a_11*a_33*a_44 + a_11*a_43*a_44 + a_21*a_33*a_34 + a_21*a_33*a_44 + a_21*a_43*a_44 + a_31*a_33*a_34 + a_31*a_33*a_44 + a_31*a_43*a_44)*x4 + (a_11*a_12*a_34 + a_11*a_12*a_44 + a_11*a_22*a_34 + a_11*a_22*a_44 + a_12*a_31*a_34 + a_12*a_31*a_44 + a_21*a_22*a_34 + a_21*a_22*a_44 + a_22*a_31*a_34 + a_22*a_31*a_44)*x5 + (a_11*a_12*a_44 + a_11*a_22*a_44 + a_12*a_31*a_44 + a_21*a_22*a_44 + a_22*a_31*a_44)*x6 + (a_11*a_12*a_33 + a_11*a_22*a_33 + a_12*a_31*a_33 + a_21*a_22*a_33 + a_22*a_31*a_33)*x7 - a_11*a_12*a_33 - a_11*a_12*a_43 - a_11*a_22*a_33 - a_11*a_22*a_43 - a_12*a_31*a_33 - a_12*a_31*a_43 - a_21*a_22*a_33 - a_21*a_22*a_43 - a_22*a_31*a_33 - a_22*a_31*a_43, + (a_12*a_33 + a_12*a_34 + a_12*a_43 + a_12*a_44 + a_22*a_33 + a_22*a_34 + a_22*a_43 + a_22*a_44 + a_33*a_34 + a_33*a_44 + a_43*a_44)*x0 + (a_22*a_33 + a_22*a_34 + a_22*a_43 + a_22*a_44 + a_33*a_34 + a_33*a_44 + a_43*a_44)*x1 + (a_12*a_33 + a_12*a_34 + a_12*a_43 + a_12*a_44 + a_22*a_33 + a_22*a_34 + a_22*a_43 + a_22*a_44 + a_33*a_34 + a_33*a_44 + a_43*a_44)*x2 + (a_11*a_33 + a_11*a_34 + a_11*a_43 + a_11*a_44 + a_31*a_33 + a_31*a_34 + a_31*a_43 + a_31*a_44 + a_33*a_34 + a_33*a_44 + a_43*a_44)*x3 + (a_11*a_33 + a_11*a_34 + a_11*a_43 + a_11*a_44 + a_21*a_33 + a_21*a_34 + a_21*a_43 + a_21*a_44 + a_31*a_33 + a_31*a_34 + a_31*a_43 + a_31*a_44 + a_33*a_34 + a_33*a_44 + a_43*a_44)*x4 + (a_11*a_12 + a_11*a_22 + a_11*a_34 + a_11*a_44 + a_12*a_31 + a_12*a_34 + a_12*a_44 + a_21*a_22 + a_21*a_34 + a_21*a_44 + a_22*a_31 + a_22*a_34 + a_22*a_44 + a_31*a_34 + a_31*a_44)*x5 + (a_11*a_12 + a_11*a_22 + a_11*a_44 + a_12*a_31 + a_12*a_44 + a_21*a_22 + a_21*a_44 + a_22*a_31 + a_22*a_44 + a_31*a_44)*x6 + (a_11*a_12 + a_11*a_22 + a_11*a_33 + a_12*a_31 + a_12*a_33 + a_21*a_22 + a_21*a_33 + a_22*a_31 + a_22*a_33 + a_31*a_33)*x7 - a_11*a_12 - a_11*a_22 - a_11*a_33 - a_11*a_43 - a_12*a_31 - a_12*a_33 - a_12*a_43 - a_21*a_22 - a_21*a_33 - a_21*a_43 - a_22*a_31 - a_22*a_33 - a_22*a_43 - a_31*a_33 - a_31*a_43, + (a_12 + a_22 + a_33 + a_34 + a_43 + a_44)*x0 + (a_22 + a_33 + a_34 + a_43 + a_44)*x1 + (a_12 + a_22 + a_33 + a_34 + a_43 + a_44)*x2 + (a_11 + a_31 + a_33 + a_34 + a_43 + a_44)*x3 + (a_11 + a_21 + a_31 + a_33 + a_34 + a_43 + a_44)*x4 + (a_11 + a_12 + a_21 + a_22 + a_31 + a_34 + a_44)*x5 + (a_11 + a_12 + a_21 + a_22 + a_31 + a_44)*x6 + (a_11 + a_12 + a_21 + a_22 + a_31 + a_33)*x7 - a_11 - a_12 - a_21 - a_22 - a_31 - a_33 - a_43, + x0 + x1 + x2 + x3 + x4 + x5 + x6 + x7 - 1, + (a_12*a_34 + a_12*a_44 + a_22*a_34 + a_22*a_44)*x2 + (a_31*a_34 + a_31*a_44)*x3 + (a_31*a_34 + a_31*a_44)*x4 + (a_12*a_31 + a_22*a_31)*x7 - a_12*a_31 - a_22*a_31, + (a_12 + a_22 + a_34 + a_44)*x2 + a_31*x3 + a_31*x4 + a_31*x7 - a_31, + x2, + ] + +def sol_10x8(): + return { + x0: -a_21/a_12*x4, + x1: a_21/a_12*x4, + x2: 0, + x3: -x4, + x5: a_43/a_34, + x6: -a_43/a_34, + x7: 1, + } + +def time_eqs_10x8(): + if len(eqs_10x8()) != 10: + raise ValueError("Value should be equal to 10") + +def time_solve_lin_sys_10x8(): + eqs = eqs_10x8() + sol = solve_lin_sys(eqs, R_8) + if sol != sol_10x8(): + raise ValueError("Values should be equal") + +def time_verify_sol_10x8(): + eqs = eqs_10x8() + sol = sol_10x8() + zeros = [ eq.compose(sol) for eq in eqs ] + if not all(zero == 0 for zero in zeros): + raise ValueError("All values in zero should be 0") + +def time_to_expr_eqs_10x8(): + eqs = eqs_10x8() + assert [ R_8.from_expr(eq.as_expr()) for eq in eqs ] == eqs diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__init__.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e4ebc3d71ba3dac9ccc695d046d6b3d2ad940fa1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__init__.py @@ -0,0 +1,15 @@ +""" + +sympy.polys.matrices package. + +The main export from this package is the DomainMatrix class which is a +lower-level implementation of matrices based on the polys Domains. This +implementation is typically a lot faster than SymPy's standard Matrix class +but is a work in progress and is still experimental. + +""" +from .domainmatrix import DomainMatrix, DM + +__all__ = [ + 'DomainMatrix', 'DM', +] diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26ddb4a842bdd5529c346f57f32b98ffc8df4f86 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/_dfm.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/_dfm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24c1a084b2556b7cca8561092a6d2ef288e1c891 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/_dfm.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/_typing.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/_typing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e9ed767adfe36dbb30d9b9bb21b303fcf7f3326 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/_typing.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/ddm.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/ddm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32c26449ff4ed2b1b19de1b9323d6efa0baf8a60 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/ddm.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/dense.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/dense.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efe5200b1d2d00492283e8f3cb27ce9da9454e02 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/dense.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/dfm.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/dfm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..381b4a705effd70d32c08d024240ba38d26ec95d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/dfm.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/domainscalar.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/domainscalar.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1378d89104edab00a2ab562aa0090e901d517d87 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/domainscalar.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/eigen.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/eigen.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..982a2d8d672a3e23888be9d275e0126f089a1333 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/eigen.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/exceptions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/exceptions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c5f5f2307d35193b38ccbf739953481b4fde47a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/exceptions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/linsolve.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/linsolve.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a53f6676d3d8e4955e37073251dc638d804a314 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/linsolve.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/lll.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/lll.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c499430ad7fda7af1a308d142efb292a7c1c90f5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/lll.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/normalforms.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/normalforms.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d86662d24df8fd4b6abeb060e005f317053f85d0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/normalforms.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/rref.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/rref.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2880c03d348020a36dcf2acbf7162b1900da31b6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/rref.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/sdm.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/sdm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90889f78328dc4b3a3d06fb41844cec6f554e5d7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/__pycache__/sdm.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/_dfm.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/_dfm.py new file mode 100644 index 0000000000000000000000000000000000000000..1d02076014168ed4966fecd07f3d7a1d4828ae63 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/_dfm.py @@ -0,0 +1,951 @@ +# +# sympy.polys.matrices.dfm +# +# This modules defines the DFM class which is a wrapper for dense flint +# matrices as found in python-flint. +# +# As of python-flint 0.4.1 matrices over the following domains can be supported +# by python-flint: +# +# ZZ: flint.fmpz_mat +# QQ: flint.fmpq_mat +# GF(p): flint.nmod_mat (p prime and p < ~2**62) +# +# The underlying flint library has many more domains, but these are not yet +# supported by python-flint. +# +# The DFM class is a wrapper for the flint matrices and provides a common +# interface for all supported domains that is interchangeable with the DDM +# and SDM classes so that DomainMatrix can be used with any as its internal +# matrix representation. +# + +# TODO: +# +# Implement the following methods that are provided by python-flint: +# +# - hnf (Hermite normal form) +# - snf (Smith normal form) +# - minpoly +# - is_hnf +# - is_snf +# - rank +# +# The other types DDM and SDM do not have these methods and the algorithms +# for hnf, snf and rank are already implemented. Algorithms for minpoly, +# is_hnf and is_snf would need to be added. +# +# Add more methods to python-flint to expose more of Flint's functionality +# and also to make some of the above methods simpler or more efficient e.g. +# slicing, fancy indexing etc. + +from sympy.external.gmpy import GROUND_TYPES +from sympy.external.importtools import import_module +from sympy.utilities.decorator import doctest_depends_on + +from sympy.polys.domains import ZZ, QQ + +from .exceptions import ( + DMBadInputError, + DMDomainError, + DMNonSquareMatrixError, + DMNonInvertibleMatrixError, + DMRankError, + DMShapeError, + DMValueError, +) + + +if GROUND_TYPES != 'flint': + __doctest_skip__ = ['*'] + + +flint = import_module('flint') + + +__all__ = ['DFM'] + + +@doctest_depends_on(ground_types=['flint']) +class DFM: + """ + Dense FLINT matrix. This class is a wrapper for matrices from python-flint. + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.matrices.dfm import DFM + >>> dfm = DFM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> dfm + [[1, 2], [3, 4]] + >>> dfm.rep + [1, 2] + [3, 4] + >>> type(dfm.rep) # doctest: +SKIP + + + Usually, the DFM class is not instantiated directly, but is created as the + internal representation of :class:`~.DomainMatrix`. When + `SYMPY_GROUND_TYPES` is set to `flint` and `python-flint` is installed, the + :class:`DFM` class is used automatically as the internal representation of + :class:`~.DomainMatrix` in dense format if the domain is supported by + python-flint. + + >>> from sympy.polys.matrices.domainmatrix import DM + >>> dM = DM([[1, 2], [3, 4]], ZZ) + >>> dM.rep + [[1, 2], [3, 4]] + + A :class:`~.DomainMatrix` can be converted to :class:`DFM` by calling the + :meth:`to_dfm` method: + + >>> dM.to_dfm() + [[1, 2], [3, 4]] + + """ + + fmt = 'dense' + is_DFM = True + is_DDM = False + + def __new__(cls, rowslist, shape, domain): + """Construct from a nested list.""" + flint_mat = cls._get_flint_func(domain) + + if 0 not in shape: + try: + rep = flint_mat(rowslist) + except (ValueError, TypeError): + raise DMBadInputError(f"Input should be a list of list of {domain}") + else: + rep = flint_mat(*shape) + + return cls._new(rep, shape, domain) + + @classmethod + def _new(cls, rep, shape, domain): + """Internal constructor from a flint matrix.""" + cls._check(rep, shape, domain) + obj = object.__new__(cls) + obj.rep = rep + obj.shape = obj.rows, obj.cols = shape + obj.domain = domain + return obj + + def _new_rep(self, rep): + """Create a new DFM with the same shape and domain but a new rep.""" + return self._new(rep, self.shape, self.domain) + + @classmethod + def _check(cls, rep, shape, domain): + repshape = (rep.nrows(), rep.ncols()) + if repshape != shape: + raise DMBadInputError("Shape of rep does not match shape of DFM") + if domain == ZZ and not isinstance(rep, flint.fmpz_mat): + raise RuntimeError("Rep is not a flint.fmpz_mat") + elif domain == QQ and not isinstance(rep, flint.fmpq_mat): + raise RuntimeError("Rep is not a flint.fmpq_mat") + elif domain.is_FF and not isinstance(rep, (flint.fmpz_mod_mat, flint.nmod_mat)): + raise RuntimeError("Rep is not a flint.fmpz_mod_mat or flint.nmod_mat") + elif domain not in (ZZ, QQ) and not domain.is_FF: + raise NotImplementedError("Only ZZ and QQ are supported by DFM") + + @classmethod + def _supports_domain(cls, domain): + """Return True if the given domain is supported by DFM.""" + return domain in (ZZ, QQ) or domain.is_FF and domain._is_flint + + @classmethod + def _get_flint_func(cls, domain): + """Return the flint matrix class for the given domain.""" + if domain == ZZ: + return flint.fmpz_mat + elif domain == QQ: + return flint.fmpq_mat + elif domain.is_FF: + c = domain.characteristic() + if isinstance(domain.one, flint.nmod): + _cls = flint.nmod_mat + def _func(*e): + if len(e) == 1 and isinstance(e[0], flint.nmod_mat): + return _cls(e[0]) + else: + return _cls(*e, c) + else: + m = flint.fmpz_mod_ctx(c) + _func = lambda *e: flint.fmpz_mod_mat(*e, m) + return _func + else: + raise NotImplementedError("Only ZZ and QQ are supported by DFM") + + @property + def _func(self): + """Callable to create a flint matrix of the same domain.""" + return self._get_flint_func(self.domain) + + def __str__(self): + """Return ``str(self)``.""" + return str(self.to_ddm()) + + def __repr__(self): + """Return ``repr(self)``.""" + return f'DFM{repr(self.to_ddm())[3:]}' + + def __eq__(self, other): + """Return ``self == other``.""" + if not isinstance(other, DFM): + return NotImplemented + # Compare domains first because we do *not* want matrices with + # different domains to be equal but e.g. a flint fmpz_mat and fmpq_mat + # with the same entries will compare equal. + return self.domain == other.domain and self.rep == other.rep + + @classmethod + def from_list(cls, rowslist, shape, domain): + """Construct from a nested list.""" + return cls(rowslist, shape, domain) + + def to_list(self): + """Convert to a nested list.""" + return self.rep.tolist() + + def copy(self): + """Return a copy of self.""" + return self._new_rep(self._func(self.rep)) + + def to_ddm(self): + """Convert to a DDM.""" + return DDM.from_list(self.to_list(), self.shape, self.domain) + + def to_sdm(self): + """Convert to a SDM.""" + return SDM.from_list(self.to_list(), self.shape, self.domain) + + def to_dfm(self): + """Return self.""" + return self + + def to_dfm_or_ddm(self): + """ + Convert to a :class:`DFM`. + + This :class:`DFM` method exists to parallel the :class:`~.DDM` and + :class:`~.SDM` methods. For :class:`DFM` it will always return self. + + See Also + ======== + + to_ddm + to_sdm + sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm + """ + return self + + @classmethod + def from_ddm(cls, ddm): + """Convert from a DDM.""" + return cls.from_list(ddm.to_list(), ddm.shape, ddm.domain) + + @classmethod + def from_list_flat(cls, elements, shape, domain): + """Inverse of :meth:`to_list_flat`.""" + func = cls._get_flint_func(domain) + try: + rep = func(*shape, elements) + except ValueError: + raise DMBadInputError(f"Incorrect number of elements for shape {shape}") + except TypeError: + raise DMBadInputError(f"Input should be a list of {domain}") + return cls(rep, shape, domain) + + def to_list_flat(self): + """Convert to a flat list.""" + return self.rep.entries() + + def to_flat_nz(self): + """Convert to a flat list of non-zeros.""" + return self.to_ddm().to_flat_nz() + + @classmethod + def from_flat_nz(cls, elements, data, domain): + """Inverse of :meth:`to_flat_nz`.""" + return DDM.from_flat_nz(elements, data, domain).to_dfm() + + def to_dod(self): + """Convert to a DOD.""" + return self.to_ddm().to_dod() + + @classmethod + def from_dod(cls, dod, shape, domain): + """Inverse of :meth:`to_dod`.""" + return DDM.from_dod(dod, shape, domain).to_dfm() + + def to_dok(self): + """Convert to a DOK.""" + return self.to_ddm().to_dok() + + @classmethod + def from_dok(cls, dok, shape, domain): + """Inverse of :math:`to_dod`.""" + return DDM.from_dok(dok, shape, domain).to_dfm() + + def iter_values(self): + """Iterate over the non-zero values of the matrix.""" + m, n = self.shape + rep = self.rep + for i in range(m): + for j in range(n): + repij = rep[i, j] + if repij: + yield rep[i, j] + + def iter_items(self): + """Iterate over indices and values of nonzero elements of the matrix.""" + m, n = self.shape + rep = self.rep + for i in range(m): + for j in range(n): + repij = rep[i, j] + if repij: + yield ((i, j), repij) + + def convert_to(self, domain): + """Convert to a new domain.""" + if domain == self.domain: + return self.copy() + elif domain == QQ and self.domain == ZZ: + return self._new(flint.fmpq_mat(self.rep), self.shape, domain) + elif self._supports_domain(domain): + # XXX: Use more efficient conversions when possible. + return self.to_ddm().convert_to(domain).to_dfm() + else: + # It is the callers responsibility to convert to DDM before calling + # this method if the domain is not supported by DFM. + raise NotImplementedError("Only ZZ and QQ are supported by DFM") + + def getitem(self, i, j): + """Get the ``(i, j)``-th entry.""" + # XXX: flint matrices do not support negative indices + # XXX: They also raise ValueError instead of IndexError + m, n = self.shape + if i < 0: + i += m + if j < 0: + j += n + try: + return self.rep[i, j] + except ValueError: + raise IndexError(f"Invalid indices ({i}, {j}) for Matrix of shape {self.shape}") + + def setitem(self, i, j, value): + """Set the ``(i, j)``-th entry.""" + # XXX: flint matrices do not support negative indices + # XXX: They also raise ValueError instead of IndexError + m, n = self.shape + if i < 0: + i += m + if j < 0: + j += n + try: + self.rep[i, j] = value + except ValueError: + raise IndexError(f"Invalid indices ({i}, {j}) for Matrix of shape {self.shape}") + + def _extract(self, i_indices, j_indices): + """Extract a submatrix with no checking.""" + # Indices must be positive and in range. + M = self.rep + lol = [[M[i, j] for j in j_indices] for i in i_indices] + shape = (len(i_indices), len(j_indices)) + return self.from_list(lol, shape, self.domain) + + def extract(self, rowslist, colslist): + """Extract a submatrix.""" + # XXX: flint matrices do not support fancy indexing or negative indices + # + # Check and convert negative indices before calling _extract. + m, n = self.shape + + new_rows = [] + new_cols = [] + + for i in rowslist: + if i < 0: + i_pos = i + m + else: + i_pos = i + if not 0 <= i_pos < m: + raise IndexError(f"Invalid row index {i} for Matrix of shape {self.shape}") + new_rows.append(i_pos) + + for j in colslist: + if j < 0: + j_pos = j + n + else: + j_pos = j + if not 0 <= j_pos < n: + raise IndexError(f"Invalid column index {j} for Matrix of shape {self.shape}") + new_cols.append(j_pos) + + return self._extract(new_rows, new_cols) + + def extract_slice(self, rowslice, colslice): + """Slice a DFM.""" + # XXX: flint matrices do not support slicing + m, n = self.shape + i_indices = range(m)[rowslice] + j_indices = range(n)[colslice] + return self._extract(i_indices, j_indices) + + def neg(self): + """Negate a DFM matrix.""" + return self._new_rep(-self.rep) + + def add(self, other): + """Add two DFM matrices.""" + return self._new_rep(self.rep + other.rep) + + def sub(self, other): + """Subtract two DFM matrices.""" + return self._new_rep(self.rep - other.rep) + + def mul(self, other): + """Multiply a DFM matrix from the right by a scalar.""" + return self._new_rep(self.rep * other) + + def rmul(self, other): + """Multiply a DFM matrix from the left by a scalar.""" + return self._new_rep(other * self.rep) + + def mul_elementwise(self, other): + """Elementwise multiplication of two DFM matrices.""" + # XXX: flint matrices do not support elementwise multiplication + return self.to_ddm().mul_elementwise(other.to_ddm()).to_dfm() + + def matmul(self, other): + """Multiply two DFM matrices.""" + shape = (self.rows, other.cols) + return self._new(self.rep * other.rep, shape, self.domain) + + # XXX: For the most part DomainMatrix does not expect DDM, SDM, or DFM to + # have arithmetic operators defined. The only exception is negation. + # Perhaps that should be removed. + + def __neg__(self): + """Negate a DFM matrix.""" + return self.neg() + + @classmethod + def zeros(cls, shape, domain): + """Return a zero DFM matrix.""" + func = cls._get_flint_func(domain) + return cls._new(func(*shape), shape, domain) + + # XXX: flint matrices do not have anything like ones or eye + # In the methods below we convert to DDM and then back to DFM which is + # probably about as efficient as implementing these methods directly. + + @classmethod + def ones(cls, shape, domain): + """Return a one DFM matrix.""" + # XXX: flint matrices do not have anything like ones + return DDM.ones(shape, domain).to_dfm() + + @classmethod + def eye(cls, n, domain): + """Return the identity matrix of size n.""" + # XXX: flint matrices do not have anything like eye + return DDM.eye(n, domain).to_dfm() + + @classmethod + def diag(cls, elements, domain): + """Return a diagonal matrix.""" + return DDM.diag(elements, domain).to_dfm() + + def applyfunc(self, func, domain): + """Apply a function to each entry of a DFM matrix.""" + return self.to_ddm().applyfunc(func, domain).to_dfm() + + def transpose(self): + """Transpose a DFM matrix.""" + return self._new(self.rep.transpose(), (self.cols, self.rows), self.domain) + + def hstack(self, *others): + """Horizontally stack matrices.""" + return self.to_ddm().hstack(*[o.to_ddm() for o in others]).to_dfm() + + def vstack(self, *others): + """Vertically stack matrices.""" + return self.to_ddm().vstack(*[o.to_ddm() for o in others]).to_dfm() + + def diagonal(self): + """Return the diagonal of a DFM matrix.""" + M = self.rep + m, n = self.shape + return [M[i, i] for i in range(min(m, n))] + + def is_upper(self): + """Return ``True`` if the matrix is upper triangular.""" + M = self.rep + for i in range(self.rows): + for j in range(min(i, self.cols)): + if M[i, j]: + return False + return True + + def is_lower(self): + """Return ``True`` if the matrix is lower triangular.""" + M = self.rep + for i in range(self.rows): + for j in range(i + 1, self.cols): + if M[i, j]: + return False + return True + + def is_diagonal(self): + """Return ``True`` if the matrix is diagonal.""" + return self.is_upper() and self.is_lower() + + def is_zero_matrix(self): + """Return ``True`` if the matrix is the zero matrix.""" + M = self.rep + for i in range(self.rows): + for j in range(self.cols): + if M[i, j]: + return False + return True + + def nnz(self): + """Return the number of non-zero elements in the matrix.""" + return self.to_ddm().nnz() + + def scc(self): + """Return the strongly connected components of the matrix.""" + return self.to_ddm().scc() + + @doctest_depends_on(ground_types='flint') + def det(self): + """ + Compute the determinant of the matrix using FLINT. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2], [3, 4]]) + >>> dfm = M.to_DM().to_dfm() + >>> dfm + [[1, 2], [3, 4]] + >>> dfm.det() + -2 + + Notes + ===== + + Calls the ``.det()`` method of the underlying FLINT matrix. + + For :ref:`ZZ` or :ref:`QQ` this calls ``fmpz_mat_det`` or + ``fmpq_mat_det`` respectively. + + At the time of writing the implementation of ``fmpz_mat_det`` uses one + of several algorithms depending on the size of the matrix and bit size + of the entries. The algorithms used are: + + - Cofactor for very small (up to 4x4) matrices. + - Bareiss for small (up to 25x25) matrices. + - Modular algorithms for larger matrices (up to 60x60) or for larger + matrices with large bit sizes. + - Modular "accelerated" for larger matrices (60x60 upwards) if the bit + size is smaller than the dimensions of the matrix. + + The implementation of ``fmpq_mat_det`` clears denominators from each + row (not the whole matrix) and then calls ``fmpz_mat_det`` and divides + by the product of the denominators. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.det + Higher level interface to compute the determinant of a matrix. + """ + # XXX: At least the first three algorithms described above should also + # be implemented in the pure Python DDM and SDM classes which at the + # time of writng just use Bareiss for all matrices and domains. + # Probably in Python the thresholds would be different though. + return self.rep.det() + + @doctest_depends_on(ground_types='flint') + def charpoly(self): + """ + Compute the characteristic polynomial of the matrix using FLINT. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2], [3, 4]]) + >>> dfm = M.to_DM().to_dfm() # need ground types = 'flint' + >>> dfm + [[1, 2], [3, 4]] + >>> dfm.charpoly() + [1, -5, -2] + + Notes + ===== + + Calls the ``.charpoly()`` method of the underlying FLINT matrix. + + For :ref:`ZZ` or :ref:`QQ` this calls ``fmpz_mat_charpoly`` or + ``fmpq_mat_charpoly`` respectively. + + At the time of writing the implementation of ``fmpq_mat_charpoly`` + clears a denominator from the whole matrix and then calls + ``fmpz_mat_charpoly``. The coefficients of the characteristic + polynomial are then multiplied by powers of the denominator. + + The ``fmpz_mat_charpoly`` method uses a modular algorithm with CRT + reconstruction. The modular algorithm uses ``nmod_mat_charpoly`` which + uses Berkowitz for small matrices and non-prime moduli or otherwise + the Danilevsky method. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly + Higher level interface to compute the characteristic polynomial of + a matrix. + """ + # FLINT polynomial coefficients are in reverse order compared to SymPy. + return self.rep.charpoly().coeffs()[::-1] + + @doctest_depends_on(ground_types='flint') + def inv(self): + """ + Compute the inverse of a matrix using FLINT. + + Examples + ======== + + >>> from sympy import Matrix, QQ + >>> M = Matrix([[1, 2], [3, 4]]) + >>> dfm = M.to_DM().to_dfm().convert_to(QQ) + >>> dfm + [[1, 2], [3, 4]] + >>> dfm.inv() + [[-2, 1], [3/2, -1/2]] + >>> dfm.matmul(dfm.inv()) + [[1, 0], [0, 1]] + + Notes + ===== + + Calls the ``.inv()`` method of the underlying FLINT matrix. + + For now this will raise an error if the domain is :ref:`ZZ` but will + use the FLINT method for :ref:`QQ`. + + The FLINT methods for :ref:`ZZ` and :ref:`QQ` are ``fmpz_mat_inv`` and + ``fmpq_mat_inv`` respectively. The ``fmpz_mat_inv`` method computes an + inverse with denominator. This is implemented by calling + ``fmpz_mat_solve`` (see notes in :meth:`lu_solve` about the algorithm). + + The ``fmpq_mat_inv`` method clears denominators from each row and then + multiplies those into the rhs identity matrix before calling + ``fmpz_mat_solve``. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.inv + Higher level method for computing the inverse of a matrix. + """ + # TODO: Implement similar algorithms for DDM and SDM. + # + # XXX: The flint fmpz_mat and fmpq_mat inv methods both return fmpq_mat + # by default. The fmpz_mat method has an optional argument to return + # fmpz_mat instead for unimodular matrices. + # + # The convention in DomainMatrix is to raise an error if the matrix is + # not over a field regardless of whether the matrix is invertible over + # its domain or over any associated field. Maybe DomainMatrix.inv + # should be changed to always return a matrix over an associated field + # except with a unimodular argument for returning an inverse over a + # ring if possible. + # + # For now we follow the existing DomainMatrix convention... + K = self.domain + m, n = self.shape + + if m != n: + raise DMNonSquareMatrixError("cannot invert a non-square matrix") + + if K == ZZ: + raise DMDomainError("field expected, got %s" % K) + elif K == QQ or K.is_FF: + try: + return self._new_rep(self.rep.inv()) + except ZeroDivisionError: + raise DMNonInvertibleMatrixError("matrix is not invertible") + else: + # If more domains are added for DFM then we will need to consider + # what happens here. + raise NotImplementedError("DFM.inv() is not implemented for %s" % K) + + def lu(self): + """Return the LU decomposition of the matrix.""" + L, U, swaps = self.to_ddm().lu() + return L.to_dfm(), U.to_dfm(), swaps + + def qr(self): + """Return the QR decomposition of the matrix.""" + Q, R = self.to_ddm().qr() + return Q.to_dfm(), R.to_dfm() + + # XXX: The lu_solve function should be renamed to solve. Whether or not it + # uses an LU decomposition is an implementation detail. A method called + # lu_solve would make sense for a situation in which an LU decomposition is + # reused several times to solve with different rhs but that would imply a + # different call signature. + # + # The underlying python-flint method has an algorithm= argument so we could + # use that and have e.g. solve_lu and solve_modular or perhaps also a + # method= argument to choose between the two. Flint itself has more + # possible algorithms to choose from than are exposed by python-flint. + + @doctest_depends_on(ground_types='flint') + def lu_solve(self, rhs): + """ + Solve a matrix equation using FLINT. + + Examples + ======== + + >>> from sympy import Matrix, QQ + >>> M = Matrix([[1, 2], [3, 4]]) + >>> dfm = M.to_DM().to_dfm().convert_to(QQ) + >>> dfm + [[1, 2], [3, 4]] + >>> rhs = Matrix([1, 2]).to_DM().to_dfm().convert_to(QQ) + >>> dfm.lu_solve(rhs) + [[0], [1/2]] + + Notes + ===== + + Calls the ``.solve()`` method of the underlying FLINT matrix. + + For now this will raise an error if the domain is :ref:`ZZ` but will + use the FLINT method for :ref:`QQ`. + + The FLINT methods for :ref:`ZZ` and :ref:`QQ` are ``fmpz_mat_solve`` + and ``fmpq_mat_solve`` respectively. The ``fmpq_mat_solve`` method + uses one of two algorithms: + + - For small matrices (<25 rows) it clears denominators between the + matrix and rhs and uses ``fmpz_mat_solve``. + - For larger matrices it uses ``fmpq_mat_solve_dixon`` which is a + modular approach with CRT reconstruction over :ref:`QQ`. + + The ``fmpz_mat_solve`` method uses one of four algorithms: + + - For very small (<= 3x3) matrices it uses a Cramer's rule. + - For small (<= 15x15) matrices it uses a fraction-free LU solve. + - Otherwise it uses either Dixon or another multimodular approach. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.lu_solve + Higher level interface to solve a matrix equation. + """ + if not self.domain == rhs.domain: + raise DMDomainError("Domains must match: %s != %s" % (self.domain, rhs.domain)) + + # XXX: As for inv we should consider whether to return a matrix over + # over an associated field or attempt to find a solution in the ring. + # For now we follow the existing DomainMatrix convention... + if not self.domain.is_Field: + raise DMDomainError("Field expected, got %s" % self.domain) + + m, n = self.shape + j, k = rhs.shape + if m != j: + raise DMShapeError("Matrix size mismatch: %s * %s vs %s * %s" % (m, n, j, k)) + sol_shape = (n, k) + + # XXX: The Flint solve method only handles square matrices. Probably + # Flint has functions that could be used to solve non-square systems + # but they are not exposed in python-flint yet. Alternatively we could + # put something here using the features that are available like rref. + if m != n: + return self.to_ddm().lu_solve(rhs.to_ddm()).to_dfm() + + try: + sol = self.rep.solve(rhs.rep) + except ZeroDivisionError: + raise DMNonInvertibleMatrixError("Matrix det == 0; not invertible.") + + return self._new(sol, sol_shape, self.domain) + + def fflu(self): + """ + Fraction-free LU decomposition of DFM. + + Explanation + =========== + + Uses `python-flint` if possible for a matrix of + integers otherwise uses the DDM method. + + See Also + ======== + + sympy.polys.matrices.ddm.DDM.fflu + """ + if self.domain == ZZ: + fflu = getattr(self.rep, 'fflu', None) + if fflu is not None: + P, L, D, U = self.rep.fflu() + m, n = self.shape + return ( + self._new(P, (m, m), self.domain), + self._new(L, (m, m), self.domain), + self._new(D, (m, m), self.domain), + self._new(U, self.shape, self.domain) + ) + ddm_p, ddm_l, ddm_d, ddm_u = self.to_ddm().fflu() + P = ddm_p.to_dfm() + L = ddm_l.to_dfm() + D = ddm_d.to_dfm() + U = ddm_u.to_dfm() + return P, L, D, U + + def nullspace(self): + """Return a basis for the nullspace of the matrix.""" + # Code to compute nullspace using flint: + # + # V, nullity = self.rep.nullspace() + # V_dfm = self._new_rep(V)._extract(range(self.rows), range(nullity)) + # + # XXX: That gives the nullspace but does not give us nonpivots. So we + # use the slower DDM method anyway. It would be better to change the + # signature of the nullspace method to not return nonpivots. + # + # XXX: Also python-flint exposes a nullspace method for fmpz_mat but + # not for fmpq_mat. This is the reverse of the situation for DDM etc + # which only allow nullspace over a field. The nullspace method for + # DDM, SDM etc should be changed to allow nullspace over ZZ as well. + # The DomainMatrix nullspace method does allow the domain to be a ring + # but does not directly call the lower-level nullspace methods and uses + # rref_den instead. Nullspace methods should also be added to all + # matrix types in python-flint. + ddm, nonpivots = self.to_ddm().nullspace() + return ddm.to_dfm(), nonpivots + + def nullspace_from_rref(self, pivots=None): + """Return a basis for the nullspace of the matrix.""" + # XXX: Use the flint nullspace method!!! + sdm, nonpivots = self.to_sdm().nullspace_from_rref(pivots=pivots) + return sdm.to_dfm(), nonpivots + + def particular(self): + """Return a particular solution to the system.""" + return self.to_ddm().particular().to_dfm() + + def _lll(self, transform=False, delta=0.99, eta=0.51, rep='zbasis', gram='approx'): + """Call the fmpz_mat.lll() method but check rank to avoid segfaults.""" + + # XXX: There are tests that pass e.g. QQ(5,6) for delta. That fails + # with a TypeError in flint because if QQ is fmpq then conversion with + # float fails. We handle that here but there are two better fixes: + # + # - Make python-flint's fmpq convert with float(x) + # - Change the tests because delta should just be a float. + + def to_float(x): + if QQ.of_type(x): + return float(x.numerator) / float(x.denominator) + else: + return float(x) + + delta = to_float(delta) + eta = to_float(eta) + + if not 0.25 < delta < 1: + raise DMValueError("delta must be between 0.25 and 1") + + # XXX: The flint lll method segfaults if the matrix is not full rank. + m, n = self.shape + if self.rep.rank() != m: + raise DMRankError("Matrix must have full row rank for Flint LLL.") + + # Actually call the flint method. + return self.rep.lll(transform=transform, delta=delta, eta=eta, rep=rep, gram=gram) + + @doctest_depends_on(ground_types='flint') + def lll(self, delta=0.75): + """Compute LLL-reduced basis using FLINT. + + See :meth:`lll_transform` for more information. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2, 3], [4, 5, 6]]) + >>> M.to_DM().to_dfm().lll() + [[2, 1, 0], [-1, 1, 3]] + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.lll + Higher level interface to compute LLL-reduced basis. + lll_transform + Compute LLL-reduced basis and transform matrix. + """ + if self.domain != ZZ: + raise DMDomainError("ZZ expected, got %s" % self.domain) + elif self.rows > self.cols: + raise DMShapeError("Matrix must not have more rows than columns.") + + rep = self._lll(delta=delta) + return self._new_rep(rep) + + @doctest_depends_on(ground_types='flint') + def lll_transform(self, delta=0.75): + """Compute LLL-reduced basis and transform using FLINT. + + Examples + ======== + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2, 3], [4, 5, 6]]).to_DM().to_dfm() + >>> M_lll, T = M.lll_transform() + >>> M_lll + [[2, 1, 0], [-1, 1, 3]] + >>> T + [[-2, 1], [3, -1]] + >>> T.matmul(M) == M_lll + True + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.lll + Higher level interface to compute LLL-reduced basis. + lll + Compute LLL-reduced basis without transform matrix. + """ + if self.domain != ZZ: + raise DMDomainError("ZZ expected, got %s" % self.domain) + elif self.rows > self.cols: + raise DMShapeError("Matrix must not have more rows than columns.") + + rep, T = self._lll(transform=True, delta=delta) + basis = self._new_rep(rep) + T_dfm = self._new(T, (self.rows, self.rows), self.domain) + return basis, T_dfm + + +# Avoid circular imports +from sympy.polys.matrices.ddm import DDM +from sympy.polys.matrices.ddm import SDM diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/_typing.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/_typing.py new file mode 100644 index 0000000000000000000000000000000000000000..fc7c3b601fe85d591ddf853acbf33f5bba64b11c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/_typing.py @@ -0,0 +1,16 @@ +from typing import TypeVar, Protocol + + +T = TypeVar('T') + + +class RingElement(Protocol): + """A ring element. + + Must support ``+``, ``-``, ``*``, ``**`` and ``-``. + """ + def __add__(self: T, other: T, /) -> T: ... + def __sub__(self: T, other: T, /) -> T: ... + def __mul__(self: T, other: T, /) -> T: ... + def __pow__(self: T, other: int, /) -> T: ... + def __neg__(self: T, /) -> T: ... diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/ddm.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/ddm.py new file mode 100644 index 0000000000000000000000000000000000000000..9b7836ef298fe27a1c02ed069f33711a632d6ed8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/ddm.py @@ -0,0 +1,1176 @@ +""" + +Module for the DDM class. + +The DDM class is an internal representation used by DomainMatrix. The letters +DDM stand for Dense Domain Matrix. A DDM instance represents a matrix using +elements from a polynomial Domain (e.g. ZZ, QQ, ...) in a dense-matrix +representation. + +Basic usage: + + >>> from sympy import ZZ, QQ + >>> from sympy.polys.matrices.ddm import DDM + >>> A = DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ) + >>> A.shape + (2, 2) + >>> A + [[0, 1], [-1, 0]] + >>> type(A) + + >>> A @ A + [[-1, 0], [0, -1]] + +The ddm_* functions are designed to operate on DDM as well as on an ordinary +list of lists: + + >>> from sympy.polys.matrices.dense import ddm_idet + >>> ddm_idet(A, QQ) + 1 + >>> ddm_idet([[0, 1], [-1, 0]], QQ) + 1 + >>> A + [[-1, 0], [0, -1]] + +Note that ddm_idet modifies the input matrix in-place. It is recommended to +use the DDM.det method as a friendlier interface to this instead which takes +care of copying the matrix: + + >>> B = DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ) + >>> B.det() + 1 + +Normally DDM would not be used directly and is just part of the internal +representation of DomainMatrix which adds further functionality including e.g. +unifying domains. + +The dense format used by DDM is a list of lists of elements e.g. the 2x2 +identity matrix is like [[1, 0], [0, 1]]. The DDM class itself is a subclass +of list and its list items are plain lists. Elements are accessed as e.g. +ddm[i][j] where ddm[i] gives the ith row and ddm[i][j] gets the element in the +jth column of that row. Subclassing list makes e.g. iteration and indexing +very efficient. We do not override __getitem__ because it would lose that +benefit. + +The core routines are implemented by the ddm_* functions defined in dense.py. +Those functions are intended to be able to operate on a raw list-of-lists +representation of matrices with most functions operating in-place. The DDM +class takes care of copying etc and also stores a Domain object associated +with its elements. This makes it possible to implement things like A + B with +domain checking and also shape checking so that the list of lists +representation is friendlier. + +""" +from itertools import chain + +from sympy.external.gmpy import GROUND_TYPES +from sympy.utilities.decorator import doctest_depends_on + +from .exceptions import ( + DMBadInputError, + DMDomainError, + DMNonSquareMatrixError, + DMShapeError, +) + +from sympy.polys.domains import QQ + +from .dense import ( + ddm_transpose, + ddm_iadd, + ddm_isub, + ddm_ineg, + ddm_imul, + ddm_irmul, + ddm_imatmul, + ddm_irref, + ddm_irref_den, + ddm_idet, + ddm_iinv, + ddm_ilu_split, + ddm_ilu_solve, + ddm_berk, + ) + +from .lll import ddm_lll, ddm_lll_transform + + +if GROUND_TYPES != 'flint': + __doctest_skip__ = ['DDM.to_dfm', 'DDM.to_dfm_or_ddm'] + + +class DDM(list): + """Dense matrix based on polys domain elements + + This is a list subclass and is a wrapper for a list of lists that supports + basic matrix arithmetic +, -, *, **. + """ + + fmt = 'dense' + is_DFM = False + is_DDM = True + + def __init__(self, rowslist, shape, domain): + if not (isinstance(rowslist, list) and all(type(row) is list for row in rowslist)): + raise DMBadInputError("rowslist must be a list of lists") + m, n = shape + if len(rowslist) != m or any(len(row) != n for row in rowslist): + raise DMBadInputError("Inconsistent row-list/shape") + + super().__init__([i.copy() for i in rowslist]) + self.shape = (m, n) + self.rows = m + self.cols = n + self.domain = domain + + def getitem(self, i, j): + return self[i][j] + + def setitem(self, i, j, value): + self[i][j] = value + + def extract_slice(self, slice1, slice2): + ddm = [row[slice2] for row in self[slice1]] + rows = len(ddm) + cols = len(ddm[0]) if ddm else len(range(self.shape[1])[slice2]) + return DDM(ddm, (rows, cols), self.domain) + + def extract(self, rows, cols): + ddm = [] + for i in rows: + rowi = self[i] + ddm.append([rowi[j] for j in cols]) + return DDM(ddm, (len(rows), len(cols)), self.domain) + + @classmethod + def from_list(cls, rowslist, shape, domain): + """ + Create a :class:`DDM` from a list of lists. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.ddm import DDM + >>> A = DDM.from_list([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ) + >>> A + [[0, 1], [-1, 0]] + >>> A == DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ) + True + + See Also + ======== + + from_list_flat + """ + return cls(rowslist, shape, domain) + + @classmethod + def from_ddm(cls, other): + return other.copy() + + def to_list(self): + """ + Convert to a list of lists. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.ddm import DDM + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> A.to_list() + [[1, 2], [3, 4]] + + See Also + ======== + + to_list_flat + sympy.polys.matrices.domainmatrix.DomainMatrix.to_list + """ + return [row[:] for row in self] + + def to_list_flat(self): + """ + Convert to a flat list of elements. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.ddm import DDM + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> A.to_list_flat() + [1, 2, 3, 4] + >>> A == DDM.from_list_flat(A.to_list_flat(), A.shape, A.domain) + True + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.to_list_flat + """ + flat = [] + for row in self: + flat.extend(row) + return flat + + @classmethod + def from_list_flat(cls, flat, shape, domain): + """ + Create a :class:`DDM` from a flat list of elements. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.ddm import DDM + >>> A = DDM.from_list_flat([1, 2, 3, 4], (2, 2), QQ) + >>> A + [[1, 2], [3, 4]] + >>> A == DDM.from_list_flat(A.to_list_flat(), A.shape, A.domain) + True + + See Also + ======== + + to_list_flat + sympy.polys.matrices.domainmatrix.DomainMatrix.from_list_flat + """ + assert type(flat) is list + rows, cols = shape + if not (len(flat) == rows*cols): + raise DMBadInputError("Inconsistent flat-list shape") + lol = [flat[i*cols:(i+1)*cols] for i in range(rows)] + return cls(lol, shape, domain) + + def flatiter(self): + return chain.from_iterable(self) + + def flat(self): + items = [] + for row in self: + items.extend(row) + return items + + def to_flat_nz(self): + """ + Convert to a flat list of nonzero elements and data. + + Explanation + =========== + + This is used to operate on a list of the elements of a matrix and then + reconstruct a matrix using :meth:`from_flat_nz`. Zero elements are + included in the list but that may change in the future. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> elements, data = A.to_flat_nz() + >>> elements + [1, 2, 3, 4] + >>> A == DDM.from_flat_nz(elements, data, A.domain) + True + + See Also + ======== + + from_flat_nz + sympy.polys.matrices.sdm.SDM.to_flat_nz + sympy.polys.matrices.domainmatrix.DomainMatrix.to_flat_nz + """ + return self.to_sdm().to_flat_nz() + + @classmethod + def from_flat_nz(cls, elements, data, domain): + """ + Reconstruct a :class:`DDM` after calling :meth:`to_flat_nz`. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> elements, data = A.to_flat_nz() + >>> elements + [1, 2, 3, 4] + >>> A == DDM.from_flat_nz(elements, data, A.domain) + True + + See Also + ======== + + to_flat_nz + sympy.polys.matrices.sdm.SDM.from_flat_nz + sympy.polys.matrices.domainmatrix.DomainMatrix.from_flat_nz + """ + return SDM.from_flat_nz(elements, data, domain).to_ddm() + + def to_dod(self): + """ + Convert to a dictionary of dictionaries (dod) format. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> A.to_dod() + {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}} + + See Also + ======== + + from_dod + sympy.polys.matrices.sdm.SDM.to_dod + sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod + """ + dod = {} + for i, row in enumerate(self): + row = {j:e for j, e in enumerate(row) if e} + if row: + dod[i] = row + return dod + + @classmethod + def from_dod(cls, dod, shape, domain): + """ + Create a :class:`DDM` from a dictionary of dictionaries (dod) format. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> dod = {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}} + >>> A = DDM.from_dod(dod, (2, 2), QQ) + >>> A + [[1, 2], [3, 4]] + + See Also + ======== + + to_dod + sympy.polys.matrices.sdm.SDM.from_dod + sympy.polys.matrices.domainmatrix.DomainMatrix.from_dod + """ + rows, cols = shape + lol = [[domain.zero] * cols for _ in range(rows)] + for i, row in dod.items(): + for j, element in row.items(): + lol[i][j] = element + return DDM(lol, shape, domain) + + def to_dok(self): + """ + Convert :class:`DDM` to dictionary of keys (dok) format. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> A.to_dok() + {(0, 0): 1, (0, 1): 2, (1, 0): 3, (1, 1): 4} + + See Also + ======== + + from_dok + sympy.polys.matrices.sdm.SDM.to_dok + sympy.polys.matrices.domainmatrix.DomainMatrix.to_dok + """ + dok = {} + for i, row in enumerate(self): + for j, element in enumerate(row): + if element: + dok[i, j] = element + return dok + + @classmethod + def from_dok(cls, dok, shape, domain): + """ + Create a :class:`DDM` from a dictionary of keys (dok) format. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> dok = {(0, 0): 1, (0, 1): 2, (1, 0): 3, (1, 1): 4} + >>> A = DDM.from_dok(dok, (2, 2), QQ) + >>> A + [[1, 2], [3, 4]] + + See Also + ======== + + to_dok + sympy.polys.matrices.sdm.SDM.from_dok + sympy.polys.matrices.domainmatrix.DomainMatrix.from_dok + """ + rows, cols = shape + lol = [[domain.zero] * cols for _ in range(rows)] + for (i, j), element in dok.items(): + lol[i][j] = element + return DDM(lol, shape, domain) + + def iter_values(self): + """ + Iterate over the non-zero values of the matrix. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[QQ(1), QQ(0)], [QQ(3), QQ(4)]], (2, 2), QQ) + >>> list(A.iter_values()) + [1, 3, 4] + + See Also + ======== + + iter_items + to_list_flat + sympy.polys.matrices.domainmatrix.DomainMatrix.iter_values + """ + for row in self: + yield from filter(None, row) + + def iter_items(self): + """ + Iterate over indices and values of nonzero elements of the matrix. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[QQ(1), QQ(0)], [QQ(3), QQ(4)]], (2, 2), QQ) + >>> list(A.iter_items()) + [((0, 0), 1), ((1, 0), 3), ((1, 1), 4)] + + See Also + ======== + + iter_values + to_dok + sympy.polys.matrices.domainmatrix.DomainMatrix.iter_items + """ + for i, row in enumerate(self): + for j, element in enumerate(row): + if element: + yield (i, j), element + + def to_ddm(self): + """ + Convert to a :class:`DDM`. + + This just returns ``self`` but exists to parallel the corresponding + method in other matrix types like :class:`~.SDM`. + + See Also + ======== + + to_sdm + to_dfm + to_dfm_or_ddm + sympy.polys.matrices.sdm.SDM.to_ddm + sympy.polys.matrices.domainmatrix.DomainMatrix.to_ddm + """ + return self + + def to_sdm(self): + """ + Convert to a :class:`~.SDM`. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> A.to_sdm() + {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}} + >>> type(A.to_sdm()) + + + See Also + ======== + + SDM + sympy.polys.matrices.sdm.SDM.to_ddm + """ + return SDM.from_list(self, self.shape, self.domain) + + @doctest_depends_on(ground_types=['flint']) + def to_dfm(self): + """ + Convert to :class:`~.DDM` to :class:`~.DFM`. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> A.to_dfm() + [[1, 2], [3, 4]] + >>> type(A.to_dfm()) + + + See Also + ======== + + DFM + sympy.polys.matrices._dfm.DFM.to_ddm + """ + return DFM(list(self), self.shape, self.domain) + + @doctest_depends_on(ground_types=['flint']) + def to_dfm_or_ddm(self): + """ + Convert to :class:`~.DFM` if possible or otherwise return self. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy import QQ + >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ) + >>> A.to_dfm_or_ddm() + [[1, 2], [3, 4]] + >>> type(A.to_dfm_or_ddm()) + + + See Also + ======== + + to_dfm + to_ddm + sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm + """ + if DFM._supports_domain(self.domain): + return self.to_dfm() + return self + + def convert_to(self, K): + Kold = self.domain + if K == Kold: + return self.copy() + rows = [[K.convert_from(e, Kold) for e in row] for row in self] + return DDM(rows, self.shape, K) + + def __str__(self): + rowsstr = ['[%s]' % ', '.join(map(str, row)) for row in self] + return '[%s]' % ', '.join(rowsstr) + + def __repr__(self): + cls = type(self).__name__ + rows = list.__repr__(self) + return '%s(%s, %s, %s)' % (cls, rows, self.shape, self.domain) + + def __eq__(self, other): + if not isinstance(other, DDM): + return False + return (super().__eq__(other) and self.domain == other.domain) + + def __ne__(self, other): + return not self.__eq__(other) + + @classmethod + def zeros(cls, shape, domain): + z = domain.zero + m, n = shape + rowslist = [[z] * n for _ in range(m)] + return DDM(rowslist, shape, domain) + + @classmethod + def ones(cls, shape, domain): + one = domain.one + m, n = shape + rowlist = [[one] * n for _ in range(m)] + return DDM(rowlist, shape, domain) + + @classmethod + def eye(cls, size, domain): + if isinstance(size, tuple): + m, n = size + elif isinstance(size, int): + m = n = size + one = domain.one + ddm = cls.zeros((m, n), domain) + for i in range(min(m, n)): + ddm[i][i] = one + return ddm + + def copy(self): + copyrows = [row[:] for row in self] + return DDM(copyrows, self.shape, self.domain) + + def transpose(self): + rows, cols = self.shape + if rows: + ddmT = ddm_transpose(self) + else: + ddmT = [[]] * cols + return DDM(ddmT, (cols, rows), self.domain) + + def __add__(a, b): + if not isinstance(b, DDM): + return NotImplemented + return a.add(b) + + def __sub__(a, b): + if not isinstance(b, DDM): + return NotImplemented + return a.sub(b) + + def __neg__(a): + return a.neg() + + def __mul__(a, b): + if b in a.domain: + return a.mul(b) + else: + return NotImplemented + + def __rmul__(a, b): + if b in a.domain: + return a.mul(b) + else: + return NotImplemented + + def __matmul__(a, b): + if isinstance(b, DDM): + return a.matmul(b) + else: + return NotImplemented + + @classmethod + def _check(cls, a, op, b, ashape, bshape): + if a.domain != b.domain: + msg = "Domain mismatch: %s %s %s" % (a.domain, op, b.domain) + raise DMDomainError(msg) + if ashape != bshape: + msg = "Shape mismatch: %s %s %s" % (a.shape, op, b.shape) + raise DMShapeError(msg) + + def add(a, b): + """a + b""" + a._check(a, '+', b, a.shape, b.shape) + c = a.copy() + ddm_iadd(c, b) + return c + + def sub(a, b): + """a - b""" + a._check(a, '-', b, a.shape, b.shape) + c = a.copy() + ddm_isub(c, b) + return c + + def neg(a): + """-a""" + b = a.copy() + ddm_ineg(b) + return b + + def mul(a, b): + c = a.copy() + ddm_imul(c, b) + return c + + def rmul(a, b): + c = a.copy() + ddm_irmul(c, b) + return c + + def matmul(a, b): + """a @ b (matrix product)""" + m, o = a.shape + o2, n = b.shape + a._check(a, '*', b, o, o2) + c = a.zeros((m, n), a.domain) + ddm_imatmul(c, a, b) + return c + + def mul_elementwise(a, b): + assert a.shape == b.shape + assert a.domain == b.domain + c = [[aij * bij for aij, bij in zip(ai, bi)] for ai, bi in zip(a, b)] + return DDM(c, a.shape, a.domain) + + def hstack(A, *B): + """Horizontally stacks :py:class:`~.DDM` matrices. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import DDM + + >>> A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> B = DDM([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ) + >>> A.hstack(B) + [[1, 2, 5, 6], [3, 4, 7, 8]] + + >>> C = DDM([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ) + >>> A.hstack(B, C) + [[1, 2, 5, 6, 9, 10], [3, 4, 7, 8, 11, 12]] + """ + Anew = list(A.copy()) + rows, cols = A.shape + domain = A.domain + + for Bk in B: + Bkrows, Bkcols = Bk.shape + assert Bkrows == rows + assert Bk.domain == domain + + cols += Bkcols + + for i, Bki in enumerate(Bk): + Anew[i].extend(Bki) + + return DDM(Anew, (rows, cols), A.domain) + + def vstack(A, *B): + """Vertically stacks :py:class:`~.DDM` matrices. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import DDM + + >>> A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> B = DDM([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ) + >>> A.vstack(B) + [[1, 2], [3, 4], [5, 6], [7, 8]] + + >>> C = DDM([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ) + >>> A.vstack(B, C) + [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]] + """ + Anew = list(A.copy()) + rows, cols = A.shape + domain = A.domain + + for Bk in B: + Bkrows, Bkcols = Bk.shape + assert Bkcols == cols + assert Bk.domain == domain + + rows += Bkrows + + Anew.extend(Bk.copy()) + + return DDM(Anew, (rows, cols), A.domain) + + def applyfunc(self, func, domain): + elements = [list(map(func, row)) for row in self] + return DDM(elements, self.shape, domain) + + def nnz(a): + """Number of non-zero entries in :py:class:`~.DDM` matrix. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.nnz + """ + return sum(sum(map(bool, row)) for row in a) + + def scc(a): + """Strongly connected components of a square matrix *a*. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import DDM + >>> A = DDM([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(1)]], (2, 2), ZZ) + >>> A.scc() + [[0], [1]] + + See also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.scc + + """ + return a.to_sdm().scc() + + @classmethod + def diag(cls, values, domain): + """Returns a square diagonal matrix with *values* on the diagonal. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import DDM + >>> DDM.diag([ZZ(1), ZZ(2), ZZ(3)], ZZ) + [[1, 0, 0], [0, 2, 0], [0, 0, 3]] + + See also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.diag + """ + return SDM.diag(values, domain).to_ddm() + + def rref(a): + """Reduced-row echelon form of a and list of pivots. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.rref + Higher level interface to this function. + sympy.polys.matrices.dense.ddm_irref + The underlying algorithm. + """ + b = a.copy() + K = a.domain + partial_pivot = K.is_RealField or K.is_ComplexField + pivots = ddm_irref(b, _partial_pivot=partial_pivot) + return b, pivots + + def rref_den(a): + """Reduced-row echelon form of a with denominator and list of pivots + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den + Higher level interface to this function. + sympy.polys.matrices.dense.ddm_irref_den + The underlying algorithm. + """ + b = a.copy() + K = a.domain + denom, pivots = ddm_irref_den(b, K) + return b, denom, pivots + + def nullspace(a): + """Returns a basis for the nullspace of a. + + The domain of the matrix must be a field. + + See Also + ======== + + rref + sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace + """ + rref, pivots = a.rref() + return rref.nullspace_from_rref(pivots) + + def nullspace_from_rref(a, pivots=None): + """Compute the nullspace of a matrix from its rref. + + The domain of the matrix can be any domain. + + Returns a tuple (basis, nonpivots). + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace + The higher level interface to this function. + """ + m, n = a.shape + K = a.domain + + if pivots is None: + pivots = [] + last_pivot = -1 + for i in range(m): + ai = a[i] + for j in range(last_pivot+1, n): + if ai[j]: + last_pivot = j + pivots.append(j) + break + + if not pivots: + return (a.eye(n, K), list(range(n))) + + # After rref the pivots are all one but after rref_den they may not be. + pivot_val = a[0][pivots[0]] + + basis = [] + nonpivots = [] + for i in range(n): + if i in pivots: + continue + nonpivots.append(i) + vec = [pivot_val if i == j else K.zero for j in range(n)] + for ii, jj in enumerate(pivots): + vec[jj] -= a[ii][i] + basis.append(vec) + + basis_ddm = DDM(basis, (len(basis), n), K) + + return (basis_ddm, nonpivots) + + def particular(a): + return a.to_sdm().particular().to_ddm() + + def det(a): + """Determinant of a""" + m, n = a.shape + if m != n: + raise DMNonSquareMatrixError("Determinant of non-square matrix") + b = a.copy() + K = b.domain + deta = ddm_idet(b, K) + return deta + + def inv(a): + """Inverse of a""" + m, n = a.shape + if m != n: + raise DMNonSquareMatrixError("Determinant of non-square matrix") + ainv = a.copy() + K = a.domain + ddm_iinv(ainv, a, K) + return ainv + + def lu(a): + """L, U decomposition of a""" + m, n = a.shape + K = a.domain + + U = a.copy() + L = a.eye(m, K) + swaps = ddm_ilu_split(L, U, K) + + return L, U, swaps + + def _fflu(self): + """ + Private method for Phase 1 of fraction-free LU decomposition. + Performs row operations and elimination to compute U and permutation indices. + + Returns: + LU : decomposition as a single matrix. + perm (list): Permutation indices for row swaps. + """ + rows, cols = self.shape + K = self.domain + + LU = self.copy() + perm = list(range(rows)) + rank = 0 + + for j in range(min(rows, cols)): + # Skip columns where all entries are zero + if all(LU[i][j] == K.zero for i in range(rows)): + continue + + # Find the first non-zero pivot in the current column + pivot_row = -1 + for i in range(rank, rows): + if LU[i][j] != K.zero: + pivot_row = i + break + + # If no pivot is found, skip column + if pivot_row == -1: + continue + + # Swap rows to bring the pivot to the current rank + if pivot_row != rank: + LU[rank], LU[pivot_row] = LU[pivot_row], LU[rank] + perm[rank], perm[pivot_row] = perm[pivot_row], perm[rank] + + # Found pivot - (Gauss-Bareiss elimination) + pivot = LU[rank][j] + for i in range(rank + 1, rows): + multiplier = LU[i][j] + # Denominator is previous pivot or 1 + denominator = LU[rank - 1][rank - 1] if rank > 0 else K.one + for k in range(j + 1, cols): + LU[i][k] = K.exquo(pivot * LU[i][k] - LU[rank][k] * multiplier, denominator) + # Keep the multiplier for L matrix + LU[i][j] = multiplier + rank += 1 + + return LU, perm + + def fflu(self): + """ + Fraction-free LU decomposition of DDM. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.fflu + The higher-level interface to this function. + """ + rows, cols = self.shape + K = self.domain + + # Phase 1: Perform row operations and get permutation + U, perm = self._fflu() + + # Phase 2: Construct P, L, D matrices + # Create P from permutation + P = self.zeros((rows, rows), K) + for i, pi in enumerate(perm): + P[i][pi] = K.one + + # Create L matrix + L = self.zeros((rows, rows), K) + i = j = 0 + while i < rows and j < cols: + if U[i][j] != K.zero: + # Found non-zero pivot + # Diagonal entry is the pivot + L[i][i] = U[i][j] + for l in range(i + 1, rows): + # Off-diagonal entries are the multipliers + L[l][i] = U[l][j] + # zero out the entries in U + U[l][j] = K.zero + i += 1 + j += 1 + + # Fill remaining diagonal of L with ones + for i in range(i, rows): + L[i][i] = K.one + + # Create D matrix - using FLINT's approach with accumulator + D = self.zeros((rows, rows), K) + if rows >= 1: + D[0][0] = L[0][0] + di = K.one + for i in range(1, rows): + # Accumulate product of pivots + di = L[i - 1][i - 1] * L[i][i] + D[i][i] = di + + return P, L, D, U + + def qr(self): + """ + QR decomposition for DDM. + + Returns: + - Q: Orthogonal matrix as a DDM. + - R: Upper triangular matrix as a DDM. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.qr + The higher-level interface to this function. + """ + rows, cols = self.shape + K = self.domain + Q = self.copy() + R = self.zeros((min(rows, cols), cols), K) + + # Check that the domain is a field + if not K.is_Field: + raise DMDomainError("QR decomposition requires a field (e.g. QQ).") + + dot_cols = lambda i, j: K.sum(Q[k][i] * Q[k][j] for k in range(rows)) + + for j in range(cols): + for i in range(min(j, rows)): + dot_ii = dot_cols(i, i) + if dot_ii != K.zero: + R[i][j] = dot_cols(i, j) / dot_ii + for k in range(rows): + Q[k][j] -= R[i][j] * Q[k][i] + + if j < rows: + dot_jj = dot_cols(j, j) + if dot_jj != K.zero: + R[j][j] = K.one + + Q = Q.extract(range(rows), range(min(rows, cols))) + + return Q, R + + def lu_solve(a, b): + """x where a*x = b""" + m, n = a.shape + m2, o = b.shape + a._check(a, 'lu_solve', b, m, m2) + if not a.domain.is_Field: + raise DMDomainError("lu_solve requires a field") + + L, U, swaps = a.lu() + x = a.zeros((n, o), a.domain) + ddm_ilu_solve(x, L, U, swaps, b) + return x + + def charpoly(a): + """Coefficients of characteristic polynomial of a""" + K = a.domain + m, n = a.shape + if m != n: + raise DMNonSquareMatrixError("Charpoly of non-square matrix") + vec = ddm_berk(a, K) + coeffs = [vec[i][0] for i in range(n+1)] + return coeffs + + def is_zero_matrix(self): + """ + Says whether this matrix has all zero entries. + """ + zero = self.domain.zero + return all(Mij == zero for Mij in self.flatiter()) + + def is_upper(self): + """ + Says whether this matrix is upper-triangular. True can be returned + even if the matrix is not square. + """ + zero = self.domain.zero + return all(Mij == zero for i, Mi in enumerate(self) for Mij in Mi[:i]) + + def is_lower(self): + """ + Says whether this matrix is lower-triangular. True can be returned + even if the matrix is not square. + """ + zero = self.domain.zero + return all(Mij == zero for i, Mi in enumerate(self) for Mij in Mi[i+1:]) + + def is_diagonal(self): + """ + Says whether this matrix is diagonal. True can be returned even if + the matrix is not square. + """ + return self.is_upper() and self.is_lower() + + def diagonal(self): + """ + Returns a list of the elements from the diagonal of the matrix. + """ + m, n = self.shape + return [self[i][i] for i in range(min(m, n))] + + def lll(A, delta=QQ(3, 4)): + return ddm_lll(A, delta=delta) + + def lll_transform(A, delta=QQ(3, 4)): + return ddm_lll_transform(A, delta=delta) + + +from .sdm import SDM +from .dfm import DFM diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/dense.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/dense.py new file mode 100644 index 0000000000000000000000000000000000000000..47ab2d6897c6d9f3781af23ccb68f96f15c7e859 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/dense.py @@ -0,0 +1,824 @@ +""" + +Module for the ddm_* routines for operating on a matrix in list of lists +matrix representation. + +These routines are used internally by the DDM class which also provides a +friendlier interface for them. The idea here is to implement core matrix +routines in a way that can be applied to any simple list representation +without the need to use any particular matrix class. For example we can +compute the RREF of a matrix like: + + >>> from sympy.polys.matrices.dense import ddm_irref + >>> M = [[1, 2, 3], [4, 5, 6]] + >>> pivots = ddm_irref(M) + >>> M + [[1.0, 0.0, -1.0], [0, 1.0, 2.0]] + +These are lower-level routines that work mostly in place.The routines at this +level should not need to know what the domain of the elements is but should +ideally document what operations they will use and what functions they need to +be provided with. + +The next-level up is the DDM class which uses these routines but wraps them up +with an interface that handles copying etc and keeps track of the Domain of +the elements of the matrix: + + >>> from sympy.polys.domains import QQ + >>> from sympy.polys.matrices.ddm import DDM + >>> M = DDM([[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]], (2, 3), QQ) + >>> M + [[1, 2, 3], [4, 5, 6]] + >>> Mrref, pivots = M.rref() + >>> Mrref + [[1, 0, -1], [0, 1, 2]] + +""" +from __future__ import annotations +from operator import mul +from .exceptions import ( + DMShapeError, + DMDomainError, + DMNonInvertibleMatrixError, + DMNonSquareMatrixError, +) +from typing import Sequence, TypeVar +from sympy.polys.matrices._typing import RingElement + + +#: Type variable for the elements of the matrix +T = TypeVar('T') + +#: Type variable for the elements of the matrix that are in a ring +R = TypeVar('R', bound=RingElement) + + +def ddm_transpose(matrix: Sequence[Sequence[T]]) -> list[list[T]]: + """matrix transpose""" + return list(map(list, zip(*matrix))) + + +def ddm_iadd(a: list[list[R]], b: Sequence[Sequence[R]]) -> None: + """a += b""" + for ai, bi in zip(a, b): + for j, bij in enumerate(bi): + ai[j] += bij + + +def ddm_isub(a: list[list[R]], b: Sequence[Sequence[R]]) -> None: + """a -= b""" + for ai, bi in zip(a, b): + for j, bij in enumerate(bi): + ai[j] -= bij + + +def ddm_ineg(a: list[list[R]]) -> None: + """a <-- -a""" + for ai in a: + for j, aij in enumerate(ai): + ai[j] = -aij + + +def ddm_imul(a: list[list[R]], b: R) -> None: + """a <-- a*b""" + for ai in a: + for j, aij in enumerate(ai): + ai[j] = aij * b + + +def ddm_irmul(a: list[list[R]], b: R) -> None: + """a <-- b*a""" + for ai in a: + for j, aij in enumerate(ai): + ai[j] = b * aij + + +def ddm_imatmul( + a: list[list[R]], b: Sequence[Sequence[R]], c: Sequence[Sequence[R]] +) -> None: + """a += b @ c""" + cT = list(zip(*c)) + + for bi, ai in zip(b, a): + for j, cTj in enumerate(cT): + ai[j] = sum(map(mul, bi, cTj), ai[j]) + + +def ddm_irref(a, _partial_pivot=False): + """In-place reduced row echelon form of a matrix. + + Compute the reduced row echelon form of $a$. Modifies $a$ in place and + returns a list of the pivot columns. + + Uses naive Gauss-Jordan elimination in the ground domain which must be a + field. + + This routine is only really suitable for use with simple field domains like + :ref:`GF(p)`, :ref:`QQ` and :ref:`QQ(a)` although even for :ref:`QQ` with + larger matrices it is possibly more efficient to use fraction free + approaches. + + This method is not suitable for use with rational function fields + (:ref:`K(x)`) because the elements will blowup leading to costly gcd + operations. In this case clearing denominators and using fraction free + approaches is likely to be more efficient. + + For inexact numeric domains like :ref:`RR` and :ref:`CC` pass + ``_partial_pivot=True`` to use partial pivoting to control rounding errors. + + Examples + ======== + + >>> from sympy.polys.matrices.dense import ddm_irref + >>> from sympy import QQ + >>> M = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]] + >>> pivots = ddm_irref(M) + >>> M + [[1, 0, -1], [0, 1, 2]] + >>> pivots + [0, 1] + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.rref + Higher level interface to this routine. + ddm_irref_den + The fraction free version of this routine. + sdm_irref + A sparse version of this routine. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Row_echelon_form#Reduced_row_echelon_form + """ + # We compute aij**-1 below and then use multiplication instead of division + # in the innermost loop. The domain here is a field so either operation is + # defined. There are significant performance differences for some domains + # though. In the case of e.g. QQ or QQ(x) inversion is free but + # multiplication and division have the same cost so it makes no difference. + # In cases like GF(p), QQ, RR or CC though multiplication is + # faster than division so reusing a precomputed inverse for many + # multiplications can be a lot faster. The biggest win is QQ when + # deg(minpoly(a)) is large. + # + # With domains like QQ(x) this can perform badly for other reasons. + # Typically the initial matrix has simple denominators and the + # fraction-free approach with exquo (ddm_irref_den) will preserve that + # property throughout. The method here causes denominator blowup leading to + # expensive gcd reductions in the intermediate expressions. With many + # generators like QQ(x,y,z,...) this is extremely bad. + # + # TODO: Use a nontrivial pivoting strategy to control intermediate + # expression growth. Rearranging rows and/or columns could defer the most + # complicated elements until the end. If the first pivot is a + # complicated/large element then the first round of reduction will + # immediately introduce expression blowup across the whole matrix. + + # a is (m x n) + m = len(a) + if not m: + return [] + n = len(a[0]) + + i = 0 + pivots = [] + + for j in range(n): + # Proper pivoting should be used for all domains for performance + # reasons but it is only strictly needed for RR and CC (and possibly + # other domains like RR(x)). This path is used by DDM.rref() if the + # domain is RR or CC. It uses partial (row) pivoting based on the + # absolute value of the pivot candidates. + if _partial_pivot: + ip = max(range(i, m), key=lambda ip: abs(a[ip][j])) + a[i], a[ip] = a[ip], a[i] + + # pivot + aij = a[i][j] + + # zero-pivot + if not aij: + for ip in range(i+1, m): + aij = a[ip][j] + # row-swap + if aij: + a[i], a[ip] = a[ip], a[i] + break + else: + # next column + continue + + # normalise row + ai = a[i] + aijinv = aij**-1 + for l in range(j, n): + ai[l] *= aijinv # ai[j] = one + + # eliminate above and below to the right + for k, ak in enumerate(a): + if k == i or not ak[j]: + continue + akj = ak[j] + ak[j] -= akj # ak[j] = zero + for l in range(j+1, n): + ak[l] -= akj * ai[l] + + # next row + pivots.append(j) + i += 1 + + # no more rows? + if i >= m: + break + + return pivots + + +def ddm_irref_den(a, K): + """a <-- rref(a); return (den, pivots) + + Compute the fraction-free reduced row echelon form (RREF) of $a$. Modifies + $a$ in place and returns a tuple containing the denominator of the RREF and + a list of the pivot columns. + + Explanation + =========== + + The algorithm used is the fraction-free version of Gauss-Jordan elimination + described as FFGJ in [1]_. Here it is modified to handle zero or missing + pivots and to avoid redundant arithmetic. + + The domain $K$ must support exact division (``K.exquo``) but does not need + to be a field. This method is suitable for most exact rings and fields like + :ref:`ZZ`, :ref:`QQ` and :ref:`QQ(a)`. In the case of :ref:`QQ` or + :ref:`K(x)` it might be more efficient to clear denominators and use + :ref:`ZZ` or :ref:`K[x]` instead. + + For inexact domains like :ref:`RR` and :ref:`CC` use ``ddm_irref`` instead. + + Examples + ======== + + >>> from sympy.polys.matrices.dense import ddm_irref_den + >>> from sympy import ZZ, Matrix + >>> M = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)]] + >>> den, pivots = ddm_irref_den(M, ZZ) + >>> M + [[-3, 0, 3], [0, -3, -6]] + >>> den + -3 + >>> pivots + [0, 1] + >>> Matrix(M).rref()[0] + Matrix([ + [1, 0, -1], + [0, 1, 2]]) + + See Also + ======== + + ddm_irref + A version of this routine that uses field division. + sdm_irref + A sparse version of :func:`ddm_irref`. + sdm_rref_den + A sparse version of :func:`ddm_irref_den`. + sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den + Higher level interface. + + References + ========== + + .. [1] Fraction-free algorithms for linear and polynomial equations. + George C. Nakos , Peter R. Turner , Robert M. Williams. + https://dl.acm.org/doi/10.1145/271130.271133 + """ + # + # A simpler presentation of this algorithm is given in [1]: + # + # Given an n x n matrix A and n x 1 matrix b: + # + # for i in range(n): + # if i != 0: + # d = a[i-1][i-1] + # for j in range(n): + # if j == i: + # continue + # b[j] = a[i][i]*b[j] - a[j][i]*b[i] + # for k in range(n): + # a[j][k] = a[i][i]*a[j][k] - a[j][i]*a[i][k] + # if i != 0: + # a[j][k] /= d + # + # Our version here is a bit more complicated because: + # + # 1. We use row-swaps to avoid zero pivots. + # 2. We allow for some columns to be missing pivots. + # 3. We avoid a lot of redundant arithmetic. + # + # TODO: Use a non-trivial pivoting strategy. Even just row swapping makes a + # big difference to performance if e.g. the upper-left entry of the matrix + # is a huge polynomial. + + # a is (m x n) + m = len(a) + if not m: + return K.one, [] + n = len(a[0]) + + d = None + pivots = [] + no_pivots = [] + + # i, j will be the row and column indices of the current pivot + i = 0 + for j in range(n): + # next pivot? + aij = a[i][j] + + # swap rows if zero + if not aij: + for ip in range(i+1, m): + aij = a[ip][j] + # row-swap + if aij: + a[i], a[ip] = a[ip], a[i] + break + else: + # go to next column + no_pivots.append(j) + continue + + # Now aij is the pivot and i,j are the row and column. We need to clear + # the column above and below but we also need to keep track of the + # denominator of the RREF which means also multiplying everything above + # and to the left by the current pivot aij and dividing by d (which we + # multiplied everything by in the previous iteration so this is an + # exact division). + # + # First handle the upper left corner which is usually already diagonal + # with all diagonal entries equal to the current denominator but there + # can be other non-zero entries in any column that has no pivot. + + # Update previous pivots in the matrix + if pivots: + pivot_val = aij * a[0][pivots[0]] + # Divide out the common factor + if d is not None: + pivot_val = K.exquo(pivot_val, d) + + # Could defer this until the end but it is pretty cheap and + # helps when debugging. + for ip, jp in enumerate(pivots): + a[ip][jp] = pivot_val + + # Update columns without pivots + for jnp in no_pivots: + for ip in range(i): + aijp = a[ip][jnp] + if aijp: + aijp *= aij + if d is not None: + aijp = K.exquo(aijp, d) + a[ip][jnp] = aijp + + # Eliminate above, below and to the right as in ordinary division free + # Gauss-Jordan elmination except also dividing out d from every entry. + + for jp, aj in enumerate(a): + + # Skip the current row + if jp == i: + continue + + # Eliminate to the right in all rows + for kp in range(j+1, n): + ajk = aij * aj[kp] - aj[j] * a[i][kp] + if d is not None: + ajk = K.exquo(ajk, d) + aj[kp] = ajk + + # Set to zero above and below the pivot + aj[j] = K.zero + + # next row + pivots.append(j) + i += 1 + + # no more rows left? + if i >= m: + break + + if not K.is_one(aij): + d = aij + else: + d = None + + if not pivots: + denom = K.one + else: + denom = a[0][pivots[0]] + + return denom, pivots + + +def ddm_idet(a, K): + """a <-- echelon(a); return det + + Explanation + =========== + + Compute the determinant of $a$ using the Bareiss fraction-free algorithm. + The matrix $a$ is modified in place. Its diagonal elements are the + determinants of the leading principal minors. The determinant of $a$ is + returned. + + The domain $K$ must support exact division (``K.exquo``). This method is + suitable for most exact rings and fields like :ref:`ZZ`, :ref:`QQ` and + :ref:`QQ(a)` but not for inexact domains like :ref:`RR` and :ref:`CC`. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.ddm import ddm_idet + >>> a = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]] + >>> a + [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + >>> ddm_idet(a, ZZ) + 0 + >>> a + [[1, 2, 3], [4, -3, -6], [7, -6, 0]] + >>> [a[i][i] for i in range(len(a))] + [1, -3, 0] + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.det + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Bareiss_algorithm + .. [2] https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf + """ + # Bareiss algorithm + # https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf + + # a is (m x n) + m = len(a) + if not m: + return K.one + n = len(a[0]) + + exquo = K.exquo + # uf keeps track of the sign change from row swaps + uf = K.one + + for k in range(n-1): + if not a[k][k]: + for i in range(k+1, n): + if a[i][k]: + a[k], a[i] = a[i], a[k] + uf = -uf + break + else: + return K.zero + + akkm1 = a[k-1][k-1] if k else K.one + + for i in range(k+1, n): + for j in range(k+1, n): + a[i][j] = exquo(a[i][j]*a[k][k] - a[i][k]*a[k][j], akkm1) + + return uf * a[-1][-1] + + +def ddm_iinv(ainv, a, K): + """ainv <-- inv(a) + + Compute the inverse of a matrix $a$ over a field $K$ using Gauss-Jordan + elimination. The result is stored in $ainv$. + + Uses division in the ground domain which should be an exact field. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import ddm_iinv, ddm_imatmul + >>> from sympy import QQ + >>> a = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]] + >>> ainv = [[None, None], [None, None]] + >>> ddm_iinv(ainv, a, QQ) + >>> ainv + [[-2, 1], [3/2, -1/2]] + >>> result = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]] + >>> ddm_imatmul(result, a, ainv) + >>> result + [[1, 0], [0, 1]] + + See Also + ======== + + ddm_irref: the underlying routine. + """ + if not K.is_Field: + raise DMDomainError('Not a field') + + # a is (m x n) + m = len(a) + if not m: + return + n = len(a[0]) + if m != n: + raise DMNonSquareMatrixError + + eye = [[K.one if i==j else K.zero for j in range(n)] for i in range(n)] + Aaug = [row + eyerow for row, eyerow in zip(a, eye)] + pivots = ddm_irref(Aaug) + if pivots != list(range(n)): + raise DMNonInvertibleMatrixError('Matrix det == 0; not invertible.') + ainv[:] = [row[n:] for row in Aaug] + + +def ddm_ilu_split(L, U, K): + """L, U <-- LU(U) + + Compute the LU decomposition of a matrix $L$ in place and store the lower + and upper triangular matrices in $L$ and $U$, respectively. Returns a list + of row swaps that were performed. + + Uses division in the ground domain which should be an exact field. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import ddm_ilu_split + >>> from sympy import QQ + >>> L = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]] + >>> U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]] + >>> swaps = ddm_ilu_split(L, U, QQ) + >>> swaps + [] + >>> L + [[0, 0], [3, 0]] + >>> U + [[1, 2], [0, -2]] + + See Also + ======== + + ddm_ilu + ddm_ilu_solve + """ + m = len(U) + if not m: + return [] + n = len(U[0]) + + swaps = ddm_ilu(U) + + zeros = [K.zero] * min(m, n) + for i in range(1, m): + j = min(i, n) + L[i][:j] = U[i][:j] + U[i][:j] = zeros[:j] + + return swaps + + +def ddm_ilu(a): + """a <-- LU(a) + + Computes the LU decomposition of a matrix in place. Returns a list of + row swaps that were performed. + + Uses division in the ground domain which should be an exact field. + + This is only suitable for domains like :ref:`GF(p)`, :ref:`QQ`, :ref:`QQ_I` + and :ref:`QQ(a)`. With a rational function field like :ref:`K(x)` it is + better to clear denominators and use division-free algorithms. Pivoting is + used to avoid exact zeros but not for floating point accuracy so :ref:`RR` + and :ref:`CC` are not suitable (use :func:`ddm_irref` instead). + + Examples + ======== + + >>> from sympy.polys.matrices.dense import ddm_ilu + >>> from sympy import QQ + >>> a = [[QQ(1, 2), QQ(1, 3)], [QQ(1, 4), QQ(1, 5)]] + >>> swaps = ddm_ilu(a) + >>> swaps + [] + >>> a + [[1/2, 1/3], [1/2, 1/30]] + + The same example using ``Matrix``: + + >>> from sympy import Matrix, S + >>> M = Matrix([[S(1)/2, S(1)/3], [S(1)/4, S(1)/5]]) + >>> L, U, swaps = M.LUdecomposition() + >>> L + Matrix([ + [ 1, 0], + [1/2, 1]]) + >>> U + Matrix([ + [1/2, 1/3], + [ 0, 1/30]]) + >>> swaps + [] + + See Also + ======== + + ddm_irref + ddm_ilu_solve + sympy.matrices.matrixbase.MatrixBase.LUdecomposition + """ + m = len(a) + if not m: + return [] + n = len(a[0]) + + swaps = [] + + for i in range(min(m, n)): + if not a[i][i]: + for ip in range(i+1, m): + if a[ip][i]: + swaps.append((i, ip)) + a[i], a[ip] = a[ip], a[i] + break + else: + # M = Matrix([[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]]) + continue + for j in range(i+1, m): + l_ji = a[j][i] / a[i][i] + a[j][i] = l_ji + for k in range(i+1, n): + a[j][k] -= l_ji * a[i][k] + + return swaps + + +def ddm_ilu_solve(x, L, U, swaps, b): + """x <-- solve(L*U*x = swaps(b)) + + Solve a linear system, $A*x = b$, given an LU factorization of $A$. + + Uses division in the ground domain which must be a field. + + Modifies $x$ in place. + + Examples + ======== + + Compute the LU decomposition of $A$ (in place): + + >>> from sympy import QQ + >>> from sympy.polys.matrices.dense import ddm_ilu, ddm_ilu_solve + >>> A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]] + >>> swaps = ddm_ilu(A) + >>> A + [[1, 2], [3, -2]] + >>> L = U = A + + Solve the linear system: + + >>> b = [[QQ(5)], [QQ(6)]] + >>> x = [[None], [None]] + >>> ddm_ilu_solve(x, L, U, swaps, b) + >>> x + [[-4], [9/2]] + + See Also + ======== + + ddm_ilu + Compute the LU decomposition of a matrix in place. + ddm_ilu_split + Compute the LU decomposition of a matrix and separate $L$ and $U$. + sympy.polys.matrices.domainmatrix.DomainMatrix.lu_solve + Higher level interface to this function. + """ + m = len(U) + if not m: + return + n = len(U[0]) + + m2 = len(b) + if not m2: + raise DMShapeError("Shape mismtch") + o = len(b[0]) + + if m != m2: + raise DMShapeError("Shape mismtch") + if m < n: + raise NotImplementedError("Underdetermined") + + if swaps: + b = [row[:] for row in b] + for i1, i2 in swaps: + b[i1], b[i2] = b[i2], b[i1] + + # solve Ly = b + y = [[None] * o for _ in range(m)] + for k in range(o): + for i in range(m): + rhs = b[i][k] + for j in range(i): + rhs -= L[i][j] * y[j][k] + y[i][k] = rhs + + if m > n: + for i in range(n, m): + for j in range(o): + if y[i][j]: + raise DMNonInvertibleMatrixError + + # Solve Ux = y + for k in range(o): + for i in reversed(range(n)): + if not U[i][i]: + raise DMNonInvertibleMatrixError + rhs = y[i][k] + for j in range(i+1, n): + rhs -= U[i][j] * x[j][k] + x[i][k] = rhs / U[i][i] + + +def ddm_berk(M, K): + """ + Berkowitz algorithm for computing the characteristic polynomial. + + Explanation + =========== + + The Berkowitz algorithm is a division-free algorithm for computing the + characteristic polynomial of a matrix over any commutative ring using only + arithmetic in the coefficient ring. + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.polys.matrices.dense import ddm_berk + >>> from sympy.polys.domains import ZZ + >>> M = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]] + >>> ddm_berk(M, ZZ) + [[1], [-5], [-2]] + >>> Matrix(M).charpoly() + PurePoly(lambda**2 - 5*lambda - 2, lambda, domain='ZZ') + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly + The high-level interface to this function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Samuelson%E2%80%93Berkowitz_algorithm + """ + m = len(M) + if not m: + return [[K.one]] + n = len(M[0]) + + if m != n: + raise DMShapeError("Not square") + + if n == 1: + return [[K.one], [-M[0][0]]] + + a = M[0][0] + R = [M[0][1:]] + C = [[row[0]] for row in M[1:]] + A = [row[1:] for row in M[1:]] + + q = ddm_berk(A, K) + + T = [[K.zero] * n for _ in range(n+1)] + for i in range(n): + T[i][i] = K.one + T[i+1][i] = -a + for i in range(2, n+1): + if i == 2: + AnC = C + else: + C = AnC + AnC = [[K.zero] for row in C] + ddm_imatmul(AnC, A, C) + RAnC = [[K.zero]] + ddm_imatmul(RAnC, R, AnC) + for j in range(0, n+1-i): + T[i+j][j] = -RAnC[0][0] + + qout = [[K.zero] for _ in range(n+1)] + ddm_imatmul(qout, T, q) + return qout diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/dfm.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/dfm.py new file mode 100644 index 0000000000000000000000000000000000000000..22938b7004654121f74b020bd6649bee84909e1e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/dfm.py @@ -0,0 +1,35 @@ +""" +sympy.polys.matrices.dfm + +Provides the :class:`DFM` class if ``GROUND_TYPES=flint'``. Otherwise, ``DFM`` +is a placeholder class that raises NotImplementedError when instantiated. +""" + +from sympy.external.gmpy import GROUND_TYPES + +if GROUND_TYPES == "flint": # pragma: no cover + # When python-flint is installed we will try to use it for dense matrices + # if the domain is supported by python-flint. + from ._dfm import DFM + +else: # pragma: no cover + # Other code should be able to import this and it should just present as a + # version of DFM that does not support any domains. + class DFM_dummy: + """ + Placeholder class for DFM when python-flint is not installed. + """ + def __init__(*args, **kwargs): + raise NotImplementedError("DFM requires GROUND_TYPES=flint.") + + @classmethod + def _supports_domain(cls, domain): + return False + + @classmethod + def _get_flint_func(cls, domain): + raise NotImplementedError("DFM requires GROUND_TYPES=flint.") + + # mypy really struggles with this kind of conditional type assignment. + # Maybe there is a better way to annotate this rather than type: ignore. + DFM = DFM_dummy # type: ignore diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/domainmatrix.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/domainmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..627835eca93b5e70f9aa121f097c9828a709ca78 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/domainmatrix.py @@ -0,0 +1,3983 @@ +""" + +Module for the DomainMatrix class. + +A DomainMatrix represents a matrix with elements that are in a particular +Domain. Each DomainMatrix internally wraps a DDM which is used for the +lower-level operations. The idea is that the DomainMatrix class provides the +convenience routines for converting between Expr and the poly domains as well +as unifying matrices with different domains. + +""" +from __future__ import annotations +from collections import Counter +from functools import reduce + +from sympy.external.gmpy import GROUND_TYPES +from sympy.utilities.decorator import doctest_depends_on + +from sympy.core.sympify import _sympify + +from ..domains import Domain + +from ..constructor import construct_domain + +from .exceptions import ( + DMFormatError, + DMBadInputError, + DMShapeError, + DMDomainError, + DMNotAField, + DMNonSquareMatrixError, + DMNonInvertibleMatrixError +) + +from .domainscalar import DomainScalar + +from sympy.polys.domains import ZZ, EXRAW, QQ + +from sympy.polys.densearith import dup_mul +from sympy.polys.densebasic import dup_convert +from sympy.polys.densetools import ( + dup_mul_ground, + dup_quo_ground, + dup_content, + dup_clear_denoms, + dup_primitive, + dup_transform, +) +from sympy.polys.factortools import dup_factor_list +from sympy.polys.polyutils import _sort_factors + +from .ddm import DDM + +from .sdm import SDM + +from .dfm import DFM + +from .rref import _dm_rref, _dm_rref_den + + +if GROUND_TYPES != 'flint': + __doctest_skip__ = ['DomainMatrix.to_dfm', 'DomainMatrix.to_dfm_or_ddm'] +else: + __doctest_skip__ = ['DomainMatrix.from_list'] + + +def DM(rows, domain): + """Convenient alias for DomainMatrix.from_list + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> DM([[1, 2], [3, 4]], ZZ) + DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ) + + See Also + ======== + + DomainMatrix.from_list + """ + return DomainMatrix.from_list(rows, domain) + + +class DomainMatrix: + r""" + Associate Matrix with :py:class:`~.Domain` + + Explanation + =========== + + DomainMatrix uses :py:class:`~.Domain` for its internal representation + which makes it faster than the SymPy Matrix class (currently) for many + common operations, but this advantage makes it not entirely compatible + with Matrix. DomainMatrix are analogous to numpy arrays with "dtype". + In the DomainMatrix, each element has a domain such as :ref:`ZZ` + or :ref:`QQ(a)`. + + + Examples + ======== + + Creating a DomainMatrix from the existing Matrix class: + + >>> from sympy import Matrix + >>> from sympy.polys.matrices import DomainMatrix + >>> Matrix1 = Matrix([ + ... [1, 2], + ... [3, 4]]) + >>> A = DomainMatrix.from_Matrix(Matrix1) + >>> A + DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ) + + Directly forming a DomainMatrix: + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> A + DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ) + + See Also + ======== + + DDM + SDM + Domain + Poly + + """ + rep: SDM | DDM | DFM + shape: tuple[int, int] + domain: Domain + + def __new__(cls, rows, shape, domain, *, fmt=None): + """ + Creates a :py:class:`~.DomainMatrix`. + + Parameters + ========== + + rows : Represents elements of DomainMatrix as list of lists + shape : Represents dimension of DomainMatrix + domain : Represents :py:class:`~.Domain` of DomainMatrix + + Raises + ====== + + TypeError + If any of rows, shape and domain are not provided + + """ + if isinstance(rows, (DDM, SDM, DFM)): + raise TypeError("Use from_rep to initialise from SDM/DDM") + elif isinstance(rows, list): + rep = DDM(rows, shape, domain) + elif isinstance(rows, dict): + rep = SDM(rows, shape, domain) + else: + msg = "Input should be list-of-lists or dict-of-dicts" + raise TypeError(msg) + + if fmt is not None: + if fmt == 'sparse': + rep = rep.to_sdm() + elif fmt == 'dense': + rep = rep.to_ddm() + else: + raise ValueError("fmt should be 'sparse' or 'dense'") + + # Use python-flint for dense matrices if possible + if rep.fmt == 'dense' and DFM._supports_domain(domain): + rep = rep.to_dfm() + + return cls.from_rep(rep) + + def __reduce__(self): + rep = self.rep + if rep.fmt == 'dense': + arg = self.to_list() + elif rep.fmt == 'sparse': + arg = dict(rep) + else: + raise RuntimeError # pragma: no cover + args = (arg, rep.shape, rep.domain) + return (self.__class__, args) + + def __getitem__(self, key): + i, j = key + m, n = self.shape + if not (isinstance(i, slice) or isinstance(j, slice)): + return DomainScalar(self.rep.getitem(i, j), self.domain) + + if not isinstance(i, slice): + if not -m <= i < m: + raise IndexError("Row index out of range") + i = i % m + i = slice(i, i+1) + if not isinstance(j, slice): + if not -n <= j < n: + raise IndexError("Column index out of range") + j = j % n + j = slice(j, j+1) + + return self.from_rep(self.rep.extract_slice(i, j)) + + def getitem_sympy(self, i, j): + return self.domain.to_sympy(self.rep.getitem(i, j)) + + def extract(self, rowslist, colslist): + return self.from_rep(self.rep.extract(rowslist, colslist)) + + def __setitem__(self, key, value): + i, j = key + if not self.domain.of_type(value): + raise TypeError + if isinstance(i, int) and isinstance(j, int): + self.rep.setitem(i, j, value) + else: + raise NotImplementedError + + @classmethod + def from_rep(cls, rep): + """Create a new DomainMatrix efficiently from DDM/SDM. + + Examples + ======== + + Create a :py:class:`~.DomainMatrix` with an dense internal + representation as :py:class:`~.DDM`: + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.polys.matrices.ddm import DDM + >>> drep = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> dM = DomainMatrix.from_rep(drep) + >>> dM + DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ) + + Create a :py:class:`~.DomainMatrix` with a sparse internal + representation as :py:class:`~.SDM`: + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import ZZ + >>> drep = SDM({0:{1:ZZ(1)},1:{0:ZZ(2)}}, (2, 2), ZZ) + >>> dM = DomainMatrix.from_rep(drep) + >>> dM + DomainMatrix({0: {1: 1}, 1: {0: 2}}, (2, 2), ZZ) + + Parameters + ========== + + rep: SDM or DDM + The internal sparse or dense representation of the matrix. + + Returns + ======= + + DomainMatrix + A :py:class:`~.DomainMatrix` wrapping *rep*. + + Notes + ===== + + This takes ownership of rep as its internal representation. If rep is + being mutated elsewhere then a copy should be provided to + ``from_rep``. Only minimal verification or checking is done on *rep* + as this is supposed to be an efficient internal routine. + + """ + if not (isinstance(rep, (DDM, SDM)) or (DFM is not None and isinstance(rep, DFM))): + raise TypeError("rep should be of type DDM or SDM") + self = super().__new__(cls) + self.rep = rep + self.shape = rep.shape + self.domain = rep.domain + return self + + @classmethod + @doctest_depends_on(ground_types=['python', 'gmpy']) + def from_list(cls, rows, domain): + r""" + Convert a list of lists into a DomainMatrix + + Parameters + ========== + + rows: list of lists + Each element of the inner lists should be either the single arg, + or tuple of args, that would be passed to the domain constructor + in order to form an element of the domain. See examples. + + Returns + ======= + + DomainMatrix containing elements defined in rows + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import FF, QQ, ZZ + >>> A = DomainMatrix.from_list([[1, 0, 1], [0, 0, 1]], ZZ) + >>> A + DomainMatrix([[1, 0, 1], [0, 0, 1]], (2, 3), ZZ) + >>> B = DomainMatrix.from_list([[1, 0, 1], [0, 0, 1]], FF(7)) + >>> B + DomainMatrix([[1 mod 7, 0 mod 7, 1 mod 7], [0 mod 7, 0 mod 7, 1 mod 7]], (2, 3), GF(7)) + >>> C = DomainMatrix.from_list([[(1, 2), (3, 1)], [(1, 4), (5, 1)]], QQ) + >>> C + DomainMatrix([[1/2, 3], [1/4, 5]], (2, 2), QQ) + + See Also + ======== + + from_list_sympy + + """ + nrows = len(rows) + ncols = 0 if not nrows else len(rows[0]) + conv = lambda e: domain(*e) if isinstance(e, tuple) else domain(e) + domain_rows = [[conv(e) for e in row] for row in rows] + return DomainMatrix(domain_rows, (nrows, ncols), domain) + + @classmethod + def from_list_sympy(cls, nrows, ncols, rows, **kwargs): + r""" + Convert a list of lists of Expr into a DomainMatrix using construct_domain + + Parameters + ========== + + nrows: number of rows + ncols: number of columns + rows: list of lists + + Returns + ======= + + DomainMatrix containing elements of rows + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.abc import x, y, z + >>> A = DomainMatrix.from_list_sympy(1, 3, [[x, y, z]]) + >>> A + DomainMatrix([[x, y, z]], (1, 3), ZZ[x,y,z]) + + See Also + ======== + + sympy.polys.constructor.construct_domain, from_dict_sympy + + """ + assert len(rows) == nrows + assert all(len(row) == ncols for row in rows) + + items_sympy = [_sympify(item) for row in rows for item in row] + + domain, items_domain = cls.get_domain(items_sympy, **kwargs) + + domain_rows = [[items_domain[ncols*r + c] for c in range(ncols)] for r in range(nrows)] + + return DomainMatrix(domain_rows, (nrows, ncols), domain) + + @classmethod + def from_dict_sympy(cls, nrows, ncols, elemsdict, **kwargs): + """ + + Parameters + ========== + + nrows: number of rows + ncols: number of cols + elemsdict: dict of dicts containing non-zero elements of the DomainMatrix + + Returns + ======= + + DomainMatrix containing elements of elemsdict + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.abc import x,y,z + >>> elemsdict = {0: {0:x}, 1:{1: y}, 2: {2: z}} + >>> A = DomainMatrix.from_dict_sympy(3, 3, elemsdict) + >>> A + DomainMatrix({0: {0: x}, 1: {1: y}, 2: {2: z}}, (3, 3), ZZ[x,y,z]) + + See Also + ======== + + from_list_sympy + + """ + if not all(0 <= r < nrows for r in elemsdict): + raise DMBadInputError("Row out of range") + if not all(0 <= c < ncols for row in elemsdict.values() for c in row): + raise DMBadInputError("Column out of range") + + items_sympy = [_sympify(item) for row in elemsdict.values() for item in row.values()] + domain, items_domain = cls.get_domain(items_sympy, **kwargs) + + idx = 0 + items_dict = {} + for i, row in elemsdict.items(): + items_dict[i] = {} + for j in row: + items_dict[i][j] = items_domain[idx] + idx += 1 + + return DomainMatrix(items_dict, (nrows, ncols), domain) + + @classmethod + def from_Matrix(cls, M, fmt='sparse',**kwargs): + r""" + Convert Matrix to DomainMatrix + + Parameters + ========== + + M: Matrix + + Returns + ======= + + Returns DomainMatrix with identical elements as M + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.polys.matrices import DomainMatrix + >>> M = Matrix([ + ... [1.0, 3.4], + ... [2.4, 1]]) + >>> A = DomainMatrix.from_Matrix(M) + >>> A + DomainMatrix({0: {0: 1.0, 1: 3.4}, 1: {0: 2.4, 1: 1.0}}, (2, 2), RR) + + We can keep internal representation as ddm using fmt='dense' + >>> from sympy import Matrix, QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix.from_Matrix(Matrix([[QQ(1, 2), QQ(3, 4)], [QQ(0, 1), QQ(0, 1)]]), fmt='dense') + >>> A.rep + [[1/2, 3/4], [0, 0]] + + See Also + ======== + + Matrix + + """ + if fmt == 'dense': + return cls.from_list_sympy(*M.shape, M.tolist(), **kwargs) + + return cls.from_dict_sympy(*M.shape, M.todod(), **kwargs) + + @classmethod + def get_domain(cls, items_sympy, **kwargs): + K, items_K = construct_domain(items_sympy, **kwargs) + return K, items_K + + def choose_domain(self, **opts): + """Convert to a domain found by :func:`~.construct_domain`. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> M = DM([[1, 2], [3, 4]], ZZ) + >>> M + DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ) + >>> M.choose_domain(field=True) + DomainMatrix([[1, 2], [3, 4]], (2, 2), QQ) + + >>> from sympy.abc import x + >>> M = DM([[1, x], [x**2, x**3]], ZZ[x]) + >>> M.choose_domain(field=True).domain + ZZ(x) + + Keyword arguments are passed to :func:`~.construct_domain`. + + See Also + ======== + + construct_domain + convert_to + """ + elements, data = self.to_sympy().to_flat_nz() + dom, elements_dom = construct_domain(elements, **opts) + return self.from_flat_nz(elements_dom, data, dom) + + def copy(self): + return self.from_rep(self.rep.copy()) + + def convert_to(self, K): + r""" + Change the domain of DomainMatrix to desired domain or field + + Parameters + ========== + + K : Represents the desired domain or field. + Alternatively, ``None`` may be passed, in which case this method + just returns a copy of this DomainMatrix. + + Returns + ======= + + DomainMatrix + DomainMatrix with the desired domain or field + + Examples + ======== + + >>> from sympy import ZZ, ZZ_I + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + >>> A.convert_to(ZZ_I) + DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ_I) + + """ + if K == self.domain: + return self.copy() + + rep = self.rep + + # The DFM, DDM and SDM types do not do any implicit conversions so we + # manage switching between DDM and DFM here. + if rep.is_DFM and not DFM._supports_domain(K): + rep_K = rep.to_ddm().convert_to(K) + elif rep.is_DDM and DFM._supports_domain(K): + rep_K = rep.convert_to(K).to_dfm() + else: + rep_K = rep.convert_to(K) + + return self.from_rep(rep_K) + + def to_sympy(self): + return self.convert_to(EXRAW) + + def to_field(self): + r""" + Returns a DomainMatrix with the appropriate field + + Returns + ======= + + DomainMatrix + DomainMatrix with the appropriate field + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + >>> A.to_field() + DomainMatrix([[1, 2], [3, 4]], (2, 2), QQ) + + """ + K = self.domain.get_field() + return self.convert_to(K) + + def to_sparse(self): + """ + Return a sparse DomainMatrix representation of *self*. + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ) + >>> A.rep + [[1, 0], [0, 2]] + >>> B = A.to_sparse() + >>> B.rep + {0: {0: 1}, 1: {1: 2}} + """ + if self.rep.fmt == 'sparse': + return self + + return self.from_rep(self.rep.to_sdm()) + + def to_dense(self): + """ + Return a dense DomainMatrix representation of *self*. + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> A = DomainMatrix({0: {0: 1}, 1: {1: 2}}, (2, 2), QQ) + >>> A.rep + {0: {0: 1}, 1: {1: 2}} + >>> B = A.to_dense() + >>> B.rep + [[1, 0], [0, 2]] + + """ + rep = self.rep + + if rep.fmt == 'dense': + return self + + return self.from_rep(rep.to_dfm_or_ddm()) + + def to_ddm(self): + """ + Return a :class:`~.DDM` representation of *self*. + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> A = DomainMatrix({0: {0: 1}, 1: {1: 2}}, (2, 2), QQ) + >>> ddm = A.to_ddm() + >>> ddm + [[1, 0], [0, 2]] + >>> type(ddm) + + + See Also + ======== + + to_sdm + to_dense + sympy.polys.matrices.ddm.DDM.to_sdm + """ + return self.rep.to_ddm() + + def to_sdm(self): + """ + Return a :class:`~.SDM` representation of *self*. + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ) + >>> sdm = A.to_sdm() + >>> sdm + {0: {0: 1}, 1: {1: 2}} + >>> type(sdm) + + + See Also + ======== + + to_ddm + to_sparse + sympy.polys.matrices.sdm.SDM.to_ddm + """ + return self.rep.to_sdm() + + @doctest_depends_on(ground_types=['flint']) + def to_dfm(self): + """ + Return a :class:`~.DFM` representation of *self*. + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ) + >>> dfm = A.to_dfm() + >>> dfm + [[1, 0], [0, 2]] + >>> type(dfm) + + + See Also + ======== + + to_ddm + to_dense + DFM + """ + return self.rep.to_dfm() + + @doctest_depends_on(ground_types=['flint']) + def to_dfm_or_ddm(self): + """ + Return a :class:`~.DFM` or :class:`~.DDM` representation of *self*. + + Explanation + =========== + + The :class:`~.DFM` representation can only be used if the ground types + are ``flint`` and the ground domain is supported by ``python-flint``. + This method will return a :class:`~.DFM` representation if possible, + but will return a :class:`~.DDM` representation otherwise. + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ) + >>> dfm = A.to_dfm_or_ddm() + >>> dfm + [[1, 0], [0, 2]] + >>> type(dfm) # Depends on the ground domain and ground types + + + See Also + ======== + + to_ddm: Always return a :class:`~.DDM` representation. + to_dfm: Returns a :class:`~.DFM` representation or raise an error. + to_dense: Convert internally to a :class:`~.DFM` or :class:`~.DDM` + DFM: The :class:`~.DFM` dense FLINT matrix representation. + DDM: The Python :class:`~.DDM` dense domain matrix representation. + """ + return self.rep.to_dfm_or_ddm() + + @classmethod + def _unify_domain(cls, *matrices): + """Convert matrices to a common domain""" + domains = {matrix.domain for matrix in matrices} + if len(domains) == 1: + return matrices + domain = reduce(lambda x, y: x.unify(y), domains) + return tuple(matrix.convert_to(domain) for matrix in matrices) + + @classmethod + def _unify_fmt(cls, *matrices, fmt=None): + """Convert matrices to the same format. + + If all matrices have the same format, then return unmodified. + Otherwise convert both to the preferred format given as *fmt* which + should be 'dense' or 'sparse'. + """ + formats = {matrix.rep.fmt for matrix in matrices} + if len(formats) == 1: + return matrices + if fmt == 'sparse': + return tuple(matrix.to_sparse() for matrix in matrices) + elif fmt == 'dense': + return tuple(matrix.to_dense() for matrix in matrices) + else: + raise ValueError("fmt should be 'sparse' or 'dense'") + + def unify(self, *others, fmt=None): + """ + Unifies the domains and the format of self and other + matrices. + + Parameters + ========== + + others : DomainMatrix + + fmt: string 'dense', 'sparse' or `None` (default) + The preferred format to convert to if self and other are not + already in the same format. If `None` or not specified then no + conversion if performed. + + Returns + ======= + + Tuple[DomainMatrix] + Matrices with unified domain and format + + Examples + ======== + + Unify the domain of DomainMatrix that have different domains: + + >>> from sympy import ZZ, QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + >>> B = DomainMatrix([[QQ(1, 2), QQ(2)]], (1, 2), QQ) + >>> Aq, Bq = A.unify(B) + >>> Aq + DomainMatrix([[1, 2]], (1, 2), QQ) + >>> Bq + DomainMatrix([[1/2, 2]], (1, 2), QQ) + + Unify the format (dense or sparse): + + >>> A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + >>> B = DomainMatrix({0:{0: ZZ(1)}}, (2, 2), ZZ) + >>> B.rep + {0: {0: 1}} + + >>> A2, B2 = A.unify(B, fmt='dense') + >>> B2.rep + [[1, 0], [0, 0]] + + See Also + ======== + + convert_to, to_dense, to_sparse + + """ + matrices = (self,) + others + matrices = DomainMatrix._unify_domain(*matrices) + if fmt is not None: + matrices = DomainMatrix._unify_fmt(*matrices, fmt=fmt) + return matrices + + def to_Matrix(self): + r""" + Convert DomainMatrix to Matrix + + Returns + ======= + + Matrix + MutableDenseMatrix for the DomainMatrix + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + >>> A.to_Matrix() + Matrix([ + [1, 2], + [3, 4]]) + + See Also + ======== + + from_Matrix + + """ + from sympy.matrices.dense import MutableDenseMatrix + + # XXX: If the internal representation of RepMatrix changes then this + # might need to be changed also. + if self.domain in (ZZ, QQ, EXRAW): + if self.rep.fmt == "sparse": + rep = self.copy() + else: + rep = self.to_sparse() + else: + rep = self.convert_to(EXRAW).to_sparse() + + return MutableDenseMatrix._fromrep(rep) + + def to_list(self): + """ + Convert :class:`DomainMatrix` to list of lists. + + See Also + ======== + + from_list + to_list_flat + to_flat_nz + to_dok + """ + return self.rep.to_list() + + def to_list_flat(self): + """ + Convert :class:`DomainMatrix` to flat list. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> A.to_list_flat() + [1, 2, 3, 4] + + See Also + ======== + + from_list_flat + to_list + to_flat_nz + to_dok + """ + return self.rep.to_list_flat() + + @classmethod + def from_list_flat(cls, elements, shape, domain): + """ + Create :class:`DomainMatrix` from flat list. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> element_list = [ZZ(1), ZZ(2), ZZ(3), ZZ(4)] + >>> A = DomainMatrix.from_list_flat(element_list, (2, 2), ZZ) + >>> A + DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ) + >>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain) + True + + See Also + ======== + + to_list_flat + """ + ddm = DDM.from_list_flat(elements, shape, domain) + return cls.from_rep(ddm.to_dfm_or_ddm()) + + def to_flat_nz(self): + """ + Convert :class:`DomainMatrix` to list of nonzero elements and data. + + Explanation + =========== + + Returns a tuple ``(elements, data)`` where ``elements`` is a list of + elements of the matrix with zeros possibly excluded. The matrix can be + reconstructed by passing these to :meth:`from_flat_nz`. The idea is to + be able to modify a flat list of the elements and then create a new + matrix of the same shape with the modified elements in the same + positions. + + The format of ``data`` differs depending on whether the underlying + representation is dense or sparse but either way it represents the + positions of the elements in the list in a way that + :meth:`from_flat_nz` can use to reconstruct the matrix. The + :meth:`from_flat_nz` method should be called on the same + :class:`DomainMatrix` that was used to call :meth:`to_flat_nz`. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> elements, data = A.to_flat_nz() + >>> elements + [1, 2, 3, 4] + >>> A == A.from_flat_nz(elements, data, A.domain) + True + + Create a matrix with the elements doubled: + + >>> elements_doubled = [2*x for x in elements] + >>> A2 = A.from_flat_nz(elements_doubled, data, A.domain) + >>> A2 == 2*A + True + + See Also + ======== + + from_flat_nz + """ + return self.rep.to_flat_nz() + + def from_flat_nz(self, elements, data, domain): + """ + Reconstruct :class:`DomainMatrix` after calling :meth:`to_flat_nz`. + + See :meth:`to_flat_nz` for explanation. + + See Also + ======== + + to_flat_nz + """ + rep = self.rep.from_flat_nz(elements, data, domain) + return self.from_rep(rep) + + def to_dod(self): + """ + Convert :class:`DomainMatrix` to dictionary of dictionaries (dod) format. + + Explanation + =========== + + Returns a dictionary of dictionaries representing the matrix. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> A = DM([[ZZ(1), ZZ(2), ZZ(0)], [ZZ(3), ZZ(0), ZZ(4)]], ZZ) + >>> A.to_dod() + {0: {0: 1, 1: 2}, 1: {0: 3, 2: 4}} + >>> A.to_sparse() == A.from_dod(A.to_dod(), A.shape, A.domain) + True + >>> A == A.from_dod_like(A.to_dod()) + True + + See Also + ======== + + from_dod + from_dod_like + to_dok + to_list + to_list_flat + to_flat_nz + sympy.matrices.matrixbase.MatrixBase.todod + """ + return self.rep.to_dod() + + @classmethod + def from_dod(cls, dod, shape, domain): + """ + Create sparse :class:`DomainMatrix` from dict of dict (dod) format. + + See :meth:`to_dod` for explanation. + + See Also + ======== + + to_dod + from_dod_like + """ + return cls.from_rep(SDM.from_dod(dod, shape, domain)) + + def from_dod_like(self, dod, domain=None): + """ + Create :class:`DomainMatrix` like ``self`` from dict of dict (dod) format. + + See :meth:`to_dod` for explanation. + + See Also + ======== + + to_dod + from_dod + """ + if domain is None: + domain = self.domain + return self.from_rep(self.rep.from_dod(dod, self.shape, domain)) + + def to_dok(self): + """ + Convert :class:`DomainMatrix` to dictionary of keys (dok) format. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(0)], + ... [ZZ(0), ZZ(4)]], (2, 2), ZZ) + >>> A.to_dok() + {(0, 0): 1, (1, 1): 4} + + The matrix can be reconstructed by calling :meth:`from_dok` although + the reconstructed matrix will always be in sparse format: + + >>> A.to_sparse() == A.from_dok(A.to_dok(), A.shape, A.domain) + True + + See Also + ======== + + from_dok + to_list + to_list_flat + to_flat_nz + """ + return self.rep.to_dok() + + @classmethod + def from_dok(cls, dok, shape, domain): + """ + Create :class:`DomainMatrix` from dictionary of keys (dok) format. + + See :meth:`to_dok` for explanation. + + See Also + ======== + + to_dok + """ + return cls.from_rep(SDM.from_dok(dok, shape, domain)) + + def iter_values(self): + """ + Iterate over nonzero elements of the matrix. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> list(A.iter_values()) + [1, 3, 4] + + See Also + ======== + + iter_items + to_list_flat + sympy.matrices.matrixbase.MatrixBase.iter_values + """ + return self.rep.iter_values() + + def iter_items(self): + """ + Iterate over indices and values of nonzero elements of the matrix. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> list(A.iter_items()) + [((0, 0), 1), ((1, 0), 3), ((1, 1), 4)] + + See Also + ======== + + iter_values + to_dok + sympy.matrices.matrixbase.MatrixBase.iter_items + """ + return self.rep.iter_items() + + def nnz(self): + """ + Number of nonzero elements in the matrix. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> A = DM([[1, 0], [0, 4]], ZZ) + >>> A.nnz() + 2 + """ + return self.rep.nnz() + + def __repr__(self): + return 'DomainMatrix(%s, %r, %r)' % (str(self.rep), self.shape, self.domain) + + def transpose(self): + """Matrix transpose of ``self``""" + return self.from_rep(self.rep.transpose()) + + def flat(self): + rows, cols = self.shape + return [self[i,j].element for i in range(rows) for j in range(cols)] + + @property + def is_zero_matrix(self): + return self.rep.is_zero_matrix() + + @property + def is_upper(self): + """ + Says whether this matrix is upper-triangular. True can be returned + even if the matrix is not square. + """ + return self.rep.is_upper() + + @property + def is_lower(self): + """ + Says whether this matrix is lower-triangular. True can be returned + even if the matrix is not square. + """ + return self.rep.is_lower() + + @property + def is_diagonal(self): + """ + True if the matrix is diagonal. + + Can return true for non-square matrices. A matrix is diagonal if + ``M[i,j] == 0`` whenever ``i != j``. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> M = DM([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(1)]], ZZ) + >>> M.is_diagonal + True + + See Also + ======== + + is_upper + is_lower + is_square + diagonal + """ + return self.rep.is_diagonal() + + def diagonal(self): + """ + Get the diagonal entries of the matrix as a list. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> M = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ) + >>> M.diagonal() + [1, 4] + + See Also + ======== + + is_diagonal + diag + """ + return self.rep.diagonal() + + @property + def is_square(self): + """ + True if the matrix is square. + """ + return self.shape[0] == self.shape[1] + + def rank(self): + rref, pivots = self.rref() + return len(pivots) + + def hstack(A, *B): + r"""Horizontally stack the given matrices. + + Parameters + ========== + + B: DomainMatrix + Matrices to stack horizontally. + + Returns + ======= + + DomainMatrix + DomainMatrix by stacking horizontally. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + + >>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ) + >>> A.hstack(B) + DomainMatrix([[1, 2, 5, 6], [3, 4, 7, 8]], (2, 4), ZZ) + + >>> C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ) + >>> A.hstack(B, C) + DomainMatrix([[1, 2, 5, 6, 9, 10], [3, 4, 7, 8, 11, 12]], (2, 6), ZZ) + + See Also + ======== + + unify + """ + A, *B = A.unify(*B, fmt=A.rep.fmt) + return DomainMatrix.from_rep(A.rep.hstack(*(Bk.rep for Bk in B))) + + def vstack(A, *B): + r"""Vertically stack the given matrices. + + Parameters + ========== + + B: DomainMatrix + Matrices to stack vertically. + + Returns + ======= + + DomainMatrix + DomainMatrix by stacking vertically. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + + >>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ) + >>> A.vstack(B) + DomainMatrix([[1, 2], [3, 4], [5, 6], [7, 8]], (4, 2), ZZ) + + >>> C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ) + >>> A.vstack(B, C) + DomainMatrix([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], (6, 2), ZZ) + + See Also + ======== + + unify + """ + A, *B = A.unify(*B, fmt='dense') + return DomainMatrix.from_rep(A.rep.vstack(*(Bk.rep for Bk in B))) + + def applyfunc(self, func, domain=None): + if domain is None: + domain = self.domain + return self.from_rep(self.rep.applyfunc(func, domain)) + + def __add__(A, B): + if not isinstance(B, DomainMatrix): + return NotImplemented + A, B = A.unify(B, fmt='dense') + return A.add(B) + + def __sub__(A, B): + if not isinstance(B, DomainMatrix): + return NotImplemented + A, B = A.unify(B, fmt='dense') + return A.sub(B) + + def __neg__(A): + return A.neg() + + def __mul__(A, B): + """A * B""" + if isinstance(B, DomainMatrix): + A, B = A.unify(B, fmt='dense') + return A.matmul(B) + elif B in A.domain: + return A.scalarmul(B) + elif isinstance(B, DomainScalar): + A, B = A.unify(B) + return A.scalarmul(B.element) + else: + return NotImplemented + + def __rmul__(A, B): + if B in A.domain: + return A.rscalarmul(B) + elif isinstance(B, DomainScalar): + A, B = A.unify(B) + return A.rscalarmul(B.element) + else: + return NotImplemented + + def __pow__(A, n): + """A ** n""" + if not isinstance(n, int): + return NotImplemented + return A.pow(n) + + def _check(a, op, b, ashape, bshape): + if a.domain != b.domain: + msg = "Domain mismatch: %s %s %s" % (a.domain, op, b.domain) + raise DMDomainError(msg) + if ashape != bshape: + msg = "Shape mismatch: %s %s %s" % (a.shape, op, b.shape) + raise DMShapeError(msg) + if a.rep.fmt != b.rep.fmt: + msg = "Format mismatch: %s %s %s" % (a.rep.fmt, op, b.rep.fmt) + raise DMFormatError(msg) + if type(a.rep) != type(b.rep): + msg = "Type mismatch: %s %s %s" % (type(a.rep), op, type(b.rep)) + raise DMFormatError(msg) + + def add(A, B): + r""" + Adds two DomainMatrix matrices of the same Domain + + Parameters + ========== + + A, B: DomainMatrix + matrices to add + + Returns + ======= + + DomainMatrix + DomainMatrix after Addition + + Raises + ====== + + DMShapeError + If the dimensions of the two DomainMatrix are not equal + + ValueError + If the domain of the two DomainMatrix are not same + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> B = DomainMatrix([ + ... [ZZ(4), ZZ(3)], + ... [ZZ(2), ZZ(1)]], (2, 2), ZZ) + + >>> A.add(B) + DomainMatrix([[5, 5], [5, 5]], (2, 2), ZZ) + + See Also + ======== + + sub, matmul + + """ + A._check('+', B, A.shape, B.shape) + return A.from_rep(A.rep.add(B.rep)) + + + def sub(A, B): + r""" + Subtracts two DomainMatrix matrices of the same Domain + + Parameters + ========== + + A, B: DomainMatrix + matrices to subtract + + Returns + ======= + + DomainMatrix + DomainMatrix after Subtraction + + Raises + ====== + + DMShapeError + If the dimensions of the two DomainMatrix are not equal + + ValueError + If the domain of the two DomainMatrix are not same + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> B = DomainMatrix([ + ... [ZZ(4), ZZ(3)], + ... [ZZ(2), ZZ(1)]], (2, 2), ZZ) + + >>> A.sub(B) + DomainMatrix([[-3, -1], [1, 3]], (2, 2), ZZ) + + See Also + ======== + + add, matmul + + """ + A._check('-', B, A.shape, B.shape) + return A.from_rep(A.rep.sub(B.rep)) + + def neg(A): + r""" + Returns the negative of DomainMatrix + + Parameters + ========== + + A : Represents a DomainMatrix + + Returns + ======= + + DomainMatrix + DomainMatrix after Negation + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + >>> A.neg() + DomainMatrix([[-1, -2], [-3, -4]], (2, 2), ZZ) + + """ + return A.from_rep(A.rep.neg()) + + def mul(A, b): + r""" + Performs term by term multiplication for the second DomainMatrix + w.r.t first DomainMatrix. Returns a DomainMatrix whose rows are + list of DomainMatrix matrices created after term by term multiplication. + + Parameters + ========== + + A, B: DomainMatrix + matrices to multiply term-wise + + Returns + ======= + + DomainMatrix + DomainMatrix after term by term multiplication + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> b = ZZ(2) + + >>> A.mul(b) + DomainMatrix([[2, 4], [6, 8]], (2, 2), ZZ) + + See Also + ======== + + matmul + + """ + return A.from_rep(A.rep.mul(b)) + + def rmul(A, b): + return A.from_rep(A.rep.rmul(b)) + + def matmul(A, B): + r""" + Performs matrix multiplication of two DomainMatrix matrices + + Parameters + ========== + + A, B: DomainMatrix + to multiply + + Returns + ======= + + DomainMatrix + DomainMatrix after multiplication + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> B = DomainMatrix([ + ... [ZZ(1), ZZ(1)], + ... [ZZ(0), ZZ(1)]], (2, 2), ZZ) + + >>> A.matmul(B) + DomainMatrix([[1, 3], [3, 7]], (2, 2), ZZ) + + See Also + ======== + + mul, pow, add, sub + + """ + + A._check('*', B, A.shape[1], B.shape[0]) + return A.from_rep(A.rep.matmul(B.rep)) + + def _scalarmul(A, lamda, reverse): + if lamda == A.domain.zero: + return DomainMatrix.zeros(A.shape, A.domain) + elif lamda == A.domain.one: + return A.copy() + elif reverse: + return A.rmul(lamda) + else: + return A.mul(lamda) + + def scalarmul(A, lamda): + return A._scalarmul(lamda, reverse=False) + + def rscalarmul(A, lamda): + return A._scalarmul(lamda, reverse=True) + + def mul_elementwise(A, B): + assert A.domain == B.domain + return A.from_rep(A.rep.mul_elementwise(B.rep)) + + def __truediv__(A, lamda): + """ Method for Scalar Division""" + if isinstance(lamda, int) or ZZ.of_type(lamda): + lamda = DomainScalar(ZZ(lamda), ZZ) + elif A.domain.is_Field and lamda in A.domain: + K = A.domain + lamda = DomainScalar(K.convert(lamda), K) + + if not isinstance(lamda, DomainScalar): + return NotImplemented + + A, lamda = A.to_field().unify(lamda) + if lamda.element == lamda.domain.zero: + raise ZeroDivisionError + if lamda.element == lamda.domain.one: + return A + + return A.mul(1 / lamda.element) + + def pow(A, n): + r""" + Computes A**n + + Parameters + ========== + + A : DomainMatrix + + n : exponent for A + + Returns + ======= + + DomainMatrix + DomainMatrix on computing A**n + + Raises + ====== + + NotImplementedError + if n is negative. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(1)], + ... [ZZ(0), ZZ(1)]], (2, 2), ZZ) + + >>> A.pow(2) + DomainMatrix([[1, 2], [0, 1]], (2, 2), ZZ) + + See Also + ======== + + matmul + + """ + nrows, ncols = A.shape + if nrows != ncols: + raise DMNonSquareMatrixError('Power of a nonsquare matrix') + if n < 0: + raise NotImplementedError('Negative powers') + elif n == 0: + return A.eye(nrows, A.domain) + elif n == 1: + return A + elif n % 2 == 1: + return A * A**(n - 1) + else: + sqrtAn = A ** (n // 2) + return sqrtAn * sqrtAn + + def scc(self): + """Compute the strongly connected components of a DomainMatrix + + Explanation + =========== + + A square matrix can be considered as the adjacency matrix for a + directed graph where the row and column indices are the vertices. In + this graph if there is an edge from vertex ``i`` to vertex ``j`` if + ``M[i, j]`` is nonzero. This routine computes the strongly connected + components of that graph which are subsets of the rows and columns that + are connected by some nonzero element of the matrix. The strongly + connected components are useful because many operations such as the + determinant can be computed by working with the submatrices + corresponding to each component. + + Examples + ======== + + Find the strongly connected components of a matrix: + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> M = DomainMatrix([[ZZ(1), ZZ(0), ZZ(2)], + ... [ZZ(0), ZZ(3), ZZ(0)], + ... [ZZ(4), ZZ(6), ZZ(5)]], (3, 3), ZZ) + >>> M.scc() + [[1], [0, 2]] + + Compute the determinant from the components: + + >>> MM = M.to_Matrix() + >>> MM + Matrix([ + [1, 0, 2], + [0, 3, 0], + [4, 6, 5]]) + >>> MM[[1], [1]] + Matrix([[3]]) + >>> MM[[0, 2], [0, 2]] + Matrix([ + [1, 2], + [4, 5]]) + >>> MM.det() + -9 + >>> MM[[1], [1]].det() * MM[[0, 2], [0, 2]].det() + -9 + + The components are given in reverse topological order and represent a + permutation of the rows and columns that will bring the matrix into + block lower-triangular form: + + >>> MM[[1, 0, 2], [1, 0, 2]] + Matrix([ + [3, 0, 0], + [0, 1, 2], + [6, 4, 5]]) + + Returns + ======= + + List of lists of integers + Each list represents a strongly connected component. + + See also + ======== + + sympy.matrices.matrixbase.MatrixBase.strongly_connected_components + sympy.utilities.iterables.strongly_connected_components + + """ + if not self.is_square: + raise DMNonSquareMatrixError('Matrix must be square for scc') + + return self.rep.scc() + + def clear_denoms(self, convert=False): + """ + Clear denominators, but keep the domain unchanged. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DM + >>> A = DM([[(1,2), (1,3)], [(1,4), (1,5)]], QQ) + >>> den, Anum = A.clear_denoms() + >>> den.to_sympy() + 60 + >>> Anum.to_Matrix() + Matrix([ + [30, 20], + [15, 12]]) + >>> den * A == Anum + True + + The numerator matrix will be in the same domain as the original matrix + unless ``convert`` is set to ``True``: + + >>> A.clear_denoms()[1].domain + QQ + >>> A.clear_denoms(convert=True)[1].domain + ZZ + + The denominator is always in the associated ring: + + >>> A.clear_denoms()[0].domain + ZZ + >>> A.domain.get_ring() + ZZ + + See Also + ======== + + sympy.polys.polytools.Poly.clear_denoms + clear_denoms_rowwise + """ + elems0, data = self.to_flat_nz() + + K0 = self.domain + K1 = K0.get_ring() if K0.has_assoc_Ring else K0 + + den, elems1 = dup_clear_denoms(elems0, K0, K1, convert=convert) + + if convert: + Kden, Knum = K1, K1 + else: + Kden, Knum = K1, K0 + + den = DomainScalar(den, Kden) + num = self.from_flat_nz(elems1, data, Knum) + + return den, num + + def clear_denoms_rowwise(self, convert=False): + """ + Clear denominators from each row of the matrix. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DM + >>> A = DM([[(1,2), (1,3), (1,4)], [(1,5), (1,6), (1,7)]], QQ) + >>> den, Anum = A.clear_denoms_rowwise() + >>> den.to_Matrix() + Matrix([ + [12, 0], + [ 0, 210]]) + >>> Anum.to_Matrix() + Matrix([ + [ 6, 4, 3], + [42, 35, 30]]) + + The denominator matrix is a diagonal matrix with the denominators of + each row on the diagonal. The invariants are: + + >>> den * A == Anum + True + >>> A == den.to_field().inv() * Anum + True + + The numerator matrix will be in the same domain as the original matrix + unless ``convert`` is set to ``True``: + + >>> A.clear_denoms_rowwise()[1].domain + QQ + >>> A.clear_denoms_rowwise(convert=True)[1].domain + ZZ + + The domain of the denominator matrix is the associated ring: + + >>> A.clear_denoms_rowwise()[0].domain + ZZ + + See Also + ======== + + sympy.polys.polytools.Poly.clear_denoms + clear_denoms + """ + dod = self.to_dod() + + K0 = self.domain + K1 = K0.get_ring() if K0.has_assoc_Ring else K0 + + diagonals = [K0.one] * self.shape[0] + dod_num = {} + for i, rowi in dod.items(): + indices, elems = zip(*rowi.items()) + den, elems_num = dup_clear_denoms(elems, K0, K1, convert=convert) + rowi_num = dict(zip(indices, elems_num)) + diagonals[i] = den + dod_num[i] = rowi_num + + if convert: + Kden, Knum = K1, K1 + else: + Kden, Knum = K1, K0 + + den = self.diag(diagonals, Kden) + num = self.from_dod_like(dod_num, Knum) + + return den, num + + def cancel_denom(self, denom): + """ + Cancel factors between a matrix and a denominator. + + Returns a matrix and denominator on lowest terms. + + Requires ``gcd`` in the ground domain. + + Methods like :meth:`solve_den`, :meth:`inv_den` and :meth:`rref_den` + return a matrix and denominator but not necessarily on lowest terms. + Reduction to lowest terms without fractions can be performed with + :meth:`cancel_denom`. + + Examples + ======== + + >>> from sympy.polys.matrices import DM + >>> from sympy import ZZ + >>> M = DM([[2, 2, 0], + ... [0, 2, 2], + ... [0, 0, 2]], ZZ) + >>> Minv, den = M.inv_den() + >>> Minv.to_Matrix() + Matrix([ + [1, -1, 1], + [0, 1, -1], + [0, 0, 1]]) + >>> den + 2 + >>> Minv_reduced, den_reduced = Minv.cancel_denom(den) + >>> Minv_reduced.to_Matrix() + Matrix([ + [1, -1, 1], + [0, 1, -1], + [0, 0, 1]]) + >>> den_reduced + 2 + >>> Minv_reduced.to_field() / den_reduced == Minv.to_field() / den + True + + The denominator is made canonical with respect to units (e.g. a + negative denominator is made positive): + + >>> M = DM([[2, 2, 0]], ZZ) + >>> den = ZZ(-4) + >>> M.cancel_denom(den) + (DomainMatrix([[-1, -1, 0]], (1, 3), ZZ), 2) + + Any factor common to _all_ elements will be cancelled but there can + still be factors in common between _some_ elements of the matrix and + the denominator. To cancel factors between each element and the + denominator, use :meth:`cancel_denom_elementwise` or otherwise convert + to a field and use division: + + >>> M = DM([[4, 6]], ZZ) + >>> den = ZZ(12) + >>> M.cancel_denom(den) + (DomainMatrix([[2, 3]], (1, 2), ZZ), 6) + >>> numers, denoms = M.cancel_denom_elementwise(den) + >>> numers + DomainMatrix([[1, 1]], (1, 2), ZZ) + >>> denoms + DomainMatrix([[3, 2]], (1, 2), ZZ) + >>> M.to_field() / den + DomainMatrix([[1/3, 1/2]], (1, 2), QQ) + + See Also + ======== + + solve_den + inv_den + rref_den + cancel_denom_elementwise + """ + M = self + K = self.domain + + if K.is_zero(denom): + raise ZeroDivisionError('denominator is zero') + elif K.is_one(denom): + return (M.copy(), denom) + + elements, data = M.to_flat_nz() + + # First canonicalize the denominator (e.g. multiply by -1). + if K.is_negative(denom): + u = -K.one + else: + u = K.canonical_unit(denom) + + # Often after e.g. solve_den the denominator will be much more + # complicated than the elements of the numerator. Hopefully it will be + # quicker to find the gcd of the numerator and if there is no content + # then we do not need to look at the denominator at all. + content = dup_content(elements, K) + common = K.gcd(content, denom) + + if not K.is_one(content): + + common = K.gcd(content, denom) + + if not K.is_one(common): + elements = dup_quo_ground(elements, common, K) + denom = K.quo(denom, common) + + if not K.is_one(u): + elements = dup_mul_ground(elements, u, K) + denom = u * denom + elif K.is_one(common): + return (M.copy(), denom) + + M_cancelled = M.from_flat_nz(elements, data, K) + + return M_cancelled, denom + + def cancel_denom_elementwise(self, denom): + """ + Cancel factors between the elements of a matrix and a denominator. + + Returns a matrix of numerators and matrix of denominators. + + Requires ``gcd`` in the ground domain. + + Examples + ======== + + >>> from sympy.polys.matrices import DM + >>> from sympy import ZZ + >>> M = DM([[2, 3], [4, 12]], ZZ) + >>> denom = ZZ(6) + >>> numers, denoms = M.cancel_denom_elementwise(denom) + >>> numers.to_Matrix() + Matrix([ + [1, 1], + [2, 2]]) + >>> denoms.to_Matrix() + Matrix([ + [3, 2], + [3, 1]]) + >>> M_frac = (M.to_field() / denom).to_Matrix() + >>> M_frac + Matrix([ + [1/3, 1/2], + [2/3, 2]]) + >>> denoms_inverted = denoms.to_Matrix().applyfunc(lambda e: 1/e) + >>> numers.to_Matrix().multiply_elementwise(denoms_inverted) == M_frac + True + + Use :meth:`cancel_denom` to cancel factors between the matrix and the + denominator while preserving the form of a matrix with a scalar + denominator. + + See Also + ======== + + cancel_denom + """ + K = self.domain + M = self + + if K.is_zero(denom): + raise ZeroDivisionError('denominator is zero') + elif K.is_one(denom): + M_numers = M.copy() + M_denoms = M.ones(M.shape, M.domain) + return (M_numers, M_denoms) + + elements, data = M.to_flat_nz() + + cofactors = [K.cofactors(numer, denom) for numer in elements] + gcds, numers, denoms = zip(*cofactors) + + M_numers = M.from_flat_nz(list(numers), data, K) + M_denoms = M.from_flat_nz(list(denoms), data, K) + + return (M_numers, M_denoms) + + def content(self): + """ + Return the gcd of the elements of the matrix. + + Requires ``gcd`` in the ground domain. + + Examples + ======== + + >>> from sympy.polys.matrices import DM + >>> from sympy import ZZ + >>> M = DM([[2, 4], [4, 12]], ZZ) + >>> M.content() + 2 + + See Also + ======== + + primitive + cancel_denom + """ + K = self.domain + elements, _ = self.to_flat_nz() + return dup_content(elements, K) + + def primitive(self): + """ + Factor out gcd of the elements of a matrix. + + Requires ``gcd`` in the ground domain. + + Examples + ======== + + >>> from sympy.polys.matrices import DM + >>> from sympy import ZZ + >>> M = DM([[2, 4], [4, 12]], ZZ) + >>> content, M_primitive = M.primitive() + >>> content + 2 + >>> M_primitive + DomainMatrix([[1, 2], [2, 6]], (2, 2), ZZ) + >>> content * M_primitive == M + True + >>> M_primitive.content() == ZZ(1) + True + + See Also + ======== + + content + cancel_denom + """ + K = self.domain + elements, data = self.to_flat_nz() + content, prims = dup_primitive(elements, K) + M_primitive = self.from_flat_nz(prims, data, K) + return content, M_primitive + + def rref(self, *, method='auto'): + r""" + Returns reduced-row echelon form (RREF) and list of pivots. + + If the domain is not a field then it will be converted to a field. See + :meth:`rref_den` for the fraction-free version of this routine that + returns RREF with denominator instead. + + The domain must either be a field or have an associated fraction field + (see :meth:`to_field`). + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [QQ(2), QQ(-1), QQ(0)], + ... [QQ(-1), QQ(2), QQ(-1)], + ... [QQ(0), QQ(0), QQ(2)]], (3, 3), QQ) + + >>> rref_matrix, rref_pivots = A.rref() + >>> rref_matrix + DomainMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], (3, 3), QQ) + >>> rref_pivots + (0, 1, 2) + + Parameters + ========== + + method : str, optional (default: 'auto') + The method to use to compute the RREF. The default is ``'auto'``, + which will attempt to choose the fastest method. The other options + are: + + - ``A.rref(method='GJ')`` uses Gauss-Jordan elimination with + division. If the domain is not a field then it will be converted + to a field with :meth:`to_field` first and RREF will be computed + by inverting the pivot elements in each row. This is most + efficient for very sparse matrices or for matrices whose elements + have complex denominators. + + - ``A.rref(method='FF')`` uses fraction-free Gauss-Jordan + elimination. Elimination is performed using exact division + (``exquo``) to control the growth of the coefficients. In this + case the current domain is always used for elimination but if + the domain is not a field then it will be converted to a field + at the end and divided by the denominator. This is most efficient + for dense matrices or for matrices with simple denominators. + + - ``A.rref(method='CD')`` clears the denominators before using + fraction-free Gauss-Jordan elimination in the associated ring. + This is most efficient for dense matrices with very simple + denominators. + + - ``A.rref(method='GJ_dense')``, ``A.rref(method='FF_dense')``, and + ``A.rref(method='CD_dense')`` are the same as the above methods + except that the dense implementations of the algorithms are used. + By default ``A.rref(method='auto')`` will usually choose the + sparse implementations for RREF. + + Regardless of which algorithm is used the returned matrix will + always have the same format (sparse or dense) as the input and its + domain will always be the field of fractions of the input domain. + + Returns + ======= + + (DomainMatrix, list) + reduced-row echelon form and list of pivots for the DomainMatrix + + See Also + ======== + + rref_den + RREF with denominator + sympy.polys.matrices.sdm.sdm_irref + Sparse implementation of ``method='GJ'``. + sympy.polys.matrices.sdm.sdm_rref_den + Sparse implementation of ``method='FF'`` and ``method='CD'``. + sympy.polys.matrices.dense.ddm_irref + Dense implementation of ``method='GJ'``. + sympy.polys.matrices.dense.ddm_irref_den + Dense implementation of ``method='FF'`` and ``method='CD'``. + clear_denoms + Clear denominators from a matrix, used by ``method='CD'`` and + by ``method='GJ'`` when the original domain is not a field. + + """ + return _dm_rref(self, method=method) + + def rref_den(self, *, method='auto', keep_domain=True): + r""" + Returns reduced-row echelon form with denominator and list of pivots. + + Requires exact division in the ground domain (``exquo``). + + Examples + ======== + + >>> from sympy import ZZ, QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(2), ZZ(-1), ZZ(0)], + ... [ZZ(-1), ZZ(2), ZZ(-1)], + ... [ZZ(0), ZZ(0), ZZ(2)]], (3, 3), ZZ) + + >>> A_rref, denom, pivots = A.rref_den() + >>> A_rref + DomainMatrix([[6, 0, 0], [0, 6, 0], [0, 0, 6]], (3, 3), ZZ) + >>> denom + 6 + >>> pivots + (0, 1, 2) + >>> A_rref.to_field() / denom + DomainMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], (3, 3), QQ) + >>> A_rref.to_field() / denom == A.convert_to(QQ).rref()[0] + True + + Parameters + ========== + + method : str, optional (default: 'auto') + The method to use to compute the RREF. The default is ``'auto'``, + which will attempt to choose the fastest method. The other options + are: + + - ``A.rref(method='FF')`` uses fraction-free Gauss-Jordan + elimination. Elimination is performed using exact division + (``exquo``) to control the growth of the coefficients. In this + case the current domain is always used for elimination and the + result is always returned as a matrix over the current domain. + This is most efficient for dense matrices or for matrices with + simple denominators. + + - ``A.rref(method='CD')`` clears denominators before using + fraction-free Gauss-Jordan elimination in the associated ring. + The result will be converted back to the original domain unless + ``keep_domain=False`` is passed in which case the result will be + over the ring used for elimination. This is most efficient for + dense matrices with very simple denominators. + + - ``A.rref(method='GJ')`` uses Gauss-Jordan elimination with + division. If the domain is not a field then it will be converted + to a field with :meth:`to_field` first and RREF will be computed + by inverting the pivot elements in each row. The result is + converted back to the original domain by clearing denominators + unless ``keep_domain=False`` is passed in which case the result + will be over the field used for elimination. This is most + efficient for very sparse matrices or for matrices whose elements + have complex denominators. + + - ``A.rref(method='GJ_dense')``, ``A.rref(method='FF_dense')``, and + ``A.rref(method='CD_dense')`` are the same as the above methods + except that the dense implementations of the algorithms are used. + By default ``A.rref(method='auto')`` will usually choose the + sparse implementations for RREF. + + Regardless of which algorithm is used the returned matrix will + always have the same format (sparse or dense) as the input and if + ``keep_domain=True`` its domain will always be the same as the + input. + + keep_domain : bool, optional + If True (the default), the domain of the returned matrix and + denominator are the same as the domain of the input matrix. If + False, the domain of the returned matrix might be changed to an + associated ring or field if the algorithm used a different domain. + This is useful for efficiency if the caller does not need the + result to be in the original domain e.g. it avoids clearing + denominators in the case of ``A.rref(method='GJ')``. + + Returns + ======= + + (DomainMatrix, scalar, list) + Reduced-row echelon form, denominator and list of pivot indices. + + See Also + ======== + + rref + RREF without denominator for field domains. + sympy.polys.matrices.sdm.sdm_irref + Sparse implementation of ``method='GJ'``. + sympy.polys.matrices.sdm.sdm_rref_den + Sparse implementation of ``method='FF'`` and ``method='CD'``. + sympy.polys.matrices.dense.ddm_irref + Dense implementation of ``method='GJ'``. + sympy.polys.matrices.dense.ddm_irref_den + Dense implementation of ``method='FF'`` and ``method='CD'``. + clear_denoms + Clear denominators from a matrix, used by ``method='CD'``. + + """ + return _dm_rref_den(self, method=method, keep_domain=keep_domain) + + def columnspace(self): + r""" + Returns the columnspace for the DomainMatrix + + Returns + ======= + + DomainMatrix + The columns of this matrix form a basis for the columnspace. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [QQ(1), QQ(-1)], + ... [QQ(2), QQ(-2)]], (2, 2), QQ) + >>> A.columnspace() + DomainMatrix([[1], [2]], (2, 1), QQ) + + """ + if not self.domain.is_Field: + raise DMNotAField('Not a field') + rref, pivots = self.rref() + rows, cols = self.shape + return self.extract(range(rows), pivots) + + def rowspace(self): + r""" + Returns the rowspace for the DomainMatrix + + Returns + ======= + + DomainMatrix + The rows of this matrix form a basis for the rowspace. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [QQ(1), QQ(-1)], + ... [QQ(2), QQ(-2)]], (2, 2), QQ) + >>> A.rowspace() + DomainMatrix([[1, -1]], (1, 2), QQ) + + """ + if not self.domain.is_Field: + raise DMNotAField('Not a field') + rref, pivots = self.rref() + rows, cols = self.shape + return self.extract(range(len(pivots)), range(cols)) + + def nullspace(self, divide_last=False): + r""" + Returns the nullspace for the DomainMatrix + + Returns + ======= + + DomainMatrix + The rows of this matrix form a basis for the nullspace. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DM + >>> A = DM([ + ... [QQ(2), QQ(-2)], + ... [QQ(4), QQ(-4)]], QQ) + >>> A.nullspace() + DomainMatrix([[1, 1]], (1, 2), QQ) + + The returned matrix is a basis for the nullspace: + + >>> A_null = A.nullspace().transpose() + >>> A * A_null + DomainMatrix([[0], [0]], (2, 1), QQ) + >>> rows, cols = A.shape + >>> nullity = rows - A.rank() + >>> A_null.shape == (cols, nullity) + True + + Nullspace can also be computed for non-field rings. If the ring is not + a field then division is not used. Setting ``divide_last`` to True will + raise an error in this case: + + >>> from sympy import ZZ + >>> B = DM([[6, -3], + ... [4, -2]], ZZ) + >>> B.nullspace() + DomainMatrix([[3, 6]], (1, 2), ZZ) + >>> B.nullspace(divide_last=True) + Traceback (most recent call last): + ... + DMNotAField: Cannot normalize vectors over a non-field + + Over a ring with ``gcd`` defined the nullspace can potentially be + reduced with :meth:`primitive`: + + >>> B.nullspace().primitive() + (3, DomainMatrix([[1, 2]], (1, 2), ZZ)) + + A matrix over a ring can often be normalized by converting it to a + field but it is often a bad idea to do so: + + >>> from sympy.abc import a, b, c + >>> from sympy import Matrix + >>> M = Matrix([[ a*b, b + c, c], + ... [ a - b, b*c, c**2], + ... [a*b + a - b, b*c + b + c, c**2 + c]]) + >>> M.to_DM().domain + ZZ[a,b,c] + >>> M.to_DM().nullspace().to_Matrix().transpose() + Matrix([ + [ c**3], + [ -a*b*c**2 + a*c - b*c], + [a*b**2*c - a*b - a*c + b**2 + b*c]]) + + The unnormalized form here is nicer than the normalized form that + spreads a large denominator throughout the matrix: + + >>> M.to_DM().to_field().nullspace(divide_last=True).to_Matrix().transpose() + Matrix([ + [ c**3/(a*b**2*c - a*b - a*c + b**2 + b*c)], + [(-a*b*c**2 + a*c - b*c)/(a*b**2*c - a*b - a*c + b**2 + b*c)], + [ 1]]) + + Parameters + ========== + + divide_last : bool, optional + If False (the default), the vectors are not normalized and the RREF + is computed using :meth:`rref_den` and the denominator is + discarded. If True, then each row is divided by its final element; + the domain must be a field in this case. + + See Also + ======== + + nullspace_from_rref + rref + rref_den + rowspace + """ + A = self + K = A.domain + + if divide_last and not K.is_Field: + raise DMNotAField("Cannot normalize vectors over a non-field") + + if divide_last: + A_rref, pivots = A.rref() + else: + A_rref, den, pivots = A.rref_den() + + # Ensure that the sign is canonical before discarding the + # denominator. Then M.nullspace().primitive() is canonical. + u = K.canonical_unit(den) + if u != K.one: + A_rref *= u + + A_null = A_rref.nullspace_from_rref(pivots) + + return A_null + + def nullspace_from_rref(self, pivots=None): + """ + Compute nullspace from rref and pivots. + + The domain of the matrix can be any domain. + + The matrix must be in reduced row echelon form already. Otherwise the + result will be incorrect. Use :meth:`rref` or :meth:`rref_den` first + to get the reduced row echelon form or use :meth:`nullspace` instead. + + See Also + ======== + + nullspace + rref + rref_den + sympy.polys.matrices.sdm.SDM.nullspace_from_rref + sympy.polys.matrices.ddm.DDM.nullspace_from_rref + """ + null_rep, nonpivots = self.rep.nullspace_from_rref(pivots) + return self.from_rep(null_rep) + + def inv(self): + r""" + Finds the inverse of the DomainMatrix if exists + + Returns + ======= + + DomainMatrix + DomainMatrix after inverse + + Raises + ====== + + ValueError + If the domain of DomainMatrix not a Field + + DMNonSquareMatrixError + If the DomainMatrix is not a not Square DomainMatrix + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [QQ(2), QQ(-1), QQ(0)], + ... [QQ(-1), QQ(2), QQ(-1)], + ... [QQ(0), QQ(0), QQ(2)]], (3, 3), QQ) + >>> A.inv() + DomainMatrix([[2/3, 1/3, 1/6], [1/3, 2/3, 1/3], [0, 0, 1/2]], (3, 3), QQ) + + See Also + ======== + + neg + + """ + if not self.domain.is_Field: + raise DMNotAField('Not a field') + m, n = self.shape + if m != n: + raise DMNonSquareMatrixError + inv = self.rep.inv() + return self.from_rep(inv) + + def det(self): + r""" + Returns the determinant of a square :class:`DomainMatrix`. + + Returns + ======= + + determinant: DomainElement + Determinant of the matrix. + + Raises + ====== + + ValueError + If the domain of DomainMatrix is not a Field + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + >>> A.det() + -2 + + """ + m, n = self.shape + if m != n: + raise DMNonSquareMatrixError + return self.rep.det() + + def adj_det(self): + """ + Adjugate and determinant of a square :class:`DomainMatrix`. + + Returns + ======= + + (adjugate, determinant) : (DomainMatrix, DomainScalar) + The adjugate matrix and determinant of this matrix. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> A = DM([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], ZZ) + >>> adjA, detA = A.adj_det() + >>> adjA + DomainMatrix([[4, -2], [-3, 1]], (2, 2), ZZ) + >>> detA + -2 + + See Also + ======== + + adjugate + Returns only the adjugate matrix. + det + Returns only the determinant. + inv_den + Returns a matrix/denominator pair representing the inverse matrix + but perhaps differing from the adjugate and determinant by a common + factor. + """ + m, n = self.shape + I_m = self.eye((m, m), self.domain) + adjA, detA = self.solve_den_charpoly(I_m, check=False) + if self.rep.fmt == "dense": + adjA = adjA.to_dense() + return adjA, detA + + def adjugate(self): + """ + Adjugate of a square :class:`DomainMatrix`. + + The adjugate matrix is the transpose of the cofactor matrix and is + related to the inverse by:: + + adj(A) = det(A) * A.inv() + + Unlike the inverse matrix the adjugate matrix can be computed and + expressed without division or fractions in the ground domain. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ) + >>> A.adjugate() + DomainMatrix([[4, -2], [-3, 1]], (2, 2), ZZ) + + Returns + ======= + + DomainMatrix + The adjugate matrix of this matrix with the same domain. + + See Also + ======== + + adj_det + """ + adjA, detA = self.adj_det() + return adjA + + def inv_den(self, method=None): + """ + Return the inverse as a :class:`DomainMatrix` with denominator. + + Returns + ======= + + (inv, den) : (:class:`DomainMatrix`, :class:`~.DomainElement`) + The inverse matrix and its denominator. + + This is more or less equivalent to :meth:`adj_det` except that ``inv`` + and ``den`` are not guaranteed to be the adjugate and inverse. The + ratio ``inv/den`` is equivalent to ``adj/det`` but some factors + might be cancelled between ``inv`` and ``den``. In simple cases this + might just be a minus sign so that ``(inv, den) == (-adj, -det)`` but + factors more complicated than ``-1`` can also be cancelled. + Cancellation is not guaranteed to be complete so ``inv`` and ``den`` + may not be on lowest terms. The denominator ``den`` will be zero if and + only if the determinant is zero. + + If the actual adjugate and determinant are needed, use :meth:`adj_det` + instead. If the intention is to compute the inverse matrix or solve a + system of equations then :meth:`inv_den` is more efficient. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(2), ZZ(-1), ZZ(0)], + ... [ZZ(-1), ZZ(2), ZZ(-1)], + ... [ZZ(0), ZZ(0), ZZ(2)]], (3, 3), ZZ) + >>> Ainv, den = A.inv_den() + >>> den + 6 + >>> Ainv + DomainMatrix([[4, 2, 1], [2, 4, 2], [0, 0, 3]], (3, 3), ZZ) + >>> A * Ainv == den * A.eye(A.shape, A.domain).to_dense() + True + + Parameters + ========== + + method : str, optional + The method to use to compute the inverse. Can be one of ``None``, + ``'rref'`` or ``'charpoly'``. If ``None`` then the method is + chosen automatically (see :meth:`solve_den` for details). + + See Also + ======== + + inv + det + adj_det + solve_den + """ + I = self.eye(self.shape, self.domain) + return self.solve_den(I, method=method) + + def solve_den(self, b, method=None): + """ + Solve matrix equation $Ax = b$ without fractions in the ground domain. + + Examples + ======== + + Solve a matrix equation over the integers: + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ) + >>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ) + >>> xnum, xden = A.solve_den(b) + >>> xden + -2 + >>> xnum + DomainMatrix([[8], [-9]], (2, 1), ZZ) + >>> A * xnum == xden * b + True + + Solve a matrix equation over a polynomial ring: + + >>> from sympy import ZZ + >>> from sympy.abc import x, y, z, a, b + >>> R = ZZ[x, y, z, a, b] + >>> M = DM([[x*y, x*z], [y*z, x*z]], R) + >>> b = DM([[a], [b]], R) + >>> M.to_Matrix() + Matrix([ + [x*y, x*z], + [y*z, x*z]]) + >>> b.to_Matrix() + Matrix([ + [a], + [b]]) + >>> xnum, xden = M.solve_den(b) + >>> xden + x**2*y*z - x*y*z**2 + >>> xnum.to_Matrix() + Matrix([ + [ a*x*z - b*x*z], + [-a*y*z + b*x*y]]) + >>> M * xnum == xden * b + True + + The solution can be expressed over a fraction field which will cancel + gcds between the denominator and the elements of the numerator: + + >>> xsol = xnum.to_field() / xden + >>> xsol.to_Matrix() + Matrix([ + [ (a - b)/(x*y - y*z)], + [(-a*z + b*x)/(x**2*z - x*z**2)]]) + >>> (M * xsol).to_Matrix() == b.to_Matrix() + True + + When solving a large system of equations this cancellation step might + be a lot slower than :func:`solve_den` itself. The solution can also be + expressed as a ``Matrix`` without attempting any polynomial + cancellation between the numerator and denominator giving a less + simplified result more quickly: + + >>> xsol_uncancelled = xnum.to_Matrix() / xnum.domain.to_sympy(xden) + >>> xsol_uncancelled + Matrix([ + [ (a*x*z - b*x*z)/(x**2*y*z - x*y*z**2)], + [(-a*y*z + b*x*y)/(x**2*y*z - x*y*z**2)]]) + >>> from sympy import cancel + >>> cancel(xsol_uncancelled) == xsol.to_Matrix() + True + + Parameters + ========== + + self : :class:`DomainMatrix` + The ``m x n`` matrix $A$ in the equation $Ax = b$. Underdetermined + systems are not supported so ``m >= n``: $A$ should be square or + have more rows than columns. + b : :class:`DomainMatrix` + The ``n x m`` matrix $b$ for the rhs. + cp : list of :class:`~.DomainElement`, optional + The characteristic polynomial of the matrix $A$. If not given, it + will be computed using :meth:`charpoly`. + method: str, optional + The method to use for solving the system. Can be one of ``None``, + ``'charpoly'`` or ``'rref'``. If ``None`` (the default) then the + method will be chosen automatically. + + The ``charpoly`` method uses :meth:`solve_den_charpoly` and can + only be used if the matrix is square. This method is division free + and can be used with any domain. + + The ``rref`` method is fraction free but requires exact division + in the ground domain (``exquo``). This is also suitable for most + domains. This method can be used with overdetermined systems (more + equations than unknowns) but not underdetermined systems as a + unique solution is sought. + + Returns + ======= + + (xnum, xden) : (DomainMatrix, DomainElement) + The solution of the equation $Ax = b$ as a pair consisting of an + ``n x m`` matrix numerator ``xnum`` and a scalar denominator + ``xden``. + + The solution $x$ is given by ``x = xnum / xden``. The division free + invariant is ``A * xnum == xden * b``. If $A$ is square then the + denominator ``xden`` will be a divisor of the determinant $det(A)$. + + Raises + ====== + + DMNonInvertibleMatrixError + If the system $Ax = b$ does not have a unique solution. + + See Also + ======== + + solve_den_charpoly + solve_den_rref + inv_den + """ + m, n = self.shape + bm, bn = b.shape + + if m != bm: + raise DMShapeError("Matrix equation shape mismatch.") + + if method is None: + method = 'rref' + elif method == 'charpoly' and m != n: + raise DMNonSquareMatrixError("method='charpoly' requires a square matrix.") + + if method == 'charpoly': + xnum, xden = self.solve_den_charpoly(b) + elif method == 'rref': + xnum, xden = self.solve_den_rref(b) + else: + raise DMBadInputError("method should be 'rref' or 'charpoly'") + + return xnum, xden + + def solve_den_rref(self, b): + """ + Solve matrix equation $Ax = b$ using fraction-free RREF + + Solves the matrix equation $Ax = b$ for $x$ and returns the solution + as a numerator/denominator pair. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ) + >>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ) + >>> xnum, xden = A.solve_den_rref(b) + >>> xden + -2 + >>> xnum + DomainMatrix([[8], [-9]], (2, 1), ZZ) + >>> A * xnum == xden * b + True + + See Also + ======== + + solve_den + solve_den_charpoly + """ + A = self + m, n = A.shape + bm, bn = b.shape + + if m != bm: + raise DMShapeError("Matrix equation shape mismatch.") + + if m < n: + raise DMShapeError("Underdetermined matrix equation.") + + Aaug = A.hstack(b) + Aaug_rref, denom, pivots = Aaug.rref_den() + + # XXX: We check here if there are pivots after the last column. If + # there were than it possibly means that rref_den performed some + # unnecessary elimination. It would be better if rref methods had a + # parameter indicating how many columns should be used for elimination. + if len(pivots) != n or pivots and pivots[-1] >= n: + raise DMNonInvertibleMatrixError("Non-unique solution.") + + xnum = Aaug_rref[:n, n:] + xden = denom + + return xnum, xden + + def solve_den_charpoly(self, b, cp=None, check=True): + """ + Solve matrix equation $Ax = b$ using the characteristic polynomial. + + This method solves the square matrix equation $Ax = b$ for $x$ using + the characteristic polynomial without any division or fractions in the + ground domain. + + Examples + ======== + + Solve a matrix equation over the integers: + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DM + >>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ) + >>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ) + >>> xnum, detA = A.solve_den_charpoly(b) + >>> detA + -2 + >>> xnum + DomainMatrix([[8], [-9]], (2, 1), ZZ) + >>> A * xnum == detA * b + True + + Parameters + ========== + + self : DomainMatrix + The ``n x n`` matrix `A` in the equation `Ax = b`. Must be square + and invertible. + b : DomainMatrix + The ``n x m`` matrix `b` for the rhs. + cp : list, optional + The characteristic polynomial of the matrix `A` if known. If not + given, it will be computed using :meth:`charpoly`. + check : bool, optional + If ``True`` (the default) check that the determinant is not zero + and raise an error if it is. If ``False`` then if the determinant + is zero the return value will be equal to ``(A.adjugate()*b, 0)``. + + Returns + ======= + + (xnum, detA) : (DomainMatrix, DomainElement) + The solution of the equation `Ax = b` as a matrix numerator and + scalar denominator pair. The denominator is equal to the + determinant of `A` and the numerator is ``adj(A)*b``. + + The solution $x$ is given by ``x = xnum / detA``. The division free + invariant is ``A * xnum == detA * b``. + + If ``b`` is the identity matrix, then ``xnum`` is the adjugate matrix + and we have ``A * adj(A) == detA * I``. + + See Also + ======== + + solve_den + Main frontend for solving matrix equations with denominator. + solve_den_rref + Solve matrix equations using fraction-free RREF. + inv_den + Invert a matrix using the characteristic polynomial. + """ + A, b = self.unify(b) + m, n = self.shape + mb, nb = b.shape + + if m != n: + raise DMNonSquareMatrixError("Matrix must be square") + + if mb != m: + raise DMShapeError("Matrix and vector must have the same number of rows") + + f, detA = self.adj_poly_det(cp=cp) + + if check and not detA: + raise DMNonInvertibleMatrixError("Matrix is not invertible") + + # Compute adj(A)*b = det(A)*inv(A)*b using Horner's method without + # constructing inv(A) explicitly. + adjA_b = self.eval_poly_mul(f, b) + + return (adjA_b, detA) + + def adj_poly_det(self, cp=None): + """ + Return the polynomial $p$ such that $p(A) = adj(A)$ and also the + determinant of $A$. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DM + >>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ) + >>> p, detA = A.adj_poly_det() + >>> p + [-1, 5] + >>> p_A = A.eval_poly(p) + >>> p_A + DomainMatrix([[4, -2], [-3, 1]], (2, 2), QQ) + >>> p[0]*A**1 + p[1]*A**0 == p_A + True + >>> p_A == A.adjugate() + True + >>> A * A.adjugate() == detA * A.eye(A.shape, A.domain).to_dense() + True + + See Also + ======== + + adjugate + eval_poly + adj_det + """ + + # Cayley-Hamilton says that a matrix satisfies its own minimal + # polynomial + # + # p[0]*A^n + p[1]*A^(n-1) + ... + p[n]*I = 0 + # + # with p[0]=1 and p[n]=(-1)^n*det(A) or + # + # det(A)*I = -(-1)^n*(p[0]*A^(n-1) + p[1]*A^(n-2) + ... + p[n-1]*A). + # + # Define a new polynomial f with f[i] = -(-1)^n*p[i] for i=0..n-1. Then + # + # det(A)*I = f[0]*A^n + f[1]*A^(n-1) + ... + f[n-1]*A. + # + # Multiplying on the right by inv(A) gives + # + # det(A)*inv(A) = f[0]*A^(n-1) + f[1]*A^(n-2) + ... + f[n-1]. + # + # So adj(A) = det(A)*inv(A) = f(A) + + A = self + m, n = self.shape + + if m != n: + raise DMNonSquareMatrixError("Matrix must be square") + + if cp is None: + cp = A.charpoly() + + if len(cp) % 2: + # n is even + detA = cp[-1] + f = [-cpi for cpi in cp[:-1]] + else: + # n is odd + detA = -cp[-1] + f = cp[:-1] + + return f, detA + + def eval_poly(self, p): + """ + Evaluate polynomial function of a matrix $p(A)$. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DM + >>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ) + >>> p = [QQ(1), QQ(2), QQ(3)] + >>> p_A = A.eval_poly(p) + >>> p_A + DomainMatrix([[12, 14], [21, 33]], (2, 2), QQ) + >>> p_A == p[0]*A**2 + p[1]*A + p[2]*A**0 + True + + See Also + ======== + + eval_poly_mul + """ + A = self + m, n = A.shape + + if m != n: + raise DMNonSquareMatrixError("Matrix must be square") + + if not p: + return self.zeros(self.shape, self.domain) + elif len(p) == 1: + return p[0] * self.eye(self.shape, self.domain) + + # Evaluate p(A) using Horner's method: + # XXX: Use Paterson-Stockmeyer method? + I = A.eye(A.shape, A.domain) + p_A = p[0] * I + for pi in p[1:]: + p_A = A*p_A + pi*I + + return p_A + + def eval_poly_mul(self, p, B): + r""" + Evaluate polynomial matrix product $p(A) \times B$. + + Evaluate the polynomial matrix product $p(A) \times B$ using Horner's + method without creating the matrix $p(A)$ explicitly. If $B$ is a + column matrix then this method will only use matrix-vector multiplies + and no matrix-matrix multiplies are needed. + + If $B$ is square or wide or if $A$ can be represented in a simpler + domain than $B$ then it might be faster to evaluate $p(A)$ explicitly + (see :func:`eval_poly`) and then multiply with $B$. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DM + >>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ) + >>> b = DM([[QQ(5)], [QQ(6)]], QQ) + >>> p = [QQ(1), QQ(2), QQ(3)] + >>> p_A_b = A.eval_poly_mul(p, b) + >>> p_A_b + DomainMatrix([[144], [303]], (2, 1), QQ) + >>> p_A_b == p[0]*A**2*b + p[1]*A*b + p[2]*b + True + >>> A.eval_poly_mul(p, b) == A.eval_poly(p)*b + True + + See Also + ======== + + eval_poly + solve_den_charpoly + """ + A = self + m, n = A.shape + mb, nb = B.shape + + if m != n: + raise DMNonSquareMatrixError("Matrix must be square") + + if mb != n: + raise DMShapeError("Matrices are not aligned") + + if A.domain != B.domain: + raise DMDomainError("Matrices must have the same domain") + + # Given a polynomial p(x) = p[0]*x^n + p[1]*x^(n-1) + ... + p[n-1] + # and matrices A and B we want to find + # + # p(A)*B = p[0]*A^n*B + p[1]*A^(n-1)*B + ... + p[n-1]*B + # + # Factoring out A term by term we get + # + # p(A)*B = A*(...A*(A*(A*(p[0]*B) + p[1]*B) + p[2]*B) + ...) + p[n-1]*B + # + # where each pair of brackets represents one iteration of the loop + # below starting from the innermost p[0]*B. If B is a column matrix + # then products like A*(...) are matrix-vector multiplies and products + # like p[i]*B are scalar-vector multiplies so there are no + # matrix-matrix multiplies. + + if not p: + return B.zeros(B.shape, B.domain, fmt=B.rep.fmt) + + p_A_B = p[0]*B + + for p_i in p[1:]: + p_A_B = A*p_A_B + p_i*B + + return p_A_B + + def lu(self): + r""" + Returns Lower and Upper decomposition of the DomainMatrix + + Returns + ======= + + (L, U, exchange) + L, U are Lower and Upper decomposition of the DomainMatrix, + exchange is the list of indices of rows exchanged in the + decomposition. + + Raises + ====== + + ValueError + If the domain of DomainMatrix not a Field + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [QQ(1), QQ(-1)], + ... [QQ(2), QQ(-2)]], (2, 2), QQ) + >>> L, U, exchange = A.lu() + >>> L + DomainMatrix([[1, 0], [2, 1]], (2, 2), QQ) + >>> U + DomainMatrix([[1, -1], [0, 0]], (2, 2), QQ) + >>> exchange + [] + + See Also + ======== + + lu_solve + + """ + if not self.domain.is_Field: + raise DMNotAField('Not a field') + L, U, swaps = self.rep.lu() + return self.from_rep(L), self.from_rep(U), swaps + + def qr(self): + r""" + QR decomposition of the DomainMatrix. + + Explanation + =========== + + The QR decomposition expresses a matrix as the product of an orthogonal + matrix (Q) and an upper triangular matrix (R). In this implementation, + Q is not orthonormal: its columns are orthogonal but not normalized to + unit vectors. This avoids unnecessary divisions and is particularly + suited for exact arithmetic domains. + + Note + ==== + + This implementation is valid only for matrices over real domains. For + matrices over complex domains, a proper QR decomposition would require + handling conjugation to ensure orthogonality. + + Returns + ======= + + (Q, R) + Q is the orthogonal matrix, and R is the upper triangular matrix + resulting from the QR decomposition of the DomainMatrix. + + Raises + ====== + + DMDomainError + If the domain of the DomainMatrix is not a field (e.g., QQ). + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([[1, 2], [3, 4], [5, 6]], (3, 2), QQ) + >>> Q, R = A.qr() + >>> Q + DomainMatrix([[1, 26/35], [3, 8/35], [5, -2/7]], (3, 2), QQ) + >>> R + DomainMatrix([[1, 44/35], [0, 1]], (2, 2), QQ) + >>> Q * R == A + True + >>> (Q.transpose() * Q).is_diagonal + True + >>> R.is_upper + True + + See Also + ======== + + lu + + """ + ddm_q, ddm_r = self.rep.qr() + Q = self.from_rep(ddm_q) + R = self.from_rep(ddm_r) + return Q, R + + def lu_solve(self, rhs): + r""" + Solver for DomainMatrix x in the A*x = B + + Parameters + ========== + + rhs : DomainMatrix B + + Returns + ======= + + DomainMatrix + x in A*x = B + + Raises + ====== + + DMShapeError + If the DomainMatrix A and rhs have different number of rows + + ValueError + If the domain of DomainMatrix A not a Field + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [QQ(1), QQ(2)], + ... [QQ(3), QQ(4)]], (2, 2), QQ) + >>> B = DomainMatrix([ + ... [QQ(1), QQ(1)], + ... [QQ(0), QQ(1)]], (2, 2), QQ) + + >>> A.lu_solve(B) + DomainMatrix([[-2, -1], [3/2, 1]], (2, 2), QQ) + + See Also + ======== + + lu + + """ + if self.shape[0] != rhs.shape[0]: + raise DMShapeError("Shape") + if not self.domain.is_Field: + raise DMNotAField('Not a field') + sol = self.rep.lu_solve(rhs.rep) + return self.from_rep(sol) + + def fflu(self): + """ + Fraction-free LU decomposition of DomainMatrix. + + Explanation + =========== + + This method computes the PLDU decomposition + using Gauss-Bareiss elimination in a fraction-free manner, + it ensures that all intermediate results remain in + the domain of the input matrix. Unlike standard + LU decomposition, which introduces division, this approach + avoids fractions, making it particularly suitable + for exact arithmetic over integers or polynomials. + + This method satisfies the invariant: + + P * A = L * inv(D) * U + + Returns + ======= + + (P, L, D, U) + - P (Permutation matrix) + - L (Lower triangular matrix) + - D (Diagonal matrix) + - U (Upper triangular matrix) + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ) + >>> P, L, D, U = A.fflu() + >>> P + DomainMatrix([[1, 0], [0, 1]], (2, 2), ZZ) + >>> L + DomainMatrix([[1, 0], [3, -2]], (2, 2), ZZ) + >>> D + DomainMatrix([[1, 0], [0, -2]], (2, 2), ZZ) + >>> U + DomainMatrix([[1, 2], [0, -2]], (2, 2), ZZ) + >>> L.is_lower and U.is_upper and D.is_diagonal + True + >>> L * D.to_field().inv() * U == P * A.to_field() + True + >>> I, d = D.inv_den() + >>> L * I * U == d * P * A + True + + See Also + ======== + + sympy.polys.matrices.ddm.DDM.fflu + + References + ========== + + .. [1] Nakos, G. C., Turner, P. R., & Williams, R. M. (1997). Fraction-free + algorithms for linear and polynomial equations. ACM SIGSAM Bulletin, + 31(3), 11-19. https://doi.org/10.1145/271130.271133 + .. [2] Middeke, J.; Jeffrey, D.J.; Koutschan, C. (2020), "Common Factors + in Fraction-Free Matrix Decompositions", Mathematics in Computer Science, + 15 (4): 589–608, arXiv:2005.12380, doi:10.1007/s11786-020-00495-9 + .. [3] https://en.wikipedia.org/wiki/Bareiss_algorithm + """ + from_rep = self.from_rep + P, L, D, U = self.rep.fflu() + return from_rep(P), from_rep(L), from_rep(D), from_rep(U) + + def _solve(A, b): + # XXX: Not sure about this method or its signature. It is just created + # because it is needed by the holonomic module. + if A.shape[0] != b.shape[0]: + raise DMShapeError("Shape") + if A.domain != b.domain or not A.domain.is_Field: + raise DMNotAField('Not a field') + Aaug = A.hstack(b) + Arref, pivots = Aaug.rref() + particular = Arref.from_rep(Arref.rep.particular()) + nullspace_rep, nonpivots = Arref[:,:-1].rep.nullspace() + nullspace = Arref.from_rep(nullspace_rep) + return particular, nullspace + + def charpoly(self): + r""" + Characteristic polynomial of a square matrix. + + Computes the characteristic polynomial in a fully expanded form using + division free arithmetic. If a factorization of the characteristic + polynomial is needed then it is more efficient to call + :meth:`charpoly_factor_list` than calling :meth:`charpoly` and then + factorizing the result. + + Returns + ======= + + list: list of DomainElement + coefficients of the characteristic polynomial + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + >>> A.charpoly() + [1, -5, -2] + + See Also + ======== + + charpoly_factor_list + Compute the factorisation of the characteristic polynomial. + charpoly_factor_blocks + A partial factorisation of the characteristic polynomial that can + be computed more efficiently than either the full factorisation or + the fully expanded polynomial. + """ + M = self + K = M.domain + + factors = M.charpoly_factor_blocks() + + cp = [K.one] + + for f, mult in factors: + for _ in range(mult): + cp = dup_mul(cp, f, K) + + return cp + + def charpoly_factor_list(self): + """ + Full factorization of the characteristic polynomial. + + Examples + ======== + + >>> from sympy.polys.matrices import DM + >>> from sympy import ZZ + >>> M = DM([[6, -1, 0, 0], + ... [9, 12, 0, 0], + ... [0, 0, 1, 2], + ... [0, 0, 5, 6]], ZZ) + + Compute the factorization of the characteristic polynomial: + + >>> M.charpoly_factor_list() + [([1, -9], 2), ([1, -7, -4], 1)] + + Use :meth:`charpoly` to get the unfactorized characteristic polynomial: + + >>> M.charpoly() + [1, -25, 203, -495, -324] + + The same calculations with ``Matrix``: + + >>> M.to_Matrix().charpoly().as_expr() + lambda**4 - 25*lambda**3 + 203*lambda**2 - 495*lambda - 324 + >>> M.to_Matrix().charpoly().as_expr().factor() + (lambda - 9)**2*(lambda**2 - 7*lambda - 4) + + Returns + ======= + + list: list of pairs (factor, multiplicity) + A full factorization of the characteristic polynomial. + + See Also + ======== + + charpoly + Expanded form of the characteristic polynomial. + charpoly_factor_blocks + A partial factorisation of the characteristic polynomial that can + be computed more efficiently. + """ + M = self + K = M.domain + + # It is more efficient to start from the partial factorization provided + # for free by M.charpoly_factor_blocks than the expanded M.charpoly. + factors = M.charpoly_factor_blocks() + + factors_irreducible = [] + + for factor_i, mult_i in factors: + + _, factors_list = dup_factor_list(factor_i, K) + + for factor_j, mult_j in factors_list: + factors_irreducible.append((factor_j, mult_i * mult_j)) + + return _collect_factors(factors_irreducible) + + def charpoly_factor_blocks(self): + """ + Partial factorisation of the characteristic polynomial. + + This factorisation arises from a block structure of the matrix (if any) + and so the factors are not guaranteed to be irreducible. The + :meth:`charpoly_factor_blocks` method is the most efficient way to get + a representation of the characteristic polynomial but the result is + neither fully expanded nor fully factored. + + Examples + ======== + + >>> from sympy.polys.matrices import DM + >>> from sympy import ZZ + >>> M = DM([[6, -1, 0, 0], + ... [9, 12, 0, 0], + ... [0, 0, 1, 2], + ... [0, 0, 5, 6]], ZZ) + + This computes a partial factorization using only the block structure of + the matrix to reveal factors: + + >>> M.charpoly_factor_blocks() + [([1, -18, 81], 1), ([1, -7, -4], 1)] + + These factors correspond to the two diagonal blocks in the matrix: + + >>> DM([[6, -1], [9, 12]], ZZ).charpoly() + [1, -18, 81] + >>> DM([[1, 2], [5, 6]], ZZ).charpoly() + [1, -7, -4] + + Use :meth:`charpoly_factor_list` to get a complete factorization into + irreducibles: + + >>> M.charpoly_factor_list() + [([1, -9], 2), ([1, -7, -4], 1)] + + Use :meth:`charpoly` to get the expanded characteristic polynomial: + + >>> M.charpoly() + [1, -25, 203, -495, -324] + + Returns + ======= + + list: list of pairs (factor, multiplicity) + A partial factorization of the characteristic polynomial. + + See Also + ======== + + charpoly + Compute the fully expanded characteristic polynomial. + charpoly_factor_list + Compute a full factorization of the characteristic polynomial. + """ + M = self + + if not M.is_square: + raise DMNonSquareMatrixError("not square") + + # scc returns indices that permute the matrix into block triangular + # form and can extract the diagonal blocks. M.charpoly() is equal to + # the product of the diagonal block charpolys. + components = M.scc() + + block_factors = [] + + for indices in components: + block = M.extract(indices, indices) + block_factors.append((block.charpoly_base(), 1)) + + return _collect_factors(block_factors) + + def charpoly_base(self): + """ + Base case for :meth:`charpoly_factor_blocks` after block decomposition. + + This method is used internally by :meth:`charpoly_factor_blocks` as the + base case for computing the characteristic polynomial of a block. It is + more efficient to call :meth:`charpoly_factor_blocks`, :meth:`charpoly` + or :meth:`charpoly_factor_list` rather than call this method directly. + + This will use either the dense or the sparse implementation depending + on the sparsity of the matrix and will clear denominators if possible + before calling :meth:`charpoly_berk` to compute the characteristic + polynomial using the Berkowitz algorithm. + + See Also + ======== + + charpoly + charpoly_factor_list + charpoly_factor_blocks + charpoly_berk + """ + M = self + K = M.domain + + # It seems that the sparse implementation is always faster for random + # matrices with fewer than 50% non-zero entries. This does not seem to + # depend on domain, size, bit count etc. + density = self.nnz() / self.shape[0]**2 + if density < 0.5: + M = M.to_sparse() + else: + M = M.to_dense() + + # Clearing denominators is always more efficient if it can be done. + # Doing it here after block decomposition is good because each block + # might have a smaller denominator. However it might be better for + # charpoly and charpoly_factor_list to restore the denominators only at + # the very end so that they can call e.g. dup_factor_list before + # restoring the denominators. The methods would need to be changed to + # return (poly, denom) pairs to make that work though. + clear_denoms = K.is_Field and K.has_assoc_Ring + + if clear_denoms: + clear_denoms = True + d, M = M.clear_denoms(convert=True) + d = d.element + K_f = K + K_r = M.domain + + # Berkowitz algorithm over K_r. + cp = M.charpoly_berk() + + if clear_denoms: + # Restore the denominator in the charpoly over K_f. + # + # If M = N/d then p_M(x) = p_N(x*d)/d^n. + cp = dup_convert(cp, K_r, K_f) + p = [K_f.one, K_f.zero] + q = [K_f.one/d] + cp = dup_transform(cp, p, q, K_f) + + return cp + + def charpoly_berk(self): + """Compute the characteristic polynomial using the Berkowitz algorithm. + + This method directly calls the underlying implementation of the + Berkowitz algorithm (:meth:`sympy.polys.matrices.dense.ddm_berk` or + :meth:`sympy.polys.matrices.sdm.sdm_berk`). + + This is used by :meth:`charpoly` and other methods as the base case for + for computing the characteristic polynomial. However those methods will + apply other optimizations such as block decomposition, clearing + denominators and converting between dense and sparse representations + before calling this method. It is more efficient to call those methods + instead of this one but this method is provided for direct access to + the Berkowitz algorithm. + + Examples + ======== + + >>> from sympy.polys.matrices import DM + >>> from sympy import QQ + >>> M = DM([[6, -1, 0, 0], + ... [9, 12, 0, 0], + ... [0, 0, 1, 2], + ... [0, 0, 5, 6]], QQ) + >>> M.charpoly_berk() + [1, -25, 203, -495, -324] + + See Also + ======== + + charpoly + charpoly_base + charpoly_factor_list + charpoly_factor_blocks + sympy.polys.matrices.dense.ddm_berk + sympy.polys.matrices.sdm.sdm_berk + """ + return self.rep.charpoly() + + @classmethod + def eye(cls, shape, domain): + r""" + Return identity matrix of size n or shape (m, n). + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> DomainMatrix.eye(3, QQ) + DomainMatrix({0: {0: 1}, 1: {1: 1}, 2: {2: 1}}, (3, 3), QQ) + + """ + if isinstance(shape, int): + shape = (shape, shape) + return cls.from_rep(SDM.eye(shape, domain)) + + @classmethod + def diag(cls, diagonal, domain, shape=None): + r""" + Return diagonal matrix with entries from ``diagonal``. + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import ZZ + >>> DomainMatrix.diag([ZZ(5), ZZ(6)], ZZ) + DomainMatrix({0: {0: 5}, 1: {1: 6}}, (2, 2), ZZ) + + """ + if shape is None: + N = len(diagonal) + shape = (N, N) + return cls.from_rep(SDM.diag(diagonal, domain, shape)) + + @classmethod + def zeros(cls, shape, domain, *, fmt='sparse'): + """Returns a zero DomainMatrix of size shape, belonging to the specified domain + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> DomainMatrix.zeros((2, 3), QQ) + DomainMatrix({}, (2, 3), QQ) + + """ + return cls.from_rep(SDM.zeros(shape, domain)) + + @classmethod + def ones(cls, shape, domain): + """Returns a DomainMatrix of 1s, of size shape, belonging to the specified domain + + Examples + ======== + + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy import QQ + >>> DomainMatrix.ones((2,3), QQ) + DomainMatrix([[1, 1, 1], [1, 1, 1]], (2, 3), QQ) + + """ + return cls.from_rep(DDM.ones(shape, domain).to_dfm_or_ddm()) + + def __eq__(A, B): + r""" + Checks for two DomainMatrix matrices to be equal or not + + Parameters + ========== + + A, B: DomainMatrix + to check equality + + Returns + ======= + + Boolean + True for equal, else False + + Raises + ====== + + NotImplementedError + If B is not a DomainMatrix + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> A = DomainMatrix([ + ... [ZZ(1), ZZ(2)], + ... [ZZ(3), ZZ(4)]], (2, 2), ZZ) + >>> B = DomainMatrix([ + ... [ZZ(1), ZZ(1)], + ... [ZZ(0), ZZ(1)]], (2, 2), ZZ) + >>> A.__eq__(A) + True + >>> A.__eq__(B) + False + + """ + if not isinstance(A, type(B)): + return NotImplemented + return A.domain == B.domain and A.rep == B.rep + + def unify_eq(A, B): + if A.shape != B.shape: + return False + if A.domain != B.domain: + A, B = A.unify(B) + return A == B + + def lll(A, delta=QQ(3, 4)): + """ + Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm. + See [1]_ and [2]_. + + Parameters + ========== + + delta : QQ, optional + The Lovász parameter. Must be in the interval (0.25, 1), with larger + values producing a more reduced basis. The default is 0.75 for + historical reasons. + + Returns + ======= + + The reduced basis as a DomainMatrix over ZZ. + + Throws + ====== + + DMValueError: if delta is not in the range (0.25, 1) + DMShapeError: if the matrix is not of shape (m, n) with m <= n + DMDomainError: if the matrix domain is not ZZ + DMRankError: if the matrix contains linearly dependent rows + + Examples + ======== + + >>> from sympy.polys.domains import ZZ, QQ + >>> from sympy.polys.matrices import DM + >>> x = DM([[1, 0, 0, 0, -20160], + ... [0, 1, 0, 0, 33768], + ... [0, 0, 1, 0, 39578], + ... [0, 0, 0, 1, 47757]], ZZ) + >>> y = DM([[10, -3, -2, 8, -4], + ... [3, -9, 8, 1, -11], + ... [-3, 13, -9, -3, -9], + ... [-12, -7, -11, 9, -1]], ZZ) + >>> assert x.lll(delta=QQ(5, 6)) == y + + Notes + ===== + + The implementation is derived from the Maple code given in Figures 4.3 + and 4.4 of [3]_ (pp.68-69). It uses the efficient method of only calculating + state updates as they are required. + + See also + ======== + + lll_transform + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_algorithm + .. [2] https://web.archive.org/web/20221029115428/https://web.cs.elte.hu/~lovasz/scans/lll.pdf + .. [3] Murray R. Bremner, "Lattice Basis Reduction: An Introduction to the LLL Algorithm and Its Applications" + + """ + return DomainMatrix.from_rep(A.rep.lll(delta=delta)) + + def lll_transform(A, delta=QQ(3, 4)): + """ + Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm + and returns the reduced basis and transformation matrix. + + Explanation + =========== + + Parameters, algorithm and basis are the same as for :meth:`lll` except that + the return value is a tuple `(B, T)` with `B` the reduced basis and + `T` a transformation matrix. The original basis `A` is transformed to + `B` with `T*A == B`. If only `B` is needed then :meth:`lll` should be + used as it is a little faster. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ, QQ + >>> from sympy.polys.matrices import DM + >>> X = DM([[1, 0, 0, 0, -20160], + ... [0, 1, 0, 0, 33768], + ... [0, 0, 1, 0, 39578], + ... [0, 0, 0, 1, 47757]], ZZ) + >>> B, T = X.lll_transform(delta=QQ(5, 6)) + >>> T * X == B + True + + See also + ======== + + lll + + """ + reduced, transform = A.rep.lll_transform(delta=delta) + return DomainMatrix.from_rep(reduced), DomainMatrix.from_rep(transform) + + +def _collect_factors(factors_list): + """ + Collect repeating factors and sort. + + >>> from sympy.polys.matrices.domainmatrix import _collect_factors + >>> _collect_factors([([1, 2], 2), ([1, 4], 3), ([1, 2], 5)]) + [([1, 4], 3), ([1, 2], 7)] + """ + factors = Counter() + for factor, exponent in factors_list: + factors[tuple(factor)] += exponent + + factors_list = [(list(f), e) for f, e in factors.items()] + + return _sort_factors(factors_list) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/domainscalar.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/domainscalar.py new file mode 100644 index 0000000000000000000000000000000000000000..df439a60a0ea0df5f6fac988c06da2a06a4fbac2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/domainscalar.py @@ -0,0 +1,122 @@ +""" + +Module for the DomainScalar class. + +A DomainScalar represents an element which is in a particular +Domain. The idea is that the DomainScalar class provides the +convenience routines for unifying elements with different domains. + +It assists in Scalar Multiplication and getitem for DomainMatrix. + +""" +from ..constructor import construct_domain + +from sympy.polys.domains import Domain, ZZ + + +class DomainScalar: + r""" + docstring + """ + + def __new__(cls, element, domain): + if not isinstance(domain, Domain): + raise TypeError("domain should be of type Domain") + if not domain.of_type(element): + raise TypeError("element %s should be in domain %s" % (element, domain)) + return cls.new(element, domain) + + @classmethod + def new(cls, element, domain): + obj = super().__new__(cls) + obj.element = element + obj.domain = domain + return obj + + def __repr__(self): + return repr(self.element) + + @classmethod + def from_sympy(cls, expr): + [domain, [element]] = construct_domain([expr]) + return cls.new(element, domain) + + def to_sympy(self): + return self.domain.to_sympy(self.element) + + def to_domain(self, domain): + element = domain.convert_from(self.element, self.domain) + return self.new(element, domain) + + def convert_to(self, domain): + return self.to_domain(domain) + + def unify(self, other): + domain = self.domain.unify(other.domain) + return self.to_domain(domain), other.to_domain(domain) + + def __bool__(self): + return bool(self.element) + + def __add__(self, other): + if not isinstance(other, DomainScalar): + return NotImplemented + self, other = self.unify(other) + return self.new(self.element + other.element, self.domain) + + def __sub__(self, other): + if not isinstance(other, DomainScalar): + return NotImplemented + self, other = self.unify(other) + return self.new(self.element - other.element, self.domain) + + def __mul__(self, other): + if not isinstance(other, DomainScalar): + if isinstance(other, int): + other = DomainScalar(ZZ(other), ZZ) + else: + return NotImplemented + + self, other = self.unify(other) + return self.new(self.element * other.element, self.domain) + + def __floordiv__(self, other): + if not isinstance(other, DomainScalar): + return NotImplemented + self, other = self.unify(other) + return self.new(self.domain.quo(self.element, other.element), self.domain) + + def __mod__(self, other): + if not isinstance(other, DomainScalar): + return NotImplemented + self, other = self.unify(other) + return self.new(self.domain.rem(self.element, other.element), self.domain) + + def __divmod__(self, other): + if not isinstance(other, DomainScalar): + return NotImplemented + self, other = self.unify(other) + q, r = self.domain.div(self.element, other.element) + return (self.new(q, self.domain), self.new(r, self.domain)) + + def __pow__(self, n): + if not isinstance(n, int): + return NotImplemented + return self.new(self.element**n, self.domain) + + def __pos__(self): + return self.new(+self.element, self.domain) + + def __neg__(self): + return self.new(-self.element, self.domain) + + def __eq__(self, other): + if not isinstance(other, DomainScalar): + return NotImplemented + return self.element == other.element and self.domain == other.domain + + def is_zero(self): + return self.element == self.domain.zero + + def is_one(self): + return self.element == self.domain.one diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/eigen.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/eigen.py new file mode 100644 index 0000000000000000000000000000000000000000..17d673c6ea09002e1cfd5357f301c447a7af4341 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/eigen.py @@ -0,0 +1,90 @@ +""" + +Routines for computing eigenvectors with DomainMatrix. + +""" +from sympy.core.symbol import Dummy + +from ..agca.extensions import FiniteExtension +from ..factortools import dup_factor_list +from ..polyroots import roots +from ..polytools import Poly +from ..rootoftools import CRootOf + +from .domainmatrix import DomainMatrix + + +def dom_eigenvects(A, l=Dummy('lambda')): + charpoly = A.charpoly() + rows, cols = A.shape + domain = A.domain + _, factors = dup_factor_list(charpoly, domain) + + rational_eigenvects = [] + algebraic_eigenvects = [] + for base, exp in factors: + if len(base) == 2: + field = domain + eigenval = -base[1] / base[0] + + EE_items = [ + [eigenval if i == j else field.zero for j in range(cols)] + for i in range(rows)] + EE = DomainMatrix(EE_items, (rows, cols), field) + + basis = (A - EE).nullspace(divide_last=True) + rational_eigenvects.append((field, eigenval, exp, basis)) + else: + minpoly = Poly.from_list(base, l, domain=domain) + field = FiniteExtension(minpoly) + eigenval = field(l) + + AA_items = [ + [Poly.from_list([item], l, domain=domain).rep for item in row] + for row in A.rep.to_ddm()] + AA_items = [[field(item) for item in row] for row in AA_items] + AA = DomainMatrix(AA_items, (rows, cols), field) + EE_items = [ + [eigenval if i == j else field.zero for j in range(cols)] + for i in range(rows)] + EE = DomainMatrix(EE_items, (rows, cols), field) + + basis = (AA - EE).nullspace(divide_last=True) + algebraic_eigenvects.append((field, minpoly, exp, basis)) + + return rational_eigenvects, algebraic_eigenvects + + +def dom_eigenvects_to_sympy( + rational_eigenvects, algebraic_eigenvects, + Matrix, **kwargs +): + result = [] + + for field, eigenvalue, multiplicity, eigenvects in rational_eigenvects: + eigenvects = eigenvects.rep.to_ddm() + eigenvalue = field.to_sympy(eigenvalue) + new_eigenvects = [ + Matrix([field.to_sympy(x) for x in vect]) + for vect in eigenvects] + result.append((eigenvalue, multiplicity, new_eigenvects)) + + for field, minpoly, multiplicity, eigenvects in algebraic_eigenvects: + eigenvects = eigenvects.rep.to_ddm() + l = minpoly.gens[0] + + eigenvects = [[field.to_sympy(x) for x in vect] for vect in eigenvects] + + degree = minpoly.degree() + minpoly = minpoly.as_expr() + eigenvals = roots(minpoly, l, **kwargs) + if len(eigenvals) != degree: + eigenvals = [CRootOf(minpoly, l, idx) for idx in range(degree)] + + for eigenvalue in eigenvals: + new_eigenvects = [ + Matrix([x.subs(l, eigenvalue) for x in vect]) + for vect in eigenvects] + result.append((eigenvalue, multiplicity, new_eigenvects)) + + return result diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/exceptions.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..b1e5a4195c66aceed2d5ac1994381d3dec6a64ba --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/exceptions.py @@ -0,0 +1,67 @@ +""" + +Module to define exceptions to be used in sympy.polys.matrices modules and +classes. + +Ideally all exceptions raised in these modules would be defined and documented +here and not e.g. imported from matrices. Also ideally generic exceptions like +ValueError/TypeError would not be raised anywhere. + +""" + + +class DMError(Exception): + """Base class for errors raised by DomainMatrix""" + pass + + +class DMBadInputError(DMError): + """list of lists is inconsistent with shape""" + pass + + +class DMDomainError(DMError): + """domains do not match""" + pass + + +class DMNotAField(DMDomainError): + """domain is not a field""" + pass + + +class DMFormatError(DMError): + """mixed dense/sparse not supported""" + pass + + +class DMNonInvertibleMatrixError(DMError): + """The matrix in not invertible""" + pass + + +class DMRankError(DMError): + """matrix does not have expected rank""" + pass + + +class DMShapeError(DMError): + """shapes are inconsistent""" + pass + + +class DMNonSquareMatrixError(DMShapeError): + """The matrix is not square""" + pass + + +class DMValueError(DMError): + """The value passed is invalid""" + pass + + +__all__ = [ + 'DMError', 'DMBadInputError', 'DMDomainError', 'DMFormatError', + 'DMRankError', 'DMShapeError', 'DMNotAField', + 'DMNonInvertibleMatrixError', 'DMNonSquareMatrixError', 'DMValueError' +] diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/linsolve.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/linsolve.py new file mode 100644 index 0000000000000000000000000000000000000000..af74058d859b744cf8fe1059ddb7c775fece79c7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/linsolve.py @@ -0,0 +1,230 @@ +# +# sympy.polys.matrices.linsolve module +# +# This module defines the _linsolve function which is the internal workhorse +# used by linsolve. This computes the solution of a system of linear equations +# using the SDM sparse matrix implementation in sympy.polys.matrices.sdm. This +# is a replacement for solve_lin_sys in sympy.polys.solvers which is +# inefficient for large sparse systems due to the use of a PolyRing with many +# generators: +# +# https://github.com/sympy/sympy/issues/20857 +# +# The implementation of _linsolve here handles: +# +# - Extracting the coefficients from the Expr/Eq input equations. +# - Constructing a domain and converting the coefficients to +# that domain. +# - Using the SDM.rref, SDM.nullspace etc methods to generate the full +# solution working with arithmetic only in the domain of the coefficients. +# +# The routines here are particularly designed to be efficient for large sparse +# systems of linear equations although as well as dense systems. It is +# possible that for some small dense systems solve_lin_sys which uses the +# dense matrix implementation DDM will be more efficient. With smaller systems +# though the bulk of the time is spent just preprocessing the inputs and the +# relative time spent in rref is too small to be noticeable. +# + +from collections import defaultdict + +from sympy.core.add import Add +from sympy.core.mul import Mul +from sympy.core.singleton import S + +from sympy.polys.constructor import construct_domain +from sympy.polys.solvers import PolyNonlinearError + +from .sdm import ( + SDM, + sdm_irref, + sdm_particular_from_rref, + sdm_nullspace_from_rref +) + +from sympy.utilities.misc import filldedent + + +def _linsolve(eqs, syms): + + """Solve a linear system of equations. + + Examples + ======== + + Solve a linear system with a unique solution: + + >>> from sympy import symbols, Eq + >>> from sympy.polys.matrices.linsolve import _linsolve + >>> x, y = symbols('x, y') + >>> eqs = [Eq(x + y, 1), Eq(x - y, 2)] + >>> _linsolve(eqs, [x, y]) + {x: 3/2, y: -1/2} + + In the case of underdetermined systems the solution will be expressed in + terms of the unknown symbols that are unconstrained: + + >>> _linsolve([Eq(x + y, 0)], [x, y]) + {x: -y, y: y} + + """ + # Number of unknowns (columns in the non-augmented matrix) + nsyms = len(syms) + + # Convert to sparse augmented matrix (len(eqs) x (nsyms+1)) + eqsdict, const = _linear_eq_to_dict(eqs, syms) + Aaug = sympy_dict_to_dm(eqsdict, const, syms) + K = Aaug.domain + + # sdm_irref has issues with float matrices. This uses the ddm_rref() + # function. When sdm_rref() can handle float matrices reasonably this + # should be removed... + if K.is_RealField or K.is_ComplexField: + Aaug = Aaug.to_ddm().rref()[0].to_sdm() + + # Compute reduced-row echelon form (RREF) + Arref, pivots, nzcols = sdm_irref(Aaug) + + # No solution: + if pivots and pivots[-1] == nsyms: + return None + + # Particular solution for non-homogeneous system: + P = sdm_particular_from_rref(Arref, nsyms+1, pivots) + + # Nullspace - general solution to homogeneous system + # Note: using nsyms not nsyms+1 to ignore last column + V, nonpivots = sdm_nullspace_from_rref(Arref, K.one, nsyms, pivots, nzcols) + + # Collect together terms from particular and nullspace: + sol = defaultdict(list) + for i, v in P.items(): + sol[syms[i]].append(K.to_sympy(v)) + for npi, Vi in zip(nonpivots, V): + sym = syms[npi] + for i, v in Vi.items(): + sol[syms[i]].append(sym * K.to_sympy(v)) + + # Use a single call to Add for each term: + sol = {s: Add(*terms) for s, terms in sol.items()} + + # Fill in the zeros: + zero = S.Zero + for s in set(syms) - set(sol): + sol[s] = zero + + # All done! + return sol + + +def sympy_dict_to_dm(eqs_coeffs, eqs_rhs, syms): + """Convert a system of dict equations to a sparse augmented matrix""" + elems = set(eqs_rhs).union(*(e.values() for e in eqs_coeffs)) + K, elems_K = construct_domain(elems, field=True, extension=True) + elem_map = dict(zip(elems, elems_K)) + neqs = len(eqs_coeffs) + nsyms = len(syms) + sym2index = dict(zip(syms, range(nsyms))) + eqsdict = [] + for eq, rhs in zip(eqs_coeffs, eqs_rhs): + eqdict = {sym2index[s]: elem_map[c] for s, c in eq.items()} + if rhs: + eqdict[nsyms] = -elem_map[rhs] + if eqdict: + eqsdict.append(eqdict) + sdm_aug = SDM(enumerate(eqsdict), (neqs, nsyms + 1), K) + return sdm_aug + + +def _linear_eq_to_dict(eqs, syms): + """Convert a system Expr/Eq equations into dict form, returning + the coefficient dictionaries and a list of syms-independent terms + from each expression in ``eqs```. + + Examples + ======== + + >>> from sympy.polys.matrices.linsolve import _linear_eq_to_dict + >>> from sympy.abc import x + >>> _linear_eq_to_dict([2*x + 3], {x}) + ([{x: 2}], [3]) + """ + coeffs = [] + ind = [] + symset = set(syms) + for e in eqs: + if e.is_Equality: + coeff, terms = _lin_eq2dict(e.lhs, symset) + cR, tR = _lin_eq2dict(e.rhs, symset) + # there were no nonlinear errors so now + # cancellation is allowed + coeff -= cR + for k, v in tR.items(): + if k in terms: + terms[k] -= v + else: + terms[k] = -v + # don't store coefficients of 0, however + terms = {k: v for k, v in terms.items() if v} + c, d = coeff, terms + else: + c, d = _lin_eq2dict(e, symset) + coeffs.append(d) + ind.append(c) + return coeffs, ind + + +def _lin_eq2dict(a, symset): + """return (c, d) where c is the sym-independent part of ``a`` and + ``d`` is an efficiently calculated dictionary mapping symbols to + their coefficients. A PolyNonlinearError is raised if non-linearity + is detected. + + The values in the dictionary will be non-zero. + + Examples + ======== + + >>> from sympy.polys.matrices.linsolve import _lin_eq2dict + >>> from sympy.abc import x, y + >>> _lin_eq2dict(x + 2*y + 3, {x, y}) + (3, {x: 1, y: 2}) + """ + if a in symset: + return S.Zero, {a: S.One} + elif a.is_Add: + terms_list = defaultdict(list) + coeff_list = [] + for ai in a.args: + ci, ti = _lin_eq2dict(ai, symset) + coeff_list.append(ci) + for mij, cij in ti.items(): + terms_list[mij].append(cij) + coeff = Add(*coeff_list) + terms = {sym: Add(*coeffs) for sym, coeffs in terms_list.items()} + return coeff, terms + elif a.is_Mul: + terms = terms_coeff = None + coeff_list = [] + for ai in a.args: + ci, ti = _lin_eq2dict(ai, symset) + if not ti: + coeff_list.append(ci) + elif terms is None: + terms = ti + terms_coeff = ci + else: + # since ti is not null and we already have + # a term, this is a cross term + raise PolyNonlinearError(filldedent(''' + nonlinear cross-term: %s''' % a)) + coeff = Mul._from_args(coeff_list) + if terms is None: + return coeff, {} + else: + terms = {sym: coeff * c for sym, c in terms.items()} + return coeff * terms_coeff, terms + elif not a.has_xfree(symset): + return a, {} + else: + raise PolyNonlinearError('nonlinear term: %s' % a) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/lll.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/lll.py new file mode 100644 index 0000000000000000000000000000000000000000..f33f91d92c5e20f89f302991e494a6a5b9fa4b2e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/lll.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from math import floor as mfloor + +from sympy.polys.domains import ZZ, QQ +from sympy.polys.matrices.exceptions import DMRankError, DMShapeError, DMValueError, DMDomainError + + +def _ddm_lll(x, delta=QQ(3, 4), return_transform=False): + if QQ(1, 4) >= delta or delta >= QQ(1, 1): + raise DMValueError("delta must lie in range (0.25, 1)") + if x.shape[0] > x.shape[1]: + raise DMShapeError("input matrix must have shape (m, n) with m <= n") + if x.domain != ZZ: + raise DMDomainError("input matrix domain must be ZZ") + m = x.shape[0] + n = x.shape[1] + k = 1 + y = x.copy() + y_star = x.zeros((m, n), QQ) + mu = x.zeros((m, m), QQ) + g_star = [QQ(0, 1) for _ in range(m)] + half = QQ(1, 2) + T = x.eye(m, ZZ) if return_transform else None + linear_dependent_error = "input matrix contains linearly dependent rows" + + def closest_integer(x): + return ZZ(mfloor(x + half)) + + def lovasz_condition(k: int) -> bool: + return g_star[k] >= ((delta - mu[k][k - 1] ** 2) * g_star[k - 1]) + + def mu_small(k: int, j: int) -> bool: + return abs(mu[k][j]) <= half + + def dot_rows(x, y, rows: tuple[int, int]): + return sum(x[rows[0]][z] * y[rows[1]][z] for z in range(x.shape[1])) + + def reduce_row(T, mu, y, rows: tuple[int, int]): + r = closest_integer(mu[rows[0]][rows[1]]) + y[rows[0]] = [y[rows[0]][z] - r * y[rows[1]][z] for z in range(n)] + mu[rows[0]][:rows[1]] = [mu[rows[0]][z] - r * mu[rows[1]][z] for z in range(rows[1])] + mu[rows[0]][rows[1]] -= r + if return_transform: + T[rows[0]] = [T[rows[0]][z] - r * T[rows[1]][z] for z in range(m)] + + for i in range(m): + y_star[i] = [QQ.convert_from(z, ZZ) for z in y[i]] + for j in range(i): + row_dot = dot_rows(y, y_star, (i, j)) + try: + mu[i][j] = row_dot / g_star[j] + except ZeroDivisionError: + raise DMRankError(linear_dependent_error) + y_star[i] = [y_star[i][z] - mu[i][j] * y_star[j][z] for z in range(n)] + g_star[i] = dot_rows(y_star, y_star, (i, i)) + while k < m: + if not mu_small(k, k - 1): + reduce_row(T, mu, y, (k, k - 1)) + if lovasz_condition(k): + for l in range(k - 2, -1, -1): + if not mu_small(k, l): + reduce_row(T, mu, y, (k, l)) + k += 1 + else: + nu = mu[k][k - 1] + alpha = g_star[k] + nu ** 2 * g_star[k - 1] + try: + beta = g_star[k - 1] / alpha + except ZeroDivisionError: + raise DMRankError(linear_dependent_error) + mu[k][k - 1] = nu * beta + g_star[k] = g_star[k] * beta + g_star[k - 1] = alpha + y[k], y[k - 1] = y[k - 1], y[k] + mu[k][:k - 1], mu[k - 1][:k - 1] = mu[k - 1][:k - 1], mu[k][:k - 1] + for i in range(k + 1, m): + xi = mu[i][k] + mu[i][k] = mu[i][k - 1] - nu * xi + mu[i][k - 1] = mu[k][k - 1] * mu[i][k] + xi + if return_transform: + T[k], T[k - 1] = T[k - 1], T[k] + k = max(k - 1, 1) + assert all(lovasz_condition(i) for i in range(1, m)) + assert all(mu_small(i, j) for i in range(m) for j in range(i)) + return y, T + + +def ddm_lll(x, delta=QQ(3, 4)): + return _ddm_lll(x, delta=delta, return_transform=False)[0] + + +def ddm_lll_transform(x, delta=QQ(3, 4)): + return _ddm_lll(x, delta=delta, return_transform=True) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/normalforms.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/normalforms.py new file mode 100644 index 0000000000000000000000000000000000000000..506a68b6946acbeb235eed7650246104da265b78 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/normalforms.py @@ -0,0 +1,540 @@ +'''Functions returning normal forms of matrices''' + +from collections import defaultdict + +from .domainmatrix import DomainMatrix +from .exceptions import DMDomainError, DMShapeError +from sympy.ntheory.modular import symmetric_residue +from sympy.polys.domains import QQ, ZZ + + +# TODO (future work): +# There are faster algorithms for Smith and Hermite normal forms, which +# we should implement. See e.g. the Kannan-Bachem algorithm: +# + + +def smith_normal_form(m): + ''' + Return the Smith Normal Form of a matrix `m` over the ring `domain`. + This will only work if the ring is a principal ideal domain. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.polys.matrices.normalforms import smith_normal_form + >>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)], + ... [ZZ(3), ZZ(9), ZZ(6)], + ... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ) + >>> print(smith_normal_form(m).to_Matrix()) + Matrix([[1, 0, 0], [0, 10, 0], [0, 0, 30]]) + + ''' + invs = invariant_factors(m) + smf = DomainMatrix.diag(invs, m.domain, m.shape) + return smf + + +def is_smith_normal_form(m): + ''' + Checks that the matrix is in Smith Normal Form + ''' + domain = m.domain + shape = m.shape + zero = domain.zero + m = m.to_list() + + for i in range(shape[0]): + for j in range(shape[1]): + if i == j: + continue + if not m[i][j] == zero: + return False + + upper = min(shape[0], shape[1]) + for i in range(1, upper): + if m[i-1][i-1] == zero: + if m[i][i] != zero: + return False + else: + r = domain.div(m[i][i], m[i-1][i-1])[1] + if r != zero: + return False + + return True + + +def add_columns(m, i, j, a, b, c, d): + # replace m[:, i] by a*m[:, i] + b*m[:, j] + # and m[:, j] by c*m[:, i] + d*m[:, j] + for k in range(len(m)): + e = m[k][i] + m[k][i] = a*e + b*m[k][j] + m[k][j] = c*e + d*m[k][j] + + +def invariant_factors(m): + ''' + Return the tuple of abelian invariants for a matrix `m` + (as in the Smith-Normal form) + + References + ========== + + [1] https://en.wikipedia.org/wiki/Smith_normal_form#Algorithm + [2] https://web.archive.org/web/20200331143852/https://sierra.nmsu.edu/morandi/notes/SmithNormalForm.pdf + + ''' + domain = m.domain + shape = m.shape + m = m.to_list() + return _smith_normal_decomp(m, domain, shape=shape, full=False) + + +def smith_normal_decomp(m): + ''' + Return the Smith-Normal form decomposition of matrix `m`. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.polys.matrices.normalforms import smith_normal_decomp + >>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)], + ... [ZZ(3), ZZ(9), ZZ(6)], + ... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ) + >>> a, s, t = smith_normal_decomp(m) + >>> assert a == s * m * t + ''' + domain = m.domain + rows, cols = shape = m.shape + m = m.to_list() + + invs, s, t = _smith_normal_decomp(m, domain, shape=shape, full=True) + smf = DomainMatrix.diag(invs, domain, shape).to_dense() + + s = DomainMatrix(s, domain=domain, shape=(rows, rows)) + t = DomainMatrix(t, domain=domain, shape=(cols, cols)) + return smf, s, t + + +def _smith_normal_decomp(m, domain, shape, full): + ''' + Return the tuple of abelian invariants for a matrix `m` + (as in the Smith-Normal form). If `full=True` then invertible matrices + ``s, t`` such that the product ``s, m, t`` is the Smith Normal Form + are also returned. + ''' + if not domain.is_PID: + msg = f"The matrix entries must be over a principal ideal domain, but got {domain}" + raise ValueError(msg) + + rows, cols = shape + zero = domain.zero + one = domain.one + + def eye(n): + return [[one if i == j else zero for i in range(n)] for j in range(n)] + + if 0 in shape: + if full: + return (), eye(rows), eye(cols) + else: + return () + + if full: + s = eye(rows) + t = eye(cols) + + def add_rows(m, i, j, a, b, c, d): + # replace m[i, :] by a*m[i, :] + b*m[j, :] + # and m[j, :] by c*m[i, :] + d*m[j, :] + for k in range(len(m[0])): + e = m[i][k] + m[i][k] = a*e + b*m[j][k] + m[j][k] = c*e + d*m[j][k] + + def clear_column(): + # make m[1:, 0] zero by row and column operations + pivot = m[0][0] + for j in range(1, rows): + if m[j][0] == zero: + continue + d, r = domain.div(m[j][0], pivot) + if r == zero: + add_rows(m, 0, j, 1, 0, -d, 1) + if full: + add_rows(s, 0, j, 1, 0, -d, 1) + else: + a, b, g = domain.gcdex(pivot, m[j][0]) + d_0 = domain.exquo(m[j][0], g) + d_j = domain.exquo(pivot, g) + add_rows(m, 0, j, a, b, d_0, -d_j) + if full: + add_rows(s, 0, j, a, b, d_0, -d_j) + pivot = g + + def clear_row(): + # make m[0, 1:] zero by row and column operations + pivot = m[0][0] + for j in range(1, cols): + if m[0][j] == zero: + continue + d, r = domain.div(m[0][j], pivot) + if r == zero: + add_columns(m, 0, j, 1, 0, -d, 1) + if full: + add_columns(t, 0, j, 1, 0, -d, 1) + else: + a, b, g = domain.gcdex(pivot, m[0][j]) + d_0 = domain.exquo(m[0][j], g) + d_j = domain.exquo(pivot, g) + add_columns(m, 0, j, a, b, d_0, -d_j) + if full: + add_columns(t, 0, j, a, b, d_0, -d_j) + pivot = g + + # permute the rows and columns until m[0,0] is non-zero if possible + ind = [i for i in range(rows) if m[i][0] != zero] + if ind and ind[0] != zero: + m[0], m[ind[0]] = m[ind[0]], m[0] + if full: + s[0], s[ind[0]] = s[ind[0]], s[0] + else: + ind = [j for j in range(cols) if m[0][j] != zero] + if ind and ind[0] != zero: + for row in m: + row[0], row[ind[0]] = row[ind[0]], row[0] + if full: + for row in t: + row[0], row[ind[0]] = row[ind[0]], row[0] + + # make the first row and column except m[0,0] zero + while (any(m[0][i] != zero for i in range(1,cols)) or + any(m[i][0] != zero for i in range(1,rows))): + clear_column() + clear_row() + + def to_domain_matrix(m): + return DomainMatrix(m, shape=(len(m), len(m[0])), domain=domain) + + if m[0][0] != 0: + c = domain.canonical_unit(m[0][0]) + if domain.is_Field: + c = 1 / m[0][0] + if c != domain.one: + m[0][0] *= c + if full: + s[0] = [elem * c for elem in s[0]] + + if 1 in shape: + invs = () + else: + lower_right = [r[1:] for r in m[1:]] + ret = _smith_normal_decomp(lower_right, domain, + shape=(rows - 1, cols - 1), full=full) + if full: + invs, s_small, t_small = ret + s2 = [[1] + [0]*(rows-1)] + [[0] + row for row in s_small] + t2 = [[1] + [0]*(cols-1)] + [[0] + row for row in t_small] + s, s2, t, t2 = list(map(to_domain_matrix, [s, s2, t, t2])) + s = s2 * s + t = t * t2 + s = s.to_list() + t = t.to_list() + else: + invs = ret + + if m[0][0]: + result = [m[0][0]] + result.extend(invs) + # in case m[0] doesn't divide the invariants of the rest of the matrix + for i in range(len(result)-1): + a, b = result[i], result[i+1] + if b and domain.div(b, a)[1] != zero: + if full: + x, y, d = domain.gcdex(a, b) + else: + d = domain.gcd(a, b) + + alpha = domain.div(a, d)[0] + if full: + beta = domain.div(b, d)[0] + add_rows(s, i, i + 1, 1, 0, x, 1) + add_columns(t, i, i + 1, 1, y, 0, 1) + add_rows(s, i, i + 1, 1, -alpha, 0, 1) + add_columns(t, i, i + 1, 1, 0, -beta, 1) + add_rows(s, i, i + 1, 0, 1, -1, 0) + + result[i+1] = b * alpha + result[i] = d + else: + break + else: + if full: + if rows > 1: + s = s[1:] + [s[0]] + if cols > 1: + t = [row[1:] + [row[0]] for row in t] + result = invs + (m[0][0],) + + if full: + return tuple(result), s, t + else: + return tuple(result) + + +def _gcdex(a, b): + r""" + This supports the functions that compute Hermite Normal Form. + + Explanation + =========== + + Let x, y be the coefficients returned by the extended Euclidean + Algorithm, so that x*a + y*b = g. In the algorithms for computing HNF, + it is critical that x, y not only satisfy the condition of being small + in magnitude -- namely that |x| <= |b|/g, |y| <- |a|/g -- but also that + y == 0 when a | b. + + """ + x, y, g = ZZ.gcdex(a, b) + if a != 0 and b % a == 0: + y = 0 + x = -1 if a < 0 else 1 + return x, y, g + + +def _hermite_normal_form(A): + r""" + Compute the Hermite Normal Form of DomainMatrix *A* over :ref:`ZZ`. + + Parameters + ========== + + A : :py:class:`~.DomainMatrix` over domain :ref:`ZZ`. + + Returns + ======= + + :py:class:`~.DomainMatrix` + The HNF of matrix *A*. + + Raises + ====== + + DMDomainError + If the domain of the matrix is not :ref:`ZZ`. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Algorithm 2.4.5.) + + """ + if not A.domain.is_ZZ: + raise DMDomainError('Matrix must be over domain ZZ.') + # We work one row at a time, starting from the bottom row, and working our + # way up. + m, n = A.shape + A = A.to_ddm().copy() + # Our goal is to put pivot entries in the rightmost columns. + # Invariant: Before processing each row, k should be the index of the + # leftmost column in which we have so far put a pivot. + k = n + for i in range(m - 1, -1, -1): + if k == 0: + # This case can arise when n < m and we've already found n pivots. + # We don't need to consider any more rows, because this is already + # the maximum possible number of pivots. + break + k -= 1 + # k now points to the column in which we want to put a pivot. + # We want zeros in all entries to the left of the pivot column. + for j in range(k - 1, -1, -1): + if A[i][j] != 0: + # Replace cols j, k by lin combs of these cols such that, in row i, + # col j has 0, while col k has the gcd of their row i entries. Note + # that this ensures a nonzero entry in col k. + u, v, d = _gcdex(A[i][k], A[i][j]) + r, s = A[i][k] // d, A[i][j] // d + add_columns(A, k, j, u, v, -s, r) + b = A[i][k] + # Do not want the pivot entry to be negative. + if b < 0: + add_columns(A, k, k, -1, 0, -1, 0) + b = -b + # The pivot entry will be 0 iff the row was 0 from the pivot col all the + # way to the left. In this case, we are still working on the same pivot + # col for the next row. Therefore: + if b == 0: + k += 1 + # If the pivot entry is nonzero, then we want to reduce all entries to its + # right in the sense of the division algorithm, i.e. make them all remainders + # w.r.t. the pivot as divisor. + else: + for j in range(k + 1, n): + q = A[i][j] // b + add_columns(A, j, k, 1, -q, 0, 1) + # Finally, the HNF consists of those columns of A in which we succeeded in making + # a nonzero pivot. + return DomainMatrix.from_rep(A.to_dfm_or_ddm())[:, k:] + + +def _hermite_normal_form_modulo_D(A, D): + r""" + Perform the mod *D* Hermite Normal Form reduction algorithm on + :py:class:`~.DomainMatrix` *A*. + + Explanation + =========== + + If *A* is an $m \times n$ matrix of rank $m$, having Hermite Normal Form + $W$, and if *D* is any positive integer known in advance to be a multiple + of $\det(W)$, then the HNF of *A* can be computed by an algorithm that + works mod *D* in order to prevent coefficient explosion. + + Parameters + ========== + + A : :py:class:`~.DomainMatrix` over :ref:`ZZ` + $m \times n$ matrix, having rank $m$. + D : :ref:`ZZ` + Positive integer, known to be a multiple of the determinant of the + HNF of *A*. + + Returns + ======= + + :py:class:`~.DomainMatrix` + The HNF of matrix *A*. + + Raises + ====== + + DMDomainError + If the domain of the matrix is not :ref:`ZZ`, or + if *D* is given but is not in :ref:`ZZ`. + + DMShapeError + If the matrix has more rows than columns. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Algorithm 2.4.8.) + + """ + if not A.domain.is_ZZ: + raise DMDomainError('Matrix must be over domain ZZ.') + if not ZZ.of_type(D) or D < 1: + raise DMDomainError('Modulus D must be positive element of domain ZZ.') + + def add_columns_mod_R(m, R, i, j, a, b, c, d): + # replace m[:, i] by (a*m[:, i] + b*m[:, j]) % R + # and m[:, j] by (c*m[:, i] + d*m[:, j]) % R + for k in range(len(m)): + e = m[k][i] + m[k][i] = symmetric_residue((a * e + b * m[k][j]) % R, R) + m[k][j] = symmetric_residue((c * e + d * m[k][j]) % R, R) + + W = defaultdict(dict) + + m, n = A.shape + if n < m: + raise DMShapeError('Matrix must have at least as many columns as rows.') + A = A.to_list() + k = n + R = D + for i in range(m - 1, -1, -1): + k -= 1 + for j in range(k - 1, -1, -1): + if A[i][j] != 0: + u, v, d = _gcdex(A[i][k], A[i][j]) + r, s = A[i][k] // d, A[i][j] // d + add_columns_mod_R(A, R, k, j, u, v, -s, r) + b = A[i][k] + if b == 0: + A[i][k] = b = R + u, v, d = _gcdex(b, R) + for ii in range(m): + W[ii][i] = u*A[ii][k] % R + if W[i][i] == 0: + W[i][i] = R + for j in range(i + 1, m): + q = W[i][j] // W[i][i] + add_columns(W, j, i, 1, -q, 0, 1) + R //= d + return DomainMatrix(W, (m, m), ZZ).to_dense() + + +def hermite_normal_form(A, *, D=None, check_rank=False): + r""" + Compute the Hermite Normal Form of :py:class:`~.DomainMatrix` *A* over + :ref:`ZZ`. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.polys.matrices.normalforms import hermite_normal_form + >>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)], + ... [ZZ(3), ZZ(9), ZZ(6)], + ... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ) + >>> print(hermite_normal_form(m).to_Matrix()) + Matrix([[10, 0, 2], [0, 15, 3], [0, 0, 2]]) + + Parameters + ========== + + A : $m \times n$ ``DomainMatrix`` over :ref:`ZZ`. + + D : :ref:`ZZ`, optional + Let $W$ be the HNF of *A*. If known in advance, a positive integer *D* + being any multiple of $\det(W)$ may be provided. In this case, if *A* + also has rank $m$, then we may use an alternative algorithm that works + mod *D* in order to prevent coefficient explosion. + + check_rank : boolean, optional (default=False) + The basic assumption is that, if you pass a value for *D*, then + you already believe that *A* has rank $m$, so we do not waste time + checking it for you. If you do want this to be checked (and the + ordinary, non-modulo *D* algorithm to be used if the check fails), then + set *check_rank* to ``True``. + + Returns + ======= + + :py:class:`~.DomainMatrix` + The HNF of matrix *A*. + + Raises + ====== + + DMDomainError + If the domain of the matrix is not :ref:`ZZ`, or + if *D* is given but is not in :ref:`ZZ`. + + DMShapeError + If the mod *D* algorithm is used but the matrix has more rows than + columns. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Algorithms 2.4.5 and 2.4.8.) + + """ + if not A.domain.is_ZZ: + raise DMDomainError('Matrix must be over domain ZZ.') + if D is not None and (not check_rank or A.convert_to(QQ).rank() == A.shape[0]): + return _hermite_normal_form_modulo_D(A, D) + else: + return _hermite_normal_form(A) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/rref.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/rref.py new file mode 100644 index 0000000000000000000000000000000000000000..c5a71b04971e8dc8ecac5cc2691f98ba68e35d45 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/rref.py @@ -0,0 +1,422 @@ +# Algorithms for computing the reduced row echelon form of a matrix. +# +# We need to choose carefully which algorithms to use depending on the domain, +# shape, and sparsity of the matrix as well as things like the bit count in the +# case of ZZ or QQ. This is important because the algorithms have different +# performance characteristics in the extremes of dense vs sparse. +# +# In all cases we use the sparse implementations but we need to choose between +# Gauss-Jordan elimination with division and fraction-free Gauss-Jordan +# elimination. For very sparse matrices over ZZ with low bit counts it is +# asymptotically faster to use Gauss-Jordan elimination with division. For +# dense matrices with high bit counts it is asymptotically faster to use +# fraction-free Gauss-Jordan. +# +# The most important thing is to get the extreme cases right because it can +# make a big difference. In between the extremes though we have to make a +# choice and here we use empirically determined thresholds based on timings +# with random sparse matrices. +# +# In the case of QQ we have to consider the denominators as well. If the +# denominators are small then it is faster to clear them and use fraction-free +# Gauss-Jordan over ZZ. If the denominators are large then it is faster to use +# Gauss-Jordan elimination with division over QQ. +# +# Timings for the various algorithms can be found at +# +# https://github.com/sympy/sympy/issues/25410 +# https://github.com/sympy/sympy/pull/25443 + +from sympy.polys.domains import ZZ + +from sympy.polys.matrices.sdm import SDM, sdm_irref, sdm_rref_den +from sympy.polys.matrices.ddm import DDM +from sympy.polys.matrices.dense import ddm_irref, ddm_irref_den + + +def _dm_rref(M, *, method='auto'): + """ + Compute the reduced row echelon form of a ``DomainMatrix``. + + This function is the implementation of :meth:`DomainMatrix.rref`. + + Chooses the best algorithm depending on the domain, shape, and sparsity of + the matrix as well as things like the bit count in the case of :ref:`ZZ` or + :ref:`QQ`. The result is returned over the field associated with the domain + of the Matrix. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.rref + The ``DomainMatrix`` method that calls this function. + sympy.polys.matrices.rref._dm_rref_den + Alternative function for computing RREF with denominator. + """ + method, use_fmt = _dm_rref_choose_method(M, method, denominator=False) + + M, old_fmt = _dm_to_fmt(M, use_fmt) + + if method == 'GJ': + # Use Gauss-Jordan with division over the associated field. + Mf = _to_field(M) + M_rref, pivots = _dm_rref_GJ(Mf) + + elif method == 'FF': + # Use fraction-free GJ over the current domain. + M_rref_f, den, pivots = _dm_rref_den_FF(M) + M_rref = _to_field(M_rref_f) / den + + elif method == 'CD': + # Clear denominators and use fraction-free GJ in the associated ring. + _, Mr = M.clear_denoms_rowwise(convert=True) + M_rref_f, den, pivots = _dm_rref_den_FF(Mr) + M_rref = _to_field(M_rref_f) / den + + else: + raise ValueError(f"Unknown method for rref: {method}") + + M_rref, _ = _dm_to_fmt(M_rref, old_fmt) + + # Invariants: + # - M_rref is in the same format (sparse or dense) as the input matrix. + # - M_rref is in the associated field domain and any denominator was + # divided in (so is implicitly 1 now). + + return M_rref, pivots + + +def _dm_rref_den(M, *, keep_domain=True, method='auto'): + """ + Compute the reduced row echelon form of a ``DomainMatrix`` with denominator. + + This function is the implementation of :meth:`DomainMatrix.rref_den`. + + Chooses the best algorithm depending on the domain, shape, and sparsity of + the matrix as well as things like the bit count in the case of :ref:`ZZ` or + :ref:`QQ`. The result is returned over the same domain as the input matrix + unless ``keep_domain=False`` in which case the result might be over an + associated ring or field domain. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den + The ``DomainMatrix`` method that calls this function. + sympy.polys.matrices.rref._dm_rref + Alternative function for computing RREF without denominator. + """ + method, use_fmt = _dm_rref_choose_method(M, method, denominator=True) + + M, old_fmt = _dm_to_fmt(M, use_fmt) + + if method == 'FF': + # Use fraction-free GJ over the current domain. + M_rref, den, pivots = _dm_rref_den_FF(M) + + elif method == 'GJ': + # Use Gauss-Jordan with division over the associated field. + M_rref_f, pivots = _dm_rref_GJ(_to_field(M)) + + # Convert back to the ring? + if keep_domain and M_rref_f.domain != M.domain: + _, M_rref = M_rref_f.clear_denoms(convert=True) + + if pivots: + den = M_rref[0, pivots[0]].element + else: + den = M_rref.domain.one + else: + # Possibly an associated field + M_rref = M_rref_f + den = M_rref.domain.one + + elif method == 'CD': + # Clear denominators and use fraction-free GJ in the associated ring. + _, Mr = M.clear_denoms_rowwise(convert=True) + + M_rref_r, den, pivots = _dm_rref_den_FF(Mr) + + if keep_domain and M_rref_r.domain != M.domain: + # Convert back to the field + M_rref = _to_field(M_rref_r) / den + den = M.domain.one + else: + # Possibly an associated ring + M_rref = M_rref_r + + if pivots: + den = M_rref[0, pivots[0]].element + else: + den = M_rref.domain.one + else: + raise ValueError(f"Unknown method for rref: {method}") + + M_rref, _ = _dm_to_fmt(M_rref, old_fmt) + + # Invariants: + # - M_rref is in the same format (sparse or dense) as the input matrix. + # - If keep_domain=True then M_rref and den are in the same domain as the + # input matrix + # - If keep_domain=False then M_rref might be in an associated ring or + # field domain but den is always in the same domain as M_rref. + + return M_rref, den, pivots + + +def _dm_to_fmt(M, fmt): + """Convert a matrix to the given format and return the old format.""" + old_fmt = M.rep.fmt + if old_fmt == fmt: + pass + elif fmt == 'dense': + M = M.to_dense() + elif fmt == 'sparse': + M = M.to_sparse() + else: + raise ValueError(f'Unknown format: {fmt}') # pragma: no cover + return M, old_fmt + + +# These are the four basic implementations that we want to choose between: + + +def _dm_rref_GJ(M): + """Compute RREF using Gauss-Jordan elimination with division.""" + if M.rep.fmt == 'sparse': + return _dm_rref_GJ_sparse(M) + else: + return _dm_rref_GJ_dense(M) + + +def _dm_rref_den_FF(M): + """Compute RREF using fraction-free Gauss-Jordan elimination.""" + if M.rep.fmt == 'sparse': + return _dm_rref_den_FF_sparse(M) + else: + return _dm_rref_den_FF_dense(M) + + +def _dm_rref_GJ_sparse(M): + """Compute RREF using sparse Gauss-Jordan elimination with division.""" + M_rref_d, pivots, _ = sdm_irref(M.rep) + M_rref_sdm = SDM(M_rref_d, M.shape, M.domain) + pivots = tuple(pivots) + return M.from_rep(M_rref_sdm), pivots + + +def _dm_rref_GJ_dense(M): + """Compute RREF using dense Gauss-Jordan elimination with division.""" + partial_pivot = M.domain.is_RR or M.domain.is_CC + ddm = M.rep.to_ddm().copy() + pivots = ddm_irref(ddm, _partial_pivot=partial_pivot) + M_rref_ddm = DDM(ddm, M.shape, M.domain) + pivots = tuple(pivots) + return M.from_rep(M_rref_ddm.to_dfm_or_ddm()), pivots + + +def _dm_rref_den_FF_sparse(M): + """Compute RREF using sparse fraction-free Gauss-Jordan elimination.""" + M_rref_d, den, pivots = sdm_rref_den(M.rep, M.domain) + M_rref_sdm = SDM(M_rref_d, M.shape, M.domain) + pivots = tuple(pivots) + return M.from_rep(M_rref_sdm), den, pivots + + +def _dm_rref_den_FF_dense(M): + """Compute RREF using sparse fraction-free Gauss-Jordan elimination.""" + ddm = M.rep.to_ddm().copy() + den, pivots = ddm_irref_den(ddm, M.domain) + M_rref_ddm = DDM(ddm, M.shape, M.domain) + pivots = tuple(pivots) + return M.from_rep(M_rref_ddm.to_dfm_or_ddm()), den, pivots + + +def _dm_rref_choose_method(M, method, *, denominator=False): + """Choose the fastest method for computing RREF for M.""" + + if method != 'auto': + if method.endswith('_dense'): + method = method[:-len('_dense')] + use_fmt = 'dense' + else: + use_fmt = 'sparse' + + else: + # The sparse implementations are always faster + use_fmt = 'sparse' + + K = M.domain + + if K.is_ZZ: + method = _dm_rref_choose_method_ZZ(M, denominator=denominator) + elif K.is_QQ: + method = _dm_rref_choose_method_QQ(M, denominator=denominator) + elif K.is_RR or K.is_CC: + # TODO: Add partial pivot support to the sparse implementations. + method = 'GJ' + use_fmt = 'dense' + elif K.is_EX and M.rep.fmt == 'dense' and not denominator: + # Do not switch to the sparse implementation for EX because the + # domain does not have proper canonicalization and the sparse + # implementation gives equivalent but non-identical results over EX + # from performing arithmetic in a different order. Specifically + # test_issue_23718 ends up getting a more complicated expression + # when using the sparse implementation. Probably the best fix for + # this is something else but for now we stick with the dense + # implementation for EX if the matrix is already dense. + method = 'GJ' + use_fmt = 'dense' + else: + # This is definitely suboptimal. More work is needed to determine + # the best method for computing RREF over different domains. + if denominator: + method = 'FF' + else: + method = 'GJ' + + return method, use_fmt + + +def _dm_rref_choose_method_QQ(M, *, denominator=False): + """Choose the fastest method for computing RREF over QQ.""" + # The same sorts of considerations apply here as in the case of ZZ. Here + # though a new more significant consideration is what sort of denominators + # we have and what to do with them so we focus on that. + + # First compute the density. This is the average number of non-zero entries + # per row but only counting rows that have at least one non-zero entry + # since RREF can ignore fully zero rows. + density, _, ncols = _dm_row_density(M) + + # For sparse matrices use Gauss-Jordan elimination over QQ regardless. + if density < min(5, ncols/2): + return 'GJ' + + # Compare the bit-length of the lcm of the denominators to the bit length + # of the numerators. + # + # The threshold here is empirical: we prefer rref over QQ if clearing + # denominators would result in a numerator matrix having 5x the bit size of + # the current numerators. + numers, denoms = _dm_QQ_numers_denoms(M) + numer_bits = max([n.bit_length() for n in numers], default=1) + + denom_lcm = ZZ.one + for d in denoms: + denom_lcm = ZZ.lcm(denom_lcm, d) + if denom_lcm.bit_length() > 5*numer_bits: + return 'GJ' + + # If we get here then the matrix is dense and the lcm of the denominators + # is not too large compared to the numerators. For particularly small + # denominators it is fastest just to clear them and use fraction-free + # Gauss-Jordan over ZZ. With very small denominators this is a little + # faster than using rref_den over QQ but there is an intermediate regime + # where rref_den over QQ is significantly faster. The small denominator + # case is probably very common because small fractions like 1/2 or 1/3 are + # often seen in user inputs. + + if denom_lcm.bit_length() < 50: + return 'CD' + else: + return 'FF' + + +def _dm_rref_choose_method_ZZ(M, *, denominator=False): + """Choose the fastest method for computing RREF over ZZ.""" + # In the extreme of very sparse matrices and low bit counts it is faster to + # use Gauss-Jordan elimination over QQ rather than fraction-free + # Gauss-Jordan over ZZ. In the opposite extreme of dense matrices and high + # bit counts it is faster to use fraction-free Gauss-Jordan over ZZ. These + # two extreme cases need to be handled differently because they lead to + # different asymptotic complexities. In between these two extremes we need + # a threshold for deciding which method to use. This threshold is + # determined empirically by timing the two methods with random matrices. + + # The disadvantage of using empirical timings is that future optimisations + # might change the relative speeds so this can easily become out of date. + # The main thing is to get the asymptotic complexity right for the extreme + # cases though so the precise value of the threshold is hopefully not too + # important. + + # Empirically determined parameter. + PARAM = 10000 + + # First compute the density. This is the average number of non-zero entries + # per row but only counting rows that have at least one non-zero entry + # since RREF can ignore fully zero rows. + density, nrows_nz, ncols = _dm_row_density(M) + + # For small matrices use QQ if more than half the entries are zero. + if nrows_nz < 10: + if density < ncols/2: + return 'GJ' + else: + return 'FF' + + # These are just shortcuts for the formula below. + if density < 5: + return 'GJ' + elif density > 5 + PARAM/nrows_nz: + return 'FF' # pragma: no cover + + # Maximum bitsize of any entry. + elements = _dm_elements(M) + bits = max([e.bit_length() for e in elements], default=1) + + # Wideness parameter. This is 1 for square or tall matrices but >1 for wide + # matrices. + wideness = max(1, 2/3*ncols/nrows_nz) + + max_density = (5 + PARAM/(nrows_nz*bits**2)) * wideness + + if density < max_density: + return 'GJ' + else: + return 'FF' + + +def _dm_row_density(M): + """Density measure for sparse matrices. + + Defines the "density", ``d`` as the average number of non-zero entries per + row except ignoring rows that are fully zero. RREF can ignore fully zero + rows so they are excluded. By definition ``d >= 1`` except that we define + ``d = 0`` for the zero matrix. + + Returns ``(density, nrows_nz, ncols)`` where ``nrows_nz`` counts the number + of nonzero rows and ``ncols`` is the number of columns. + """ + # Uses the SDM dict-of-dicts representation. + ncols = M.shape[1] + rows_nz = M.rep.to_sdm().values() + if not rows_nz: + return 0, 0, ncols + else: + nrows_nz = len(rows_nz) + density = sum(map(len, rows_nz)) / nrows_nz + return density, nrows_nz, ncols + + +def _dm_elements(M): + """Return nonzero elements of a DomainMatrix.""" + elements, _ = M.to_flat_nz() + return elements + + +def _dm_QQ_numers_denoms(Mq): + """Returns the numerators and denominators of a DomainMatrix over QQ.""" + elements = _dm_elements(Mq) + numers = [e.numerator for e in elements] + denoms = [e.denominator for e in elements] + return numers, denoms + + +def _to_field(M): + """Convert a DomainMatrix to a field if possible.""" + K = M.domain + if K.has_assoc_Field: + return M.to_field() + else: + return M diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/sdm.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/sdm.py new file mode 100644 index 0000000000000000000000000000000000000000..84558d83b6f58a3a9074d31f1a315ac901cd68da --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/sdm.py @@ -0,0 +1,2197 @@ +""" + +Module for the SDM class. + +""" + +from operator import add, neg, pos, sub, mul +from collections import defaultdict + +from sympy.external.gmpy import GROUND_TYPES +from sympy.utilities.decorator import doctest_depends_on +from sympy.utilities.iterables import _strongly_connected_components + +from .exceptions import DMBadInputError, DMDomainError, DMShapeError + +from sympy.polys.domains import QQ + +from .ddm import DDM + + +if GROUND_TYPES != 'flint': + __doctest_skip__ = ['SDM.to_dfm', 'SDM.to_dfm_or_ddm'] + + +class SDM(dict): + r"""Sparse matrix based on polys domain elements + + This is a dict subclass and is a wrapper for a dict of dicts that supports + basic matrix arithmetic +, -, *, **. + + + In order to create a new :py:class:`~.SDM`, a dict + of dicts mapping non-zero elements to their + corresponding row and column in the matrix is needed. + + We also need to specify the shape and :py:class:`~.Domain` + of our :py:class:`~.SDM` object. + + We declare a 2x2 :py:class:`~.SDM` matrix belonging + to QQ domain as shown below. + The 2x2 Matrix in the example is + + .. math:: + A = \left[\begin{array}{ccc} + 0 & \frac{1}{2} \\ + 0 & 0 \end{array} \right] + + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> elemsdict = {0:{1:QQ(1, 2)}} + >>> A = SDM(elemsdict, (2, 2), QQ) + >>> A + {0: {1: 1/2}} + + We can manipulate :py:class:`~.SDM` the same way + as a Matrix class + + >>> from sympy import ZZ + >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + >>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ) + >>> A + B + {0: {0: 3, 1: 2}, 1: {0: 1, 1: 4}} + + Multiplication + + >>> A*B + {0: {1: 8}, 1: {0: 3}} + >>> A*ZZ(2) + {0: {1: 4}, 1: {0: 2}} + + """ + + fmt = 'sparse' + is_DFM = False + is_DDM = False + + def __init__(self, elemsdict, shape, domain): + super().__init__(elemsdict) + self.shape = self.rows, self.cols = m, n = shape + self.domain = domain + + if not all(0 <= r < m for r in self): + raise DMBadInputError("Row out of range") + if not all(0 <= c < n for row in self.values() for c in row): + raise DMBadInputError("Column out of range") + + def getitem(self, i, j): + try: + return self[i][j] + except KeyError: + m, n = self.shape + if -m <= i < m and -n <= j < n: + try: + return self[i % m][j % n] + except KeyError: + return self.domain.zero + else: + raise IndexError("index out of range") + + def setitem(self, i, j, value): + m, n = self.shape + if not (-m <= i < m and -n <= j < n): + raise IndexError("index out of range") + i, j = i % m, j % n + if value: + try: + self[i][j] = value + except KeyError: + self[i] = {j: value} + else: + rowi = self.get(i, None) + if rowi is not None: + try: + del rowi[j] + except KeyError: + pass + else: + if not rowi: + del self[i] + + def extract_slice(self, slice1, slice2): + m, n = self.shape + ri = range(m)[slice1] + ci = range(n)[slice2] + + sdm = {} + for i, row in self.items(): + if i in ri: + row = {ci.index(j): e for j, e in row.items() if j in ci} + if row: + sdm[ri.index(i)] = row + + return self.new(sdm, (len(ri), len(ci)), self.domain) + + def extract(self, rows, cols): + if not (self and rows and cols): + return self.zeros((len(rows), len(cols)), self.domain) + + m, n = self.shape + if not (-m <= min(rows) <= max(rows) < m): + raise IndexError('Row index out of range') + if not (-n <= min(cols) <= max(cols) < n): + raise IndexError('Column index out of range') + + # rows and cols can contain duplicates e.g. M[[1, 2, 2], [0, 1]] + # Build a map from row/col in self to list of rows/cols in output + rowmap = defaultdict(list) + colmap = defaultdict(list) + for i2, i1 in enumerate(rows): + rowmap[i1 % m].append(i2) + for j2, j1 in enumerate(cols): + colmap[j1 % n].append(j2) + + # Used to efficiently skip zero rows/cols + rowset = set(rowmap) + colset = set(colmap) + + sdm1 = self + sdm2 = {} + for i1 in rowset & sdm1.keys(): + row1 = sdm1[i1] + row2 = {} + for j1 in colset & row1.keys(): + row1_j1 = row1[j1] + for j2 in colmap[j1]: + row2[j2] = row1_j1 + if row2: + for i2 in rowmap[i1]: + sdm2[i2] = row2.copy() + + return self.new(sdm2, (len(rows), len(cols)), self.domain) + + def __str__(self): + rowsstr = [] + for i, row in self.items(): + elemsstr = ', '.join('%s: %s' % (j, elem) for j, elem in row.items()) + rowsstr.append('%s: {%s}' % (i, elemsstr)) + return '{%s}' % ', '.join(rowsstr) + + def __repr__(self): + cls = type(self).__name__ + rows = dict.__repr__(self) + return '%s(%s, %s, %s)' % (cls, rows, self.shape, self.domain) + + @classmethod + def new(cls, sdm, shape, domain): + """ + + Parameters + ========== + + sdm: A dict of dicts for non-zero elements in SDM + shape: tuple representing dimension of SDM + domain: Represents :py:class:`~.Domain` of SDM + + Returns + ======= + + An :py:class:`~.SDM` object + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> elemsdict = {0:{1: QQ(2)}} + >>> A = SDM.new(elemsdict, (2, 2), QQ) + >>> A + {0: {1: 2}} + + """ + return cls(sdm, shape, domain) + + def copy(A): + """ + Returns the copy of a :py:class:`~.SDM` object + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> elemsdict = {0:{1:QQ(2)}, 1:{}} + >>> A = SDM(elemsdict, (2, 2), QQ) + >>> B = A.copy() + >>> B + {0: {1: 2}, 1: {}} + + """ + Ac = {i: Ai.copy() for i, Ai in A.items()} + return A.new(Ac, A.shape, A.domain) + + @classmethod + def from_list(cls, ddm, shape, domain): + """ + Create :py:class:`~.SDM` object from a list of lists. + + Parameters + ========== + + ddm: + list of lists containing domain elements + shape: + Dimensions of :py:class:`~.SDM` matrix + domain: + Represents :py:class:`~.Domain` of :py:class:`~.SDM` object + + Returns + ======= + + :py:class:`~.SDM` containing elements of ddm + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> ddm = [[QQ(1, 2), QQ(0)], [QQ(0), QQ(3, 4)]] + >>> A = SDM.from_list(ddm, (2, 2), QQ) + >>> A + {0: {0: 1/2}, 1: {1: 3/4}} + + See Also + ======== + + to_list + from_list_flat + from_dok + from_ddm + """ + + m, n = shape + if not (len(ddm) == m and all(len(row) == n for row in ddm)): + raise DMBadInputError("Inconsistent row-list/shape") + getrow = lambda i: {j:ddm[i][j] for j in range(n) if ddm[i][j]} + irows = ((i, getrow(i)) for i in range(m)) + sdm = {i: row for i, row in irows if row} + return cls(sdm, shape, domain) + + @classmethod + def from_ddm(cls, ddm): + """ + Create :py:class:`~.SDM` from a :py:class:`~.DDM`. + + Examples + ======== + + >>> from sympy.polys.matrices.ddm import DDM + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> ddm = DDM( [[QQ(1, 2), 0], [0, QQ(3, 4)]], (2, 2), QQ) + >>> A = SDM.from_ddm(ddm) + >>> A + {0: {0: 1/2}, 1: {1: 3/4}} + >>> SDM.from_ddm(ddm).to_ddm() == ddm + True + + See Also + ======== + + to_ddm + from_list + from_list_flat + from_dok + """ + return cls.from_list(ddm, ddm.shape, ddm.domain) + + def to_list(M): + """ + Convert a :py:class:`~.SDM` object to a list of lists. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> elemsdict = {0:{1:QQ(2)}, 1:{}} + >>> A = SDM(elemsdict, (2, 2), QQ) + >>> A.to_list() + [[0, 2], [0, 0]] + + + """ + m, n = M.shape + zero = M.domain.zero + ddm = [[zero] * n for _ in range(m)] + for i, row in M.items(): + for j, e in row.items(): + ddm[i][j] = e + return ddm + + def to_list_flat(M): + """ + Convert :py:class:`~.SDM` to a flat list. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0:{1:QQ(2)}, 1:{0: QQ(3)}}, (2, 2), QQ) + >>> A.to_list_flat() + [0, 2, 3, 0] + >>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain) + True + + See Also + ======== + + from_list_flat + to_list + to_dok + to_ddm + """ + m, n = M.shape + zero = M.domain.zero + flat = [zero] * (m * n) + for i, row in M.items(): + for j, e in row.items(): + flat[i*n + j] = e + return flat + + @classmethod + def from_list_flat(cls, elements, shape, domain): + """ + Create :py:class:`~.SDM` from a flat list of elements. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM.from_list_flat([QQ(0), QQ(2), QQ(0), QQ(0)], (2, 2), QQ) + >>> A + {0: {1: 2}} + >>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain) + True + + See Also + ======== + + to_list_flat + from_list + from_dok + from_ddm + """ + m, n = shape + if len(elements) != m * n: + raise DMBadInputError("Inconsistent flat-list shape") + sdm = defaultdict(dict) + for inj, element in enumerate(elements): + if element: + i, j = divmod(inj, n) + sdm[i][j] = element + return cls(sdm, shape, domain) + + def to_flat_nz(M): + """ + Convert :class:`SDM` to a flat list of nonzero elements and data. + + Explanation + =========== + + This is used to operate on a list of the elements of a matrix and then + reconstruct a modified matrix with elements in the same positions using + :meth:`from_flat_nz`. Zero elements are omitted from the list. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0:{1:QQ(2)}, 1:{0: QQ(3)}}, (2, 2), QQ) + >>> elements, data = A.to_flat_nz() + >>> elements + [2, 3] + >>> A == A.from_flat_nz(elements, data, A.domain) + True + + See Also + ======== + + from_flat_nz + to_list_flat + sympy.polys.matrices.ddm.DDM.to_flat_nz + sympy.polys.matrices.domainmatrix.DomainMatrix.to_flat_nz + """ + dok = M.to_dok() + indices = tuple(dok) + elements = list(dok.values()) + data = (indices, M.shape) + return elements, data + + @classmethod + def from_flat_nz(cls, elements, data, domain): + """ + Reconstruct a :class:`~.SDM` after calling :meth:`to_flat_nz`. + + See :meth:`to_flat_nz` for explanation. + + See Also + ======== + + to_flat_nz + from_list_flat + sympy.polys.matrices.ddm.DDM.from_flat_nz + sympy.polys.matrices.domainmatrix.DomainMatrix.from_flat_nz + """ + indices, shape = data + dok = dict(zip(indices, elements)) + return cls.from_dok(dok, shape, domain) + + def to_dod(M): + """ + Convert to dictionary of dictionaries (dod) format. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ) + >>> A.to_dod() + {0: {1: 2}, 1: {0: 3}} + + See Also + ======== + + from_dod + sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod + """ + return {i: row.copy() for i, row in M.items()} + + @classmethod + def from_dod(cls, dod, shape, domain): + """ + Create :py:class:`~.SDM` from dictionary of dictionaries (dod) format. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> dod = {0: {1: QQ(2)}, 1: {0: QQ(3)}} + >>> A = SDM.from_dod(dod, (2, 2), QQ) + >>> A + {0: {1: 2}, 1: {0: 3}} + >>> A == SDM.from_dod(A.to_dod(), A.shape, A.domain) + True + + See Also + ======== + + to_dod + sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod + """ + sdm = defaultdict(dict) + for i, row in dod.items(): + for j, e in row.items(): + if e: + sdm[i][j] = e + return cls(sdm, shape, domain) + + def to_dok(M): + """ + Convert to dictionary of keys (dok) format. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ) + >>> A.to_dok() + {(0, 1): 2, (1, 0): 3} + + See Also + ======== + + from_dok + to_list + to_list_flat + to_ddm + """ + return {(i, j): e for i, row in M.items() for j, e in row.items()} + + @classmethod + def from_dok(cls, dok, shape, domain): + """ + Create :py:class:`~.SDM` from dictionary of keys (dok) format. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> dok = {(0, 1): QQ(2), (1, 0): QQ(3)} + >>> A = SDM.from_dok(dok, (2, 2), QQ) + >>> A + {0: {1: 2}, 1: {0: 3}} + >>> A == SDM.from_dok(A.to_dok(), A.shape, A.domain) + True + + See Also + ======== + + to_dok + from_list + from_list_flat + from_ddm + """ + sdm = defaultdict(dict) + for (i, j), e in dok.items(): + if e: + sdm[i][j] = e + return cls(sdm, shape, domain) + + def iter_values(M): + """ + Iterate over the nonzero values of a :py:class:`~.SDM` matrix. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ) + >>> list(A.iter_values()) + [2, 3] + + """ + for row in M.values(): + yield from row.values() + + def iter_items(M): + """ + Iterate over indices and values of the nonzero elements. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ) + >>> list(A.iter_items()) + [((0, 1), 2), ((1, 0), 3)] + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.iter_items + """ + for i, row in M.items(): + for j, e in row.items(): + yield (i, j), e + + def to_ddm(M): + """ + Convert a :py:class:`~.SDM` object to a :py:class:`~.DDM` object + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ) + >>> A.to_ddm() + [[0, 2], [0, 0]] + + """ + return DDM(M.to_list(), M.shape, M.domain) + + def to_sdm(M): + """ + Convert to :py:class:`~.SDM` format (returns self). + """ + return M + + @doctest_depends_on(ground_types=['flint']) + def to_dfm(M): + """ + Convert a :py:class:`~.SDM` object to a :py:class:`~.DFM` object + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ) + >>> A.to_dfm() + [[0, 2], [0, 0]] + + See Also + ======== + + to_ddm + to_dfm_or_ddm + sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm + """ + return M.to_ddm().to_dfm() + + @doctest_depends_on(ground_types=['flint']) + def to_dfm_or_ddm(M): + """ + Convert to :py:class:`~.DFM` if possible, else :py:class:`~.DDM`. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ) + >>> A.to_dfm_or_ddm() + [[0, 2], [0, 0]] + >>> type(A.to_dfm_or_ddm()) # depends on the ground types + + + See Also + ======== + + to_ddm + to_dfm + sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm + """ + return M.to_ddm().to_dfm_or_ddm() + + @classmethod + def zeros(cls, shape, domain): + r""" + + Returns a :py:class:`~.SDM` of size shape, + belonging to the specified domain + + In the example below we declare a matrix A where, + + .. math:: + A := \left[\begin{array}{ccc} + 0 & 0 & 0 \\ + 0 & 0 & 0 \end{array} \right] + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM.zeros((2, 3), QQ) + >>> A + {} + + """ + return cls({}, shape, domain) + + @classmethod + def ones(cls, shape, domain): + one = domain.one + m, n = shape + row = dict(zip(range(n), [one]*n)) + sdm = {i: row.copy() for i in range(m)} + return cls(sdm, shape, domain) + + @classmethod + def eye(cls, shape, domain): + """ + + Returns a identity :py:class:`~.SDM` matrix of dimensions + size x size, belonging to the specified domain + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> I = SDM.eye((2, 2), QQ) + >>> I + {0: {0: 1}, 1: {1: 1}} + + """ + if isinstance(shape, int): + rows, cols = shape, shape + else: + rows, cols = shape + one = domain.one + sdm = {i: {i: one} for i in range(min(rows, cols))} + return cls(sdm, (rows, cols), domain) + + @classmethod + def diag(cls, diagonal, domain, shape=None): + if shape is None: + shape = (len(diagonal), len(diagonal)) + sdm = {i: {i: v} for i, v in enumerate(diagonal) if v} + return cls(sdm, shape, domain) + + def transpose(M): + """ + + Returns the transpose of a :py:class:`~.SDM` matrix + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy import QQ + >>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ) + >>> A.transpose() + {1: {0: 2}} + + """ + MT = sdm_transpose(M) + return M.new(MT, M.shape[::-1], M.domain) + + def __add__(A, B): + if not isinstance(B, SDM): + return NotImplemented + elif A.shape != B.shape: + raise DMShapeError("Matrix size mismatch: %s + %s" % (A.shape, B.shape)) + return A.add(B) + + def __sub__(A, B): + if not isinstance(B, SDM): + return NotImplemented + elif A.shape != B.shape: + raise DMShapeError("Matrix size mismatch: %s - %s" % (A.shape, B.shape)) + return A.sub(B) + + def __neg__(A): + return A.neg() + + def __mul__(A, B): + """A * B""" + if isinstance(B, SDM): + return A.matmul(B) + elif B in A.domain: + return A.mul(B) + else: + return NotImplemented + + def __rmul__(a, b): + if b in a.domain: + return a.rmul(b) + else: + return NotImplemented + + def matmul(A, B): + """ + Performs matrix multiplication of two SDM matrices + + Parameters + ========== + + A, B: SDM to multiply + + Returns + ======= + + SDM + SDM after multiplication + + Raises + ====== + + DomainError + If domain of A does not match + with that of B + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + >>> B = SDM({0:{0:ZZ(2), 1:ZZ(3)}, 1:{0:ZZ(4)}}, (2, 2), ZZ) + >>> A.matmul(B) + {0: {0: 8}, 1: {0: 2, 1: 3}} + + """ + if A.domain != B.domain: + raise DMDomainError + m, n = A.shape + n2, o = B.shape + if n != n2: + raise DMShapeError + C = sdm_matmul(A, B, A.domain, m, o) + return A.new(C, (m, o), A.domain) + + def mul(A, b): + """ + Multiplies each element of A with a scalar b + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + >>> A.mul(ZZ(3)) + {0: {1: 6}, 1: {0: 3}} + + """ + Csdm = unop_dict(A, lambda aij: aij*b) + return A.new(Csdm, A.shape, A.domain) + + def rmul(A, b): + Csdm = unop_dict(A, lambda aij: b*aij) + return A.new(Csdm, A.shape, A.domain) + + def mul_elementwise(A, B): + if A.domain != B.domain: + raise DMDomainError + if A.shape != B.shape: + raise DMShapeError + zero = A.domain.zero + fzero = lambda e: zero + Csdm = binop_dict(A, B, mul, fzero, fzero) + return A.new(Csdm, A.shape, A.domain) + + def add(A, B): + """ + + Adds two :py:class:`~.SDM` matrices + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + >>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ) + >>> A.add(B) + {0: {0: 3, 1: 2}, 1: {0: 1, 1: 4}} + + """ + Csdm = binop_dict(A, B, add, pos, pos) + return A.new(Csdm, A.shape, A.domain) + + def sub(A, B): + """ + + Subtracts two :py:class:`~.SDM` matrices + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + >>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ) + >>> A.sub(B) + {0: {0: -3, 1: 2}, 1: {0: 1, 1: -4}} + + """ + Csdm = binop_dict(A, B, sub, pos, neg) + return A.new(Csdm, A.shape, A.domain) + + def neg(A): + """ + + Returns the negative of a :py:class:`~.SDM` matrix + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + >>> A.neg() + {0: {1: -2}, 1: {0: -1}} + + """ + Csdm = unop_dict(A, neg) + return A.new(Csdm, A.shape, A.domain) + + def convert_to(A, K): + """ + Converts the :py:class:`~.Domain` of a :py:class:`~.SDM` matrix to K + + Examples + ======== + + >>> from sympy import ZZ, QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + >>> A.convert_to(QQ) + {0: {1: 2}, 1: {0: 1}} + + """ + Kold = A.domain + if K == Kold: + return A.copy() + Ak = unop_dict(A, lambda e: K.convert_from(e, Kold)) + return A.new(Ak, A.shape, K) + + def nnz(A): + """Number of non-zero elements in the :py:class:`~.SDM` matrix. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + >>> A.nnz() + 2 + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.nnz + """ + return sum(map(len, A.values())) + + def scc(A): + """Strongly connected components of a square matrix *A*. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0: ZZ(2)}, 1:{1:ZZ(1)}}, (2, 2), ZZ) + >>> A.scc() + [[0], [1]] + + See also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.scc + """ + rows, cols = A.shape + assert rows == cols + V = range(rows) + Emap = {v: list(A.get(v, [])) for v in V} + return _strongly_connected_components(V, Emap) + + def rref(A): + """ + + Returns reduced-row echelon form and list of pivots for the :py:class:`~.SDM` + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(2), 1:QQ(4)}}, (2, 2), QQ) + >>> A.rref() + ({0: {0: 1, 1: 2}}, [0]) + + """ + B, pivots, _ = sdm_irref(A) + return A.new(B, A.shape, A.domain), pivots + + def rref_den(A): + """ + + Returns reduced-row echelon form (RREF) with denominator and pivots. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(2), 1:QQ(4)}}, (2, 2), QQ) + >>> A.rref_den() + ({0: {0: 1, 1: 2}}, 1, [0]) + + """ + K = A.domain + A_rref_sdm, denom, pivots = sdm_rref_den(A, K) + A_rref = A.new(A_rref_sdm, A.shape, A.domain) + return A_rref, denom, pivots + + def inv(A): + """ + + Returns inverse of a matrix A + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + >>> A.inv() + {0: {0: -2, 1: 1}, 1: {0: 3/2, 1: -1/2}} + + """ + return A.to_dfm_or_ddm().inv().to_sdm() + + def det(A): + """ + Returns determinant of A + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + >>> A.det() + -2 + + """ + # It would be better to have a sparse implementation of det for use + # with very sparse matrices. Extremely sparse matrices probably just + # have determinant zero and we could probably detect that very quickly. + # In the meantime, we convert to a dense matrix and use ddm_idet. + # + # If GROUND_TYPES=flint though then we will use Flint's implementation + # if possible (dfm). + return A.to_dfm_or_ddm().det() + + def lu(A): + """ + + Returns LU decomposition for a matrix A + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + >>> A.lu() + ({0: {0: 1}, 1: {0: 3, 1: 1}}, {0: {0: 1, 1: 2}, 1: {1: -2}}, []) + + """ + L, U, swaps = A.to_ddm().lu() + return A.from_ddm(L), A.from_ddm(U), swaps + + def qr(self): + """ + QR decomposition for SDM (Sparse Domain Matrix). + + Returns: + - Q: Orthogonal matrix as a SDM. + - R: Upper triangular matrix as a SDM. + """ + ddm_q, ddm_r = self.to_ddm().qr() + Q = ddm_q.to_sdm() + R = ddm_r.to_sdm() + return Q, R + + def lu_solve(A, b): + """ + + Uses LU decomposition to solve Ax = b, + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + >>> b = SDM({0:{0:QQ(1)}, 1:{0:QQ(2)}}, (2, 1), QQ) + >>> A.lu_solve(b) + {1: {0: 1/2}} + + """ + return A.from_ddm(A.to_ddm().lu_solve(b.to_ddm())) + + def fflu(self): + """ + Fraction free LU decomposition of SDM. + + Uses DDM implementation. + + See Also + ======== + + sympy.polys.matrices.ddm.DDM.fflu + """ + ddm_p, ddm_l, ddm_d, ddm_u = self.to_dfm_or_ddm().fflu() + P = ddm_p.to_sdm() + L = ddm_l.to_sdm() + D = ddm_d.to_sdm() + U = ddm_u.to_sdm() + return P, L, D, U + + def nullspace(A): + """ + Nullspace of a :py:class:`~.SDM` matrix A. + + The domain of the matrix must be a field. + + It is better to use the :meth:`~.DomainMatrix.nullspace` method rather + than this method which is otherwise no longer used. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0: QQ(2), 1: QQ(4)}}, (2, 2), QQ) + >>> A.nullspace() + ({0: {0: -2, 1: 1}}, [1]) + + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace + The preferred way to get the nullspace of a matrix. + + """ + ncols = A.shape[1] + one = A.domain.one + B, pivots, nzcols = sdm_irref(A) + K, nonpivots = sdm_nullspace_from_rref(B, one, ncols, pivots, nzcols) + K = dict(enumerate(K)) + shape = (len(K), ncols) + return A.new(K, shape, A.domain), nonpivots + + def nullspace_from_rref(A, pivots=None): + """ + Returns nullspace for a :py:class:`~.SDM` matrix ``A`` in RREF. + + The domain of the matrix can be any domain. + + The matrix must already be in reduced row echelon form (RREF). + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import SDM + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0: QQ(2), 1: QQ(4)}}, (2, 2), QQ) + >>> A_rref, pivots = A.rref() + >>> A_null, nonpivots = A_rref.nullspace_from_rref(pivots) + >>> A_null + {0: {0: -2, 1: 1}} + >>> pivots + [0] + >>> nonpivots + [1] + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace + The higher-level function that would usually be called instead of + calling this one directly. + + sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace_from_rref + The higher-level direct equivalent of this function. + + sympy.polys.matrices.ddm.DDM.nullspace_from_rref + The equivalent function for dense :py:class:`~.DDM` matrices. + + """ + m, n = A.shape + K = A.domain + + if pivots is None: + pivots = sorted(map(min, A.values())) + + if not pivots: + return A.eye((n, n), K), list(range(n)) + elif len(pivots) == n: + return A.zeros((0, n), K), [] + + # In fraction-free RREF the nonzero entry inserted for the pivots is + # not necessarily 1. + pivot_val = A[0][pivots[0]] + assert not K.is_zero(pivot_val) + + pivots_set = set(pivots) + + # Loop once over all nonzero entries making a map from column indices + # to the nonzero entries in that column along with the row index of the + # nonzero entry. This is basically the transpose of the matrix. + nonzero_cols = defaultdict(list) + for i, Ai in A.items(): + for j, Aij in Ai.items(): + nonzero_cols[j].append((i, Aij)) + + # Usually in SDM we want to avoid looping over the dimensions of the + # matrix because it is optimised to support extremely sparse matrices. + # Here in nullspace though every zero column becomes a nonzero column + # so we need to loop once over the columns at least (range(n)) rather + # than just the nonzero entries of the matrix. We can still avoid + # an inner loop over the rows though by using the nonzero_cols map. + basis = [] + nonpivots = [] + for j in range(n): + if j in pivots_set: + continue + nonpivots.append(j) + + vec = {j: pivot_val} + for ip, Aij in nonzero_cols[j]: + vec[pivots[ip]] = -Aij + + basis.append(vec) + + sdm = dict(enumerate(basis)) + A_null = A.new(sdm, (len(basis), n), K) + + return (A_null, nonpivots) + + def particular(A): + ncols = A.shape[1] + B, pivots, nzcols = sdm_irref(A) + P = sdm_particular_from_rref(B, ncols, pivots) + rep = {0:P} if P else {} + return A.new(rep, (1, ncols-1), A.domain) + + def hstack(A, *B): + """Horizontally stacks :py:class:`~.SDM` matrices. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + + >>> A = SDM({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}, (2, 2), ZZ) + >>> B = SDM({0: {0: ZZ(5), 1: ZZ(6)}, 1: {0: ZZ(7), 1: ZZ(8)}}, (2, 2), ZZ) + >>> A.hstack(B) + {0: {0: 1, 1: 2, 2: 5, 3: 6}, 1: {0: 3, 1: 4, 2: 7, 3: 8}} + + >>> C = SDM({0: {0: ZZ(9), 1: ZZ(10)}, 1: {0: ZZ(11), 1: ZZ(12)}}, (2, 2), ZZ) + >>> A.hstack(B, C) + {0: {0: 1, 1: 2, 2: 5, 3: 6, 4: 9, 5: 10}, 1: {0: 3, 1: 4, 2: 7, 3: 8, 4: 11, 5: 12}} + """ + Anew = dict(A.copy()) + rows, cols = A.shape + domain = A.domain + + for Bk in B: + Bkrows, Bkcols = Bk.shape + assert Bkrows == rows + assert Bk.domain == domain + + for i, Bki in Bk.items(): + Ai = Anew.get(i, None) + if Ai is None: + Anew[i] = Ai = {} + for j, Bkij in Bki.items(): + Ai[j + cols] = Bkij + cols += Bkcols + + return A.new(Anew, (rows, cols), A.domain) + + def vstack(A, *B): + """Vertically stacks :py:class:`~.SDM` matrices. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.polys.matrices.sdm import SDM + + >>> A = SDM({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}, (2, 2), ZZ) + >>> B = SDM({0: {0: ZZ(5), 1: ZZ(6)}, 1: {0: ZZ(7), 1: ZZ(8)}}, (2, 2), ZZ) + >>> A.vstack(B) + {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}, 2: {0: 5, 1: 6}, 3: {0: 7, 1: 8}} + + >>> C = SDM({0: {0: ZZ(9), 1: ZZ(10)}, 1: {0: ZZ(11), 1: ZZ(12)}}, (2, 2), ZZ) + >>> A.vstack(B, C) + {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}, 2: {0: 5, 1: 6}, 3: {0: 7, 1: 8}, 4: {0: 9, 1: 10}, 5: {0: 11, 1: 12}} + """ + Anew = dict(A.copy()) + rows, cols = A.shape + domain = A.domain + + for Bk in B: + Bkrows, Bkcols = Bk.shape + assert Bkcols == cols + assert Bk.domain == domain + + for i, Bki in Bk.items(): + Anew[i + rows] = Bki + rows += Bkrows + + return A.new(Anew, (rows, cols), A.domain) + + def applyfunc(self, func, domain): + sdm = {i: {j: func(e) for j, e in row.items()} for i, row in self.items()} + return self.new(sdm, self.shape, domain) + + def charpoly(A): + """ + Returns the coefficients of the characteristic polynomial + of the :py:class:`~.SDM` matrix. These elements will be domain elements. + The domain of the elements will be same as domain of the :py:class:`~.SDM`. + + Examples + ======== + + >>> from sympy import QQ, Symbol + >>> from sympy.polys.matrices.sdm import SDM + >>> from sympy.polys import Poly + >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + >>> A.charpoly() + [1, -5, -2] + + We can create a polynomial using the + coefficients using :py:class:`~.Poly` + + >>> x = Symbol('x') + >>> p = Poly(A.charpoly(), x, domain=A.domain) + >>> p + Poly(x**2 - 5*x - 2, x, domain='QQ') + + """ + K = A.domain + n, _ = A.shape + pdict = sdm_berk(A, n, K) + plist = [K.zero] * (n + 1) + for i, pi in pdict.items(): + plist[i] = pi + return plist + + def is_zero_matrix(self): + """ + Says whether this matrix has all zero entries. + """ + return not self + + def is_upper(self): + """ + Says whether this matrix is upper-triangular. True can be returned + even if the matrix is not square. + """ + return all(i <= j for i, row in self.items() for j in row) + + def is_lower(self): + """ + Says whether this matrix is lower-triangular. True can be returned + even if the matrix is not square. + """ + return all(i >= j for i, row in self.items() for j in row) + + def is_diagonal(self): + """ + Says whether this matrix is diagonal. True can be returned + even if the matrix is not square. + """ + return all(i == j for i, row in self.items() for j in row) + + def diagonal(self): + """ + Returns the diagonal of the matrix as a list. + """ + m, n = self.shape + zero = self.domain.zero + return [row.get(i, zero) for i, row in self.items() if i < n] + + def lll(A, delta=QQ(3, 4)): + """ + Returns the LLL-reduced basis for the :py:class:`~.SDM` matrix. + """ + return A.to_dfm_or_ddm().lll(delta=delta).to_sdm() + + def lll_transform(A, delta=QQ(3, 4)): + """ + Returns the LLL-reduced basis and transformation matrix. + """ + reduced, transform = A.to_dfm_or_ddm().lll_transform(delta=delta) + return reduced.to_sdm(), transform.to_sdm() + + +def binop_dict(A, B, fab, fa, fb): + Anz, Bnz = set(A), set(B) + C = {} + + for i in Anz & Bnz: + Ai, Bi = A[i], B[i] + Ci = {} + Anzi, Bnzi = set(Ai), set(Bi) + for j in Anzi & Bnzi: + Cij = fab(Ai[j], Bi[j]) + if Cij: + Ci[j] = Cij + for j in Anzi - Bnzi: + Cij = fa(Ai[j]) + if Cij: + Ci[j] = Cij + for j in Bnzi - Anzi: + Cij = fb(Bi[j]) + if Cij: + Ci[j] = Cij + if Ci: + C[i] = Ci + + for i in Anz - Bnz: + Ai = A[i] + Ci = {} + for j, Aij in Ai.items(): + Cij = fa(Aij) + if Cij: + Ci[j] = Cij + if Ci: + C[i] = Ci + + for i in Bnz - Anz: + Bi = B[i] + Ci = {} + for j, Bij in Bi.items(): + Cij = fb(Bij) + if Cij: + Ci[j] = Cij + if Ci: + C[i] = Ci + + return C + + +def unop_dict(A, f): + B = {} + for i, Ai in A.items(): + Bi = {} + for j, Aij in Ai.items(): + Bij = f(Aij) + if Bij: + Bi[j] = Bij + if Bi: + B[i] = Bi + return B + + +def sdm_transpose(M): + MT = {} + for i, Mi in M.items(): + for j, Mij in Mi.items(): + try: + MT[j][i] = Mij + except KeyError: + MT[j] = {i: Mij} + return MT + + +def sdm_dotvec(A, B, K): + return K.sum(A[j] * B[j] for j in A.keys() & B.keys()) + + +def sdm_matvecmul(A, B, K): + C = {} + for i, Ai in A.items(): + Ci = sdm_dotvec(Ai, B, K) + if Ci: + C[i] = Ci + return C + + +def sdm_matmul(A, B, K, m, o): + # + # Should be fast if A and B are very sparse. + # Consider e.g. A = B = eye(1000). + # + # The idea here is that we compute C = A*B in terms of the rows of C and + # B since the dict of dicts representation naturally stores the matrix as + # rows. The ith row of C (Ci) is equal to the sum of Aik * Bk where Bk is + # the kth row of B. The algorithm below loops over each nonzero element + # Aik of A and if the corresponding row Bj is nonzero then we do + # Ci += Aik * Bk. + # To make this more efficient we don't need to loop over all elements Aik. + # Instead for each row Ai we compute the intersection of the nonzero + # columns in Ai with the nonzero rows in B. That gives the k such that + # Aik and Bk are both nonzero. In Python the intersection of two sets + # of int can be computed very efficiently. + # + if K.is_EXRAW: + return sdm_matmul_exraw(A, B, K, m, o) + + C = {} + B_knz = set(B) + for i, Ai in A.items(): + Ci = {} + Ai_knz = set(Ai) + for k in Ai_knz & B_knz: + Aik = Ai[k] + for j, Bkj in B[k].items(): + Cij = Ci.get(j, None) + if Cij is not None: + Cij = Cij + Aik * Bkj + if Cij: + Ci[j] = Cij + else: + Ci.pop(j) + else: + Cij = Aik * Bkj + if Cij: + Ci[j] = Cij + if Ci: + C[i] = Ci + return C + + +def sdm_matmul_exraw(A, B, K, m, o): + # + # Like sdm_matmul above except that: + # + # - Handles cases like 0*oo -> nan (sdm_matmul skips multiplication by zero) + # - Uses K.sum (Add(*items)) for efficient addition of Expr + # + zero = K.zero + C = {} + B_knz = set(B) + for i, Ai in A.items(): + Ci_list = defaultdict(list) + Ai_knz = set(Ai) + + # Nonzero row/column pair + for k in Ai_knz & B_knz: + Aik = Ai[k] + if zero * Aik == zero: + # This is the main inner loop: + for j, Bkj in B[k].items(): + Ci_list[j].append(Aik * Bkj) + else: + for j in range(o): + Ci_list[j].append(Aik * B[k].get(j, zero)) + + # Zero row in B, check for infinities in A + for k in Ai_knz - B_knz: + zAik = zero * Ai[k] + if zAik != zero: + for j in range(o): + Ci_list[j].append(zAik) + + # Add terms using K.sum (Add(*terms)) for efficiency + Ci = {} + for j, Cij_list in Ci_list.items(): + Cij = K.sum(Cij_list) + if Cij: + Ci[j] = Cij + if Ci: + C[i] = Ci + + # Find all infinities in B + for k, Bk in B.items(): + for j, Bkj in Bk.items(): + if zero * Bkj != zero: + for i in range(m): + Aik = A.get(i, {}).get(k, zero) + # If Aik is not zero then this was handled above + if Aik == zero: + Ci = C.get(i, {}) + Cij = Ci.get(j, zero) + Aik * Bkj + if Cij != zero: + Ci[j] = Cij + C[i] = Ci + else: + Ci.pop(j, None) + if Ci: + C[i] = Ci + else: + C.pop(i, None) + + return C + + +def sdm_irref(A): + """RREF and pivots of a sparse matrix *A*. + + Compute the reduced row echelon form (RREF) of the matrix *A* and return a + list of the pivot columns. This routine does not work in place and leaves + the original matrix *A* unmodified. + + The domain of the matrix must be a field. + + Examples + ======== + + This routine works with a dict of dicts sparse representation of a matrix: + + >>> from sympy import QQ + >>> from sympy.polys.matrices.sdm import sdm_irref + >>> A = {0: {0: QQ(1), 1: QQ(2)}, 1: {0: QQ(3), 1: QQ(4)}} + >>> Arref, pivots, _ = sdm_irref(A) + >>> Arref + {0: {0: 1}, 1: {1: 1}} + >>> pivots + [0, 1] + + The analogous calculation with :py:class:`~.MutableDenseMatrix` would be + + >>> from sympy import Matrix + >>> M = Matrix([[1, 2], [3, 4]]) + >>> Mrref, pivots = M.rref() + >>> Mrref + Matrix([ + [1, 0], + [0, 1]]) + >>> pivots + (0, 1) + + Notes + ===== + + The cost of this algorithm is determined purely by the nonzero elements of + the matrix. No part of the cost of any step in this algorithm depends on + the number of rows or columns in the matrix. No step depends even on the + number of nonzero rows apart from the primary loop over those rows. The + implementation is much faster than ddm_rref for sparse matrices. In fact + at the time of writing it is also (slightly) faster than the dense + implementation even if the input is a fully dense matrix so it seems to be + faster in all cases. + + The elements of the matrix should support exact division with ``/``. For + example elements of any domain that is a field (e.g. ``QQ``) should be + fine. No attempt is made to handle inexact arithmetic. + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.rref + The higher-level function that would normally be used to call this + routine. + sympy.polys.matrices.dense.ddm_irref + The dense equivalent of this routine. + sdm_rref_den + Fraction-free version of this routine. + """ + # + # Any zeros in the matrix are not stored at all so an element is zero if + # its row dict has no index at that key. A row is entirely zero if its + # row index is not in the outer dict. Since rref reorders the rows and + # removes zero rows we can completely discard the row indices. The first + # step then copies the row dicts into a list sorted by the index of the + # first nonzero column in each row. + # + # The algorithm then processes each row Ai one at a time. Previously seen + # rows are used to cancel their pivot columns from Ai. Then a pivot from + # Ai is chosen and is cancelled from all previously seen rows. At this + # point Ai joins the previously seen rows. Once all rows are seen all + # elimination has occurred and the rows are sorted by pivot column index. + # + # The previously seen rows are stored in two separate groups. The reduced + # group consists of all rows that have been reduced to a single nonzero + # element (the pivot). There is no need to attempt any further reduction + # with these. Rows that still have other nonzeros need to be considered + # when Ai is cancelled from the previously seen rows. + # + # A dict nonzerocolumns is used to map from a column index to a set of + # previously seen rows that still have a nonzero element in that column. + # This means that we can cancel the pivot from Ai into the previously seen + # rows without needing to loop over each row that might have a zero in + # that column. + # + + # Row dicts sorted by index of first nonzero column + # (Maybe sorting is not needed/useful.) + Arows = sorted((Ai.copy() for Ai in A.values()), key=min) + + # Each processed row has an associated pivot column. + # pivot_row_map maps from the pivot column index to the row dict. + # This means that we can represent a set of rows purely as a set of their + # pivot indices. + pivot_row_map = {} + + # Set of pivot indices for rows that are fully reduced to a single nonzero. + reduced_pivots = set() + + # Set of pivot indices for rows not fully reduced + nonreduced_pivots = set() + + # Map from column index to a set of pivot indices representing the rows + # that have a nonzero at that column. + nonzero_columns = defaultdict(set) + + while Arows: + # Select pivot element and row + Ai = Arows.pop() + + # Nonzero columns from fully reduced pivot rows can be removed + Ai = {j: Aij for j, Aij in Ai.items() if j not in reduced_pivots} + + # Others require full row cancellation + for j in nonreduced_pivots & set(Ai): + Aj = pivot_row_map[j] + Aij = Ai[j] + Ainz = set(Ai) + Ajnz = set(Aj) + for k in Ajnz - Ainz: + Ai[k] = - Aij * Aj[k] + Ai.pop(j) + Ainz.remove(j) + for k in Ajnz & Ainz: + Aik = Ai[k] - Aij * Aj[k] + if Aik: + Ai[k] = Aik + else: + Ai.pop(k) + + # We have now cancelled previously seen pivots from Ai. + # If it is zero then discard it. + if not Ai: + continue + + # Choose a pivot from Ai: + j = min(Ai) + Aij = Ai[j] + pivot_row_map[j] = Ai + Ainz = set(Ai) + + # Normalise the pivot row to make the pivot 1. + # + # This approach is slow for some domains. Cross cancellation might be + # better for e.g. QQ(x) with division delayed to the final steps. + Aijinv = Aij**-1 + for l in Ai: + Ai[l] *= Aijinv + + # Use Aij to cancel column j from all previously seen rows + for k in nonzero_columns.pop(j, ()): + Ak = pivot_row_map[k] + Akj = Ak[j] + Aknz = set(Ak) + for l in Ainz - Aknz: + Ak[l] = - Akj * Ai[l] + nonzero_columns[l].add(k) + Ak.pop(j) + Aknz.remove(j) + for l in Ainz & Aknz: + Akl = Ak[l] - Akj * Ai[l] + if Akl: + Ak[l] = Akl + else: + # Drop nonzero elements + Ak.pop(l) + if l != j: + nonzero_columns[l].remove(k) + if len(Ak) == 1: + reduced_pivots.add(k) + nonreduced_pivots.remove(k) + + if len(Ai) == 1: + reduced_pivots.add(j) + else: + nonreduced_pivots.add(j) + for l in Ai: + if l != j: + nonzero_columns[l].add(j) + + # All done! + pivots = sorted(reduced_pivots | nonreduced_pivots) + pivot2row = {p: n for n, p in enumerate(pivots)} + nonzero_columns = {c: {pivot2row[p] for p in s} for c, s in nonzero_columns.items()} + rows = [pivot_row_map[i] for i in pivots] + rref = dict(enumerate(rows)) + return rref, pivots, nonzero_columns + + +def sdm_rref_den(A, K): + """ + Return the reduced row echelon form (RREF) of A with denominator. + + The RREF is computed using fraction-free Gauss-Jordan elimination. + + Explanation + =========== + + The algorithm used is the fraction-free version of Gauss-Jordan elimination + described as FFGJ in [1]_. Here it is modified to handle zero or missing + pivots and to avoid redundant arithmetic. This implementation is also + optimized for sparse matrices. + + The domain $K$ must support exact division (``K.exquo``) but does not need + to be a field. This method is suitable for most exact rings and fields like + :ref:`ZZ`, :ref:`QQ` and :ref:`QQ(a)`. In the case of :ref:`QQ` or + :ref:`K(x)` it might be more efficient to clear denominators and use + :ref:`ZZ` or :ref:`K[x]` instead. + + For inexact domains like :ref:`RR` and :ref:`CC` use ``ddm_irref`` instead. + + Examples + ======== + + >>> from sympy.polys.matrices.sdm import sdm_rref_den + >>> from sympy.polys.domains import ZZ + >>> A = {0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}} + >>> A_rref, den, pivots = sdm_rref_den(A, ZZ) + >>> A_rref + {0: {0: -2}, 1: {1: -2}} + >>> den + -2 + >>> pivots + [0, 1] + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den + Higher-level interface to ``sdm_rref_den`` that would usually be used + instead of calling this function directly. + sympy.polys.matrices.sdm.sdm_rref_den + The ``SDM`` method that uses this function. + sdm_irref + Computes RREF using field division. + ddm_irref_den + The dense version of this algorithm. + + References + ========== + + .. [1] Fraction-free algorithms for linear and polynomial equations. + George C. Nakos , Peter R. Turner , Robert M. Williams. + https://dl.acm.org/doi/10.1145/271130.271133 + """ + # + # We represent each row of the matrix as a dict mapping column indices to + # nonzero elements. We will build the RREF matrix starting from an empty + # matrix and appending one row at a time. At each step we will have the + # RREF of the rows we have processed so far. + # + # Our representation of the RREF divides it into three parts: + # + # 1. Fully reduced rows having only a single nonzero element (the pivot). + # 2. Partially reduced rows having nonzeros after the pivot. + # 3. The current denominator and divisor. + # + # For example if the incremental RREF might be: + # + # [2, 0, 0, 0, 0, 0, 0, 0, 0, 0] + # [0, 0, 2, 0, 0, 0, 7, 0, 0, 0] + # [0, 0, 0, 0, 0, 2, 0, 0, 0, 0] + # [0, 0, 0, 0, 0, 0, 0, 2, 0, 0] + # [0, 0, 0, 0, 0, 0, 0, 0, 2, 0] + # + # Here the second row is partially reduced and the other rows are fully + # reduced. The denominator would be 2 in this case. We distinguish the + # fully reduced rows because we can handle them more efficiently when + # adding a new row. + # + # When adding a new row we need to multiply it by the current denominator. + # Then we reduce the new row by cross cancellation with the previous rows. + # Then if it is not reduced to zero we take its leading entry as the new + # pivot, cross cancel the new row from the previous rows and update the + # denominator. In the fraction-free version this last step requires + # multiplying and dividing the whole matrix by the new pivot and the + # current divisor. The advantage of building the RREF one row at a time is + # that in the sparse case we only need to work with the relatively sparse + # upper rows of the matrix. The simple version of FFGJ in [1] would + # multiply and divide all the dense lower rows at each step. + + # Handle the trivial cases. + if not A: + return ({}, K.one, []) + elif len(A) == 1: + Ai, = A.values() + j = min(Ai) + Aij = Ai[j] + return ({0: Ai.copy()}, Aij, [j]) + + # For inexact domains like RR[x] we use quo and discard the remainder. + # Maybe it would be better for K.exquo to do this automatically. + if K.is_Exact: + exquo = K.exquo + else: + exquo = K.quo + + # Make sure we have the rows in order to make this deterministic from the + # outset. + _, rows_in_order = zip(*sorted(A.items())) + + col_to_row_reduced = {} + col_to_row_unreduced = {} + reduced = col_to_row_reduced.keys() + unreduced = col_to_row_unreduced.keys() + + # Our representation of the RREF so far. + A_rref_rows = [] + denom = None + divisor = None + + # The rows that remain to be added to the RREF. These are sorted by the + # column index of their leading entry. Note that sorted() is stable so the + # previous sort by unique row index is still needed to make this + # deterministic (there may be multiple rows with the same leading column). + A_rows = sorted(rows_in_order, key=min) + + for Ai in A_rows: + + # All fully reduced columns can be immediately discarded. + Ai = {j: Aij for j, Aij in Ai.items() if j not in reduced} + + # We need to multiply the new row by the current denominator to bring + # it into the same scale as the previous rows and then cross-cancel to + # reduce it wrt the previous unreduced rows. All pivots in the previous + # rows are equal to denom so the coefficients we need to make a linear + # combination of the previous rows to cancel into the new row are just + # the ones that are already in the new row *before* we multiply by + # denom. We compute that linear combination first and then multiply the + # new row by denom before subtraction. + Ai_cancel = {} + + for j in unreduced & Ai.keys(): + # Remove the pivot column from the new row since it would become + # zero anyway. + Aij = Ai.pop(j) + + Aj = A_rref_rows[col_to_row_unreduced[j]] + + for k, Ajk in Aj.items(): + Aik_cancel = Ai_cancel.get(k) + if Aik_cancel is None: + Ai_cancel[k] = Aij * Ajk + else: + Aik_cancel = Aik_cancel + Aij * Ajk + if Aik_cancel: + Ai_cancel[k] = Aik_cancel + else: + Ai_cancel.pop(k) + + # Multiply the new row by the current denominator and subtract. + Ai_nz = set(Ai) + Ai_cancel_nz = set(Ai_cancel) + + d = denom or K.one + + for k in Ai_cancel_nz - Ai_nz: + Ai[k] = -Ai_cancel[k] + + for k in Ai_nz - Ai_cancel_nz: + Ai[k] = Ai[k] * d + + for k in Ai_cancel_nz & Ai_nz: + Aik = Ai[k] * d - Ai_cancel[k] + if Aik: + Ai[k] = Aik + else: + Ai.pop(k) + + # Now Ai has the same scale as the other rows and is reduced wrt the + # unreduced rows. + + # If the row is reduced to zero then discard it. + if not Ai: + continue + + # Choose a pivot for this row. + j = min(Ai) + Aij = Ai.pop(j) + + # Cross cancel the unreduced rows by the new row. + # a[k][l] = (a[i][j]*a[k][l] - a[k][j]*a[i][l]) / divisor + for pk, k in list(col_to_row_unreduced.items()): + + Ak = A_rref_rows[k] + + if j not in Ak: + # This row is already reduced wrt the new row but we need to + # bring it to the same scale as the new denominator. This step + # is not needed in sdm_irref. + for l, Akl in Ak.items(): + Akl = Akl * Aij + if divisor is not None: + Akl = exquo(Akl, divisor) + Ak[l] = Akl + continue + + Akj = Ak.pop(j) + Ai_nz = set(Ai) + Ak_nz = set(Ak) + + for l in Ai_nz - Ak_nz: + Ak[l] = - Akj * Ai[l] + if divisor is not None: + Ak[l] = exquo(Ak[l], divisor) + + # This loop also not needed in sdm_irref. + for l in Ak_nz - Ai_nz: + Ak[l] = Aij * Ak[l] + if divisor is not None: + Ak[l] = exquo(Ak[l], divisor) + + for l in Ai_nz & Ak_nz: + Akl = Aij * Ak[l] - Akj * Ai[l] + if Akl: + if divisor is not None: + Akl = exquo(Akl, divisor) + Ak[l] = Akl + else: + Ak.pop(l) + + if not Ak: + col_to_row_unreduced.pop(pk) + col_to_row_reduced[pk] = k + + i = len(A_rref_rows) + A_rref_rows.append(Ai) + if Ai: + col_to_row_unreduced[j] = i + else: + col_to_row_reduced[j] = i + + # Update the denominator. + if not K.is_one(Aij): + if denom is None: + denom = Aij + else: + denom *= Aij + + if divisor is not None: + denom = exquo(denom, divisor) + + # Update the divisor. + divisor = denom + + if denom is None: + denom = K.one + + # Sort the rows by their leading column index. + col_to_row = {**col_to_row_reduced, **col_to_row_unreduced} + row_to_col = {i: j for j, i in col_to_row.items()} + A_rref_rows_col = [(row_to_col[i], Ai) for i, Ai in enumerate(A_rref_rows)] + pivots, A_rref = zip(*sorted(A_rref_rows_col)) + pivots = list(pivots) + + # Insert the pivot values + for i, Ai in enumerate(A_rref): + Ai[pivots[i]] = denom + + A_rref_sdm = dict(enumerate(A_rref)) + + return A_rref_sdm, denom, pivots + + +def sdm_nullspace_from_rref(A, one, ncols, pivots, nonzero_cols): + """Get nullspace from A which is in RREF""" + nonpivots = sorted(set(range(ncols)) - set(pivots)) + + K = [] + for j in nonpivots: + Kj = {j:one} + for i in nonzero_cols.get(j, ()): + Kj[pivots[i]] = -A[i][j] + K.append(Kj) + + return K, nonpivots + + +def sdm_particular_from_rref(A, ncols, pivots): + """Get a particular solution from A which is in RREF""" + P = {} + for i, j in enumerate(pivots): + Ain = A[i].get(ncols-1, None) + if Ain is not None: + P[j] = Ain / A[i][j] + return P + + +def sdm_berk(M, n, K): + """ + Berkowitz algorithm for computing the characteristic polynomial. + + Explanation + =========== + + The Berkowitz algorithm is a division-free algorithm for computing the + characteristic polynomial of a matrix over any commutative ring using only + arithmetic in the coefficient ring. This implementation is for sparse + matrices represented in a dict-of-dicts format (like :class:`SDM`). + + Examples + ======== + + >>> from sympy import Matrix + >>> from sympy.polys.matrices.sdm import sdm_berk + >>> from sympy.polys.domains import ZZ + >>> M = {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}} + >>> sdm_berk(M, 2, ZZ) + {0: 1, 1: -5, 2: -2} + >>> Matrix([[1, 2], [3, 4]]).charpoly() + PurePoly(lambda**2 - 5*lambda - 2, lambda, domain='ZZ') + + See Also + ======== + + sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly + The high-level interface to this function. + sympy.polys.matrices.dense.ddm_berk + The dense version of this function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Samuelson%E2%80%93Berkowitz_algorithm + """ + zero = K.zero + one = K.one + + if n == 0: + return {0: one} + elif n == 1: + pdict = {0: one} + if M00 := M.get(0, {}).get(0, zero): + pdict[1] = -M00 + + # M = [[a, R], + # [C, A]] + a, R, C, A = K.zero, {}, {}, defaultdict(dict) + for i, Mi in M.items(): + for j, Mij in Mi.items(): + if i and j: + A[i-1][j-1] = Mij + elif i: + C[i-1] = Mij + elif j: + R[j-1] = Mij + else: + a = Mij + + # T = [ 1, 0, 0, 0, 0, ... ] + # [ -a, 1, 0, 0, 0, ... ] + # [ -R*C, -a, 1, 0, 0, ... ] + # [ -R*A*C, -R*C, -a, 1, 0, ... ] + # [-R*A^2*C, -R*A*C, -R*C, -a, 1, ... ] + # [ ... ] + # T is (n+1) x n + # + # In the sparse case we might have A^m*C = 0 for some m making T banded + # rather than triangular so we just compute the nonzero entries of the + # first column rather than constructing the matrix explicitly. + + AnC = C + RC = sdm_dotvec(R, C, K) + + Tvals = [one, -a, -RC] + for i in range(3, n+1): + AnC = sdm_matvecmul(A, AnC, K) + if not AnC: + break + RAnC = sdm_dotvec(R, AnC, K) + Tvals.append(-RAnC) + + # Strip trailing zeros + while Tvals and not Tvals[-1]: + Tvals.pop() + + q = sdm_berk(A, n-1, K) + + # This would be the explicit multiplication T*q but we can do better: + # + # T = {} + # for i in range(n+1): + # Ti = {} + # for j in range(max(0, i-len(Tvals)+1), min(i+1, n)): + # Ti[j] = Tvals[i-j] + # T[i] = Ti + # Tq = sdm_matvecmul(T, q, K) + # + # In the sparse case q might be mostly zero. We know that T[i,j] is nonzero + # for i <= j < i + len(Tvals) so if q does not have a nonzero entry in that + # range then Tq[j] must be zero. We exploit this potential banded + # structure and the potential sparsity of q to compute Tq more efficiently. + + Tvals = Tvals[::-1] + + Tq = {} + + for i in range(min(q), min(max(q)+len(Tvals), n+1)): + Ti = dict(enumerate(Tvals, i-len(Tvals)+1)) + if Tqi := sdm_dotvec(Ti, q, K): + Tq[i] = Tqi + + return Tq diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04bec2031c35e911d700e38c070244d1b29ba2e1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_ddm.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_ddm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f4137ebb94eeaf8451d8e4d50b5fba7d74a047d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_ddm.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_dense.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_dense.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c153b01d175b5c9493314e890eec1764ddf45bd0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_dense.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_domainmatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_domainmatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80ae8a7475693d4251ca244241839ac03d7d0ab8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_domainmatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_domainscalar.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_domainscalar.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad5f98b1392a6de534e69615ec901a174e675d4a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_domainscalar.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_eigen.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_eigen.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..073d72cd0d07409a239bc5fbf529755cb185a771 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_eigen.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_fflu.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_fflu.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5903b0cc9958075960a0f6c88bd5e75ef092faa4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_fflu.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_inverse.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_inverse.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29f41652d013803cd4c9ff1b20cfdca35096ea10 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_inverse.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_linsolve.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_linsolve.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cffe2a9a3241e70d2075b37ffa470f9fd60fa0d2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_linsolve.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_lll.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_lll.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cbdd1676ef036bb64279b581c265ccb18cc7e35 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_lll.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_normalforms.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_normalforms.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7e66f9c65099232548fa8850b446157005b8b9c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_normalforms.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_nullspace.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_nullspace.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc9bd9b843d54065d99265d469320d791faa89e0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_nullspace.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_rref.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_rref.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13ee6257bd6efe707c1f43547b64b696fc671cff Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_rref.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_sdm.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_sdm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e109d447c342199bb0e5b98420ae718ee4b486b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_sdm.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_xxm.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_xxm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efdad86041ae2da532be12e8c6aafa9e2aa01195 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/__pycache__/test_xxm.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_ddm.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_ddm.py new file mode 100644 index 0000000000000000000000000000000000000000..44c862461e85d503696e621874c10d67d8ee1f1d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_ddm.py @@ -0,0 +1,558 @@ +from sympy.testing.pytest import raises +from sympy.external.gmpy import GROUND_TYPES + +from sympy.polys import ZZ, QQ + +from sympy.polys.matrices.ddm import DDM +from sympy.polys.matrices.exceptions import ( + DMShapeError, DMNonInvertibleMatrixError, DMDomainError, + DMBadInputError) + + +def test_DDM_init(): + items = [[ZZ(0), ZZ(1), ZZ(2)], [ZZ(3), ZZ(4), ZZ(5)]] + shape = (2, 3) + ddm = DDM(items, shape, ZZ) + assert ddm.shape == shape + assert ddm.rows == 2 + assert ddm.cols == 3 + assert ddm.domain == ZZ + + raises(DMBadInputError, lambda: DDM([[ZZ(2), ZZ(3)]], (2, 2), ZZ)) + raises(DMBadInputError, lambda: DDM([[ZZ(1)], [ZZ(2), ZZ(3)]], (2, 2), ZZ)) + + +def test_DDM_getsetitem(): + ddm = DDM([[ZZ(2), ZZ(3)], [ZZ(4), ZZ(5)]], (2, 2), ZZ) + + assert ddm[0][0] == ZZ(2) + assert ddm[0][1] == ZZ(3) + assert ddm[1][0] == ZZ(4) + assert ddm[1][1] == ZZ(5) + + raises(IndexError, lambda: ddm[2][0]) + raises(IndexError, lambda: ddm[0][2]) + + ddm[0][0] = ZZ(-1) + assert ddm[0][0] == ZZ(-1) + + +def test_DDM_str(): + ddm = DDM([[ZZ(0), ZZ(1)], [ZZ(2), ZZ(3)]], (2, 2), ZZ) + if GROUND_TYPES == 'gmpy': # pragma: no cover + assert str(ddm) == '[[0, 1], [2, 3]]' + assert repr(ddm) == 'DDM([[mpz(0), mpz(1)], [mpz(2), mpz(3)]], (2, 2), ZZ)' + else: # pragma: no cover + assert repr(ddm) == 'DDM([[0, 1], [2, 3]], (2, 2), ZZ)' + assert str(ddm) == '[[0, 1], [2, 3]]' + + +def test_DDM_eq(): + items = [[ZZ(0), ZZ(1)], [ZZ(2), ZZ(3)]] + ddm1 = DDM(items, (2, 2), ZZ) + ddm2 = DDM(items, (2, 2), ZZ) + + assert (ddm1 == ddm1) is True + assert (ddm1 == items) is False + assert (items == ddm1) is False + assert (ddm1 == ddm2) is True + assert (ddm2 == ddm1) is True + + assert (ddm1 != ddm1) is False + assert (ddm1 != items) is True + assert (items != ddm1) is True + assert (ddm1 != ddm2) is False + assert (ddm2 != ddm1) is False + + ddm3 = DDM([[ZZ(0), ZZ(1)], [ZZ(3), ZZ(3)]], (2, 2), ZZ) + ddm3 = DDM(items, (2, 2), QQ) + + assert (ddm1 == ddm3) is False + assert (ddm3 == ddm1) is False + assert (ddm1 != ddm3) is True + assert (ddm3 != ddm1) is True + + +def test_DDM_convert_to(): + ddm = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + assert ddm.convert_to(ZZ) == ddm + ddmq = ddm.convert_to(QQ) + assert ddmq.domain == QQ + + +def test_DDM_zeros(): + ddmz = DDM.zeros((3, 4), QQ) + assert list(ddmz) == [[QQ(0)] * 4] * 3 + assert ddmz.shape == (3, 4) + assert ddmz.domain == QQ + +def test_DDM_ones(): + ddmone = DDM.ones((2, 3), QQ) + assert list(ddmone) == [[QQ(1)] * 3] * 2 + assert ddmone.shape == (2, 3) + assert ddmone.domain == QQ + +def test_DDM_eye(): + ddmz = DDM.eye(3, QQ) + f = lambda i, j: QQ(1) if i == j else QQ(0) + assert list(ddmz) == [[f(i, j) for i in range(3)] for j in range(3)] + assert ddmz.shape == (3, 3) + assert ddmz.domain == QQ + + +def test_DDM_copy(): + ddm1 = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + ddm2 = ddm1.copy() + assert (ddm1 == ddm2) is True + ddm1[0][0] = QQ(-1) + assert (ddm1 == ddm2) is False + ddm2[0][0] = QQ(-1) + assert (ddm1 == ddm2) is True + + +def test_DDM_transpose(): + ddm = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + ddmT = DDM([[QQ(1), QQ(2)]], (1, 2), QQ) + assert ddm.transpose() == ddmT + ddm02 = DDM([], (0, 2), QQ) + ddm02T = DDM([[], []], (2, 0), QQ) + assert ddm02.transpose() == ddm02T + assert ddm02T.transpose() == ddm02 + ddm0 = DDM([], (0, 0), QQ) + assert ddm0.transpose() == ddm0 + + +def test_DDM_add(): + A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + B = DDM([[ZZ(3)], [ZZ(4)]], (2, 1), ZZ) + C = DDM([[ZZ(4)], [ZZ(6)]], (2, 1), ZZ) + AQ = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + assert A + B == A.add(B) == C + + raises(DMShapeError, lambda: A + DDM([[ZZ(5)]], (1, 1), ZZ)) + raises(TypeError, lambda: A + ZZ(1)) + raises(TypeError, lambda: ZZ(1) + A) + raises(DMDomainError, lambda: A + AQ) + raises(DMDomainError, lambda: AQ + A) + + +def test_DDM_sub(): + A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + B = DDM([[ZZ(3)], [ZZ(4)]], (2, 1), ZZ) + C = DDM([[ZZ(-2)], [ZZ(-2)]], (2, 1), ZZ) + AQ = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + D = DDM([[ZZ(5)]], (1, 1), ZZ) + assert A - B == A.sub(B) == C + + raises(TypeError, lambda: A - ZZ(1)) + raises(TypeError, lambda: ZZ(1) - A) + raises(DMShapeError, lambda: A - D) + raises(DMShapeError, lambda: D - A) + raises(DMShapeError, lambda: A.sub(D)) + raises(DMShapeError, lambda: D.sub(A)) + raises(DMDomainError, lambda: A - AQ) + raises(DMDomainError, lambda: AQ - A) + raises(DMDomainError, lambda: A.sub(AQ)) + raises(DMDomainError, lambda: AQ.sub(A)) + + +def test_DDM_neg(): + A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + An = DDM([[ZZ(-1)], [ZZ(-2)]], (2, 1), ZZ) + assert -A == A.neg() == An + assert -An == An.neg() == A + + +def test_DDM_mul(): + A = DDM([[ZZ(1)]], (1, 1), ZZ) + A2 = DDM([[ZZ(2)]], (1, 1), ZZ) + assert A * ZZ(2) == A2 + assert ZZ(2) * A == A2 + raises(TypeError, lambda: [[1]] * A) + raises(TypeError, lambda: A * [[1]]) + + +def test_DDM_matmul(): + A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + B = DDM([[ZZ(3), ZZ(4)]], (1, 2), ZZ) + AB = DDM([[ZZ(3), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ) + BA = DDM([[ZZ(11)]], (1, 1), ZZ) + + assert A @ B == A.matmul(B) == AB + assert B @ A == B.matmul(A) == BA + + raises(TypeError, lambda: A @ 1) + raises(TypeError, lambda: A @ [[3, 4]]) + + Bq = DDM([[QQ(3), QQ(4)]], (1, 2), QQ) + + raises(DMDomainError, lambda: A @ Bq) + raises(DMDomainError, lambda: Bq @ A) + + C = DDM([[ZZ(1)]], (1, 1), ZZ) + + assert A @ C == A.matmul(C) == A + + raises(DMShapeError, lambda: C @ A) + raises(DMShapeError, lambda: C.matmul(A)) + + Z04 = DDM([], (0, 4), ZZ) + Z40 = DDM([[]]*4, (4, 0), ZZ) + Z50 = DDM([[]]*5, (5, 0), ZZ) + Z05 = DDM([], (0, 5), ZZ) + Z45 = DDM([[0] * 5] * 4, (4, 5), ZZ) + Z54 = DDM([[0] * 4] * 5, (5, 4), ZZ) + Z00 = DDM([], (0, 0), ZZ) + + assert Z04 @ Z45 == Z04.matmul(Z45) == Z05 + assert Z45 @ Z50 == Z45.matmul(Z50) == Z40 + assert Z00 @ Z04 == Z00.matmul(Z04) == Z04 + assert Z50 @ Z00 == Z50.matmul(Z00) == Z50 + assert Z00 @ Z00 == Z00.matmul(Z00) == Z00 + assert Z50 @ Z04 == Z50.matmul(Z04) == Z54 + + raises(DMShapeError, lambda: Z05 @ Z40) + raises(DMShapeError, lambda: Z05.matmul(Z40)) + + +def test_DDM_hstack(): + A = DDM([[ZZ(1), ZZ(2), ZZ(3)]], (1, 3), ZZ) + B = DDM([[ZZ(4), ZZ(5)]], (1, 2), ZZ) + C = DDM([[ZZ(6)]], (1, 1), ZZ) + + Ah = A.hstack(B) + assert Ah.shape == (1, 5) + assert Ah.domain == ZZ + assert Ah == DDM([[ZZ(1), ZZ(2), ZZ(3), ZZ(4), ZZ(5)]], (1, 5), ZZ) + + Ah = A.hstack(B, C) + assert Ah.shape == (1, 6) + assert Ah.domain == ZZ + assert Ah == DDM([[ZZ(1), ZZ(2), ZZ(3), ZZ(4), ZZ(5), ZZ(6)]], (1, 6), ZZ) + + +def test_DDM_vstack(): + A = DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)]], (3, 1), ZZ) + B = DDM([[ZZ(4)], [ZZ(5)]], (2, 1), ZZ) + C = DDM([[ZZ(6)]], (1, 1), ZZ) + + Ah = A.vstack(B) + assert Ah.shape == (5, 1) + assert Ah.domain == ZZ + assert Ah == DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)], [ZZ(4)], [ZZ(5)]], (5, 1), ZZ) + + Ah = A.vstack(B, C) + assert Ah.shape == (6, 1) + assert Ah.domain == ZZ + assert Ah == DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)], [ZZ(4)], [ZZ(5)], [ZZ(6)]], (6, 1), ZZ) + + +def test_DDM_applyfunc(): + A = DDM([[ZZ(1), ZZ(2), ZZ(3)]], (1, 3), ZZ) + B = DDM([[ZZ(2), ZZ(4), ZZ(6)]], (1, 3), ZZ) + assert A.applyfunc(lambda x: 2*x, ZZ) == B + +def test_DDM_rref(): + + A = DDM([], (0, 4), QQ) + assert A.rref() == (A, []) + + A = DDM([[QQ(0), QQ(1)], [QQ(1), QQ(1)]], (2, 2), QQ) + Ar = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ) + pivots = [0, 1] + assert A.rref() == (Ar, pivots) + + A = DDM([[QQ(1), QQ(2), QQ(1)], [QQ(3), QQ(4), QQ(1)]], (2, 3), QQ) + Ar = DDM([[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]], (2, 3), QQ) + pivots = [0, 1] + assert A.rref() == (Ar, pivots) + + A = DDM([[QQ(3), QQ(4), QQ(1)], [QQ(1), QQ(2), QQ(1)]], (2, 3), QQ) + Ar = DDM([[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]], (2, 3), QQ) + pivots = [0, 1] + assert A.rref() == (Ar, pivots) + + A = DDM([[QQ(1), QQ(0)], [QQ(1), QQ(3)], [QQ(0), QQ(1)]], (3, 2), QQ) + Ar = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]], (3, 2), QQ) + pivots = [0, 1] + assert A.rref() == (Ar, pivots) + + A = DDM([[QQ(1), QQ(0), QQ(1)], [QQ(3), QQ(0), QQ(1)]], (2, 3), QQ) + Ar = DDM([[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(0), QQ(1)]], (2, 3), QQ) + pivots = [0, 2] + assert A.rref() == (Ar, pivots) + + +def test_DDM_nullspace(): + # more tests are in test_nullspace.py + A = DDM([[QQ(1), QQ(1)], [QQ(1), QQ(1)]], (2, 2), QQ) + Anull = DDM([[QQ(-1), QQ(1)]], (1, 2), QQ) + nonpivots = [1] + assert A.nullspace() == (Anull, nonpivots) + + +def test_DDM_particular(): + A = DDM([[QQ(1), QQ(0)]], (1, 2), QQ) + assert A.particular() == DDM.zeros((1, 1), QQ) + + +def test_DDM_det(): + # 0x0 case + A = DDM([], (0, 0), ZZ) + assert A.det() == ZZ(1) + + # 1x1 case + A = DDM([[ZZ(2)]], (1, 1), ZZ) + assert A.det() == ZZ(2) + + # 2x2 case + A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.det() == ZZ(-2) + + # 3x3 with swap + A = DDM([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(2), ZZ(5)]], (3, 3), ZZ) + assert A.det() == ZZ(0) + + # 2x2 QQ case + A = DDM([[QQ(1, 2), QQ(1, 2)], [QQ(1, 3), QQ(1, 4)]], (2, 2), QQ) + assert A.det() == QQ(-1, 24) + + # Nonsquare error + A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + raises(DMShapeError, lambda: A.det()) + + # Nonsquare error with empty matrix + A = DDM([], (0, 1), ZZ) + raises(DMShapeError, lambda: A.det()) + + +def test_DDM_inv(): + A = DDM([[QQ(1, 1), QQ(2, 1)], [QQ(3, 1), QQ(4, 1)]], (2, 2), QQ) + Ainv = DDM([[QQ(-2, 1), QQ(1, 1)], [QQ(3, 2), QQ(-1, 2)]], (2, 2), QQ) + assert A.inv() == Ainv + + A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ) + raises(DMShapeError, lambda: A.inv()) + + A = DDM([[ZZ(2)]], (1, 1), ZZ) + raises(DMDomainError, lambda: A.inv()) + + A = DDM([], (0, 0), QQ) + assert A.inv() == A + + A = DDM([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ) + raises(DMNonInvertibleMatrixError, lambda: A.inv()) + + +def test_DDM_lu(): + A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + L, U, swaps = A.lu() + assert L == DDM([[QQ(1), QQ(0)], [QQ(3), QQ(1)]], (2, 2), QQ) + assert U == DDM([[QQ(1), QQ(2)], [QQ(0), QQ(-2)]], (2, 2), QQ) + assert swaps == [] + + A = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]] + Lexp = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 1, 1]] + Uexp = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 0, 1]] + to_dom = lambda rows, dom: [[dom(e) for e in row] for row in rows] + A = DDM(to_dom(A, QQ), (4, 4), QQ) + Lexp = DDM(to_dom(Lexp, QQ), (4, 4), QQ) + Uexp = DDM(to_dom(Uexp, QQ), (4, 4), QQ) + L, U, swaps = A.lu() + assert L == Lexp + assert U == Uexp + assert swaps == [] + + +def test_DDM_lu_solve(): + # Basic example + A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + x = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ) + assert A.lu_solve(b) == x + + # Example with swaps + A = DDM([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + assert A.lu_solve(b) == x + + # Overdetermined, consistent + A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ) + b = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ) + assert A.lu_solve(b) == x + + # Overdetermined, inconsistent + b = DDM([[QQ(1)], [QQ(2)], [QQ(4)]], (3, 1), QQ) + raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b)) + + # Square, noninvertible + A = DDM([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ) + b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b)) + + # Underdetermined + A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ) + b = DDM([[QQ(3)]], (1, 1), QQ) + raises(NotImplementedError, lambda: A.lu_solve(b)) + + # Domain mismatch + bz = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + raises(DMDomainError, lambda: A.lu_solve(bz)) + + # Shape mismatch + b3 = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ) + raises(DMShapeError, lambda: A.lu_solve(b3)) + + +def test_DDM_charpoly(): + A = DDM([], (0, 0), ZZ) + assert A.charpoly() == [ZZ(1)] + + A = DDM([ + [ZZ(1), ZZ(2), ZZ(3)], + [ZZ(4), ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ) + Avec = [ZZ(1), ZZ(-15), ZZ(-18), ZZ(0)] + assert A.charpoly() == Avec + + A = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMShapeError, lambda: A.charpoly()) + + +def test_DDM_getitem(): + dm = DDM([ + [ZZ(1), ZZ(2), ZZ(3)], + [ZZ(4), ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ) + + assert dm.getitem(1, 1) == ZZ(5) + assert dm.getitem(1, -2) == ZZ(5) + assert dm.getitem(-1, -3) == ZZ(7) + + raises(IndexError, lambda: dm.getitem(3, 3)) + + +def test_DDM_setitem(): + dm = DDM.zeros((3, 3), ZZ) + dm.setitem(0, 0, 1) + dm.setitem(1, -2, 1) + dm.setitem(-1, -1, 1) + assert dm == DDM.eye(3, ZZ) + + raises(IndexError, lambda: dm.setitem(3, 3, 0)) + + +def test_DDM_extract_slice(): + dm = DDM([ + [ZZ(1), ZZ(2), ZZ(3)], + [ZZ(4), ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ) + + assert dm.extract_slice(slice(0, 3), slice(0, 3)) == dm + assert dm.extract_slice(slice(1, 3), slice(-2)) == DDM([[4], [7]], (2, 1), ZZ) + assert dm.extract_slice(slice(1, 3), slice(-2)) == DDM([[4], [7]], (2, 1), ZZ) + assert dm.extract_slice(slice(2, 3), slice(-2)) == DDM([[ZZ(7)]], (1, 1), ZZ) + assert dm.extract_slice(slice(0, 2), slice(-2)) == DDM([[1], [4]], (2, 1), ZZ) + assert dm.extract_slice(slice(-1), slice(-1)) == DDM([[1, 2], [4, 5]], (2, 2), ZZ) + + assert dm.extract_slice(slice(2), slice(3, 4)) == DDM([[], []], (2, 0), ZZ) + assert dm.extract_slice(slice(3, 4), slice(2)) == DDM([], (0, 2), ZZ) + assert dm.extract_slice(slice(3, 4), slice(3, 4)) == DDM([], (0, 0), ZZ) + + +def test_DDM_extract(): + dm1 = DDM([ + [ZZ(1), ZZ(2), ZZ(3)], + [ZZ(4), ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ) + dm2 = DDM([ + [ZZ(6), ZZ(4)], + [ZZ(3), ZZ(1)]], (2, 2), ZZ) + assert dm1.extract([1, 0], [2, 0]) == dm2 + assert dm1.extract([-2, 0], [-1, 0]) == dm2 + + assert dm1.extract([], []) == DDM.zeros((0, 0), ZZ) + assert dm1.extract([1], []) == DDM.zeros((1, 0), ZZ) + assert dm1.extract([], [1]) == DDM.zeros((0, 1), ZZ) + + raises(IndexError, lambda: dm2.extract([2], [0])) + raises(IndexError, lambda: dm2.extract([0], [2])) + raises(IndexError, lambda: dm2.extract([-3], [0])) + raises(IndexError, lambda: dm2.extract([0], [-3])) + + +def test_DDM_flat(): + dm = DDM([ + [ZZ(6), ZZ(4)], + [ZZ(3), ZZ(1)]], (2, 2), ZZ) + assert dm.flat() == [ZZ(6), ZZ(4), ZZ(3), ZZ(1)] + + +def test_DDM_is_zero_matrix(): + A = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(0)]], (2, 2), QQ) + Azero = DDM.zeros((1, 2), QQ) + assert A.is_zero_matrix() is False + assert Azero.is_zero_matrix() is True + + +def test_DDM_is_upper(): + # Wide matrices: + A = DDM([ + [QQ(1), QQ(2), QQ(3), QQ(4)], + [QQ(0), QQ(5), QQ(6), QQ(7)], + [QQ(0), QQ(0), QQ(8), QQ(9)] + ], (3, 4), QQ) + B = DDM([ + [QQ(1), QQ(2), QQ(3), QQ(4)], + [QQ(0), QQ(5), QQ(6), QQ(7)], + [QQ(0), QQ(7), QQ(8), QQ(9)] + ], (3, 4), QQ) + assert A.is_upper() is True + assert B.is_upper() is False + + # Tall matrices: + A = DDM([ + [QQ(1), QQ(2), QQ(3)], + [QQ(0), QQ(5), QQ(6)], + [QQ(0), QQ(0), QQ(8)], + [QQ(0), QQ(0), QQ(0)] + ], (4, 3), QQ) + B = DDM([ + [QQ(1), QQ(2), QQ(3)], + [QQ(0), QQ(5), QQ(6)], + [QQ(0), QQ(0), QQ(8)], + [QQ(0), QQ(0), QQ(10)] + ], (4, 3), QQ) + assert A.is_upper() is True + assert B.is_upper() is False + + +def test_DDM_is_lower(): + # Tall matrices: + A = DDM([ + [QQ(1), QQ(2), QQ(3), QQ(4)], + [QQ(0), QQ(5), QQ(6), QQ(7)], + [QQ(0), QQ(0), QQ(8), QQ(9)] + ], (3, 4), QQ).transpose() + B = DDM([ + [QQ(1), QQ(2), QQ(3), QQ(4)], + [QQ(0), QQ(5), QQ(6), QQ(7)], + [QQ(0), QQ(7), QQ(8), QQ(9)] + ], (3, 4), QQ).transpose() + assert A.is_lower() is True + assert B.is_lower() is False + + # Wide matrices: + A = DDM([ + [QQ(1), QQ(2), QQ(3)], + [QQ(0), QQ(5), QQ(6)], + [QQ(0), QQ(0), QQ(8)], + [QQ(0), QQ(0), QQ(0)] + ], (4, 3), QQ).transpose() + B = DDM([ + [QQ(1), QQ(2), QQ(3)], + [QQ(0), QQ(5), QQ(6)], + [QQ(0), QQ(0), QQ(8)], + [QQ(0), QQ(0), QQ(10)] + ], (4, 3), QQ).transpose() + assert A.is_lower() is True + assert B.is_lower() is False diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_dense.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_dense.py new file mode 100644 index 0000000000000000000000000000000000000000..75315ebf6b2ae7d53b4a5737578d3ac5ed4ea36a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_dense.py @@ -0,0 +1,350 @@ +from sympy.testing.pytest import raises + +from sympy.polys import ZZ, QQ + +from sympy.polys.matrices.ddm import DDM +from sympy.polys.matrices.dense import ( + ddm_transpose, + ddm_iadd, ddm_isub, ddm_ineg, ddm_imatmul, ddm_imul, ddm_irref, + ddm_idet, ddm_iinv, ddm_ilu, ddm_ilu_split, ddm_ilu_solve, ddm_berk) + +from sympy.polys.matrices.exceptions import ( + DMDomainError, + DMNonInvertibleMatrixError, + DMNonSquareMatrixError, + DMShapeError, +) + + +def test_ddm_transpose(): + a = [[1, 2], [3, 4]] + assert ddm_transpose(a) == [[1, 3], [2, 4]] + + +def test_ddm_iadd(): + a = [[1, 2], [3, 4]] + b = [[5, 6], [7, 8]] + ddm_iadd(a, b) + assert a == [[6, 8], [10, 12]] + + +def test_ddm_isub(): + a = [[1, 2], [3, 4]] + b = [[5, 6], [7, 8]] + ddm_isub(a, b) + assert a == [[-4, -4], [-4, -4]] + + +def test_ddm_ineg(): + a = [[1, 2], [3, 4]] + ddm_ineg(a) + assert a == [[-1, -2], [-3, -4]] + + +def test_ddm_matmul(): + a = [[1, 2], [3, 4]] + ddm_imul(a, 2) + assert a == [[2, 4], [6, 8]] + + a = [[1, 2], [3, 4]] + ddm_imul(a, 0) + assert a == [[0, 0], [0, 0]] + + +def test_ddm_imatmul(): + a = [[1, 2, 3], [4, 5, 6]] + b = [[1, 2], [3, 4], [5, 6]] + + c1 = [[0, 0], [0, 0]] + ddm_imatmul(c1, a, b) + assert c1 == [[22, 28], [49, 64]] + + c2 = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] + ddm_imatmul(c2, b, a) + assert c2 == [[9, 12, 15], [19, 26, 33], [29, 40, 51]] + + b3 = [[1], [2], [3]] + c3 = [[0], [0]] + ddm_imatmul(c3, a, b3) + assert c3 == [[14], [32]] + + +def test_ddm_irref(): + # Empty matrix + A = [] + Ar = [] + pivots = [] + assert ddm_irref(A) == pivots + assert A == Ar + + # Standard square case + A = [[QQ(0), QQ(1)], [QQ(1), QQ(1)]] + Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]] + pivots = [0, 1] + assert ddm_irref(A) == pivots + assert A == Ar + + # m < n case + A = [[QQ(1), QQ(2), QQ(1)], [QQ(3), QQ(4), QQ(1)]] + Ar = [[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]] + pivots = [0, 1] + assert ddm_irref(A) == pivots + assert A == Ar + + # same m < n but reversed + A = [[QQ(3), QQ(4), QQ(1)], [QQ(1), QQ(2), QQ(1)]] + Ar = [[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]] + pivots = [0, 1] + assert ddm_irref(A) == pivots + assert A == Ar + + # m > n case + A = [[QQ(1), QQ(0)], [QQ(1), QQ(3)], [QQ(0), QQ(1)]] + Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]] + pivots = [0, 1] + assert ddm_irref(A) == pivots + assert A == Ar + + # Example with missing pivot + A = [[QQ(1), QQ(0), QQ(1)], [QQ(3), QQ(0), QQ(1)]] + Ar = [[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(0), QQ(1)]] + pivots = [0, 2] + assert ddm_irref(A) == pivots + assert A == Ar + + # Example with missing pivot and no replacement + A = [[QQ(0), QQ(1)], [QQ(0), QQ(2)], [QQ(1), QQ(0)]] + Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]] + pivots = [0, 1] + assert ddm_irref(A) == pivots + assert A == Ar + + +def test_ddm_idet(): + A = [] + assert ddm_idet(A, ZZ) == ZZ(1) + + A = [[ZZ(2)]] + assert ddm_idet(A, ZZ) == ZZ(2) + + A = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]] + assert ddm_idet(A, ZZ) == ZZ(-2) + + A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(3), ZZ(5)]] + assert ddm_idet(A, ZZ) == ZZ(-1) + + A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(2), ZZ(5)]] + assert ddm_idet(A, ZZ) == ZZ(0) + + A = [[QQ(1, 2), QQ(1, 2)], [QQ(1, 3), QQ(1, 4)]] + assert ddm_idet(A, QQ) == QQ(-1, 24) + + +def test_ddm_inv(): + A = [] + Ainv = [] + ddm_iinv(Ainv, A, QQ) + assert Ainv == A + + A = [] + Ainv = [] + raises(DMDomainError, lambda: ddm_iinv(Ainv, A, ZZ)) + + A = [[QQ(1), QQ(2)]] + Ainv = [[QQ(0), QQ(0)]] + raises(DMNonSquareMatrixError, lambda: ddm_iinv(Ainv, A, QQ)) + + A = [[QQ(1, 1), QQ(2, 1)], [QQ(3, 1), QQ(4, 1)]] + Ainv = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]] + Ainv_expected = [[QQ(-2, 1), QQ(1, 1)], [QQ(3, 2), QQ(-1, 2)]] + ddm_iinv(Ainv, A, QQ) + assert Ainv == Ainv_expected + + A = [[QQ(1, 1), QQ(2, 1)], [QQ(2, 1), QQ(4, 1)]] + Ainv = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]] + raises(DMNonInvertibleMatrixError, lambda: ddm_iinv(Ainv, A, QQ)) + + +def test_ddm_ilu(): + A = [] + Alu = [] + swaps = ddm_ilu(A) + assert A == Alu + assert swaps == [] + + A = [[]] + Alu = [[]] + swaps = ddm_ilu(A) + assert A == Alu + assert swaps == [] + + A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]] + Alu = [[QQ(1), QQ(2)], [QQ(3), QQ(-2)]] + swaps = ddm_ilu(A) + assert A == Alu + assert swaps == [] + + A = [[QQ(0), QQ(2)], [QQ(3), QQ(4)]] + Alu = [[QQ(3), QQ(4)], [QQ(0), QQ(2)]] + swaps = ddm_ilu(A) + assert A == Alu + assert swaps == [(0, 1)] + + A = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)], [QQ(7), QQ(8), QQ(9)]] + Alu = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(-3), QQ(-6)], [QQ(7), QQ(2), QQ(0)]] + swaps = ddm_ilu(A) + assert A == Alu + assert swaps == [] + + A = [[QQ(0), QQ(1), QQ(2)], [QQ(0), QQ(1), QQ(3)], [QQ(1), QQ(1), QQ(2)]] + Alu = [[QQ(1), QQ(1), QQ(2)], [QQ(0), QQ(1), QQ(3)], [QQ(0), QQ(1), QQ(-1)]] + swaps = ddm_ilu(A) + assert A == Alu + assert swaps == [(0, 2)] + + A = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]] + Alu = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(-3), QQ(-6)]] + swaps = ddm_ilu(A) + assert A == Alu + assert swaps == [] + + A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]] + Alu = [[QQ(1), QQ(2)], [QQ(3), QQ(-2)], [QQ(5), QQ(2)]] + swaps = ddm_ilu(A) + assert A == Alu + assert swaps == [] + + +def test_ddm_ilu_split(): + U = [] + L = [] + Uexp = [] + Lexp = [] + swaps = ddm_ilu_split(L, U, QQ) + assert U == Uexp + assert L == Lexp + assert swaps == [] + + U = [[]] + L = [[QQ(1)]] + Uexp = [[]] + Lexp = [[QQ(1)]] + swaps = ddm_ilu_split(L, U, QQ) + assert U == Uexp + assert L == Lexp + assert swaps == [] + + U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]] + L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]] + Uexp = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)]] + Lexp = [[QQ(1), QQ(0)], [QQ(3), QQ(1)]] + swaps = ddm_ilu_split(L, U, QQ) + assert U == Uexp + assert L == Lexp + assert swaps == [] + + U = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]] + L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]] + Uexp = [[QQ(1), QQ(2), QQ(3)], [QQ(0), QQ(-3), QQ(-6)]] + Lexp = [[QQ(1), QQ(0)], [QQ(4), QQ(1)]] + swaps = ddm_ilu_split(L, U, QQ) + assert U == Uexp + assert L == Lexp + assert swaps == [] + + U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]] + L = [[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(1), QQ(0)], [QQ(0), QQ(0), QQ(1)]] + Uexp = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)], [QQ(0), QQ(0)]] + Lexp = [[QQ(1), QQ(0), QQ(0)], [QQ(3), QQ(1), QQ(0)], [QQ(5), QQ(2), QQ(1)]] + swaps = ddm_ilu_split(L, U, QQ) + assert U == Uexp + assert L == Lexp + assert swaps == [] + + +def test_ddm_ilu_solve(): + # Basic example + # A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]] + U = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)]] + L = [[QQ(1), QQ(0)], [QQ(3), QQ(1)]] + swaps = [] + b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ) + xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ) + ddm_ilu_solve(x, L, U, swaps, b) + assert x == xexp + + # Example with swaps + # A = [[QQ(0), QQ(2)], [QQ(3), QQ(4)]] + U = [[QQ(3), QQ(4)], [QQ(0), QQ(2)]] + L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]] + swaps = [(0, 1)] + b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ) + xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ) + ddm_ilu_solve(x, L, U, swaps, b) + assert x == xexp + + # Overdetermined, consistent + # A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ) + U = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)], [QQ(0), QQ(0)]] + L = [[QQ(1), QQ(0), QQ(0)], [QQ(3), QQ(1), QQ(0)], [QQ(5), QQ(2), QQ(1)]] + swaps = [] + b = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ) + x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ) + xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ) + ddm_ilu_solve(x, L, U, swaps, b) + assert x == xexp + + # Overdetermined, inconsistent + b = DDM([[QQ(1)], [QQ(2)], [QQ(4)]], (3, 1), QQ) + raises(DMNonInvertibleMatrixError, lambda: ddm_ilu_solve(x, L, U, swaps, b)) + + # Square, noninvertible + # A = DDM([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ) + U = [[QQ(1), QQ(2)], [QQ(0), QQ(0)]] + L = [[QQ(1), QQ(0)], [QQ(1), QQ(1)]] + swaps = [] + b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ) + raises(DMNonInvertibleMatrixError, lambda: ddm_ilu_solve(x, L, U, swaps, b)) + + # Underdetermined + # A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ) + U = [[QQ(1), QQ(2)]] + L = [[QQ(1)]] + swaps = [] + b = DDM([[QQ(3)]], (1, 1), QQ) + raises(NotImplementedError, lambda: ddm_ilu_solve(x, L, U, swaps, b)) + + # Shape mismatch + b3 = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ) + raises(DMShapeError, lambda: ddm_ilu_solve(x, L, U, swaps, b3)) + + # Empty shape mismatch + U = [[QQ(1)]] + L = [[QQ(1)]] + swaps = [] + x = [[QQ(1)]] + b = [] + raises(DMShapeError, lambda: ddm_ilu_solve(x, L, U, swaps, b)) + + # Empty system + U = [] + L = [] + swaps = [] + b = [] + x = [] + ddm_ilu_solve(x, L, U, swaps, b) + assert x == [] + + +def test_ddm_charpoly(): + A = [] + assert ddm_berk(A, ZZ) == [[ZZ(1)]] + + A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]] + Avec = [[ZZ(1)], [ZZ(-15)], [ZZ(-18)], [ZZ(0)]] + assert ddm_berk(A, ZZ) == Avec + + A = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMShapeError, lambda: ddm_berk(A, ZZ)) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_domainmatrix.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_domainmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..2f45029fb080ca91e98ea04aa4717fa675492052 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_domainmatrix.py @@ -0,0 +1,1383 @@ +from sympy.external.gmpy import GROUND_TYPES + +from sympy import Integer, Rational, S, sqrt, Matrix, symbols +from sympy import FF, ZZ, QQ, QQ_I, EXRAW + +from sympy.polys.matrices.domainmatrix import DomainMatrix, DomainScalar, DM +from sympy.polys.matrices.exceptions import ( + DMBadInputError, DMDomainError, DMShapeError, DMFormatError, DMNotAField, + DMNonSquareMatrixError, DMNonInvertibleMatrixError, +) +from sympy.polys.matrices.ddm import DDM +from sympy.polys.matrices.sdm import SDM + +from sympy.testing.pytest import raises + + +def test_DM(): + ddm = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A = DM([[1, 2], [3, 4]], ZZ) + if GROUND_TYPES != 'flint': + assert A.rep == ddm + else: + assert A.rep == ddm.to_dfm() + assert A.shape == (2, 2) + assert A.domain == ZZ + + +def test_DomainMatrix_init(): + lol = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]] + dod = {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}} + ddm = DDM(lol, (2, 2), ZZ) + sdm = SDM(dod, (2, 2), ZZ) + + A = DomainMatrix(lol, (2, 2), ZZ) + if GROUND_TYPES != 'flint': + assert A.rep == ddm + else: + assert A.rep == ddm.to_dfm() + assert A.shape == (2, 2) + assert A.domain == ZZ + + A = DomainMatrix(dod, (2, 2), ZZ) + assert A.rep == sdm + assert A.shape == (2, 2) + assert A.domain == ZZ + + raises(TypeError, lambda: DomainMatrix(ddm, (2, 2), ZZ)) + raises(TypeError, lambda: DomainMatrix(sdm, (2, 2), ZZ)) + raises(TypeError, lambda: DomainMatrix(Matrix([[1]]), (1, 1), ZZ)) + + for fmt, rep in [('sparse', sdm), ('dense', ddm)]: + if fmt == 'dense' and GROUND_TYPES == 'flint': + rep = rep.to_dfm() + A = DomainMatrix(lol, (2, 2), ZZ, fmt=fmt) + assert A.rep == rep + A = DomainMatrix(dod, (2, 2), ZZ, fmt=fmt) + assert A.rep == rep + + raises(ValueError, lambda: DomainMatrix(lol, (2, 2), ZZ, fmt='invalid')) + + raises(DMBadInputError, lambda: DomainMatrix([[ZZ(1), ZZ(2)]], (2, 2), ZZ)) + + # uses copy + was = [i.copy() for i in lol] + A[0,0] = ZZ(42) + assert was == lol + + +def test_DomainMatrix_from_rep(): + ddm = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A = DomainMatrix.from_rep(ddm) + # XXX: Should from_rep convert to DFM? + assert A.rep == ddm + assert A.shape == (2, 2) + assert A.domain == ZZ + + sdm = SDM({0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ) + A = DomainMatrix.from_rep(sdm) + assert A.rep == sdm + assert A.shape == (2, 2) + assert A.domain == ZZ + + A = DomainMatrix([[ZZ(1)]], (1, 1), ZZ) + raises(TypeError, lambda: DomainMatrix.from_rep(A)) + + +def test_DomainMatrix_from_list(): + ddm = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A = DomainMatrix.from_list([[1, 2], [3, 4]], ZZ) + if GROUND_TYPES != 'flint': + assert A.rep == ddm + else: + assert A.rep == ddm.to_dfm() + assert A.shape == (2, 2) + assert A.domain == ZZ + + dom = FF(7) + ddm = DDM([[dom(1), dom(2)], [dom(3), dom(4)]], (2, 2), dom) + A = DomainMatrix.from_list([[1, 2], [3, 4]], dom) + if GROUND_TYPES != 'flint': + assert A.rep == ddm + else: + assert A.rep == ddm.to_dfm() + assert A.shape == (2, 2) + assert A.domain == dom + + dom = FF(2**127-1) + ddm = DDM([[dom(1), dom(2)], [dom(3), dom(4)]], (2, 2), dom) + A = DomainMatrix.from_list([[1, 2], [3, 4]], dom) + if GROUND_TYPES != 'flint': + assert A.rep == ddm + else: + assert A.rep == ddm.to_dfm() + assert A.shape == (2, 2) + assert A.domain == dom + + ddm = DDM([[QQ(1, 2), QQ(3, 1)], [QQ(1, 4), QQ(5, 1)]], (2, 2), QQ) + A = DomainMatrix.from_list([[(1, 2), (3, 1)], [(1, 4), (5, 1)]], QQ) + if GROUND_TYPES != 'flint': + assert A.rep == ddm + else: + assert A.rep == ddm.to_dfm() + assert A.shape == (2, 2) + assert A.domain == QQ + + +def test_DomainMatrix_from_list_sympy(): + ddm = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A = DomainMatrix.from_list_sympy(2, 2, [[1, 2], [3, 4]]) + if GROUND_TYPES != 'flint': + assert A.rep == ddm + else: + assert A.rep == ddm.to_dfm() + assert A.shape == (2, 2) + assert A.domain == ZZ + + K = QQ.algebraic_field(sqrt(2)) + ddm = DDM( + [[K.convert(1 + sqrt(2)), K.convert(2 + sqrt(2))], + [K.convert(3 + sqrt(2)), K.convert(4 + sqrt(2))]], + (2, 2), + K + ) + A = DomainMatrix.from_list_sympy( + 2, 2, [[1 + sqrt(2), 2 + sqrt(2)], [3 + sqrt(2), 4 + sqrt(2)]], + extension=True) + assert A.rep == ddm + assert A.shape == (2, 2) + assert A.domain == K + + +def test_DomainMatrix_from_dict_sympy(): + sdm = SDM({0: {0: QQ(1, 2)}, 1: {1: QQ(2, 3)}}, (2, 2), QQ) + sympy_dict = {0: {0: Rational(1, 2)}, 1: {1: Rational(2, 3)}} + A = DomainMatrix.from_dict_sympy(2, 2, sympy_dict) + assert A.rep == sdm + assert A.shape == (2, 2) + assert A.domain == QQ + + fds = DomainMatrix.from_dict_sympy + raises(DMBadInputError, lambda: fds(2, 2, {3: {0: Rational(1, 2)}})) + raises(DMBadInputError, lambda: fds(2, 2, {0: {3: Rational(1, 2)}})) + + +def test_DomainMatrix_from_Matrix(): + sdm = SDM({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}, (2, 2), ZZ) + A = DomainMatrix.from_Matrix(Matrix([[1, 2], [3, 4]])) + assert A.rep == sdm + assert A.shape == (2, 2) + assert A.domain == ZZ + + K = QQ.algebraic_field(sqrt(2)) + sdm = SDM( + {0: {0: K.convert(1 + sqrt(2)), 1: K.convert(2 + sqrt(2))}, + 1: {0: K.convert(3 + sqrt(2)), 1: K.convert(4 + sqrt(2))}}, + (2, 2), + K + ) + A = DomainMatrix.from_Matrix( + Matrix([[1 + sqrt(2), 2 + sqrt(2)], [3 + sqrt(2), 4 + sqrt(2)]]), + extension=True) + assert A.rep == sdm + assert A.shape == (2, 2) + assert A.domain == K + + A = DomainMatrix.from_Matrix(Matrix([[QQ(1, 2), QQ(3, 4)], [QQ(0, 1), QQ(0, 1)]]), fmt='dense') + ddm = DDM([[QQ(1, 2), QQ(3, 4)], [QQ(0, 1), QQ(0, 1)]], (2, 2), QQ) + + if GROUND_TYPES != 'flint': + assert A.rep == ddm + else: + assert A.rep == ddm.to_dfm() + assert A.shape == (2, 2) + assert A.domain == QQ + + +def test_DomainMatrix_eq(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A == A + B = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(1)]], (2, 2), ZZ) + assert A != B + C = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]] + assert A != C + + +def test_DomainMatrix_unify_eq(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + B1 = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + B2 = DomainMatrix([[QQ(1), QQ(3)], [QQ(3), QQ(4)]], (2, 2), QQ) + B3 = DomainMatrix([[ZZ(1)]], (1, 1), ZZ) + assert A.unify_eq(B1) is True + assert A.unify_eq(B2) is False + assert A.unify_eq(B3) is False + + +def test_DomainMatrix_get_domain(): + K, items = DomainMatrix.get_domain([1, 2, 3, 4]) + assert items == [ZZ(1), ZZ(2), ZZ(3), ZZ(4)] + assert K == ZZ + + K, items = DomainMatrix.get_domain([1, 2, 3, Rational(1, 2)]) + assert items == [QQ(1), QQ(2), QQ(3), QQ(1, 2)] + assert K == QQ + + +def test_DomainMatrix_convert_to(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + Aq = A.convert_to(QQ) + assert Aq == DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + + +def test_DomainMatrix_choose_domain(): + A = [[1, 2], [3, 0]] + assert DM(A, QQ).choose_domain() == DM(A, ZZ) + assert DM(A, QQ).choose_domain(field=True) == DM(A, QQ) + assert DM(A, ZZ).choose_domain(field=True) == DM(A, QQ) + + x = symbols('x') + B = [[1, x], [x**2, x**3]] + assert DM(B, QQ[x]).choose_domain(field=True) == DM(B, ZZ.frac_field(x)) + + +def test_DomainMatrix_to_flat_nz(): + Adm = DM([[1, 2], [3, 0]], ZZ) + Addm = Adm.rep.to_ddm() + Asdm = Adm.rep.to_sdm() + for A in [Adm, Addm, Asdm]: + elems, data = A.to_flat_nz() + assert A.from_flat_nz(elems, data, A.domain) == A + elemsq = [QQ(e) for e in elems] + assert A.from_flat_nz(elemsq, data, QQ) == A.convert_to(QQ) + elems2 = [2*e for e in elems] + assert A.from_flat_nz(elems2, data, A.domain) == 2*A + + +def test_DomainMatrix_to_sympy(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.to_sympy() == A.convert_to(EXRAW) + + +def test_DomainMatrix_to_field(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + Aq = A.to_field() + assert Aq == DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + + +def test_DomainMatrix_to_sparse(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A_sparse = A.to_sparse() + assert A_sparse.rep == {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}} + + +def test_DomainMatrix_to_dense(): + A = DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ) + A_dense = A.to_dense() + ddm = DDM([[1, 2], [3, 4]], (2, 2), ZZ) + if GROUND_TYPES != 'flint': + assert A_dense.rep == ddm + else: + assert A_dense.rep == ddm.to_dfm() + + +def test_DomainMatrix_unify(): + Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + Aq = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + assert Az.unify(Az) == (Az, Az) + assert Az.unify(Aq) == (Aq, Aq) + assert Aq.unify(Az) == (Aq, Aq) + assert Aq.unify(Aq) == (Aq, Aq) + + As = DomainMatrix({0: {1: ZZ(1)}, 1:{0:ZZ(2)}}, (2, 2), ZZ) + Ad = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + assert As.unify(As) == (As, As) + assert Ad.unify(Ad) == (Ad, Ad) + + Bs, Bd = As.unify(Ad, fmt='dense') + assert Bs.rep == DDM([[0, 1], [2, 0]], (2, 2), ZZ).to_dfm_or_ddm() + assert Bd.rep == DDM([[1, 2],[3, 4]], (2, 2), ZZ).to_dfm_or_ddm() + + Bs, Bd = As.unify(Ad, fmt='sparse') + assert Bs.rep == SDM({0: {1: 1}, 1: {0: 2}}, (2, 2), ZZ) + assert Bd.rep == SDM({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ) + + raises(ValueError, lambda: As.unify(Ad, fmt='invalid')) + + +def test_DomainMatrix_to_Matrix(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A_Matrix = Matrix([[1, 2], [3, 4]]) + assert A.to_Matrix() == A_Matrix + assert A.to_sparse().to_Matrix() == A_Matrix + assert A.convert_to(QQ).to_Matrix() == A_Matrix + assert A.convert_to(QQ.algebraic_field(sqrt(2))).to_Matrix() == A_Matrix + + +def test_DomainMatrix_to_list(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.to_list() == [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]] + + +def test_DomainMatrix_to_list_flat(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.to_list_flat() == [ZZ(1), ZZ(2), ZZ(3), ZZ(4)] + + +def test_DomainMatrix_flat(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.flat() == [ZZ(1), ZZ(2), ZZ(3), ZZ(4)] + + +def test_DomainMatrix_from_list_flat(): + nums = [ZZ(1), ZZ(2), ZZ(3), ZZ(4)] + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + assert DomainMatrix.from_list_flat(nums, (2, 2), ZZ) == A + assert DDM.from_list_flat(nums, (2, 2), ZZ) == A.rep.to_ddm() + assert SDM.from_list_flat(nums, (2, 2), ZZ) == A.rep.to_sdm() + + assert A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain) + + raises(DMBadInputError, DomainMatrix.from_list_flat, nums, (2, 3), ZZ) + raises(DMBadInputError, DDM.from_list_flat, nums, (2, 3), ZZ) + raises(DMBadInputError, SDM.from_list_flat, nums, (2, 3), ZZ) + + +def test_DomainMatrix_to_dod(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.to_dod() == {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}} + A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(4)]], (2, 2), ZZ) + assert A.to_dod() == {0: {0: ZZ(1)}, 1: {1: ZZ(4)}} + + +def test_DomainMatrix_from_dod(): + items = {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}} + A = DM([[1, 2], [3, 4]], ZZ) + assert DomainMatrix.from_dod(items, (2, 2), ZZ) == A.to_sparse() + assert A.from_dod_like(items) == A + assert A.from_dod_like(items, QQ) == A.convert_to(QQ) + + +def test_DomainMatrix_to_dok(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.to_dok() == {(0, 0):ZZ(1), (0, 1):ZZ(2), (1, 0):ZZ(3), (1, 1):ZZ(4)} + A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(4)]], (2, 2), ZZ) + dok = {(0, 0):ZZ(1), (1, 1):ZZ(4)} + assert A.to_dok() == dok + assert A.to_dense().to_dok() == dok + assert A.to_sparse().to_dok() == dok + assert A.rep.to_ddm().to_dok() == dok + assert A.rep.to_sdm().to_dok() == dok + + +def test_DomainMatrix_from_dok(): + items = {(0, 0): ZZ(1), (1, 1): ZZ(2)} + A = DM([[1, 0], [0, 2]], ZZ) + assert DomainMatrix.from_dok(items, (2, 2), ZZ) == A.to_sparse() + assert DDM.from_dok(items, (2, 2), ZZ) == A.rep.to_ddm() + assert SDM.from_dok(items, (2, 2), ZZ) == A.rep.to_sdm() + + +def test_DomainMatrix_repr(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert repr(A) == 'DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)' + + +def test_DomainMatrix_transpose(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + AT = DomainMatrix([[ZZ(1), ZZ(3)], [ZZ(2), ZZ(4)]], (2, 2), ZZ) + assert A.transpose() == AT + + +def test_DomainMatrix_is_zero_matrix(): + A = DomainMatrix([[ZZ(1)]], (1, 1), ZZ) + B = DomainMatrix([[ZZ(0)]], (1, 1), ZZ) + assert A.is_zero_matrix is False + assert B.is_zero_matrix is True + + +def test_DomainMatrix_is_upper(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(0), ZZ(4)]], (2, 2), ZZ) + B = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.is_upper is True + assert B.is_upper is False + + +def test_DomainMatrix_is_lower(): + A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + B = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.is_lower is True + assert B.is_lower is False + + +def test_DomainMatrix_is_diagonal(): + A = DM([[1, 0], [0, 4]], ZZ) + B = DM([[1, 2], [3, 4]], ZZ) + assert A.is_diagonal is A.to_sparse().is_diagonal is True + assert B.is_diagonal is B.to_sparse().is_diagonal is False + + +def test_DomainMatrix_is_square(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + B = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)], [ZZ(5), ZZ(6)]], (3, 2), ZZ) + assert A.is_square is True + assert B.is_square is False + + +def test_DomainMatrix_diagonal(): + A = DM([[1, 2], [3, 4]], ZZ) + assert A.diagonal() == A.to_sparse().diagonal() == [ZZ(1), ZZ(4)] + A = DM([[1, 2], [3, 4], [5, 6]], ZZ) + assert A.diagonal() == A.to_sparse().diagonal() == [ZZ(1), ZZ(4)] + A = DM([[1, 2, 3], [4, 5, 6]], ZZ) + assert A.diagonal() == A.to_sparse().diagonal() == [ZZ(1), ZZ(5)] + + +def test_DomainMatrix_rank(): + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(6), QQ(8)]], (3, 2), QQ) + assert A.rank() == 2 + + +def test_DomainMatrix_add(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + B = DomainMatrix([[ZZ(2), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ) + assert A + A == A.add(A) == B + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + L = [[2, 3], [3, 4]] + raises(TypeError, lambda: A + L) + raises(TypeError, lambda: L + A) + + A1 = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A2 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMShapeError, lambda: A1 + A2) + raises(DMShapeError, lambda: A2 + A1) + raises(DMShapeError, lambda: A1.add(A2)) + raises(DMShapeError, lambda: A2.add(A1)) + + Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + Aq = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + Asum = DomainMatrix([[QQ(2), QQ(4)], [QQ(6), QQ(8)]], (2, 2), QQ) + assert Az + Aq == Asum + assert Aq + Az == Asum + raises(DMDomainError, lambda: Az.add(Aq)) + raises(DMDomainError, lambda: Aq.add(Az)) + + As = DomainMatrix({0: {1: ZZ(1)}, 1: {0: ZZ(2)}}, (2, 2), ZZ) + Ad = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + Asd = As + Ad + Ads = Ad + As + assert Asd == DomainMatrix([[1, 3], [5, 4]], (2, 2), ZZ) + assert Asd.rep == DDM([[1, 3], [5, 4]], (2, 2), ZZ).to_dfm_or_ddm() + assert Ads == DomainMatrix([[1, 3], [5, 4]], (2, 2), ZZ) + assert Ads.rep == DDM([[1, 3], [5, 4]], (2, 2), ZZ).to_dfm_or_ddm() + raises(DMFormatError, lambda: As.add(Ad)) + + +def test_DomainMatrix_sub(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + B = DomainMatrix([[ZZ(0), ZZ(0)], [ZZ(0), ZZ(0)]], (2, 2), ZZ) + assert A - A == A.sub(A) == B + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + L = [[2, 3], [3, 4]] + raises(TypeError, lambda: A - L) + raises(TypeError, lambda: L - A) + + A1 = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A2 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMShapeError, lambda: A1 - A2) + raises(DMShapeError, lambda: A2 - A1) + raises(DMShapeError, lambda: A1.sub(A2)) + raises(DMShapeError, lambda: A2.sub(A1)) + + Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + Aq = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + Adiff = DomainMatrix([[QQ(0), QQ(0)], [QQ(0), QQ(0)]], (2, 2), QQ) + assert Az - Aq == Adiff + assert Aq - Az == Adiff + raises(DMDomainError, lambda: Az.sub(Aq)) + raises(DMDomainError, lambda: Aq.sub(Az)) + + As = DomainMatrix({0: {1: ZZ(1)}, 1: {0: ZZ(2)}}, (2, 2), ZZ) + Ad = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + Asd = As - Ad + Ads = Ad - As + assert Asd == DomainMatrix([[-1, -1], [-1, -4]], (2, 2), ZZ) + assert Asd.rep == DDM([[-1, -1], [-1, -4]], (2, 2), ZZ).to_dfm_or_ddm() + assert Asd == -Ads + assert Asd.rep == -Ads.rep + + +def test_DomainMatrix_neg(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + Aneg = DomainMatrix([[ZZ(-1), ZZ(-2)], [ZZ(-3), ZZ(-4)]], (2, 2), ZZ) + assert -A == A.neg() == Aneg + + +def test_DomainMatrix_mul(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A2 = DomainMatrix([[ZZ(7), ZZ(10)], [ZZ(15), ZZ(22)]], (2, 2), ZZ) + assert A*A == A.matmul(A) == A2 + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + L = [[1, 2], [3, 4]] + raises(TypeError, lambda: A * L) + raises(TypeError, lambda: L * A) + + Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + Aq = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + Aprod = DomainMatrix([[QQ(7), QQ(10)], [QQ(15), QQ(22)]], (2, 2), QQ) + assert Az * Aq == Aprod + assert Aq * Az == Aprod + raises(DMDomainError, lambda: Az.matmul(Aq)) + raises(DMDomainError, lambda: Aq.matmul(Az)) + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + AA = DomainMatrix([[ZZ(2), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ) + x = ZZ(2) + assert A * x == x * A == A.mul(x) == AA + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + AA = DomainMatrix.zeros((2, 2), ZZ) + x = ZZ(0) + assert A * x == x * A == A.mul(x).to_sparse() == AA + + As = DomainMatrix({0: {1: ZZ(1)}, 1: {0: ZZ(2)}}, (2, 2), ZZ) + Ad = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + + Asd = As * Ad + Ads = Ad * As + assert Asd == DomainMatrix([[3, 4], [2, 4]], (2, 2), ZZ) + assert Asd.rep == DDM([[3, 4], [2, 4]], (2, 2), ZZ).to_dfm_or_ddm() + assert Ads == DomainMatrix([[4, 1], [8, 3]], (2, 2), ZZ) + assert Ads.rep == DDM([[4, 1], [8, 3]], (2, 2), ZZ).to_dfm_or_ddm() + + +def test_DomainMatrix_mul_elementwise(): + A = DomainMatrix([[ZZ(2), ZZ(2)], [ZZ(0), ZZ(0)]], (2, 2), ZZ) + B = DomainMatrix([[ZZ(4), ZZ(0)], [ZZ(3), ZZ(0)]], (2, 2), ZZ) + C = DomainMatrix([[ZZ(8), ZZ(0)], [ZZ(0), ZZ(0)]], (2, 2), ZZ) + assert A.mul_elementwise(B) == C + assert B.mul_elementwise(A) == C + + +def test_DomainMatrix_pow(): + eye = DomainMatrix.eye(2, ZZ) + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + A2 = DomainMatrix([[ZZ(7), ZZ(10)], [ZZ(15), ZZ(22)]], (2, 2), ZZ) + A3 = DomainMatrix([[ZZ(37), ZZ(54)], [ZZ(81), ZZ(118)]], (2, 2), ZZ) + assert A**0 == A.pow(0) == eye + assert A**1 == A.pow(1) == A + assert A**2 == A.pow(2) == A2 + assert A**3 == A.pow(3) == A3 + + raises(TypeError, lambda: A ** Rational(1, 2)) + raises(NotImplementedError, lambda: A ** -1) + raises(NotImplementedError, lambda: A.pow(-1)) + + A = DomainMatrix.zeros((2, 1), ZZ) + raises(DMNonSquareMatrixError, lambda: A ** 1) + + +def test_DomainMatrix_clear_denoms(): + A = DM([[(1,2),(1,3)],[(1,4),(1,5)]], QQ) + + den_Z = DomainScalar(ZZ(60), ZZ) + Anum_Z = DM([[30, 20], [15, 12]], ZZ) + Anum_Q = Anum_Z.convert_to(QQ) + + assert A.clear_denoms() == (den_Z, Anum_Q) + assert A.clear_denoms(convert=True) == (den_Z, Anum_Z) + assert A * den_Z == Anum_Q + assert A == Anum_Q / den_Z + + +def test_DomainMatrix_clear_denoms_rowwise(): + A = DM([[(1,2),(1,3)],[(1,4),(1,5)]], QQ) + + den_Z = DM([[6, 0], [0, 20]], ZZ).to_sparse() + Anum_Z = DM([[3, 2], [5, 4]], ZZ) + Anum_Q = DM([[3, 2], [5, 4]], QQ) + + assert A.clear_denoms_rowwise() == (den_Z, Anum_Q) + assert A.clear_denoms_rowwise(convert=True) == (den_Z, Anum_Z) + assert den_Z * A == Anum_Q + assert A == den_Z.to_field().inv() * Anum_Q + + A = DM([[(1,2),(1,3),0,0],[0,0,0,0], [(1,4),(1,5),(1,6),(1,7)]], QQ) + den_Z = DM([[6, 0, 0], [0, 1, 0], [0, 0, 420]], ZZ).to_sparse() + Anum_Z = DM([[3, 2, 0, 0], [0, 0, 0, 0], [105, 84, 70, 60]], ZZ) + Anum_Q = Anum_Z.convert_to(QQ) + + assert A.clear_denoms_rowwise() == (den_Z, Anum_Q) + assert A.clear_denoms_rowwise(convert=True) == (den_Z, Anum_Z) + assert den_Z * A == Anum_Q + assert A == den_Z.to_field().inv() * Anum_Q + + +def test_DomainMatrix_cancel_denom(): + A = DM([[2, 4], [6, 8]], ZZ) + assert A.cancel_denom(ZZ(1)) == (DM([[2, 4], [6, 8]], ZZ), ZZ(1)) + assert A.cancel_denom(ZZ(3)) == (DM([[2, 4], [6, 8]], ZZ), ZZ(3)) + assert A.cancel_denom(ZZ(4)) == (DM([[1, 2], [3, 4]], ZZ), ZZ(2)) + + A = DM([[1, 2], [3, 4]], ZZ) + assert A.cancel_denom(ZZ(2)) == (A, ZZ(2)) + assert A.cancel_denom(ZZ(-2)) == (-A, ZZ(2)) + + # Test canonicalization of denominator over Gaussian rationals. + A = DM([[1, 2], [3, 4]], QQ_I) + assert A.cancel_denom(QQ_I(0,2)) == (QQ_I(0,-1)*A, QQ_I(2)) + + raises(ZeroDivisionError, lambda: A.cancel_denom(ZZ(0))) + + +def test_DomainMatrix_cancel_denom_elementwise(): + A = DM([[2, 4], [6, 8]], ZZ) + numers, denoms = A.cancel_denom_elementwise(ZZ(1)) + assert numers == DM([[2, 4], [6, 8]], ZZ) + assert denoms == DM([[1, 1], [1, 1]], ZZ) + numers, denoms = A.cancel_denom_elementwise(ZZ(4)) + assert numers == DM([[1, 1], [3, 2]], ZZ) + assert denoms == DM([[2, 1], [2, 1]], ZZ) + + raises(ZeroDivisionError, lambda: A.cancel_denom_elementwise(ZZ(0))) + + +def test_DomainMatrix_content_primitive(): + A = DM([[2, 4], [6, 8]], ZZ) + A_primitive = DM([[1, 2], [3, 4]], ZZ) + A_content = ZZ(2) + assert A.content() == A_content + assert A.primitive() == (A_content, A_primitive) + + +def test_DomainMatrix_scc(): + Ad = DomainMatrix([[ZZ(1), ZZ(2), ZZ(3)], + [ZZ(0), ZZ(1), ZZ(0)], + [ZZ(2), ZZ(0), ZZ(4)]], (3, 3), ZZ) + As = Ad.to_sparse() + Addm = Ad.rep + Asdm = As.rep + for A in [Ad, As, Addm, Asdm]: + assert Ad.scc() == [[1], [0, 2]] + + A = DM([[ZZ(1), ZZ(2), ZZ(3)]], ZZ) + raises(DMNonSquareMatrixError, lambda: A.scc()) + + +def test_DomainMatrix_rref(): + # More tests in test_rref.py + A = DomainMatrix([], (0, 1), QQ) + assert A.rref() == (A, ()) + + A = DomainMatrix([[QQ(1)]], (1, 1), QQ) + assert A.rref() == (A, (0,)) + + A = DomainMatrix([[QQ(0)]], (1, 1), QQ) + assert A.rref() == (A, ()) + + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + Ar, pivots = A.rref() + assert Ar == DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ) + assert pivots == (0, 1) + + A = DomainMatrix([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + Ar, pivots = A.rref() + assert Ar == DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ) + assert pivots == (0, 1) + + A = DomainMatrix([[QQ(0), QQ(2)], [QQ(0), QQ(4)]], (2, 2), QQ) + Ar, pivots = A.rref() + assert Ar == DomainMatrix([[QQ(0), QQ(1)], [QQ(0), QQ(0)]], (2, 2), QQ) + assert pivots == (1,) + + Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + Ar, pivots = Az.rref() + assert Ar == DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ) + assert pivots == (0, 1) + + methods = ('auto', 'GJ', 'FF', 'CD', 'GJ_dense', 'FF_dense', 'CD_dense') + Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + for method in methods: + Ar, pivots = Az.rref(method=method) + assert Ar == DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ) + assert pivots == (0, 1) + + raises(ValueError, lambda: Az.rref(method='foo')) + raises(ValueError, lambda: Az.rref_den(method='foo')) + + +def test_DomainMatrix_columnspace(): + A = DomainMatrix([[QQ(1), QQ(-1), QQ(1)], [QQ(2), QQ(-2), QQ(3)]], (2, 3), QQ) + Acol = DomainMatrix([[QQ(1), QQ(1)], [QQ(2), QQ(3)]], (2, 2), QQ) + assert A.columnspace() == Acol + + Az = DomainMatrix([[ZZ(1), ZZ(-1), ZZ(1)], [ZZ(2), ZZ(-2), ZZ(3)]], (2, 3), ZZ) + raises(DMNotAField, lambda: Az.columnspace()) + + A = DomainMatrix([[QQ(1), QQ(-1), QQ(1)], [QQ(2), QQ(-2), QQ(3)]], (2, 3), QQ, fmt='sparse') + Acol = DomainMatrix({0: {0: QQ(1), 1: QQ(1)}, 1: {0: QQ(2), 1: QQ(3)}}, (2, 2), QQ) + assert A.columnspace() == Acol + + +def test_DomainMatrix_rowspace(): + A = DomainMatrix([[QQ(1), QQ(-1), QQ(1)], [QQ(2), QQ(-2), QQ(3)]], (2, 3), QQ) + assert A.rowspace() == A + + Az = DomainMatrix([[ZZ(1), ZZ(-1), ZZ(1)], [ZZ(2), ZZ(-2), ZZ(3)]], (2, 3), ZZ) + raises(DMNotAField, lambda: Az.rowspace()) + + A = DomainMatrix([[QQ(1), QQ(-1), QQ(1)], [QQ(2), QQ(-2), QQ(3)]], (2, 3), QQ, fmt='sparse') + assert A.rowspace() == A + + +def test_DomainMatrix_nullspace(): + A = DomainMatrix([[QQ(1), QQ(1)], [QQ(1), QQ(1)]], (2, 2), QQ) + Anull = DomainMatrix([[QQ(-1), QQ(1)]], (1, 2), QQ) + assert A.nullspace() == Anull + + A = DomainMatrix([[ZZ(1), ZZ(1)], [ZZ(1), ZZ(1)]], (2, 2), ZZ) + Anull = DomainMatrix([[ZZ(-1), ZZ(1)]], (1, 2), ZZ) + assert A.nullspace() == Anull + + raises(DMNotAField, lambda: A.nullspace(divide_last=True)) + + A = DomainMatrix([[ZZ(2), ZZ(2)], [ZZ(2), ZZ(2)]], (2, 2), ZZ) + Anull = DomainMatrix([[ZZ(-2), ZZ(2)]], (1, 2), ZZ) + + Arref, den, pivots = A.rref_den() + assert den == ZZ(2) + assert Arref.nullspace_from_rref() == Anull + assert Arref.nullspace_from_rref(pivots) == Anull + assert Arref.to_sparse().nullspace_from_rref() == Anull.to_sparse() + assert Arref.to_sparse().nullspace_from_rref(pivots) == Anull.to_sparse() + + +def test_DomainMatrix_solve(): + # XXX: Maybe the _solve method should be changed... + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ) + b = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ) + particular = DomainMatrix([[1, 0]], (1, 2), QQ) + nullspace = DomainMatrix([[-2, 1]], (1, 2), QQ) + assert A._solve(b) == (particular, nullspace) + + b3 = DomainMatrix([[QQ(1)], [QQ(1)], [QQ(1)]], (3, 1), QQ) + raises(DMShapeError, lambda: A._solve(b3)) + + bz = DomainMatrix([[ZZ(1)], [ZZ(1)]], (2, 1), ZZ) + raises(DMNotAField, lambda: A._solve(bz)) + + +def test_DomainMatrix_inv(): + A = DomainMatrix([], (0, 0), QQ) + assert A.inv() == A + + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + Ainv = DomainMatrix([[QQ(-2), QQ(1)], [QQ(3, 2), QQ(-1, 2)]], (2, 2), QQ) + assert A.inv() == Ainv + + Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + raises(DMNotAField, lambda: Az.inv()) + + Ans = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ) + raises(DMNonSquareMatrixError, lambda: Ans.inv()) + + Aninv = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(6)]], (2, 2), QQ) + raises(DMNonInvertibleMatrixError, lambda: Aninv.inv()) + + Z3 = FF(3) + assert DM([[1, 2], [3, 4]], Z3).inv() == DM([[1, 1], [0, 1]], Z3) + + Z6 = FF(6) + raises(DMNotAField, lambda: DM([[1, 2], [3, 4]], Z6).inv()) + + +def test_DomainMatrix_det(): + A = DomainMatrix([], (0, 0), ZZ) + assert A.det() == 1 + + A = DomainMatrix([[1]], (1, 1), ZZ) + assert A.det() == 1 + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.det() == ZZ(-2) + + A = DomainMatrix([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(3), ZZ(5)]], (3, 3), ZZ) + assert A.det() == ZZ(-1) + + A = DomainMatrix([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(2), ZZ(5)]], (3, 3), ZZ) + assert A.det() == ZZ(0) + + Ans = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ) + raises(DMNonSquareMatrixError, lambda: Ans.det()) + + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + assert A.det() == QQ(-2) + + +def test_DomainMatrix_eval_poly(): + dM = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + p = [ZZ(1), ZZ(2), ZZ(3)] + result = DomainMatrix([[ZZ(12), ZZ(14)], [ZZ(21), ZZ(33)]], (2, 2), ZZ) + assert dM.eval_poly(p) == result == p[0]*dM**2 + p[1]*dM + p[2]*dM**0 + assert dM.eval_poly([]) == dM.zeros(dM.shape, dM.domain) + assert dM.eval_poly([ZZ(2)]) == 2*dM.eye(2, dM.domain) + + dM2 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMNonSquareMatrixError, lambda: dM2.eval_poly([ZZ(1)])) + + +def test_DomainMatrix_eval_poly_mul(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + p = [ZZ(1), ZZ(2), ZZ(3)] + result = DomainMatrix([[ZZ(40)], [ZZ(87)]], (2, 1), ZZ) + assert A.eval_poly_mul(p, b) == result == p[0]*A**2*b + p[1]*A*b + p[2]*b + + dM = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + dM1 = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + raises(DMNonSquareMatrixError, lambda: dM1.eval_poly_mul([ZZ(1)], b)) + b1 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMShapeError, lambda: dM.eval_poly_mul([ZZ(1)], b1)) + bq = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ) + raises(DMDomainError, lambda: dM.eval_poly_mul([ZZ(1)], bq)) + + +def _check_solve_den(A, b, xnum, xden): + # Examples for solve_den, solve_den_charpoly, solve_den_rref should use + # this so that all methods and types are tested. + + case1 = (A, xnum, b) + case2 = (A.to_sparse(), xnum.to_sparse(), b.to_sparse()) + + for Ai, xnum_i, b_i in [case1, case2]: + # The key invariant for solve_den: + assert Ai*xnum_i == xden*b_i + + # solve_den_rref can differ at least by a minus sign + answers = [(xnum_i, xden), (-xnum_i, -xden)] + assert Ai.solve_den(b) in answers + assert Ai.solve_den(b, method='rref') in answers + assert Ai.solve_den_rref(b) in answers + + # charpoly can only be used if A is square and guarantees to return the + # actual determinant as a denominator. + m, n = Ai.shape + if m == n: + assert Ai.solve_den(b_i, method='charpoly') == (xnum_i, xden) + assert Ai.solve_den_charpoly(b_i) == (xnum_i, xden) + else: + raises(DMNonSquareMatrixError, lambda: Ai.solve_den_charpoly(b)) + raises(DMNonSquareMatrixError, lambda: Ai.solve_den(b, method='charpoly')) + + +def test_DomainMatrix_solve_den(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + result = DomainMatrix([[ZZ(0)], [ZZ(-1)]], (2, 1), ZZ) + den = ZZ(-2) + _check_solve_den(A, b, result, den) + + A = DomainMatrix([ + [ZZ(1), ZZ(2), ZZ(3)], + [ZZ(1), ZZ(2), ZZ(4)], + [ZZ(1), ZZ(3), ZZ(5)]], (3, 3), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(2)], [ZZ(3)]], (3, 1), ZZ) + result = DomainMatrix([[ZZ(2)], [ZZ(0)], [ZZ(-1)]], (3, 1), ZZ) + den = ZZ(-1) + _check_solve_den(A, b, result, den) + + A = DomainMatrix([[ZZ(2)], [ZZ(2)]], (2, 1), ZZ) + b = DomainMatrix([[ZZ(3)], [ZZ(3)]], (2, 1), ZZ) + result = DomainMatrix([[ZZ(3)]], (1, 1), ZZ) + den = ZZ(2) + _check_solve_den(A, b, result, den) + + +def test_DomainMatrix_solve_den_charpoly(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + A1 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMNonSquareMatrixError, lambda: A1.solve_den_charpoly(b)) + b1 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMShapeError, lambda: A.solve_den_charpoly(b1)) + bq = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ) + raises(DMDomainError, lambda: A.solve_den_charpoly(bq)) + + +def test_DomainMatrix_solve_den_charpoly_check(): + # Test check + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(2), ZZ(4)]], (2, 2), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(3)]], (2, 1), ZZ) + raises(DMNonInvertibleMatrixError, lambda: A.solve_den_charpoly(b)) + adjAb = DomainMatrix([[ZZ(-2)], [ZZ(1)]], (2, 1), ZZ) + assert A.adjugate() * b == adjAb + assert A.solve_den_charpoly(b, check=False) == (adjAb, ZZ(0)) + + +def test_DomainMatrix_solve_den_errors(): + A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + raises(DMShapeError, lambda: A.solve_den(b)) + raises(DMShapeError, lambda: A.solve_den_rref(b)) + + A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + b = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMShapeError, lambda: A.solve_den(b)) + raises(DMShapeError, lambda: A.solve_den_rref(b)) + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + b1 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + raises(DMShapeError, lambda: A.solve_den(b1)) + + A = DomainMatrix([[ZZ(2)]], (1, 1), ZZ) + b = DomainMatrix([[ZZ(2)]], (1, 1), ZZ) + raises(DMBadInputError, lambda: A.solve_den(b1, method='invalid')) + + A = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + raises(DMNonSquareMatrixError, lambda: A.solve_den_charpoly(b)) + + +def test_DomainMatrix_solve_den_rref_underdetermined(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(1), ZZ(2)]], (2, 2), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(1)]], (2, 1), ZZ) + raises(DMNonInvertibleMatrixError, lambda: A.solve_den(b)) + raises(DMNonInvertibleMatrixError, lambda: A.solve_den_rref(b)) + + +def test_DomainMatrix_adj_poly_det(): + A = DM([[ZZ(1), ZZ(2), ZZ(3)], + [ZZ(4), ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8), ZZ(9)]], ZZ) + p, detA = A.adj_poly_det() + assert p == [ZZ(1), ZZ(-15), ZZ(-18)] + assert A.adjugate() == p[0]*A**2 + p[1]*A**1 + p[2]*A**0 == A.eval_poly(p) + assert A.det() == detA + + A = DM([[ZZ(1), ZZ(2), ZZ(3)], + [ZZ(7), ZZ(8), ZZ(9)]], ZZ) + raises(DMNonSquareMatrixError, lambda: A.adj_poly_det()) + + +def test_DomainMatrix_inv_den(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + den = ZZ(-2) + result = DomainMatrix([[ZZ(4), ZZ(-2)], [ZZ(-3), ZZ(1)]], (2, 2), ZZ) + assert A.inv_den() == (result, den) + + +def test_DomainMatrix_adjugate(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + result = DomainMatrix([[ZZ(4), ZZ(-2)], [ZZ(-3), ZZ(1)]], (2, 2), ZZ) + assert A.adjugate() == result + + +def test_DomainMatrix_adj_det(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + adjA = DomainMatrix([[ZZ(4), ZZ(-2)], [ZZ(-3), ZZ(1)]], (2, 2), ZZ) + assert A.adj_det() == (adjA, ZZ(-2)) + + +def test_DomainMatrix_lu(): + A = DomainMatrix([], (0, 0), QQ) + assert A.lu() == (A, A, []) + + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + L = DomainMatrix([[QQ(1), QQ(0)], [QQ(3), QQ(1)]], (2, 2), QQ) + U = DomainMatrix([[QQ(1), QQ(2)], [QQ(0), QQ(-2)]], (2, 2), QQ) + swaps = [] + assert A.lu() == (L, U, swaps) + + A = DomainMatrix([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + L = DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ) + U = DomainMatrix([[QQ(3), QQ(4)], [QQ(0), QQ(2)]], (2, 2), QQ) + swaps = [(0, 1)] + assert A.lu() == (L, U, swaps) + + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ) + L = DomainMatrix([[QQ(1), QQ(0)], [QQ(2), QQ(1)]], (2, 2), QQ) + U = DomainMatrix([[QQ(1), QQ(2)], [QQ(0), QQ(0)]], (2, 2), QQ) + swaps = [] + assert A.lu() == (L, U, swaps) + + A = DomainMatrix([[QQ(0), QQ(2)], [QQ(0), QQ(4)]], (2, 2), QQ) + L = DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ) + U = DomainMatrix([[QQ(0), QQ(2)], [QQ(0), QQ(4)]], (2, 2), QQ) + swaps = [] + assert A.lu() == (L, U, swaps) + + A = DomainMatrix([[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]], (2, 3), QQ) + L = DomainMatrix([[QQ(1), QQ(0)], [QQ(4), QQ(1)]], (2, 2), QQ) + U = DomainMatrix([[QQ(1), QQ(2), QQ(3)], [QQ(0), QQ(-3), QQ(-6)]], (2, 3), QQ) + swaps = [] + assert A.lu() == (L, U, swaps) + + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ) + L = DomainMatrix([ + [QQ(1), QQ(0), QQ(0)], + [QQ(3), QQ(1), QQ(0)], + [QQ(5), QQ(2), QQ(1)]], (3, 3), QQ) + U = DomainMatrix([[QQ(1), QQ(2)], [QQ(0), QQ(-2)], [QQ(0), QQ(0)]], (3, 2), QQ) + swaps = [] + assert A.lu() == (L, U, swaps) + + A = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]] + L = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 1, 1]] + U = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 0, 1]] + to_dom = lambda rows, dom: [[dom(e) for e in row] for row in rows] + A = DomainMatrix(to_dom(A, QQ), (4, 4), QQ) + L = DomainMatrix(to_dom(L, QQ), (4, 4), QQ) + U = DomainMatrix(to_dom(U, QQ), (4, 4), QQ) + assert A.lu() == (L, U, []) + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + raises(DMNotAField, lambda: A.lu()) + + +def test_DomainMatrix_lu_solve(): + # Base case + A = b = x = DomainMatrix([], (0, 0), QQ) + assert A.lu_solve(b) == x + + # Basic example + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + b = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ) + x = DomainMatrix([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ) + assert A.lu_solve(b) == x + + # Example with swaps + A = DomainMatrix([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + b = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ) + x = DomainMatrix([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ) + assert A.lu_solve(b) == x + + # Non-invertible + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ) + b = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ) + raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b)) + + # Overdetermined, consistent + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ) + b = DomainMatrix([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ) + x = DomainMatrix([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ) + assert A.lu_solve(b) == x + + # Overdetermined, inconsistent + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ) + b = DomainMatrix([[QQ(1)], [QQ(2)], [QQ(4)]], (3, 1), QQ) + raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b)) + + # Underdetermined + A = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ) + b = DomainMatrix([[QQ(1)]], (1, 1), QQ) + raises(NotImplementedError, lambda: A.lu_solve(b)) + + # Non-field + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ) + raises(DMNotAField, lambda: A.lu_solve(b)) + + # Shape mismatch + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + b = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ) + raises(DMShapeError, lambda: A.lu_solve(b)) + + +def test_DomainMatrix_charpoly(): + A = DomainMatrix([], (0, 0), ZZ) + p = [ZZ(1)] + assert A.charpoly() == p + assert A.to_sparse().charpoly() == p + + A = DomainMatrix([[1]], (1, 1), ZZ) + p = [ZZ(1), ZZ(-1)] + assert A.charpoly() == p + assert A.to_sparse().charpoly() == p + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + p = [ZZ(1), ZZ(-5), ZZ(-2)] + assert A.charpoly() == p + assert A.to_sparse().charpoly() == p + + A = DomainMatrix([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ) + p = [ZZ(1), ZZ(-15), ZZ(-18), ZZ(0)] + assert A.charpoly() == p + assert A.to_sparse().charpoly() == p + + A = DomainMatrix([[ZZ(0), ZZ(1), ZZ(0)], + [ZZ(1), ZZ(0), ZZ(1)], + [ZZ(0), ZZ(1), ZZ(0)]], (3, 3), ZZ) + p = [ZZ(1), ZZ(0), ZZ(-2), ZZ(0)] + assert A.charpoly() == p + assert A.to_sparse().charpoly() == p + + A = DM([[17, 0, 30, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [69, 0, 0, 0, 0, 86, 0, 0, 0, 0], + [23, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 13, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 32, 0, 0], + [ 0, 0, 0, 0, 37, 67, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], ZZ) + p = ZZ.map([1, -17, -2070, 0, -771420, 0, 0, 0, 0, 0, 0]) + assert A.charpoly() == p + assert A.to_sparse().charpoly() == p + + Ans = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ) + raises(DMNonSquareMatrixError, lambda: Ans.charpoly()) + + +def test_DomainMatrix_charpoly_factor_list(): + A = DomainMatrix([], (0, 0), ZZ) + assert A.charpoly_factor_list() == [] + + A = DM([[1]], ZZ) + assert A.charpoly_factor_list() == [ + ([ZZ(1), ZZ(-1)], 1) + ] + + A = DM([[1, 2], [3, 4]], ZZ) + assert A.charpoly_factor_list() == [ + ([ZZ(1), ZZ(-5), ZZ(-2)], 1) + ] + + A = DM([[1, 2, 0], [3, 4, 0], [0, 0, 1]], ZZ) + assert A.charpoly_factor_list() == [ + ([ZZ(1), ZZ(-1)], 1), + ([ZZ(1), ZZ(-5), ZZ(-2)], 1) + ] + + +def test_DomainMatrix_eye(): + A = DomainMatrix.eye(3, QQ) + assert A.rep == SDM.eye((3, 3), QQ) + assert A.shape == (3, 3) + assert A.domain == QQ + + +def test_DomainMatrix_zeros(): + A = DomainMatrix.zeros((1, 2), QQ) + assert A.rep == SDM.zeros((1, 2), QQ) + assert A.shape == (1, 2) + assert A.domain == QQ + + +def test_DomainMatrix_ones(): + A = DomainMatrix.ones((2, 3), QQ) + if GROUND_TYPES != 'flint': + assert A.rep == DDM.ones((2, 3), QQ) + else: + assert A.rep == SDM.ones((2, 3), QQ).to_dfm() + assert A.shape == (2, 3) + assert A.domain == QQ + + +def test_DomainMatrix_diag(): + A = DomainMatrix({0:{0:ZZ(2)}, 1:{1:ZZ(3)}}, (2, 2), ZZ) + assert DomainMatrix.diag([ZZ(2), ZZ(3)], ZZ) == A + + A = DomainMatrix({0:{0:ZZ(2)}, 1:{1:ZZ(3)}}, (3, 4), ZZ) + assert DomainMatrix.diag([ZZ(2), ZZ(3)], ZZ, (3, 4)) == A + + +def test_DomainMatrix_hstack(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ) + C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ) + + AB = DomainMatrix([ + [ZZ(1), ZZ(2), ZZ(5), ZZ(6)], + [ZZ(3), ZZ(4), ZZ(7), ZZ(8)]], (2, 4), ZZ) + ABC = DomainMatrix([ + [ZZ(1), ZZ(2), ZZ(5), ZZ(6), ZZ(9), ZZ(10)], + [ZZ(3), ZZ(4), ZZ(7), ZZ(8), ZZ(11), ZZ(12)]], (2, 6), ZZ) + assert A.hstack(B) == AB + assert A.hstack(B, C) == ABC + + +def test_DomainMatrix_vstack(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ) + C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ) + + AB = DomainMatrix([ + [ZZ(1), ZZ(2)], + [ZZ(3), ZZ(4)], + [ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8)]], (4, 2), ZZ) + ABC = DomainMatrix([ + [ZZ(1), ZZ(2)], + [ZZ(3), ZZ(4)], + [ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8)], + [ZZ(9), ZZ(10)], + [ZZ(11), ZZ(12)]], (6, 2), ZZ) + assert A.vstack(B) == AB + assert A.vstack(B, C) == ABC + + +def test_DomainMatrix_applyfunc(): + A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ) + B = DomainMatrix([[ZZ(2), ZZ(4)]], (1, 2), ZZ) + assert A.applyfunc(lambda x: 2*x) == B + + +def test_DomainMatrix_scalarmul(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + lamda = DomainScalar(QQ(3)/QQ(2), QQ) + assert A * lamda == DomainMatrix([[QQ(3, 2), QQ(3)], [QQ(9, 2), QQ(6)]], (2, 2), QQ) + assert A * 2 == DomainMatrix([[ZZ(2), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ) + assert 2 * A == DomainMatrix([[ZZ(2), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ) + assert A * DomainScalar(ZZ(0), ZZ) == DomainMatrix({}, (2, 2), ZZ) + assert A * DomainScalar(ZZ(1), ZZ) == A + + raises(TypeError, lambda: A * 1.5) + + +def test_DomainMatrix_truediv(): + A = DomainMatrix.from_Matrix(Matrix([[1, 2], [3, 4]])) + lamda = DomainScalar(QQ(3)/QQ(2), QQ) + assert A / lamda == DomainMatrix({0: {0: QQ(2, 3), 1: QQ(4, 3)}, 1: {0: QQ(2), 1: QQ(8, 3)}}, (2, 2), QQ) + b = DomainScalar(ZZ(1), ZZ) + assert A / b == DomainMatrix({0: {0: QQ(1), 1: QQ(2)}, 1: {0: QQ(3), 1: QQ(4)}}, (2, 2), QQ) + + assert A / 1 == DomainMatrix({0: {0: QQ(1), 1: QQ(2)}, 1: {0: QQ(3), 1: QQ(4)}}, (2, 2), QQ) + assert A / 2 == DomainMatrix({0: {0: QQ(1, 2), 1: QQ(1)}, 1: {0: QQ(3, 2), 1: QQ(2)}}, (2, 2), QQ) + + raises(ZeroDivisionError, lambda: A / 0) + raises(TypeError, lambda: A / 1.5) + raises(ZeroDivisionError, lambda: A / DomainScalar(ZZ(0), ZZ)) + + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A.to_field() / 2 == DomainMatrix([[QQ(1, 2), QQ(1)], [QQ(3, 2), QQ(2)]], (2, 2), QQ) + assert A / 2 == DomainMatrix([[QQ(1, 2), QQ(1)], [QQ(3, 2), QQ(2)]], (2, 2), QQ) + assert A.to_field() / QQ(2,3) == DomainMatrix([[QQ(3, 2), QQ(3)], [QQ(9, 2), QQ(6)]], (2, 2), QQ) + + +def test_DomainMatrix_getitem(): + dM = DomainMatrix([ + [ZZ(1), ZZ(2), ZZ(3)], + [ZZ(4), ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ) + + assert dM[1:,:-2] == DomainMatrix([[ZZ(4)], [ZZ(7)]], (2, 1), ZZ) + assert dM[2,:-2] == DomainMatrix([[ZZ(7)]], (1, 1), ZZ) + assert dM[:-2,:-2] == DomainMatrix([[ZZ(1)]], (1, 1), ZZ) + assert dM[:-1,0:2] == DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(4), ZZ(5)]], (2, 2), ZZ) + assert dM[:, -1] == DomainMatrix([[ZZ(3)], [ZZ(6)], [ZZ(9)]], (3, 1), ZZ) + assert dM[-1, :] == DomainMatrix([[ZZ(7), ZZ(8), ZZ(9)]], (1, 3), ZZ) + assert dM[::-1, :] == DomainMatrix([ + [ZZ(7), ZZ(8), ZZ(9)], + [ZZ(4), ZZ(5), ZZ(6)], + [ZZ(1), ZZ(2), ZZ(3)]], (3, 3), ZZ) + + raises(IndexError, lambda: dM[4, :-2]) + raises(IndexError, lambda: dM[:-2, 4]) + + assert dM[1, 2] == DomainScalar(ZZ(6), ZZ) + assert dM[-2, 2] == DomainScalar(ZZ(6), ZZ) + assert dM[1, -2] == DomainScalar(ZZ(5), ZZ) + assert dM[-1, -3] == DomainScalar(ZZ(7), ZZ) + + raises(IndexError, lambda: dM[3, 3]) + raises(IndexError, lambda: dM[1, 4]) + raises(IndexError, lambda: dM[-1, -4]) + + dM = DomainMatrix({0: {0: ZZ(1)}}, (10, 10), ZZ) + assert dM[5, 5] == DomainScalar(ZZ(0), ZZ) + assert dM[0, 0] == DomainScalar(ZZ(1), ZZ) + + dM = DomainMatrix({1: {0: 1}}, (2,1), ZZ) + assert dM[0:, 0] == DomainMatrix({1: {0: 1}}, (2, 1), ZZ) + raises(IndexError, lambda: dM[3, 0]) + + dM = DomainMatrix({2: {2: ZZ(1)}, 4: {4: ZZ(1)}}, (5, 5), ZZ) + assert dM[:2,:2] == DomainMatrix({}, (2, 2), ZZ) + assert dM[2:,2:] == DomainMatrix({0: {0: 1}, 2: {2: 1}}, (3, 3), ZZ) + assert dM[3:,3:] == DomainMatrix({1: {1: 1}}, (2, 2), ZZ) + assert dM[2:, 6:] == DomainMatrix({}, (3, 0), ZZ) + + +def test_DomainMatrix_getitem_sympy(): + dM = DomainMatrix({2: {2: ZZ(2)}, 4: {4: ZZ(1)}}, (5, 5), ZZ) + val1 = dM.getitem_sympy(0, 0) + assert val1 is S.Zero + val2 = dM.getitem_sympy(2, 2) + assert val2 == 2 and isinstance(val2, Integer) + + +def test_DomainMatrix_extract(): + dM1 = DomainMatrix([ + [ZZ(1), ZZ(2), ZZ(3)], + [ZZ(4), ZZ(5), ZZ(6)], + [ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ) + dM2 = DomainMatrix([ + [ZZ(1), ZZ(3)], + [ZZ(7), ZZ(9)]], (2, 2), ZZ) + assert dM1.extract([0, 2], [0, 2]) == dM2 + assert dM1.to_sparse().extract([0, 2], [0, 2]) == dM2.to_sparse() + assert dM1.extract([0, -1], [0, -1]) == dM2 + assert dM1.to_sparse().extract([0, -1], [0, -1]) == dM2.to_sparse() + + dM3 = DomainMatrix([ + [ZZ(1), ZZ(2), ZZ(2)], + [ZZ(4), ZZ(5), ZZ(5)], + [ZZ(4), ZZ(5), ZZ(5)]], (3, 3), ZZ) + assert dM1.extract([0, 1, 1], [0, 1, 1]) == dM3 + assert dM1.to_sparse().extract([0, 1, 1], [0, 1, 1]) == dM3.to_sparse() + + empty = [ + ([], [], (0, 0)), + ([1], [], (1, 0)), + ([], [1], (0, 1)), + ] + for rows, cols, size in empty: + assert dM1.extract(rows, cols) == DomainMatrix.zeros(size, ZZ).to_dense() + assert dM1.to_sparse().extract(rows, cols) == DomainMatrix.zeros(size, ZZ) + + dM = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + bad_indices = [([2], [0]), ([0], [2]), ([-3], [0]), ([0], [-3])] + for rows, cols in bad_indices: + raises(IndexError, lambda: dM.extract(rows, cols)) + raises(IndexError, lambda: dM.to_sparse().extract(rows, cols)) + + +def test_DomainMatrix_setitem(): + dM = DomainMatrix({2: {2: ZZ(1)}, 4: {4: ZZ(1)}}, (5, 5), ZZ) + dM[2, 2] = ZZ(2) + assert dM == DomainMatrix({2: {2: ZZ(2)}, 4: {4: ZZ(1)}}, (5, 5), ZZ) + def setitem(i, j, val): + dM[i, j] = val + raises(TypeError, lambda: setitem(2, 2, QQ(1, 2))) + raises(NotImplementedError, lambda: setitem(slice(1, 2), 2, ZZ(1))) + + +def test_DomainMatrix_pickling(): + import pickle + dM = DomainMatrix({2: {2: ZZ(1)}, 4: {4: ZZ(1)}}, (5, 5), ZZ) + assert pickle.loads(pickle.dumps(dM)) == dM + dM = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert pickle.loads(pickle.dumps(dM)) == dM + + +def test_DomainMatrix_fflu(): + A = DM([[1, 2], [3, 4]], ZZ) + P, L, D, U = A.fflu() + assert P.shape == A.shape + assert L.shape == A.shape + assert D.shape == A.shape + assert U.shape == A.shape + assert P == DM([[1, 0], [0, 1]], ZZ) + assert L == DM([[1, 0], [3, -2]], ZZ) + assert D == DM([[1, 0], [0, -2]], ZZ) + assert U == DM([[1, 2], [0, -2]], ZZ) + di, d = D.inv_den() + assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_domainscalar.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_domainscalar.py new file mode 100644 index 0000000000000000000000000000000000000000..8c507caf079cc62ba23ba171a50d0d27c98eb6d9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_domainscalar.py @@ -0,0 +1,153 @@ +from sympy.testing.pytest import raises + +from sympy.core.symbol import S +from sympy.polys import ZZ, QQ +from sympy.polys.matrices.domainscalar import DomainScalar +from sympy.polys.matrices.domainmatrix import DomainMatrix + + +def test_DomainScalar___new__(): + raises(TypeError, lambda: DomainScalar(ZZ(1), QQ)) + raises(TypeError, lambda: DomainScalar(ZZ(1), 1)) + + +def test_DomainScalar_new(): + A = DomainScalar(ZZ(1), ZZ) + B = A.new(ZZ(4), ZZ) + assert B == DomainScalar(ZZ(4), ZZ) + + +def test_DomainScalar_repr(): + A = DomainScalar(ZZ(1), ZZ) + assert repr(A) in {'1', 'mpz(1)'} + + +def test_DomainScalar_from_sympy(): + expr = S(1) + B = DomainScalar.from_sympy(expr) + assert B == DomainScalar(ZZ(1), ZZ) + + +def test_DomainScalar_to_sympy(): + B = DomainScalar(ZZ(1), ZZ) + expr = B.to_sympy() + assert expr.is_Integer and expr == 1 + + +def test_DomainScalar_to_domain(): + A = DomainScalar(ZZ(1), ZZ) + B = A.to_domain(QQ) + assert B == DomainScalar(QQ(1), QQ) + + +def test_DomainScalar_convert_to(): + A = DomainScalar(ZZ(1), ZZ) + B = A.convert_to(QQ) + assert B == DomainScalar(QQ(1), QQ) + + +def test_DomainScalar_unify(): + A = DomainScalar(ZZ(1), ZZ) + B = DomainScalar(QQ(2), QQ) + A, B = A.unify(B) + assert A.domain == B.domain == QQ + + +def test_DomainScalar_add(): + A = DomainScalar(ZZ(1), ZZ) + B = DomainScalar(QQ(2), QQ) + assert A + B == DomainScalar(QQ(3), QQ) + + raises(TypeError, lambda: A + 1.5) + +def test_DomainScalar_sub(): + A = DomainScalar(ZZ(1), ZZ) + B = DomainScalar(QQ(2), QQ) + assert A - B == DomainScalar(QQ(-1), QQ) + + raises(TypeError, lambda: A - 1.5) + +def test_DomainScalar_mul(): + A = DomainScalar(ZZ(1), ZZ) + B = DomainScalar(QQ(2), QQ) + dm = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + assert A * B == DomainScalar(QQ(2), QQ) + assert A * dm == dm + assert B * 2 == DomainScalar(QQ(4), QQ) + + raises(TypeError, lambda: A * 1.5) + + +def test_DomainScalar_floordiv(): + A = DomainScalar(ZZ(-5), ZZ) + B = DomainScalar(QQ(2), QQ) + assert A // B == DomainScalar(QQ(-5, 2), QQ) + C = DomainScalar(ZZ(2), ZZ) + assert A // C == DomainScalar(ZZ(-3), ZZ) + + raises(TypeError, lambda: A // 1.5) + + +def test_DomainScalar_mod(): + A = DomainScalar(ZZ(5), ZZ) + B = DomainScalar(QQ(2), QQ) + assert A % B == DomainScalar(QQ(0), QQ) + C = DomainScalar(ZZ(2), ZZ) + assert A % C == DomainScalar(ZZ(1), ZZ) + + raises(TypeError, lambda: A % 1.5) + + +def test_DomainScalar_divmod(): + A = DomainScalar(ZZ(5), ZZ) + B = DomainScalar(QQ(2), QQ) + assert divmod(A, B) == (DomainScalar(QQ(5, 2), QQ), DomainScalar(QQ(0), QQ)) + C = DomainScalar(ZZ(2), ZZ) + assert divmod(A, C) == (DomainScalar(ZZ(2), ZZ), DomainScalar(ZZ(1), ZZ)) + + raises(TypeError, lambda: divmod(A, 1.5)) + + +def test_DomainScalar_pow(): + A = DomainScalar(ZZ(-5), ZZ) + B = A**(2) + assert B == DomainScalar(ZZ(25), ZZ) + + raises(TypeError, lambda: A**(1.5)) + + +def test_DomainScalar_pos(): + A = DomainScalar(QQ(2), QQ) + B = DomainScalar(QQ(2), QQ) + assert +A == B + + +def test_DomainScalar_neg(): + A = DomainScalar(QQ(2), QQ) + B = DomainScalar(QQ(-2), QQ) + assert -A == B + + +def test_DomainScalar_eq(): + A = DomainScalar(QQ(2), QQ) + assert A == A + B = DomainScalar(ZZ(-5), ZZ) + assert A != B + C = DomainScalar(ZZ(2), ZZ) + assert A != C + D = [1] + assert A != D + + +def test_DomainScalar_isZero(): + A = DomainScalar(ZZ(0), ZZ) + assert A.is_zero() == True + B = DomainScalar(ZZ(1), ZZ) + assert B.is_zero() == False + + +def test_DomainScalar_isOne(): + A = DomainScalar(ZZ(1), ZZ) + assert A.is_one() == True + B = DomainScalar(ZZ(0), ZZ) + assert B.is_one() == False diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_eigen.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_eigen.py new file mode 100644 index 0000000000000000000000000000000000000000..70482eab686d5b4e1c45d552f5eccb5bdaa9e1ed --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_eigen.py @@ -0,0 +1,90 @@ +""" +Tests for the sympy.polys.matrices.eigen module +""" + +from sympy.core.singleton import S +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.matrices.dense import Matrix + +from sympy.polys.agca.extensions import FiniteExtension +from sympy.polys.domains import QQ +from sympy.polys.polytools import Poly +from sympy.polys.rootoftools import CRootOf +from sympy.polys.matrices.domainmatrix import DomainMatrix + +from sympy.polys.matrices.eigen import dom_eigenvects, dom_eigenvects_to_sympy + + +def test_dom_eigenvects_rational(): + # Rational eigenvalues + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ) + rational_eigenvects = [ + (QQ, QQ(3), 1, DomainMatrix([[QQ(1), QQ(1)]], (1, 2), QQ)), + (QQ, QQ(0), 1, DomainMatrix([[QQ(-2), QQ(1)]], (1, 2), QQ)), + ] + assert dom_eigenvects(A) == (rational_eigenvects, []) + + # Test converting to Expr: + sympy_eigenvects = [ + (S(3), 1, [Matrix([1, 1])]), + (S(0), 1, [Matrix([-2, 1])]), + ] + assert dom_eigenvects_to_sympy(rational_eigenvects, [], Matrix) == sympy_eigenvects + + +def test_dom_eigenvects_algebraic(): + # Algebraic eigenvalues + A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ) + Avects = dom_eigenvects(A) + + # Extract the dummy to build the expected result: + lamda = Avects[1][0][1].gens[0] + irreducible = Poly(lamda**2 - 5*lamda - 2, lamda, domain=QQ) + K = FiniteExtension(irreducible) + KK = K.from_sympy + algebraic_eigenvects = [ + (K, irreducible, 1, DomainMatrix([[KK((lamda-4)/3), KK(1)]], (1, 2), K)), + ] + assert Avects == ([], algebraic_eigenvects) + + # Test converting to Expr: + sympy_eigenvects = [ + (S(5)/2 - sqrt(33)/2, 1, [Matrix([[-sqrt(33)/6 - S(1)/2], [1]])]), + (S(5)/2 + sqrt(33)/2, 1, [Matrix([[-S(1)/2 + sqrt(33)/6], [1]])]), + ] + assert dom_eigenvects_to_sympy([], algebraic_eigenvects, Matrix) == sympy_eigenvects + + +def test_dom_eigenvects_rootof(): + # Algebraic eigenvalues + A = DomainMatrix([ + [0, 0, 0, 0, -1], + [1, 0, 0, 0, 1], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 1, 0]], (5, 5), QQ) + Avects = dom_eigenvects(A) + + # Extract the dummy to build the expected result: + lamda = Avects[1][0][1].gens[0] + irreducible = Poly(lamda**5 - lamda + 1, lamda, domain=QQ) + K = FiniteExtension(irreducible) + KK = K.from_sympy + algebraic_eigenvects = [ + (K, irreducible, 1, + DomainMatrix([ + [KK(lamda**4-1), KK(lamda**3), KK(lamda**2), KK(lamda), KK(1)] + ], (1, 5), K)), + ] + assert Avects == ([], algebraic_eigenvects) + + # Test converting to Expr (slow): + l0, l1, l2, l3, l4 = [CRootOf(lamda**5 - lamda + 1, i) for i in range(5)] + sympy_eigenvects = [ + (l0, 1, [Matrix([-1 + l0**4, l0**3, l0**2, l0, 1])]), + (l1, 1, [Matrix([-1 + l1**4, l1**3, l1**2, l1, 1])]), + (l2, 1, [Matrix([-1 + l2**4, l2**3, l2**2, l2, 1])]), + (l3, 1, [Matrix([-1 + l3**4, l3**3, l3**2, l3, 1])]), + (l4, 1, [Matrix([-1 + l4**4, l4**3, l4**2, l4, 1])]), + ] + assert dom_eigenvects_to_sympy([], algebraic_eigenvects, Matrix) == sympy_eigenvects diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_fflu.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_fflu.py new file mode 100644 index 0000000000000000000000000000000000000000..0a4676ce0c3ee2d495b7011ddc48db8c8c40648b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_fflu.py @@ -0,0 +1,301 @@ +from sympy.polys.matrices import DomainMatrix, DM +from sympy.polys.domains import ZZ, QQ +from sympy import Matrix +import pytest + + +FFLU_EXAMPLES = [ + ( + 'zz_2x3', + DM([[1, 2, 3], [4, 5, 6]], ZZ), + DM([[1, 0], [0, 1]], ZZ), + DM([[1, 0], [4, -3]], ZZ), + DM([[1, 0], [0, -3]], ZZ), + DM([[1, 2, 3], [0, -3, -6]], ZZ), + ), + + ( + 'zz_2x2', + DM([[4, 3], [6, 3]], ZZ), + DM([[1, 0], [0, 1]], ZZ), + DM([[1, 0], [6, -6]], ZZ), + DM([[4, 0], [0, -3]], ZZ), + DM([[4, 3], [0, -3]], ZZ), + ), + + ( + 'zz_3x2', + DM([[1, 2], [3, 4], [5, 6]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[1, 0, 0], [3, 1, 0], [5, 2, 1]], ZZ), + DM([[1, 0], [0, -2]], ZZ), + DM([[1, 2], [0, -2], [0, 0]], ZZ), + ), + + ( + 'zz_3x3', + DM([[1, 2, 3], [4, 5, 6], [7, 8, 9]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[1, 0, 0], [4, 1, 0], [7, 2, 1]], ZZ), + DM([[1, 0, 0], [0, -3, 0], [0, 0, 0]], ZZ), + DM([[1, 2, 3], [0, -3, -6], [0, 0, 0]], ZZ), + ), + + ( + 'zz_zero', + DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ), + DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ), + ), + + ( + 'zz_empty', + DM([], ZZ), + DM([], ZZ), + DM([], ZZ), + DM([], ZZ), + DM([], ZZ), + ), + + ( + 'zz_empty_0x2', + DomainMatrix([], (0, 2), ZZ), + DomainMatrix([], (0, 0), ZZ), + DomainMatrix([], (0, 0), ZZ), + DomainMatrix([], (0, 0), ZZ), + DomainMatrix([], (0, 2), ZZ) + ), + + ( + + 'zz_empty_2x0', + DomainMatrix([[], []], (2, 0), ZZ), + DomainMatrix.eye((2, 2), ZZ), + DomainMatrix.eye((2, 2), ZZ), + DomainMatrix.eye((2, 2), ZZ), + DomainMatrix([[], []], (2, 0), ZZ) + + ), + + ( + 'zz_negative', + DM([[-1, -2], [-3, -4]], ZZ), + DM([[1, 0], [0, 1]], ZZ), + DM([[-1, 0], [-3, -2]], ZZ), + DM([[-1, 0], [0, 2]], ZZ), + DM([[-1, -2], [0, -2]], ZZ), + ), + + ( + 'zz_mixed_signs', + DM([[1, -2], [-3, 4]], ZZ), + DM([[1, 0], [0, 1]], ZZ), + DM([[1, 0], [-3, 1]], ZZ), + DM([[1, 0], [0, -2]], ZZ), + DM([[1, -2], [0, -2]], ZZ), + ), + + ( + 'zz_upper_triangular', + DM([[1, 2, 3], [0, 4, 5], [0, 0, 6]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[1, 0, 0], [0, 4, 0], [0, 0, 24]], ZZ), + DM([[1, 0, 0], [0, 4, 0], [0, 0, 96]], ZZ), + DM([[1, 2, 3], [0, 4, 5], [0, 0, 24]], ZZ), + ), + + ( + 'zz_lower_triangular', + DM([[1, 0, 0], [2, 3, 0], [4, 5, 6]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[1, 0, 0], [2, 3, 0], [4, 5, 18]], ZZ), + DM([[1, 0, 0], [0, 3, 0], [0, 0, 54]], ZZ), + DM([[1, 0, 0], [0, 3, 0], [0, 0, 18]], ZZ), + ), + + ( + 'zz_diagonal', + DM([[2, 0, 0], [0, 3, 0], [0, 0, 4]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[2, 0, 0], [0, 6, 0], [0, 0, 24]], ZZ), + DM([[2, 0, 0], [0, 12, 0], [0, 0, 144]], ZZ), + DM([[2, 0, 0], [0, 6, 0], [0, 0, 24]], ZZ) + + ), + + ( + 'rank_deficient_3x3', + DM([[1, 2, 3], [2, 4, 6], [3, 6, 9]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[1, 0, 0], [2, 1, 0], [3, 0, 1]], ZZ), + DM([[1, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ), + DM([[1, 2, 3], [0, 0, 0], [0, 0, 0]], ZZ), + ), + + ( + 'zz_1x1', + DM([[5]], ZZ), + DM([[1]], ZZ), + DM([[5]], ZZ), + DM([[5]], ZZ), + DM([[5]], ZZ), + ), + + ( + 'zz_nx1_2rows', + DM([[81], [54]], ZZ), + DM([[1, 0], [0, 1]], ZZ), + DM([[81, 0], [54, 81]], ZZ), + DM([[81, 0], [0, 81]], ZZ), + DM([[81], [0]], ZZ), + ), + + ( + 'zz_nx2_3rows', + DM([[2, 7], [7, 45], [25, 84]], ZZ), + DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ), + DM([[2, 0, 0], [7, 82, 0], [25, 41, 41]], ZZ), + DM([[2, 0, 0], [0, 82, 0], [0, 0, 41]], ZZ), + DM([[2, 7], [0, 82], [0, 0]], ZZ), + ), + + ( + + 'zz_1x2', + DM([[0, 28]], ZZ), + DM([[1]], ZZ), + DM([[28]], ZZ), + DM([[28]], ZZ), + DM([[0, 28]], ZZ) + ), + + ( + 'zz_nx3_4rows', + DM([[84, 30, 9], [20, 59, 13], [53, 46, 81], [63, 48, 29]], ZZ), + DM([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], ZZ), + DM([[84, 0, 0, 0], [20, 365904, 0, 0], [53, 303411, 303411, 0], [63, 303411, 303411, 303411]], ZZ), + DM([[84, 0, 0, 0], [0, 365904, 0, 0], [0, 0, 1321658316, 0], [0, 0, 0, 303411]], ZZ), + DM([[84, 30, 9], [0, 365904, 13], [0, 0, 1321658316], [0, 0, 0]], ZZ), + ), + + ( + 'fflu_row_swap', + DM([[0, 1, 2], [3, 4, 5], [6, 7, 8]], ZZ), + DM([[0, 1, 0], [1, 0, 0], [0, 0, 1]], ZZ), + DM([[3, 0, 0], [0, 3, 0], [6, -3, 1]], ZZ), + DM([[3, 0, 0], [0, 9, 0], [0, 0, 3]], ZZ), + DM([[3, 4, 5], [0, 3, 6], [0, 0, 0]], ZZ) + ), +] + + +def _check_fflu(A, P, L, D, U): + P_field = P.to_field().to_dense() + L_field = L.to_field().to_dense() + D_field = D.to_field().to_dense() + U_field = U.to_field().to_dense() + m, n = A.shape + assert P_field.shape == (m, m) + assert L_field.shape == (m, m) + assert D_field.shape == (m, m) + assert U_field.shape == (m, n) + assert L_field.is_lower + assert D_field.is_diagonal + di, d = D.inv_den() + assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U) + assert U_field.is_upper + + +def _to_DM(A, ans): + if isinstance(A, DomainMatrix): + return A + elif isinstance(A, Matrix): + return A.to_DM(ans.domain) + return DomainMatrix(A.to_list(), A.shape, A.domain) + + +def _check_fflu_result(result, A, P_ans, L_ans, D_ans, U_ans): + P, L, D, U = result + P = _to_DM(P, P_ans) + L = _to_DM(L, L_ans) + D = _to_DM(D, D_ans) + U = _to_DM(U, U_ans) + A = _to_DM(A, P_ans) + m, n = A.shape + assert P.shape == (m, m) + assert L.shape == (m, m) + assert D.shape == (m, m) + assert U.shape == (m, n) + assert L.is_lower + assert D.is_diagonal + di, d = D.inv_den() + assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U) + assert U.is_upper + + +@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES) +def test_dm_dense_fflu(name, A, P_ans, L_ans, D_ans, U_ans): + A = A.to_dense() + _check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans) + + +@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES) +def test_dm_sparse_fflu(name, A, P_ans, L_ans, D_ans, U_ans): + A = A.to_sparse() + _check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans) + + +@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES) +def test_ddm_fflu(name, A, P_ans, L_ans, D_ans, U_ans): + A = A.to_ddm() + _check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans) + + +@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES) +def test_sdm_fflu(name, A, P_ans, L_ans, D_ans, U_ans): + A = A.to_sdm() + _check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans) + + +@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES) +def test_dfm_fflu(name, A, P_ans, L_ans, D_ans, U_ans): + pytest.importorskip('flint') + if A.domain not in (ZZ, QQ) and not A.domain.is_FF: + pytest.skip("Domain not supported by DFM") + A = A.to_dfm() + _check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans) + + +def test_fflu_empty_matrix(): + A = DomainMatrix([], (0, 0), ZZ) + P, L, D, U = A.fflu() + assert P.shape == (0, 0) + assert L.shape == (0, 0) + assert D.shape == (0, 0) + assert U.shape == (0, 0) + + +def test_fflu_properties(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ) + P, L, D, U = A.fflu() + assert P.shape == (2, 2) + assert L.shape == (2, 2) + assert D.shape == (2, 2) + assert U.shape == (2, 2) + assert L.is_lower + assert U.is_upper + assert D.is_diagonal + di, d = D.inv_den() + assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U) + + +def test_fflu_rank_deficient(): + A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(2), ZZ(4)]], (2, 2), ZZ) + P, L, D, U = A.fflu() + assert P.shape == (2, 2) + assert L.shape == (2, 2) + assert D.shape == (2, 2) + assert U.shape == (2, 2) + assert U.getitem_sympy(1, 1) == 0 diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_inverse.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_inverse.py new file mode 100644 index 0000000000000000000000000000000000000000..47c82799324518bd7d1cc2405ade0aa0a5a4f6e9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_inverse.py @@ -0,0 +1,193 @@ +from sympy import ZZ, Matrix +from sympy.polys.matrices import DM, DomainMatrix +from sympy.polys.matrices.dense import ddm_iinv +from sympy.polys.matrices.exceptions import DMNonInvertibleMatrixError +from sympy.matrices.exceptions import NonInvertibleMatrixError + +import pytest +from sympy.testing.pytest import raises +from sympy.core.numbers import all_close + +from sympy.abc import x + + +# Examples are given as adjugate matrix and determinant adj_det should match +# these exactly but inv_den only matches after cancel_denom. + + +INVERSE_EXAMPLES = [ + + ( + 'zz_1', + DomainMatrix([], (0, 0), ZZ), + DomainMatrix([], (0, 0), ZZ), + ZZ(1), + ), + + ( + 'zz_2', + DM([[2]], ZZ), + DM([[1]], ZZ), + ZZ(2), + ), + + ( + 'zz_3', + DM([[2, 0], + [0, 2]], ZZ), + DM([[2, 0], + [0, 2]], ZZ), + ZZ(4), + ), + + ( + 'zz_4', + DM([[1, 2], + [3, 4]], ZZ), + DM([[ 4, -2], + [-3, 1]], ZZ), + ZZ(-2), + ), + + ( + 'zz_5', + DM([[2, 2, 0], + [0, 2, 2], + [0, 0, 2]], ZZ), + DM([[4, -4, 4], + [0, 4, -4], + [0, 0, 4]], ZZ), + ZZ(8), + ), + + ( + 'zz_6', + DM([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]], ZZ), + DM([[-3, 6, -3], + [ 6, -12, 6], + [-3, 6, -3]], ZZ), + ZZ(0), + ), +] + + +@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES) +def test_Matrix_inv(name, A, A_inv, den): + + def _check(**kwargs): + if den != 0: + assert A.inv(**kwargs) == A_inv + else: + raises(NonInvertibleMatrixError, lambda: A.inv(**kwargs)) + + K = A.domain + A = A.to_Matrix() + A_inv = A_inv.to_Matrix() / K.to_sympy(den) + _check() + for method in ['GE', 'LU', 'ADJ', 'CH', 'LDL', 'QR']: + _check(method=method) + + +@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES) +def test_dm_inv_den(name, A, A_inv, den): + if den != 0: + A_inv_f, den_f = A.inv_den() + assert A_inv_f.cancel_denom(den_f) == A_inv.cancel_denom(den) + else: + raises(DMNonInvertibleMatrixError, lambda: A.inv_den()) + + +@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES) +def test_dm_inv(name, A, A_inv, den): + A = A.to_field() + if den != 0: + A_inv = A_inv.to_field() / den + assert A.inv() == A_inv + else: + raises(DMNonInvertibleMatrixError, lambda: A.inv()) + + +@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES) +def test_ddm_inv(name, A, A_inv, den): + A = A.to_field().to_ddm() + if den != 0: + A_inv = (A_inv.to_field() / den).to_ddm() + assert A.inv() == A_inv + else: + raises(DMNonInvertibleMatrixError, lambda: A.inv()) + + +@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES) +def test_sdm_inv(name, A, A_inv, den): + A = A.to_field().to_sdm() + if den != 0: + A_inv = (A_inv.to_field() / den).to_sdm() + assert A.inv() == A_inv + else: + raises(DMNonInvertibleMatrixError, lambda: A.inv()) + + +@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES) +def test_dense_ddm_iinv(name, A, A_inv, den): + A = A.to_field().to_ddm().copy() + K = A.domain + A_result = A.copy() + if den != 0: + A_inv = (A_inv.to_field() / den).to_ddm() + ddm_iinv(A_result, A, K) + assert A_result == A_inv + else: + raises(DMNonInvertibleMatrixError, lambda: ddm_iinv(A_result, A, K)) + + +@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES) +def test_Matrix_adjugate(name, A, A_inv, den): + A = A.to_Matrix() + A_inv = A_inv.to_Matrix() + assert A.adjugate() == A_inv + for method in ["bareiss", "berkowitz", "bird", "laplace", "lu"]: + assert A.adjugate(method=method) == A_inv + + +@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES) +def test_dm_adj_det(name, A, A_inv, den): + assert A.adj_det() == (A_inv, den) + + +def test_inverse_inexact(): + + M = Matrix([[x-0.3, -0.06, -0.22], + [-0.46, x-0.48, -0.41], + [-0.14, -0.39, x-0.64]]) + + Mn = Matrix([[1.0*x**2 - 1.12*x + 0.1473, 0.06*x + 0.0474, 0.22*x - 0.081], + [0.46*x - 0.237, 1.0*x**2 - 0.94*x + 0.1612, 0.41*x - 0.0218], + [0.14*x + 0.1122, 0.39*x - 0.1086, 1.0*x**2 - 0.78*x + 0.1164]]) + + d = 1.0*x**3 - 1.42*x**2 + 0.4249*x - 0.0546540000000002 + + Mi = Mn / d + + M_dm = M.to_DM() + M_dmd = M_dm.to_dense() + M_dm_num, M_dm_den = M_dm.inv_den() + M_dmd_num, M_dmd_den = M_dmd.inv_den() + + # XXX: We don't check M_dm().to_field().inv() which currently uses division + # and produces a more complicate result from gcd cancellation failing. + # DomainMatrix.inv() over RR(x) should be changed to clear denominators and + # use DomainMatrix.inv_den(). + + Minvs = [ + M.inv(), + (M_dm_num.to_field() / M_dm_den).to_Matrix(), + (M_dmd_num.to_field() / M_dmd_den).to_Matrix(), + M_dm_num.to_Matrix() / M_dm_den.as_expr(), + M_dmd_num.to_Matrix() / M_dmd_den.as_expr(), + ] + + for Minv in Minvs: + for Mi1, Mi2 in zip(Minv.flat(), Mi.flat()): + assert all_close(Mi2, Mi1) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_linsolve.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_linsolve.py new file mode 100644 index 0000000000000000000000000000000000000000..25300ef2cb4792e4424c9c15c0bbbc313ce062e6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_linsolve.py @@ -0,0 +1,112 @@ +# +# test_linsolve.py +# +# Test the internal implementation of linsolve. +# + +from sympy.testing.pytest import raises + +from sympy.core.numbers import I +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.abc import x, y, z + +from sympy.polys.matrices.linsolve import _linsolve +from sympy.polys.solvers import PolyNonlinearError + + +def test__linsolve(): + assert _linsolve([], [x]) == {x:x} + assert _linsolve([S.Zero], [x]) == {x:x} + assert _linsolve([x-1,x-2], [x]) is None + assert _linsolve([x-1], [x]) == {x:1} + assert _linsolve([x-1, y], [x, y]) == {x:1, y:S.Zero} + assert _linsolve([2*I], [x]) is None + raises(PolyNonlinearError, lambda: _linsolve([x*(1 + x)], [x])) + + +def test__linsolve_float(): + + # This should give the exact answer: + eqs = [ + y - x, + y - 0.0216 * x + ] + # Should _linsolve return floats here? + sol = {x:0, y:0} + assert _linsolve(eqs, (x, y)) == sol + + # Other cases should be close to eps + + def all_close(sol1, sol2, eps=1e-15): + close = lambda a, b: abs(a - b) < eps + assert sol1.keys() == sol2.keys() + return all(close(sol1[s], sol2[s]) for s in sol1) + + eqs = [ + 0.8*x + 0.8*z + 0.2, + 0.9*x + 0.7*y + 0.2*z + 0.9, + 0.7*x + 0.2*y + 0.2*z + 0.5 + ] + sol_exact = {x:-29/42, y:-11/21, z:37/84} + sol_linsolve = _linsolve(eqs, [x,y,z]) + assert all_close(sol_exact, sol_linsolve) + + eqs = [ + 0.9*x + 0.3*y + 0.4*z + 0.6, + 0.6*x + 0.9*y + 0.1*z + 0.7, + 0.4*x + 0.6*y + 0.9*z + 0.5 + ] + sol_exact = {x:-88/175, y:-46/105, z:-1/25} + sol_linsolve = _linsolve(eqs, [x,y,z]) + assert all_close(sol_exact, sol_linsolve) + + eqs = [ + 0.4*x + 0.3*y + 0.6*z + 0.7, + 0.4*x + 0.3*y + 0.9*z + 0.9, + 0.7*x + 0.9*y, + ] + sol_exact = {x:-9/5, y:7/5, z:-2/3} + sol_linsolve = _linsolve(eqs, [x,y,z]) + assert all_close(sol_exact, sol_linsolve) + + eqs = [ + x*(0.7 + 0.6*I) + y*(0.4 + 0.7*I) + z*(0.9 + 0.1*I) + 0.5, + 0.2*I*x + 0.2*I*y + z*(0.9 + 0.2*I) + 0.1, + x*(0.9 + 0.7*I) + y*(0.9 + 0.7*I) + z*(0.9 + 0.4*I) + 0.4, + ] + sol_exact = { + x:-6157/7995 - 411/5330*I, + y:8519/15990 + 1784/7995*I, + z:-34/533 + 107/1599*I, + } + sol_linsolve = _linsolve(eqs, [x,y,z]) + assert all_close(sol_exact, sol_linsolve) + + # XXX: This system for x and y over RR(z) is problematic. + # + # eqs = [ + # x*(0.2*z + 0.9) + y*(0.5*z + 0.8) + 0.6, + # 0.1*x*z + y*(0.1*z + 0.6) + 0.9, + # ] + # + # linsolve(eqs, [x, y]) + # The solution for x comes out as + # + # -3.9e-5*z**2 - 3.6e-5*z - 8.67361737988404e-20 + # x = ---------------------------------------------- + # 3.0e-6*z**3 - 1.3e-5*z**2 - 5.4e-5*z + # + # The 8e-20 in the numerator should be zero which would allow z to cancel + # from top and bottom. It should be possible to avoid this somehow because + # the inverse of the matrix only has a quadratic factor (the determinant) + # in the denominator. + + +def test__linsolve_deprecated(): + raises(PolyNonlinearError, lambda: + _linsolve([Eq(x**2, x**2 + y)], [x, y])) + raises(PolyNonlinearError, lambda: + _linsolve([(x + y)**2 - x**2], [x])) + raises(PolyNonlinearError, lambda: + _linsolve([Eq((x + y)**2, x**2)], [x])) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_lll.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_lll.py new file mode 100644 index 0000000000000000000000000000000000000000..2cf91a00703532f02d763656d6117018fbc496cf --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_lll.py @@ -0,0 +1,145 @@ +from sympy.polys.domains import ZZ, QQ +from sympy.polys.matrices import DM +from sympy.polys.matrices.domainmatrix import DomainMatrix +from sympy.polys.matrices.exceptions import DMRankError, DMValueError, DMShapeError, DMDomainError +from sympy.polys.matrices.lll import _ddm_lll, ddm_lll, ddm_lll_transform +from sympy.testing.pytest import raises + + +def test_lll(): + normal_test_data = [ + ( + DM([[1, 0, 0, 0, -20160], + [0, 1, 0, 0, 33768], + [0, 0, 1, 0, 39578], + [0, 0, 0, 1, 47757]], ZZ), + DM([[10, -3, -2, 8, -4], + [3, -9, 8, 1, -11], + [-3, 13, -9, -3, -9], + [-12, -7, -11, 9, -1]], ZZ) + ), + ( + DM([[20, 52, 3456], + [14, 31, -1], + [34, -442, 0]], ZZ), + DM([[14, 31, -1], + [188, -101, -11], + [236, 13, 3443]], ZZ) + ), + ( + DM([[34, -1, -86, 12], + [-54, 34, 55, 678], + [23, 3498, 234, 6783], + [87, 49, 665, 11]], ZZ), + DM([[34, -1, -86, 12], + [291, 43, 149, 83], + [-54, 34, 55, 678], + [-189, 3077, -184, -223]], ZZ) + ) + ] + delta = QQ(5, 6) + for basis_dm, reduced_dm in normal_test_data: + reduced = _ddm_lll(basis_dm.rep.to_ddm(), delta=delta)[0] + assert reduced == reduced_dm.rep.to_ddm() + + reduced = ddm_lll(basis_dm.rep.to_ddm(), delta=delta) + assert reduced == reduced_dm.rep.to_ddm() + + reduced, transform = _ddm_lll(basis_dm.rep.to_ddm(), delta=delta, return_transform=True) + assert reduced == reduced_dm.rep.to_ddm() + assert transform.matmul(basis_dm.rep.to_ddm()) == reduced_dm.rep.to_ddm() + + reduced, transform = ddm_lll_transform(basis_dm.rep.to_ddm(), delta=delta) + assert reduced == reduced_dm.rep.to_ddm() + assert transform.matmul(basis_dm.rep.to_ddm()) == reduced_dm.rep.to_ddm() + + reduced = basis_dm.rep.lll(delta=delta) + assert reduced == reduced_dm.rep + + reduced, transform = basis_dm.rep.lll_transform(delta=delta) + assert reduced == reduced_dm.rep + assert transform.matmul(basis_dm.rep) == reduced_dm.rep + + reduced = basis_dm.rep.to_sdm().lll(delta=delta) + assert reduced == reduced_dm.rep.to_sdm() + + reduced, transform = basis_dm.rep.to_sdm().lll_transform(delta=delta) + assert reduced == reduced_dm.rep.to_sdm() + assert transform.matmul(basis_dm.rep.to_sdm()) == reduced_dm.rep.to_sdm() + + reduced = basis_dm.lll(delta=delta) + assert reduced == reduced_dm + + reduced, transform = basis_dm.lll_transform(delta=delta) + assert reduced == reduced_dm + assert transform.matmul(basis_dm) == reduced_dm + + +def test_lll_linear_dependent(): + linear_dependent_test_data = [ + DM([[0, -1, -2, -3], + [1, 0, -1, -2], + [2, 1, 0, -1], + [3, 2, 1, 0]], ZZ), + DM([[1, 0, 0, 1], + [0, 1, 0, 1], + [0, 0, 1, 1], + [1, 2, 3, 6]], ZZ), + DM([[3, -5, 1], + [4, 6, 0], + [10, -4, 2]], ZZ) + ] + for not_basis in linear_dependent_test_data: + raises(DMRankError, lambda: _ddm_lll(not_basis.rep.to_ddm())) + raises(DMRankError, lambda: ddm_lll(not_basis.rep.to_ddm())) + raises(DMRankError, lambda: not_basis.rep.lll()) + raises(DMRankError, lambda: not_basis.rep.to_sdm().lll()) + raises(DMRankError, lambda: not_basis.lll()) + raises(DMRankError, lambda: _ddm_lll(not_basis.rep.to_ddm(), return_transform=True)) + raises(DMRankError, lambda: ddm_lll_transform(not_basis.rep.to_ddm())) + raises(DMRankError, lambda: not_basis.rep.lll_transform()) + raises(DMRankError, lambda: not_basis.rep.to_sdm().lll_transform()) + raises(DMRankError, lambda: not_basis.lll_transform()) + + +def test_lll_wrong_delta(): + dummy_matrix = DomainMatrix.ones((3, 3), ZZ) + for wrong_delta in [QQ(-1, 4), QQ(0, 1), QQ(1, 4), QQ(1, 1), QQ(100, 1)]: + raises(DMValueError, lambda: _ddm_lll(dummy_matrix.rep, delta=wrong_delta)) + raises(DMValueError, lambda: ddm_lll(dummy_matrix.rep, delta=wrong_delta)) + raises(DMValueError, lambda: dummy_matrix.rep.lll(delta=wrong_delta)) + raises(DMValueError, lambda: dummy_matrix.rep.to_sdm().lll(delta=wrong_delta)) + raises(DMValueError, lambda: dummy_matrix.lll(delta=wrong_delta)) + raises(DMValueError, lambda: _ddm_lll(dummy_matrix.rep, delta=wrong_delta, return_transform=True)) + raises(DMValueError, lambda: ddm_lll_transform(dummy_matrix.rep, delta=wrong_delta)) + raises(DMValueError, lambda: dummy_matrix.rep.lll_transform(delta=wrong_delta)) + raises(DMValueError, lambda: dummy_matrix.rep.to_sdm().lll_transform(delta=wrong_delta)) + raises(DMValueError, lambda: dummy_matrix.lll_transform(delta=wrong_delta)) + + +def test_lll_wrong_shape(): + wrong_shape_matrix = DomainMatrix.ones((4, 3), ZZ) + raises(DMShapeError, lambda: _ddm_lll(wrong_shape_matrix.rep)) + raises(DMShapeError, lambda: ddm_lll(wrong_shape_matrix.rep)) + raises(DMShapeError, lambda: wrong_shape_matrix.rep.lll()) + raises(DMShapeError, lambda: wrong_shape_matrix.rep.to_sdm().lll()) + raises(DMShapeError, lambda: wrong_shape_matrix.lll()) + raises(DMShapeError, lambda: _ddm_lll(wrong_shape_matrix.rep, return_transform=True)) + raises(DMShapeError, lambda: ddm_lll_transform(wrong_shape_matrix.rep)) + raises(DMShapeError, lambda: wrong_shape_matrix.rep.lll_transform()) + raises(DMShapeError, lambda: wrong_shape_matrix.rep.to_sdm().lll_transform()) + raises(DMShapeError, lambda: wrong_shape_matrix.lll_transform()) + + +def test_lll_wrong_domain(): + wrong_domain_matrix = DomainMatrix.ones((3, 3), QQ) + raises(DMDomainError, lambda: _ddm_lll(wrong_domain_matrix.rep)) + raises(DMDomainError, lambda: ddm_lll(wrong_domain_matrix.rep)) + raises(DMDomainError, lambda: wrong_domain_matrix.rep.lll()) + raises(DMDomainError, lambda: wrong_domain_matrix.rep.to_sdm().lll()) + raises(DMDomainError, lambda: wrong_domain_matrix.lll()) + raises(DMDomainError, lambda: _ddm_lll(wrong_domain_matrix.rep, return_transform=True)) + raises(DMDomainError, lambda: ddm_lll_transform(wrong_domain_matrix.rep)) + raises(DMDomainError, lambda: wrong_domain_matrix.rep.lll_transform()) + raises(DMDomainError, lambda: wrong_domain_matrix.rep.to_sdm().lll_transform()) + raises(DMDomainError, lambda: wrong_domain_matrix.lll_transform()) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_normalforms.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_normalforms.py new file mode 100644 index 0000000000000000000000000000000000000000..542d9064aea204759158578a4bfbbf5acbb06db3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_normalforms.py @@ -0,0 +1,156 @@ +from sympy.testing.pytest import raises + +from sympy.core.symbol import Symbol +from sympy.polys.matrices.normalforms import ( + invariant_factors, + smith_normal_form, + smith_normal_decomp, + is_smith_normal_form, + hermite_normal_form, + _hermite_normal_form, + _hermite_normal_form_modulo_D +) +from sympy.polys.domains import ZZ, QQ +from sympy.polys.matrices import DomainMatrix, DM +from sympy.polys.matrices.exceptions import DMDomainError, DMShapeError + + +def test_is_smith_normal_form(): + + snf_examples = [ + DM([[0, 0], [0, 0]], ZZ), + DM([[1, 0], [0, 0]], ZZ), + DM([[1, 0], [0, 1]], ZZ), + DM([[1, 0], [0, 2]], ZZ), + ] + + non_snf_examples = [ + DM([[0, 1], [0, 0]], ZZ), + DM([[0, 0], [0, 1]], ZZ), + DM([[2, 0], [0, 3]], ZZ), + ] + + for m in snf_examples: + assert is_smith_normal_form(m) is True + + for m in non_snf_examples: + assert is_smith_normal_form(m) is False + + +def test_smith_normal(): + + m = DM([ + [12, 6, 4, 8], + [3, 9, 6, 12], + [2, 16, 14, 28], + [20, 10, 10, 20]], ZZ) + + smf = DM([ + [1, 0, 0, 0], + [0, 10, 0, 0], + [0, 0, 30, 0], + [0, 0, 0, 0]], ZZ) + + s = DM([ + [0, 1, -1, 0], + [1, -4, 0, 0], + [0, -2, 3, 0], + [-2, 2, -1, 1]], ZZ) + + t = DM([ + [1, 1, 10, 0], + [0, -1, -2, 0], + [0, 1, 3, -2], + [0, 0, 0, 1]], ZZ) + + assert smith_normal_form(m).to_dense() == smf + assert smith_normal_decomp(m) == (smf, s, t) + assert is_smith_normal_form(smf) + assert smf == s * m * t + + m00 = DomainMatrix.zeros((0, 0), ZZ).to_dense() + m01 = DomainMatrix.zeros((0, 1), ZZ).to_dense() + m10 = DomainMatrix.zeros((1, 0), ZZ).to_dense() + i11 = DM([[1]], ZZ) + + assert smith_normal_form(m00) == m00.to_sparse() + assert smith_normal_form(m01) == m01.to_sparse() + assert smith_normal_form(m10) == m10.to_sparse() + assert smith_normal_form(i11) == i11.to_sparse() + + assert smith_normal_decomp(m00) == (m00, m00, m00) + assert smith_normal_decomp(m01) == (m01, m00, i11) + assert smith_normal_decomp(m10) == (m10, i11, m00) + assert smith_normal_decomp(i11) == (i11, i11, i11) + + x = Symbol('x') + m = DM([[x-1, 1, -1], + [ 0, x, -1], + [ 0, -1, x]], QQ[x]) + dx = m.domain.gens[0] + assert invariant_factors(m) == (1, dx-1, dx**2-1) + + zr = DomainMatrix([], (0, 2), ZZ) + zc = DomainMatrix([[], []], (2, 0), ZZ) + assert smith_normal_form(zr).to_dense() == zr + assert smith_normal_form(zc).to_dense() == zc + + assert smith_normal_form(DM([[2, 4]], ZZ)).to_dense() == DM([[2, 0]], ZZ) + assert smith_normal_form(DM([[0, -2]], ZZ)).to_dense() == DM([[2, 0]], ZZ) + assert smith_normal_form(DM([[0], [-2]], ZZ)).to_dense() == DM([[2], [0]], ZZ) + + assert smith_normal_decomp(DM([[0, -2]], ZZ)) == ( + DM([[2, 0]], ZZ), DM([[-1]], ZZ), DM([[0, 1], [1, 0]], ZZ) + ) + assert smith_normal_decomp(DM([[0], [-2]], ZZ)) == ( + DM([[2], [0]], ZZ), DM([[0, -1], [1, 0]], ZZ), DM([[1]], ZZ) + ) + + m = DM([[3, 0, 0, 0], [0, 0, 0, 0], [0, 0, 2, 0]], ZZ) + snf = DM([[1, 0, 0, 0], [0, 6, 0, 0], [0, 0, 0, 0]], ZZ) + s = DM([[1, 0, 1], [2, 0, 3], [0, 1, 0]], ZZ) + t = DM([[1, -2, 0, 0], [0, 0, 0, 1], [-1, 3, 0, 0], [0, 0, 1, 0]], ZZ) + + assert smith_normal_form(m).to_dense() == snf + assert smith_normal_decomp(m) == (snf, s, t) + assert is_smith_normal_form(snf) + assert snf == s * m * t + + raises(ValueError, lambda: smith_normal_form(DM([[1]], ZZ[x]))) + + +def test_hermite_normal(): + m = DM([[2, 7, 17, 29, 41], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]], ZZ) + hnf = DM([[1, 0, 0], [0, 2, 1], [0, 0, 1]], ZZ) + assert hermite_normal_form(m) == hnf + assert hermite_normal_form(m, D=ZZ(2)) == hnf + assert hermite_normal_form(m, D=ZZ(2), check_rank=True) == hnf + + m = m.transpose() + hnf = DM([[37, 0, 19], [222, -6, 113], [48, 0, 25], [0, 2, 1], [0, 0, 1]], ZZ) + assert hermite_normal_form(m) == hnf + raises(DMShapeError, lambda: _hermite_normal_form_modulo_D(m, ZZ(96))) + raises(DMDomainError, lambda: _hermite_normal_form_modulo_D(m, QQ(96))) + + m = DM([[8, 28, 68, 116, 164], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]], ZZ) + hnf = DM([[4, 0, 0], [0, 2, 1], [0, 0, 1]], ZZ) + assert hermite_normal_form(m) == hnf + assert hermite_normal_form(m, D=ZZ(8)) == hnf + assert hermite_normal_form(m, D=ZZ(8), check_rank=True) == hnf + + m = DM([[10, 8, 6, 30, 2], [45, 36, 27, 18, 9], [5, 4, 3, 2, 1]], ZZ) + hnf = DM([[26, 2], [0, 9], [0, 1]], ZZ) + assert hermite_normal_form(m) == hnf + + m = DM([[2, 7], [0, 0], [0, 0]], ZZ) + hnf = DM([[1], [0], [0]], ZZ) + assert hermite_normal_form(m) == hnf + + m = DM([[-2, 1], [0, 1]], ZZ) + hnf = DM([[2, 1], [0, 1]], ZZ) + assert hermite_normal_form(m) == hnf + + m = DomainMatrix([[QQ(1)]], (1, 1), QQ) + raises(DMDomainError, lambda: hermite_normal_form(m)) + raises(DMDomainError, lambda: _hermite_normal_form(m)) + raises(DMDomainError, lambda: _hermite_normal_form_modulo_D(m, ZZ(1))) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_nullspace.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_nullspace.py new file mode 100644 index 0000000000000000000000000000000000000000..dbb025b7dc9dff31bc97d86e175147ffede5a7e3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_nullspace.py @@ -0,0 +1,209 @@ +from sympy import ZZ, Matrix +from sympy.polys.matrices import DM, DomainMatrix +from sympy.polys.matrices.ddm import DDM +from sympy.polys.matrices.sdm import SDM + +import pytest + +zeros = lambda shape, K: DomainMatrix.zeros(shape, K).to_dense() +eye = lambda n, K: DomainMatrix.eye(n, K).to_dense() + + +# +# DomainMatrix.nullspace can have a divided answer or can return an undivided +# uncanonical answer. The uncanonical answer is not unique but we can make it +# unique by making it primitive (remove gcd). The tests here all show the +# primitive form. We test two things: +# +# A.nullspace().primitive()[1] == answer. +# A.nullspace(divide_last=True) == _divide_last(answer). +# +# The nullspace as returned by DomainMatrix and related classes is the +# transpose of the nullspace as returned by Matrix. Matrix returns a list of +# of column vectors whereas DomainMatrix returns a matrix whose rows are the +# nullspace vectors. +# + + +NULLSPACE_EXAMPLES = [ + + ( + 'zz_1', + DM([[ 1, 2, 3]], ZZ), + DM([[-2, 1, 0], + [-3, 0, 1]], ZZ), + ), + + ( + 'zz_2', + zeros((0, 0), ZZ), + zeros((0, 0), ZZ), + ), + + ( + 'zz_3', + zeros((2, 0), ZZ), + zeros((0, 0), ZZ), + ), + + ( + 'zz_4', + zeros((0, 2), ZZ), + eye(2, ZZ), + ), + + ( + 'zz_5', + zeros((2, 2), ZZ), + eye(2, ZZ), + ), + + ( + 'zz_6', + DM([[1, 2], + [3, 4]], ZZ), + zeros((0, 2), ZZ), + ), + + ( + 'zz_7', + DM([[1, 1], + [1, 1]], ZZ), + DM([[-1, 1]], ZZ), + ), + + ( + 'zz_8', + DM([[1], + [1]], ZZ), + zeros((0, 1), ZZ), + ), + + ( + 'zz_9', + DM([[1, 1]], ZZ), + DM([[-1, 1]], ZZ), + ), + + ( + 'zz_10', + DM([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], ZZ), + DM([[ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [-1, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [ 0, -1, 0, 0, 0, 0, 0, 1, 0, 0], + [ 0, 0, 0, -1, 0, 0, 0, 0, 1, 0], + [ 0, 0, 0, 0, -1, 0, 0, 0, 0, 1]], ZZ), + ), + +] + + +def _to_DM(A, ans): + """Convert the answer to DomainMatrix.""" + if isinstance(A, DomainMatrix): + return A.to_dense() + elif isinstance(A, DDM): + return DomainMatrix(list(A), A.shape, A.domain).to_dense() + elif isinstance(A, SDM): + return DomainMatrix(dict(A), A.shape, A.domain).to_dense() + else: + assert False # pragma: no cover + + +def _divide_last(null): + """Normalize the nullspace by the rightmost non-zero entry.""" + null = null.to_field() + + if null.is_zero_matrix: + return null + + rows = [] + for i in range(null.shape[0]): + for j in reversed(range(null.shape[1])): + if null[i, j]: + rows.append(null[i, :] / null[i, j]) + break + else: + assert False # pragma: no cover + + return DomainMatrix.vstack(*rows) + + +def _check_primitive(null, null_ans): + """Check that the primitive of the answer matches.""" + null = _to_DM(null, null_ans) + cont, null_prim = null.primitive() + assert null_prim == null_ans + + +def _check_divided(null, null_ans): + """Check the divided answer.""" + null = _to_DM(null, null_ans) + null_ans_norm = _divide_last(null_ans) + assert null == null_ans_norm + + +@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) +def test_Matrix_nullspace(name, A, A_null): + A = A.to_Matrix() + + A_null_cols = A.nullspace() + + # We have to patch up the case where the nullspace is empty + if A_null_cols: + A_null_found = Matrix.hstack(*A_null_cols) + else: + A_null_found = Matrix.zeros(A.cols, 0) + + A_null_found = A_null_found.to_DM().to_field().to_dense() + + # The Matrix result is the transpose of DomainMatrix result. + A_null_found = A_null_found.transpose() + + _check_divided(A_null_found, A_null) + + +@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) +def test_dm_dense_nullspace(name, A, A_null): + A = A.to_field().to_dense() + A_null_found = A.nullspace(divide_last=True) + _check_divided(A_null_found, A_null) + + +@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) +def test_dm_sparse_nullspace(name, A, A_null): + A = A.to_field().to_sparse() + A_null_found = A.nullspace(divide_last=True) + _check_divided(A_null_found, A_null) + + +@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) +def test_ddm_nullspace(name, A, A_null): + A = A.to_field().to_ddm() + A_null_found, _ = A.nullspace() + _check_divided(A_null_found, A_null) + + +@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) +def test_sdm_nullspace(name, A, A_null): + A = A.to_field().to_sdm() + A_null_found, _ = A.nullspace() + _check_divided(A_null_found, A_null) + + +@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) +def test_dm_dense_nullspace_fracfree(name, A, A_null): + A = A.to_dense() + A_null_found = A.nullspace() + _check_primitive(A_null_found, A_null) + + +@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) +def test_dm_sparse_nullspace_fracfree(name, A, A_null): + A = A.to_sparse() + A_null_found = A.nullspace() + _check_primitive(A_null_found, A_null) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_rref.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_rref.py new file mode 100644 index 0000000000000000000000000000000000000000..49def18c8132c0537540163a96bf6cf323c5a85c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_rref.py @@ -0,0 +1,737 @@ +from sympy import ZZ, QQ, ZZ_I, EX, Matrix, eye, zeros, symbols +from sympy.polys.matrices import DM, DomainMatrix +from sympy.polys.matrices.dense import ddm_irref_den, ddm_irref +from sympy.polys.matrices.ddm import DDM +from sympy.polys.matrices.sdm import SDM, sdm_irref, sdm_rref_den + +import pytest + + +# +# The dense and sparse implementations of rref_den are ddm_irref_den and +# sdm_irref_den. These can give results that differ by some factor and also +# give different results if the order of the rows is changed. The tests below +# show all results on lowest terms as should be returned by cancel_denom. +# +# The EX domain is also a case where the dense and sparse implementations +# can give results in different forms: the results should be equivalent but +# are not canonical because EX does not have a canonical form. +# + + +a, b, c, d = symbols('a, b, c, d') + + +qq_large_1 = DM([ +[ (1,2), (1,3), (1,5), (1,7), (1,11), (1,13), (1,17), (1,19), (1,23), (1,29), (1,31)], +[ (1,37), (1,41), (1,43), (1,47), (1,53), (1,59), (1,61), (1,67), (1,71), (1,73), (1,79)], +[ (1,83), (1,89), (1,97),(1,101),(1,103),(1,107),(1,109),(1,113),(1,127),(1,131),(1,137)], +[(1,139),(1,149),(1,151),(1,157),(1,163),(1,167),(1,173),(1,179),(1,181),(1,191),(1,193)], +[(1,197),(1,199),(1,211),(1,223),(1,227),(1,229),(1,233),(1,239),(1,241),(1,251),(1,257)], +[(1,263),(1,269),(1,271),(1,277),(1,281),(1,283),(1,293),(1,307),(1,311),(1,313),(1,317)], +[(1,331),(1,337),(1,347),(1,349),(1,353),(1,359),(1,367),(1,373),(1,379),(1,383),(1,389)], +[(1,397),(1,401),(1,409),(1,419),(1,421),(1,431),(1,433),(1,439),(1,443),(1,449),(1,457)], +[(1,461),(1,463),(1,467),(1,479),(1,487),(1,491),(1,499),(1,503),(1,509),(1,521),(1,523)], +[(1,541),(1,547),(1,557),(1,563),(1,569),(1,571),(1,577),(1,587),(1,593),(1,599),(1,601)], +[(1,607),(1,613),(1,617),(1,619),(1,631),(1,641),(1,643),(1,647),(1,653),(1,659),(1,661)]], + QQ) + +qq_large_2 = qq_large_1 + 10**100 * DomainMatrix.eye(11, QQ) + + +RREF_EXAMPLES = [ + ( + 'zz_1', + DM([[1, 2, 3]], ZZ), + DM([[1, 2, 3]], ZZ), + ZZ(1), + ), + + ( + 'zz_2', + DomainMatrix([], (0, 0), ZZ), + DomainMatrix([], (0, 0), ZZ), + ZZ(1), + ), + + ( + 'zz_3', + DM([[1, 2], + [3, 4]], ZZ), + DM([[1, 0], + [0, 1]], ZZ), + ZZ(1), + ), + + ( + 'zz_4', + DM([[1, 0], + [3, 4]], ZZ), + DM([[1, 0], + [0, 1]], ZZ), + ZZ(1), + ), + + ( + 'zz_5', + DM([[0, 2], + [3, 4]], ZZ), + DM([[1, 0], + [0, 1]], ZZ), + ZZ(1), + ), + + ( + 'zz_6', + DM([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]], ZZ), + DM([[1, 0, -1], + [0, 1, 2], + [0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_7', + DM([[0, 0, 0], + [0, 0, 0], + [1, 0, 0]], ZZ), + DM([[1, 0, 0], + [0, 0, 0], + [0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_8', + DM([[0, 0, 0], + [0, 0, 0], + [0, 0, 0]], ZZ), + DM([[0, 0, 0], + [0, 0, 0], + [0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_9', + DM([[1, 1, 0], + [0, 0, 2], + [0, 0, 0]], ZZ), + DM([[1, 1, 0], + [0, 0, 1], + [0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_10', + DM([[2, 2, 0], + [0, 0, 2], + [0, 0, 0]], ZZ), + DM([[1, 1, 0], + [0, 0, 1], + [0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_11', + DM([[2, 2, 0], + [0, 2, 2], + [0, 0, 2]], ZZ), + DM([[1, 0, 0], + [0, 1, 0], + [0, 0, 1]], ZZ), + ZZ(1), + ), + + ( + 'zz_12', + DM([[ 1, 2, 3], + [ 4, 5, 6], + [ 7, 8, 9], + [10, 11, 12]], ZZ), + DM([[1, 0, -1], + [0, 1, 2], + [0, 0, 0], + [0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_13', + DM([[ 1, 2, 3], + [ 4, 5, 6], + [ 7, 8, 9], + [10, 11, 13]], ZZ), + DM([[ 1, 0, 0], + [ 0, 1, 0], + [ 0, 0, 1], + [ 0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_14', + DM([[1, 2, 4, 3], + [4, 5, 10, 6], + [7, 8, 16, 9]], ZZ), + DM([[1, 0, 0, -1], + [0, 1, 2, 2], + [0, 0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_15', + DM([[1, 2, 4, 3], + [4, 5, 10, 6], + [7, 8, 17, 9]], ZZ), + DM([[1, 0, 0, -1], + [0, 1, 0, 2], + [0, 0, 1, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_16', + DM([[1, 2, 0, 1], + [1, 1, 9, 0]], ZZ), + DM([[1, 0, 18, -1], + [0, 1, -9, 1]], ZZ), + ZZ(1), + ), + + ( + 'zz_17', + DM([[1, 1, 1], + [1, 2, 2]], ZZ), + DM([[1, 0, 0], + [0, 1, 1]], ZZ), + ZZ(1), + ), + + ( + # Here the sparse implementation and dense implementation give very + # different denominators: 4061232 and -1765176. + 'zz_18', + DM([[94, 24, 0, 27, 0], + [79, 0, 0, 0, 0], + [85, 16, 71, 81, 0], + [ 0, 0, 72, 77, 0], + [21, 0, 34, 0, 0]], ZZ), + DM([[ 1, 0, 0, 0, 0], + [ 0, 1, 0, 0, 0], + [ 0, 0, 1, 0, 0], + [ 0, 0, 0, 1, 0], + [ 0, 0, 0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + # Let's have a denominator that cannot be cancelled. + 'zz_19', + DM([[1, 2, 4], + [4, 5, 6]], ZZ), + DM([[3, 0, -8], + [0, 3, 10]], ZZ), + ZZ(3), + ), + + ( + 'zz_20', + DM([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 4]], ZZ), + DM([[0, 0, 0, 0, 1], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_21', + DM([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], ZZ), + DM([[1, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_22', + DM([[1, 1, 1, 0, 1], + [1, 1, 0, 1, 0], + [1, 0, 1, 0, 1], + [1, 1, 0, 1, 0], + [1, 0, 0, 0, 0]], ZZ), + DM([[1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 1], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 0]], ZZ), + ZZ(1), + ), + + ( + 'zz_large_1', + DM([ +[ 0, 0, 0, 81, 0, 0, 75, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0], +[ 0, 0, 0, 0, 0, 86, 0, 92, 79, 54, 0, 7, 0, 0, 0, 0, 79, 0, 0, 0], +[89, 54, 81, 0, 0, 20, 0, 0, 0, 0, 0, 0, 51, 0, 94, 0, 0, 77, 0, 0], +[ 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 48, 29, 0, 0, 5, 0, 32, 0], +[ 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 11], +[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 43, 0, 0], +[ 0, 0, 0, 0, 0, 38, 91, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 26, 0, 0], +[69, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55], +[ 0, 13, 18, 49, 49, 88, 0, 0, 35, 54, 0, 0, 51, 0, 0, 0, 0, 0, 0, 87], +[ 0, 0, 0, 0, 31, 0, 40, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 88, 0], +[ 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 15, 53, 0, 92, 0, 0, 0, 0], +[ 0, 0, 0, 95, 0, 0, 0, 36, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 73, 19], +[ 0, 65, 14, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0, 0, 34, 0, 0], +[ 0, 0, 0, 16, 39, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0], +[ 0, 17, 0, 0, 0, 99, 84, 13, 50, 84, 0, 0, 0, 0, 95, 0, 43, 33, 20, 0], +[79, 0, 17, 52, 99, 12, 69, 0, 98, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[ 0, 0, 0, 82, 0, 44, 0, 0, 0, 97, 0, 0, 0, 0, 0, 10, 0, 0, 31, 0], +[ 0, 0, 21, 0, 67, 0, 0, 0, 0, 0, 4, 0, 50, 0, 0, 0, 33, 0, 0, 0], +[ 0, 0, 0, 0, 9, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8], +[ 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 34, 93, 0, 0, 0, 0, 47, 0, 0, 0]], + ZZ), + DM([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], ZZ), + ZZ(1), + ), + + ( + 'zz_large_2', + DM([ +[ 0, 0, 0, 0, 50, 0, 6, 81, 0, 1, 86, 0, 0, 98, 82, 94, 4, 0, 0, 29], +[ 0, 44, 43, 0, 62, 0, 0, 0, 60, 0, 0, 0, 0, 71, 9, 0, 57, 41, 0, 93], +[ 0, 0, 28, 0, 74, 89, 42, 0, 28, 0, 6, 0, 0, 0, 44, 0, 0, 0, 77, 19], +[ 0, 21, 82, 0, 30, 88, 0, 89, 68, 0, 0, 0, 79, 41, 0, 0, 99, 0, 0, 0], +[31, 0, 0, 0, 19, 64, 0, 0, 79, 0, 5, 0, 72, 10, 60, 32, 64, 59, 0, 24], +[ 0, 0, 0, 0, 0, 57, 0, 94, 0, 83, 20, 0, 0, 9, 31, 0, 49, 26, 58, 0], +[ 0, 65, 56, 31, 64, 0, 0, 0, 0, 0, 0, 52, 85, 0, 0, 0, 0, 51, 0, 0], +[ 0, 35, 0, 0, 0, 69, 0, 0, 64, 0, 0, 0, 0, 70, 0, 0, 90, 0, 75, 76], +[69, 7, 0, 90, 0, 0, 84, 0, 47, 69, 19, 20, 42, 0, 0, 32, 71, 35, 0, 0], +[39, 0, 90, 0, 0, 4, 85, 0, 0, 55, 0, 0, 0, 35, 67, 40, 0, 40, 0, 77], +[98, 63, 0, 71, 0, 50, 0, 2, 61, 0, 38, 0, 0, 0, 0, 75, 0, 40, 33, 56], +[ 0, 73, 0, 64, 0, 38, 0, 35, 61, 0, 0, 52, 0, 7, 0, 51, 0, 0, 0, 34], +[ 0, 0, 28, 0, 34, 5, 63, 45, 14, 42, 60, 16, 76, 54, 99, 0, 28, 30, 0, 0], +[58, 37, 14, 0, 0, 0, 94, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 8, 90, 53], +[86, 74, 94, 0, 49, 10, 60, 0, 40, 18, 0, 0, 0, 31, 60, 24, 0, 1, 0, 29], +[53, 0, 0, 97, 0, 0, 58, 0, 0, 39, 44, 47, 0, 0, 0, 12, 50, 0, 0, 11], +[ 4, 0, 92, 10, 28, 0, 0, 89, 0, 0, 18, 54, 23, 39, 0, 2, 0, 48, 0, 92], +[ 0, 0, 90, 77, 95, 33, 0, 0, 49, 22, 39, 0, 0, 0, 0, 0, 0, 40, 0, 0], +[96, 0, 0, 0, 0, 38, 86, 0, 22, 76, 0, 0, 0, 0, 83, 88, 95, 65, 72, 0], +[81, 65, 0, 4, 60, 0, 19, 0, 0, 68, 0, 0, 89, 0, 67, 22, 0, 0, 55, 33]], + ZZ), + DM([ +[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], + ZZ), + ZZ(1), + ), + + ( + 'zz_large_3', + DM([ +[62,35,89,58,22,47,30,28,52,72,17,56,80,26,64,21,10,35,24,42,96,32,23,50,92,37,76,94,63,66], +[20,47,96,34,10,98,19,6,29,2,19,92,61,94,38,41,32,9,5,94,31,58,27,41,72,85,61,62,40,46], +[69,26,35,68,25,52,94,13,38,65,81,10,29,15,5,4,13,99,85,0,80,51,60,60,26,77,85,2,87,25], +[99,58,69,15,52,12,18,7,27,56,12,54,21,92,38,95,33,83,28,1,44,8,29,84,92,12,2,25,46,46], +[93,13,55,48,35,87,24,40,23,35,25,32,0,19,0,85,4,79,26,11,46,75,7,96,76,11,7,57,99,75], +[128,85,26,51,161,173,77,78,85,103,123,58,91,147,38,91,161,36,123,81,102,25,75,59,17,150,112,65,77,143], +[15,59,61,82,12,83,34,8,94,71,66,7,91,21,48,69,26,12,64,38,97,87,38,15,51,33,93,43,66,89], +[74,74,53,39,69,90,41,80,32,66,40,83,87,87,61,38,12,80,24,49,37,90,19,33,56,0,46,57,56,60], +[82,11,0,25,56,58,39,49,92,93,80,38,19,62,33,85,19,61,14,30,45,91,97,34,97,53,92,28,33,43], +[83,79,41,16,95,35,53,45,26,4,71,76,61,69,69,72,87,92,59,72,54,11,22,83,8,57,77,55,19,22], +[49,34,13,31,72,77,52,70,46,41,37,6,42,66,35,6,75,33,62,57,30,14,26,31,9,95,89,13,12,90], +[29,3,49,30,51,32,77,41,38,50,16,1,87,81,93,88,58,91,83,0,38,67,29,64,60,84,5,60,23,28], +[79,51,13,20,89,96,25,8,39,62,86,52,49,81,3,85,86,3,61,24,72,11,49,28,8,55,23,52,65,53], +[96,86,73,20,41,20,37,18,10,61,85,24,40,83,69,41,4,92,23,99,64,33,18,36,32,56,60,98,39,24], +[32,62,47,80,51,66,17,1,9,30,65,75,75,88,99,92,64,53,53,86,38,51,41,14,35,18,39,25,26,32], +[39,21,8,16,33,6,35,85,75,62,43,34,18,68,71,28,32,18,12,0,81,53,1,99,3,5,45,99,35,33], +[19,95,89,45,75,94,92,5,84,93,34,17,50,56,79,98,68,82,65,81,51,90,5,95,33,71,46,61,14,7], +[53,92,8,49,67,84,21,79,49,95,66,48,36,14,62,97,26,45,58,31,83,48,11,89,67,72,91,34,56,89], +[56,76,99,92,40,8,0,16,15,48,35,72,91,46,81,14,86,60,51,7,33,12,53,78,48,21,3,89,15,79], +[81,43,33,49,6,49,36,32,57,74,87,91,17,37,31,17,67,1,40,38,69,8,3,48,59,37,64,97,11,3], +[98,48,77,16,2,48,57,38,63,59,79,35,16,71,60,86,71,41,14,76,80,97,77,69,4,58,22,55,26,73], +[80,47,78,44,31,48,47,29,29,62,19,21,17,24,19,3,53,93,97,57,13,54,12,10,77,66,60,75,32,21], +[86,63,2,13,71,38,86,23,18,15,91,65,77,65,9,92,50,0,17,42,99,80,99,27,10,99,92,9,87,84], +[66,27,72,13,13,15,72,75,39,3,14,71,15,68,10,19,49,54,11,29,47,20,63,13,97,47,24,62,16,96], +[42,63,83,60,49,68,9,53,75,87,40,25,12,63,0,12,0,95,46,46,55,25,89,1,51,1,1,96,80,52], +[35,9,97,13,86,39,66,48,41,57,23,38,11,9,35,72,88,13,41,60,10,64,71,23,1,5,23,57,6,19], +[70,61,5,50,72,60,77,13,41,94,1,45,52,22,99,47,27,18,99,42,16,48,26,9,88,77,10,94,11,92], +[55,68,58,2,72,56,81,52,79,37,1,40,21,46,27,60,37,13,97,42,85,98,69,60,76,44,42,46,29,73], +[73,0,43,17,89,97,45,2,68,14,55,60,95,2,74,85,88,68,93,76,38,76,2,51,45,76,50,79,56,18], +[72,58,41,39,24,80,23,79,44,7,98,75,30,6,85,60,20,58,77,71,90,51,38,80,30,15,33,10,82,8]], + ZZ), + Matrix([ + [eye(29) * 2028539767964472550625641331179545072876560857886207583101, + Matrix([ 4260575808093245475167216057435155595594339172099000182569, + 169148395880755256182802335904188369274227936894862744452, + 4915975976683942569102447281579134986891620721539038348914, + 6113916866367364958834844982578214901958429746875633283248, + 5585689617819894460378537031623265659753379011388162534838, + 359776822829880747716695359574308645968094838905181892423, + -2800926112141776386671436511182421432449325232461665113305, + 941642292388230001722444876624818265766384442910688463158, + 3648811843256146649321864698600908938933015862008642023935, + -4104526163246702252932955226754097174212129127510547462419, + -704814955438106792441896903238080197619233342348191408078, + 1640882266829725529929398131287244562048075707575030019335, + -4068330845192910563212155694231438198040299927120544468520, + 136589038308366497790495711534532612862715724187671166593, + 2544937011460702462290799932536905731142196510605191645593, + 755591839174293940486133926192300657264122907519174116472, + -3683838489869297144348089243628436188645897133242795965021, + -522207137101161299969706310062775465103537953077871128403, + -2260451796032703984456606059649402832441331339246756656334, + -6476809325293587953616004856993300606040336446656916663680, + 3521944238996782387785653800944972787867472610035040989081, + 2270762115788407950241944504104975551914297395787473242379, + -3259947194628712441902262570532921252128444706733549251156, + -5624569821491886970999097239695637132075823246850431083557, + -3262698255682055804320585332902837076064075936601504555698, + 5786719943788937667411185880136324396357603606944869545501, + -955257841973865996077323863289453200904051299086000660036, + -1294235552446355326174641248209752679127075717918392702116, + -3718353510747301598130831152458342785269166356215331448279, + ]),], + [zeros(1, 29), zeros(1, 1)], + ]).to_DM().to_dense(), + ZZ(2028539767964472550625641331179545072876560857886207583101), + ), + + + ( + 'qq_1', + DM([[(1,2), 0], [0, 2]], QQ), + DM([[1, 0], [0, 1]], QQ), + QQ(1), + ), + + ( + # Standard square case + 'qq_2', + DM([[0, 1], + [1, 1]], QQ), + DM([[1, 0], + [0, 1]], QQ), + QQ(1), + ), + + ( + # m < n case + 'qq_3', + DM([[1, 2, 1], + [3, 4, 1]], QQ), + DM([[1, 0, -1], + [0, 1, 1]], QQ), + QQ(1), + ), + + ( + # same m < n but reversed + 'qq_4', + DM([[3, 4, 1], + [1, 2, 1]], QQ), + DM([[1, 0, -1], + [0, 1, 1]], QQ), + QQ(1), + ), + + ( + # m > n case + 'qq_5', + DM([[1, 0], + [1, 3], + [0, 1]], QQ), + DM([[1, 0], + [0, 1], + [0, 0]], QQ), + QQ(1), + ), + + ( + # Example with missing pivot + 'qq_6', + DM([[1, 0, 1], + [3, 0, 1]], QQ), + DM([[1, 0, 0], + [0, 0, 1]], QQ), + QQ(1), + ), + + ( + # This is intended to trigger the threshold where we give up on + # clearing denominators. + 'qq_large_1', + qq_large_1, + DomainMatrix.eye(11, QQ).to_dense(), + QQ(1), + ), + + ( + # This is intended to trigger the threshold where we use rref_den over + # QQ. + 'qq_large_2', + qq_large_2, + DomainMatrix.eye(11, QQ).to_dense(), + QQ(1), + ), + + ( + # Example with missing pivot and no replacement + + # This example is just enough to show a different result from the dense + # and sparse versions of the algorithm: + # + # >>> A = Matrix([[0, 1], [0, 2], [1, 0]]) + # >>> A.to_DM().to_sparse().rref_den()[0].to_Matrix() + # Matrix([ + # [1, 0], + # [0, 1], + # [0, 0]]) + # >>> A.to_DM().to_dense().rref_den()[0].to_Matrix() + # Matrix([ + # [2, 0], + # [0, 2], + # [0, 0]]) + # + 'qq_7', + DM([[0, 1], + [0, 2], + [1, 0]], QQ), + DM([[1, 0], + [0, 1], + [0, 0]], QQ), + QQ(1), + ), + + ( + # Gaussian integers + 'zz_i_1', + DM([[(0,1), 1, 1], + [ 1, 1, 1]], ZZ_I), + DM([[1, 0, 0], + [0, 1, 1]], ZZ_I), + ZZ_I(1), + ), + + ( + # EX: test_issue_23718 + 'EX_1', + DM([ + [a, b, 1], + [c, d, 1]], EX), + DM([[a*d - b*c, 0, -b + d], + [ 0, a*d - b*c, a - c]], EX), + EX(a*d - b*c), + ), + +] + + +def _to_DM(A, ans): + """Convert the answer to DomainMatrix.""" + if isinstance(A, DomainMatrix): + return A.to_dense() + elif isinstance(A, Matrix): + return A.to_DM(ans.domain).to_dense() + + if not (hasattr(A, 'shape') and hasattr(A, 'domain')): + shape, domain = ans.shape, ans.domain + else: + shape, domain = A.shape, A.domain + + if isinstance(A, (DDM, list)): + return DomainMatrix(list(A), shape, domain).to_dense() + elif isinstance(A, (SDM, dict)): + return DomainMatrix(dict(A), shape, domain).to_dense() + else: + assert False # pragma: no cover + + +def _pivots(A_rref): + """Return the pivots from the rref of A.""" + return tuple(sorted(map(min, A_rref.to_sdm().values()))) + + +def _check_cancel(result, rref_ans, den_ans): + """Check the cancelled result.""" + rref, den, pivots = result + if isinstance(rref, (DDM, SDM, list, dict)): + assert type(pivots) is list + pivots = tuple(pivots) + rref = _to_DM(rref, rref_ans) + rref2, den2 = rref.cancel_denom(den) + assert rref2 == rref_ans + assert den2 == den_ans + assert pivots == _pivots(rref) + + +def _check_divide(result, rref_ans, den_ans): + """Check the divided result.""" + rref, pivots = result + if isinstance(rref, (DDM, SDM, list, dict)): + assert type(pivots) is list + pivots = tuple(pivots) + rref_ans = rref_ans.to_field() / den_ans + rref = _to_DM(rref, rref_ans) + assert rref == rref_ans + assert _pivots(rref) == pivots + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_Matrix_rref(name, A, A_rref, den): + K = A.domain + A = A.to_Matrix() + A_rref_found, pivots = A.rref() + if K.is_EX: + A_rref_found = A_rref_found.expand() + _check_divide((A_rref_found, pivots), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_dm_dense_rref(name, A, A_rref, den): + A = A.to_field() + _check_divide(A.rref(), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_dm_dense_rref_den(name, A, A_rref, den): + _check_cancel(A.rref_den(), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_dm_sparse_rref(name, A, A_rref, den): + A = A.to_field().to_sparse() + _check_divide(A.rref(), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_dm_sparse_rref_den(name, A, A_rref, den): + A = A.to_sparse() + _check_cancel(A.rref_den(), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_dm_sparse_rref_den_keep_domain(name, A, A_rref, den): + A = A.to_sparse() + A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False) + A_rref_f = A_rref_f.to_field() / den_f + _check_divide((A_rref_f, pivots_f), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_dm_sparse_rref_den_keep_domain_CD(name, A, A_rref, den): + A = A.to_sparse() + A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False, method='CD') + A_rref_f = A_rref_f.to_field() / den_f + _check_divide((A_rref_f, pivots_f), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_dm_sparse_rref_den_keep_domain_GJ(name, A, A_rref, den): + A = A.to_sparse() + A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False, method='GJ') + A_rref_f = A_rref_f.to_field() / den_f + _check_divide((A_rref_f, pivots_f), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_ddm_rref_den(name, A, A_rref, den): + A = A.to_ddm() + _check_cancel(A.rref_den(), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_sdm_rref_den(name, A, A_rref, den): + A = A.to_sdm() + _check_cancel(A.rref_den(), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_ddm_rref(name, A, A_rref, den): + A = A.to_field().to_ddm() + _check_divide(A.rref(), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_sdm_rref(name, A, A_rref, den): + A = A.to_field().to_sdm() + _check_divide(A.rref(), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_ddm_irref(name, A, A_rref, den): + A = A.to_field().to_ddm().copy() + pivots_found = ddm_irref(A) + _check_divide((A, pivots_found), A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_ddm_irref_den(name, A, A_rref, den): + A = A.to_ddm().copy() + (den_found, pivots_found) = ddm_irref_den(A, A.domain) + result = (A, den_found, pivots_found) + _check_cancel(result, A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_sparse_sdm_rref(name, A, A_rref, den): + A = A.to_field().to_sdm() + _check_divide(sdm_irref(A)[:2], A_rref, den) + + +@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES) +def test_sparse_sdm_rref_den(name, A, A_rref, den): + A = A.to_sdm().copy() + K = A.domain + _check_cancel(sdm_rref_den(A, K), A_rref, den) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_sdm.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_sdm.py new file mode 100644 index 0000000000000000000000000000000000000000..cd7e5d460a1b2d44279a2a1772cc901f80ca733e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_sdm.py @@ -0,0 +1,428 @@ +""" +Tests for the basic functionality of the SDM class. +""" + +from itertools import product + +from sympy.core.singleton import S +from sympy.external.gmpy import GROUND_TYPES +from sympy.testing.pytest import raises + +from sympy.polys.domains import QQ, ZZ, EXRAW +from sympy.polys.matrices.sdm import SDM +from sympy.polys.matrices.ddm import DDM +from sympy.polys.matrices.exceptions import (DMBadInputError, DMDomainError, + DMShapeError) + + +def test_SDM(): + A = SDM({0:{0:ZZ(1)}}, (2, 2), ZZ) + assert A.domain == ZZ + assert A.shape == (2, 2) + assert dict(A) == {0:{0:ZZ(1)}} + + raises(DMBadInputError, lambda: SDM({5:{1:ZZ(0)}}, (2, 2), ZZ)) + raises(DMBadInputError, lambda: SDM({0:{5:ZZ(0)}}, (2, 2), ZZ)) + + +def test_DDM_str(): + sdm = SDM({0:{0:ZZ(1)}, 1:{1:ZZ(1)}}, (2, 2), ZZ) + assert str(sdm) == '{0: {0: 1}, 1: {1: 1}}' + if GROUND_TYPES == 'gmpy': # pragma: no cover + assert repr(sdm) == 'SDM({0: {0: mpz(1)}, 1: {1: mpz(1)}}, (2, 2), ZZ)' + else: # pragma: no cover + assert repr(sdm) == 'SDM({0: {0: 1}, 1: {1: 1}}, (2, 2), ZZ)' + + +def test_SDM_new(): + A = SDM({0:{0:ZZ(1)}}, (2, 2), ZZ) + B = A.new({}, (2, 2), ZZ) + assert B == SDM({}, (2, 2), ZZ) + + +def test_SDM_copy(): + A = SDM({0:{0:ZZ(1)}}, (2, 2), ZZ) + B = A.copy() + assert A == B + A[0][0] = ZZ(2) + assert A != B + + +def test_SDM_from_list(): + A = SDM.from_list([[ZZ(0), ZZ(1)], [ZZ(1), ZZ(0)]], (2, 2), ZZ) + assert A == SDM({0:{1:ZZ(1)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + + raises(DMBadInputError, lambda: SDM.from_list([[ZZ(0)], [ZZ(0), ZZ(1)]], (2, 2), ZZ)) + raises(DMBadInputError, lambda: SDM.from_list([[ZZ(0), ZZ(1)]], (2, 2), ZZ)) + + +def test_SDM_to_list(): + A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ) + assert A.to_list() == [[ZZ(0), ZZ(1)], [ZZ(0), ZZ(0)]] + + A = SDM({}, (0, 2), ZZ) + assert A.to_list() == [] + + A = SDM({}, (2, 0), ZZ) + assert A.to_list() == [[], []] + + +def test_SDM_to_list_flat(): + A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ) + assert A.to_list_flat() == [ZZ(0), ZZ(1), ZZ(0), ZZ(0)] + + +def test_SDM_to_dok(): + A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ) + assert A.to_dok() == {(0, 1): ZZ(1)} + + +def test_SDM_from_ddm(): + A = DDM([[ZZ(1), ZZ(0)], [ZZ(1), ZZ(0)]], (2, 2), ZZ) + B = SDM.from_ddm(A) + assert B.domain == ZZ + assert B.shape == (2, 2) + assert dict(B) == {0:{0:ZZ(1)}, 1:{0:ZZ(1)}} + + +def test_SDM_to_ddm(): + A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ) + B = DDM([[ZZ(0), ZZ(1)], [ZZ(0), ZZ(0)]], (2, 2), ZZ) + assert A.to_ddm() == B + + +def test_SDM_to_sdm(): + A = SDM({0:{1: ZZ(1)}}, (2, 2), ZZ) + assert A.to_sdm() == A + + +def test_SDM_getitem(): + A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ) + assert A.getitem(0, 0) == ZZ.zero + assert A.getitem(0, 1) == ZZ.one + assert A.getitem(1, 0) == ZZ.zero + assert A.getitem(-2, -2) == ZZ.zero + assert A.getitem(-2, -1) == ZZ.one + assert A.getitem(-1, -2) == ZZ.zero + raises(IndexError, lambda: A.getitem(2, 0)) + raises(IndexError, lambda: A.getitem(0, 2)) + + +def test_SDM_setitem(): + A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ) + A.setitem(0, 0, ZZ(1)) + assert A == SDM({0:{0:ZZ(1), 1:ZZ(1)}}, (2, 2), ZZ) + A.setitem(1, 0, ZZ(1)) + assert A == SDM({0:{0:ZZ(1), 1:ZZ(1)}, 1:{0:ZZ(1)}}, (2, 2), ZZ) + A.setitem(1, 0, ZZ(0)) + assert A == SDM({0:{0:ZZ(1), 1:ZZ(1)}}, (2, 2), ZZ) + # Repeat the above test so that this time the row is empty + A.setitem(1, 0, ZZ(0)) + assert A == SDM({0:{0:ZZ(1), 1:ZZ(1)}}, (2, 2), ZZ) + A.setitem(0, 0, ZZ(0)) + assert A == SDM({0:{1:ZZ(1)}}, (2, 2), ZZ) + # This time the row is there but column is empty + A.setitem(0, 0, ZZ(0)) + assert A == SDM({0:{1:ZZ(1)}}, (2, 2), ZZ) + raises(IndexError, lambda: A.setitem(2, 0, ZZ(1))) + raises(IndexError, lambda: A.setitem(0, 2, ZZ(1))) + + +def test_SDM_extract_slice(): + A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ) + B = A.extract_slice(slice(1, 2), slice(1, 2)) + assert B == SDM({0:{0:ZZ(4)}}, (1, 1), ZZ) + + +def test_SDM_extract(): + A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ) + B = A.extract([1], [1]) + assert B == SDM({0:{0:ZZ(4)}}, (1, 1), ZZ) + B = A.extract([1, 0], [1, 0]) + assert B == SDM({0:{0:ZZ(4), 1:ZZ(3)}, 1:{0:ZZ(2), 1:ZZ(1)}}, (2, 2), ZZ) + B = A.extract([1, 1], [1, 1]) + assert B == SDM({0:{0:ZZ(4), 1:ZZ(4)}, 1:{0:ZZ(4), 1:ZZ(4)}}, (2, 2), ZZ) + B = A.extract([-1], [-1]) + assert B == SDM({0:{0:ZZ(4)}}, (1, 1), ZZ) + + A = SDM({}, (2, 2), ZZ) + B = A.extract([0, 1, 0], [0, 0]) + assert B == SDM({}, (3, 2), ZZ) + + A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ) + assert A.extract([], []) == SDM.zeros((0, 0), ZZ) + assert A.extract([1], []) == SDM.zeros((1, 0), ZZ) + assert A.extract([], [1]) == SDM.zeros((0, 1), ZZ) + + raises(IndexError, lambda: A.extract([2], [0])) + raises(IndexError, lambda: A.extract([0], [2])) + raises(IndexError, lambda: A.extract([-3], [0])) + raises(IndexError, lambda: A.extract([0], [-3])) + + +def test_SDM_zeros(): + A = SDM.zeros((2, 2), ZZ) + assert A.domain == ZZ + assert A.shape == (2, 2) + assert dict(A) == {} + +def test_SDM_ones(): + A = SDM.ones((1, 2), QQ) + assert A.domain == QQ + assert A.shape == (1, 2) + assert dict(A) == {0:{0:QQ(1), 1:QQ(1)}} + +def test_SDM_eye(): + A = SDM.eye((2, 2), ZZ) + assert A.domain == ZZ + assert A.shape == (2, 2) + assert dict(A) == {0:{0:ZZ(1)}, 1:{1:ZZ(1)}} + + +def test_SDM_diag(): + A = SDM.diag([ZZ(1), ZZ(2)], ZZ, (2, 3)) + assert A == SDM({0:{0:ZZ(1)}, 1:{1:ZZ(2)}}, (2, 3), ZZ) + + +def test_SDM_transpose(): + A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ) + B = SDM({0:{0:ZZ(1), 1:ZZ(3)}, 1:{0:ZZ(2), 1:ZZ(4)}}, (2, 2), ZZ) + assert A.transpose() == B + + A = SDM({0:{1:ZZ(2)}}, (2, 2), ZZ) + B = SDM({1:{0:ZZ(2)}}, (2, 2), ZZ) + assert A.transpose() == B + + A = SDM({0:{1:ZZ(2)}}, (1, 2), ZZ) + B = SDM({1:{0:ZZ(2)}}, (2, 1), ZZ) + assert A.transpose() == B + + +def test_SDM_mul(): + A = SDM({0:{0:ZZ(2)}}, (2, 2), ZZ) + B = SDM({0:{0:ZZ(4)}}, (2, 2), ZZ) + assert A*ZZ(2) == B + assert ZZ(2)*A == B + + raises(TypeError, lambda: A*QQ(1, 2)) + raises(TypeError, lambda: QQ(1, 2)*A) + + +def test_SDM_mul_elementwise(): + A = SDM({0:{0:ZZ(2), 1:ZZ(2)}}, (2, 2), ZZ) + B = SDM({0:{0:ZZ(4)}, 1:{0:ZZ(3)}}, (2, 2), ZZ) + C = SDM({0:{0:ZZ(8)}}, (2, 2), ZZ) + assert A.mul_elementwise(B) == C + assert B.mul_elementwise(A) == C + + Aq = A.convert_to(QQ) + A1 = SDM({0:{0:ZZ(1)}}, (1, 1), ZZ) + + raises(DMDomainError, lambda: Aq.mul_elementwise(B)) + raises(DMShapeError, lambda: A1.mul_elementwise(B)) + + +def test_SDM_matmul(): + A = SDM({0:{0:ZZ(2)}}, (2, 2), ZZ) + B = SDM({0:{0:ZZ(4)}}, (2, 2), ZZ) + assert A.matmul(A) == A*A == B + + C = SDM({0:{0:ZZ(2)}}, (2, 2), QQ) + raises(DMDomainError, lambda: A.matmul(C)) + + A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ) + B = SDM({0:{0:ZZ(7), 1:ZZ(10)}, 1:{0:ZZ(15), 1:ZZ(22)}}, (2, 2), ZZ) + assert A.matmul(A) == A*A == B + + A22 = SDM({0:{0:ZZ(4)}}, (2, 2), ZZ) + A32 = SDM({0:{0:ZZ(2)}}, (3, 2), ZZ) + A23 = SDM({0:{0:ZZ(4)}}, (2, 3), ZZ) + A33 = SDM({0:{0:ZZ(8)}}, (3, 3), ZZ) + A22 = SDM({0:{0:ZZ(8)}}, (2, 2), ZZ) + assert A32.matmul(A23) == A33 + assert A23.matmul(A32) == A22 + # XXX: @ not supported by SDM... + #assert A32.matmul(A23) == A32 @ A23 == A33 + #assert A23.matmul(A32) == A23 @ A32 == A22 + #raises(DMShapeError, lambda: A23 @ A22) + raises(DMShapeError, lambda: A23.matmul(A22)) + + A = SDM({0: {0: ZZ(-1), 1: ZZ(1)}}, (1, 2), ZZ) + B = SDM({0: {0: ZZ(-1)}, 1: {0: ZZ(-1)}}, (2, 1), ZZ) + assert A.matmul(B) == A*B == SDM({}, (1, 1), ZZ) + + +def test_matmul_exraw(): + + def dm(d): + result = {} + for i, row in d.items(): + row = {j:val for j, val in row.items() if val} + if row: + result[i] = row + return SDM(result, (2, 2), EXRAW) + + values = [S.NegativeInfinity, S.NegativeOne, S.Zero, S.One, S.Infinity] + for a, b, c, d in product(*[values]*4): + Ad = dm({0: {0:a, 1:b}, 1: {0:c, 1:d}}) + Ad2 = dm({0: {0:a*a + b*c, 1:a*b + b*d}, 1:{0:c*a + d*c, 1: c*b + d*d}}) + assert Ad * Ad == Ad2 + + +def test_SDM_add(): + A = SDM({0:{1:ZZ(1)}, 1:{0:ZZ(2), 1:ZZ(3)}}, (2, 2), ZZ) + B = SDM({0:{0:ZZ(1)}, 1:{0:ZZ(-2), 1:ZZ(3)}}, (2, 2), ZZ) + C = SDM({0:{0:ZZ(1), 1:ZZ(1)}, 1:{1:ZZ(6)}}, (2, 2), ZZ) + assert A.add(B) == B.add(A) == A + B == B + A == C + + A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ) + B = SDM({0:{0:ZZ(1)}, 1:{0:ZZ(-2), 1:ZZ(3)}}, (2, 2), ZZ) + C = SDM({0:{0:ZZ(1), 1:ZZ(1)}, 1:{0:ZZ(-2), 1:ZZ(3)}}, (2, 2), ZZ) + assert A.add(B) == B.add(A) == A + B == B + A == C + + raises(TypeError, lambda: A + []) + + +def test_SDM_sub(): + A = SDM({0:{1:ZZ(1)}, 1:{0:ZZ(2), 1:ZZ(3)}}, (2, 2), ZZ) + B = SDM({0:{0:ZZ(1)}, 1:{0:ZZ(-2), 1:ZZ(3)}}, (2, 2), ZZ) + C = SDM({0:{0:ZZ(-1), 1:ZZ(1)}, 1:{0:ZZ(4)}}, (2, 2), ZZ) + assert A.sub(B) == A - B == C + + raises(TypeError, lambda: A - []) + + +def test_SDM_neg(): + A = SDM({0:{1:ZZ(1)}, 1:{0:ZZ(2), 1:ZZ(3)}}, (2, 2), ZZ) + B = SDM({0:{1:ZZ(-1)}, 1:{0:ZZ(-2), 1:ZZ(-3)}}, (2, 2), ZZ) + assert A.neg() == -A == B + + +def test_SDM_convert_to(): + A = SDM({0:{1:ZZ(1)}, 1:{0:ZZ(2), 1:ZZ(3)}}, (2, 2), ZZ) + B = SDM({0:{1:QQ(1)}, 1:{0:QQ(2), 1:QQ(3)}}, (2, 2), QQ) + C = A.convert_to(QQ) + assert C == B + assert C.domain == QQ + + D = A.convert_to(ZZ) + assert D == A + assert D.domain == ZZ + + +def test_SDM_hstack(): + A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ) + B = SDM({1:{1:ZZ(1)}}, (2, 2), ZZ) + AA = SDM({0:{1:ZZ(1), 3:ZZ(1)}}, (2, 4), ZZ) + AB = SDM({0:{1:ZZ(1)}, 1:{3:ZZ(1)}}, (2, 4), ZZ) + assert SDM.hstack(A) == A + assert SDM.hstack(A, A) == AA + assert SDM.hstack(A, B) == AB + + +def test_SDM_vstack(): + A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ) + B = SDM({1:{1:ZZ(1)}}, (2, 2), ZZ) + AA = SDM({0:{1:ZZ(1)}, 2:{1:ZZ(1)}}, (4, 2), ZZ) + AB = SDM({0:{1:ZZ(1)}, 3:{1:ZZ(1)}}, (4, 2), ZZ) + assert SDM.vstack(A) == A + assert SDM.vstack(A, A) == AA + assert SDM.vstack(A, B) == AB + + +def test_SDM_applyfunc(): + A = SDM({0:{1:ZZ(1)}}, (2, 2), ZZ) + B = SDM({0:{1:ZZ(2)}}, (2, 2), ZZ) + assert A.applyfunc(lambda x: 2*x, ZZ) == B + + +def test_SDM_inv(): + A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + B = SDM({0:{0:QQ(-2), 1:QQ(1)}, 1:{0:QQ(3, 2), 1:QQ(-1, 2)}}, (2, 2), QQ) + assert A.inv() == B + + +def test_SDM_det(): + A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + assert A.det() == QQ(-2) + + +def test_SDM_lu(): + A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + L = SDM({0:{0:QQ(1)}, 1:{0:QQ(3), 1:QQ(1)}}, (2, 2), QQ) + #U = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(-2)}}, (2, 2), QQ) + #swaps = [] + # This doesn't quite work. U has some nonzero elements in the lower part. + #assert A.lu() == (L, U, swaps) + assert A.lu()[0] == L + + +def test_SDM_lu_solve(): + A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + b = SDM({0:{0:QQ(1)}, 1:{0:QQ(2)}}, (2, 1), QQ) + x = SDM({1:{0:QQ(1, 2)}}, (2, 1), QQ) + assert A.matmul(x) == b + assert A.lu_solve(b) == x + + +def test_SDM_charpoly(): + A = SDM({0:{0:ZZ(1), 1:ZZ(2)}, 1:{0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ) + assert A.charpoly() == [ZZ(1), ZZ(-5), ZZ(-2)] + + +def test_SDM_nullspace(): + # More tests are in test_nullspace.py + A = SDM({0:{0:QQ(1), 1:QQ(1)}}, (2, 2), QQ) + assert A.nullspace()[0] == SDM({0:{0:QQ(-1), 1:QQ(1)}}, (1, 2), QQ) + + +def test_SDM_rref(): + # More tests are in test_rref.py + + A = SDM({0:{0:QQ(1), 1:QQ(2)}, + 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ) + A_rref = SDM({0:{0:QQ(1)}, 1:{1:QQ(1)}}, (2, 2), QQ) + assert A.rref() == (A_rref, [0, 1]) + + A = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(2)}, + 1: {0: QQ(3), 2: QQ(4)}}, (2, 3), ZZ) + A_rref = SDM({0: {0: QQ(1,1), 2: QQ(4,3)}, + 1: {1: QQ(1,1), 2: QQ(1,3)}}, (2, 3), QQ) + assert A.rref() == (A_rref, [0, 1]) + + +def test_SDM_particular(): + A = SDM({0:{0:QQ(1)}}, (2, 2), QQ) + Apart = SDM.zeros((1, 2), QQ) + assert A.particular() == Apart + + +def test_SDM_is_zero_matrix(): + A = SDM({0: {0: QQ(1)}}, (2, 2), QQ) + Azero = SDM.zeros((1, 2), QQ) + assert A.is_zero_matrix() is False + assert Azero.is_zero_matrix() is True + + +def test_SDM_is_upper(): + A = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(3), 3: QQ(4)}, + 1: {1: QQ(5), 2: QQ(6), 3: QQ(7)}, + 2: {2: QQ(8), 3: QQ(9)}}, (3, 4), QQ) + B = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(3), 3: QQ(4)}, + 1: {1: QQ(5), 2: QQ(6), 3: QQ(7)}, + 2: {1: QQ(7), 2: QQ(8), 3: QQ(9)}}, (3, 4), QQ) + assert A.is_upper() is True + assert B.is_upper() is False + + +def test_SDM_is_lower(): + A = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(3), 3: QQ(4)}, + 1: {1: QQ(5), 2: QQ(6), 3: QQ(7)}, + 2: {2: QQ(8), 3: QQ(9)}}, (3, 4), QQ + ).transpose() + B = SDM({0: {0: QQ(1), 1: QQ(2), 2: QQ(3), 3: QQ(4)}, + 1: {1: QQ(5), 2: QQ(6), 3: QQ(7)}, + 2: {1: QQ(7), 2: QQ(8), 3: QQ(9)}}, (3, 4), QQ + ).transpose() + assert A.is_lower() is True + assert B.is_lower() is False diff --git a/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_xxm.py b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_xxm.py new file mode 100644 index 0000000000000000000000000000000000000000..628d66d15f5db82718231ba8f89bc0dadd393594 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/matrices/tests/test_xxm.py @@ -0,0 +1,1023 @@ +# +# Test basic features of DDM, SDM and DFM. +# +# These three types are supposed to be interchangeable, so we should use the +# same tests for all of them for the most part. +# +# The tests here cover the basic part of the interface that the three types +# should expose and that DomainMatrix should mostly rely on. +# +# More in-depth tests of the heavier algorithms like rref etc should go in +# their own test files. +# +# Any new methods added to the DDM, SDM or DFM classes should be tested here +# and added to all classes. +# + +from sympy.external.gmpy import GROUND_TYPES + +from sympy import ZZ, QQ, GF, ZZ_I, symbols + +from sympy.polys.matrices.exceptions import ( + DMBadInputError, + DMDomainError, + DMNonSquareMatrixError, + DMNonInvertibleMatrixError, + DMShapeError, +) + +from sympy.polys.matrices.domainmatrix import DM, DomainMatrix, DDM, SDM, DFM + +from sympy.testing.pytest import raises, skip +import pytest + + +def test_XXM_constructors(): + """Test the DDM, etc constructors.""" + + lol = [ + [ZZ(1), ZZ(2)], + [ZZ(3), ZZ(4)], + [ZZ(5), ZZ(6)], + ] + dod = { + 0: {0: ZZ(1), 1: ZZ(2)}, + 1: {0: ZZ(3), 1: ZZ(4)}, + 2: {0: ZZ(5), 1: ZZ(6)}, + } + + lol_0x0 = [] + lol_0x2 = [] + lol_2x0 = [[], []] + dod_0x0 = {} + dod_0x2 = {} + dod_2x0 = {} + + lol_bad = [ + [ZZ(1), ZZ(2)], + [ZZ(3), ZZ(4)], + [ZZ(5), ZZ(6), ZZ(7)], + ] + dod_bad = { + 0: {0: ZZ(1), 1: ZZ(2)}, + 1: {0: ZZ(3), 1: ZZ(4)}, + 2: {0: ZZ(5), 1: ZZ(6), 2: ZZ(7)}, + } + + XDM_dense = [DDM] + XDM_sparse = [SDM] + + if GROUND_TYPES == 'flint': + XDM_dense.append(DFM) + + for XDM in XDM_dense: + + A = XDM(lol, (3, 2), ZZ) + assert A.rows == 3 + assert A.cols == 2 + assert A.domain == ZZ + assert A.shape == (3, 2) + if XDM is not DFM: + assert ZZ.of_type(A[0][0]) is True + else: + assert ZZ.of_type(A.rep[0, 0]) is True + + Adm = DomainMatrix(lol, (3, 2), ZZ) + if XDM is DFM: + assert Adm.rep == A + assert Adm.rep.to_ddm() != A + elif GROUND_TYPES == 'flint': + assert Adm.rep.to_ddm() == A + assert Adm.rep != A + else: + assert Adm.rep == A + assert Adm.rep.to_ddm() == A + + assert XDM(lol_0x0, (0, 0), ZZ).shape == (0, 0) + assert XDM(lol_0x2, (0, 2), ZZ).shape == (0, 2) + assert XDM(lol_2x0, (2, 0), ZZ).shape == (2, 0) + raises(DMBadInputError, lambda: XDM(lol, (2, 3), ZZ)) + raises(DMBadInputError, lambda: XDM(lol_bad, (3, 2), ZZ)) + raises(DMBadInputError, lambda: XDM(dod, (3, 2), ZZ)) + + for XDM in XDM_sparse: + + A = XDM(dod, (3, 2), ZZ) + assert A.rows == 3 + assert A.cols == 2 + assert A.domain == ZZ + assert A.shape == (3, 2) + assert ZZ.of_type(A[0][0]) is True + + assert DomainMatrix(dod, (3, 2), ZZ).rep == A + + assert XDM(dod_0x0, (0, 0), ZZ).shape == (0, 0) + assert XDM(dod_0x2, (0, 2), ZZ).shape == (0, 2) + assert XDM(dod_2x0, (2, 0), ZZ).shape == (2, 0) + raises(DMBadInputError, lambda: XDM(dod, (2, 3), ZZ)) + raises(DMBadInputError, lambda: XDM(lol, (3, 2), ZZ)) + raises(DMBadInputError, lambda: XDM(dod_bad, (3, 2), ZZ)) + + raises(DMBadInputError, lambda: DomainMatrix(lol, (2, 3), ZZ)) + raises(DMBadInputError, lambda: DomainMatrix(lol_bad, (3, 2), ZZ)) + raises(DMBadInputError, lambda: DomainMatrix(dod_bad, (3, 2), ZZ)) + + +def test_XXM_eq(): + """Test equality for DDM, SDM, DFM and DomainMatrix.""" + + lol1 = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]] + dod1 = {0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}} + + lol2 = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(5)]] + dod2 = {0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(5)}} + + A1_ddm = DDM(lol1, (2, 2), ZZ) + A1_sdm = SDM(dod1, (2, 2), ZZ) + A1_dm_d = DomainMatrix(lol1, (2, 2), ZZ) + A1_dm_s = DomainMatrix(dod1, (2, 2), ZZ) + + A2_ddm = DDM(lol2, (2, 2), ZZ) + A2_sdm = SDM(dod2, (2, 2), ZZ) + A2_dm_d = DomainMatrix(lol2, (2, 2), ZZ) + A2_dm_s = DomainMatrix(dod2, (2, 2), ZZ) + + A1_all = [A1_ddm, A1_sdm, A1_dm_d, A1_dm_s] + A2_all = [A2_ddm, A2_sdm, A2_dm_d, A2_dm_s] + + if GROUND_TYPES == 'flint': + + A1_dfm = DFM([[1, 2], [3, 4]], (2, 2), ZZ) + A2_dfm = DFM([[1, 2], [3, 5]], (2, 2), ZZ) + + A1_all.append(A1_dfm) + A2_all.append(A2_dfm) + + for n, An in enumerate(A1_all): + for m, Am in enumerate(A1_all): + if n == m: + assert (An == Am) is True + assert (An != Am) is False + else: + assert (An == Am) is False + assert (An != Am) is True + + for n, An in enumerate(A2_all): + for m, Am in enumerate(A2_all): + if n == m: + assert (An == Am) is True + assert (An != Am) is False + else: + assert (An == Am) is False + assert (An != Am) is True + + for n, A1 in enumerate(A1_all): + for m, A2 in enumerate(A2_all): + assert (A1 == A2) is False + assert (A1 != A2) is True + + +def test_to_XXM(): + """Test to_ddm etc. for DDM, SDM, DFM and DomainMatrix.""" + + lol = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]] + dod = {0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}} + + A_ddm = DDM(lol, (2, 2), ZZ) + A_sdm = SDM(dod, (2, 2), ZZ) + A_dm_d = DomainMatrix(lol, (2, 2), ZZ) + A_dm_s = DomainMatrix(dod, (2, 2), ZZ) + + A_all = [A_ddm, A_sdm, A_dm_d, A_dm_s] + + if GROUND_TYPES == 'flint': + A_dfm = DFM(lol, (2, 2), ZZ) + A_all.append(A_dfm) + + for A in A_all: + assert A.to_ddm() == A_ddm + assert A.to_sdm() == A_sdm + if GROUND_TYPES != 'flint': + raises(NotImplementedError, lambda: A.to_dfm()) + assert A.to_dfm_or_ddm() == A_ddm + + # Add e.g. DDM.to_DM()? + # assert A.to_DM() == A_dm + + if GROUND_TYPES == 'flint': + for A in A_all: + assert A.to_dfm() == A_dfm + for K in [ZZ, QQ, GF(5), ZZ_I]: + if isinstance(A, DFM) and not DFM._supports_domain(K): + raises(NotImplementedError, lambda: A.convert_to(K)) + else: + A_K = A.convert_to(K) + if DFM._supports_domain(K): + A_dfm_K = A_dfm.convert_to(K) + assert A_K.to_dfm() == A_dfm_K + assert A_K.to_dfm_or_ddm() == A_dfm_K + else: + raises(NotImplementedError, lambda: A_K.to_dfm()) + assert A_K.to_dfm_or_ddm() == A_ddm.convert_to(K) + + +def test_DFM_domains(): + """Test which domains are supported by DFM.""" + + x, y = symbols('x, y') + + if GROUND_TYPES in ('python', 'gmpy'): + + supported = [] + flint_funcs = {} + not_supported = [ZZ, QQ, GF(5), QQ[x], QQ[x,y]] + + elif GROUND_TYPES == 'flint': + + import flint + supported = [ZZ, QQ] + flint_funcs = { + ZZ: flint.fmpz_mat, + QQ: flint.fmpq_mat, + GF(5): None, + } + not_supported = [ + # Other domains could be supported but not implemented as matrices + # in python-flint: + QQ[x], + QQ[x,y], + QQ.frac_field(x,y), + # Others would potentially never be supported by python-flint: + ZZ_I, + ] + + else: + assert False, "Unknown GROUND_TYPES: %s" % GROUND_TYPES + + for domain in supported: + assert DFM._supports_domain(domain) is True + if flint_funcs[domain] is not None: + assert DFM._get_flint_func(domain) == flint_funcs[domain] + for domain in not_supported: + assert DFM._supports_domain(domain) is False + raises(NotImplementedError, lambda: DFM._get_flint_func(domain)) + + +def _DM(lol, typ, K): + """Make a DM of type typ over K from lol.""" + A = DM(lol, K) + + if typ == 'DDM': + return A.to_ddm() + elif typ == 'SDM': + return A.to_sdm() + elif typ == 'DFM': + if GROUND_TYPES != 'flint': + skip("DFM not supported in this ground type") + return A.to_dfm() + else: + assert False, "Unknown type %s" % typ + + +def _DMZ(lol, typ): + """Make a DM of type typ over ZZ from lol.""" + return _DM(lol, typ, ZZ) + + +def _DMQ(lol, typ): + """Make a DM of type typ over QQ from lol.""" + return _DM(lol, typ, QQ) + + +def DM_ddm(lol, K): + """Make a DDM over K from lol.""" + return _DM(lol, 'DDM', K) + + +def DM_sdm(lol, K): + """Make a SDM over K from lol.""" + return _DM(lol, 'SDM', K) + + +def DM_dfm(lol, K): + """Make a DFM over K from lol.""" + return _DM(lol, 'DFM', K) + + +def DMZ_ddm(lol): + """Make a DDM from lol.""" + return _DMZ(lol, 'DDM') + + +def DMZ_sdm(lol): + """Make a SDM from lol.""" + return _DMZ(lol, 'SDM') + + +def DMZ_dfm(lol): + """Make a DFM from lol.""" + return _DMZ(lol, 'DFM') + + +def DMQ_ddm(lol): + """Make a DDM from lol.""" + return _DMQ(lol, 'DDM') + + +def DMQ_sdm(lol): + """Make a SDM from lol.""" + return _DMQ(lol, 'SDM') + + +def DMQ_dfm(lol): + """Make a DFM from lol.""" + return _DMQ(lol, 'DFM') + + +DM_all = [DM_ddm, DM_sdm, DM_dfm] +DMZ_all = [DMZ_ddm, DMZ_sdm, DMZ_dfm] +DMQ_all = [DMQ_ddm, DMQ_sdm, DMQ_dfm] + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XDM_getitem(DM): + """Test getitem for DDM, etc.""" + + lol = [[0, 1], [2, 0]] + A = DM(lol) + m, n = A.shape + + indices = [-3, -2, -1, 0, 1, 2] + + for i in indices: + for j in indices: + if -2 <= i < m and -2 <= j < n: + assert A.getitem(i, j) == ZZ(lol[i][j]) + else: + raises(IndexError, lambda: A.getitem(i, j)) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XDM_setitem(DM): + """Test setitem for DDM, etc.""" + + A = DM([[0, 1, 2], [3, 4, 5]]) + + A.setitem(0, 0, ZZ(6)) + assert A == DM([[6, 1, 2], [3, 4, 5]]) + + A.setitem(0, 1, ZZ(7)) + assert A == DM([[6, 7, 2], [3, 4, 5]]) + + A.setitem(0, 2, ZZ(8)) + assert A == DM([[6, 7, 8], [3, 4, 5]]) + + A.setitem(0, -1, ZZ(9)) + assert A == DM([[6, 7, 9], [3, 4, 5]]) + + A.setitem(0, -2, ZZ(10)) + assert A == DM([[6, 10, 9], [3, 4, 5]]) + + A.setitem(0, -3, ZZ(11)) + assert A == DM([[11, 10, 9], [3, 4, 5]]) + + raises(IndexError, lambda: A.setitem(0, 3, ZZ(12))) + raises(IndexError, lambda: A.setitem(0, -4, ZZ(13))) + + A.setitem(1, 0, ZZ(14)) + assert A == DM([[11, 10, 9], [14, 4, 5]]) + + A.setitem(1, 1, ZZ(15)) + assert A == DM([[11, 10, 9], [14, 15, 5]]) + + A.setitem(-1, 1, ZZ(16)) + assert A == DM([[11, 10, 9], [14, 16, 5]]) + + A.setitem(-2, 1, ZZ(17)) + assert A == DM([[11, 17, 9], [14, 16, 5]]) + + raises(IndexError, lambda: A.setitem(2, 0, ZZ(18))) + raises(IndexError, lambda: A.setitem(-3, 0, ZZ(19))) + + A.setitem(1, 2, ZZ(0)) + assert A == DM([[11, 17, 9], [14, 16, 0]]) + + A.setitem(1, -2, ZZ(0)) + assert A == DM([[11, 17, 9], [14, 0, 0]]) + + A.setitem(1, -3, ZZ(0)) + assert A == DM([[11, 17, 9], [0, 0, 0]]) + + A.setitem(0, 0, ZZ(0)) + assert A == DM([[0, 17, 9], [0, 0, 0]]) + + A.setitem(0, -1, ZZ(0)) + assert A == DM([[0, 17, 0], [0, 0, 0]]) + + A.setitem(0, 0, ZZ(0)) + assert A == DM([[0, 17, 0], [0, 0, 0]]) + + A.setitem(0, -2, ZZ(0)) + assert A == DM([[0, 0, 0], [0, 0, 0]]) + + A.setitem(0, -3, ZZ(1)) + assert A == DM([[1, 0, 0], [0, 0, 0]]) + + +class _Sliced: + def __getitem__(self, item): + return item + + +_slice = _Sliced() + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_extract_slice(DM): + A = DM([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert A.extract_slice(*_slice[:,:]) == A + assert A.extract_slice(*_slice[1:,:]) == DM([[4, 5, 6], [7, 8, 9]]) + assert A.extract_slice(*_slice[1:,1:]) == DM([[5, 6], [8, 9]]) + assert A.extract_slice(*_slice[1:,:-1]) == DM([[4, 5], [7, 8]]) + assert A.extract_slice(*_slice[1:,:-1:2]) == DM([[4], [7]]) + assert A.extract_slice(*_slice[:,::2]) == DM([[1, 3], [4, 6], [7, 9]]) + assert A.extract_slice(*_slice[::2,:]) == DM([[1, 2, 3], [7, 8, 9]]) + assert A.extract_slice(*_slice[::2,::2]) == DM([[1, 3], [7, 9]]) + assert A.extract_slice(*_slice[::2,::-2]) == DM([[3, 1], [9, 7]]) + assert A.extract_slice(*_slice[::-2,::2]) == DM([[7, 9], [1, 3]]) + assert A.extract_slice(*_slice[::-2,::-2]) == DM([[9, 7], [3, 1]]) + assert A.extract_slice(*_slice[:,::-1]) == DM([[3, 2, 1], [6, 5, 4], [9, 8, 7]]) + assert A.extract_slice(*_slice[::-1,:]) == DM([[7, 8, 9], [4, 5, 6], [1, 2, 3]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_extract(DM): + + A = DM([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + + assert A.extract([0, 1, 2], [0, 1, 2]) == A + assert A.extract([1, 2], [1, 2]) == DM([[5, 6], [8, 9]]) + assert A.extract([1, 2], [0, 1]) == DM([[4, 5], [7, 8]]) + assert A.extract([1, 2], [0, 2]) == DM([[4, 6], [7, 9]]) + assert A.extract([1, 2], [0]) == DM([[4], [7]]) + assert A.extract([1, 2], []) == DM([[1]]).zeros((2, 0), ZZ) + assert A.extract([], [0, 1, 2]) == DM([[1]]).zeros((0, 3), ZZ) + + raises(IndexError, lambda: A.extract([1, 2], [0, 3])) + raises(IndexError, lambda: A.extract([1, 2], [0, -4])) + raises(IndexError, lambda: A.extract([3, 1], [0, 1])) + raises(IndexError, lambda: A.extract([-4, 2], [3, 1])) + + B = DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + assert B.extract([1, 2], [1, 2]) == DM([[0, 0], [0, 0]]) + + +def test_XXM_str(): + + A = DomainMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]], (3, 3), ZZ) + + assert str(A) == \ + 'DomainMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]], (3, 3), ZZ)' + assert str(A.to_ddm()) == \ + '[[1, 2, 3], [4, 5, 6], [7, 8, 9]]' + assert str(A.to_sdm()) == \ + '{0: {0: 1, 1: 2, 2: 3}, 1: {0: 4, 1: 5, 2: 6}, 2: {0: 7, 1: 8, 2: 9}}' + + assert repr(A) == \ + 'DomainMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]], (3, 3), ZZ)' + assert repr(A.to_ddm()) == \ + 'DDM([[1, 2, 3], [4, 5, 6], [7, 8, 9]], (3, 3), ZZ)' + assert repr(A.to_sdm()) == \ + 'SDM({0: {0: 1, 1: 2, 2: 3}, 1: {0: 4, 1: 5, 2: 6}, 2: {0: 7, 1: 8, 2: 9}}, (3, 3), ZZ)' + + B = DomainMatrix({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3)}}, (2, 2), ZZ) + + assert str(B) == \ + 'DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3}}, (2, 2), ZZ)' + assert str(B.to_ddm()) == \ + '[[1, 2], [3, 0]]' + assert str(B.to_sdm()) == \ + '{0: {0: 1, 1: 2}, 1: {0: 3}}' + + assert repr(B) == \ + 'DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3}}, (2, 2), ZZ)' + + if GROUND_TYPES != 'gmpy': + assert repr(B.to_ddm()) == \ + 'DDM([[1, 2], [3, 0]], (2, 2), ZZ)' + assert repr(B.to_sdm()) == \ + 'SDM({0: {0: 1, 1: 2}, 1: {0: 3}}, (2, 2), ZZ)' + else: + assert repr(B.to_ddm()) == \ + 'DDM([[mpz(1), mpz(2)], [mpz(3), mpz(0)]], (2, 2), ZZ)' + assert repr(B.to_sdm()) == \ + 'SDM({0: {0: mpz(1), 1: mpz(2)}, 1: {0: mpz(3)}}, (2, 2), ZZ)' + + if GROUND_TYPES == 'flint': + + assert str(A.to_dfm()) == \ + '[[1, 2, 3], [4, 5, 6], [7, 8, 9]]' + assert str(B.to_dfm()) == \ + '[[1, 2], [3, 0]]' + + assert repr(A.to_dfm()) == \ + 'DFM([[1, 2, 3], [4, 5, 6], [7, 8, 9]], (3, 3), ZZ)' + assert repr(B.to_dfm()) == \ + 'DFM([[1, 2], [3, 0]], (2, 2), ZZ)' + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_from_list(DM): + T = type(DM([[0]])) + + lol = [[1, 2, 4], [4, 5, 6]] + lol_ZZ = [[ZZ(1), ZZ(2), ZZ(4)], [ZZ(4), ZZ(5), ZZ(6)]] + lol_ZZ_bad = [[ZZ(1), ZZ(2), ZZ(4)], [ZZ(4), ZZ(5), ZZ(6), ZZ(7)]] + + assert T.from_list(lol_ZZ, (2, 3), ZZ) == DM(lol) + raises(DMBadInputError, lambda: T.from_list(lol_ZZ_bad, (3, 2), ZZ)) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_to_list(DM): + lol = [[1, 2, 4], [4, 5, 6]] + assert DM(lol).to_list() == [[ZZ(1), ZZ(2), ZZ(4)], [ZZ(4), ZZ(5), ZZ(6)]] + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_to_list_flat(DM): + lol = [[1, 2, 4], [4, 5, 6]] + assert DM(lol).to_list_flat() == [ZZ(1), ZZ(2), ZZ(4), ZZ(4), ZZ(5), ZZ(6)] + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_from_list_flat(DM): + T = type(DM([[0]])) + flat = [ZZ(1), ZZ(2), ZZ(4), ZZ(4), ZZ(5), ZZ(6)] + assert T.from_list_flat(flat, (2, 3), ZZ) == DM([[1, 2, 4], [4, 5, 6]]) + raises(DMBadInputError, lambda: T.from_list_flat(flat, (3, 3), ZZ)) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_to_flat_nz(DM): + M = DM([[1, 2, 0], [0, 0, 0], [0, 0, 3]]) + elements = [ZZ(1), ZZ(2), ZZ(3)] + indices = ((0, 0), (0, 1), (2, 2)) + assert M.to_flat_nz() == (elements, (indices, M.shape)) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_from_flat_nz(DM): + T = type(DM([[0]])) + elements = [ZZ(1), ZZ(2), ZZ(3)] + indices = ((0, 0), (0, 1), (2, 2)) + data = (indices, (3, 3)) + result = DM([[1, 2, 0], [0, 0, 0], [0, 0, 3]]) + assert T.from_flat_nz(elements, data, ZZ) == result + raises(DMBadInputError, lambda: T.from_flat_nz(elements, (indices, (2, 3)), ZZ)) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_to_dod(DM): + dod = {0: {0: ZZ(1), 2: ZZ(4)}, 1: {0: ZZ(4), 1: ZZ(5), 2: ZZ(6)}} + assert DM([[1, 0, 4], [4, 5, 6]]).to_dod() == dod + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_from_dod(DM): + T = type(DM([[0]])) + dod = {0: {0: ZZ(1), 2: ZZ(4)}, 1: {0: ZZ(4), 1: ZZ(5), 2: ZZ(6)}} + assert T.from_dod(dod, (2, 3), ZZ) == DM([[1, 0, 4], [4, 5, 6]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_to_dok(DM): + dod = {(0, 0): ZZ(1), (0, 2): ZZ(4), + (1, 0): ZZ(4), (1, 1): ZZ(5), (1, 2): ZZ(6)} + assert DM([[1, 0, 4], [4, 5, 6]]).to_dok() == dod + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_from_dok(DM): + T = type(DM([[0]])) + dod = {(0, 0): ZZ(1), (0, 2): ZZ(4), + (1, 0): ZZ(4), (1, 1): ZZ(5), (1, 2): ZZ(6)} + assert T.from_dok(dod, (2, 3), ZZ) == DM([[1, 0, 4], [4, 5, 6]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_iter_values(DM): + values = [ZZ(1), ZZ(4), ZZ(4), ZZ(5), ZZ(6)] + assert sorted(DM([[1, 0, 4], [4, 5, 6]]).iter_values()) == values + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_iter_items(DM): + items = [((0, 0), ZZ(1)), ((0, 2), ZZ(4)), + ((1, 0), ZZ(4)), ((1, 1), ZZ(5)), ((1, 2), ZZ(6))] + assert sorted(DM([[1, 0, 4], [4, 5, 6]]).iter_items()) == items + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_from_ddm(DM): + T = type(DM([[0]])) + ddm = DDM([[1, 2, 4], [4, 5, 6]], (2, 3), ZZ) + assert T.from_ddm(ddm) == DM([[1, 2, 4], [4, 5, 6]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_zeros(DM): + T = type(DM([[0]])) + assert T.zeros((2, 3), ZZ) == DM([[0, 0, 0], [0, 0, 0]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_ones(DM): + T = type(DM([[0]])) + assert T.ones((2, 3), ZZ) == DM([[1, 1, 1], [1, 1, 1]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_eye(DM): + T = type(DM([[0]])) + assert T.eye(3, ZZ) == DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + assert T.eye((3, 2), ZZ) == DM([[1, 0], [0, 1], [0, 0]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_diag(DM): + T = type(DM([[0]])) + assert T.diag([1, 2, 3], ZZ) == DM([[1, 0, 0], [0, 2, 0], [0, 0, 3]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_transpose(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + assert A.transpose() == DM([[1, 4], [2, 5], [3, 6]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_add(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + B = DM([[1, 2, 3], [4, 5, 6]]) + C = DM([[2, 4, 6], [8, 10, 12]]) + assert A.add(B) == C + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_sub(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + B = DM([[1, 2, 3], [4, 5, 6]]) + C = DM([[0, 0, 0], [0, 0, 0]]) + assert A.sub(B) == C + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_mul(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + b = ZZ(2) + assert A.mul(b) == DM([[2, 4, 6], [8, 10, 12]]) + assert A.rmul(b) == DM([[2, 4, 6], [8, 10, 12]]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_matmul(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + B = DM([[1, 2], [3, 4], [5, 6]]) + C = DM([[22, 28], [49, 64]]) + assert A.matmul(B) == C + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_mul_elementwise(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + B = DM([[1, 2, 3], [4, 5, 6]]) + C = DM([[1, 4, 9], [16, 25, 36]]) + assert A.mul_elementwise(B) == C + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_neg(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + C = DM([[-1, -2, -3], [-4, -5, -6]]) + assert A.neg() == C + + +@pytest.mark.parametrize('DM', DM_all) +def test_XXM_convert_to(DM): + A = DM([[1, 2, 3], [4, 5, 6]], ZZ) + B = DM([[1, 2, 3], [4, 5, 6]], QQ) + assert A.convert_to(QQ) == B + assert B.convert_to(ZZ) == A + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_scc(DM): + A = DM([ + [0, 1, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 1], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 1, 0, 1]]) + assert A.scc() == [[0, 1], [2], [3, 5], [4]] + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_hstack(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + B = DM([[7, 8], [9, 10]]) + C = DM([[1, 2, 3, 7, 8], [4, 5, 6, 9, 10]]) + ABC = DM([[1, 2, 3, 7, 8, 1, 2, 3, 7, 8], + [4, 5, 6, 9, 10, 4, 5, 6, 9, 10]]) + assert A.hstack(B) == C + assert A.hstack(B, C) == ABC + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_vstack(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + B = DM([[7, 8, 9]]) + C = DM([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + ABC = DM([[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert A.vstack(B) == C + assert A.vstack(B, C) == ABC + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_applyfunc(DM): + A = DM([[1, 2, 3], [4, 5, 6]]) + B = DM([[2, 4, 6], [8, 10, 12]]) + assert A.applyfunc(lambda x: 2*x, ZZ) == B + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_is_upper(DM): + assert DM([[1, 2, 3], [0, 5, 6]]).is_upper() is True + assert DM([[1, 2, 3], [4, 5, 6]]).is_upper() is False + assert DM([]).is_upper() is True + assert DM([[], []]).is_upper() is True + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_is_lower(DM): + assert DM([[1, 0, 0], [4, 5, 0]]).is_lower() is True + assert DM([[1, 2, 3], [4, 5, 6]]).is_lower() is False + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_is_diagonal(DM): + assert DM([[1, 0, 0], [0, 5, 0]]).is_diagonal() is True + assert DM([[1, 2, 3], [4, 5, 6]]).is_diagonal() is False + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_diagonal(DM): + assert DM([[1, 0, 0], [0, 5, 0]]).diagonal() == [1, 5] + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_is_zero_matrix(DM): + assert DM([[0, 0, 0], [0, 0, 0]]).is_zero_matrix() is True + assert DM([[1, 0, 0], [0, 0, 0]]).is_zero_matrix() is False + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_det_ZZ(DM): + assert DM([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).det() == 0 + assert DM([[1, 2, 3], [4, 5, 6], [7, 8, 10]]).det() == -3 + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_det_QQ(DM): + dM1 = DM([[(1,2), (2,3)], [(3,4), (4,5)]]) + assert dM1.det() == QQ(-1,10) + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_inv_QQ(DM): + dM1 = DM([[(1,2), (2,3)], [(3,4), (4,5)]]) + dM2 = DM([[(-8,1), (20,3)], [(15,2), (-5,1)]]) + assert dM1.inv() == dM2 + assert dM1.matmul(dM2) == DM([[1, 0], [0, 1]]) + + dM3 = DM([[(1,2), (2,3)], [(1,4), (1,3)]]) + raises(DMNonInvertibleMatrixError, lambda: dM3.inv()) + + dM4 = DM([[(1,2), (2,3), (3,4)], [(1,4), (1,3), (1,2)]]) + raises(DMNonSquareMatrixError, lambda: dM4.inv()) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_inv_ZZ(DM): + dM1 = DM([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) + # XXX: Maybe this should return a DM over QQ instead? + # XXX: Handle unimodular matrices? + raises(DMDomainError, lambda: dM1.inv()) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_charpoly_ZZ(DM): + dM1 = DM([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) + assert dM1.charpoly() == [1, -16, -12, 3] + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_charpoly_QQ(DM): + dM1 = DM([[(1,2), (2,3)], [(3,4), (4,5)]]) + assert dM1.charpoly() == [QQ(1,1), QQ(-13,10), QQ(-1,10)] + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_lu_solve_ZZ(DM): + dM1 = DM([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) + dM2 = DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + raises(DMDomainError, lambda: dM1.lu_solve(dM2)) + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_lu_solve_QQ(DM): + dM1 = DM([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) + dM2 = DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + dM3 = DM([[(-2,3),(-4,3),(1,1)],[(-2,3),(11,3),(-2,1)],[(1,1),(-2,1),(1,1)]]) + assert dM1.lu_solve(dM2) == dM3 == dM1.inv() + + dM4 = DM([[1, 2, 3], [4, 5, 6]]) + dM5 = DM([[1, 0], [0, 1], [0, 0]]) + raises(DMShapeError, lambda: dM4.lu_solve(dM5)) + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_nullspace_QQ(DM): + dM1 = DM([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + # XXX: Change the signature to just return the nullspace. Possibly + # returning the rank or nullity makes sense but the list of nonpivots is + # not useful. + assert dM1.nullspace() == (DM([[1, -2, 1]]), [2]) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_lll(DM): + M = DM([[1, 2, 3], [4, 5, 20]]) + M_lll = DM([[1, 2, 3], [-1, -5, 5]]) + T = DM([[1, 0], [-5, 1]]) + assert M.lll() == M_lll + assert M.lll_transform() == (M_lll, T) + assert T.matmul(M) == M_lll + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_mixed_signs(DM): + lol = [[QQ(1), QQ(-2)], [QQ(-3), QQ(4)]] + A = DM(lol) + Q, R = A.qr() + assert Q.matmul(R) == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_large_matrix(DM): + lol = [[QQ(i + j) for j in range(10)] for i in range(10)] + A = DM(lol) + Q, R = A.qr() + assert Q.matmul(R) == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_identity_matrix(DM): + T = type(DM([[0]])) + A = T.eye(3, QQ) + Q, R = A.qr() + assert Q == A + assert R == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + assert Q.shape == (3, 3) + assert R.shape == (3, 3) + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_square_matrix(DM): + lol = [[QQ(3), QQ(1)], [QQ(4), QQ(3)]] + A = DM(lol) + Q, R = A.qr() + assert Q.matmul(R) == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_matrix_with_zero_columns(DM): + lol = [[QQ(3), QQ(0)], [QQ(4), QQ(0)]] + A = DM(lol) + Q, R = A.qr() + assert Q.matmul(R) == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_linearly_dependent_columns(DM): + lol = [[QQ(1), QQ(2)], [QQ(2), QQ(4)]] + A = DM(lol) + Q, R = A.qr() + assert Q.matmul(R) == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_qr_non_field(DM): + lol = [[ZZ(3), ZZ(1)], [ZZ(4), ZZ(3)]] + A = DM(lol) + with pytest.raises(DMDomainError): + A.qr() + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_field(DM): + lol = [[QQ(3), QQ(1)], [QQ(4), QQ(3)]] + A = DM(lol) + Q, R = A.qr() + assert Q.matmul(R) == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_tall_matrix(DM): + lol = [[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]] + A = DM(lol) + Q, R = A.qr() + assert Q.matmul(R) == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_wide_matrix(DM): + lol = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]] + A = DM(lol) + Q, R = A.qr() + assert Q.matmul(R) == A + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_empty_matrix_0x0(DM): + T = type(DM([[0]])) + A = T.zeros((0, 0), QQ) + Q, R = A.qr() + assert Q.matmul(R).shape == A.shape + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + assert Q.shape == (0, 0) + assert R.shape == (0, 0) + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_empty_matrix_2x0(DM): + T = type(DM([[0]])) + A = T.zeros((2, 0), QQ) + Q, R = A.qr() + assert Q.matmul(R).shape == A.shape + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + assert Q.shape == (2, 0) + assert R.shape == (0, 0) + + +@pytest.mark.parametrize('DM', DMQ_all) +def test_XXM_qr_empty_matrix_0x2(DM): + T = type(DM([[0]])) + A = T.zeros((0, 2), QQ) + Q, R = A.qr() + assert Q.matmul(R).shape == A.shape + assert (Q.transpose().matmul(Q)).is_diagonal + assert R.is_upper + assert Q.shape == (0, 0) + assert R.shape == (0, 2) + + +@pytest.mark.parametrize('DM', DMZ_all) +def test_XXM_fflu(DM): + A = DM([[1, 2], [3, 4]]) + P, L, D, U = A.fflu() + A_field = A.convert_to(QQ) + P_field = P.convert_to(QQ) + L_field = L.convert_to(QQ) + D_field = D.convert_to(QQ) + U_field = U.convert_to(QQ) + assert P.shape == A.shape + assert L.shape == A.shape + assert D.shape == A.shape + assert U.shape == A.shape + assert P == DM([[1, 0], [0, 1]]) + assert L == DM([[1, 0], [3, -2]]) + assert D == DM([[1, 0], [0, -2]]) + assert U == DM([[1, 2], [0, -2]]) + assert L_field.matmul(D_field.inv()).matmul(U_field) == P_field.matmul(A_field) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__init__.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..38403fdf80be22d47589a346d1b1878b982c3c93 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__init__.py @@ -0,0 +1,27 @@ +"""Computational algebraic field theory. """ + +__all__ = [ + 'minpoly', 'minimal_polynomial', + + 'field_isomorphism', 'primitive_element', 'to_number_field', + + 'isolate', + + 'round_two', + + 'prime_decomp', 'prime_valuation', + + 'galois_group', +] + +from .minpoly import minpoly, minimal_polynomial + +from .subfield import field_isomorphism, primitive_element, to_number_field + +from .utilities import isolate + +from .basis import round_two + +from .primes import prime_decomp, prime_valuation + +from .galoisgroups import galois_group diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ff2d128a2e5e904235573c7595946358dcb15cd Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/basis.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/basis.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62730ab0a6920fe63626ad0f4702b5575ac36cbe Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/basis.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/exceptions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/exceptions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f47640e704b1e6ce00b0c411df53ebc222f323f3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/exceptions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/galois_resolvents.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/galois_resolvents.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10bb66a6cf62b57a33c0d4082cebb3b42e2d6474 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/galois_resolvents.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/galoisgroups.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/galoisgroups.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc93b7e4a2e3b202a90f5812d542252f9484f3ee Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/galoisgroups.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/minpoly.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/minpoly.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..751300cc5c0b5d4e712b33e16f944a1d787044ff Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/minpoly.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/modules.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/modules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..133862ef5d0be06de0deac107a096fc89e9dacc8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/modules.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/primes.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/primes.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3d7c16b06f64356f896a94c77cdaf5ea0873c82 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/primes.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/resolvent_lookup.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/resolvent_lookup.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3982f39ff00bc7cccd87f959404f892b757b4db Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/resolvent_lookup.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/subfield.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/subfield.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06c781c91fc0dd0e1d5f46902374bbce1139daa2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/subfield.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/utilities.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/utilities.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f2e2d4e44a90c4950de30a5c2d96ffd7f28b9d6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/__pycache__/utilities.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/basis.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/basis.py new file mode 100644 index 0000000000000000000000000000000000000000..7c9cb41925973b3a10a80cc6ba1442cf44330971 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/basis.py @@ -0,0 +1,246 @@ +"""Computing integral bases for number fields. """ + +from sympy.polys.polytools import Poly +from sympy.polys.domains.algebraicfield import AlgebraicField +from sympy.polys.domains.integerring import ZZ +from sympy.polys.domains.rationalfield import QQ +from sympy.utilities.decorator import public +from .modules import ModuleEndomorphism, ModuleHomomorphism, PowerBasis +from .utilities import extract_fundamental_discriminant + + +def _apply_Dedekind_criterion(T, p): + r""" + Apply the "Dedekind criterion" to test whether the order needs to be + enlarged relative to a given prime *p*. + """ + x = T.gen + T_bar = Poly(T, modulus=p) + lc, fl = T_bar.factor_list() + assert lc == 1 + g_bar = Poly(1, x, modulus=p) + for ti_bar, _ in fl: + g_bar *= ti_bar + h_bar = T_bar // g_bar + g = Poly(g_bar, domain=ZZ) + h = Poly(h_bar, domain=ZZ) + f = (g * h - T) // p + f_bar = Poly(f, modulus=p) + Z_bar = f_bar + for b in [g_bar, h_bar]: + Z_bar = Z_bar.gcd(b) + U_bar = T_bar // Z_bar + m = Z_bar.degree() + return U_bar, m + + +def nilradical_mod_p(H, p, q=None): + r""" + Compute the nilradical mod *p* for a given order *H*, and prime *p*. + + Explanation + =========== + + This is the ideal $I$ in $H/pH$ consisting of all elements some positive + power of which is zero in this quotient ring, i.e. is a multiple of *p*. + + Parameters + ========== + + H : :py:class:`~.Submodule` + The given order. + p : int + The rational prime. + q : int, optional + If known, the smallest power of *p* that is $>=$ the dimension of *H*. + If not provided, we compute it here. + + Returns + ======= + + :py:class:`~.Module` representing the nilradical mod *p* in *H*. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*. + (See Lemma 6.1.6.) + + """ + n = H.n + if q is None: + q = p + while q < n: + q *= p + phi = ModuleEndomorphism(H, lambda x: x**q) + return phi.kernel(modulus=p) + + +def _second_enlargement(H, p, q): + r""" + Perform the second enlargement in the Round Two algorithm. + """ + Ip = nilradical_mod_p(H, p, q=q) + B = H.parent.submodule_from_matrix(H.matrix * Ip.matrix, denom=H.denom) + C = B + p*H + E = C.endomorphism_ring() + phi = ModuleHomomorphism(H, E, lambda x: E.inner_endomorphism(x)) + gamma = phi.kernel(modulus=p) + G = H.parent.submodule_from_matrix(H.matrix * gamma.matrix, denom=H.denom * p) + H1 = G + H + return H1, Ip + + +@public +def round_two(T, radicals=None): + r""" + Zassenhaus's "Round 2" algorithm. + + Explanation + =========== + + Carry out Zassenhaus's "Round 2" algorithm on an irreducible polynomial + *T* over :ref:`ZZ` or :ref:`QQ`. This computes an integral basis and the + discriminant for the field $K = \mathbb{Q}[x]/(T(x))$. + + Alternatively, you may pass an :py:class:`~.AlgebraicField` instance, in + place of the polynomial *T*, in which case the algorithm is applied to the + minimal polynomial for the field's primitive element. + + Ordinarily this function need not be called directly, as one can instead + access the :py:meth:`~.AlgebraicField.maximal_order`, + :py:meth:`~.AlgebraicField.integral_basis`, and + :py:meth:`~.AlgebraicField.discriminant` methods of an + :py:class:`~.AlgebraicField`. + + Examples + ======== + + Working through an AlgebraicField: + + >>> from sympy import Poly, QQ + >>> from sympy.abc import x + >>> T = Poly(x ** 3 + x ** 2 - 2 * x + 8) + >>> K = QQ.alg_field_from_poly(T, "theta") + >>> print(K.maximal_order()) + Submodule[[2, 0, 0], [0, 2, 0], [0, 1, 1]]/2 + >>> print(K.discriminant()) + -503 + >>> print(K.integral_basis(fmt='sympy')) + [1, theta, theta/2 + theta**2/2] + + Calling directly: + + >>> from sympy import Poly + >>> from sympy.abc import x + >>> from sympy.polys.numberfields.basis import round_two + >>> T = Poly(x ** 3 + x ** 2 - 2 * x + 8) + >>> print(round_two(T)) + (Submodule[[2, 0, 0], [0, 2, 0], [0, 1, 1]]/2, -503) + + The nilradicals mod $p$ that are sometimes computed during the Round Two + algorithm may be useful in further calculations. Pass a dictionary under + `radicals` to receive these: + + >>> T = Poly(x**3 + 3*x**2 + 5) + >>> rad = {} + >>> ZK, dK = round_two(T, radicals=rad) + >>> print(rad) + {3: Submodule[[-1, 1, 0], [-1, 0, 1]]} + + Parameters + ========== + + T : :py:class:`~.Poly`, :py:class:`~.AlgebraicField` + Either (1) the irreducible polynomial over :ref:`ZZ` or :ref:`QQ` + defining the number field, or (2) an :py:class:`~.AlgebraicField` + representing the number field itself. + + radicals : dict, optional + This is a way for any $p$-radicals (if computed) to be returned by + reference. If desired, pass an empty dictionary. If the algorithm + reaches the point where it computes the nilradical mod $p$ of the ring + of integers $Z_K$, then an $\mathbb{F}_p$-basis for this ideal will be + stored in this dictionary under the key ``p``. This can be useful for + other algorithms, such as prime decomposition. + + Returns + ======= + + Pair ``(ZK, dK)``, where: + + ``ZK`` is a :py:class:`~sympy.polys.numberfields.modules.Submodule` + representing the maximal order. + + ``dK`` is the discriminant of the field $K = \mathbb{Q}[x]/(T(x))$. + + See Also + ======== + + .AlgebraicField.maximal_order + .AlgebraicField.integral_basis + .AlgebraicField.discriminant + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + + """ + K = None + if isinstance(T, AlgebraicField): + K, T = T, T.ext.minpoly_of_element() + if ( not T.is_univariate + or not T.is_irreducible + or T.domain not in [ZZ, QQ]): + raise ValueError('Round 2 requires an irreducible univariate polynomial over ZZ or QQ.') + T, _ = T.make_monic_over_integers_by_scaling_roots() + n = T.degree() + D = T.discriminant() + D_modulus = ZZ.from_sympy(abs(D)) + # D must be 0 or 1 mod 4 (see Cohen Sec 4.4), which ensures we can write + # it in the form D = D_0 * F**2, where D_0 is 1 or a fundamental discriminant. + _, F = extract_fundamental_discriminant(D) + Ztheta = PowerBasis(K or T) + H = Ztheta.whole_submodule() + nilrad = None + while F: + # Next prime: + p, e = F.popitem() + U_bar, m = _apply_Dedekind_criterion(T, p) + if m == 0: + continue + # For a given prime p, the first enlargement of the order spanned by + # the current basis can be done in a simple way: + U = Ztheta.element_from_poly(Poly(U_bar, domain=ZZ)) + # TODO: + # Theory says only first m columns of the U//p*H term below are needed. + # Could be slightly more efficient to use only those. Maybe `Submodule` + # class should support a slice operator? + H = H.add(U // p * H, hnf_modulus=D_modulus) + if e <= m: + continue + # A second, and possibly more, enlargements for p will be needed. + # These enlargements require a more involved procedure. + q = p + while q < n: + q *= p + H1, nilrad = _second_enlargement(H, p, q) + while H1 != H: + H = H1 + H1, nilrad = _second_enlargement(H, p, q) + # Note: We do not store all nilradicals mod p, only the very last. This is + # because, unless computed against the entire integral basis, it might not + # be accurate. (In other words, if H was not already equal to ZK when we + # passed it to `_second_enlargement`, then we can't trust the nilradical + # so computed.) Example: if T(x) = x ** 3 + 15 * x ** 2 - 9 * x + 13, then + # F is divisible by 2, 3, and 7, and the nilradical mod 2 as computed above + # will not be accurate for the full, maximal order ZK. + if nilrad is not None and isinstance(radicals, dict): + radicals[p] = nilrad + ZK = H + # Pre-set expensive boolean properties which we already know to be true: + ZK._starts_with_unity = True + ZK._is_sq_maxrank_HNF = True + dK = (D * ZK.matrix.det() ** 2) // ZK.denom ** (2 * n) + return ZK, dK diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/exceptions.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..6e0d1ddc23c39295626fa036cf34974f50e4f53a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/exceptions.py @@ -0,0 +1,54 @@ +"""Special exception classes for numberfields. """ + + +class ClosureFailure(Exception): + r""" + Signals that a :py:class:`ModuleElement` which we tried to represent in a + certain :py:class:`Module` cannot in fact be represented there. + + Examples + ======== + + >>> from sympy.polys import Poly, cyclotomic_poly, ZZ + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.polys.numberfields.modules import PowerBasis, to_col + >>> T = Poly(cyclotomic_poly(5)) + >>> A = PowerBasis(T) + >>> B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + + Because we are in a cyclotomic field, the power basis ``A`` is an integral + basis, and the submodule ``B`` is just the ideal $(2)$. Therefore ``B`` can + represent an element having all even coefficients over the power basis: + + >>> a1 = A(to_col([2, 4, 6, 8])) + >>> print(B.represent(a1)) + DomainMatrix([[1], [2], [3], [4]], (4, 1), ZZ) + + but ``B`` cannot represent an element with an odd coefficient: + + >>> a2 = A(to_col([1, 2, 2, 2])) + >>> B.represent(a2) + Traceback (most recent call last): + ... + ClosureFailure: Element in QQ-span but not ZZ-span of this basis. + + """ + pass + + +class StructureError(Exception): + r""" + Represents cases in which an algebraic structure was expected to have a + certain property, or be of a certain type, but was not. + """ + pass + + +class MissingUnityError(StructureError): + r"""Structure should contain a unity element but does not.""" + pass + + +__all__ = [ + 'ClosureFailure', 'StructureError', 'MissingUnityError', +] diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/galois_resolvents.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/galois_resolvents.py new file mode 100644 index 0000000000000000000000000000000000000000..5d73b56870a498f09102787da3517e7520edb3db --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/galois_resolvents.py @@ -0,0 +1,676 @@ +r""" +Galois resolvents + +Each of the functions in ``sympy.polys.numberfields.galoisgroups`` that +computes Galois groups for a particular degree $n$ uses resolvents. Given the +polynomial $T$ whose Galois group is to be computed, a resolvent is a +polynomial $R$ whose roots are defined as functions of the roots of $T$. + +One way to compute the coefficients of $R$ is by approximating the roots of $T$ +to sufficient precision. This module defines a :py:class:`~.Resolvent` class +that handles this job, determining the necessary precision, and computing $R$. + +In some cases, the coefficients of $R$ are symmetric in the roots of $T$, +meaning they are equal to fixed functions of the coefficients of $T$. Therefore +another approach is to compute these functions once and for all, and record +them in a lookup table. This module defines code that can compute such tables. +The tables for polynomials $T$ of degrees 4 through 6, produced by this code, +are recorded in the resolvent_lookup.py module. + +""" + +from sympy.core.evalf import ( + evalf, fastlog, _evalf_with_bounded_error, quad_to_mpmath, +) +from sympy.core.symbol import symbols, Dummy +from sympy.polys.densetools import dup_eval +from sympy.polys.domains import ZZ +from sympy.polys.orderings import lex +from sympy.polys.polyroots import preprocess_roots +from sympy.polys.polytools import Poly +from sympy.polys.rings import xring +from sympy.polys.specialpolys import symmetric_poly +from sympy.utilities.lambdify import lambdify + +from mpmath import MPContext +from mpmath.libmp.libmpf import prec_to_dps + + +class GaloisGroupException(Exception): + ... + + +class ResolventException(GaloisGroupException): + ... + + +class Resolvent: + r""" + If $G$ is a subgroup of the symmetric group $S_n$, + $F$ a multivariate polynomial in $\mathbb{Z}[X_1, \ldots, X_n]$, + $H$ the stabilizer of $F$ in $G$ (i.e. the permutations $\sigma$ such that + $F(X_{\sigma(1)}, \ldots, X_{\sigma(n)}) = F(X_1, \ldots, X_n)$), and $s$ + a set of left coset representatives of $H$ in $G$, then the resolvent + polynomial $R(Y)$ is the product over $\sigma \in s$ of + $Y - F(X_{\sigma(1)}, \ldots, X_{\sigma(n)})$. + + For example, consider the resolvent for the form + $$F = X_0 X_2 + X_1 X_3$$ + and the group $G = S_4$. In this case, the stabilizer $H$ is the dihedral + group $D4 = < (0123), (02) >$, and a set of representatives of $G/H$ is + $\{I, (01), (03)\}$. The resolvent can be constructed as follows: + + >>> from sympy.combinatorics.permutations import Permutation + >>> from sympy.core.symbol import symbols + >>> from sympy.polys.numberfields.galoisgroups import Resolvent + >>> X = symbols('X0 X1 X2 X3') + >>> F = X[0]*X[2] + X[1]*X[3] + >>> s = [Permutation([0, 1, 2, 3]), Permutation([1, 0, 2, 3]), + ... Permutation([3, 1, 2, 0])] + >>> R = Resolvent(F, X, s) + + This resolvent has three roots, which are the conjugates of ``F`` under the + three permutations in ``s``: + + >>> R.root_lambdas[0](*X) + X0*X2 + X1*X3 + >>> R.root_lambdas[1](*X) + X0*X3 + X1*X2 + >>> R.root_lambdas[2](*X) + X0*X1 + X2*X3 + + Resolvents are useful for computing Galois groups. Given a polynomial $T$ + of degree $n$, we will use a resolvent $R$ where $Gal(T) \leq G \leq S_n$. + We will then want to substitute the roots of $T$ for the variables $X_i$ + in $R$, and study things like the discriminant of $R$, and the way $R$ + factors over $\mathbb{Q}$. + + From the symmetry in $R$'s construction, and since $Gal(T) \leq G$, we know + from Galois theory that the coefficients of $R$ must lie in $\mathbb{Z}$. + This allows us to compute the coefficients of $R$ by approximating the + roots of $T$ to sufficient precision, plugging these values in for the + variables $X_i$ in the coefficient expressions of $R$, and then simply + rounding to the nearest integer. + + In order to determine a sufficient precision for the roots of $T$, this + ``Resolvent`` class imposes certain requirements on the form ``F``. It + could be possible to design a different ``Resolvent`` class, that made + different precision estimates, and different assumptions about ``F``. + + ``F`` must be homogeneous, and all terms must have unit coefficient. + Furthermore, if $r$ is the number of terms in ``F``, and $t$ the total + degree, and if $m$ is the number of conjugates of ``F``, i.e. the number + of permutations in ``s``, then we require that $m < r 2^t$. Again, it is + not impossible to work with forms ``F`` that violate these assumptions, but + this ``Resolvent`` class requires them. + + Since determining the integer coefficients of the resolvent for a given + polynomial $T$ is one of the main problems this class solves, we take some + time to explain the precision bounds it uses. + + The general problem is: + Given a multivariate polynomial $P \in \mathbb{Z}[X_1, \ldots, X_n]$, and a + bound $M \in \mathbb{R}_+$, compute an $\varepsilon > 0$ such that for any + complex numbers $a_1, \ldots, a_n$ with $|a_i| < M$, if the $a_i$ are + approximated to within an accuracy of $\varepsilon$ by $b_i$, that is, + $|a_i - b_i| < \varepsilon$ for $i = 1, \ldots, n$, then + $|P(a_1, \ldots, a_n) - P(b_1, \ldots, b_n)| < 1/2$. In other words, if it + is known that $P(a_1, \ldots, a_n) = c$ for some $c \in \mathbb{Z}$, then + $P(b_1, \ldots, b_n)$ can be rounded to the nearest integer in order to + determine $c$. + + To derive our error bound, consider the monomial $xyz$. Defining + $d_i = b_i - a_i$, our error is + $|(a_1 + d_1)(a_2 + d_2)(a_3 + d_3) - a_1 a_2 a_3|$, which is bounded + above by $|(M + \varepsilon)^3 - M^3|$. Passing to a general monomial of + total degree $t$, this expression is bounded by + $M^{t-1}\varepsilon(t + 2^t\varepsilon/M)$ provided $\varepsilon < M$, + and by $(t+1)M^{t-1}\varepsilon$ provided $\varepsilon < M/2^t$. + But since our goal is to make the error less than $1/2$, we will choose + $\varepsilon < 1/(2(t+1)M^{t-1})$, which implies the condition that + $\varepsilon < M/2^t$, as long as $M \geq 2$. + + Passing from the general monomial to the general polynomial is easy, by + scaling and summing error bounds. + + In our specific case, we are given a homogeneous polynomial $F$ of + $r$ terms and total degree $t$, all of whose coefficients are $\pm 1$. We + are given the $m$ permutations that make the conjugates of $F$, and + we want to bound the error in the coefficients of the monic polynomial + $R(Y)$ having $F$ and its conjugates as roots (i.e. the resolvent). + + For $j$ from $1$ to $m$, the coefficient of $Y^{m-j}$ in $R(Y)$ is the + $j$th elementary symmetric polynomial in the conjugates of $F$. This sums + the products of these conjugates, taken $j$ at a time, in all possible + combinations. There are $\binom{m}{j}$ such combinations, and each product + of $j$ conjugates of $F$ expands to a sum of $r^j$ terms, each of unit + coefficient, and total degree $jt$. An error bound for the $j$th coeff of + $R$ is therefore + $$\binom{m}{j} r^j (jt + 1) M^{jt - 1} \varepsilon$$ + When our goal is to evaluate all the coefficients of $R$, we will want to + use the maximum of these error bounds. It is clear that this bound is + strictly increasing for $j$ up to the ceiling of $m/2$. After that point, + the first factor $\binom{m}{j}$ begins to decrease, while the others + continue to increase. However, the binomial coefficient never falls by more + than a factor of $1/m$ at a time, so our assumptions that $M \geq 2$ and + $m < r 2^t$ are enough to tell us that the constant coefficient of $R$, + i.e. that where $j = m$, has the largest error bound. Therefore we can use + $$r^m (mt + 1) M^{mt - 1} \varepsilon$$ + as our error bound for all the coefficients. + + Note that this bound is also (more than) adequate to determine whether any + of the roots of $R$ is an integer. Each of these roots is a single + conjugate of $F$, which contains less error than the trace, i.e. the + coefficient of $Y^{m - 1}$. By rounding the roots of $R$ to the nearest + integers, we therefore get all the candidates for integer roots of $R$. By + plugging these candidates into $R$, we can check whether any of them + actually is a root. + + Note: We take the definition of resolvent from Cohen, but the error bound + is ours. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*. + (Def 6.3.2) + + """ + + def __init__(self, F, X, s): + r""" + Parameters + ========== + + F : :py:class:`~.Expr` + polynomial in the symbols in *X* + X : list of :py:class:`~.Symbol` + s : list of :py:class:`~.Permutation` + representing the cosets of the stabilizer of *F* in + some subgroup $G$ of $S_n$, where $n$ is the length of *X*. + """ + self.F = F + self.X = X + self.s = s + + # Number of conjugates: + self.m = len(s) + # Total degree of F (computed below): + self.t = None + # Number of terms in F (computed below): + self.r = 0 + + for monom, coeff in Poly(F).terms(): + if abs(coeff) != 1: + raise ResolventException('Resolvent class expects forms with unit coeffs') + t = sum(monom) + if t != self.t and self.t is not None: + raise ResolventException('Resolvent class expects homogeneous forms') + self.t = t + self.r += 1 + + m, t, r = self.m, self.t, self.r + if not m < r * 2**t: + raise ResolventException('Resolvent class expects m < r*2^t') + M = symbols('M') + # Precision sufficient for computing the coeffs of the resolvent: + self.coeff_prec_func = Poly(r**m*(m*t + 1)*M**(m*t - 1)) + # Precision sufficient for checking whether any of the roots of the + # resolvent are integers: + self.root_prec_func = Poly(r*(t + 1)*M**(t - 1)) + + # The conjugates of F are the roots of the resolvent. + # For evaluating these to required numerical precisions, we need + # lambdified versions. + # Note: for a given permutation sigma, the conjugate (sigma F) is + # equivalent to lambda [sigma^(-1) X]: F. + self.root_lambdas = [ + lambdify((~s[j])(X), F) + for j in range(self.m) + ] + + # For evaluating the coeffs, we'll also need lambdified versions of + # the elementary symmetric functions for degree m. + Y = symbols('Y') + R = symbols(' '.join(f'R{i}' for i in range(m))) + f = 1 + for r in R: + f *= (Y - r) + C = Poly(f, Y).coeffs() + self.esf_lambdas = [lambdify(R, c) for c in C] + + def get_prec(self, M, target='coeffs'): + r""" + For a given upper bound *M* on the magnitude of the complex numbers to + be plugged in for this resolvent's symbols, compute a sufficient + precision for evaluating those complex numbers, such that the + coefficients, or the integer roots, of the resolvent can be determined. + + Parameters + ========== + + M : real number + Upper bound on magnitude of the complex numbers to be plugged in. + + target : str, 'coeffs' or 'roots', default='coeffs' + Name the task for which a sufficient precision is desired. + This is either determining the coefficients of the resolvent + ('coeffs') or determining its possible integer roots ('roots'). + The latter may require significantly lower precision. + + Returns + ======= + + int $m$ + such that $2^{-m}$ is a sufficient upper bound on the + error in approximating the complex numbers to be plugged in. + + """ + # As explained in the docstring for this class, our precision estimates + # require that M be at least 2. + M = max(M, 2) + f = self.coeff_prec_func if target == 'coeffs' else self.root_prec_func + r, _, _, _ = evalf(2*f(M), 1, {}) + return fastlog(r) + 1 + + def approximate_roots_of_poly(self, T, target='coeffs'): + """ + Approximate the roots of a given polynomial *T* to sufficient precision + in order to evaluate this resolvent's coefficients, or determine + whether the resolvent has an integer root. + + Parameters + ========== + + T : :py:class:`~.Poly` + + target : str, 'coeffs' or 'roots', default='coeffs' + Set the approximation precision to be sufficient for the desired + task, which is either determining the coefficients of the resolvent + ('coeffs') or determining its possible integer roots ('roots'). + The latter may require significantly lower precision. + + Returns + ======= + + list of elements of :ref:`CC` + + """ + ctx = MPContext() + # Because sympy.polys.polyroots._integer_basis() is called when a CRootOf + # is formed, we proactively extract the integer basis now. This means that + # when we call T.all_roots(), every root will be a CRootOf, not a Mul + # of Integer*CRootOf. + coeff, T = preprocess_roots(T) + coeff = ctx.mpf(str(coeff)) + + scaled_roots = T.all_roots(radicals=False) + + # Since we're going to be approximating the roots of T anyway, we can + # get a good upper bound on the magnitude of the roots by starting with + # a very low precision approx. + approx0 = [coeff * quad_to_mpmath(_evalf_with_bounded_error(r, m=0)) for r in scaled_roots] + # Here we add 1 to account for the possible error in our initial approximation. + M = max(abs(b) for b in approx0) + 1 + m = self.get_prec(M, target=target) + n = fastlog(M._mpf_) + 1 + p = m + n + 1 + ctx.prec = p + d = prec_to_dps(p) + + approx1 = [r.eval_approx(d, return_mpmath=True) for r in scaled_roots] + approx1 = [coeff*ctx.mpc(r) for r in approx1] + + return approx1 + + @staticmethod + def round_mpf(a): + if isinstance(a, int): + return a + # If we use python's built-in `round()`, we lose precision. + # If we use `ZZ` directly, we may add or subtract 1. + # + # XXX: We have to convert to int before converting to ZZ because + # flint.fmpz cannot convert a mpmath mpf. + return ZZ(int(a.context.nint(a))) + + def round_roots_to_integers_for_poly(self, T): + """ + For a given polynomial *T*, round the roots of this resolvent to the + nearest integers. + + Explanation + =========== + + None of the integers returned by this method is guaranteed to be a + root of the resolvent; however, if the resolvent has any integer roots + (for the given polynomial *T*), then they must be among these. + + If the coefficients of the resolvent are also desired, then this method + should not be used. Instead, use the ``eval_for_poly`` method. This + method may be significantly faster than ``eval_for_poly``. + + Parameters + ========== + + T : :py:class:`~.Poly` + + Returns + ======= + + dict + Keys are the indices of those permutations in ``self.s`` such that + the corresponding root did round to a rational integer. + + Values are :ref:`ZZ`. + + + """ + approx_roots_of_T = self.approximate_roots_of_poly(T, target='roots') + approx_roots_of_self = [r(*approx_roots_of_T) for r in self.root_lambdas] + return { + i: self.round_mpf(r.real) + for i, r in enumerate(approx_roots_of_self) + if self.round_mpf(r.imag) == 0 + } + + def eval_for_poly(self, T, find_integer_root=False): + r""" + Compute the integer values of the coefficients of this resolvent, when + plugging in the roots of a given polynomial. + + Parameters + ========== + + T : :py:class:`~.Poly` + + find_integer_root : ``bool``, default ``False`` + If ``True``, then also determine whether the resolvent has an + integer root, and return the first one found, along with its + index, i.e. the index of the permutation ``self.s[i]`` it + corresponds to. + + Returns + ======= + + Tuple ``(R, a, i)`` + + ``R`` is this resolvent as a dense univariate polynomial over + :ref:`ZZ`, i.e. a list of :ref:`ZZ`. + + If *find_integer_root* was ``True``, then ``a`` and ``i`` are the + first integer root found, and its index, if one exists. + Otherwise ``a`` and ``i`` are both ``None``. + + """ + approx_roots_of_T = self.approximate_roots_of_poly(T, target='coeffs') + approx_roots_of_self = [r(*approx_roots_of_T) for r in self.root_lambdas] + approx_coeffs_of_self = [c(*approx_roots_of_self) for c in self.esf_lambdas] + + R = [] + for c in approx_coeffs_of_self: + if self.round_mpf(c.imag) != 0: + # If precision was enough, this should never happen. + raise ResolventException(f"Got non-integer coeff for resolvent: {c}") + R.append(self.round_mpf(c.real)) + + a0, i0 = None, None + + if find_integer_root: + for i, r in enumerate(approx_roots_of_self): + if self.round_mpf(r.imag) != 0: + continue + if not dup_eval(R, (a := self.round_mpf(r.real)), ZZ): + a0, i0 = a, i + break + + return R, a0, i0 + + +def wrap(text, width=80): + """Line wrap a polynomial expression. """ + out = '' + col = 0 + for c in text: + if c == ' ' and col > width: + c, col = '\n', 0 + else: + col += 1 + out += c + return out + + +def s_vars(n): + """Form the symbols s1, s2, ..., sn to stand for elem. symm. polys. """ + return symbols([f's{i + 1}' for i in range(n)]) + + +def sparse_symmetrize_resolvent_coeffs(F, X, s, verbose=False): + """ + Compute the coefficients of a resolvent as functions of the coefficients of + the associated polynomial. + + F must be a sparse polynomial. + """ + import time, sys + # Roots of resolvent as multivariate forms over vars X: + root_forms = [ + F.compose(list(zip(X, sigma(X)))) + for sigma in s + ] + + # Coeffs of resolvent (besides lead coeff of 1) as symmetric forms over vars X: + Y = [Dummy(f'Y{i}') for i in range(len(s))] + coeff_forms = [] + for i in range(1, len(s) + 1): + if verbose: + print('----') + print(f'Computing symmetric poly of degree {i}...') + sys.stdout.flush() + t0 = time.time() + G = symmetric_poly(i, *Y) + t1 = time.time() + if verbose: + print(f'took {t1 - t0} seconds') + print('lambdifying...') + sys.stdout.flush() + t0 = time.time() + C = lambdify(Y, (-1)**i*G) + t1 = time.time() + if verbose: + print(f'took {t1 - t0} seconds') + sys.stdout.flush() + coeff_forms.append(C) + + coeffs = [] + for i, f in enumerate(coeff_forms): + if verbose: + print('----') + print(f'Plugging root forms into elem symm poly {i+1}...') + sys.stdout.flush() + t0 = time.time() + g = f(*root_forms) + t1 = time.time() + coeffs.append(g) + if verbose: + print(f'took {t1 - t0} seconds') + sys.stdout.flush() + + # Now symmetrize these coeffs. This means recasting them as polynomials in + # the elementary symmetric polys over X. + symmetrized = [] + symmetrization_times = [] + ss = s_vars(len(X)) + for i, A in list(enumerate(coeffs)): + if verbose: + print('-----') + print(f'Coeff {i+1}...') + sys.stdout.flush() + t0 = time.time() + B, rem, _ = A.symmetrize() + t1 = time.time() + if rem != 0: + msg = f"Got nonzero remainder {rem} for resolvent (F, X, s) = ({F}, {X}, {s})" + raise ResolventException(msg) + B_str = str(B.as_expr(*ss)) + symmetrized.append(B_str) + symmetrization_times.append(t1 - t0) + if verbose: + print(wrap(B_str)) + print(f'took {t1 - t0} seconds') + sys.stdout.flush() + + return symmetrized, symmetrization_times + + +def define_resolvents(): + """Define all the resolvents for polys T of degree 4 through 6. """ + from sympy.combinatorics.galois import PGL2F5 + from sympy.combinatorics.permutations import Permutation + + R4, X4 = xring("X0,X1,X2,X3", ZZ, lex) + X = X4 + + # The one resolvent used in `_galois_group_degree_4_lookup()`: + F40 = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[0]**2 + s40 = [ + Permutation(3), + Permutation(3)(0, 1), + Permutation(3)(0, 2), + Permutation(3)(0, 3), + Permutation(3)(1, 2), + Permutation(3)(2, 3), + ] + + # First resolvent used in `_galois_group_degree_4_root_approx()`: + F41 = X[0]*X[2] + X[1]*X[3] + s41 = [ + Permutation(3), + Permutation(3)(0, 1), + Permutation(3)(0, 3) + ] + + R5, X5 = xring("X0,X1,X2,X3,X4", ZZ, lex) + X = X5 + + # First resolvent used in `_galois_group_degree_5_hybrid()`, + # and only one used in `_galois_group_degree_5_lookup_ext_factor()`: + F51 = ( X[0]**2*(X[1]*X[4] + X[2]*X[3]) + + X[1]**2*(X[2]*X[0] + X[3]*X[4]) + + X[2]**2*(X[3]*X[1] + X[4]*X[0]) + + X[3]**2*(X[4]*X[2] + X[0]*X[1]) + + X[4]**2*(X[0]*X[3] + X[1]*X[2])) + s51 = [ + Permutation(4), + Permutation(4)(0, 1), + Permutation(4)(0, 2), + Permutation(4)(0, 3), + Permutation(4)(0, 4), + Permutation(4)(1, 4) + ] + + R6, X6 = xring("X0,X1,X2,X3,X4,X5", ZZ, lex) + X = X6 + + # First resolvent used in `_galois_group_degree_6_lookup()`: + H = PGL2F5() + term0 = X[0]**2*X[5]**2*(X[1]*X[4] + X[2]*X[3]) + terms = {term0.compose(list(zip(X, s(X)))) for s in H.elements} + F61 = sum(terms) + s61 = [Permutation(5)] + [Permutation(5)(0, n) for n in range(1, 6)] + + # Second resolvent used in `_galois_group_degree_6_lookup()`: + F62 = X[0]*X[1]*X[2] + X[3]*X[4]*X[5] + s62 = [Permutation(5)] + [ + Permutation(5)(i, j + 3) for i in range(3) for j in range(3) + ] + + return { + (4, 0): (F40, X4, s40), + (4, 1): (F41, X4, s41), + (5, 1): (F51, X5, s51), + (6, 1): (F61, X6, s61), + (6, 2): (F62, X6, s62), + } + + +def generate_lambda_lookup(verbose=False, trial_run=False): + """ + Generate the whole lookup table of coeff lambdas, for all resolvents. + """ + jobs = define_resolvents() + lambda_lists = {} + total_time = 0 + time_for_61 = 0 + time_for_61_last = 0 + for k, (F, X, s) in jobs.items(): + symmetrized, times = sparse_symmetrize_resolvent_coeffs(F, X, s, verbose=verbose) + + total_time += sum(times) + if k == (6, 1): + time_for_61 = sum(times) + time_for_61_last = times[-1] + + sv = s_vars(len(X)) + head = f'lambda {", ".join(str(v) for v in sv)}:' + lambda_lists[k] = ',\n '.join([ + f'{head} ({wrap(f)})' + for f in symmetrized + ]) + + if trial_run: + break + + table = ( + "# This table was generated by a call to\n" + "# `sympy.polys.numberfields.galois_resolvents.generate_lambda_lookup()`.\n" + f"# The entire job took {total_time:.2f}s.\n" + f"# Of this, Case (6, 1) took {time_for_61:.2f}s.\n" + f"# The final polynomial of Case (6, 1) alone took {time_for_61_last:.2f}s.\n" + "resolvent_coeff_lambdas = {\n") + + for k, L in lambda_lists.items(): + table += f" {k}: [\n" + table += " " + L + '\n' + table += " ],\n" + table += "}\n" + return table + + +def get_resolvent_by_lookup(T, number): + """ + Use the lookup table, to return a resolvent (as dup) for a given + polynomial *T*. + + Parameters + ========== + + T : Poly + The polynomial whose resolvent is needed + + number : int + For some degrees, there are multiple resolvents. + Use this to indicate which one you want. + + Returns + ======= + + dup + + """ + from sympy.polys.numberfields.resolvent_lookup import resolvent_coeff_lambdas + degree = T.degree() + L = resolvent_coeff_lambdas[(degree, number)] + T_coeffs = T.rep.to_list()[1:] + return [ZZ(1)] + [c(*T_coeffs) for c in L] + + +# Use +# (.venv) $ python -m sympy.polys.numberfields.galois_resolvents +# to reproduce the table found in resolvent_lookup.py +if __name__ == "__main__": + import sys + verbose = '-v' in sys.argv[1:] + trial_run = '-t' in sys.argv[1:] + table = generate_lambda_lookup(verbose=verbose, trial_run=trial_run) + print(table) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/galoisgroups.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/galoisgroups.py new file mode 100644 index 0000000000000000000000000000000000000000..a0e424bf7554c0cedd926902e7322b9640735a8b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/galoisgroups.py @@ -0,0 +1,623 @@ +""" +Compute Galois groups of polynomials. + +We use algorithms from [1], with some modifications to use lookup tables for +resolvents. + +References +========== + +.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*. + +""" + +from collections import defaultdict +import random + +from sympy.core.symbol import Dummy, symbols +from sympy.ntheory.primetest import is_square +from sympy.polys.domains import ZZ +from sympy.polys.densebasic import dup_random +from sympy.polys.densetools import dup_eval +from sympy.polys.euclidtools import dup_discriminant +from sympy.polys.factortools import dup_factor_list, dup_irreducible_p +from sympy.polys.numberfields.galois_resolvents import ( + GaloisGroupException, get_resolvent_by_lookup, define_resolvents, + Resolvent, +) +from sympy.polys.numberfields.utilities import coeff_search +from sympy.polys.polytools import (Poly, poly_from_expr, + PolificationFailed, ComputationFailed) +from sympy.polys.sqfreetools import dup_sqf_p +from sympy.utilities import public + + +class MaxTriesException(GaloisGroupException): + ... + + +def tschirnhausen_transformation(T, max_coeff=10, max_tries=30, history=None, + fixed_order=True): + r""" + Given a univariate, monic, irreducible polynomial over the integers, find + another such polynomial defining the same number field. + + Explanation + =========== + + See Alg 6.3.4 of [1]. + + Parameters + ========== + + T : Poly + The given polynomial + max_coeff : int + When choosing a transformation as part of the process, + keep the coeffs between plus and minus this. + max_tries : int + Consider at most this many transformations. + history : set, None, optional (default=None) + Pass a set of ``Poly.rep``'s in order to prevent any of these + polynomials from being returned as the polynomial ``U`` i.e. the + transformation of the given polynomial *T*. The given poly *T* will + automatically be added to this set, before we try to find a new one. + fixed_order : bool, default True + If ``True``, work through candidate transformations A(x) in a fixed + order, from small coeffs to large, resulting in deterministic behavior. + If ``False``, the A(x) are chosen randomly, while still working our way + up from small coefficients to larger ones. + + Returns + ======= + + Pair ``(A, U)`` + + ``A`` and ``U`` are ``Poly``, ``A`` is the + transformation, and ``U`` is the transformed polynomial that defines + the same number field as *T*. The polynomial ``A`` maps the roots of + *T* to the roots of ``U``. + + Raises + ====== + + MaxTriesException + if could not find a polynomial before exceeding *max_tries*. + + """ + X = Dummy('X') + n = T.degree() + if history is None: + history = set() + history.add(T.rep) + + if fixed_order: + coeff_generators = {} + deg_coeff_sum = 3 + current_degree = 2 + + def get_coeff_generator(degree): + gen = coeff_generators.get(degree, coeff_search(degree, 1)) + coeff_generators[degree] = gen + return gen + + for i in range(max_tries): + + # We never use linear A(x), since applying a fixed linear transformation + # to all roots will only multiply the discriminant of T by a square + # integer. This will change nothing important. In particular, if disc(T) + # was zero before, it will still be zero now, and typically we apply + # the transformation in hopes of replacing T by a squarefree poly. + + if fixed_order: + # If d is degree and c max coeff, we move through the dc-space + # along lines of constant sum. First d + c = 3 with (d, c) = (2, 1). + # Then d + c = 4 with (d, c) = (3, 1), (2, 2). Then d + c = 5 with + # (d, c) = (4, 1), (3, 2), (2, 3), and so forth. For a given (d, c) + # we go though all sets of coeffs where max = c, before moving on. + gen = get_coeff_generator(current_degree) + coeffs = next(gen) + m = max(abs(c) for c in coeffs) + if current_degree + m > deg_coeff_sum: + if current_degree == 2: + deg_coeff_sum += 1 + current_degree = deg_coeff_sum - 1 + else: + current_degree -= 1 + gen = get_coeff_generator(current_degree) + coeffs = next(gen) + a = [ZZ(1)] + [ZZ(c) for c in coeffs] + + else: + # We use a progressive coeff bound, up to the max specified, since it + # is preferable to succeed with smaller coeffs. + # Give each coeff bound five tries, before incrementing. + C = min(i//5 + 1, max_coeff) + d = random.randint(2, n - 1) + a = dup_random(d, -C, C, ZZ) + + A = Poly(a, T.gen) + U = Poly(T.resultant(X - A), X) + if U.rep not in history and dup_sqf_p(U.rep.to_list(), ZZ): + return A, U + raise MaxTriesException + + +def has_square_disc(T): + """Convenience to check if a Poly or dup has square discriminant. """ + d = T.discriminant() if isinstance(T, Poly) else dup_discriminant(T, ZZ) + return is_square(d) + + +def _galois_group_degree_3(T, max_tries=30, randomize=False): + r""" + Compute the Galois group of a polynomial of degree 3. + + Explanation + =========== + + Uses Prop 6.3.5 of [1]. + + """ + from sympy.combinatorics.galois import S3TransitiveSubgroups + return ((S3TransitiveSubgroups.A3, True) if has_square_disc(T) + else (S3TransitiveSubgroups.S3, False)) + + +def _galois_group_degree_4_root_approx(T, max_tries=30, randomize=False): + r""" + Compute the Galois group of a polynomial of degree 4. + + Explanation + =========== + + Follows Alg 6.3.7 of [1], using a pure root approximation approach. + + """ + from sympy.combinatorics.permutations import Permutation + from sympy.combinatorics.galois import S4TransitiveSubgroups + + X = symbols('X0 X1 X2 X3') + # We start by considering the resolvent for the form + # F = X0*X2 + X1*X3 + # and the group G = S4. In this case, the stabilizer H is D4 = < (0123), (02) >, + # and a set of representatives of G/H is {I, (01), (03)} + F1 = X[0]*X[2] + X[1]*X[3] + s1 = [ + Permutation(3), + Permutation(3)(0, 1), + Permutation(3)(0, 3) + ] + R1 = Resolvent(F1, X, s1) + + # In the second half of the algorithm (if we reach it), we use another + # form and set of coset representatives. However, we may need to permute + # them first, so cannot form their resolvent now. + F2_pre = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[0]**2 + s2_pre = [ + Permutation(3), + Permutation(3)(0, 2) + ] + + history = set() + for i in range(max_tries): + if i > 0: + # If we're retrying, need a new polynomial T. + _, T = tschirnhausen_transformation(T, max_tries=max_tries, + history=history, + fixed_order=not randomize) + + R_dup, _, i0 = R1.eval_for_poly(T, find_integer_root=True) + # If R is not squarefree, must retry. + if not dup_sqf_p(R_dup, ZZ): + continue + + # By Prop 6.3.1 of [1], Gal(T) is contained in A4 iff disc(T) is square. + sq_disc = has_square_disc(T) + + if i0 is None: + # By Thm 6.3.3 of [1], Gal(T) is not conjugate to any subgroup of the + # stabilizer H = D4 that we chose. This means Gal(T) is either A4 or S4. + return ((S4TransitiveSubgroups.A4, True) if sq_disc + else (S4TransitiveSubgroups.S4, False)) + + # Gal(T) is conjugate to a subgroup of H = D4, so it is either V, C4 + # or D4 itself. + + if sq_disc: + # Neither C4 nor D4 is contained in A4, so Gal(T) must be V. + return (S4TransitiveSubgroups.V, True) + + # Gal(T) can only be D4 or C4. + # We will now use our second resolvent, with G being that conjugate of D4 that + # Gal(T) is contained in. To determine the right conjugate, we will need + # the permutation corresponding to the integer root we found. + sigma = s1[i0] + # Applying sigma means permuting the args of F, and + # conjugating the set of coset representatives. + F2 = F2_pre.subs(zip(X, sigma(X)), simultaneous=True) + s2 = [sigma*tau*sigma for tau in s2_pre] + R2 = Resolvent(F2, X, s2) + R_dup, _, _ = R2.eval_for_poly(T) + d = dup_discriminant(R_dup, ZZ) + # If d is zero (R has a repeated root), must retry. + if d == 0: + continue + if is_square(d): + return (S4TransitiveSubgroups.C4, False) + else: + return (S4TransitiveSubgroups.D4, False) + + raise MaxTriesException + + +def _galois_group_degree_4_lookup(T, max_tries=30, randomize=False): + r""" + Compute the Galois group of a polynomial of degree 4. + + Explanation + =========== + + Based on Alg 6.3.6 of [1], but uses resolvent coeff lookup. + + """ + from sympy.combinatorics.galois import S4TransitiveSubgroups + + history = set() + for i in range(max_tries): + R_dup = get_resolvent_by_lookup(T, 0) + if dup_sqf_p(R_dup, ZZ): + break + _, T = tschirnhausen_transformation(T, max_tries=max_tries, + history=history, + fixed_order=not randomize) + else: + raise MaxTriesException + + # Compute list L of degrees of irreducible factors of R, in increasing order: + fl = dup_factor_list(R_dup, ZZ) + L = sorted(sum([ + [len(r) - 1] * e for r, e in fl[1] + ], [])) + + if L == [6]: + return ((S4TransitiveSubgroups.A4, True) if has_square_disc(T) + else (S4TransitiveSubgroups.S4, False)) + + if L == [1, 1, 4]: + return (S4TransitiveSubgroups.C4, False) + + if L == [2, 2, 2]: + return (S4TransitiveSubgroups.V, True) + + assert L == [2, 4] + return (S4TransitiveSubgroups.D4, False) + + +def _galois_group_degree_5_hybrid(T, max_tries=30, randomize=False): + r""" + Compute the Galois group of a polynomial of degree 5. + + Explanation + =========== + + Based on Alg 6.3.9 of [1], but uses a hybrid approach, combining resolvent + coeff lookup, with root approximation. + + """ + from sympy.combinatorics.galois import S5TransitiveSubgroups + from sympy.combinatorics.permutations import Permutation + + X5 = symbols("X0,X1,X2,X3,X4") + res = define_resolvents() + F51, _, s51 = res[(5, 1)] + F51 = F51.as_expr(*X5) + R51 = Resolvent(F51, X5, s51) + + history = set() + reached_second_stage = False + for i in range(max_tries): + if i > 0: + _, T = tschirnhausen_transformation(T, max_tries=max_tries, + history=history, + fixed_order=not randomize) + R51_dup = get_resolvent_by_lookup(T, 1) + if not dup_sqf_p(R51_dup, ZZ): + continue + + # First stage + # If we have not yet reached the second stage, then the group still + # might be S5, A5, or M20, so must test for that. + if not reached_second_stage: + sq_disc = has_square_disc(T) + + if dup_irreducible_p(R51_dup, ZZ): + return ((S5TransitiveSubgroups.A5, True) if sq_disc + else (S5TransitiveSubgroups.S5, False)) + + if not sq_disc: + return (S5TransitiveSubgroups.M20, False) + + # Second stage + reached_second_stage = True + # R51 must have an integer root for T. + # To choose our second resolvent, we need to know which conjugate of + # F51 is a root. + rounded_roots = R51.round_roots_to_integers_for_poly(T) + # These are integers, and candidates to be roots of R51. + # We find the first one that actually is a root. + for permutation_index, candidate_root in rounded_roots.items(): + if not dup_eval(R51_dup, candidate_root, ZZ): + break + + X = X5 + F2_pre = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[4]**2 + X[4]*X[0]**2 + s2_pre = [ + Permutation(4), + Permutation(4)(0, 1)(2, 4) + ] + + i0 = permutation_index + sigma = s51[i0] + F2 = F2_pre.subs(zip(X, sigma(X)), simultaneous=True) + s2 = [sigma*tau*sigma for tau in s2_pre] + R2 = Resolvent(F2, X, s2) + R_dup, _, _ = R2.eval_for_poly(T) + d = dup_discriminant(R_dup, ZZ) + + if d == 0: + continue + if is_square(d): + return (S5TransitiveSubgroups.C5, True) + else: + return (S5TransitiveSubgroups.D5, True) + + raise MaxTriesException + + +def _galois_group_degree_5_lookup_ext_factor(T, max_tries=30, randomize=False): + r""" + Compute the Galois group of a polynomial of degree 5. + + Explanation + =========== + + Based on Alg 6.3.9 of [1], but uses resolvent coeff lookup, plus + factorization over an algebraic extension. + + """ + from sympy.combinatorics.galois import S5TransitiveSubgroups + + _T = T + + history = set() + for i in range(max_tries): + R_dup = get_resolvent_by_lookup(T, 1) + if dup_sqf_p(R_dup, ZZ): + break + _, T = tschirnhausen_transformation(T, max_tries=max_tries, + history=history, + fixed_order=not randomize) + else: + raise MaxTriesException + + sq_disc = has_square_disc(T) + + if dup_irreducible_p(R_dup, ZZ): + return ((S5TransitiveSubgroups.A5, True) if sq_disc + else (S5TransitiveSubgroups.S5, False)) + + if not sq_disc: + return (S5TransitiveSubgroups.M20, False) + + # If we get this far, Gal(T) can only be D5 or C5. + # But for Gal(T) to have order 5, T must already split completely in + # the extension field obtained by adjoining a single one of its roots. + fl = Poly(_T, domain=ZZ.alg_field_from_poly(_T)).factor_list()[1] + if len(fl) == 5: + return (S5TransitiveSubgroups.C5, True) + else: + return (S5TransitiveSubgroups.D5, True) + + +def _galois_group_degree_6_lookup(T, max_tries=30, randomize=False): + r""" + Compute the Galois group of a polynomial of degree 6. + + Explanation + =========== + + Based on Alg 6.3.10 of [1], but uses resolvent coeff lookup. + + """ + from sympy.combinatorics.galois import S6TransitiveSubgroups + + # First resolvent: + + history = set() + for i in range(max_tries): + R_dup = get_resolvent_by_lookup(T, 1) + if dup_sqf_p(R_dup, ZZ): + break + _, T = tschirnhausen_transformation(T, max_tries=max_tries, + history=history, + fixed_order=not randomize) + else: + raise MaxTriesException + + fl = dup_factor_list(R_dup, ZZ) + + # Group the factors by degree. + factors_by_deg = defaultdict(list) + for r, _ in fl[1]: + factors_by_deg[len(r) - 1].append(r) + + L = sorted(sum([ + [d] * len(ff) for d, ff in factors_by_deg.items() + ], [])) + + T_has_sq_disc = has_square_disc(T) + + if L == [1, 2, 3]: + f1 = factors_by_deg[3][0] + return ((S6TransitiveSubgroups.C6, False) if has_square_disc(f1) + else (S6TransitiveSubgroups.D6, False)) + + elif L == [3, 3]: + f1, f2 = factors_by_deg[3] + any_square = has_square_disc(f1) or has_square_disc(f2) + return ((S6TransitiveSubgroups.G18, False) if any_square + else (S6TransitiveSubgroups.G36m, False)) + + elif L == [2, 4]: + if T_has_sq_disc: + return (S6TransitiveSubgroups.S4p, True) + else: + f1 = factors_by_deg[4][0] + return ((S6TransitiveSubgroups.A4xC2, False) if has_square_disc(f1) + else (S6TransitiveSubgroups.S4xC2, False)) + + elif L == [1, 1, 4]: + return ((S6TransitiveSubgroups.A4, True) if T_has_sq_disc + else (S6TransitiveSubgroups.S4m, False)) + + elif L == [1, 5]: + return ((S6TransitiveSubgroups.PSL2F5, True) if T_has_sq_disc + else (S6TransitiveSubgroups.PGL2F5, False)) + + elif L == [1, 1, 1, 3]: + return (S6TransitiveSubgroups.S3, False) + + assert L == [6] + + # Second resolvent: + + history = set() + for i in range(max_tries): + R_dup = get_resolvent_by_lookup(T, 2) + if dup_sqf_p(R_dup, ZZ): + break + _, T = tschirnhausen_transformation(T, max_tries=max_tries, + history=history, + fixed_order=not randomize) + else: + raise MaxTriesException + + T_has_sq_disc = has_square_disc(T) + + if dup_irreducible_p(R_dup, ZZ): + return ((S6TransitiveSubgroups.A6, True) if T_has_sq_disc + else (S6TransitiveSubgroups.S6, False)) + else: + return ((S6TransitiveSubgroups.G36p, True) if T_has_sq_disc + else (S6TransitiveSubgroups.G72, False)) + + +@public +def galois_group(f, *gens, by_name=False, max_tries=30, randomize=False, **args): + r""" + Compute the Galois group for polynomials *f* up to degree 6. + + Examples + ======== + + >>> from sympy import galois_group + >>> from sympy.abc import x + >>> f = x**4 + 1 + >>> G, alt = galois_group(f) + >>> print(G) + PermutationGroup([ + (0 1)(2 3), + (0 2)(1 3)]) + + The group is returned along with a boolean, indicating whether it is + contained in the alternating group $A_n$, where $n$ is the degree of *T*. + Along with other group properties, this can help determine which group it + is: + + >>> alt + True + >>> G.order() + 4 + + Alternatively, the group can be returned by name: + + >>> G_name, _ = galois_group(f, by_name=True) + >>> print(G_name) + S4TransitiveSubgroups.V + + The group itself can then be obtained by calling the name's + ``get_perm_group()`` method: + + >>> G_name.get_perm_group() + PermutationGroup([ + (0 1)(2 3), + (0 2)(1 3)]) + + Group names are values of the enum classes + :py:class:`sympy.combinatorics.galois.S1TransitiveSubgroups`, + :py:class:`sympy.combinatorics.galois.S2TransitiveSubgroups`, + etc. + + Parameters + ========== + + f : Expr + Irreducible polynomial over :ref:`ZZ` or :ref:`QQ`, whose Galois group + is to be determined. + gens : optional list of symbols + For converting *f* to Poly, and will be passed on to the + :py:func:`~.poly_from_expr` function. + by_name : bool, default False + If ``True``, the Galois group will be returned by name. + Otherwise it will be returned as a :py:class:`~.PermutationGroup`. + max_tries : int, default 30 + Make at most this many attempts in those steps that involve + generating Tschirnhausen transformations. + randomize : bool, default False + If ``True``, then use random coefficients when generating Tschirnhausen + transformations. Otherwise try transformations in a fixed order. Both + approaches start with small coefficients and degrees and work upward. + args : optional + For converting *f* to Poly, and will be passed on to the + :py:func:`~.poly_from_expr` function. + + Returns + ======= + + Pair ``(G, alt)`` + The first element ``G`` indicates the Galois group. It is an instance + of one of the :py:class:`sympy.combinatorics.galois.S1TransitiveSubgroups` + :py:class:`sympy.combinatorics.galois.S2TransitiveSubgroups`, etc. enum + classes if *by_name* was ``True``, and a :py:class:`~.PermutationGroup` + if ``False``. + + The second element is a boolean, saying whether the group is contained + in the alternating group $A_n$ ($n$ the degree of *T*). + + Raises + ====== + + ValueError + if *f* is of an unsupported degree. + + MaxTriesException + if could not complete before exceeding *max_tries* in those steps + that involve generating Tschirnhausen transformations. + + See Also + ======== + + .Poly.galois_group + + """ + gens = gens or [] + args = args or {} + + try: + F, opt = poly_from_expr(f, *gens, **args) + except PolificationFailed as exc: + raise ComputationFailed('galois_group', 1, exc) + + return F.galois_group(by_name=by_name, max_tries=max_tries, + randomize=randomize) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/minpoly.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/minpoly.py new file mode 100644 index 0000000000000000000000000000000000000000..e5f556e6f82a9790aa7c421fc14ac0fb637b7b49 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/minpoly.py @@ -0,0 +1,882 @@ +"""Minimal polynomials for algebraic numbers.""" + +from functools import reduce + +from sympy.core.add import Add +from sympy.core.exprtools import Factors +from sympy.core.function import expand_mul, expand_multinomial, _mexpand +from sympy.core.mul import Mul +from sympy.core.numbers import (I, Rational, pi, _illegal) +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify +from sympy.core.traversal import preorder_traversal +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt, cbrt +from sympy.functions.elementary.trigonometric import cos, sin, tan +from sympy.ntheory.factor_ import divisors +from sympy.utilities.iterables import subsets + +from sympy.polys.domains import ZZ, QQ, FractionField +from sympy.polys.orthopolys import dup_chebyshevt +from sympy.polys.polyerrors import ( + NotAlgebraic, + GeneratorsError, +) +from sympy.polys.polytools import ( + Poly, PurePoly, invert, factor_list, groebner, resultant, + degree, poly_from_expr, parallel_poly_from_expr, lcm +) +from sympy.polys.polyutils import dict_from_expr, expr_from_dict +from sympy.polys.ring_series import rs_compose_add +from sympy.polys.rings import ring +from sympy.polys.rootoftools import CRootOf +from sympy.polys.specialpolys import cyclotomic_poly +from sympy.utilities import ( + numbered_symbols, public, sift +) + + +def _choose_factor(factors, x, v, dom=QQ, prec=200, bound=5): + """ + Return a factor having root ``v`` + It is assumed that one of the factors has root ``v``. + """ + + if isinstance(factors[0], tuple): + factors = [f[0] for f in factors] + if len(factors) == 1: + return factors[0] + + prec1 = 10 + points = {} + symbols = dom.symbols if hasattr(dom, 'symbols') else [] + while prec1 <= prec: + # when dealing with non-Rational numbers we usually evaluate + # with `subs` argument but we only need a ballpark evaluation + fe = [f.as_expr().xreplace({x:v}) for f in factors] + if v.is_number: + fe = [f.n(prec) for f in fe] + + # assign integers [0, n) to symbols (if any) + for n in subsets(range(bound), k=len(symbols), repetition=True): + for s, i in zip(symbols, n): + points[s] = i + + # evaluate the expression at these points + candidates = [(abs(f.subs(points).n(prec1)), i) + for i,f in enumerate(fe)] + + # if we get invalid numbers (e.g. from division by zero) + # we try again + if any(i in _illegal for i, _ in candidates): + continue + + # find the smallest two -- if they differ significantly + # then we assume we have found the factor that becomes + # 0 when v is substituted into it + can = sorted(candidates) + (a, ix), (b, _) = can[:2] + if b > a * 10**6: # XXX what to use? + return factors[ix] + + prec1 *= 2 + + raise NotImplementedError("multiple candidates for the minimal polynomial of %s" % v) + + +def _is_sum_surds(p): + return all(f.is_Rational or f.is_Pow and + f.base.is_Rational and (2*f.exp).is_Integer and f.is_extended_real + for t in Add.make_args(p) for f in Mul.make_args(t)) + + +def _separate_sq(p): + """ + helper function for ``_minimal_polynomial_sq`` + + It selects a rational ``g`` such that the polynomial ``p`` + consists of a sum of terms whose surds squared have gcd equal to ``g`` + and a sum of terms with surds squared prime with ``g``; + then it takes the field norm to eliminate ``sqrt(g)`` + + See simplify.simplify.split_surds and polytools.sqf_norm. + + Examples + ======== + + >>> from sympy import sqrt + >>> from sympy.abc import x + >>> from sympy.polys.numberfields.minpoly import _separate_sq + >>> p= -x + sqrt(2) + sqrt(3) + sqrt(7) + >>> p = _separate_sq(p); p + -x**2 + 2*sqrt(3)*x + 2*sqrt(7)*x - 2*sqrt(21) - 8 + >>> p = _separate_sq(p); p + -x**4 + 4*sqrt(7)*x**3 - 32*x**2 + 8*sqrt(7)*x + 20 + >>> p = _separate_sq(p); p + -x**8 + 48*x**6 - 536*x**4 + 1728*x**2 - 400 + + """ + def is_sqrt(expr): + return expr.is_Pow and expr.exp is S.Half + # p = c1*sqrt(q1) + ... + cn*sqrt(qn) -> a = [(c1, q1), .., (cn, qn)] + a = [] + for y in p.args: + if not y.is_Mul: + if is_sqrt(y): + a.append((S.One, y**2)) + elif y.is_Atom: + a.append((y, S.One)) + elif y.is_Pow and y.exp.is_integer: + a.append((y, S.One)) + else: + raise NotImplementedError + else: + T, F = sift(y.args, is_sqrt, binary=True) + a.append((Mul(*F), Mul(*T)**2)) + a.sort(key=lambda z: z[1]) + if a[-1][1] is S.One: + # there are no surds + return p + surds = [z for y, z in a] + for i in range(len(surds)): + if surds[i] != 1: + break + from sympy.simplify.radsimp import _split_gcd + g, b1, b2 = _split_gcd(*surds[i:]) + a1 = [] + a2 = [] + for y, z in a: + if z in b1: + a1.append(y*z**S.Half) + else: + a2.append(y*z**S.Half) + p1 = Add(*a1) + p2 = Add(*a2) + p = _mexpand(p1**2) - _mexpand(p2**2) + return p + +def _minimal_polynomial_sq(p, n, x): + """ + Returns the minimal polynomial for the ``nth-root`` of a sum of surds + or ``None`` if it fails. + + Parameters + ========== + + p : sum of surds + n : positive integer + x : variable of the returned polynomial + + Examples + ======== + + >>> from sympy.polys.numberfields.minpoly import _minimal_polynomial_sq + >>> from sympy import sqrt + >>> from sympy.abc import x + >>> q = 1 + sqrt(2) + sqrt(3) + >>> _minimal_polynomial_sq(q, 3, x) + x**12 - 4*x**9 - 4*x**6 + 16*x**3 - 8 + + """ + p = sympify(p) + n = sympify(n) + if not n.is_Integer or not n > 0 or not _is_sum_surds(p): + return None + pn = p**Rational(1, n) + # eliminate the square roots + p -= x + while 1: + p1 = _separate_sq(p) + if p1 is p: + p = p1.subs({x:x**n}) + break + else: + p = p1 + + # _separate_sq eliminates field extensions in a minimal way, so that + # if n = 1 then `p = constant*(minimal_polynomial(p))` + # if n > 1 it contains the minimal polynomial as a factor. + if n == 1: + p1 = Poly(p) + if p.coeff(x**p1.degree(x)) < 0: + p = -p + p = p.primitive()[1] + return p + # by construction `p` has root `pn` + # the minimal polynomial is the factor vanishing in x = pn + factors = factor_list(p)[1] + + result = _choose_factor(factors, x, pn) + return result + +def _minpoly_op_algebraic_element(op, ex1, ex2, x, dom, mp1=None, mp2=None): + """ + return the minimal polynomial for ``op(ex1, ex2)`` + + Parameters + ========== + + op : operation ``Add`` or ``Mul`` + ex1, ex2 : expressions for the algebraic elements + x : indeterminate of the polynomials + dom: ground domain + mp1, mp2 : minimal polynomials for ``ex1`` and ``ex2`` or None + + Examples + ======== + + >>> from sympy import sqrt, Add, Mul, QQ + >>> from sympy.polys.numberfields.minpoly import _minpoly_op_algebraic_element + >>> from sympy.abc import x, y + >>> p1 = sqrt(sqrt(2) + 1) + >>> p2 = sqrt(sqrt(2) - 1) + >>> _minpoly_op_algebraic_element(Mul, p1, p2, x, QQ) + x - 1 + >>> q1 = sqrt(y) + >>> q2 = 1 / y + >>> _minpoly_op_algebraic_element(Add, q1, q2, x, QQ.frac_field(y)) + x**2*y**2 - 2*x*y - y**3 + 1 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Resultant + .. [2] I.M. Isaacs, Proc. Amer. Math. Soc. 25 (1970), 638 + "Degrees of sums in a separable field extension". + + """ + y = Dummy(str(x)) + if mp1 is None: + mp1 = _minpoly_compose(ex1, x, dom) + if mp2 is None: + mp2 = _minpoly_compose(ex2, y, dom) + else: + mp2 = mp2.subs({x: y}) + + if op is Add: + # mp1a = mp1.subs({x: x - y}) + if dom == QQ: + R, X = ring('X', QQ) + p1 = R(dict_from_expr(mp1)[0]) + p2 = R(dict_from_expr(mp2)[0]) + else: + (p1, p2), _ = parallel_poly_from_expr((mp1, x - y), x, y) + r = p1.compose(p2) + mp1a = r.as_expr() + + elif op is Mul: + mp1a = _muly(mp1, x, y) + else: + raise NotImplementedError('option not available') + + if op is Mul or dom != QQ: + r = resultant(mp1a, mp2, gens=[y, x]) + else: + r = rs_compose_add(p1, p2) + r = expr_from_dict(r.as_expr_dict(), x) + + deg1 = degree(mp1, x) + deg2 = degree(mp2, y) + if op is Mul and deg1 == 1 or deg2 == 1: + # if deg1 = 1, then mp1 = x - a; mp1a = x - y - a; + # r = mp2(x - a), so that `r` is irreducible + return r + + r = Poly(r, x, domain=dom) + _, factors = r.factor_list() + res = _choose_factor(factors, x, op(ex1, ex2), dom) + return res.as_expr() + + +def _invertx(p, x): + """ + Returns ``expand_mul(x**degree(p, x)*p.subs(x, 1/x))`` + """ + p1 = poly_from_expr(p, x)[0] + + n = degree(p1) + a = [c * x**(n - i) for (i,), c in p1.terms()] + return Add(*a) + + +def _muly(p, x, y): + """ + Returns ``_mexpand(y**deg*p.subs({x:x / y}))`` + """ + p1 = poly_from_expr(p, x)[0] + + n = degree(p1) + a = [c * x**i * y**(n - i) for (i,), c in p1.terms()] + return Add(*a) + + +def _minpoly_pow(ex, pw, x, dom, mp=None): + """ + Returns ``minpoly(ex**pw, x)`` + + Parameters + ========== + + ex : algebraic element + pw : rational number + x : indeterminate of the polynomial + dom: ground domain + mp : minimal polynomial of ``p`` + + Examples + ======== + + >>> from sympy import sqrt, QQ, Rational + >>> from sympy.polys.numberfields.minpoly import _minpoly_pow, minpoly + >>> from sympy.abc import x, y + >>> p = sqrt(1 + sqrt(2)) + >>> _minpoly_pow(p, 2, x, QQ) + x**2 - 2*x - 1 + >>> minpoly(p**2, x) + x**2 - 2*x - 1 + >>> _minpoly_pow(y, Rational(1, 3), x, QQ.frac_field(y)) + x**3 - y + >>> minpoly(y**Rational(1, 3), x) + x**3 - y + + """ + pw = sympify(pw) + if not mp: + mp = _minpoly_compose(ex, x, dom) + if not pw.is_rational: + raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) + if pw < 0: + if mp == x: + raise ZeroDivisionError('%s is zero' % ex) + mp = _invertx(mp, x) + if pw == -1: + return mp + pw = -pw + ex = 1/ex + + y = Dummy(str(x)) + mp = mp.subs({x: y}) + n, d = pw.as_numer_denom() + res = Poly(resultant(mp, x**d - y**n, gens=[y]), x, domain=dom) + _, factors = res.factor_list() + res = _choose_factor(factors, x, ex**pw, dom) + return res.as_expr() + + +def _minpoly_add(x, dom, *a): + """ + returns ``minpoly(Add(*a), dom, x)`` + """ + mp = _minpoly_op_algebraic_element(Add, a[0], a[1], x, dom) + p = a[0] + a[1] + for px in a[2:]: + mp = _minpoly_op_algebraic_element(Add, p, px, x, dom, mp1=mp) + p = p + px + return mp + + +def _minpoly_mul(x, dom, *a): + """ + returns ``minpoly(Mul(*a), dom, x)`` + """ + mp = _minpoly_op_algebraic_element(Mul, a[0], a[1], x, dom) + p = a[0] * a[1] + for px in a[2:]: + mp = _minpoly_op_algebraic_element(Mul, p, px, x, dom, mp1=mp) + p = p * px + return mp + + +def _minpoly_sin(ex, x): + """ + Returns the minimal polynomial of ``sin(ex)`` + see https://mathworld.wolfram.com/TrigonometryAngles.html + """ + c, a = ex.args[0].as_coeff_Mul() + if a is pi: + if c.is_rational: + n = c.q + q = sympify(n) + if q.is_prime: + # for a = pi*p/q with q odd prime, using chebyshevt + # write sin(q*a) = mp(sin(a))*sin(a); + # the roots of mp(x) are sin(pi*p/q) for p = 1,..., q - 1 + a = dup_chebyshevt(n, ZZ) + return Add(*[x**(n - i - 1)*a[i] for i in range(n)]) + if c.p == 1: + if q == 9: + return 64*x**6 - 96*x**4 + 36*x**2 - 3 + + if n % 2 == 1: + # for a = pi*p/q with q odd, use + # sin(q*a) = 0 to see that the minimal polynomial must be + # a factor of dup_chebyshevt(n, ZZ) + a = dup_chebyshevt(n, ZZ) + a = [x**(n - i)*a[i] for i in range(n + 1)] + r = Add(*a) + _, factors = factor_list(r) + res = _choose_factor(factors, x, ex) + return res + + expr = ((1 - cos(2*c*pi))/2)**S.Half + res = _minpoly_compose(expr, x, QQ) + return res + + raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) + + +def _minpoly_cos(ex, x): + """ + Returns the minimal polynomial of ``cos(ex)`` + see https://mathworld.wolfram.com/TrigonometryAngles.html + """ + c, a = ex.args[0].as_coeff_Mul() + if a is pi: + if c.is_rational: + if c.p == 1: + if c.q == 7: + return 8*x**3 - 4*x**2 - 4*x + 1 + if c.q == 9: + return 8*x**3 - 6*x - 1 + elif c.p == 2: + q = sympify(c.q) + if q.is_prime: + s = _minpoly_sin(ex, x) + return _mexpand(s.subs({x:sqrt((1 - x)/2)})) + + # for a = pi*p/q, cos(q*a) =T_q(cos(a)) = (-1)**p + n = int(c.q) + a = dup_chebyshevt(n, ZZ) + a = [x**(n - i)*a[i] for i in range(n + 1)] + r = Add(*a) - (-1)**c.p + _, factors = factor_list(r) + res = _choose_factor(factors, x, ex) + return res + + raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) + + +def _minpoly_tan(ex, x): + """ + Returns the minimal polynomial of ``tan(ex)`` + see https://github.com/sympy/sympy/issues/21430 + """ + c, a = ex.args[0].as_coeff_Mul() + if a is pi: + if c.is_rational: + c = c * 2 + n = int(c.q) + a = n if c.p % 2 == 0 else 1 + terms = [] + for k in range((c.p+1)%2, n+1, 2): + terms.append(a*x**k) + a = -(a*(n-k-1)*(n-k)) // ((k+1)*(k+2)) + + r = Add(*terms) + _, factors = factor_list(r) + res = _choose_factor(factors, x, ex) + return res + + raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) + + +def _minpoly_exp(ex, x): + """ + Returns the minimal polynomial of ``exp(ex)`` + """ + c, a = ex.args[0].as_coeff_Mul() + if a == I*pi: + if c.is_rational: + q = sympify(c.q) + if c.p == 1 or c.p == -1: + if q == 3: + return x**2 - x + 1 + if q == 4: + return x**4 + 1 + if q == 6: + return x**4 - x**2 + 1 + if q == 8: + return x**8 + 1 + if q == 9: + return x**6 - x**3 + 1 + if q == 10: + return x**8 - x**6 + x**4 - x**2 + 1 + if q.is_prime: + s = 0 + for i in range(q): + s += (-x)**i + return s + + # x**(2*q) = product(factors) + factors = [cyclotomic_poly(i, x) for i in divisors(2*q)] + mp = _choose_factor(factors, x, ex) + return mp + else: + raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) + raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) + + +def _minpoly_rootof(ex, x): + """ + Returns the minimal polynomial of a ``CRootOf`` object. + """ + p = ex.expr + p = p.subs({ex.poly.gens[0]:x}) + _, factors = factor_list(p, x) + result = _choose_factor(factors, x, ex) + return result + + +def _minpoly_compose(ex, x, dom): + """ + Computes the minimal polynomial of an algebraic element + using operations on minimal polynomials + + Examples + ======== + + >>> from sympy import minimal_polynomial, sqrt, Rational + >>> from sympy.abc import x, y + >>> minimal_polynomial(sqrt(2) + 3*Rational(1, 3), x, compose=True) + x**2 - 2*x - 1 + >>> minimal_polynomial(sqrt(y) + 1/y, x, compose=True) + x**2*y**2 - 2*x*y - y**3 + 1 + + """ + if ex.is_Rational: + return ex.q*x - ex.p + if ex is I: + _, factors = factor_list(x**2 + 1, x, domain=dom) + return x**2 + 1 if len(factors) == 1 else x - I + + if ex is S.GoldenRatio: + _, factors = factor_list(x**2 - x - 1, x, domain=dom) + if len(factors) == 1: + return x**2 - x - 1 + else: + return _choose_factor(factors, x, (1 + sqrt(5))/2, dom=dom) + + if ex is S.TribonacciConstant: + _, factors = factor_list(x**3 - x**2 - x - 1, x, domain=dom) + if len(factors) == 1: + return x**3 - x**2 - x - 1 + else: + fac = (1 + cbrt(19 - 3*sqrt(33)) + cbrt(19 + 3*sqrt(33))) / 3 + return _choose_factor(factors, x, fac, dom=dom) + + if hasattr(dom, 'symbols') and ex in dom.symbols: + return x - ex + + if dom.is_QQ and _is_sum_surds(ex): + # eliminate the square roots + v = ex + ex -= x + while 1: + ex1 = _separate_sq(ex) + if ex1 is ex: + return _choose_factor(factor_list(ex)[1], x, v) + else: + ex = ex1 + + if ex.is_Add: + res = _minpoly_add(x, dom, *ex.args) + elif ex.is_Mul: + f = Factors(ex).factors + r = sift(f.items(), lambda itx: itx[0].is_Rational and itx[1].is_Rational) + if r[True] and dom == QQ: + ex1 = Mul(*[bx**ex for bx, ex in r[False] + r[None]]) + r1 = dict(r[True]) + dens = [y.q for y in r1.values()] + lcmdens = reduce(lcm, dens, 1) + neg1 = S.NegativeOne + expn1 = r1.pop(neg1, S.Zero) + nums = [base**(y.p*lcmdens // y.q) for base, y in r1.items()] + ex2 = Mul(*nums) + mp1 = minimal_polynomial(ex1, x) + # use the fact that in SymPy canonicalization products of integers + # raised to rational powers are organized in relatively prime + # bases, and that in ``base**(n/d)`` a perfect power is + # simplified with the root + # Powers of -1 have to be treated separately to preserve sign. + mp2 = ex2.q*x**lcmdens - ex2.p*neg1**(expn1*lcmdens) + ex2 = neg1**expn1 * ex2**Rational(1, lcmdens) + res = _minpoly_op_algebraic_element(Mul, ex1, ex2, x, dom, mp1=mp1, mp2=mp2) + else: + res = _minpoly_mul(x, dom, *ex.args) + elif ex.is_Pow: + res = _minpoly_pow(ex.base, ex.exp, x, dom) + elif ex.__class__ is sin: + res = _minpoly_sin(ex, x) + elif ex.__class__ is cos: + res = _minpoly_cos(ex, x) + elif ex.__class__ is tan: + res = _minpoly_tan(ex, x) + elif ex.__class__ is exp: + res = _minpoly_exp(ex, x) + elif ex.__class__ is CRootOf: + res = _minpoly_rootof(ex, x) + else: + raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) + return res + + +@public +def minimal_polynomial(ex, x=None, compose=True, polys=False, domain=None): + """ + Computes the minimal polynomial of an algebraic element. + + Parameters + ========== + + ex : Expr + Element or expression whose minimal polynomial is to be calculated. + + x : Symbol, optional + Independent variable of the minimal polynomial + + compose : boolean, optional (default=True) + Method to use for computing minimal polynomial. If ``compose=True`` + (default) then ``_minpoly_compose`` is used, if ``compose=False`` then + groebner bases are used. + + polys : boolean, optional (default=False) + If ``True`` returns a ``Poly`` object else an ``Expr`` object. + + domain : Domain, optional + Ground domain + + Notes + ===== + + By default ``compose=True``, the minimal polynomial of the subexpressions of ``ex`` + are computed, then the arithmetic operations on them are performed using the resultant + and factorization. + If ``compose=False``, a bottom-up algorithm is used with ``groebner``. + The default algorithm stalls less frequently. + + If no ground domain is given, it will be generated automatically from the expression. + + Examples + ======== + + >>> from sympy import minimal_polynomial, sqrt, solve, QQ + >>> from sympy.abc import x, y + + >>> minimal_polynomial(sqrt(2), x) + x**2 - 2 + >>> minimal_polynomial(sqrt(2), x, domain=QQ.algebraic_field(sqrt(2))) + x - sqrt(2) + >>> minimal_polynomial(sqrt(2) + sqrt(3), x) + x**4 - 10*x**2 + 1 + >>> minimal_polynomial(solve(x**3 + x + 3)[0], x) + x**3 + x + 3 + >>> minimal_polynomial(sqrt(y), x) + x**2 - y + + """ + + ex = sympify(ex) + if ex.is_number: + # not sure if it's always needed but try it for numbers (issue 8354) + ex = _mexpand(ex, recursive=True) + for expr in preorder_traversal(ex): + if expr.is_AlgebraicNumber: + compose = False + break + + if x is not None: + x, cls = sympify(x), Poly + else: + x, cls = Dummy('x'), PurePoly + + if not domain: + if ex.free_symbols: + domain = FractionField(QQ, list(ex.free_symbols)) + else: + domain = QQ + if hasattr(domain, 'symbols') and x in domain.symbols: + raise GeneratorsError("the variable %s is an element of the ground " + "domain %s" % (x, domain)) + + if compose: + result = _minpoly_compose(ex, x, domain) + result = result.primitive()[1] + c = result.coeff(x**degree(result, x)) + if c.is_negative: + result = expand_mul(-result) + return cls(result, x, field=True) if polys else result.collect(x) + + if not domain.is_QQ: + raise NotImplementedError("groebner method only works for QQ") + + result = _minpoly_groebner(ex, x, cls) + return cls(result, x, field=True) if polys else result.collect(x) + + +def _minpoly_groebner(ex, x, cls): + """ + Computes the minimal polynomial of an algebraic number + using Groebner bases + + Examples + ======== + + >>> from sympy import minimal_polynomial, sqrt, Rational + >>> from sympy.abc import x + >>> minimal_polynomial(sqrt(2) + 3*Rational(1, 3), x, compose=False) + x**2 - 2*x - 1 + + """ + + generator = numbered_symbols('a', cls=Dummy) + mapping, symbols = {}, {} + + def update_mapping(ex, exp, base=None): + a = next(generator) + symbols[ex] = a + + if base is not None: + mapping[ex] = a**exp + base + else: + mapping[ex] = exp.as_expr(a) + + return a + + def bottom_up_scan(ex): + """ + Transform a given algebraic expression *ex* into a multivariate + polynomial, by introducing fresh variables with defining equations. + + Explanation + =========== + + The critical elements of the algebraic expression *ex* are root + extractions, instances of :py:class:`~.AlgebraicNumber`, and negative + powers. + + When we encounter a root extraction or an :py:class:`~.AlgebraicNumber` + we replace this expression with a fresh variable ``a_i``, and record + the defining polynomial for ``a_i``. For example, if ``a_0**(1/3)`` + occurs, we will replace it with ``a_1``, and record the new defining + polynomial ``a_1**3 - a_0``. + + When we encounter a negative power we transform it into a positive + power by algebraically inverting the base. This means computing the + minimal polynomial in ``x`` for the base, inverting ``x`` modulo this + poly (which generates a new polynomial) and then substituting the + original base expression for ``x`` in this last polynomial. + + We return the transformed expression, and we record the defining + equations for new symbols using the ``update_mapping()`` function. + + """ + if ex.is_Atom: + if ex is S.ImaginaryUnit: + if ex not in mapping: + return update_mapping(ex, 2, 1) + else: + return symbols[ex] + elif ex.is_Rational: + return ex + elif ex.is_Add: + return Add(*[ bottom_up_scan(g) for g in ex.args ]) + elif ex.is_Mul: + return Mul(*[ bottom_up_scan(g) for g in ex.args ]) + elif ex.is_Pow: + if ex.exp.is_Rational: + if ex.exp < 0: + minpoly_base = _minpoly_groebner(ex.base, x, cls) + inverse = invert(x, minpoly_base).as_expr() + base_inv = inverse.subs(x, ex.base).expand() + + if ex.exp == -1: + return bottom_up_scan(base_inv) + else: + ex = base_inv**(-ex.exp) + if not ex.exp.is_Integer: + base, exp = ( + ex.base**ex.exp.p).expand(), Rational(1, ex.exp.q) + else: + base, exp = ex.base, ex.exp + base = bottom_up_scan(base) + expr = base**exp + + if expr not in mapping: + if exp.is_Integer: + return expr.expand() + else: + return update_mapping(expr, 1 / exp, -base) + else: + return symbols[expr] + elif ex.is_AlgebraicNumber: + if ex not in mapping: + return update_mapping(ex, ex.minpoly_of_element()) + else: + return symbols[ex] + + raise NotAlgebraic("%s does not seem to be an algebraic number" % ex) + + def simpler_inverse(ex): + """ + Returns True if it is more likely that the minimal polynomial + algorithm works better with the inverse + """ + if ex.is_Pow: + if (1/ex.exp).is_integer and ex.exp < 0: + if ex.base.is_Add: + return True + if ex.is_Mul: + hit = True + for p in ex.args: + if p.is_Add: + return False + if p.is_Pow: + if p.base.is_Add and p.exp > 0: + return False + + if hit: + return True + return False + + inverted = False + ex = expand_multinomial(ex) + if ex.is_AlgebraicNumber: + return ex.minpoly_of_element().as_expr(x) + elif ex.is_Rational: + result = ex.q*x - ex.p + else: + inverted = simpler_inverse(ex) + if inverted: + ex = ex**-1 + res = None + if ex.is_Pow and (1/ex.exp).is_Integer: + n = 1/ex.exp + res = _minimal_polynomial_sq(ex.base, n, x) + + elif _is_sum_surds(ex): + res = _minimal_polynomial_sq(ex, S.One, x) + + if res is not None: + result = res + + if res is None: + bus = bottom_up_scan(ex) + F = [x - bus] + list(mapping.values()) + G = groebner(F, list(symbols.values()) + [x], order='lex') + + _, factors = factor_list(G[-1]) + # by construction G[-1] has root `ex` + result = _choose_factor(factors, x, ex) + if inverted: + result = _invertx(result, x) + if result.coeff(x**degree(result, x)) < 0: + result = expand_mul(-result) + + return result + + +@public +def minpoly(ex, x=None, compose=True, polys=False, domain=None): + """This is a synonym for :py:func:`~.minimal_polynomial`.""" + return minimal_polynomial(ex, x=x, compose=compose, polys=polys, domain=domain) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/modules.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/modules.py new file mode 100644 index 0000000000000000000000000000000000000000..af2e29bcc9cf73d97def0701712f90db58601b86 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/modules.py @@ -0,0 +1,2114 @@ +r"""Modules in number fields. + +The classes defined here allow us to work with finitely generated, free +modules, whose generators are algebraic numbers. + +There is an abstract base class called :py:class:`~.Module`, which has two +concrete subclasses, :py:class:`~.PowerBasis` and :py:class:`~.Submodule`. + +Every module is defined by its basis, or set of generators: + +* For a :py:class:`~.PowerBasis`, the generators are the first $n$ powers + (starting with the zeroth) of an algebraic integer $\theta$ of degree $n$. + The :py:class:`~.PowerBasis` is constructed by passing either the minimal + polynomial of $\theta$, or an :py:class:`~.AlgebraicField` having $\theta$ + as its primitive element. + +* For a :py:class:`~.Submodule`, the generators are a set of + $\mathbb{Q}$-linear combinations of the generators of another module. That + other module is then the "parent" of the :py:class:`~.Submodule`. The + coefficients of the $\mathbb{Q}$-linear combinations may be given by an + integer matrix, and a positive integer denominator. Each column of the matrix + defines a generator. + +>>> from sympy.polys import Poly, cyclotomic_poly, ZZ +>>> from sympy.abc import x +>>> from sympy.polys.matrices import DomainMatrix, DM +>>> from sympy.polys.numberfields.modules import PowerBasis +>>> T = Poly(cyclotomic_poly(5, x)) +>>> A = PowerBasis(T) +>>> print(A) +PowerBasis(x**4 + x**3 + x**2 + x + 1) +>>> B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ), denom=3) +>>> print(B) +Submodule[[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]/3 +>>> print(B.parent) +PowerBasis(x**4 + x**3 + x**2 + x + 1) + +Thus, every module is either a :py:class:`~.PowerBasis`, +or a :py:class:`~.Submodule`, some ancestor of which is a +:py:class:`~.PowerBasis`. (If ``S`` is a :py:class:`~.Submodule`, then its +ancestors are ``S.parent``, ``S.parent.parent``, and so on). + +The :py:class:`~.ModuleElement` class represents a linear combination of the +generators of any module. Critically, the coefficients of this linear +combination are not restricted to be integers, but may be any rational +numbers. This is necessary so that any and all algebraic integers be +representable, starting from the power basis in a primitive element $\theta$ +for the number field in question. For example, in a quadratic field +$\mathbb{Q}(\sqrt{d})$ where $d \equiv 1 \mod{4}$, a denominator of $2$ is +needed. + +A :py:class:`~.ModuleElement` can be constructed from an integer column vector +and a denominator: + +>>> U = Poly(x**2 - 5) +>>> M = PowerBasis(U) +>>> e = M(DM([[1], [1]], ZZ), denom=2) +>>> print(e) +[1, 1]/2 +>>> print(e.module) +PowerBasis(x**2 - 5) + +The :py:class:`~.PowerBasisElement` class is a subclass of +:py:class:`~.ModuleElement` that represents elements of a +:py:class:`~.PowerBasis`, and adds functionality pertinent to elements +represented directly over powers of the primitive element $\theta$. + + +Arithmetic with module elements +=============================== + +While a :py:class:`~.ModuleElement` represents a linear combination over the +generators of a particular module, recall that every module is either a +:py:class:`~.PowerBasis` or a descendant (along a chain of +:py:class:`~.Submodule` objects) thereof, so that in fact every +:py:class:`~.ModuleElement` represents an algebraic number in some field +$\mathbb{Q}(\theta)$, where $\theta$ is the defining element of some +:py:class:`~.PowerBasis`. It thus makes sense to talk about the number field +to which a given :py:class:`~.ModuleElement` belongs. + +This means that any two :py:class:`~.ModuleElement` instances can be added, +subtracted, multiplied, or divided, provided they belong to the same number +field. Similarly, since $\mathbb{Q}$ is a subfield of every number field, +any :py:class:`~.ModuleElement` may be added, multiplied, etc. by any +rational number. + +>>> from sympy import QQ +>>> from sympy.polys.numberfields.modules import to_col +>>> T = Poly(cyclotomic_poly(5)) +>>> A = PowerBasis(T) +>>> C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) +>>> e = A(to_col([0, 2, 0, 0]), denom=3) +>>> f = A(to_col([0, 0, 0, 7]), denom=5) +>>> g = C(to_col([1, 1, 1, 1])) +>>> e + f +[0, 10, 0, 21]/15 +>>> e - f +[0, 10, 0, -21]/15 +>>> e - g +[-9, -7, -9, -9]/3 +>>> e + QQ(7, 10) +[21, 20, 0, 0]/30 +>>> e * f +[-14, -14, -14, -14]/15 +>>> e ** 2 +[0, 0, 4, 0]/9 +>>> f // g +[7, 7, 7, 7]/15 +>>> f * QQ(2, 3) +[0, 0, 0, 14]/15 + +However, care must be taken with arithmetic operations on +:py:class:`~.ModuleElement`, because the module $C$ to which the result will +belong will be the nearest common ancestor (NCA) of the modules $A$, $B$ to +which the two operands belong, and $C$ may be different from either or both +of $A$ and $B$. + +>>> A = PowerBasis(T) +>>> B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) +>>> C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) +>>> print((B(0) * C(0)).module == A) +True + +Before the arithmetic operation is performed, copies of the two operands are +automatically converted into elements of the NCA (the operands themselves are +not modified). This upward conversion along an ancestor chain is easy: it just +requires the successive multiplication by the defining matrix of each +:py:class:`~.Submodule`. + +Conversely, downward conversion, i.e. representing a given +:py:class:`~.ModuleElement` in a submodule, is also supported -- namely by +the :py:meth:`~sympy.polys.numberfields.modules.Submodule.represent` method +-- but is not guaranteed to succeed in general, since the given element may +not belong to the submodule. The main circumstance in which this issue tends +to arise is with multiplication, since modules, while closed under addition, +need not be closed under multiplication. + + +Multiplication +-------------- + +Generally speaking, a module need not be closed under multiplication, i.e. need +not form a ring. However, many of the modules we work with in the context of +number fields are in fact rings, and our classes do support multiplication. + +Specifically, any :py:class:`~.Module` can attempt to compute its own +multiplication table, but this does not happen unless an attempt is made to +multiply two :py:class:`~.ModuleElement` instances belonging to it. + +>>> A = PowerBasis(T) +>>> print(A._mult_tab is None) +True +>>> a = A(0)*A(1) +>>> print(A._mult_tab is None) +False + +Every :py:class:`~.PowerBasis` is, by its nature, closed under multiplication, +so instances of :py:class:`~.PowerBasis` can always successfully compute their +multiplication table. + +When a :py:class:`~.Submodule` attempts to compute its multiplication table, +it converts each of its own generators into elements of its parent module, +multiplies them there, in every possible pairing, and then tries to +represent the results in itself, i.e. as $\mathbb{Z}$-linear combinations +over its own generators. This will succeed if and only if the submodule is +in fact closed under multiplication. + + +Module Homomorphisms +==================== + +Many important number theoretic algorithms require the calculation of the +kernel of one or more module homomorphisms. Accordingly we have several +lightweight classes, :py:class:`~.ModuleHomomorphism`, +:py:class:`~.ModuleEndomorphism`, :py:class:`~.InnerEndomorphism`, and +:py:class:`~.EndomorphismRing`, which provide the minimal necessary machinery +to support this. + +""" + +from sympy.core.intfunc import igcd, ilcm +from sympy.core.symbol import Dummy +from sympy.polys.polyclasses import ANP +from sympy.polys.polytools import Poly +from sympy.polys.densetools import dup_clear_denoms +from sympy.polys.domains.algebraicfield import AlgebraicField +from sympy.polys.domains.finitefield import FF +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.domains.integerring import ZZ +from sympy.polys.matrices.domainmatrix import DomainMatrix +from sympy.polys.matrices.exceptions import DMBadInputError +from sympy.polys.matrices.normalforms import hermite_normal_form +from sympy.polys.polyerrors import CoercionFailed, UnificationFailed +from sympy.polys.polyutils import IntegerPowerable +from .exceptions import ClosureFailure, MissingUnityError, StructureError +from .utilities import AlgIntPowers, is_rat, get_num_denom + + +def to_col(coeffs): + r"""Transform a list of integer coefficients into a column vector.""" + return DomainMatrix([[ZZ(c) for c in coeffs]], (1, len(coeffs)), ZZ).transpose() + + +class Module: + """ + Generic finitely-generated module. + + This is an abstract base class, and should not be instantiated directly. + The two concrete subclasses are :py:class:`~.PowerBasis` and + :py:class:`~.Submodule`. + + Every :py:class:`~.Submodule` is derived from another module, referenced + by its ``parent`` attribute. If ``S`` is a submodule, then we refer to + ``S.parent``, ``S.parent.parent``, and so on, as the "ancestors" of + ``S``. Thus, every :py:class:`~.Module` is either a + :py:class:`~.PowerBasis` or a :py:class:`~.Submodule`, some ancestor of + which is a :py:class:`~.PowerBasis`. + """ + + @property + def n(self): + """The number of generators of this module.""" + raise NotImplementedError + + def mult_tab(self): + """ + Get the multiplication table for this module (if closed under mult). + + Explanation + =========== + + Computes a dictionary ``M`` of dictionaries of lists, representing the + upper triangular half of the multiplication table. + + In other words, if ``0 <= i <= j < self.n``, then ``M[i][j]`` is the + list ``c`` of coefficients such that + ``g[i] * g[j] == sum(c[k]*g[k], k in range(self.n))``, + where ``g`` is the list of generators of this module. + + If ``j < i`` then ``M[i][j]`` is undefined. + + Examples + ======== + + >>> from sympy.polys import Poly, cyclotomic_poly + >>> from sympy.polys.numberfields.modules import PowerBasis + >>> T = Poly(cyclotomic_poly(5)) + >>> A = PowerBasis(T) + >>> print(A.mult_tab()) # doctest: +SKIP + {0: {0: [1, 0, 0, 0], 1: [0, 1, 0, 0], 2: [0, 0, 1, 0], 3: [0, 0, 0, 1]}, + 1: {1: [0, 0, 1, 0], 2: [0, 0, 0, 1], 3: [-1, -1, -1, -1]}, + 2: {2: [-1, -1, -1, -1], 3: [1, 0, 0, 0]}, + 3: {3: [0, 1, 0, 0]}} + + Returns + ======= + + dict of dict of lists + + Raises + ====== + + ClosureFailure + If the module is not closed under multiplication. + + """ + raise NotImplementedError + + @property + def parent(self): + """ + The parent module, if any, for this module. + + Explanation + =========== + + For a :py:class:`~.Submodule` this is its ``parent`` attribute; for a + :py:class:`~.PowerBasis` this is ``None``. + + Returns + ======= + + :py:class:`~.Module`, ``None`` + + See Also + ======== + + Module + + """ + return None + + def represent(self, elt): + r""" + Represent a module element as an integer-linear combination over the + generators of this module. + + Explanation + =========== + + In our system, to "represent" always means to write a + :py:class:`~.ModuleElement` as a :ref:`ZZ`-linear combination over the + generators of the present :py:class:`~.Module`. Furthermore, the + incoming :py:class:`~.ModuleElement` must belong to an ancestor of + the present :py:class:`~.Module` (or to the present + :py:class:`~.Module` itself). + + The most common application is to represent a + :py:class:`~.ModuleElement` in a :py:class:`~.Submodule`. For example, + this is involved in computing multiplication tables. + + On the other hand, representing in a :py:class:`~.PowerBasis` is an + odd case, and one which tends not to arise in practice, except for + example when using a :py:class:`~.ModuleEndomorphism` on a + :py:class:`~.PowerBasis`. + + In such a case, (1) the incoming :py:class:`~.ModuleElement` must + belong to the :py:class:`~.PowerBasis` itself (since the latter has no + proper ancestors) and (2) it is "representable" iff it belongs to + $\mathbb{Z}[\theta]$ (although generally a + :py:class:`~.PowerBasisElement` may represent any element of + $\mathbb{Q}(\theta)$, i.e. any algebraic number). + + Examples + ======== + + >>> from sympy import Poly, cyclotomic_poly + >>> from sympy.polys.numberfields.modules import PowerBasis, to_col + >>> from sympy.abc import zeta + >>> T = Poly(cyclotomic_poly(5)) + >>> A = PowerBasis(T) + >>> a = A(to_col([2, 4, 6, 8])) + + The :py:class:`~.ModuleElement` ``a`` has all even coefficients. + If we represent ``a`` in the submodule ``B = 2*A``, the coefficients in + the column vector will be halved: + + >>> B = A.submodule_from_gens([2*A(i) for i in range(4)]) + >>> b = B.represent(a) + >>> print(b.transpose()) # doctest: +SKIP + DomainMatrix([[1, 2, 3, 4]], (1, 4), ZZ) + + However, the element of ``B`` so defined still represents the same + algebraic number: + + >>> print(a.poly(zeta).as_expr()) + 8*zeta**3 + 6*zeta**2 + 4*zeta + 2 + >>> print(B(b).over_power_basis().poly(zeta).as_expr()) + 8*zeta**3 + 6*zeta**2 + 4*zeta + 2 + + Parameters + ========== + + elt : :py:class:`~.ModuleElement` + The module element to be represented. Must belong to some ancestor + module of this module (including this module itself). + + Returns + ======= + + :py:class:`~.DomainMatrix` over :ref:`ZZ` + This will be a column vector, representing the coefficients of a + linear combination of this module's generators, which equals the + given element. + + Raises + ====== + + ClosureFailure + If the given element cannot be represented as a :ref:`ZZ`-linear + combination over this module. + + See Also + ======== + + .Submodule.represent + .PowerBasis.represent + + """ + raise NotImplementedError + + def ancestors(self, include_self=False): + """ + Return the list of ancestor modules of this module, from the + foundational :py:class:`~.PowerBasis` downward, optionally including + ``self``. + + See Also + ======== + + Module + + """ + c = self.parent + a = [] if c is None else c.ancestors(include_self=True) + if include_self: + a.append(self) + return a + + def power_basis_ancestor(self): + """ + Return the :py:class:`~.PowerBasis` that is an ancestor of this module. + + See Also + ======== + + Module + + """ + if isinstance(self, PowerBasis): + return self + c = self.parent + if c is not None: + return c.power_basis_ancestor() + return None + + def nearest_common_ancestor(self, other): + """ + Locate the nearest common ancestor of this module and another. + + Returns + ======= + + :py:class:`~.Module`, ``None`` + + See Also + ======== + + Module + + """ + sA = self.ancestors(include_self=True) + oA = other.ancestors(include_self=True) + nca = None + for sa, oa in zip(sA, oA): + if sa == oa: + nca = sa + else: + break + return nca + + @property + def number_field(self): + r""" + Return the associated :py:class:`~.AlgebraicField`, if any. + + Explanation + =========== + + A :py:class:`~.PowerBasis` can be constructed on a :py:class:`~.Poly` + $f$ or on an :py:class:`~.AlgebraicField` $K$. In the latter case, the + :py:class:`~.PowerBasis` and all its descendant modules will return $K$ + as their ``.number_field`` property, while in the former case they will + all return ``None``. + + Returns + ======= + + :py:class:`~.AlgebraicField`, ``None`` + + """ + return self.power_basis_ancestor().number_field + + def is_compat_col(self, col): + """Say whether *col* is a suitable column vector for this module.""" + return isinstance(col, DomainMatrix) and col.shape == (self.n, 1) and col.domain.is_ZZ + + def __call__(self, spec, denom=1): + r""" + Generate a :py:class:`~.ModuleElement` belonging to this module. + + Examples + ======== + + >>> from sympy.polys import Poly, cyclotomic_poly + >>> from sympy.polys.numberfields.modules import PowerBasis, to_col + >>> T = Poly(cyclotomic_poly(5)) + >>> A = PowerBasis(T) + >>> e = A(to_col([1, 2, 3, 4]), denom=3) + >>> print(e) # doctest: +SKIP + [1, 2, 3, 4]/3 + >>> f = A(2) + >>> print(f) # doctest: +SKIP + [0, 0, 1, 0] + + Parameters + ========== + + spec : :py:class:`~.DomainMatrix`, int + Specifies the numerators of the coefficients of the + :py:class:`~.ModuleElement`. Can be either a column vector over + :ref:`ZZ`, whose length must equal the number $n$ of generators of + this module, or else an integer ``j``, $0 \leq j < n$, which is a + shorthand for column $j$ of $I_n$, the $n \times n$ identity + matrix. + denom : int, optional (default=1) + Denominator for the coefficients of the + :py:class:`~.ModuleElement`. + + Returns + ======= + + :py:class:`~.ModuleElement` + The coefficients are the entries of the *spec* vector, divided by + *denom*. + + """ + if isinstance(spec, int) and 0 <= spec < self.n: + spec = DomainMatrix.eye(self.n, ZZ)[:, spec].to_dense() + if not self.is_compat_col(spec): + raise ValueError('Compatible column vector required.') + return make_mod_elt(self, spec, denom=denom) + + def starts_with_unity(self): + """Say whether the module's first generator equals unity.""" + raise NotImplementedError + + def basis_elements(self): + """ + Get list of :py:class:`~.ModuleElement` being the generators of this + module. + """ + return [self(j) for j in range(self.n)] + + def zero(self): + """Return a :py:class:`~.ModuleElement` representing zero.""" + return self(0) * 0 + + def one(self): + """ + Return a :py:class:`~.ModuleElement` representing unity, + and belonging to the first ancestor of this module (including + itself) that starts with unity. + """ + return self.element_from_rational(1) + + def element_from_rational(self, a): + """ + Return a :py:class:`~.ModuleElement` representing a rational number. + + Explanation + =========== + + The returned :py:class:`~.ModuleElement` will belong to the first + module on this module's ancestor chain (including this module + itself) that starts with unity. + + Examples + ======== + + >>> from sympy.polys import Poly, cyclotomic_poly, QQ + >>> from sympy.polys.numberfields.modules import PowerBasis + >>> T = Poly(cyclotomic_poly(5)) + >>> A = PowerBasis(T) + >>> a = A.element_from_rational(QQ(2, 3)) + >>> print(a) # doctest: +SKIP + [2, 0, 0, 0]/3 + + Parameters + ========== + + a : int, :ref:`ZZ`, :ref:`QQ` + + Returns + ======= + + :py:class:`~.ModuleElement` + + """ + raise NotImplementedError + + def submodule_from_gens(self, gens, hnf=True, hnf_modulus=None): + """ + Form the submodule generated by a list of :py:class:`~.ModuleElement` + belonging to this module. + + Examples + ======== + + >>> from sympy.polys import Poly, cyclotomic_poly + >>> from sympy.polys.numberfields.modules import PowerBasis + >>> T = Poly(cyclotomic_poly(5)) + >>> A = PowerBasis(T) + >>> gens = [A(0), 2*A(1), 3*A(2), 4*A(3)//5] + >>> B = A.submodule_from_gens(gens) + >>> print(B) # doctest: +SKIP + Submodule[[5, 0, 0, 0], [0, 10, 0, 0], [0, 0, 15, 0], [0, 0, 0, 4]]/5 + + Parameters + ========== + + gens : list of :py:class:`~.ModuleElement` belonging to this module. + hnf : boolean, optional (default=True) + If True, we will reduce the matrix into Hermite Normal Form before + forming the :py:class:`~.Submodule`. + hnf_modulus : int, None, optional (default=None) + Modulus for use in the HNF reduction algorithm. See + :py:func:`~sympy.polys.matrices.normalforms.hermite_normal_form`. + + Returns + ======= + + :py:class:`~.Submodule` + + See Also + ======== + + submodule_from_matrix + + """ + if not all(g.module == self for g in gens): + raise ValueError('Generators must belong to this module.') + n = len(gens) + if n == 0: + raise ValueError('Need at least one generator.') + m = gens[0].n + d = gens[0].denom if n == 1 else ilcm(*[g.denom for g in gens]) + B = DomainMatrix.zeros((m, 0), ZZ).hstack(*[(d // g.denom) * g.col for g in gens]) + if hnf: + B = hermite_normal_form(B, D=hnf_modulus) + return self.submodule_from_matrix(B, denom=d) + + def submodule_from_matrix(self, B, denom=1): + """ + Form the submodule generated by the elements of this module indicated + by the columns of a matrix, with an optional denominator. + + Examples + ======== + + >>> from sympy.polys import Poly, cyclotomic_poly, ZZ + >>> from sympy.polys.matrices import DM + >>> from sympy.polys.numberfields.modules import PowerBasis + >>> T = Poly(cyclotomic_poly(5)) + >>> A = PowerBasis(T) + >>> B = A.submodule_from_matrix(DM([ + ... [0, 10, 0, 0], + ... [0, 0, 7, 0], + ... ], ZZ).transpose(), denom=15) + >>> print(B) # doctest: +SKIP + Submodule[[0, 10, 0, 0], [0, 0, 7, 0]]/15 + + Parameters + ========== + + B : :py:class:`~.DomainMatrix` over :ref:`ZZ` + Each column gives the numerators of the coefficients of one + generator of the submodule. Thus, the number of rows of *B* must + equal the number of generators of the present module. + denom : int, optional (default=1) + Common denominator for all generators of the submodule. + + Returns + ======= + + :py:class:`~.Submodule` + + Raises + ====== + + ValueError + If the given matrix *B* is not over :ref:`ZZ` or its number of rows + does not equal the number of generators of the present module. + + See Also + ======== + + submodule_from_gens + + """ + m, n = B.shape + if not B.domain.is_ZZ: + raise ValueError('Matrix must be over ZZ.') + if not m == self.n: + raise ValueError('Matrix row count must match base module.') + return Submodule(self, B, denom=denom) + + def whole_submodule(self): + """ + Return a submodule equal to this entire module. + + Explanation + =========== + + This is useful when you have a :py:class:`~.PowerBasis` and want to + turn it into a :py:class:`~.Submodule` (in order to use methods + belonging to the latter). + + """ + B = DomainMatrix.eye(self.n, ZZ) + return self.submodule_from_matrix(B) + + def endomorphism_ring(self): + """Form the :py:class:`~.EndomorphismRing` for this module.""" + return EndomorphismRing(self) + + +class PowerBasis(Module): + """The module generated by the powers of an algebraic integer.""" + + def __init__(self, T): + """ + Parameters + ========== + + T : :py:class:`~.Poly`, :py:class:`~.AlgebraicField` + Either (1) the monic, irreducible, univariate polynomial over + :ref:`ZZ`, a root of which is the generator of the power basis, + or (2) an :py:class:`~.AlgebraicField` whose primitive element + is the generator of the power basis. + + """ + K = None + if isinstance(T, AlgebraicField): + K, T = T, T.ext.minpoly_of_element() + # Sometimes incoming Polys are formally over QQ, although all their + # coeffs are integral. We want them to be formally over ZZ. + T = T.set_domain(ZZ) + self.K = K + self.T = T + self._n = T.degree() + self._mult_tab = None + + @property + def number_field(self): + return self.K + + def __repr__(self): + return f'PowerBasis({self.T.as_expr()})' + + def __eq__(self, other): + if isinstance(other, PowerBasis): + return self.T == other.T + return NotImplemented + + @property + def n(self): + return self._n + + def mult_tab(self): + if self._mult_tab is None: + self.compute_mult_tab() + return self._mult_tab + + def compute_mult_tab(self): + theta_pow = AlgIntPowers(self.T) + M = {} + n = self.n + for u in range(n): + M[u] = {} + for v in range(u, n): + M[u][v] = theta_pow[u + v] + self._mult_tab = M + + def represent(self, elt): + r""" + Represent a module element as an integer-linear combination over the + generators of this module. + + See Also + ======== + + .Module.represent + .Submodule.represent + + """ + if elt.module == self and elt.denom == 1: + return elt.column() + else: + raise ClosureFailure('Element not representable in ZZ[theta].') + + def starts_with_unity(self): + return True + + def element_from_rational(self, a): + return self(0) * a + + def element_from_poly(self, f): + """ + Produce an element of this module, representing *f* after reduction mod + our defining minimal polynomial. + + Parameters + ========== + + f : :py:class:`~.Poly` over :ref:`ZZ` in same var as our defining poly. + + Returns + ======= + + :py:class:`~.PowerBasisElement` + + """ + n, k = self.n, f.degree() + if k >= n: + f = f % self.T + if f == 0: + return self.zero() + d, c = dup_clear_denoms(f.rep.to_list(), QQ, convert=True) + c = list(reversed(c)) + ell = len(c) + z = [ZZ(0)] * (n - ell) + col = to_col(c + z) + return self(col, denom=d) + + def _element_from_rep_and_mod(self, rep, mod): + """ + Produce a PowerBasisElement representing a given algebraic number. + + Parameters + ========== + + rep : list of coeffs + Represents the number as polynomial in the primitive element of the + field. + + mod : list of coeffs + Represents the minimal polynomial of the primitive element of the + field. + + Returns + ======= + + :py:class:`~.PowerBasisElement` + + """ + if mod != self.T.rep.to_list(): + raise UnificationFailed('Element does not appear to be in the same field.') + return self.element_from_poly(Poly(rep, self.T.gen)) + + def element_from_ANP(self, a): + """Convert an ANP into a PowerBasisElement. """ + return self._element_from_rep_and_mod(a.to_list(), a.mod_to_list()) + + def element_from_alg_num(self, a): + """Convert an AlgebraicNumber into a PowerBasisElement. """ + return self._element_from_rep_and_mod(a.rep.to_list(), a.minpoly.rep.to_list()) + + +class Submodule(Module, IntegerPowerable): + """A submodule of another module.""" + + def __init__(self, parent, matrix, denom=1, mult_tab=None): + """ + Parameters + ========== + + parent : :py:class:`~.Module` + The module from which this one is derived. + matrix : :py:class:`~.DomainMatrix` over :ref:`ZZ` + The matrix whose columns define this submodule's generators as + linear combinations over the parent's generators. + denom : int, optional (default=1) + Denominator for the coefficients given by the matrix. + mult_tab : dict, ``None``, optional + If already known, the multiplication table for this module may be + supplied. + + """ + self._parent = parent + self._matrix = matrix + self._denom = denom + self._mult_tab = mult_tab + self._n = matrix.shape[1] + self._QQ_matrix = None + self._starts_with_unity = None + self._is_sq_maxrank_HNF = None + + def __repr__(self): + r = 'Submodule' + repr(self.matrix.transpose().to_Matrix().tolist()) + if self.denom > 1: + r += f'/{self.denom}' + return r + + def reduced(self): + """ + Produce a reduced version of this submodule. + + Explanation + =========== + + In the reduced version, it is guaranteed that 1 is the only positive + integer dividing both the submodule's denominator, and every entry in + the submodule's matrix. + + Returns + ======= + + :py:class:`~.Submodule` + + """ + if self.denom == 1: + return self + g = igcd(self.denom, *self.coeffs) + if g == 1: + return self + return type(self)(self.parent, (self.matrix / g).convert_to(ZZ), denom=self.denom // g, mult_tab=self._mult_tab) + + def discard_before(self, r): + """ + Produce a new module by discarding all generators before a given + index *r*. + """ + W = self.matrix[:, r:] + s = self.n - r + M = None + mt = self._mult_tab + if mt is not None: + M = {} + for u in range(s): + M[u] = {} + for v in range(u, s): + M[u][v] = mt[r + u][r + v][r:] + return Submodule(self.parent, W, denom=self.denom, mult_tab=M) + + @property + def n(self): + return self._n + + def mult_tab(self): + if self._mult_tab is None: + self.compute_mult_tab() + return self._mult_tab + + def compute_mult_tab(self): + gens = self.basis_element_pullbacks() + M = {} + n = self.n + for u in range(n): + M[u] = {} + for v in range(u, n): + M[u][v] = self.represent(gens[u] * gens[v]).flat() + self._mult_tab = M + + @property + def parent(self): + return self._parent + + @property + def matrix(self): + return self._matrix + + @property + def coeffs(self): + return self.matrix.flat() + + @property + def denom(self): + return self._denom + + @property + def QQ_matrix(self): + """ + :py:class:`~.DomainMatrix` over :ref:`QQ`, equal to + ``self.matrix / self.denom``, and guaranteed to be dense. + + Explanation + =========== + + Depending on how it is formed, a :py:class:`~.DomainMatrix` may have + an internal representation that is sparse or dense. We guarantee a + dense representation here, so that tests for equivalence of submodules + always come out as expected. + + Examples + ======== + + >>> from sympy.polys import Poly, cyclotomic_poly, ZZ + >>> from sympy.abc import x + >>> from sympy.polys.matrices import DomainMatrix + >>> from sympy.polys.numberfields.modules import PowerBasis + >>> T = Poly(cyclotomic_poly(5, x)) + >>> A = PowerBasis(T) + >>> B = A.submodule_from_matrix(3*DomainMatrix.eye(4, ZZ), denom=6) + >>> C = A.submodule_from_matrix(DomainMatrix.eye(4, ZZ), denom=2) + >>> print(B.QQ_matrix == C.QQ_matrix) + True + + Returns + ======= + + :py:class:`~.DomainMatrix` over :ref:`QQ` + + """ + if self._QQ_matrix is None: + self._QQ_matrix = (self.matrix / self.denom).to_dense() + return self._QQ_matrix + + def starts_with_unity(self): + if self._starts_with_unity is None: + self._starts_with_unity = self(0).equiv(1) + return self._starts_with_unity + + def is_sq_maxrank_HNF(self): + if self._is_sq_maxrank_HNF is None: + self._is_sq_maxrank_HNF = is_sq_maxrank_HNF(self._matrix) + return self._is_sq_maxrank_HNF + + def is_power_basis_submodule(self): + return isinstance(self.parent, PowerBasis) + + def element_from_rational(self, a): + if self.starts_with_unity(): + return self(0) * a + else: + return self.parent.element_from_rational(a) + + def basis_element_pullbacks(self): + """ + Return list of this submodule's basis elements as elements of the + submodule's parent module. + """ + return [e.to_parent() for e in self.basis_elements()] + + def represent(self, elt): + """ + Represent a module element as an integer-linear combination over the + generators of this module. + + See Also + ======== + + .Module.represent + .PowerBasis.represent + + """ + if elt.module == self: + return elt.column() + elif elt.module == self.parent: + try: + # The given element should be a ZZ-linear combination over our + # basis vectors; however, due to the presence of denominators, + # we need to solve over QQ. + A = self.QQ_matrix + b = elt.QQ_col + x = A._solve(b)[0].transpose() + x = x.convert_to(ZZ) + except DMBadInputError: + raise ClosureFailure('Element outside QQ-span of this basis.') + except CoercionFailed: + raise ClosureFailure('Element in QQ-span but not ZZ-span of this basis.') + return x + elif isinstance(self.parent, Submodule): + coeffs_in_parent = self.parent.represent(elt) + parent_element = self.parent(coeffs_in_parent) + return self.represent(parent_element) + else: + raise ClosureFailure('Element outside ancestor chain of this module.') + + def is_compat_submodule(self, other): + return isinstance(other, Submodule) and other.parent == self.parent + + def __eq__(self, other): + if self.is_compat_submodule(other): + return other.QQ_matrix == self.QQ_matrix + return NotImplemented + + def add(self, other, hnf=True, hnf_modulus=None): + """ + Add this :py:class:`~.Submodule` to another. + + Explanation + =========== + + This represents the module generated by the union of the two modules' + sets of generators. + + Parameters + ========== + + other : :py:class:`~.Submodule` + hnf : boolean, optional (default=True) + If ``True``, reduce the matrix of the combined module to its + Hermite Normal Form. + hnf_modulus : :ref:`ZZ`, None, optional + If a positive integer is provided, use this as modulus in the + HNF reduction. See + :py:func:`~sympy.polys.matrices.normalforms.hermite_normal_form`. + + Returns + ======= + + :py:class:`~.Submodule` + + """ + d, e = self.denom, other.denom + m = ilcm(d, e) + a, b = m // d, m // e + B = (a * self.matrix).hstack(b * other.matrix) + if hnf: + B = hermite_normal_form(B, D=hnf_modulus) + return self.parent.submodule_from_matrix(B, denom=m) + + def __add__(self, other): + if self.is_compat_submodule(other): + return self.add(other) + return NotImplemented + + __radd__ = __add__ + + def mul(self, other, hnf=True, hnf_modulus=None): + """ + Multiply this :py:class:`~.Submodule` by a rational number, a + :py:class:`~.ModuleElement`, or another :py:class:`~.Submodule`. + + Explanation + =========== + + To multiply by a rational number or :py:class:`~.ModuleElement` means + to form the submodule whose generators are the products of this + quantity with all the generators of the present submodule. + + To multiply by another :py:class:`~.Submodule` means to form the + submodule whose generators are all the products of one generator from + the one submodule, and one generator from the other. + + Parameters + ========== + + other : int, :ref:`ZZ`, :ref:`QQ`, :py:class:`~.ModuleElement`, :py:class:`~.Submodule` + hnf : boolean, optional (default=True) + If ``True``, reduce the matrix of the product module to its + Hermite Normal Form. + hnf_modulus : :ref:`ZZ`, None, optional + If a positive integer is provided, use this as modulus in the + HNF reduction. See + :py:func:`~sympy.polys.matrices.normalforms.hermite_normal_form`. + + Returns + ======= + + :py:class:`~.Submodule` + + """ + if is_rat(other): + a, b = get_num_denom(other) + if a == b == 1: + return self + else: + return Submodule(self.parent, + self.matrix * a, denom=self.denom * b, + mult_tab=None).reduced() + elif isinstance(other, ModuleElement) and other.module == self.parent: + # The submodule is multiplied by an element of the parent module. + # We presume this means we want a new submodule of the parent module. + gens = [other * e for e in self.basis_element_pullbacks()] + return self.parent.submodule_from_gens(gens, hnf=hnf, hnf_modulus=hnf_modulus) + elif self.is_compat_submodule(other): + # This case usually means you're multiplying ideals, and want another + # ideal, i.e. another submodule of the same parent module. + alphas, betas = self.basis_element_pullbacks(), other.basis_element_pullbacks() + gens = [a * b for a in alphas for b in betas] + return self.parent.submodule_from_gens(gens, hnf=hnf, hnf_modulus=hnf_modulus) + return NotImplemented + + def __mul__(self, other): + return self.mul(other) + + __rmul__ = __mul__ + + def _first_power(self): + return self + + def reduce_element(self, elt): + r""" + If this submodule $B$ has defining matrix $W$ in square, maximal-rank + Hermite normal form, then, given an element $x$ of the parent module + $A$, we produce an element $y \in A$ such that $x - y \in B$, and the + $i$th coordinate of $y$ satisfies $0 \leq y_i < w_{i,i}$. This + representative $y$ is unique, in the sense that every element of + the coset $x + B$ reduces to it under this procedure. + + Explanation + =========== + + In the special case where $A$ is a power basis for a number field $K$, + and $B$ is a submodule representing an ideal $I$, this operation + represents one of a few important ways of reducing an element of $K$ + modulo $I$ to obtain a "small" representative. See [Cohen00]_ Section + 1.4.3. + + Examples + ======== + + >>> from sympy import QQ, Poly, symbols + >>> t = symbols('t') + >>> k = QQ.alg_field_from_poly(Poly(t**3 + t**2 - 2*t + 8)) + >>> Zk = k.maximal_order() + >>> A = Zk.parent + >>> B = (A(2) - 3*A(0))*Zk + >>> B.reduce_element(A(2)) + [3, 0, 0] + + Parameters + ========== + + elt : :py:class:`~.ModuleElement` + An element of this submodule's parent module. + + Returns + ======= + + elt : :py:class:`~.ModuleElement` + An element of this submodule's parent module. + + Raises + ====== + + NotImplementedError + If the given :py:class:`~.ModuleElement` does not belong to this + submodule's parent module. + StructureError + If this submodule's defining matrix is not in square, maximal-rank + Hermite normal form. + + References + ========== + + .. [Cohen00] Cohen, H. *Advanced Topics in Computational Number + Theory.* + + """ + if not elt.module == self.parent: + raise NotImplementedError + if not self.is_sq_maxrank_HNF(): + msg = "Reduction not implemented unless matrix square max-rank HNF" + raise StructureError(msg) + B = self.basis_element_pullbacks() + a = elt + for i in range(self.n - 1, -1, -1): + b = B[i] + q = a.coeffs[i]*b.denom // (b.coeffs[i]*a.denom) + a -= q*b + return a + + +def is_sq_maxrank_HNF(dm): + r""" + Say whether a :py:class:`~.DomainMatrix` is in that special case of Hermite + Normal Form, in which the matrix is also square and of maximal rank. + + Explanation + =========== + + We commonly work with :py:class:`~.Submodule` instances whose matrix is in + this form, and it can be useful to be able to check that this condition is + satisfied. + + For example this is the case with the :py:class:`~.Submodule` ``ZK`` + returned by :py:func:`~sympy.polys.numberfields.basis.round_two`, which + represents the maximal order in a number field, and with ideals formed + therefrom, such as ``2 * ZK``. + + """ + if dm.domain.is_ZZ and dm.is_square and dm.is_upper: + n = dm.shape[0] + for i in range(n): + d = dm[i, i].element + if d <= 0: + return False + for j in range(i + 1, n): + if not (0 <= dm[i, j].element < d): + return False + return True + return False + + +def make_mod_elt(module, col, denom=1): + r""" + Factory function which builds a :py:class:`~.ModuleElement`, but ensures + that it is a :py:class:`~.PowerBasisElement` if the module is a + :py:class:`~.PowerBasis`. + """ + if isinstance(module, PowerBasis): + return PowerBasisElement(module, col, denom=denom) + else: + return ModuleElement(module, col, denom=denom) + + +class ModuleElement(IntegerPowerable): + r""" + Represents an element of a :py:class:`~.Module`. + + NOTE: Should not be constructed directly. Use the + :py:meth:`~.Module.__call__` method or the :py:func:`make_mod_elt()` + factory function instead. + """ + + def __init__(self, module, col, denom=1): + """ + Parameters + ========== + + module : :py:class:`~.Module` + The module to which this element belongs. + col : :py:class:`~.DomainMatrix` over :ref:`ZZ` + Column vector giving the numerators of the coefficients of this + element. + denom : int, optional (default=1) + Denominator for the coefficients of this element. + + """ + self.module = module + self.col = col + self.denom = denom + self._QQ_col = None + + def __repr__(self): + r = str([int(c) for c in self.col.flat()]) + if self.denom > 1: + r += f'/{self.denom}' + return r + + def reduced(self): + """ + Produce a reduced version of this ModuleElement, i.e. one in which the + gcd of the denominator together with all numerator coefficients is 1. + """ + if self.denom == 1: + return self + g = igcd(self.denom, *self.coeffs) + if g == 1: + return self + return type(self)(self.module, + (self.col / g).convert_to(ZZ), + denom=self.denom // g) + + def reduced_mod_p(self, p): + """ + Produce a version of this :py:class:`~.ModuleElement` in which all + numerator coefficients have been reduced mod *p*. + """ + return make_mod_elt(self.module, + self.col.convert_to(FF(p)).convert_to(ZZ), + denom=self.denom) + + @classmethod + def from_int_list(cls, module, coeffs, denom=1): + """ + Make a :py:class:`~.ModuleElement` from a list of ints (instead of a + column vector). + """ + col = to_col(coeffs) + return cls(module, col, denom=denom) + + @property + def n(self): + """The length of this element's column.""" + return self.module.n + + def __len__(self): + return self.n + + def column(self, domain=None): + """ + Get a copy of this element's column, optionally converting to a domain. + """ + if domain is None: + return self.col.copy() + else: + return self.col.convert_to(domain) + + @property + def coeffs(self): + return self.col.flat() + + @property + def QQ_col(self): + """ + :py:class:`~.DomainMatrix` over :ref:`QQ`, equal to + ``self.col / self.denom``, and guaranteed to be dense. + + See Also + ======== + + .Submodule.QQ_matrix + + """ + if self._QQ_col is None: + self._QQ_col = (self.col / self.denom).to_dense() + return self._QQ_col + + def to_parent(self): + """ + Transform into a :py:class:`~.ModuleElement` belonging to the parent of + this element's module. + """ + if not isinstance(self.module, Submodule): + raise ValueError('Not an element of a Submodule.') + return make_mod_elt( + self.module.parent, self.module.matrix * self.col, + denom=self.module.denom * self.denom) + + def to_ancestor(self, anc): + """ + Transform into a :py:class:`~.ModuleElement` belonging to a given + ancestor of this element's module. + + Parameters + ========== + + anc : :py:class:`~.Module` + + """ + if anc == self.module: + return self + else: + return self.to_parent().to_ancestor(anc) + + def over_power_basis(self): + """ + Transform into a :py:class:`~.PowerBasisElement` over our + :py:class:`~.PowerBasis` ancestor. + """ + e = self + while not isinstance(e.module, PowerBasis): + e = e.to_parent() + return e + + def is_compat(self, other): + """ + Test whether other is another :py:class:`~.ModuleElement` with same + module. + """ + return isinstance(other, ModuleElement) and other.module == self.module + + def unify(self, other): + """ + Try to make a compatible pair of :py:class:`~.ModuleElement`, one + equivalent to this one, and one equivalent to the other. + + Explanation + =========== + + We search for the nearest common ancestor module for the pair of + elements, and represent each one there. + + Returns + ======= + + Pair ``(e1, e2)`` + Each ``ei`` is a :py:class:`~.ModuleElement`, they belong to the + same :py:class:`~.Module`, ``e1`` is equivalent to ``self``, and + ``e2`` is equivalent to ``other``. + + Raises + ====== + + UnificationFailed + If ``self`` and ``other`` have no common ancestor module. + + """ + if self.module == other.module: + return self, other + nca = self.module.nearest_common_ancestor(other.module) + if nca is not None: + return self.to_ancestor(nca), other.to_ancestor(nca) + raise UnificationFailed(f"Cannot unify {self} with {other}") + + def __eq__(self, other): + if self.is_compat(other): + return self.QQ_col == other.QQ_col + return NotImplemented + + def equiv(self, other): + """ + A :py:class:`~.ModuleElement` may test as equivalent to a rational + number or another :py:class:`~.ModuleElement`, if they represent the + same algebraic number. + + Explanation + =========== + + This method is intended to check equivalence only in those cases in + which it is easy to test; namely, when *other* is either a + :py:class:`~.ModuleElement` that can be unified with this one (i.e. one + which shares a common :py:class:`~.PowerBasis` ancestor), or else a + rational number (which is easy because every :py:class:`~.PowerBasis` + represents every rational number). + + Parameters + ========== + + other : int, :ref:`ZZ`, :ref:`QQ`, :py:class:`~.ModuleElement` + + Returns + ======= + + bool + + Raises + ====== + + UnificationFailed + If ``self`` and ``other`` do not share a common + :py:class:`~.PowerBasis` ancestor. + + """ + if self == other: + return True + elif isinstance(other, ModuleElement): + a, b = self.unify(other) + return a == b + elif is_rat(other): + if isinstance(self, PowerBasisElement): + return self == self.module(0) * other + else: + return self.over_power_basis().equiv(other) + return False + + def __add__(self, other): + """ + A :py:class:`~.ModuleElement` can be added to a rational number, or to + another :py:class:`~.ModuleElement`. + + Explanation + =========== + + When the other summand is a rational number, it will be converted into + a :py:class:`~.ModuleElement` (belonging to the first ancestor of this + module that starts with unity). + + In all cases, the sum belongs to the nearest common ancestor (NCA) of + the modules of the two summands. If the NCA does not exist, we return + ``NotImplemented``. + """ + if self.is_compat(other): + d, e = self.denom, other.denom + m = ilcm(d, e) + u, v = m // d, m // e + col = to_col([u * a + v * b for a, b in zip(self.coeffs, other.coeffs)]) + return type(self)(self.module, col, denom=m).reduced() + elif isinstance(other, ModuleElement): + try: + a, b = self.unify(other) + except UnificationFailed: + return NotImplemented + return a + b + elif is_rat(other): + return self + self.module.element_from_rational(other) + return NotImplemented + + __radd__ = __add__ + + def __neg__(self): + return self * -1 + + def __sub__(self, other): + return self + (-other) + + def __rsub__(self, other): + return -self + other + + def __mul__(self, other): + """ + A :py:class:`~.ModuleElement` can be multiplied by a rational number, + or by another :py:class:`~.ModuleElement`. + + Explanation + =========== + + When the multiplier is a rational number, the product is computed by + operating directly on the coefficients of this + :py:class:`~.ModuleElement`. + + When the multiplier is another :py:class:`~.ModuleElement`, the product + will belong to the nearest common ancestor (NCA) of the modules of the + two operands, and that NCA must have a multiplication table. If the NCA + does not exist, we return ``NotImplemented``. If the NCA does not have + a mult. table, ``ClosureFailure`` will be raised. + """ + if self.is_compat(other): + M = self.module.mult_tab() + A, B = self.col.flat(), other.col.flat() + n = self.n + C = [0] * n + for u in range(n): + for v in range(u, n): + c = A[u] * B[v] + if v > u: + c += A[v] * B[u] + if c != 0: + R = M[u][v] + for k in range(n): + C[k] += c * R[k] + d = self.denom * other.denom + return self.from_int_list(self.module, C, denom=d) + elif isinstance(other, ModuleElement): + try: + a, b = self.unify(other) + except UnificationFailed: + return NotImplemented + return a * b + elif is_rat(other): + a, b = get_num_denom(other) + if a == b == 1: + return self + else: + return make_mod_elt(self.module, + self.col * a, denom=self.denom * b).reduced() + return NotImplemented + + __rmul__ = __mul__ + + def _zeroth_power(self): + return self.module.one() + + def _first_power(self): + return self + + def __floordiv__(self, a): + if is_rat(a): + a = QQ(a) + return self * (1/a) + elif isinstance(a, ModuleElement): + return self * (1//a) + return NotImplemented + + def __rfloordiv__(self, a): + return a // self.over_power_basis() + + def __mod__(self, m): + r""" + Reduce this :py:class:`~.ModuleElement` mod a :py:class:`~.Submodule`. + + Parameters + ========== + + m : int, :ref:`ZZ`, :ref:`QQ`, :py:class:`~.Submodule` + If a :py:class:`~.Submodule`, reduce ``self`` relative to this. + If an integer or rational, reduce relative to the + :py:class:`~.Submodule` that is our own module times this constant. + + See Also + ======== + + .Submodule.reduce_element + + """ + if is_rat(m): + m = m * self.module.whole_submodule() + if isinstance(m, Submodule) and m.parent == self.module: + return m.reduce_element(self) + return NotImplemented + + +class PowerBasisElement(ModuleElement): + r""" + Subclass for :py:class:`~.ModuleElement` instances whose module is a + :py:class:`~.PowerBasis`. + """ + + @property + def T(self): + """Access the defining polynomial of the :py:class:`~.PowerBasis`.""" + return self.module.T + + def numerator(self, x=None): + """Obtain the numerator as a polynomial over :ref:`ZZ`.""" + x = x or self.T.gen + return Poly(reversed(self.coeffs), x, domain=ZZ) + + def poly(self, x=None): + """Obtain the number as a polynomial over :ref:`QQ`.""" + return self.numerator(x=x) // self.denom + + @property + def is_rational(self): + """Say whether this element represents a rational number.""" + return self.col[1:, :].is_zero_matrix + + @property + def generator(self): + """ + Return a :py:class:`~.Symbol` to be used when expressing this element + as a polynomial. + + If we have an associated :py:class:`~.AlgebraicField` whose primitive + element has an alias symbol, we use that. Otherwise we use the variable + of the minimal polynomial defining the power basis to which we belong. + """ + K = self.module.number_field + return K.ext.alias if K and K.ext.is_aliased else self.T.gen + + def as_expr(self, x=None): + """Create a Basic expression from ``self``. """ + return self.poly(x or self.generator).as_expr() + + def norm(self, T=None): + """Compute the norm of this number.""" + T = T or self.T + x = T.gen + A = self.numerator(x=x) + return T.resultant(A) // self.denom ** self.n + + def inverse(self): + f = self.poly() + f_inv = f.invert(self.T) + return self.module.element_from_poly(f_inv) + + def __rfloordiv__(self, a): + return self.inverse() * a + + def _negative_power(self, e, modulo=None): + return self.inverse() ** abs(e) + + def to_ANP(self): + """Convert to an equivalent :py:class:`~.ANP`. """ + return ANP(list(reversed(self.QQ_col.flat())), QQ.map(self.T.rep.to_list()), QQ) + + def to_alg_num(self): + """ + Try to convert to an equivalent :py:class:`~.AlgebraicNumber`. + + Explanation + =========== + + In general, the conversion from an :py:class:`~.AlgebraicNumber` to a + :py:class:`~.PowerBasisElement` throws away information, because an + :py:class:`~.AlgebraicNumber` specifies a complex embedding, while a + :py:class:`~.PowerBasisElement` does not. However, in some cases it is + possible to convert a :py:class:`~.PowerBasisElement` back into an + :py:class:`~.AlgebraicNumber`, namely when the associated + :py:class:`~.PowerBasis` has a reference to an + :py:class:`~.AlgebraicField`. + + Returns + ======= + + :py:class:`~.AlgebraicNumber` + + Raises + ====== + + StructureError + If the :py:class:`~.PowerBasis` to which this element belongs does + not have an associated :py:class:`~.AlgebraicField`. + + """ + K = self.module.number_field + if K: + return K.to_alg_num(self.to_ANP()) + raise StructureError("No associated AlgebraicField") + + +class ModuleHomomorphism: + r"""A homomorphism from one module to another.""" + + def __init__(self, domain, codomain, mapping): + r""" + Parameters + ========== + + domain : :py:class:`~.Module` + The domain of the mapping. + + codomain : :py:class:`~.Module` + The codomain of the mapping. + + mapping : callable + An arbitrary callable is accepted, but should be chosen so as + to represent an actual module homomorphism. In particular, should + accept elements of *domain* and return elements of *codomain*. + + Examples + ======== + + >>> from sympy import Poly, cyclotomic_poly + >>> from sympy.polys.numberfields.modules import PowerBasis, ModuleHomomorphism + >>> T = Poly(cyclotomic_poly(5)) + >>> A = PowerBasis(T) + >>> B = A.submodule_from_gens([2*A(j) for j in range(4)]) + >>> phi = ModuleHomomorphism(A, B, lambda x: 6*x) + >>> print(phi.matrix()) # doctest: +SKIP + DomainMatrix([[3, 0, 0, 0], [0, 3, 0, 0], [0, 0, 3, 0], [0, 0, 0, 3]], (4, 4), ZZ) + + """ + self.domain = domain + self.codomain = codomain + self.mapping = mapping + + def matrix(self, modulus=None): + r""" + Compute the matrix of this homomorphism. + + Parameters + ========== + + modulus : int, optional + A positive prime number $p$ if the matrix should be reduced mod + $p$. + + Returns + ======= + + :py:class:`~.DomainMatrix` + The matrix is over :ref:`ZZ`, or else over :ref:`GF(p)` if a + modulus was given. + + """ + basis = self.domain.basis_elements() + cols = [self.codomain.represent(self.mapping(elt)) for elt in basis] + if not cols: + return DomainMatrix.zeros((self.codomain.n, 0), ZZ).to_dense() + M = cols[0].hstack(*cols[1:]) + if modulus: + M = M.convert_to(FF(modulus)) + return M + + def kernel(self, modulus=None): + r""" + Compute a Submodule representing the kernel of this homomorphism. + + Parameters + ========== + + modulus : int, optional + A positive prime number $p$ if the kernel should be computed mod + $p$. + + Returns + ======= + + :py:class:`~.Submodule` + This submodule's generators span the kernel of this + homomorphism over :ref:`ZZ`, or else over :ref:`GF(p)` if a + modulus was given. + + """ + M = self.matrix(modulus=modulus) + if modulus is None: + M = M.convert_to(QQ) + # Note: Even when working over a finite field, what we want here is + # the pullback into the integers, so in this case the conversion to ZZ + # below is appropriate. When working over ZZ, the kernel should be a + # ZZ-submodule, so, while the conversion to QQ above was required in + # order for the nullspace calculation to work, conversion back to ZZ + # afterward should always work. + # TODO: + # Watch , which calls + # for fraction-free algorithms. If this is implemented, we can skip + # the conversion to `QQ` above. + K = M.nullspace().convert_to(ZZ).transpose() + return self.domain.submodule_from_matrix(K) + + +class ModuleEndomorphism(ModuleHomomorphism): + r"""A homomorphism from one module to itself.""" + + def __init__(self, domain, mapping): + r""" + Parameters + ========== + + domain : :py:class:`~.Module` + The common domain and codomain of the mapping. + + mapping : callable + An arbitrary callable is accepted, but should be chosen so as + to represent an actual module endomorphism. In particular, should + accept and return elements of *domain*. + + """ + super().__init__(domain, domain, mapping) + + +class InnerEndomorphism(ModuleEndomorphism): + r""" + An inner endomorphism on a module, i.e. the endomorphism corresponding to + multiplication by a fixed element. + """ + + def __init__(self, domain, multiplier): + r""" + Parameters + ========== + + domain : :py:class:`~.Module` + The domain and codomain of the endomorphism. + + multiplier : :py:class:`~.ModuleElement` + The element $a$ defining the mapping as $x \mapsto a x$. + + """ + super().__init__(domain, lambda x: multiplier * x) + self.multiplier = multiplier + + +class EndomorphismRing: + r"""The ring of endomorphisms on a module.""" + + def __init__(self, domain): + """ + Parameters + ========== + + domain : :py:class:`~.Module` + The domain and codomain of the endomorphisms. + + """ + self.domain = domain + + def inner_endomorphism(self, multiplier): + r""" + Form an inner endomorphism belonging to this endomorphism ring. + + Parameters + ========== + + multiplier : :py:class:`~.ModuleElement` + Element $a$ defining the inner endomorphism $x \mapsto a x$. + + Returns + ======= + + :py:class:`~.InnerEndomorphism` + + """ + return InnerEndomorphism(self.domain, multiplier) + + def represent(self, element): + r""" + Represent an element of this endomorphism ring, as a single column + vector. + + Explanation + =========== + + Let $M$ be a module, and $E$ its ring of endomorphisms. Let $N$ be + another module, and consider a homomorphism $\varphi: N \rightarrow E$. + In the event that $\varphi$ is to be represented by a matrix $A$, each + column of $A$ must represent an element of $E$. This is possible when + the elements of $E$ are themselves representable as matrices, by + stacking the columns of such a matrix into a single column. + + This method supports calculating such matrices $A$, by representing + an element of this endomorphism ring first as a matrix, and then + stacking that matrix's columns into a single column. + + Examples + ======== + + Note that in these examples we print matrix transposes, to make their + columns easier to inspect. + + >>> from sympy import Poly, cyclotomic_poly + >>> from sympy.polys.numberfields.modules import PowerBasis + >>> from sympy.polys.numberfields.modules import ModuleHomomorphism + >>> T = Poly(cyclotomic_poly(5)) + >>> M = PowerBasis(T) + >>> E = M.endomorphism_ring() + + Let $\zeta$ be a primitive 5th root of unity, a generator of our field, + and consider the inner endomorphism $\tau$ on the ring of integers, + induced by $\zeta$: + + >>> zeta = M(1) + >>> tau = E.inner_endomorphism(zeta) + >>> tau.matrix().transpose() # doctest: +SKIP + DomainMatrix( + [[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [-1, -1, -1, -1]], + (4, 4), ZZ) + + The matrix representation of $\tau$ is as expected. The first column + shows that multiplying by $\zeta$ carries $1$ to $\zeta$, the second + column that it carries $\zeta$ to $\zeta^2$, and so forth. + + The ``represent`` method of the endomorphism ring ``E`` stacks these + into a single column: + + >>> E.represent(tau).transpose() # doctest: +SKIP + DomainMatrix( + [[0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1]], + (1, 16), ZZ) + + This is useful when we want to consider a homomorphism $\varphi$ having + ``E`` as codomain: + + >>> phi = ModuleHomomorphism(M, E, lambda x: E.inner_endomorphism(x)) + + and we want to compute the matrix of such a homomorphism: + + >>> phi.matrix().transpose() # doctest: +SKIP + DomainMatrix( + [[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1], + [0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1, 1, 0, 0, 0], + [0, 0, 0, 1, -1, -1, -1, -1, 1, 0, 0, 0, 0, 1, 0, 0]], + (4, 16), ZZ) + + Note that the stacked matrix of $\tau$ occurs as the second column in + this example. This is because $\zeta$ is the second basis element of + ``M``, and $\varphi(\zeta) = \tau$. + + Parameters + ========== + + element : :py:class:`~.ModuleEndomorphism` belonging to this ring. + + Returns + ======= + + :py:class:`~.DomainMatrix` + Column vector equalling the vertical stacking of all the columns + of the matrix that represents the given *element* as a mapping. + + """ + if isinstance(element, ModuleEndomorphism) and element.domain == self.domain: + M = element.matrix() + # Transform the matrix into a single column, which should reproduce + # the original columns, one after another. + m, n = M.shape + if n == 0: + return M + return M[:, 0].vstack(*[M[:, j] for j in range(1, n)]) + raise NotImplementedError + + +def find_min_poly(alpha, domain, x=None, powers=None): + r""" + Find a polynomial of least degree (not necessarily irreducible) satisfied + by an element of a finitely-generated ring with unity. + + Examples + ======== + + For the $n$th cyclotomic field, $n$ an odd prime, consider the quadratic + equation whose roots are the two periods of length $(n-1)/2$. Article 356 + of Gauss tells us that we should get $x^2 + x - (n-1)/4$ or + $x^2 + x + (n+1)/4$ according to whether $n$ is 1 or 3 mod 4, respectively. + + >>> from sympy import Poly, cyclotomic_poly, primitive_root, QQ + >>> from sympy.abc import x + >>> from sympy.polys.numberfields.modules import PowerBasis, find_min_poly + >>> n = 13 + >>> g = primitive_root(n) + >>> C = PowerBasis(Poly(cyclotomic_poly(n, x))) + >>> ee = [g**(2*k+1) % n for k in range((n-1)//2)] + >>> eta = sum(C(e) for e in ee) + >>> print(find_min_poly(eta, QQ, x=x).as_expr()) + x**2 + x - 3 + >>> n = 19 + >>> g = primitive_root(n) + >>> C = PowerBasis(Poly(cyclotomic_poly(n, x))) + >>> ee = [g**(2*k+2) % n for k in range((n-1)//2)] + >>> eta = sum(C(e) for e in ee) + >>> print(find_min_poly(eta, QQ, x=x).as_expr()) + x**2 + x + 5 + + Parameters + ========== + + alpha : :py:class:`~.ModuleElement` + The element whose min poly is to be found, and whose module has + multiplication and starts with unity. + + domain : :py:class:`~.Domain` + The desired domain of the polynomial. + + x : :py:class:`~.Symbol`, optional + The desired variable for the polynomial. + + powers : list, optional + If desired, pass an empty list. The powers of *alpha* (as + :py:class:`~.ModuleElement` instances) from the zeroth up to the degree + of the min poly will be recorded here, as we compute them. + + Returns + ======= + + :py:class:`~.Poly`, ``None`` + The minimal polynomial for alpha, or ``None`` if no polynomial could be + found over the desired domain. + + Raises + ====== + + MissingUnityError + If the module to which alpha belongs does not start with unity. + ClosureFailure + If the module to which alpha belongs is not closed under + multiplication. + + """ + R = alpha.module + if not R.starts_with_unity(): + raise MissingUnityError("alpha must belong to finitely generated ring with unity.") + if powers is None: + powers = [] + one = R(0) + powers.append(one) + powers_matrix = one.column(domain=domain) + ak = alpha + m = None + for k in range(1, R.n + 1): + powers.append(ak) + ak_col = ak.column(domain=domain) + try: + X = powers_matrix._solve(ak_col)[0] + except DMBadInputError: + # This means alpha^k still isn't in the domain-span of the lower powers. + powers_matrix = powers_matrix.hstack(ak_col) + ak *= alpha + else: + # alpha^k is in the domain-span of the lower powers, so we have found a + # minimal-degree poly for alpha. + coeffs = [1] + [-c for c in reversed(X.to_list_flat())] + x = x or Dummy('x') + if domain.is_FF: + m = Poly(coeffs, x, modulus=domain.mod) + else: + m = Poly(coeffs, x, domain=domain) + break + return m diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/primes.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/primes.py new file mode 100644 index 0000000000000000000000000000000000000000..8f28f13d94f33ed59cded8eabd05e9cf7d0f103f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/primes.py @@ -0,0 +1,784 @@ +"""Prime ideals in number fields. """ + +from sympy.polys.polytools import Poly +from sympy.polys.domains.finitefield import FF +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.domains.integerring import ZZ +from sympy.polys.matrices.domainmatrix import DomainMatrix +from sympy.polys.polyerrors import CoercionFailed +from sympy.polys.polyutils import IntegerPowerable +from sympy.utilities.decorator import public +from .basis import round_two, nilradical_mod_p +from .exceptions import StructureError +from .modules import ModuleEndomorphism, find_min_poly +from .utilities import coeff_search, supplement_a_subspace + + +def _check_formal_conditions_for_maximal_order(submodule): + r""" + Several functions in this module accept an argument which is to be a + :py:class:`~.Submodule` representing the maximal order in a number field, + such as returned by the :py:func:`~sympy.polys.numberfields.basis.round_two` + algorithm. + + We do not attempt to check that the given ``Submodule`` actually represents + a maximal order, but we do check a basic set of formal conditions that the + ``Submodule`` must satisfy, at a minimum. The purpose is to catch an + obviously ill-formed argument. + """ + prefix = 'The submodule representing the maximal order should ' + cond = None + if not submodule.is_power_basis_submodule(): + cond = 'be a direct submodule of a power basis.' + elif not submodule.starts_with_unity(): + cond = 'have 1 as its first generator.' + elif not submodule.is_sq_maxrank_HNF(): + cond = 'have square matrix, of maximal rank, in Hermite Normal Form.' + if cond is not None: + raise StructureError(prefix + cond) + + +class PrimeIdeal(IntegerPowerable): + r""" + A prime ideal in a ring of algebraic integers. + """ + + def __init__(self, ZK, p, alpha, f, e=None): + """ + Parameters + ========== + + ZK : :py:class:`~.Submodule` + The maximal order where this ideal lives. + p : int + The rational prime this ideal divides. + alpha : :py:class:`~.PowerBasisElement` + Such that the ideal is equal to ``p*ZK + alpha*ZK``. + f : int + The inertia degree. + e : int, ``None``, optional + The ramification index, if already known. If ``None``, we will + compute it here. + + """ + _check_formal_conditions_for_maximal_order(ZK) + self.ZK = ZK + self.p = p + self.alpha = alpha + self.f = f + self._test_factor = None + self.e = e if e is not None else self.valuation(p * ZK) + + def __str__(self): + if self.is_inert: + return f'({self.p})' + return f'({self.p}, {self.alpha.as_expr()})' + + @property + def is_inert(self): + """ + Say whether the rational prime we divide is inert, i.e. stays prime in + our ring of integers. + """ + return self.f == self.ZK.n + + def repr(self, field_gen=None, just_gens=False): + """ + Print a representation of this prime ideal. + + Examples + ======== + + >>> from sympy import cyclotomic_poly, QQ + >>> from sympy.abc import x, zeta + >>> T = cyclotomic_poly(7, x) + >>> K = QQ.algebraic_field((T, zeta)) + >>> P = K.primes_above(11) + >>> print(P[0].repr()) + [ (11, x**3 + 5*x**2 + 4*x - 1) e=1, f=3 ] + >>> print(P[0].repr(field_gen=zeta)) + [ (11, zeta**3 + 5*zeta**2 + 4*zeta - 1) e=1, f=3 ] + >>> print(P[0].repr(field_gen=zeta, just_gens=True)) + (11, zeta**3 + 5*zeta**2 + 4*zeta - 1) + + Parameters + ========== + + field_gen : :py:class:`~.Symbol`, ``None``, optional (default=None) + The symbol to use for the generator of the field. This will appear + in our representation of ``self.alpha``. If ``None``, we use the + variable of the defining polynomial of ``self.ZK``. + just_gens : bool, optional (default=False) + If ``True``, just print the "(p, alpha)" part, showing "just the + generators" of the prime ideal. Otherwise, print a string of the + form "[ (p, alpha) e=..., f=... ]", giving the ramification index + and inertia degree, along with the generators. + + """ + field_gen = field_gen or self.ZK.parent.T.gen + p, alpha, e, f = self.p, self.alpha, self.e, self.f + alpha_rep = str(alpha.numerator(x=field_gen).as_expr()) + if alpha.denom > 1: + alpha_rep = f'({alpha_rep})/{alpha.denom}' + gens = f'({p}, {alpha_rep})' + if just_gens: + return gens + return f'[ {gens} e={e}, f={f} ]' + + def __repr__(self): + return self.repr() + + def as_submodule(self): + r""" + Represent this prime ideal as a :py:class:`~.Submodule`. + + Explanation + =========== + + The :py:class:`~.PrimeIdeal` class serves to bundle information about + a prime ideal, such as its inertia degree, ramification index, and + two-generator representation, as well as to offer helpful methods like + :py:meth:`~.PrimeIdeal.valuation` and + :py:meth:`~.PrimeIdeal.test_factor`. + + However, in order to be added and multiplied by other ideals or + rational numbers, it must first be converted into a + :py:class:`~.Submodule`, which is a class that supports these + operations. + + In many cases, the user need not perform this conversion deliberately, + since it is automatically performed by the arithmetic operator methods + :py:meth:`~.PrimeIdeal.__add__` and :py:meth:`~.PrimeIdeal.__mul__`. + + Raising a :py:class:`~.PrimeIdeal` to a non-negative integer power is + also supported. + + Examples + ======== + + >>> from sympy import Poly, cyclotomic_poly, prime_decomp + >>> T = Poly(cyclotomic_poly(7)) + >>> P0 = prime_decomp(7, T)[0] + >>> print(P0**6 == 7*P0.ZK) + True + + Note that, on both sides of the equation above, we had a + :py:class:`~.Submodule`. In the next equation we recall that adding + ideals yields their GCD. This time, we need a deliberate conversion + to :py:class:`~.Submodule` on the right: + + >>> print(P0 + 7*P0.ZK == P0.as_submodule()) + True + + Returns + ======= + + :py:class:`~.Submodule` + Will be equal to ``self.p * self.ZK + self.alpha * self.ZK``. + + See Also + ======== + + __add__ + __mul__ + + """ + M = self.p * self.ZK + self.alpha * self.ZK + # Pre-set expensive boolean properties whose value we already know: + M._starts_with_unity = False + M._is_sq_maxrank_HNF = True + return M + + def __eq__(self, other): + if isinstance(other, PrimeIdeal): + return self.as_submodule() == other.as_submodule() + return NotImplemented + + def __add__(self, other): + """ + Convert to a :py:class:`~.Submodule` and add to another + :py:class:`~.Submodule`. + + See Also + ======== + + as_submodule + + """ + return self.as_submodule() + other + + __radd__ = __add__ + + def __mul__(self, other): + """ + Convert to a :py:class:`~.Submodule` and multiply by another + :py:class:`~.Submodule` or a rational number. + + See Also + ======== + + as_submodule + + """ + return self.as_submodule() * other + + __rmul__ = __mul__ + + def _zeroth_power(self): + return self.ZK + + def _first_power(self): + return self + + def test_factor(self): + r""" + Compute a test factor for this prime ideal. + + Explanation + =========== + + Write $\mathfrak{p}$ for this prime ideal, $p$ for the rational prime + it divides. Then, for computing $\mathfrak{p}$-adic valuations it is + useful to have a number $\beta \in \mathbb{Z}_K$ such that + $p/\mathfrak{p} = p \mathbb{Z}_K + \beta \mathbb{Z}_K$. + + Essentially, this is the same as the number $\Psi$ (or the "reagent") + from Kummer's 1847 paper (*Ueber die Zerlegung...*, Crelle vol. 35) in + which ideal divisors were invented. + """ + if self._test_factor is None: + self._test_factor = _compute_test_factor(self.p, [self.alpha], self.ZK) + return self._test_factor + + def valuation(self, I): + r""" + Compute the $\mathfrak{p}$-adic valuation of integral ideal I at this + prime ideal. + + Parameters + ========== + + I : :py:class:`~.Submodule` + + See Also + ======== + + prime_valuation + + """ + return prime_valuation(I, self) + + def reduce_element(self, elt): + """ + Reduce a :py:class:`~.PowerBasisElement` to a "small representative" + modulo this prime ideal. + + Parameters + ========== + + elt : :py:class:`~.PowerBasisElement` + The element to be reduced. + + Returns + ======= + + :py:class:`~.PowerBasisElement` + The reduced element. + + See Also + ======== + + reduce_ANP + reduce_alg_num + .Submodule.reduce_element + + """ + return self.as_submodule().reduce_element(elt) + + def reduce_ANP(self, a): + """ + Reduce an :py:class:`~.ANP` to a "small representative" modulo this + prime ideal. + + Parameters + ========== + + elt : :py:class:`~.ANP` + The element to be reduced. + + Returns + ======= + + :py:class:`~.ANP` + The reduced element. + + See Also + ======== + + reduce_element + reduce_alg_num + .Submodule.reduce_element + + """ + elt = self.ZK.parent.element_from_ANP(a) + red = self.reduce_element(elt) + return red.to_ANP() + + def reduce_alg_num(self, a): + """ + Reduce an :py:class:`~.AlgebraicNumber` to a "small representative" + modulo this prime ideal. + + Parameters + ========== + + elt : :py:class:`~.AlgebraicNumber` + The element to be reduced. + + Returns + ======= + + :py:class:`~.AlgebraicNumber` + The reduced element. + + See Also + ======== + + reduce_element + reduce_ANP + .Submodule.reduce_element + + """ + elt = self.ZK.parent.element_from_alg_num(a) + red = self.reduce_element(elt) + return a.field_element(list(reversed(red.QQ_col.flat()))) + + +def _compute_test_factor(p, gens, ZK): + r""" + Compute the test factor for a :py:class:`~.PrimeIdeal` $\mathfrak{p}$. + + Parameters + ========== + + p : int + The rational prime $\mathfrak{p}$ divides + + gens : list of :py:class:`PowerBasisElement` + A complete set of generators for $\mathfrak{p}$ over *ZK*, EXCEPT that + an element equivalent to rational *p* can and should be omitted (since + it has no effect except to waste time). + + ZK : :py:class:`~.Submodule` + The maximal order where the prime ideal $\mathfrak{p}$ lives. + + Returns + ======= + + :py:class:`~.PowerBasisElement` + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Proposition 4.8.15.) + + """ + _check_formal_conditions_for_maximal_order(ZK) + E = ZK.endomorphism_ring() + matrices = [E.inner_endomorphism(g).matrix(modulus=p) for g in gens] + B = DomainMatrix.zeros((0, ZK.n), FF(p)).vstack(*matrices) + # A nonzero element of the nullspace of B will represent a + # lin comb over the omegas which (i) is not a multiple of p + # (since it is nonzero over FF(p)), while (ii) is such that + # its product with each g in gens _is_ a multiple of p (since + # B represents multiplication by these generators). Theory + # predicts that such an element must exist, so nullspace should + # be non-trivial. + x = B.nullspace()[0, :].transpose() + beta = ZK.parent(ZK.matrix * x.convert_to(ZZ), denom=ZK.denom) + return beta + + +@public +def prime_valuation(I, P): + r""" + Compute the *P*-adic valuation for an integral ideal *I*. + + Examples + ======== + + >>> from sympy import QQ + >>> from sympy.polys.numberfields import prime_valuation + >>> K = QQ.cyclotomic_field(5) + >>> P = K.primes_above(5) + >>> ZK = K.maximal_order() + >>> print(prime_valuation(25*ZK, P[0])) + 8 + + Parameters + ========== + + I : :py:class:`~.Submodule` + An integral ideal whose valuation is desired. + + P : :py:class:`~.PrimeIdeal` + The prime at which to compute the valuation. + + Returns + ======= + + int + + See Also + ======== + + .PrimeIdeal.valuation + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Algorithm 4.8.17.) + + """ + p, ZK = P.p, P.ZK + n, W, d = ZK.n, ZK.matrix, ZK.denom + + A = W.convert_to(QQ).inv() * I.matrix * d / I.denom + # Although A must have integer entries, given that I is an integral ideal, + # as a DomainMatrix it will still be over QQ, so we convert back: + A = A.convert_to(ZZ) + D = A.det() + if D % p != 0: + return 0 + + beta = P.test_factor() + + f = d ** n // W.det() + need_complete_test = (f % p == 0) + v = 0 + while True: + # Entering the loop, the cols of A represent lin combs of omegas. + # Turn them into lin combs of thetas: + A = W * A + # And then one column at a time... + for j in range(n): + c = ZK.parent(A[:, j], denom=d) + c *= beta + # ...turn back into lin combs of omegas, after multiplying by beta: + c = ZK.represent(c).flat() + for i in range(n): + A[i, j] = c[i] + if A[n - 1, n - 1].element % p != 0: + break + A = A / p + # As noted above, domain converts to QQ even when division goes evenly. + # So must convert back, even when we don't "need_complete_test". + if need_complete_test: + # In this case, having a non-integer entry is actually just our + # halting condition. + try: + A = A.convert_to(ZZ) + except CoercionFailed: + break + else: + # In this case theory says we should not have any non-integer entries. + A = A.convert_to(ZZ) + v += 1 + return v + + +def _two_elt_rep(gens, ZK, p, f=None, Np=None): + r""" + Given a set of *ZK*-generators of a prime ideal, compute a set of just two + *ZK*-generators for the same ideal, one of which is *p* itself. + + Parameters + ========== + + gens : list of :py:class:`PowerBasisElement` + Generators for the prime ideal over *ZK*, the ring of integers of the + field $K$. + + ZK : :py:class:`~.Submodule` + The maximal order in $K$. + + p : int + The rational prime divided by the prime ideal. + + f : int, optional + The inertia degree of the prime ideal, if known. + + Np : int, optional + The norm $p^f$ of the prime ideal, if known. + NOTE: There is no reason to supply both *f* and *Np*. Either one will + save us from having to compute the norm *Np* ourselves. If both are known, + *Np* is preferred since it saves one exponentiation. + + Returns + ======= + + :py:class:`~.PowerBasisElement` representing a single algebraic integer + alpha such that the prime ideal is equal to ``p*ZK + alpha*ZK``. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Algorithm 4.7.10.) + + """ + _check_formal_conditions_for_maximal_order(ZK) + pb = ZK.parent + T = pb.T + # Detect the special cases in which either (a) all generators are multiples + # of p, or (b) there are no generators (so `all` is vacuously true): + if all((g % p).equiv(0) for g in gens): + return pb.zero() + + if Np is None: + if f is not None: + Np = p**f + else: + Np = abs(pb.submodule_from_gens(gens).matrix.det()) + + omega = ZK.basis_element_pullbacks() + beta = [p*om for om in omega[1:]] # note: we omit omega[0] == 1 + beta += gens + search = coeff_search(len(beta), 1) + for c in search: + alpha = sum(ci*betai for ci, betai in zip(c, beta)) + # Note: It may be tempting to reduce alpha mod p here, to try to work + # with smaller numbers, but must not do that, as it can result in an + # infinite loop! E.g. try factoring 2 in Q(sqrt(-7)). + n = alpha.norm(T) // Np + if n % p != 0: + # Now can reduce alpha mod p. + return alpha % p + + +def _prime_decomp_easy_case(p, ZK): + r""" + Compute the decomposition of rational prime *p* in the ring of integers + *ZK* (given as a :py:class:`~.Submodule`), in the "easy case", i.e. the + case where *p* does not divide the index of $\theta$ in *ZK*, where + $\theta$ is the generator of the ``PowerBasis`` of which *ZK* is a + ``Submodule``. + """ + T = ZK.parent.T + T_bar = Poly(T, modulus=p) + lc, fl = T_bar.factor_list() + if len(fl) == 1 and fl[0][1] == 1: + return [PrimeIdeal(ZK, p, ZK.parent.zero(), ZK.n, 1)] + return [PrimeIdeal(ZK, p, + ZK.parent.element_from_poly(Poly(t, domain=ZZ)), + t.degree(), e) + for t, e in fl] + + +def _prime_decomp_compute_kernel(I, p, ZK): + r""" + Parameters + ========== + + I : :py:class:`~.Module` + An ideal of ``ZK/pZK``. + p : int + The rational prime being factored. + ZK : :py:class:`~.Submodule` + The maximal order. + + Returns + ======= + + Pair ``(N, G)``, where: + + ``N`` is a :py:class:`~.Module` representing the kernel of the map + ``a |--> a**p - a`` on ``(O/pO)/I``, guaranteed to be a module with + unity. + + ``G`` is a :py:class:`~.Module` representing a basis for the separable + algebra ``A = O/I`` (see Cohen). + + """ + W = I.matrix + n, r = W.shape + # Want to take the Fp-basis given by the columns of I, adjoin (1, 0, ..., 0) + # (which we know is not already in there since I is a basis for a prime ideal) + # and then supplement this with additional columns to make an invertible n x n + # matrix. This will then represent a full basis for ZK, whose first r columns + # are pullbacks of the basis for I. + if r == 0: + B = W.eye(n, ZZ) + else: + B = W.hstack(W.eye(n, ZZ)[:, 0]) + if B.shape[1] < n: + B = supplement_a_subspace(B.convert_to(FF(p))).convert_to(ZZ) + + G = ZK.submodule_from_matrix(B) + # Must compute G's multiplication table _before_ discarding the first r + # columns. (See Step 9 in Alg 6.2.9 in Cohen, where the betas are actually + # needed in order to represent each product of gammas. However, once we've + # found the representations, then we can ignore the betas.) + G.compute_mult_tab() + G = G.discard_before(r) + + phi = ModuleEndomorphism(G, lambda x: x**p - x) + N = phi.kernel(modulus=p) + assert N.starts_with_unity() + return N, G + + +def _prime_decomp_maximal_ideal(I, p, ZK): + r""" + We have reached the case where we have a maximal (hence prime) ideal *I*, + which we know because the quotient ``O/I`` is a field. + + Parameters + ========== + + I : :py:class:`~.Module` + An ideal of ``O/pO``. + p : int + The rational prime being factored. + ZK : :py:class:`~.Submodule` + The maximal order. + + Returns + ======= + + :py:class:`~.PrimeIdeal` instance representing this prime + + """ + m, n = I.matrix.shape + f = m - n + G = ZK.matrix * I.matrix + gens = [ZK.parent(G[:, j], denom=ZK.denom) for j in range(G.shape[1])] + alpha = _two_elt_rep(gens, ZK, p, f=f) + return PrimeIdeal(ZK, p, alpha, f) + + +def _prime_decomp_split_ideal(I, p, N, G, ZK): + r""" + Perform the step in the prime decomposition algorithm where we have determined + the quotient ``ZK/I`` is _not_ a field, and we want to perform a non-trivial + factorization of *I* by locating an idempotent element of ``ZK/I``. + """ + assert I.parent == ZK and G.parent is ZK and N.parent is G + # Since ZK/I is not a field, the kernel computed in the previous step contains + # more than just the prime field Fp, and our basis N for the nullspace therefore + # contains at least a second column (which represents an element outside Fp). + # Let alpha be such an element: + alpha = N(1).to_parent() + assert alpha.module is G + + alpha_powers = [] + m = find_min_poly(alpha, FF(p), powers=alpha_powers) + # TODO (future work): + # We don't actually need full factorization, so might use a faster method + # to just break off a single non-constant factor m1? + lc, fl = m.factor_list() + m1 = fl[0][0] + m2 = m.quo(m1) + U, V, g = m1.gcdex(m2) + # Sanity check: theory says m is squarefree, so m1, m2 should be coprime: + assert g == 1 + E = list(reversed(Poly(U * m1, domain=ZZ).rep.to_list())) + eps1 = sum(E[i]*alpha_powers[i] for i in range(len(E))) + eps2 = 1 - eps1 + idemps = [eps1, eps2] + factors = [] + for eps in idemps: + e = eps.to_parent() + assert e.module is ZK + D = I.matrix.convert_to(FF(p)).hstack(*[ + (e * om).column(domain=FF(p)) for om in ZK.basis_elements() + ]) + W = D.columnspace().convert_to(ZZ) + H = ZK.submodule_from_matrix(W) + factors.append(H) + return factors + + +@public +def prime_decomp(p, T=None, ZK=None, dK=None, radical=None): + r""" + Compute the decomposition of rational prime *p* in a number field. + + Explanation + =========== + + Ordinarily this should be accessed through the + :py:meth:`~.AlgebraicField.primes_above` method of an + :py:class:`~.AlgebraicField`. + + Examples + ======== + + >>> from sympy import Poly, QQ + >>> from sympy.abc import x, theta + >>> T = Poly(x ** 3 + x ** 2 - 2 * x + 8) + >>> K = QQ.algebraic_field((T, theta)) + >>> print(K.primes_above(2)) + [[ (2, x**2 + 1) e=1, f=1 ], [ (2, (x**2 + 3*x + 2)/2) e=1, f=1 ], + [ (2, (3*x**2 + 3*x)/2) e=1, f=1 ]] + + Parameters + ========== + + p : int + The rational prime whose decomposition is desired. + + T : :py:class:`~.Poly`, optional + Monic irreducible polynomial defining the number field $K$ in which to + factor. NOTE: at least one of *T* or *ZK* must be provided. + + ZK : :py:class:`~.Submodule`, optional + The maximal order for $K$, if already known. + NOTE: at least one of *T* or *ZK* must be provided. + + dK : int, optional + The discriminant of the field $K$, if already known. + + radical : :py:class:`~.Submodule`, optional + The nilradical mod *p* in the integers of $K$, if already known. + + Returns + ======= + + List of :py:class:`~.PrimeIdeal` instances. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Algorithm 6.2.9.) + + """ + if T is None and ZK is None: + raise ValueError('At least one of T or ZK must be provided.') + if ZK is not None: + _check_formal_conditions_for_maximal_order(ZK) + if T is None: + T = ZK.parent.T + radicals = {} + if dK is None or ZK is None: + ZK, dK = round_two(T, radicals=radicals) + dT = T.discriminant() + f_squared = dT // dK + if f_squared % p != 0: + return _prime_decomp_easy_case(p, ZK) + radical = radical or radicals.get(p) or nilradical_mod_p(ZK, p) + stack = [radical] + primes = [] + while stack: + I = stack.pop() + N, G = _prime_decomp_compute_kernel(I, p, ZK) + if N.n == 1: + P = _prime_decomp_maximal_ideal(I, p, ZK) + primes.append(P) + else: + I1, I2 = _prime_decomp_split_ideal(I, p, N, G, ZK) + stack.extend([I1, I2]) + return primes diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/resolvent_lookup.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/resolvent_lookup.py new file mode 100644 index 0000000000000000000000000000000000000000..71812c0d7aec6501039eefe4f3602b1916628071 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/resolvent_lookup.py @@ -0,0 +1,456 @@ +"""Lookup table for Galois resolvents for polys of degree 4 through 6. """ +# This table was generated by a call to +# `sympy.polys.numberfields.galois_resolvents.generate_lambda_lookup()`. +# The entire job took 543.23s. +# Of this, Case (6, 1) took 539.03s. +# The final polynomial of Case (6, 1) alone took 455.09s. +resolvent_coeff_lambdas = { + (4, 0): [ + lambda s1, s2, s3, s4: (-2*s1*s2 + 6*s3), + lambda s1, s2, s3, s4: (2*s1**3*s3 + s1**2*s2**2 + s1**2*s4 - 17*s1*s2*s3 + 2*s2**3 - 8*s2*s4 + 24*s3**2), + lambda s1, s2, s3, s4: (-2*s1**5*s4 - 2*s1**4*s2*s3 + 10*s1**3*s2*s4 + 8*s1**3*s3**2 + 10*s1**2*s2**2*s3 - +12*s1**2*s3*s4 - 2*s1*s2**4 - 54*s1*s2*s3**2 + 32*s1*s4**2 + 8*s2**3*s3 - 32*s2*s3*s4 ++ 56*s3**3), + lambda s1, s2, s3, s4: (2*s1**6*s2*s4 + s1**6*s3**2 - 5*s1**5*s3*s4 - 11*s1**4*s2**2*s4 - 13*s1**4*s2*s3**2 ++ 7*s1**4*s4**2 + 3*s1**3*s2**3*s3 + 30*s1**3*s2*s3*s4 + 22*s1**3*s3**3 + 10*s1**2*s2**3*s4 ++ 33*s1**2*s2**2*s3**2 - 72*s1**2*s2*s4**2 - 36*s1**2*s3**2*s4 - 13*s1*s2**4*s3 + +48*s1*s2**2*s3*s4 - 116*s1*s2*s3**3 + 144*s1*s3*s4**2 + s2**6 - 12*s2**4*s4 + 22*s2**3*s3**2 ++ 48*s2**2*s4**2 - 120*s2*s3**2*s4 + 96*s3**4 - 64*s4**3), + lambda s1, s2, s3, s4: (-2*s1**8*s3*s4 - s1**7*s4**2 + 22*s1**6*s2*s3*s4 + 2*s1**6*s3**3 - 2*s1**5*s2**3*s4 +- s1**5*s2**2*s3**2 - 29*s1**5*s3**2*s4 - 60*s1**4*s2**2*s3*s4 - 19*s1**4*s2*s3**3 ++ 38*s1**4*s3*s4**2 + 9*s1**3*s2**4*s4 + 10*s1**3*s2**3*s3**2 + 24*s1**3*s2**2*s4**2 ++ 134*s1**3*s2*s3**2*s4 + 28*s1**3*s3**4 + 16*s1**3*s4**3 - s1**2*s2**5*s3 - 4*s1**2*s2**3*s3*s4 ++ 34*s1**2*s2**2*s3**3 - 288*s1**2*s2*s3*s4**2 - 104*s1**2*s3**3*s4 - 19*s1*s2**4*s3**2 ++ 120*s1*s2**2*s3**2*s4 - 128*s1*s2*s3**4 + 336*s1*s3**2*s4**2 + 2*s2**6*s3 - 24*s2**4*s3*s4 ++ 28*s2**3*s3**3 + 96*s2**2*s3*s4**2 - 176*s2*s3**3*s4 + 96*s3**5 - 128*s3*s4**3), + lambda s1, s2, s3, s4: (s1**10*s4**2 - 11*s1**8*s2*s4**2 - 2*s1**8*s3**2*s4 + s1**7*s2**2*s3*s4 + 15*s1**7*s3*s4**2 ++ 45*s1**6*s2**2*s4**2 + 17*s1**6*s2*s3**2*s4 + s1**6*s3**4 - 5*s1**6*s4**3 - 12*s1**5*s2**3*s3*s4 +- 133*s1**5*s2*s3*s4**2 - 22*s1**5*s3**3*s4 + s1**4*s2**5*s4 - 76*s1**4*s2**3*s4**2 +- 6*s1**4*s2**2*s3**2*s4 - 12*s1**4*s2*s3**4 + 32*s1**4*s2*s4**3 + 128*s1**4*s3**2*s4**2 ++ 29*s1**3*s2**4*s3*s4 + 2*s1**3*s2**3*s3**3 + 344*s1**3*s2**2*s3*s4**2 + 48*s1**3*s2*s3**3*s4 ++ 16*s1**3*s3**5 - 48*s1**3*s3*s4**3 - 4*s1**2*s2**6*s4 + 32*s1**2*s2**4*s4**2 - 134*s1**2*s2**3*s3**2*s4 ++ 36*s1**2*s2**2*s3**4 - 64*s1**2*s2**2*s4**3 - 648*s1**2*s2*s3**2*s4**2 - 48*s1**2*s3**4*s4 ++ 16*s1*s2**5*s3*s4 - 12*s1*s2**4*s3**3 - 128*s1*s2**3*s3*s4**2 + 296*s1*s2**2*s3**3*s4 +- 96*s1*s2*s3**5 + 256*s1*s2*s3*s4**3 + 416*s1*s3**3*s4**2 + s2**6*s3**2 - 28*s2**4*s3**2*s4 ++ 16*s2**3*s3**4 + 176*s2**2*s3**2*s4**2 - 224*s2*s3**4*s4 + 64*s3**6 - 320*s3**2*s4**3) + ], + (4, 1): [ + lambda s1, s2, s3, s4: (-s2), + lambda s1, s2, s3, s4: (s1*s3 - 4*s4), + lambda s1, s2, s3, s4: (-s1**2*s4 + 4*s2*s4 - s3**2) + ], + (5, 1): [ + lambda s1, s2, s3, s4, s5: (-2*s1*s3 + 8*s4), + lambda s1, s2, s3, s4, s5: (-8*s1**3*s5 + 2*s1**2*s2*s4 + s1**2*s3**2 + 30*s1*s2*s5 - 14*s1*s3*s4 - 6*s2**2*s4 ++ 2*s2*s3**2 - 50*s3*s5 + 40*s4**2), + lambda s1, s2, s3, s4, s5: (16*s1**4*s3*s5 - 2*s1**4*s4**2 - 2*s1**3*s2**2*s5 - 2*s1**3*s2*s3*s4 - 44*s1**3*s4*s5 +- 66*s1**2*s2*s3*s5 + 21*s1**2*s2*s4**2 + 6*s1**2*s3**2*s4 - 50*s1**2*s5**2 + 9*s1*s2**3*s5 ++ 5*s1*s2**2*s3*s4 - 2*s1*s2*s3**3 + 190*s1*s2*s4*s5 + 120*s1*s3**2*s5 - 80*s1*s3*s4**2 +- 15*s2**2*s3*s5 - 40*s2**2*s4**2 + 21*s2*s3**2*s4 + 125*s2*s5**2 - 2*s3**4 - 400*s3*s4*s5 ++ 160*s4**3), + lambda s1, s2, s3, s4, s5: (16*s1**6*s5**2 - 8*s1**5*s2*s4*s5 - 8*s1**5*s3**2*s5 + 2*s1**5*s3*s4**2 + 2*s1**4*s2**2*s3*s5 ++ s1**4*s2**2*s4**2 - 120*s1**4*s2*s5**2 + 68*s1**4*s3*s4*s5 - 8*s1**4*s4**3 + 46*s1**3*s2**2*s4*s5 ++ 28*s1**3*s2*s3**2*s5 - 19*s1**3*s2*s3*s4**2 + 250*s1**3*s3*s5**2 - 144*s1**3*s4**2*s5 +- 9*s1**2*s2**3*s3*s5 - 6*s1**2*s2**3*s4**2 + 3*s1**2*s2**2*s3**2*s4 + 225*s1**2*s2**2*s5**2 +- 354*s1**2*s2*s3*s4*s5 + 76*s1**2*s2*s4**3 - 70*s1**2*s3**3*s5 + 41*s1**2*s3**2*s4**2 +- 200*s1**2*s4*s5**2 - 54*s1*s2**3*s4*s5 + 45*s1*s2**2*s3**2*s5 + 30*s1*s2**2*s3*s4**2 +- 19*s1*s2*s3**3*s4 - 875*s1*s2*s3*s5**2 + 640*s1*s2*s4**2*s5 + 2*s1*s3**5 + 630*s1*s3**2*s4*s5 +- 264*s1*s3*s4**3 + 9*s2**4*s4**2 - 6*s2**3*s3**2*s4 + s2**2*s3**4 + 90*s2**2*s3*s4*s5 +- 136*s2**2*s4**3 - 50*s2*s3**3*s5 + 76*s2*s3**2*s4**2 + 500*s2*s4*s5**2 - 8*s3**4*s4 ++ 625*s3**2*s5**2 - 1400*s3*s4**2*s5 + 400*s4**4), + lambda s1, s2, s3, s4, s5: (-32*s1**7*s3*s5**2 + 8*s1**7*s4**2*s5 + 8*s1**6*s2**2*s5**2 + 8*s1**6*s2*s3*s4*s5 +- 2*s1**6*s2*s4**3 + 48*s1**6*s4*s5**2 - 2*s1**5*s2**3*s4*s5 + 264*s1**5*s2*s3*s5**2 +- 94*s1**5*s2*s4**2*s5 - 24*s1**5*s3**2*s4*s5 + 6*s1**5*s3*s4**3 - 56*s1**5*s5**3 +- 66*s1**4*s2**3*s5**2 - 50*s1**4*s2**2*s3*s4*s5 + 19*s1**4*s2**2*s4**3 + 8*s1**4*s2*s3**3*s5 +- 2*s1**4*s2*s3**2*s4**2 - 318*s1**4*s2*s4*s5**2 - 352*s1**4*s3**2*s5**2 + 166*s1**4*s3*s4**2*s5 ++ 3*s1**4*s4**4 + 15*s1**3*s2**4*s4*s5 - 2*s1**3*s2**3*s3**2*s5 - s1**3*s2**3*s3*s4**2 +- 574*s1**3*s2**2*s3*s5**2 + 347*s1**3*s2**2*s4**2*s5 + 194*s1**3*s2*s3**2*s4*s5 - +89*s1**3*s2*s3*s4**3 + 350*s1**3*s2*s5**3 - 8*s1**3*s3**4*s5 + 4*s1**3*s3**3*s4**2 ++ 1090*s1**3*s3*s4*s5**2 - 364*s1**3*s4**3*s5 + 162*s1**2*s2**4*s5**2 + 33*s1**2*s2**3*s3*s4*s5 +- 51*s1**2*s2**3*s4**3 - 32*s1**2*s2**2*s3**3*s5 + 28*s1**2*s2**2*s3**2*s4**2 + 305*s1**2*s2**2*s4*s5**2 +- 2*s1**2*s2*s3**4*s4 + 1340*s1**2*s2*s3**2*s5**2 - 901*s1**2*s2*s3*s4**2*s5 + 76*s1**2*s2*s4**4 +- 234*s1**2*s3**3*s4*s5 + 102*s1**2*s3**2*s4**3 - 750*s1**2*s3*s5**3 - 550*s1**2*s4**2*s5**2 +- 27*s1*s2**5*s4*s5 + 9*s1*s2**4*s3**2*s5 + 3*s1*s2**4*s3*s4**2 - s1*s2**3*s3**3*s4 ++ 180*s1*s2**3*s3*s5**2 - 366*s1*s2**3*s4**2*s5 - 231*s1*s2**2*s3**2*s4*s5 + 212*s1*s2**2*s3*s4**3 +- 375*s1*s2**2*s5**3 + 112*s1*s2*s3**4*s5 - 89*s1*s2*s3**3*s4**2 - 3075*s1*s2*s3*s4*s5**2 ++ 1640*s1*s2*s4**3*s5 + 6*s1*s3**5*s4 - 850*s1*s3**3*s5**2 + 1220*s1*s3**2*s4**2*s5 +- 384*s1*s3*s4**4 + 2500*s1*s4*s5**3 - 108*s2**5*s5**2 + 117*s2**4*s3*s4*s5 + 32*s2**4*s4**3 +- 31*s2**3*s3**3*s5 - 51*s2**3*s3**2*s4**2 + 525*s2**3*s4*s5**2 + 19*s2**2*s3**4*s4 +- 325*s2**2*s3**2*s5**2 + 260*s2**2*s3*s4**2*s5 - 256*s2**2*s4**4 - 2*s2*s3**6 + 105*s2*s3**3*s4*s5 ++ 76*s2*s3**2*s4**3 + 625*s2*s3*s5**3 - 500*s2*s4**2*s5**2 - 58*s3**5*s5 + 3*s3**4*s4**2 ++ 2750*s3**2*s4*s5**2 - 2400*s3*s4**3*s5 + 512*s4**5 - 3125*s5**4), + lambda s1, s2, s3, s4, s5: (16*s1**8*s3**2*s5**2 - 8*s1**8*s3*s4**2*s5 + s1**8*s4**4 - 8*s1**7*s2**2*s3*s5**2 ++ 2*s1**7*s2**2*s4**2*s5 - 48*s1**7*s3*s4*s5**2 + 12*s1**7*s4**3*s5 + s1**6*s2**4*s5**2 ++ 12*s1**6*s2**2*s4*s5**2 - 144*s1**6*s2*s3**2*s5**2 + 88*s1**6*s2*s3*s4**2*s5 - 13*s1**6*s2*s4**4 ++ 56*s1**6*s3*s5**3 + 86*s1**6*s4**2*s5**2 + 72*s1**5*s2**3*s3*s5**2 - 22*s1**5*s2**3*s4**2*s5 +- 4*s1**5*s2**2*s3**2*s4*s5 + s1**5*s2**2*s3*s4**3 - 14*s1**5*s2**2*s5**3 + 304*s1**5*s2*s3*s4*s5**2 +- 148*s1**5*s2*s4**3*s5 + 152*s1**5*s3**3*s5**2 - 54*s1**5*s3**2*s4**2*s5 + 5*s1**5*s3*s4**4 +- 468*s1**5*s4*s5**3 - 9*s1**4*s2**5*s5**2 + s1**4*s2**4*s3*s4*s5 - 76*s1**4*s2**3*s4*s5**2 ++ 370*s1**4*s2**2*s3**2*s5**2 - 287*s1**4*s2**2*s3*s4**2*s5 + 65*s1**4*s2**2*s4**4 +- 28*s1**4*s2*s3**3*s4*s5 + 5*s1**4*s2*s3**2*s4**3 - 200*s1**4*s2*s3*s5**3 - 294*s1**4*s2*s4**2*s5**2 ++ 8*s1**4*s3**5*s5 - 2*s1**4*s3**4*s4**2 - 676*s1**4*s3**2*s4*s5**2 + 180*s1**4*s3*s4**3*s5 ++ 17*s1**4*s4**5 + 625*s1**4*s5**4 - 210*s1**3*s2**4*s3*s5**2 + 76*s1**3*s2**4*s4**2*s5 ++ 43*s1**3*s2**3*s3**2*s4*s5 - 15*s1**3*s2**3*s3*s4**3 + 50*s1**3*s2**3*s5**3 - 6*s1**3*s2**2*s3**4*s5 ++ 2*s1**3*s2**2*s3**3*s4**2 - 397*s1**3*s2**2*s3*s4*s5**2 + 514*s1**3*s2**2*s4**3*s5 +- 700*s1**3*s2*s3**3*s5**2 + 447*s1**3*s2*s3**2*s4**2*s5 - 118*s1**3*s2*s3*s4**4 + +2300*s1**3*s2*s4*s5**3 - 12*s1**3*s3**4*s4*s5 + 6*s1**3*s3**3*s4**3 + 250*s1**3*s3**2*s5**3 ++ 1470*s1**3*s3*s4**2*s5**2 - 276*s1**3*s4**4*s5 + 27*s1**2*s2**6*s5**2 - 9*s1**2*s2**5*s3*s4*s5 ++ s1**2*s2**5*s4**3 + s1**2*s2**4*s3**3*s5 + 141*s1**2*s2**4*s4*s5**2 - 185*s1**2*s2**3*s3**2*s5**2 ++ 168*s1**2*s2**3*s3*s4**2*s5 - 128*s1**2*s2**3*s4**4 + 93*s1**2*s2**2*s3**3*s4*s5 ++ 19*s1**2*s2**2*s3**2*s4**3 - 125*s1**2*s2**2*s3*s5**3 - 610*s1**2*s2**2*s4**2*s5**2 +- 36*s1**2*s2*s3**5*s5 + 5*s1**2*s2*s3**4*s4**2 + 1995*s1**2*s2*s3**2*s4*s5**2 - 1174*s1**2*s2*s3*s4**3*s5 +- 16*s1**2*s2*s4**5 - 3125*s1**2*s2*s5**4 + 375*s1**2*s3**4*s5**2 - 172*s1**2*s3**3*s4**2*s5 ++ 82*s1**2*s3**2*s4**4 - 3500*s1**2*s3*s4*s5**3 - 1450*s1**2*s4**3*s5**2 + 198*s1*s2**5*s3*s5**2 +- 78*s1*s2**5*s4**2*s5 - 95*s1*s2**4*s3**2*s4*s5 + 44*s1*s2**4*s3*s4**3 + 25*s1*s2**3*s3**4*s5 +- 15*s1*s2**3*s3**3*s4**2 + 15*s1*s2**3*s3*s4*s5**2 - 384*s1*s2**3*s4**3*s5 + s1*s2**2*s3**5*s4 ++ 525*s1*s2**2*s3**3*s5**2 - 528*s1*s2**2*s3**2*s4**2*s5 + 384*s1*s2**2*s3*s4**4 - +1750*s1*s2**2*s4*s5**3 - 29*s1*s2*s3**4*s4*s5 - 118*s1*s2*s3**3*s4**3 + 625*s1*s2*s3**2*s5**3 +- 850*s1*s2*s3*s4**2*s5**2 + 1760*s1*s2*s4**4*s5 + 38*s1*s3**6*s5 + 5*s1*s3**5*s4**2 +- 2050*s1*s3**3*s4*s5**2 + 780*s1*s3**2*s4**3*s5 - 192*s1*s3*s4**5 + 3125*s1*s3*s5**4 ++ 7500*s1*s4**2*s5**3 - 27*s2**7*s5**2 + 18*s2**6*s3*s4*s5 - 4*s2**6*s4**3 - 4*s2**5*s3**3*s5 ++ s2**5*s3**2*s4**2 - 99*s2**5*s4*s5**2 - 150*s2**4*s3**2*s5**2 + 196*s2**4*s3*s4**2*s5 ++ 48*s2**4*s4**4 + 12*s2**3*s3**3*s4*s5 - 128*s2**3*s3**2*s4**3 + 1200*s2**3*s4**2*s5**2 +- 12*s2**2*s3**5*s5 + 65*s2**2*s3**4*s4**2 - 725*s2**2*s3**2*s4*s5**2 - 160*s2**2*s3*s4**3*s5 +- 192*s2**2*s4**5 + 3125*s2**2*s5**4 - 13*s2*s3**6*s4 - 125*s2*s3**4*s5**2 + 590*s2*s3**3*s4**2*s5 +- 16*s2*s3**2*s4**4 - 1250*s2*s3*s4*s5**3 - 2000*s2*s4**3*s5**2 + s3**8 - 124*s3**5*s4*s5 ++ 17*s3**4*s4**3 + 3250*s3**2*s4**2*s5**2 - 1600*s3*s4**4*s5 + 256*s4**6 - 9375*s4*s5**4) + ], + (6, 1): [ + lambda s1, s2, s3, s4, s5, s6: (8*s1*s5 - 2*s2*s4 - 18*s6), + lambda s1, s2, s3, s4, s5, s6: (-50*s1**2*s4*s6 + 40*s1**2*s5**2 + 30*s1*s2*s3*s6 - 14*s1*s2*s4*s5 - 6*s1*s3**2*s5 ++ 2*s1*s3*s4**2 - 30*s1*s5*s6 - 8*s2**3*s6 + 2*s2**2*s3*s5 + s2**2*s4**2 + 114*s2*s4*s6 +- 50*s2*s5**2 - 54*s3**2*s6 + 30*s3*s4*s5 - 8*s4**3 - 135*s6**2), + lambda s1, s2, s3, s4, s5, s6: (125*s1**3*s3*s6**2 - 400*s1**3*s4*s5*s6 + 160*s1**3*s5**3 - 50*s1**2*s2**2*s6**2 + +190*s1**2*s2*s3*s5*s6 + 120*s1**2*s2*s4**2*s6 - 80*s1**2*s2*s4*s5**2 - 15*s1**2*s3**2*s4*s6 +- 40*s1**2*s3**2*s5**2 + 21*s1**2*s3*s4**2*s5 - 2*s1**2*s4**4 + 900*s1**2*s4*s6**2 +- 80*s1**2*s5**2*s6 - 44*s1*s2**3*s5*s6 - 66*s1*s2**2*s3*s4*s6 + 21*s1*s2**2*s3*s5**2 ++ 6*s1*s2**2*s4**2*s5 + 9*s1*s2*s3**3*s6 + 5*s1*s2*s3**2*s4*s5 - 2*s1*s2*s3*s4**3 +- 990*s1*s2*s3*s6**2 + 920*s1*s2*s4*s5*s6 - 400*s1*s2*s5**3 - 135*s1*s3**2*s5*s6 - +126*s1*s3*s4**2*s6 + 190*s1*s3*s4*s5**2 - 44*s1*s4**3*s5 - 2070*s1*s5*s6**2 + 16*s2**4*s4*s6 +- 2*s2**4*s5**2 - 2*s2**3*s3**2*s6 - 2*s2**3*s3*s4*s5 + 304*s2**3*s6**2 - 126*s2**2*s3*s5*s6 +- 232*s2**2*s4**2*s6 + 120*s2**2*s4*s5**2 + 198*s2*s3**2*s4*s6 - 15*s2*s3**2*s5**2 +- 66*s2*s3*s4**2*s5 + 16*s2*s4**4 - 1440*s2*s4*s6**2 + 900*s2*s5**2*s6 - 27*s3**4*s6 ++ 9*s3**3*s4*s5 - 2*s3**2*s4**3 + 1350*s3**2*s6**2 - 990*s3*s4*s5*s6 + 125*s3*s5**3 ++ 304*s4**3*s6 - 50*s4**2*s5**2 + 3240*s6**3), + lambda s1, s2, s3, s4, s5, s6: (500*s1**4*s3*s5*s6**2 + 625*s1**4*s4**2*s6**2 - 1400*s1**4*s4*s5**2*s6 + 400*s1**4*s5**4 +- 200*s1**3*s2**2*s5*s6**2 - 875*s1**3*s2*s3*s4*s6**2 + 640*s1**3*s2*s3*s5**2*s6 + +630*s1**3*s2*s4**2*s5*s6 - 264*s1**3*s2*s4*s5**3 + 90*s1**3*s3**2*s4*s5*s6 - 136*s1**3*s3**2*s5**3 +- 50*s1**3*s3*s4**3*s6 + 76*s1**3*s3*s4**2*s5**2 - 1125*s1**3*s3*s6**3 - 8*s1**3*s4**4*s5 ++ 2550*s1**3*s4*s5*s6**2 - 200*s1**3*s5**3*s6 + 250*s1**2*s2**3*s4*s6**2 - 144*s1**2*s2**3*s5**2*s6 ++ 225*s1**2*s2**2*s3**2*s6**2 - 354*s1**2*s2**2*s3*s4*s5*s6 + 76*s1**2*s2**2*s3*s5**3 +- 70*s1**2*s2**2*s4**3*s6 + 41*s1**2*s2**2*s4**2*s5**2 + 450*s1**2*s2**2*s6**3 - 54*s1**2*s2*s3**3*s5*s6 ++ 45*s1**2*s2*s3**2*s4**2*s6 + 30*s1**2*s2*s3**2*s4*s5**2 - 19*s1**2*s2*s3*s4**3*s5 +- 2880*s1**2*s2*s3*s5*s6**2 + 2*s1**2*s2*s4**5 - 3480*s1**2*s2*s4**2*s6**2 + 4692*s1**2*s2*s4*s5**2*s6 +- 1400*s1**2*s2*s5**4 + 9*s1**2*s3**4*s5**2 - 6*s1**2*s3**3*s4**2*s5 + s1**2*s3**2*s4**4 ++ 1485*s1**2*s3**2*s4*s6**2 - 522*s1**2*s3**2*s5**2*s6 - 1257*s1**2*s3*s4**2*s5*s6 ++ 640*s1**2*s3*s4*s5**3 + 218*s1**2*s4**4*s6 - 144*s1**2*s4**3*s5**2 + 1350*s1**2*s4*s6**3 +- 5175*s1**2*s5**2*s6**2 - 120*s1*s2**4*s3*s6**2 + 68*s1*s2**4*s4*s5*s6 - 8*s1*s2**4*s5**3 ++ 46*s1*s2**3*s3**2*s5*s6 + 28*s1*s2**3*s3*s4**2*s6 - 19*s1*s2**3*s3*s4*s5**2 + 868*s1*s2**3*s5*s6**2 +- 9*s1*s2**2*s3**3*s4*s6 - 6*s1*s2**2*s3**3*s5**2 + 3*s1*s2**2*s3**2*s4**2*s5 + 2484*s1*s2**2*s3*s4*s6**2 +- 1257*s1*s2**2*s3*s5**2*s6 - 1356*s1*s2**2*s4**2*s5*s6 + 630*s1*s2**2*s4*s5**3 - +891*s1*s2*s3**3*s6**2 + 882*s1*s2*s3**2*s4*s5*s6 + 90*s1*s2*s3**2*s5**3 + 84*s1*s2*s3*s4**3*s6 +- 354*s1*s2*s3*s4**2*s5**2 + 3240*s1*s2*s3*s6**3 + 68*s1*s2*s4**4*s5 - 4392*s1*s2*s4*s5*s6**2 ++ 2550*s1*s2*s5**3*s6 + 54*s1*s3**4*s5*s6 - 54*s1*s3**3*s4**2*s6 - 54*s1*s3**3*s4*s5**2 ++ 46*s1*s3**2*s4**3*s5 + 2727*s1*s3**2*s5*s6**2 - 8*s1*s3*s4**5 + 756*s1*s3*s4**2*s6**2 +- 2880*s1*s3*s4*s5**2*s6 + 500*s1*s3*s5**4 + 868*s1*s4**3*s5*s6 - 200*s1*s4**2*s5**3 ++ 8100*s1*s5*s6**3 + 16*s2**6*s6**2 - 8*s2**5*s3*s5*s6 - 8*s2**5*s4**2*s6 + 2*s2**5*s4*s5**2 ++ 2*s2**4*s3**2*s4*s6 + s2**4*s3**2*s5**2 - 688*s2**4*s4*s6**2 + 218*s2**4*s5**2*s6 ++ 234*s2**3*s3**2*s6**2 + 84*s2**3*s3*s4*s5*s6 - 50*s2**3*s3*s5**3 + 168*s2**3*s4**3*s6 +- 70*s2**3*s4**2*s5**2 - 1224*s2**3*s6**3 - 54*s2**2*s3**3*s5*s6 - 144*s2**2*s3**2*s4**2*s6 ++ 45*s2**2*s3**2*s4*s5**2 + 28*s2**2*s3*s4**3*s5 + 756*s2**2*s3*s5*s6**2 - 8*s2**2*s4**5 ++ 4320*s2**2*s4**2*s6**2 - 3480*s2**2*s4*s5**2*s6 + 625*s2**2*s5**4 + 27*s2*s3**4*s4*s6 +- 9*s2*s3**3*s4**2*s5 + 2*s2*s3**2*s4**4 - 4752*s2*s3**2*s4*s6**2 + 1485*s2*s3**2*s5**2*s6 ++ 2484*s2*s3*s4**2*s5*s6 - 875*s2*s3*s4*s5**3 - 688*s2*s4**4*s6 + 250*s2*s4**3*s5**2 +- 4536*s2*s4*s6**3 + 1350*s2*s5**2*s6**2 + 972*s3**4*s6**2 - 891*s3**3*s4*s5*s6 + +234*s3**2*s4**3*s6 + 225*s3**2*s4**2*s5**2 - 1944*s3**2*s6**3 - 120*s3*s4**4*s5 + +3240*s3*s4*s5*s6**2 - 1125*s3*s5**3*s6 + 16*s4**6 - 1224*s4**3*s6**2 + 450*s4**2*s5**2*s6), + lambda s1, s2, s3, s4, s5, s6: (-3125*s1**6*s6**4 + 2500*s1**5*s2*s5*s6**3 + 625*s1**5*s3*s4*s6**3 - 500*s1**5*s3*s5**2*s6**2 ++ 2750*s1**5*s4**2*s5*s6**2 - 2400*s1**5*s4*s5**3*s6 + 512*s1**5*s5**5 - 750*s1**4*s2**2*s4*s6**3 +- 550*s1**4*s2**2*s5**2*s6**2 - 375*s1**4*s2*s3**2*s6**3 - 3075*s1**4*s2*s3*s4*s5*s6**2 ++ 1640*s1**4*s2*s3*s5**3*s6 - 850*s1**4*s2*s4**3*s6**2 + 1220*s1**4*s2*s4**2*s5**2*s6 +- 384*s1**4*s2*s4*s5**4 + 22500*s1**4*s2*s6**4 + 525*s1**4*s3**3*s5*s6**2 - 325*s1**4*s3**2*s4**2*s6**2 ++ 260*s1**4*s3**2*s4*s5**2*s6 - 256*s1**4*s3**2*s5**4 + 105*s1**4*s3*s4**3*s5*s6 + +76*s1**4*s3*s4**2*s5**3 + 375*s1**4*s3*s5*s6**3 - 58*s1**4*s4**5*s6 + 3*s1**4*s4**4*s5**2 +- 12750*s1**4*s4**2*s6**3 + 3700*s1**4*s4*s5**2*s6**2 + 640*s1**4*s5**4*s6 + 350*s1**3*s2**3*s3*s6**3 ++ 1090*s1**3*s2**3*s4*s5*s6**2 - 364*s1**3*s2**3*s5**3*s6 + 305*s1**3*s2**2*s3**2*s5*s6**2 ++ 1340*s1**3*s2**2*s3*s4**2*s6**2 - 901*s1**3*s2**2*s3*s4*s5**2*s6 + 76*s1**3*s2**2*s3*s5**4 +- 234*s1**3*s2**2*s4**3*s5*s6 + 102*s1**3*s2**2*s4**2*s5**3 - 16650*s1**3*s2**2*s5*s6**3 ++ 180*s1**3*s2*s3**3*s4*s6**2 - 366*s1**3*s2*s3**3*s5**2*s6 - 231*s1**3*s2*s3**2*s4**2*s5*s6 ++ 212*s1**3*s2*s3**2*s4*s5**3 + 112*s1**3*s2*s3*s4**4*s6 - 89*s1**3*s2*s3*s4**3*s5**2 ++ 10950*s1**3*s2*s3*s4*s6**3 + 1555*s1**3*s2*s3*s5**2*s6**2 + 6*s1**3*s2*s4**5*s5 +- 9540*s1**3*s2*s4**2*s5*s6**2 + 9016*s1**3*s2*s4*s5**3*s6 - 2400*s1**3*s2*s5**5 - +108*s1**3*s3**5*s6**2 + 117*s1**3*s3**4*s4*s5*s6 + 32*s1**3*s3**4*s5**3 - 31*s1**3*s3**3*s4**3*s6 +- 51*s1**3*s3**3*s4**2*s5**2 - 2025*s1**3*s3**3*s6**3 + 19*s1**3*s3**2*s4**4*s5 + +2955*s1**3*s3**2*s4*s5*s6**2 - 1436*s1**3*s3**2*s5**3*s6 - 2*s1**3*s3*s4**6 + 2770*s1**3*s3*s4**3*s6**2 +- 5123*s1**3*s3*s4**2*s5**2*s6 + 1640*s1**3*s3*s4*s5**4 - 40500*s1**3*s3*s6**4 + 914*s1**3*s4**4*s5*s6 +- 364*s1**3*s4**3*s5**3 + 53550*s1**3*s4*s5*s6**3 - 17930*s1**3*s5**3*s6**2 - 56*s1**2*s2**5*s6**3 +- 318*s1**2*s2**4*s3*s5*s6**2 - 352*s1**2*s2**4*s4**2*s6**2 + 166*s1**2*s2**4*s4*s5**2*s6 ++ 3*s1**2*s2**4*s5**4 - 574*s1**2*s2**3*s3**2*s4*s6**2 + 347*s1**2*s2**3*s3**2*s5**2*s6 ++ 194*s1**2*s2**3*s3*s4**2*s5*s6 - 89*s1**2*s2**3*s3*s4*s5**3 - 8*s1**2*s2**3*s4**4*s6 ++ 4*s1**2*s2**3*s4**3*s5**2 + 560*s1**2*s2**3*s4*s6**3 + 3662*s1**2*s2**3*s5**2*s6**2 ++ 162*s1**2*s2**2*s3**4*s6**2 + 33*s1**2*s2**2*s3**3*s4*s5*s6 - 51*s1**2*s2**2*s3**3*s5**3 +- 32*s1**2*s2**2*s3**2*s4**3*s6 + 28*s1**2*s2**2*s3**2*s4**2*s5**2 + 270*s1**2*s2**2*s3**2*s6**3 +- 2*s1**2*s2**2*s3*s4**4*s5 + 4872*s1**2*s2**2*s3*s4*s5*s6**2 - 5123*s1**2*s2**2*s3*s5**3*s6 ++ 2144*s1**2*s2**2*s4**3*s6**2 - 2812*s1**2*s2**2*s4**2*s5**2*s6 + 1220*s1**2*s2**2*s4*s5**4 +- 37800*s1**2*s2**2*s6**4 - 27*s1**2*s2*s3**5*s5*s6 + 9*s1**2*s2*s3**4*s4**2*s6 + +3*s1**2*s2*s3**4*s4*s5**2 - s1**2*s2*s3**3*s4**3*s5 - 3078*s1**2*s2*s3**3*s5*s6**2 +- 4014*s1**2*s2*s3**2*s4**2*s6**2 + 5412*s1**2*s2*s3**2*s4*s5**2*s6 + 260*s1**2*s2*s3**2*s5**4 +- 310*s1**2*s2*s3*s4**3*s5*s6 - 901*s1**2*s2*s3*s4**2*s5**3 - 3780*s1**2*s2*s3*s5*s6**3 ++ 166*s1**2*s2*s4**4*s5**2 + 40320*s1**2*s2*s4**2*s6**3 - 25344*s1**2*s2*s4*s5**2*s6**2 ++ 3700*s1**2*s2*s5**4*s6 + 918*s1**2*s3**4*s4*s6**2 + 27*s1**2*s3**4*s5**2*s6 - 342*s1**2*s3**3*s4**2*s5*s6 +- 366*s1**2*s3**3*s4*s5**3 + 32*s1**2*s3**2*s4**4*s6 + 347*s1**2*s3**2*s4**3*s5**2 +- 4590*s1**2*s3**2*s4*s6**3 + 594*s1**2*s3**2*s5**2*s6**2 - 94*s1**2*s3*s4**5*s5 + +3618*s1**2*s3*s4**2*s5*s6**2 + 1555*s1**2*s3*s4*s5**3*s6 - 500*s1**2*s3*s5**5 + 8*s1**2*s4**7 +- 7192*s1**2*s4**4*s6**2 + 3662*s1**2*s4**3*s5**2*s6 - 550*s1**2*s4**2*s5**4 - 48600*s1**2*s4*s6**4 ++ 1080*s1**2*s5**2*s6**3 + 48*s1*s2**6*s5*s6**2 + 264*s1*s2**5*s3*s4*s6**2 - 94*s1*s2**5*s3*s5**2*s6 +- 24*s1*s2**5*s4**2*s5*s6 + 6*s1*s2**5*s4*s5**3 - 66*s1*s2**4*s3**3*s6**2 - 50*s1*s2**4*s3**2*s4*s5*s6 ++ 19*s1*s2**4*s3**2*s5**3 + 8*s1*s2**4*s3*s4**3*s6 - 2*s1*s2**4*s3*s4**2*s5**2 - 552*s1*s2**4*s3*s6**3 +- 2560*s1*s2**4*s4*s5*s6**2 + 914*s1*s2**4*s5**3*s6 + 15*s1*s2**3*s3**4*s5*s6 - 2*s1*s2**3*s3**3*s4**2*s6 +- s1*s2**3*s3**3*s4*s5**2 + 1602*s1*s2**3*s3**2*s5*s6**2 - 608*s1*s2**3*s3*s4**2*s6**2 +- 310*s1*s2**3*s3*s4*s5**2*s6 + 105*s1*s2**3*s3*s5**4 + 600*s1*s2**3*s4**3*s5*s6 - +234*s1*s2**3*s4**2*s5**3 + 31368*s1*s2**3*s5*s6**3 + 756*s1*s2**2*s3**3*s4*s6**2 - +342*s1*s2**2*s3**3*s5**2*s6 + 216*s1*s2**2*s3**2*s4**2*s5*s6 - 231*s1*s2**2*s3**2*s4*s5**3 +- 192*s1*s2**2*s3*s4**4*s6 + 194*s1*s2**2*s3*s4**3*s5**2 - 39096*s1*s2**2*s3*s4*s6**3 ++ 3618*s1*s2**2*s3*s5**2*s6**2 - 24*s1*s2**2*s4**5*s5 + 9408*s1*s2**2*s4**2*s5*s6**2 +- 9540*s1*s2**2*s4*s5**3*s6 + 2750*s1*s2**2*s5**5 - 162*s1*s2*s3**5*s6**2 - 378*s1*s2*s3**4*s4*s5*s6 ++ 117*s1*s2*s3**4*s5**3 + 150*s1*s2*s3**3*s4**3*s6 + 33*s1*s2*s3**3*s4**2*s5**2 + +10044*s1*s2*s3**3*s6**3 - 50*s1*s2*s3**2*s4**4*s5 - 8640*s1*s2*s3**2*s4*s5*s6**2 + +2955*s1*s2*s3**2*s5**3*s6 + 8*s1*s2*s3*s4**6 + 6144*s1*s2*s3*s4**3*s6**2 + 4872*s1*s2*s3*s4**2*s5**2*s6 +- 3075*s1*s2*s3*s4*s5**4 + 174960*s1*s2*s3*s6**4 - 2560*s1*s2*s4**4*s5*s6 + 1090*s1*s2*s4**3*s5**3 +- 148824*s1*s2*s4*s5*s6**3 + 53550*s1*s2*s5**3*s6**2 + 81*s1*s3**6*s5*s6 - 27*s1*s3**5*s4**2*s6 +- 27*s1*s3**5*s4*s5**2 + 15*s1*s3**4*s4**3*s5 + 2430*s1*s3**4*s5*s6**2 - 2*s1*s3**3*s4**5 +- 2052*s1*s3**3*s4**2*s6**2 - 3078*s1*s3**3*s4*s5**2*s6 + 525*s1*s3**3*s5**4 + 1602*s1*s3**2*s4**3*s5*s6 ++ 305*s1*s3**2*s4**2*s5**3 + 18144*s1*s3**2*s5*s6**3 - 104*s1*s3*s4**5*s6 - 318*s1*s3*s4**4*s5**2 +- 33696*s1*s3*s4**2*s6**3 - 3780*s1*s3*s4*s5**2*s6**2 + 375*s1*s3*s5**4*s6 + 48*s1*s4**6*s5 ++ 31368*s1*s4**3*s5*s6**2 - 16650*s1*s4**2*s5**3*s6 + 2500*s1*s4*s5**5 + 77760*s1*s5*s6**4 +- 32*s2**7*s4*s6**2 + 8*s2**7*s5**2*s6 + 8*s2**6*s3**2*s6**2 + 8*s2**6*s3*s4*s5*s6 +- 2*s2**6*s3*s5**3 + 96*s2**6*s6**3 - 2*s2**5*s3**3*s5*s6 - 104*s2**5*s3*s5*s6**2 ++ 416*s2**5*s4**2*s6**2 - 58*s2**5*s5**4 - 312*s2**4*s3**2*s4*s6**2 + 32*s2**4*s3**2*s5**2*s6 +- 192*s2**4*s3*s4**2*s5*s6 + 112*s2**4*s3*s4*s5**3 - 8*s2**4*s4**3*s5**2 + 4224*s2**4*s4*s6**3 +- 7192*s2**4*s5**2*s6**2 + 54*s2**3*s3**4*s6**2 + 150*s2**3*s3**3*s4*s5*s6 - 31*s2**3*s3**3*s5**3 +- 32*s2**3*s3**2*s4**2*s5**2 - 864*s2**3*s3**2*s6**3 + 8*s2**3*s3*s4**4*s5 + 6144*s2**3*s3*s4*s5*s6**2 ++ 2770*s2**3*s3*s5**3*s6 - 4032*s2**3*s4**3*s6**2 + 2144*s2**3*s4**2*s5**2*s6 - 850*s2**3*s4*s5**4 +- 16416*s2**3*s6**4 - 27*s2**2*s3**5*s5*s6 + 9*s2**2*s3**4*s4*s5**2 - 2*s2**2*s3**3*s4**3*s5 +- 2052*s2**2*s3**3*s5*s6**2 + 2376*s2**2*s3**2*s4**2*s6**2 - 4014*s2**2*s3**2*s4*s5**2*s6 +- 325*s2**2*s3**2*s5**4 - 608*s2**2*s3*s4**3*s5*s6 + 1340*s2**2*s3*s4**2*s5**3 - 33696*s2**2*s3*s5*s6**3 ++ 416*s2**2*s4**5*s6 - 352*s2**2*s4**4*s5**2 - 6048*s2**2*s4**2*s6**3 + 40320*s2**2*s4*s5**2*s6**2 +- 12750*s2**2*s5**4*s6 - 324*s2*s3**4*s4*s6**2 + 918*s2*s3**4*s5**2*s6 + 756*s2*s3**3*s4**2*s5*s6 ++ 180*s2*s3**3*s4*s5**3 - 312*s2*s3**2*s4**4*s6 - 574*s2*s3**2*s4**3*s5**2 + 43416*s2*s3**2*s4*s6**3 +- 4590*s2*s3**2*s5**2*s6**2 + 264*s2*s3*s4**5*s5 - 39096*s2*s3*s4**2*s5*s6**2 + 10950*s2*s3*s4*s5**3*s6 ++ 625*s2*s3*s5**5 - 32*s2*s4**7 + 4224*s2*s4**4*s6**2 + 560*s2*s4**3*s5**2*s6 - 750*s2*s4**2*s5**4 ++ 85536*s2*s4*s6**4 - 48600*s2*s5**2*s6**3 - 162*s3**5*s4*s5*s6 - 108*s3**5*s5**3 ++ 54*s3**4*s4**3*s6 + 162*s3**4*s4**2*s5**2 - 11664*s3**4*s6**3 - 66*s3**3*s4**4*s5 ++ 10044*s3**3*s4*s5*s6**2 - 2025*s3**3*s5**3*s6 + 8*s3**2*s4**6 - 864*s3**2*s4**3*s6**2 ++ 270*s3**2*s4**2*s5**2*s6 - 375*s3**2*s4*s5**4 - 163296*s3**2*s6**4 - 552*s3*s4**4*s5*s6 ++ 350*s3*s4**3*s5**3 + 174960*s3*s4*s5*s6**3 - 40500*s3*s5**3*s6**2 + 96*s4**6*s6 +- 56*s4**5*s5**2 - 16416*s4**3*s6**3 - 37800*s4**2*s5**2*s6**2 + 22500*s4*s5**4*s6 +- 3125*s5**6 - 93312*s6**5), + lambda s1, s2, s3, s4, s5, s6: (-9375*s1**7*s5*s6**4 + 3125*s1**6*s2*s4*s6**4 + 7500*s1**6*s2*s5**2*s6**3 + 3125*s1**6*s3**2*s6**4 +- 1250*s1**6*s3*s4*s5*s6**3 - 2000*s1**6*s3*s5**3*s6**2 + 3250*s1**6*s4**2*s5**2*s6**2 +- 1600*s1**6*s4*s5**4*s6 + 256*s1**6*s5**6 + 40625*s1**6*s6**5 - 3125*s1**5*s2**2*s3*s6**4 +- 3500*s1**5*s2**2*s4*s5*s6**3 - 1450*s1**5*s2**2*s5**3*s6**2 - 1750*s1**5*s2*s3**2*s5*s6**3 ++ 625*s1**5*s2*s3*s4**2*s6**3 - 850*s1**5*s2*s3*s4*s5**2*s6**2 + 1760*s1**5*s2*s3*s5**4*s6 +- 2050*s1**5*s2*s4**3*s5*s6**2 + 780*s1**5*s2*s4**2*s5**3*s6 - 192*s1**5*s2*s4*s5**5 ++ 35000*s1**5*s2*s5*s6**4 + 1200*s1**5*s3**3*s5**2*s6**2 - 725*s1**5*s3**2*s4**2*s5*s6**2 +- 160*s1**5*s3**2*s4*s5**3*s6 - 192*s1**5*s3**2*s5**5 - 125*s1**5*s3*s4**4*s6**2 + +590*s1**5*s3*s4**3*s5**2*s6 - 16*s1**5*s3*s4**2*s5**4 - 20625*s1**5*s3*s4*s6**4 + +17250*s1**5*s3*s5**2*s6**3 - 124*s1**5*s4**5*s5*s6 + 17*s1**5*s4**4*s5**3 - 20250*s1**5*s4**2*s5*s6**3 ++ 1900*s1**5*s4*s5**3*s6**2 + 1344*s1**5*s5**5*s6 + 625*s1**4*s2**4*s6**4 + 2300*s1**4*s2**3*s3*s5*s6**3 ++ 250*s1**4*s2**3*s4**2*s6**3 + 1470*s1**4*s2**3*s4*s5**2*s6**2 - 276*s1**4*s2**3*s5**4*s6 +- 125*s1**4*s2**2*s3**2*s4*s6**3 - 610*s1**4*s2**2*s3**2*s5**2*s6**2 + 1995*s1**4*s2**2*s3*s4**2*s5*s6**2 +- 1174*s1**4*s2**2*s3*s4*s5**3*s6 - 16*s1**4*s2**2*s3*s5**5 + 375*s1**4*s2**2*s4**4*s6**2 +- 172*s1**4*s2**2*s4**3*s5**2*s6 + 82*s1**4*s2**2*s4**2*s5**4 - 7750*s1**4*s2**2*s4*s6**4 +- 46650*s1**4*s2**2*s5**2*s6**3 + 15*s1**4*s2*s3**3*s4*s5*s6**2 - 384*s1**4*s2*s3**3*s5**3*s6 ++ 525*s1**4*s2*s3**2*s4**3*s6**2 - 528*s1**4*s2*s3**2*s4**2*s5**2*s6 + 384*s1**4*s2*s3**2*s4*s5**4 +- 10125*s1**4*s2*s3**2*s6**4 - 29*s1**4*s2*s3*s4**4*s5*s6 - 118*s1**4*s2*s3*s4**3*s5**3 ++ 36700*s1**4*s2*s3*s4*s5*s6**3 + 2410*s1**4*s2*s3*s5**3*s6**2 + 38*s1**4*s2*s4**6*s6 ++ 5*s1**4*s2*s4**5*s5**2 + 5550*s1**4*s2*s4**3*s6**3 - 10040*s1**4*s2*s4**2*s5**2*s6**2 ++ 5800*s1**4*s2*s4*s5**4*s6 - 1600*s1**4*s2*s5**6 - 292500*s1**4*s2*s6**5 - 99*s1**4*s3**5*s5*s6**2 +- 150*s1**4*s3**4*s4**2*s6**2 + 196*s1**4*s3**4*s4*s5**2*s6 + 48*s1**4*s3**4*s5**4 ++ 12*s1**4*s3**3*s4**3*s5*s6 - 128*s1**4*s3**3*s4**2*s5**3 - 6525*s1**4*s3**3*s5*s6**3 +- 12*s1**4*s3**2*s4**5*s6 + 65*s1**4*s3**2*s4**4*s5**2 + 225*s1**4*s3**2*s4**2*s6**3 ++ 80*s1**4*s3**2*s4*s5**2*s6**2 - 13*s1**4*s3*s4**6*s5 + 5145*s1**4*s3*s4**3*s5*s6**2 +- 6746*s1**4*s3*s4**2*s5**3*s6 + 1760*s1**4*s3*s4*s5**5 - 103500*s1**4*s3*s5*s6**4 ++ s1**4*s4**8 + 954*s1**4*s4**5*s6**2 + 449*s1**4*s4**4*s5**2*s6 - 276*s1**4*s4**3*s5**4 ++ 70125*s1**4*s4**2*s6**4 + 58900*s1**4*s4*s5**2*s6**3 - 23310*s1**4*s5**4*s6**2 - +468*s1**3*s2**5*s5*s6**3 - 200*s1**3*s2**4*s3*s4*s6**3 - 294*s1**3*s2**4*s3*s5**2*s6**2 +- 676*s1**3*s2**4*s4**2*s5*s6**2 + 180*s1**3*s2**4*s4*s5**3*s6 + 17*s1**3*s2**4*s5**5 ++ 50*s1**3*s2**3*s3**3*s6**3 - 397*s1**3*s2**3*s3**2*s4*s5*s6**2 + 514*s1**3*s2**3*s3**2*s5**3*s6 +- 700*s1**3*s2**3*s3*s4**3*s6**2 + 447*s1**3*s2**3*s3*s4**2*s5**2*s6 - 118*s1**3*s2**3*s3*s4*s5**4 ++ 11700*s1**3*s2**3*s3*s6**4 - 12*s1**3*s2**3*s4**4*s5*s6 + 6*s1**3*s2**3*s4**3*s5**3 ++ 10360*s1**3*s2**3*s4*s5*s6**3 + 11404*s1**3*s2**3*s5**3*s6**2 + 141*s1**3*s2**2*s3**4*s5*s6**2 +- 185*s1**3*s2**2*s3**3*s4**2*s6**2 + 168*s1**3*s2**2*s3**3*s4*s5**2*s6 - 128*s1**3*s2**2*s3**3*s5**4 ++ 93*s1**3*s2**2*s3**2*s4**3*s5*s6 + 19*s1**3*s2**2*s3**2*s4**2*s5**3 + 5895*s1**3*s2**2*s3**2*s5*s6**3 +- 36*s1**3*s2**2*s3*s4**5*s6 + 5*s1**3*s2**2*s3*s4**4*s5**2 - 12020*s1**3*s2**2*s3*s4**2*s6**3 +- 5698*s1**3*s2**2*s3*s4*s5**2*s6**2 - 6746*s1**3*s2**2*s3*s5**4*s6 + 5064*s1**3*s2**2*s4**3*s5*s6**2 +- 762*s1**3*s2**2*s4**2*s5**3*s6 + 780*s1**3*s2**2*s4*s5**5 + 93900*s1**3*s2**2*s5*s6**4 ++ 198*s1**3*s2*s3**5*s4*s6**2 - 78*s1**3*s2*s3**5*s5**2*s6 - 95*s1**3*s2*s3**4*s4**2*s5*s6 ++ 44*s1**3*s2*s3**4*s4*s5**3 + 25*s1**3*s2*s3**3*s4**4*s6 - 15*s1**3*s2*s3**3*s4**3*s5**2 ++ 1935*s1**3*s2*s3**3*s4*s6**3 - 2808*s1**3*s2*s3**3*s5**2*s6**2 + s1**3*s2*s3**2*s4**5*s5 +- 4844*s1**3*s2*s3**2*s4**2*s5*s6**2 + 8996*s1**3*s2*s3**2*s4*s5**3*s6 - 160*s1**3*s2*s3**2*s5**5 +- 3616*s1**3*s2*s3*s4**4*s6**2 + 500*s1**3*s2*s3*s4**3*s5**2*s6 - 1174*s1**3*s2*s3*s4**2*s5**4 ++ 72900*s1**3*s2*s3*s4*s6**4 - 55665*s1**3*s2*s3*s5**2*s6**3 + 128*s1**3*s2*s4**5*s5*s6 ++ 180*s1**3*s2*s4**4*s5**3 + 16240*s1**3*s2*s4**2*s5*s6**3 - 9330*s1**3*s2*s4*s5**3*s6**2 ++ 1900*s1**3*s2*s5**5*s6 - 27*s1**3*s3**7*s6**2 + 18*s1**3*s3**6*s4*s5*s6 - 4*s1**3*s3**6*s5**3 +- 4*s1**3*s3**5*s4**3*s6 + s1**3*s3**5*s4**2*s5**2 + 54*s1**3*s3**5*s6**3 + 1143*s1**3*s3**4*s4*s5*s6**2 +- 820*s1**3*s3**4*s5**3*s6 + 923*s1**3*s3**3*s4**3*s6**2 + 57*s1**3*s3**3*s4**2*s5**2*s6 +- 384*s1**3*s3**3*s4*s5**4 + 29700*s1**3*s3**3*s6**4 - 547*s1**3*s3**2*s4**4*s5*s6 ++ 514*s1**3*s3**2*s4**3*s5**3 - 10305*s1**3*s3**2*s4*s5*s6**3 - 7405*s1**3*s3**2*s5**3*s6**2 ++ 108*s1**3*s3*s4**6*s6 - 148*s1**3*s3*s4**5*s5**2 - 11360*s1**3*s3*s4**3*s6**3 + +22209*s1**3*s3*s4**2*s5**2*s6**2 + 2410*s1**3*s3*s4*s5**4*s6 - 2000*s1**3*s3*s5**6 ++ 432000*s1**3*s3*s6**5 + 12*s1**3*s4**7*s5 - 22624*s1**3*s4**4*s5*s6**2 + 11404*s1**3*s4**3*s5**3*s6 +- 1450*s1**3*s4**2*s5**5 - 242100*s1**3*s4*s5*s6**4 + 58430*s1**3*s5**3*s6**3 + 56*s1**2*s2**6*s4*s6**3 ++ 86*s1**2*s2**6*s5**2*s6**2 - 14*s1**2*s2**5*s3**2*s6**3 + 304*s1**2*s2**5*s3*s4*s5*s6**2 +- 148*s1**2*s2**5*s3*s5**3*s6 + 152*s1**2*s2**5*s4**3*s6**2 - 54*s1**2*s2**5*s4**2*s5**2*s6 ++ 5*s1**2*s2**5*s4*s5**4 - 2472*s1**2*s2**5*s6**4 - 76*s1**2*s2**4*s3**3*s5*s6**2 ++ 370*s1**2*s2**4*s3**2*s4**2*s6**2 - 287*s1**2*s2**4*s3**2*s4*s5**2*s6 + 65*s1**2*s2**4*s3**2*s5**4 +- 28*s1**2*s2**4*s3*s4**3*s5*s6 + 5*s1**2*s2**4*s3*s4**2*s5**3 - 8092*s1**2*s2**4*s3*s5*s6**3 ++ 8*s1**2*s2**4*s4**5*s6 - 2*s1**2*s2**4*s4**4*s5**2 + 1096*s1**2*s2**4*s4**2*s6**3 +- 5144*s1**2*s2**4*s4*s5**2*s6**2 + 449*s1**2*s2**4*s5**4*s6 - 210*s1**2*s2**3*s3**4*s4*s6**2 ++ 76*s1**2*s2**3*s3**4*s5**2*s6 + 43*s1**2*s2**3*s3**3*s4**2*s5*s6 - 15*s1**2*s2**3*s3**3*s4*s5**3 +- 6*s1**2*s2**3*s3**2*s4**4*s6 + 2*s1**2*s2**3*s3**2*s4**3*s5**2 + 1962*s1**2*s2**3*s3**2*s4*s6**3 ++ 3181*s1**2*s2**3*s3**2*s5**2*s6**2 + 1684*s1**2*s2**3*s3*s4**2*s5*s6**2 + 500*s1**2*s2**3*s3*s4*s5**3*s6 ++ 590*s1**2*s2**3*s3*s5**5 - 168*s1**2*s2**3*s4**4*s6**2 - 494*s1**2*s2**3*s4**3*s5**2*s6 +- 172*s1**2*s2**3*s4**2*s5**4 - 22080*s1**2*s2**3*s4*s6**4 + 58894*s1**2*s2**3*s5**2*s6**3 ++ 27*s1**2*s2**2*s3**6*s6**2 - 9*s1**2*s2**2*s3**5*s4*s5*s6 + s1**2*s2**2*s3**5*s5**3 ++ s1**2*s2**2*s3**4*s4**3*s6 - 486*s1**2*s2**2*s3**4*s6**3 + 1071*s1**2*s2**2*s3**3*s4*s5*s6**2 ++ 57*s1**2*s2**2*s3**3*s5**3*s6 + 2262*s1**2*s2**2*s3**2*s4**3*s6**2 - 2742*s1**2*s2**2*s3**2*s4**2*s5**2*s6 +- 528*s1**2*s2**2*s3**2*s4*s5**4 - 29160*s1**2*s2**2*s3**2*s6**4 + 772*s1**2*s2**2*s3*s4**4*s5*s6 ++ 447*s1**2*s2**2*s3*s4**3*s5**3 - 96732*s1**2*s2**2*s3*s4*s5*s6**3 + 22209*s1**2*s2**2*s3*s5**3*s6**2 +- 160*s1**2*s2**2*s4**6*s6 - 54*s1**2*s2**2*s4**5*s5**2 - 7992*s1**2*s2**2*s4**3*s6**3 ++ 8634*s1**2*s2**2*s4**2*s5**2*s6**2 - 10040*s1**2*s2**2*s4*s5**4*s6 + 3250*s1**2*s2**2*s5**6 ++ 529200*s1**2*s2**2*s6**5 - 351*s1**2*s2*s3**5*s5*s6**2 - 1215*s1**2*s2*s3**4*s4**2*s6**2 +- 360*s1**2*s2*s3**4*s4*s5**2*s6 + 196*s1**2*s2*s3**4*s5**4 + 741*s1**2*s2*s3**3*s4**3*s5*s6 ++ 168*s1**2*s2*s3**3*s4**2*s5**3 + 11718*s1**2*s2*s3**3*s5*s6**3 - 106*s1**2*s2*s3**2*s4**5*s6 +- 287*s1**2*s2*s3**2*s4**4*s5**2 + 22572*s1**2*s2*s3**2*s4**2*s6**3 - 8892*s1**2*s2*s3**2*s4*s5**2*s6**2 ++ 80*s1**2*s2*s3**2*s5**4*s6 + 88*s1**2*s2*s3*s4**6*s5 + 22144*s1**2*s2*s3*s4**3*s5*s6**2 +- 5698*s1**2*s2*s3*s4**2*s5**3*s6 - 850*s1**2*s2*s3*s4*s5**5 + 169560*s1**2*s2*s3*s5*s6**4 +- 8*s1**2*s2*s4**8 + 3032*s1**2*s2*s4**5*s6**2 - 5144*s1**2*s2*s4**4*s5**2*s6 + 1470*s1**2*s2*s4**3*s5**4 +- 249480*s1**2*s2*s4**2*s6**4 - 105390*s1**2*s2*s4*s5**2*s6**3 + 58900*s1**2*s2*s5**4*s6**2 ++ 162*s1**2*s3**6*s4*s6**2 + 216*s1**2*s3**6*s5**2*s6 - 216*s1**2*s3**5*s4**2*s5*s6 +- 78*s1**2*s3**5*s4*s5**3 + 36*s1**2*s3**4*s4**4*s6 + 76*s1**2*s3**4*s4**3*s5**2 - +3564*s1**2*s3**4*s4*s6**3 + 8802*s1**2*s3**4*s5**2*s6**2 - 22*s1**2*s3**3*s4**5*s5 +- 11475*s1**2*s3**3*s4**2*s5*s6**2 - 2808*s1**2*s3**3*s4*s5**3*s6 + 1200*s1**2*s3**3*s5**5 ++ 2*s1**2*s3**2*s4**7 + 222*s1**2*s3**2*s4**4*s6**2 + 3181*s1**2*s3**2*s4**3*s5**2*s6 +- 610*s1**2*s3**2*s4**2*s5**4 - 165240*s1**2*s3**2*s4*s6**4 + 118260*s1**2*s3**2*s5**2*s6**3 ++ 572*s1**2*s3*s4**5*s5*s6 - 294*s1**2*s3*s4**4*s5**3 - 32616*s1**2*s3*s4**2*s5*s6**3 +- 55665*s1**2*s3*s4*s5**3*s6**2 + 17250*s1**2*s3*s5**5*s6 - 232*s1**2*s4**7*s6 + 86*s1**2*s4**6*s5**2 ++ 48408*s1**2*s4**4*s6**3 + 58894*s1**2*s4**3*s5**2*s6**2 - 46650*s1**2*s4**2*s5**4*s6 ++ 7500*s1**2*s4*s5**6 - 129600*s1**2*s4*s6**5 + 41040*s1**2*s5**2*s6**4 - 48*s1*s2**7*s4*s5*s6**2 ++ 12*s1*s2**7*s5**3*s6 + 12*s1*s2**6*s3**2*s5*s6**2 - 144*s1*s2**6*s3*s4**2*s6**2 ++ 88*s1*s2**6*s3*s4*s5**2*s6 - 13*s1*s2**6*s3*s5**4 + 1680*s1*s2**6*s5*s6**3 + 72*s1*s2**5*s3**3*s4*s6**2 +- 22*s1*s2**5*s3**3*s5**2*s6 - 4*s1*s2**5*s3**2*s4**2*s5*s6 + s1*s2**5*s3**2*s4*s5**3 +- 144*s1*s2**5*s3*s4*s6**3 + 572*s1*s2**5*s3*s5**2*s6**2 + 736*s1*s2**5*s4**2*s5*s6**2 ++ 128*s1*s2**5*s4*s5**3*s6 - 124*s1*s2**5*s5**5 - 9*s1*s2**4*s3**5*s6**2 + s1*s2**4*s3**4*s4*s5*s6 ++ 36*s1*s2**4*s3**3*s6**3 - 2028*s1*s2**4*s3**2*s4*s5*s6**2 - 547*s1*s2**4*s3**2*s5**3*s6 +- 480*s1*s2**4*s3*s4**3*s6**2 + 772*s1*s2**4*s3*s4**2*s5**2*s6 - 29*s1*s2**4*s3*s4*s5**4 ++ 6336*s1*s2**4*s3*s6**4 - 12*s1*s2**4*s4**3*s5**3 + 4368*s1*s2**4*s4*s5*s6**3 - 22624*s1*s2**4*s5**3*s6**2 ++ 441*s1*s2**3*s3**4*s5*s6**2 + 336*s1*s2**3*s3**3*s4**2*s6**2 + 741*s1*s2**3*s3**3*s4*s5**2*s6 ++ 12*s1*s2**3*s3**3*s5**4 - 868*s1*s2**3*s3**2*s4**3*s5*s6 + 93*s1*s2**3*s3**2*s4**2*s5**3 ++ 11016*s1*s2**3*s3**2*s5*s6**3 + 176*s1*s2**3*s3*s4**5*s6 - 28*s1*s2**3*s3*s4**4*s5**2 ++ 14784*s1*s2**3*s3*s4**2*s6**3 + 22144*s1*s2**3*s3*s4*s5**2*s6**2 + 5145*s1*s2**3*s3*s5**4*s6 +- 11344*s1*s2**3*s4**3*s5*s6**2 + 5064*s1*s2**3*s4**2*s5**3*s6 - 2050*s1*s2**3*s4*s5**5 +- 346896*s1*s2**3*s5*s6**4 - 54*s1*s2**2*s3**5*s4*s6**2 - 216*s1*s2**2*s3**5*s5**2*s6 ++ 324*s1*s2**2*s3**4*s4**2*s5*s6 - 95*s1*s2**2*s3**4*s4*s5**3 - 80*s1*s2**2*s3**3*s4**4*s6 ++ 43*s1*s2**2*s3**3*s4**3*s5**2 - 12204*s1*s2**2*s3**3*s4*s6**3 - 11475*s1*s2**2*s3**3*s5**2*s6**2 +- 4*s1*s2**2*s3**2*s4**5*s5 - 3888*s1*s2**2*s3**2*s4**2*s5*s6**2 - 4844*s1*s2**2*s3**2*s4*s5**3*s6 +- 725*s1*s2**2*s3**2*s5**5 - 1312*s1*s2**2*s3*s4**4*s6**2 + 1684*s1*s2**2*s3*s4**3*s5**2*s6 ++ 1995*s1*s2**2*s3*s4**2*s5**4 + 139104*s1*s2**2*s3*s4*s6**4 - 32616*s1*s2**2*s3*s5**2*s6**3 ++ 736*s1*s2**2*s4**5*s5*s6 - 676*s1*s2**2*s4**4*s5**3 + 131040*s1*s2**2*s4**2*s5*s6**3 ++ 16240*s1*s2**2*s4*s5**3*s6**2 - 20250*s1*s2**2*s5**5*s6 - 27*s1*s2*s3**6*s4*s5*s6 ++ 18*s1*s2*s3**6*s5**3 + 9*s1*s2*s3**5*s4**3*s6 - 9*s1*s2*s3**5*s4**2*s5**2 + 1944*s1*s2*s3**5*s6**3 ++ s1*s2*s3**4*s4**4*s5 + 6156*s1*s2*s3**4*s4*s5*s6**2 + 1143*s1*s2*s3**4*s5**3*s6 ++ 324*s1*s2*s3**3*s4**3*s6**2 + 1071*s1*s2*s3**3*s4**2*s5**2*s6 + 15*s1*s2*s3**3*s4*s5**4 +- 7776*s1*s2*s3**3*s6**4 - 2028*s1*s2*s3**2*s4**4*s5*s6 - 397*s1*s2*s3**2*s4**3*s5**3 ++ 112860*s1*s2*s3**2*s4*s5*s6**3 - 10305*s1*s2*s3**2*s5**3*s6**2 + 336*s1*s2*s3*s4**6*s6 ++ 304*s1*s2*s3*s4**5*s5**2 - 68976*s1*s2*s3*s4**3*s6**3 - 96732*s1*s2*s3*s4**2*s5**2*s6**2 ++ 36700*s1*s2*s3*s4*s5**4*s6 - 1250*s1*s2*s3*s5**6 - 1477440*s1*s2*s3*s6**5 - 48*s1*s2*s4**7*s5 ++ 4368*s1*s2*s4**4*s5*s6**2 + 10360*s1*s2*s4**3*s5**3*s6 - 3500*s1*s2*s4**2*s5**5 ++ 935280*s1*s2*s4*s5*s6**4 - 242100*s1*s2*s5**3*s6**3 - 972*s1*s3**6*s5*s6**2 - 351*s1*s3**5*s4*s5**2*s6 +- 99*s1*s3**5*s5**4 + 441*s1*s3**4*s4**3*s5*s6 + 141*s1*s3**4*s4**2*s5**3 - 36936*s1*s3**4*s5*s6**3 +- 84*s1*s3**3*s4**5*s6 - 76*s1*s3**3*s4**4*s5**2 + 17496*s1*s3**3*s4**2*s6**3 + 11718*s1*s3**3*s4*s5**2*s6**2 +- 6525*s1*s3**3*s5**4*s6 + 12*s1*s3**2*s4**6*s5 + 11016*s1*s3**2*s4**3*s5*s6**2 + +5895*s1*s3**2*s4**2*s5**3*s6 - 1750*s1*s3**2*s4*s5**5 - 252720*s1*s3**2*s5*s6**4 - +2544*s1*s3*s4**5*s6**2 - 8092*s1*s3*s4**4*s5**2*s6 + 2300*s1*s3*s4**3*s5**4 + 536544*s1*s3*s4**2*s6**4 ++ 169560*s1*s3*s4*s5**2*s6**3 - 103500*s1*s3*s5**4*s6**2 + 1680*s1*s4**6*s5*s6 - 468*s1*s4**5*s5**3 +- 346896*s1*s4**3*s5*s6**3 + 93900*s1*s4**2*s5**3*s6**2 + 35000*s1*s4*s5**5*s6 - 9375*s1*s5**7 ++ 108864*s1*s5*s6**5 + 16*s2**8*s4**2*s6**2 - 8*s2**8*s4*s5**2*s6 + s2**8*s5**4 - +8*s2**7*s3**2*s4*s6**2 + 2*s2**7*s3**2*s5**2*s6 - 96*s2**7*s4*s6**3 - 232*s2**7*s5**2*s6**2 ++ s2**6*s3**4*s6**2 + 24*s2**6*s3**2*s6**3 + 336*s2**6*s3*s4*s5*s6**2 + 108*s2**6*s3*s5**3*s6 +- 32*s2**6*s4**3*s6**2 - 160*s2**6*s4**2*s5**2*s6 + 38*s2**6*s4*s5**4 + 144*s2**6*s6**4 +- 84*s2**5*s3**3*s5*s6**2 + 8*s2**5*s3**2*s4**2*s6**2 - 106*s2**5*s3**2*s4*s5**2*s6 +- 12*s2**5*s3**2*s5**4 + 176*s2**5*s3*s4**3*s5*s6 - 36*s2**5*s3*s4**2*s5**3 - 2544*s2**5*s3*s5*s6**3 +- 32*s2**5*s4**5*s6 + 8*s2**5*s4**4*s5**2 - 3072*s2**5*s4**2*s6**3 + 3032*s2**5*s4*s5**2*s6**2 ++ 954*s2**5*s5**4*s6 + 36*s2**4*s3**4*s5**2*s6 - 80*s2**4*s3**3*s4**2*s5*s6 + 25*s2**4*s3**3*s4*s5**3 ++ 16*s2**4*s3**2*s4**4*s6 - 6*s2**4*s3**2*s4**3*s5**2 + 2520*s2**4*s3**2*s4*s6**3 ++ 222*s2**4*s3**2*s5**2*s6**2 - 1312*s2**4*s3*s4**2*s5*s6**2 - 3616*s2**4*s3*s4*s5**3*s6 +- 125*s2**4*s3*s5**5 + 1296*s2**4*s4**4*s6**2 - 168*s2**4*s4**3*s5**2*s6 + 375*s2**4*s4**2*s5**4 ++ 19296*s2**4*s4*s6**4 + 48408*s2**4*s5**2*s6**3 + 9*s2**3*s3**5*s4*s5*s6 - 4*s2**3*s3**5*s5**3 +- 2*s2**3*s3**4*s4**3*s6 + s2**3*s3**4*s4**2*s5**2 - 432*s2**3*s3**4*s6**3 + 324*s2**3*s3**3*s4*s5*s6**2 ++ 923*s2**3*s3**3*s5**3*s6 - 752*s2**3*s3**2*s4**3*s6**2 + 2262*s2**3*s3**2*s4**2*s5**2*s6 ++ 525*s2**3*s3**2*s4*s5**4 - 9936*s2**3*s3**2*s6**4 - 480*s2**3*s3*s4**4*s5*s6 - 700*s2**3*s3*s4**3*s5**3 +- 68976*s2**3*s3*s4*s5*s6**3 - 11360*s2**3*s3*s5**3*s6**2 - 32*s2**3*s4**6*s6 + 152*s2**3*s4**5*s5**2 ++ 6912*s2**3*s4**3*s6**3 - 7992*s2**3*s4**2*s5**2*s6**2 + 5550*s2**3*s4*s5**4*s6 - +29376*s2**3*s6**5 + 108*s2**2*s3**4*s4**2*s6**2 - 1215*s2**2*s3**4*s4*s5**2*s6 - 150*s2**2*s3**4*s5**4 ++ 336*s2**2*s3**3*s4**3*s5*s6 - 185*s2**2*s3**3*s4**2*s5**3 + 17496*s2**2*s3**3*s5*s6**3 ++ 8*s2**2*s3**2*s4**5*s6 + 370*s2**2*s3**2*s4**4*s5**2 - 864*s2**2*s3**2*s4**2*s6**3 ++ 22572*s2**2*s3**2*s4*s5**2*s6**2 + 225*s2**2*s3**2*s5**4*s6 - 144*s2**2*s3*s4**6*s5 ++ 14784*s2**2*s3*s4**3*s5*s6**2 - 12020*s2**2*s3*s4**2*s5**3*s6 + 625*s2**2*s3*s4*s5**5 ++ 536544*s2**2*s3*s5*s6**4 + 16*s2**2*s4**8 - 3072*s2**2*s4**5*s6**2 + 1096*s2**2*s4**4*s5**2*s6 ++ 250*s2**2*s4**3*s5**4 - 93744*s2**2*s4**2*s6**4 - 249480*s2**2*s4*s5**2*s6**3 + +70125*s2**2*s5**4*s6**2 + 162*s2*s3**6*s5**2*s6 - 54*s2*s3**5*s4**2*s5*s6 + 198*s2*s3**5*s4*s5**3 +- 210*s2*s3**4*s4**3*s5**2 - 3564*s2*s3**4*s5**2*s6**2 + 72*s2*s3**3*s4**5*s5 - 12204*s2*s3**3*s4**2*s5*s6**2 ++ 1935*s2*s3**3*s4*s5**3*s6 - 8*s2*s3**2*s4**7 + 2520*s2*s3**2*s4**4*s6**2 + 1962*s2*s3**2*s4**3*s5**2*s6 +- 125*s2*s3**2*s4**2*s5**4 - 178848*s2*s3**2*s4*s6**4 - 165240*s2*s3**2*s5**2*s6**3 +- 144*s2*s3*s4**5*s5*s6 - 200*s2*s3*s4**4*s5**3 + 139104*s2*s3*s4**2*s5*s6**3 + 72900*s2*s3*s4*s5**3*s6**2 +- 20625*s2*s3*s5**5*s6 - 96*s2*s4**7*s6 + 56*s2*s4**6*s5**2 + 19296*s2*s4**4*s6**3 +- 22080*s2*s4**3*s5**2*s6**2 - 7750*s2*s4**2*s5**4*s6 + 3125*s2*s4*s5**6 + 248832*s2*s4*s6**5 +- 129600*s2*s5**2*s6**4 - 27*s3**7*s5**3 + 27*s3**6*s4**2*s5**2 - 9*s3**5*s4**4*s5 ++ 1944*s3**5*s4*s5*s6**2 + 54*s3**5*s5**3*s6 + s3**4*s4**6 - 432*s3**4*s4**3*s6**2 +- 486*s3**4*s4**2*s5**2*s6 + 46656*s3**4*s6**4 + 36*s3**3*s4**4*s5*s6 + 50*s3**3*s4**3*s5**3 +- 7776*s3**3*s4*s5*s6**3 + 29700*s3**3*s5**3*s6**2 + 24*s3**2*s4**6*s6 - 14*s3**2*s4**5*s5**2 +- 9936*s3**2*s4**3*s6**3 - 29160*s3**2*s4**2*s5**2*s6**2 - 10125*s3**2*s4*s5**4*s6 ++ 3125*s3**2*s5**6 + 1026432*s3**2*s6**5 + 6336*s3*s4**4*s5*s6**2 + 11700*s3*s4**3*s5**3*s6 +- 3125*s3*s4**2*s5**5 - 1477440*s3*s4*s5*s6**4 + 432000*s3*s5**3*s6**3 + 144*s4**6*s6**2 +- 2472*s4**5*s5**2*s6 + 625*s4**4*s5**4 - 29376*s4**3*s6**4 + 529200*s4**2*s5**2*s6**3 +- 292500*s4*s5**4*s6**2 + 40625*s5**6*s6 - 186624*s6**6) + ], + (6, 2): [ + lambda s1, s2, s3, s4, s5, s6: (-s3), + lambda s1, s2, s3, s4, s5, s6: (-s1*s5 + s2*s4 - 9*s6), + lambda s1, s2, s3, s4, s5, s6: (s1*s2*s6 + 2*s1*s3*s5 - s1*s4**2 - s2**2*s5 + 6*s3*s6 + s4*s5), + lambda s1, s2, s3, s4, s5, s6: (s1**2*s4*s6 - s1**2*s5**2 - 3*s1*s2*s3*s6 + s1*s2*s4*s5 + 9*s1*s5*s6 + s2**3*s6 - +9*s2*s4*s6 + s2*s5**2 + 3*s3**2*s6 - 3*s3*s4*s5 + s4**3 + 27*s6**2), + lambda s1, s2, s3, s4, s5, s6: (-2*s1**3*s6**2 + 2*s1**2*s2*s5*s6 + 2*s1**2*s3*s4*s6 - s1**2*s3*s5**2 - s1*s2**2*s4*s6 +- 3*s1*s2*s6**2 - 16*s1*s3*s5*s6 + 4*s1*s4**2*s6 + 2*s1*s4*s5**2 + 4*s2**2*s5*s6 + +s2*s3*s4*s6 + 2*s2*s3*s5**2 - s2*s4**2*s5 - 9*s3*s6**2 - 3*s4*s5*s6 - 2*s5**3), + lambda s1, s2, s3, s4, s5, s6: (s1**3*s3*s6**2 - 3*s1**3*s4*s5*s6 + s1**3*s5**3 - s1**2*s2**2*s6**2 + s1**2*s2*s3*s5*s6 +- 2*s1**2*s4*s6**2 + 6*s1**2*s5**2*s6 + 16*s1*s2*s3*s6**2 - 3*s1*s2*s5**3 - s1*s3**2*s5*s6 +- 2*s1*s3*s4**2*s6 + s1*s3*s4*s5**2 - 30*s1*s5*s6**2 - 4*s2**3*s6**2 - 2*s2**2*s3*s5*s6 ++ s2**2*s4**2*s6 + 18*s2*s4*s6**2 - 2*s2*s5**2*s6 - 15*s3**2*s6**2 + 16*s3*s4*s5*s6 ++ s3*s5**3 - 4*s4**3*s6 - s4**2*s5**2 - 27*s6**3), + lambda s1, s2, s3, s4, s5, s6: (s1**4*s5*s6**2 + 2*s1**3*s2*s4*s6**2 - s1**3*s2*s5**2*s6 - s1**3*s3**2*s6**2 + 9*s1**3*s6**3 +- 14*s1**2*s2*s5*s6**2 - 11*s1**2*s3*s4*s6**2 + 6*s1**2*s3*s5**2*s6 + 3*s1**2*s4**2*s5*s6 +- s1**2*s4*s5**3 + 3*s1*s2**2*s5**2*s6 + 3*s1*s2*s3**2*s6**2 - s1*s2*s3*s4*s5*s6 + +39*s1*s3*s5*s6**2 - 14*s1*s4*s5**2*s6 + s1*s5**4 - 11*s2*s3*s5**2*s6 + 2*s2*s4*s5**3 +- 3*s3**3*s6**2 + 3*s3**2*s4*s5*s6 - s3**2*s5**3 + 9*s5**3*s6), + lambda s1, s2, s3, s4, s5, s6: (-s1**4*s2*s6**3 + s1**4*s3*s5*s6**2 - 4*s1**3*s3*s6**3 + 10*s1**3*s4*s5*s6**2 - 4*s1**3*s5**3*s6 ++ 8*s1**2*s2**2*s6**3 - 8*s1**2*s2*s3*s5*s6**2 - 2*s1**2*s2*s4**2*s6**2 + s1**2*s2*s4*s5**2*s6 ++ s1**2*s3**2*s4*s6**2 - 6*s1**2*s4*s6**3 - 7*s1**2*s5**2*s6**2 - 24*s1*s2*s3*s6**3 +- 4*s1*s2*s4*s5*s6**2 + 10*s1*s2*s5**3*s6 + 8*s1*s3**2*s5*s6**2 + 8*s1*s3*s4**2*s6**2 +- 8*s1*s3*s4*s5**2*s6 + s1*s3*s5**4 + 36*s1*s5*s6**3 + 8*s2**2*s3*s5*s6**2 - 2*s2**2*s4*s5**2*s6 +- 2*s2*s3**2*s4*s6**2 + s2*s3**2*s5**2*s6 - 6*s2*s5**2*s6**2 + 18*s3**2*s6**3 - 24*s3*s4*s5*s6**2 +- 4*s3*s5**3*s6 + 8*s4**2*s5**2*s6 - s4*s5**4), + lambda s1, s2, s3, s4, s5, s6: (-s1**5*s4*s6**3 - 2*s1**4*s5*s6**3 + 3*s1**3*s2*s5**2*s6**2 + 3*s1**3*s3**2*s6**3 +- s1**3*s3*s4*s5*s6**2 - 8*s1**3*s6**4 + 16*s1**2*s2*s5*s6**3 + 8*s1**2*s3*s4*s6**3 +- 6*s1**2*s3*s5**2*s6**2 - 8*s1**2*s4**2*s5*s6**2 + 3*s1**2*s4*s5**3*s6 - 8*s1*s2**2*s5**2*s6**2 +- 8*s1*s2*s3**2*s6**3 + 8*s1*s2*s3*s4*s5*s6**2 - s1*s2*s3*s5**3*s6 - s1*s3**3*s5*s6**2 +- 24*s1*s3*s5*s6**3 + 16*s1*s4*s5**2*s6**2 - 2*s1*s5**4*s6 + 8*s2*s3*s5**2*s6**2 - +s2*s5**5 + 8*s3**3*s6**3 - 8*s3**2*s4*s5*s6**2 + 3*s3**2*s5**3*s6 - 8*s5**3*s6**2), + lambda s1, s2, s3, s4, s5, s6: (s1**6*s6**4 - 4*s1**4*s2*s6**4 - 2*s1**4*s3*s5*s6**3 + s1**4*s4**2*s6**3 + 8*s1**3*s3*s6**4 +- 4*s1**3*s4*s5*s6**3 + 2*s1**3*s5**3*s6**2 + 8*s1**2*s2*s3*s5*s6**3 - 2*s1**2*s2*s4*s5**2*s6**2 +- 2*s1**2*s3**2*s4*s6**3 + s1**2*s3**2*s5**2*s6**2 - 4*s1*s2*s5**3*s6**2 - 12*s1*s3**2*s5*s6**3 ++ 8*s1*s3*s4*s5**2*s6**2 - 2*s1*s3*s5**4*s6 + s2**2*s5**4*s6 - 2*s2*s3**2*s5**2*s6**2 ++ s3**4*s6**3 + 8*s3*s5**3*s6**2 - 4*s4*s5**4*s6 + s5**6) + ], +} diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/subfield.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/subfield.py new file mode 100644 index 0000000000000000000000000000000000000000..c56d0662e4a38b4c0fcaa385c2e0166490354790 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/subfield.py @@ -0,0 +1,516 @@ +r""" +Functions in ``polys.numberfields.subfield`` solve the "Subfield Problem" and +allied problems, for algebraic number fields. + +Following Cohen (see [Cohen93]_ Section 4.5), we can define the main problem as +follows: + +* **Subfield Problem:** + + Given two number fields $\mathbb{Q}(\alpha)$, $\mathbb{Q}(\beta)$ + via the minimal polynomials for their generators $\alpha$ and $\beta$, decide + whether one field is isomorphic to a subfield of the other. + +From a solution to this problem flow solutions to the following problems as +well: + +* **Primitive Element Problem:** + + Given several algebraic numbers + $\alpha_1, \ldots, \alpha_m$, compute a single algebraic number $\theta$ + such that $\mathbb{Q}(\alpha_1, \ldots, \alpha_m) = \mathbb{Q}(\theta)$. + +* **Field Isomorphism Problem:** + + Decide whether two number fields + $\mathbb{Q}(\alpha)$, $\mathbb{Q}(\beta)$ are isomorphic. + +* **Field Membership Problem:** + + Given two algebraic numbers $\alpha$, + $\beta$, decide whether $\alpha \in \mathbb{Q}(\beta)$, and if so write + $\alpha = f(\beta)$ for some $f(x) \in \mathbb{Q}[x]$. +""" + +from sympy.core.add import Add +from sympy.core.numbers import AlgebraicNumber +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify, _sympify +from sympy.ntheory import sieve +from sympy.polys.densetools import dup_eval +from sympy.polys.domains import QQ +from sympy.polys.numberfields.minpoly import _choose_factor, minimal_polynomial +from sympy.polys.polyerrors import IsomorphismFailed +from sympy.polys.polytools import Poly, PurePoly, factor_list +from sympy.utilities import public + +from mpmath import MPContext + + +def is_isomorphism_possible(a, b): + """Necessary but not sufficient test for isomorphism. """ + n = a.minpoly.degree() + m = b.minpoly.degree() + + if m % n != 0: + return False + + if n == m: + return True + + da = a.minpoly.discriminant() + db = b.minpoly.discriminant() + + i, k, half = 1, m//n, db//2 + + while True: + p = sieve[i] + P = p**k + + if P > half: + break + + if ((da % p) % 2) and not (db % P): + return False + + i += 1 + + return True + + +def field_isomorphism_pslq(a, b): + """Construct field isomorphism using PSLQ algorithm. """ + if not a.root.is_real or not b.root.is_real: + raise NotImplementedError("PSLQ doesn't support complex coefficients") + + f = a.minpoly + g = b.minpoly.replace(f.gen) + + n, m, prev = 100, b.minpoly.degree(), None + ctx = MPContext() + + for i in range(1, 5): + A = a.root.evalf(n) + B = b.root.evalf(n) + + basis = [1, B] + [ B**i for i in range(2, m) ] + [-A] + + ctx.dps = n + coeffs = ctx.pslq(basis, maxcoeff=10**10, maxsteps=1000) + + if coeffs is None: + # PSLQ can't find an integer linear combination. Give up. + break + + if coeffs != prev: + prev = coeffs + else: + # Increasing precision didn't produce anything new. Give up. + break + + # We have + # c0 + c1*B + c2*B^2 + ... + cm-1*B^(m-1) - cm*A ~ 0. + # So bring cm*A to the other side, and divide through by cm, + # for an approximate representation of A as a polynomial in B. + # (We know cm != 0 since `b.minpoly` is irreducible.) + coeffs = [S(c)/coeffs[-1] for c in coeffs[:-1]] + + # Throw away leading zeros. + while not coeffs[-1]: + coeffs.pop() + + coeffs = list(reversed(coeffs)) + h = Poly(coeffs, f.gen, domain='QQ') + + # We only have A ~ h(B). We must check whether the relation is exact. + if f.compose(h).rem(g).is_zero: + # Now we know that h(b) is in fact equal to _some conjugate of_ a. + # But from the very precise approximation A ~ h(B) we can assume + # the conjugate is a itself. + return coeffs + else: + n *= 2 + + return None + + +def field_isomorphism_factor(a, b): + """Construct field isomorphism via factorization. """ + _, factors = factor_list(a.minpoly, extension=b) + for f, _ in factors: + if f.degree() == 1: + # Any linear factor f(x) represents some conjugate of a in QQ(b). + # We want to know whether this linear factor represents a itself. + # Let f = x - c + c = -f.rep.TC() + # Write c as polynomial in b + coeffs = c.to_sympy_list() + d, terms = len(coeffs) - 1, [] + for i, coeff in enumerate(coeffs): + terms.append(coeff*b.root**(d - i)) + r = Add(*terms) + # Check whether we got the number a + if a.minpoly.same_root(r, a): + return coeffs + + # If none of the linear factors represented a in QQ(b), then in fact a is + # not an element of QQ(b). + return None + + +@public +def field_isomorphism(a, b, *, fast=True): + r""" + Find an embedding of one number field into another. + + Explanation + =========== + + This function looks for an isomorphism from $\mathbb{Q}(a)$ onto some + subfield of $\mathbb{Q}(b)$. Thus, it solves the Subfield Problem. + + Examples + ======== + + >>> from sympy import sqrt, field_isomorphism, I + >>> print(field_isomorphism(3, sqrt(2))) # doctest: +SKIP + [3] + >>> print(field_isomorphism( I*sqrt(3), I*sqrt(3)/2)) # doctest: +SKIP + [2, 0] + + Parameters + ========== + + a : :py:class:`~.Expr` + Any expression representing an algebraic number. + b : :py:class:`~.Expr` + Any expression representing an algebraic number. + fast : boolean, optional (default=True) + If ``True``, we first attempt a potentially faster way of computing the + isomorphism, falling back on a slower method if this fails. If + ``False``, we go directly to the slower method, which is guaranteed to + return a result. + + Returns + ======= + + List of rational numbers, or None + If $\mathbb{Q}(a)$ is not isomorphic to some subfield of + $\mathbb{Q}(b)$, then return ``None``. Otherwise, return a list of + rational numbers representing an element of $\mathbb{Q}(b)$ to which + $a$ may be mapped, in order to define a monomorphism, i.e. an + isomorphism from $\mathbb{Q}(a)$ to some subfield of $\mathbb{Q}(b)$. + The elements of the list are the coefficients of falling powers of $b$. + + """ + a, b = sympify(a), sympify(b) + + if not a.is_AlgebraicNumber: + a = AlgebraicNumber(a) + + if not b.is_AlgebraicNumber: + b = AlgebraicNumber(b) + + a = a.to_primitive_element() + b = b.to_primitive_element() + + if a == b: + return a.coeffs() + + n = a.minpoly.degree() + m = b.minpoly.degree() + + if n == 1: + return [a.root] + + if m % n != 0: + return None + + if fast: + try: + result = field_isomorphism_pslq(a, b) + + if result is not None: + return result + except NotImplementedError: + pass + + return field_isomorphism_factor(a, b) + + +def _switch_domain(g, K): + # An algebraic relation f(a, b) = 0 over Q can also be written + # g(b) = 0 where g is in Q(a)[x] and h(a) = 0 where h is in Q(b)[x]. + # This function transforms g into h where Q(b) = K. + frep = g.rep.inject() + hrep = frep.eject(K, front=True) + + return g.new(hrep, g.gens[0]) + + +def _linsolve(p): + # Compute root of linear polynomial. + c, d = p.rep.to_list() + return -d/c + + +@public +def primitive_element(extension, x=None, *, ex=False, polys=False): + r""" + Find a single generator for a number field given by several generators. + + Explanation + =========== + + The basic problem is this: Given several algebraic numbers + $\alpha_1, \alpha_2, \ldots, \alpha_n$, find a single algebraic number + $\theta$ such that + $\mathbb{Q}(\alpha_1, \alpha_2, \ldots, \alpha_n) = \mathbb{Q}(\theta)$. + + This function actually guarantees that $\theta$ will be a linear + combination of the $\alpha_i$, with non-negative integer coefficients. + + Furthermore, if desired, this function will tell you how to express each + $\alpha_i$ as a $\mathbb{Q}$-linear combination of the powers of $\theta$. + + Examples + ======== + + >>> from sympy import primitive_element, sqrt, S, minpoly, simplify + >>> from sympy.abc import x + >>> f, lincomb, reps = primitive_element([sqrt(2), sqrt(3)], x, ex=True) + + Then ``lincomb`` tells us the primitive element as a linear combination of + the given generators ``sqrt(2)`` and ``sqrt(3)``. + + >>> print(lincomb) + [1, 1] + + This means the primtiive element is $\sqrt{2} + \sqrt{3}$. + Meanwhile ``f`` is the minimal polynomial for this primitive element. + + >>> print(f) + x**4 - 10*x**2 + 1 + >>> print(minpoly(sqrt(2) + sqrt(3), x)) + x**4 - 10*x**2 + 1 + + Finally, ``reps`` (which was returned only because we set keyword arg + ``ex=True``) tells us how to recover each of the generators $\sqrt{2}$ and + $\sqrt{3}$ as $\mathbb{Q}$-linear combinations of the powers of the + primitive element $\sqrt{2} + \sqrt{3}$. + + >>> print([S(r) for r in reps[0]]) + [1/2, 0, -9/2, 0] + >>> theta = sqrt(2) + sqrt(3) + >>> print(simplify(theta**3/2 - 9*theta/2)) + sqrt(2) + >>> print([S(r) for r in reps[1]]) + [-1/2, 0, 11/2, 0] + >>> print(simplify(-theta**3/2 + 11*theta/2)) + sqrt(3) + + Parameters + ========== + + extension : list of :py:class:`~.Expr` + Each expression must represent an algebraic number $\alpha_i$. + x : :py:class:`~.Symbol`, optional (default=None) + The desired symbol to appear in the computed minimal polynomial for the + primitive element $\theta$. If ``None``, we use a dummy symbol. + ex : boolean, optional (default=False) + If and only if ``True``, compute the representation of each $\alpha_i$ + as a $\mathbb{Q}$-linear combination over the powers of $\theta$. + polys : boolean, optional (default=False) + If ``True``, return the minimal polynomial as a :py:class:`~.Poly`. + Otherwise return it as an :py:class:`~.Expr`. + + Returns + ======= + + Pair (f, coeffs) or triple (f, coeffs, reps), where: + ``f`` is the minimal polynomial for the primitive element. + ``coeffs`` gives the primitive element as a linear combination of the + given generators. + ``reps`` is present if and only if argument ``ex=True`` was passed, + and is a list of lists of rational numbers. Each list gives the + coefficients of falling powers of the primitive element, to recover + one of the original, given generators. + + """ + if not extension: + raise ValueError("Cannot compute primitive element for empty extension") + extension = [_sympify(ext) for ext in extension] + + if x is not None: + x, cls = sympify(x), Poly + else: + x, cls = Dummy('x'), PurePoly + + def _canonicalize(f): + _, f = f.primitive() + if f.LC() < 0: + f = -f + return f + + if not ex: + gen, coeffs = extension[0], [1] + g = minimal_polynomial(gen, x, polys=True) + for ext in extension[1:]: + if ext.is_Rational: + coeffs.append(0) + continue + _, factors = factor_list(g, extension=ext) + g = _choose_factor(factors, x, gen) + [s], _, g = g.sqf_norm() + gen += s*ext + coeffs.append(s) + + g = _canonicalize(g) + if not polys: + return g.as_expr(), coeffs + else: + return cls(g), coeffs + + gen, coeffs = extension[0], [1] + f = minimal_polynomial(gen, x, polys=True) + K = QQ.algebraic_field((f, gen)) # incrementally constructed field + reps = [K.unit] # representations of extension elements in K + for ext in extension[1:]: + if ext.is_Rational: + coeffs.append(0) # rational ext is not included in the expression of a primitive element + reps.append(K.convert(ext)) # but it is included in reps + continue + p = minimal_polynomial(ext, x, polys=True) + L = QQ.algebraic_field((p, ext)) + _, factors = factor_list(f, domain=L) + f = _choose_factor(factors, x, gen) + [s], g, f = f.sqf_norm() + gen += s*ext + coeffs.append(s) + K = QQ.algebraic_field((f, gen)) + h = _switch_domain(g, K) + erep = _linsolve(h.gcd(p)) # ext as element of K + ogen = K.unit - s*erep # old gen as element of K + reps = [dup_eval(_.to_list(), ogen, K) for _ in reps] + [erep] + + if K.ext.root.is_Rational: # all extensions are rational + H = [K.convert(_).rep for _ in extension] + coeffs = [0]*len(extension) + f = cls(x, domain=QQ) + else: + H = [_.to_list() for _ in reps] + + f = _canonicalize(f) + if not polys: + return f.as_expr(), coeffs, H + else: + return f, coeffs, H + + +@public +def to_number_field(extension, theta=None, *, gen=None, alias=None): + r""" + Express one algebraic number in the field generated by another. + + Explanation + =========== + + Given two algebraic numbers $\eta, \theta$, this function either expresses + $\eta$ as an element of $\mathbb{Q}(\theta)$, or else raises an exception + if $\eta \not\in \mathbb{Q}(\theta)$. + + This function is essentially just a convenience, utilizing + :py:func:`~.field_isomorphism` (our solution of the Subfield Problem) to + solve this, the Field Membership Problem. + + As an additional convenience, this function allows you to pass a list of + algebraic numbers $\alpha_1, \alpha_2, \ldots, \alpha_n$ instead of $\eta$. + It then computes $\eta$ for you, as a solution of the Primitive Element + Problem, using :py:func:`~.primitive_element` on the list of $\alpha_i$. + + Examples + ======== + + >>> from sympy import sqrt, to_number_field + >>> eta = sqrt(2) + >>> theta = sqrt(2) + sqrt(3) + >>> a = to_number_field(eta, theta) + >>> print(type(a)) + + >>> a.root + sqrt(2) + sqrt(3) + >>> print(a) + sqrt(2) + >>> a.coeffs() + [1/2, 0, -9/2, 0] + + We get an :py:class:`~.AlgebraicNumber`, whose ``.root`` is $\theta$, whose + value is $\eta$, and whose ``.coeffs()`` show how to write $\eta$ as a + $\mathbb{Q}$-linear combination in falling powers of $\theta$. + + Parameters + ========== + + extension : :py:class:`~.Expr` or list of :py:class:`~.Expr` + Either the algebraic number that is to be expressed in the other field, + or else a list of algebraic numbers, a primitive element for which is + to be expressed in the other field. + theta : :py:class:`~.Expr`, None, optional (default=None) + If an :py:class:`~.Expr` representing an algebraic number, behavior is + as described under **Explanation**. If ``None``, then this function + reduces to a shorthand for calling :py:func:`~.primitive_element` on + ``extension`` and turning the computed primitive element into an + :py:class:`~.AlgebraicNumber`. + gen : :py:class:`~.Symbol`, None, optional (default=None) + If provided, this will be used as the generator symbol for the minimal + polynomial in the returned :py:class:`~.AlgebraicNumber`. + alias : str, :py:class:`~.Symbol`, None, optional (default=None) + If provided, this will be used as the alias symbol for the returned + :py:class:`~.AlgebraicNumber`. + + Returns + ======= + + AlgebraicNumber + Belonging to $\mathbb{Q}(\theta)$ and equaling $\eta$. + + Raises + ====== + + IsomorphismFailed + If $\eta \not\in \mathbb{Q}(\theta)$. + + See Also + ======== + + field_isomorphism + primitive_element + + """ + if hasattr(extension, '__iter__'): + extension = list(extension) + else: + extension = [extension] + + if len(extension) == 1 and isinstance(extension[0], tuple): + return AlgebraicNumber(extension[0], alias=alias) + + minpoly, coeffs = primitive_element(extension, gen, polys=True) + root = sum(coeff*ext for coeff, ext in zip(coeffs, extension)) + + if theta is None: + return AlgebraicNumber((minpoly, root), alias=alias) + else: + theta = sympify(theta) + + if not theta.is_AlgebraicNumber: + theta = AlgebraicNumber(theta, gen=gen, alias=alias) + + coeffs = field_isomorphism(root, theta) + + if coeffs is not None: + return AlgebraicNumber(theta, coeffs, alias=alias) + else: + raise IsomorphismFailed( + "%s is not in a subfield of %s" % (root, theta.root)) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8cbe04eb1323f96ff38d52888ca37a380ac071ff Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_basis.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_basis.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d182624a35a099bfb96ebf81b2b5c101dc6d19b0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_basis.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_galoisgroups.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_galoisgroups.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e96405ebadc4dc390aafad96e69999f155c938b2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_galoisgroups.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_minpoly.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_minpoly.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a8af1e26066c25a2b90a93f6caf965286132c42 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_minpoly.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_modules.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_modules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c976a916d54927e2f5ab7bbca5f4ca6083b5e85 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_modules.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_numbers.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_numbers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a88abb59fbd7c48e1c774764821c231c9f8f860 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_numbers.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_primes.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_primes.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3dd5559e8bb73a4df2b84b59b6373dbb4b7cd36 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_primes.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_subfield.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_subfield.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..115c56ca459c74801eb11106ac605d0d188ba9f1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_subfield.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_utilities.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_utilities.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6dbf9c23135951fd6b460ea3473f4241f0962417 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/__pycache__/test_utilities.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_basis.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_basis.py new file mode 100644 index 0000000000000000000000000000000000000000..c0ed017936cc5c24da63ac02ceca0480f1945feb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_basis.py @@ -0,0 +1,85 @@ +from sympy.abc import x +from sympy.core import S +from sympy.core.numbers import AlgebraicNumber +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.polys import Poly, cyclotomic_poly +from sympy.polys.domains import QQ +from sympy.polys.matrices import DomainMatrix, DM +from sympy.polys.numberfields.basis import round_two +from sympy.testing.pytest import raises + + +def test_round_two(): + # Poly must be irreducible, and over ZZ or QQ: + raises(ValueError, lambda: round_two(Poly(x ** 2 - 1))) + raises(ValueError, lambda: round_two(Poly(x ** 2 + sqrt(2)))) + + # Test on many fields: + cases = ( + # A couple of cyclotomic fields: + (cyclotomic_poly(5), DomainMatrix.eye(4, QQ), 125), + (cyclotomic_poly(7), DomainMatrix.eye(6, QQ), -16807), + # A couple of quadratic fields (one 1 mod 4, one 3 mod 4): + (x ** 2 - 5, DM([[1, (1, 2)], [0, (1, 2)]], QQ), 5), + (x ** 2 - 7, DM([[1, 0], [0, 1]], QQ), 28), + # Dedekind's example of a field with 2 as essential disc divisor: + (x ** 3 + x ** 2 - 2 * x + 8, DM([[1, 0, 0], [0, 1, 0], [0, (1, 2), (1, 2)]], QQ).transpose(), -503), + # A bunch of cubics with various forms for F -- all of these require + # second or third enlargements. (Five of them require a third, while the rest require just a second.) + # F = 2^2 + (x**3 + 3 * x**2 - 4 * x + 4, DM([((1, 2), (1, 4), (1, 4)), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), -83), + # F = 2^2 * 3 + (x**3 + 3 * x**2 + 3 * x - 3, DM([((1, 2), 0, (1, 2)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -108), + # F = 2^3 + (x**3 + 5 * x**2 - x + 3, DM([((1, 4), 0, (3, 4)), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), -31), + # F = 2^2 * 5 + (x**3 + 5 * x**2 - 5 * x - 5, DM([((1, 2), 0, (1, 2)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), 1300), + # F = 3^2 + (x**3 + 3 * x**2 + 5, DM([((1, 3), (1, 3), (1, 3)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -135), + # F = 3^3 + (x**3 + 6 * x**2 + 3 * x - 1, DM([((1, 3), (1, 3), (1, 3)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), 81), + # F = 2^2 * 3^2 + (x**3 + 6 * x**2 + 4, DM([((1, 3), (2, 3), (1, 3)), (0, 1, 0), (0, 0, (1, 2))], QQ).transpose(), -108), + # F = 2^3 * 7 + (x**3 + 7 * x**2 + 7 * x - 7, DM([((1, 4), 0, (3, 4)), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), 49), + # F = 2^2 * 13 + (x**3 + 7 * x**2 - x + 5, DM([((1, 2), 0, (1, 2)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -2028), + # F = 2^4 + (x**3 + 7 * x**2 - 5 * x + 5, DM([((1, 4), 0, (3, 4)), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), -140), + # F = 5^2 + (x**3 + 4 * x**2 - 3 * x + 7, DM([((1, 5), (4, 5), (4, 5)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -175), + # F = 7^2 + (x**3 + 8 * x**2 + 5 * x - 1, DM([((1, 7), (6, 7), (2, 7)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), 49), + # F = 2 * 5 * 7 + (x**3 + 8 * x**2 - 2 * x + 6, DM([(1, 0, 0), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -14700), + # F = 2^2 * 3 * 5 + (x**3 + 6 * x**2 - 3 * x + 8, DM([(1, 0, 0), (0, (1, 4), (1, 4)), (0, 0, 1)], QQ).transpose(), -675), + # F = 2 * 3^2 * 7 + (x**3 + 9 * x**2 + 6 * x - 8, DM([(1, 0, 0), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), 3969), + # F = 2^2 * 3^2 * 7 + (x**3 + 15 * x**2 - 9 * x + 13, DM([((1, 6), (1, 3), (1, 6)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -5292), + # Polynomial need not be monic + (5*x**3 + 5*x**2 - 10 * x + 40, DM([[1, 0, 0], [0, 1, 0], [0, (1, 2), (1, 2)]], QQ).transpose(), -503), + # Polynomial can have non-integer rational coeffs + (QQ(5, 3)*x**3 + QQ(5, 3)*x**2 - QQ(10, 3)*x + QQ(40, 3), DM([[1, 0, 0], [0, 1, 0], [0, (1, 2), (1, 2)]], QQ).transpose(), -503), + ) + for f, B_exp, d_exp in cases: + K = QQ.alg_field_from_poly(f) + B = K.maximal_order().QQ_matrix + d = K.discriminant() + assert d == d_exp + # The computed basis need not equal the expected one, but their quotient + # must be unimodular: + assert (B.inv()*B_exp).det()**2 == 1 + + +def test_AlgebraicField_integral_basis(): + alpha = AlgebraicNumber(sqrt(5), alias='alpha') + k = QQ.algebraic_field(alpha) + B0 = k.integral_basis() + B1 = k.integral_basis(fmt='sympy') + B2 = k.integral_basis(fmt='alg') + assert B0 == [k([1]), k([S.Half, S.Half])] + assert B1 == [1, S.Half + alpha/2] + assert B2 == [k.ext.field_element([1]), + k.ext.field_element([S.Half, S.Half])] diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_galoisgroups.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_galoisgroups.py new file mode 100644 index 0000000000000000000000000000000000000000..e4cb3d51bcdfad7764b3f6f62dbd2049e466e9e1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_galoisgroups.py @@ -0,0 +1,143 @@ +"""Tests for computing Galois groups. """ + +from sympy.abc import x +from sympy.combinatorics.galois import ( + S1TransitiveSubgroups, S2TransitiveSubgroups, S3TransitiveSubgroups, + S4TransitiveSubgroups, S5TransitiveSubgroups, S6TransitiveSubgroups, +) +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.numberfields.galoisgroups import ( + tschirnhausen_transformation, + galois_group, + _galois_group_degree_4_root_approx, + _galois_group_degree_5_hybrid, +) +from sympy.polys.numberfields.subfield import field_isomorphism +from sympy.polys.polytools import Poly +from sympy.testing.pytest import raises + + +def test_tschirnhausen_transformation(): + for T in [ + Poly(x**2 - 2), + Poly(x**2 + x + 1), + Poly(x**4 + 1), + Poly(x**4 - x**3 + x**2 - x + 1), + ]: + _, U = tschirnhausen_transformation(T) + assert U.degree() == T.degree() + assert U.is_monic + assert U.is_irreducible + K = QQ.alg_field_from_poly(T) + L = QQ.alg_field_from_poly(U) + assert field_isomorphism(K.ext, L.ext) is not None + + +# Test polys are from: +# Cohen, H. *A Course in Computational Algebraic Number Theory*. +test_polys_by_deg = { + # Degree 1 + 1: [ + (x, S1TransitiveSubgroups.S1, True) + ], + # Degree 2 + 2: [ + (x**2 + x + 1, S2TransitiveSubgroups.S2, False) + ], + # Degree 3 + 3: [ + (x**3 + x**2 - 2*x - 1, S3TransitiveSubgroups.A3, True), + (x**3 + 2, S3TransitiveSubgroups.S3, False), + ], + # Degree 4 + 4: [ + (x**4 + x**3 + x**2 + x + 1, S4TransitiveSubgroups.C4, False), + (x**4 + 1, S4TransitiveSubgroups.V, True), + (x**4 - 2, S4TransitiveSubgroups.D4, False), + (x**4 + 8*x + 12, S4TransitiveSubgroups.A4, True), + (x**4 + x + 1, S4TransitiveSubgroups.S4, False), + ], + # Degree 5 + 5: [ + (x**5 + x**4 - 4*x**3 - 3*x**2 + 3*x + 1, S5TransitiveSubgroups.C5, True), + (x**5 - 5*x + 12, S5TransitiveSubgroups.D5, True), + (x**5 + 2, S5TransitiveSubgroups.M20, False), + (x**5 + 20*x + 16, S5TransitiveSubgroups.A5, True), + (x**5 - x + 1, S5TransitiveSubgroups.S5, False), + ], + # Degree 6 + 6: [ + (x**6 + x**5 + x**4 + x**3 + x**2 + x + 1, S6TransitiveSubgroups.C6, False), + (x**6 + 108, S6TransitiveSubgroups.S3, False), + (x**6 + 2, S6TransitiveSubgroups.D6, False), + (x**6 - 3*x**2 - 1, S6TransitiveSubgroups.A4, True), + (x**6 + 3*x**3 + 3, S6TransitiveSubgroups.G18, False), + (x**6 - 3*x**2 + 1, S6TransitiveSubgroups.A4xC2, False), + (x**6 - 4*x**2 - 1, S6TransitiveSubgroups.S4p, True), + (x**6 - 3*x**5 + 6*x**4 - 7*x**3 + 2*x**2 + x - 4, S6TransitiveSubgroups.S4m, False), + (x**6 + 2*x**3 - 2, S6TransitiveSubgroups.G36m, False), + (x**6 + 2*x**2 + 2, S6TransitiveSubgroups.S4xC2, False), + (x**6 + 10*x**5 + 55*x**4 + 140*x**3 + 175*x**2 + 170*x + 25, S6TransitiveSubgroups.PSL2F5, True), + (x**6 + 10*x**5 + 55*x**4 + 140*x**3 + 175*x**2 - 3019*x + 25, S6TransitiveSubgroups.PGL2F5, False), + (x**6 + 6*x**4 + 2*x**3 + 9*x**2 + 6*x - 4, S6TransitiveSubgroups.G36p, True), + (x**6 + 2*x**4 + 2*x**3 + x**2 + 2*x + 2, S6TransitiveSubgroups.G72, False), + (x**6 + 24*x - 20, S6TransitiveSubgroups.A6, True), + (x**6 + x + 1, S6TransitiveSubgroups.S6, False), + ], +} + + +def test_galois_group(): + """ + Try all the test polys. + """ + for deg in range(1, 7): + polys = test_polys_by_deg[deg] + for T, G, alt in polys: + assert galois_group(T, by_name=True) == (G, alt) + + +def test_galois_group_degree_out_of_bounds(): + raises(ValueError, lambda: galois_group(Poly(0, x))) + raises(ValueError, lambda: galois_group(Poly(1, x))) + raises(ValueError, lambda: galois_group(Poly(x ** 7 + 1))) + + +def test_galois_group_not_by_name(): + """ + Check at least one polynomial of each supported degree, to see that + conversion from name to group works. + """ + for deg in range(1, 7): + T, G_name, _ = test_polys_by_deg[deg][0] + G, _ = galois_group(T) + assert G == G_name.get_perm_group() + + +def test_galois_group_not_monic_over_ZZ(): + """ + Check that we can work with polys that are not monic over ZZ. + """ + for deg in range(1, 7): + T, G, alt = test_polys_by_deg[deg][0] + assert galois_group(T/2, by_name=True) == (G, alt) + + +def test__galois_group_degree_4_root_approx(): + for T, G, alt in test_polys_by_deg[4]: + assert _galois_group_degree_4_root_approx(Poly(T)) == (G, alt) + + +def test__galois_group_degree_5_hybrid(): + for T, G, alt in test_polys_by_deg[5]: + assert _galois_group_degree_5_hybrid(Poly(T)) == (G, alt) + + +def test_AlgebraicField_galois_group(): + k = QQ.alg_field_from_poly(Poly(x**4 + 1)) + G, _ = k.galois_group(by_name=True) + assert G == S4TransitiveSubgroups.V + + k = QQ.alg_field_from_poly(Poly(x**4 - 2)) + G, _ = k.galois_group(by_name=True) + assert G == S4TransitiveSubgroups.D4 diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_minpoly.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_minpoly.py new file mode 100644 index 0000000000000000000000000000000000000000..792e5ad6e136bb00abda0b0739b2fff4fd41937b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_minpoly.py @@ -0,0 +1,490 @@ +"""Tests for minimal polynomials. """ + +from sympy.core.function import expand +from sympy.core import (GoldenRatio, TribonacciConstant) +from sympy.core.numbers import (AlgebraicNumber, I, Rational, oo, pi) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import (cbrt, sqrt) +from sympy.functions.elementary.trigonometric import (cos, sin, tan) +from sympy.ntheory.generate import nextprime +from sympy.polys.polytools import Poly +from sympy.polys.rootoftools import CRootOf +from sympy.solvers.solveset import nonlinsolve +from sympy.geometry import Circle, intersection +from sympy.testing.pytest import raises, slow +from sympy.sets.sets import FiniteSet +from sympy.geometry.point import Point2D +from sympy.polys.numberfields.minpoly import ( + minimal_polynomial, + _choose_factor, + _minpoly_op_algebraic_element, + _separate_sq, + _minpoly_groebner, +) +from sympy.polys.partfrac import apart +from sympy.polys.polyerrors import ( + NotAlgebraic, + GeneratorsError, +) + +from sympy.polys.domains import QQ +from sympy.polys.rootoftools import rootof +from sympy.polys.polytools import degree + +from sympy.abc import x, y, z + +Q = Rational + + +def test_minimal_polynomial(): + assert minimal_polynomial(-7, x) == x + 7 + assert minimal_polynomial(-1, x) == x + 1 + assert minimal_polynomial( 0, x) == x + assert minimal_polynomial( 1, x) == x - 1 + assert minimal_polynomial( 7, x) == x - 7 + + assert minimal_polynomial(sqrt(2), x) == x**2 - 2 + assert minimal_polynomial(sqrt(5), x) == x**2 - 5 + assert minimal_polynomial(sqrt(6), x) == x**2 - 6 + + assert minimal_polynomial(2*sqrt(2), x) == x**2 - 8 + assert minimal_polynomial(3*sqrt(5), x) == x**2 - 45 + assert minimal_polynomial(4*sqrt(6), x) == x**2 - 96 + + assert minimal_polynomial(2*sqrt(2) + 3, x) == x**2 - 6*x + 1 + assert minimal_polynomial(3*sqrt(5) + 6, x) == x**2 - 12*x - 9 + assert minimal_polynomial(4*sqrt(6) + 7, x) == x**2 - 14*x - 47 + + assert minimal_polynomial(2*sqrt(2) - 3, x) == x**2 + 6*x + 1 + assert minimal_polynomial(3*sqrt(5) - 6, x) == x**2 + 12*x - 9 + assert minimal_polynomial(4*sqrt(6) - 7, x) == x**2 + 14*x - 47 + + assert minimal_polynomial(sqrt(1 + sqrt(6)), x) == x**4 - 2*x**2 - 5 + assert minimal_polynomial(sqrt(I + sqrt(6)), x) == x**8 - 10*x**4 + 49 + + assert minimal_polynomial(2*I + sqrt(2 + I), x) == x**4 + 4*x**2 + 8*x + 37 + + assert minimal_polynomial(sqrt(2) + sqrt(3), x) == x**4 - 10*x**2 + 1 + assert minimal_polynomial( + sqrt(2) + sqrt(3) + sqrt(6), x) == x**4 - 22*x**2 - 48*x - 23 + + a = 1 - 9*sqrt(2) + 7*sqrt(3) + + assert minimal_polynomial( + 1/a, x) == 392*x**4 - 1232*x**3 + 612*x**2 + 4*x - 1 + assert minimal_polynomial( + 1/sqrt(a), x) == 392*x**8 - 1232*x**6 + 612*x**4 + 4*x**2 - 1 + + raises(NotAlgebraic, lambda: minimal_polynomial(oo, x)) + raises(NotAlgebraic, lambda: minimal_polynomial(2**y, x)) + raises(NotAlgebraic, lambda: minimal_polynomial(sin(1), x)) + + assert minimal_polynomial(sqrt(2)).dummy_eq(x**2 - 2) + assert minimal_polynomial(sqrt(2), x) == x**2 - 2 + + assert minimal_polynomial(sqrt(2), polys=True) == Poly(x**2 - 2) + assert minimal_polynomial(sqrt(2), x, polys=True) == Poly(x**2 - 2, domain='QQ') + assert minimal_polynomial(sqrt(2), x, polys=True, compose=False) == Poly(x**2 - 2, domain='QQ') + + a = AlgebraicNumber(sqrt(2)) + b = AlgebraicNumber(sqrt(3)) + + assert minimal_polynomial(a, x) == x**2 - 2 + assert minimal_polynomial(b, x) == x**2 - 3 + + assert minimal_polynomial(a, x, polys=True) == Poly(x**2 - 2, domain='QQ') + assert minimal_polynomial(b, x, polys=True) == Poly(x**2 - 3, domain='QQ') + + assert minimal_polynomial(sqrt(a/2 + 17), x) == 2*x**4 - 68*x**2 + 577 + assert minimal_polynomial(sqrt(b/2 + 17), x) == 4*x**4 - 136*x**2 + 1153 + + a, b = sqrt(2)/3 + 7, AlgebraicNumber(sqrt(2)/3 + 7) + + f = 81*x**8 - 2268*x**6 - 4536*x**5 + 22644*x**4 + 63216*x**3 - \ + 31608*x**2 - 189648*x + 141358 + + assert minimal_polynomial(sqrt(a) + sqrt(sqrt(a)), x) == f + assert minimal_polynomial(sqrt(b) + sqrt(sqrt(b)), x) == f + + assert minimal_polynomial( + a**Q(3, 2), x) == 729*x**4 - 506898*x**2 + 84604519 + + # issue 5994 + eq = S(''' + -1/(800*sqrt(-1/240 + 1/(18000*(-1/17280000 + + sqrt(15)*I/28800000)**(1/3)) + 2*(-1/17280000 + + sqrt(15)*I/28800000)**(1/3)))''') + assert minimal_polynomial(eq, x) == 8000*x**2 - 1 + + ex = (sqrt(5)*sqrt(I)/(5*sqrt(1 + 125*I)) + + 25*sqrt(5)/(I**Q(5,2)*(1 + 125*I)**Q(3,2)) + + 3125*sqrt(5)/(I**Q(11,2)*(1 + 125*I)**Q(3,2)) + + 5*I*sqrt(1 - I/125)) + mp = minimal_polynomial(ex, x) + assert mp == 25*x**4 + 5000*x**2 + 250016 + + ex = 1 + sqrt(2) + sqrt(3) + mp = minimal_polynomial(ex, x) + assert mp == x**4 - 4*x**3 - 4*x**2 + 16*x - 8 + + ex = 1/(1 + sqrt(2) + sqrt(3)) + mp = minimal_polynomial(ex, x) + assert mp == 8*x**4 - 16*x**3 + 4*x**2 + 4*x - 1 + + p = (expand((1 + sqrt(2) - 2*sqrt(3) + sqrt(7))**3))**Rational(1, 3) + mp = minimal_polynomial(p, x) + assert mp == x**8 - 8*x**7 - 56*x**6 + 448*x**5 + 480*x**4 - 5056*x**3 + 1984*x**2 + 7424*x - 3008 + p = expand((1 + sqrt(2) - 2*sqrt(3) + sqrt(7))**3) + mp = minimal_polynomial(p, x) + assert mp == x**8 - 512*x**7 - 118208*x**6 + 31131136*x**5 + 647362560*x**4 - 56026611712*x**3 + 116994310144*x**2 + 404854931456*x - 27216576512 + + assert minimal_polynomial(S("-sqrt(5)/2 - 1/2 + (-sqrt(5)/2 - 1/2)**2"), x) == x - 1 + a = 1 + sqrt(2) + assert minimal_polynomial((a*sqrt(2) + a)**3, x) == x**2 - 198*x + 1 + + p = 1/(1 + sqrt(2) + sqrt(3)) + assert minimal_polynomial(p, x, compose=False) == 8*x**4 - 16*x**3 + 4*x**2 + 4*x - 1 + + p = 2/(1 + sqrt(2) + sqrt(3)) + assert minimal_polynomial(p, x, compose=False) == x**4 - 4*x**3 + 2*x**2 + 4*x - 2 + + assert minimal_polynomial(1 + sqrt(2)*I, x, compose=False) == x**2 - 2*x + 3 + assert minimal_polynomial(1/(1 + sqrt(2)) + 1, x, compose=False) == x**2 - 2 + assert minimal_polynomial(sqrt(2)*I + I*(1 + sqrt(2)), x, + compose=False) == x**4 + 18*x**2 + 49 + + # minimal polynomial of I + assert minimal_polynomial(I, x, domain=QQ.algebraic_field(I)) == x - I + K = QQ.algebraic_field(I*(sqrt(2) + 1)) + assert minimal_polynomial(I, x, domain=K) == x - I + assert minimal_polynomial(I, x, domain=QQ) == x**2 + 1 + assert minimal_polynomial(I, x, domain='QQ(y)') == x**2 + 1 + + #issue 11553 + assert minimal_polynomial(GoldenRatio, x) == x**2 - x - 1 + assert minimal_polynomial(TribonacciConstant + 3, x) == x**3 - 10*x**2 + 32*x - 34 + assert minimal_polynomial(GoldenRatio, x, domain=QQ.algebraic_field(sqrt(5))) == \ + 2*x - sqrt(5) - 1 + assert minimal_polynomial(TribonacciConstant, x, domain=QQ.algebraic_field(cbrt(19 - 3*sqrt(33)))) == \ + 48*x - 19*(19 - 3*sqrt(33))**Rational(2, 3) - 3*sqrt(33)*(19 - 3*sqrt(33))**Rational(2, 3) \ + - 16*(19 - 3*sqrt(33))**Rational(1, 3) - 16 + + # AlgebraicNumber with an alias. + # Wester H24 + phi = AlgebraicNumber(S.GoldenRatio.expand(func=True), alias='phi') + assert minimal_polynomial(phi, x) == x**2 - x - 1 + + +def test_issue_26903(): + p1 = nextprime(10**16) # greater than 10**15 + p2 = nextprime(p1) + assert sqrt(p1**2*p2).is_Pow # square not extracted + zero = sqrt(p1**2*p2) - p1*sqrt(p2) + assert minimal_polynomial(zero, x) == x + assert minimal_polynomial(sqrt(2) - zero, x) == x**2 - 2 + + +def test_issue_8353(): + assert minimal_polynomial(exp(3*I*pi, evaluate=False), x) == x + 1 + assert minimal_polynomial(Pow(8, S(1)/3, evaluate=False), x + ) == x - 2 + + +def test_minimal_polynomial_issue_19732(): + # https://github.com/sympy/sympy/issues/19732 + expr = (-280898097948878450887044002323982963174671632174995451265117559518123750720061943079105185551006003416773064305074191140286225850817291393988597615/(-488144716373031204149459129212782509078221364279079444636386844223983756114492222145074506571622290776245390771587888364089507840000000*sqrt(238368341569)*sqrt(S(11918417078450)/63568729 + - 24411360*sqrt(238368341569)/63568729) + + 238326799225996604451373809274348704114327860564921529846705817404208077866956345381951726531296652901169111729944612727047670549086208000000*sqrt(S(11918417078450)/63568729 + - 24411360*sqrt(238368341569)/63568729)) - + 180561807339168676696180573852937120123827201075968945871075967679148461189459480842956689723484024031016208588658753107/(-59358007109636562851035004992802812513575019937126272896569856090962677491318275291141463850327474176000000*sqrt(238368341569)*sqrt(S(11918417078450)/63568729 + - 24411360*sqrt(238368341569)/63568729) + + 28980348180319251787320809875930301310576055074938369007463004788921613896002936637780993064387310446267596800000*sqrt(S(11918417078450)/63568729 + - 24411360*sqrt(238368341569)/63568729))) + poly = (2151288870990266634727173620565483054187142169311153766675688628985237817262915166497766867289157986631135400926544697981091151416655364879773546003475813114962656742744975460025956167152918469472166170500512008351638710934022160294849059721218824490226159355197136265032810944357335461128949781377875451881300105989490353140886315677977149440000000000000000000000*x**4 + - 5773274155644072033773937864114266313663195672820501581692669271302387257492905909558846459600429795784309388968498783843631580008547382703258503404023153694528041873101120067477617592651525155101107144042679962433039557235772239171616433004024998230222455940044709064078962397144550855715640331680262171410099614469231080995436488414164502751395405398078353242072696360734131090111239998110773292915337556205692674790561090109440000000000000*x**2 + + 211295968822207088328287206509522887719741955693091053353263782924470627623790749534705683380138972642560898936171035770539616881000369889020398551821767092685775598633794696371561234818461806577723412581353857653829324364446419444210520602157621008010129702779407422072249192199762604318993590841636967747488049176548615614290254356975376588506729604345612047361483789518445332415765213187893207704958013682516462853001964919444736320672860140355089) + assert minimal_polynomial(expr, x) == poly + + +def test_minimal_polynomial_hi_prec(): + p = 1/sqrt(1 - 9*sqrt(2) + 7*sqrt(3) + Rational(1, 10)**30) + mp = minimal_polynomial(p, x) + # checked with Wolfram Alpha + assert mp.coeff(x**6) == -1232000000000000000000000000001223999999999999999999999999999987999999999999999999999999999996000000000000000000000000000000 + + +def test_minimal_polynomial_sq(): + from sympy.core.add import Add + from sympy.core.function import expand_multinomial + p = expand_multinomial((1 + 5*sqrt(2) + 2*sqrt(3))**3) + mp = minimal_polynomial(p**Rational(1, 3), x) + assert mp == x**4 - 4*x**3 - 118*x**2 + 244*x + 1321 + p = expand_multinomial((1 + sqrt(2) - 2*sqrt(3) + sqrt(7))**3) + mp = minimal_polynomial(p**Rational(1, 3), x) + assert mp == x**8 - 8*x**7 - 56*x**6 + 448*x**5 + 480*x**4 - 5056*x**3 + 1984*x**2 + 7424*x - 3008 + p = Add(*[sqrt(i) for i in range(1, 12)]) + mp = minimal_polynomial(p, x) + assert mp.subs({x: 0}) == -71965773323122507776 + + +def test_minpoly_compose(): + # issue 6868 + eq = S(''' + -1/(800*sqrt(-1/240 + 1/(18000*(-1/17280000 + + sqrt(15)*I/28800000)**(1/3)) + 2*(-1/17280000 + + sqrt(15)*I/28800000)**(1/3)))''') + mp = minimal_polynomial(eq + 3, x) + assert mp == 8000*x**2 - 48000*x + 71999 + + # issue 5888 + assert minimal_polynomial(exp(I*pi/8), x) == x**8 + 1 + + mp = minimal_polynomial(sin(pi/7) + sqrt(2), x) + assert mp == 4096*x**12 - 63488*x**10 + 351488*x**8 - 826496*x**6 + \ + 770912*x**4 - 268432*x**2 + 28561 + mp = minimal_polynomial(cos(pi/7) + sqrt(2), x) + assert mp == 64*x**6 - 64*x**5 - 432*x**4 + 304*x**3 + 712*x**2 - \ + 232*x - 239 + mp = minimal_polynomial(exp(I*pi/7) + sqrt(2), x) + assert mp == x**12 - 2*x**11 - 9*x**10 + 16*x**9 + 43*x**8 - 70*x**7 - 97*x**6 + 126*x**5 + 211*x**4 - 212*x**3 - 37*x**2 + 142*x + 127 + + mp = minimal_polynomial(sin(pi/7) + sqrt(2), x) + assert mp == 4096*x**12 - 63488*x**10 + 351488*x**8 - 826496*x**6 + \ + 770912*x**4 - 268432*x**2 + 28561 + mp = minimal_polynomial(cos(pi/7) + sqrt(2), x) + assert mp == 64*x**6 - 64*x**5 - 432*x**4 + 304*x**3 + 712*x**2 - \ + 232*x - 239 + mp = minimal_polynomial(exp(I*pi/7) + sqrt(2), x) + assert mp == x**12 - 2*x**11 - 9*x**10 + 16*x**9 + 43*x**8 - 70*x**7 - 97*x**6 + 126*x**5 + 211*x**4 - 212*x**3 - 37*x**2 + 142*x + 127 + + mp = minimal_polynomial(exp(I*pi*Rational(2, 7)), x) + assert mp == x**6 + x**5 + x**4 + x**3 + x**2 + x + 1 + mp = minimal_polynomial(exp(I*pi*Rational(2, 15)), x) + assert mp == x**8 - x**7 + x**5 - x**4 + x**3 - x + 1 + mp = minimal_polynomial(cos(pi*Rational(2, 7)), x) + assert mp == 8*x**3 + 4*x**2 - 4*x - 1 + mp = minimal_polynomial(sin(pi*Rational(2, 7)), x) + ex = (5*cos(pi*Rational(2, 7)) - 7)/(9*cos(pi/7) - 5*cos(pi*Rational(3, 7))) + mp = minimal_polynomial(ex, x) + assert mp == x**3 + 2*x**2 - x - 1 + assert minimal_polynomial(-1/(2*cos(pi/7)), x) == x**3 + 2*x**2 - x - 1 + assert minimal_polynomial(sin(pi*Rational(2, 15)), x) == \ + 256*x**8 - 448*x**6 + 224*x**4 - 32*x**2 + 1 + assert minimal_polynomial(sin(pi*Rational(5, 14)), x) == 8*x**3 - 4*x**2 - 4*x + 1 + assert minimal_polynomial(cos(pi/15), x) == 16*x**4 + 8*x**3 - 16*x**2 - 8*x + 1 + + ex = rootof(x**3 +x*4 + 1, 0) + mp = minimal_polynomial(ex, x) + assert mp == x**3 + 4*x + 1 + mp = minimal_polynomial(ex + 1, x) + assert mp == x**3 - 3*x**2 + 7*x - 4 + assert minimal_polynomial(exp(I*pi/3), x) == x**2 - x + 1 + assert minimal_polynomial(exp(I*pi/4), x) == x**4 + 1 + assert minimal_polynomial(exp(I*pi/6), x) == x**4 - x**2 + 1 + assert minimal_polynomial(exp(I*pi/9), x) == x**6 - x**3 + 1 + assert minimal_polynomial(exp(I*pi/10), x) == x**8 - x**6 + x**4 - x**2 + 1 + assert minimal_polynomial(sin(pi/9), x) == 64*x**6 - 96*x**4 + 36*x**2 - 3 + assert minimal_polynomial(sin(pi/11), x) == 1024*x**10 - 2816*x**8 + \ + 2816*x**6 - 1232*x**4 + 220*x**2 - 11 + assert minimal_polynomial(sin(pi/21), x) == 4096*x**12 - 11264*x**10 + \ + 11264*x**8 - 4992*x**6 + 960*x**4 - 64*x**2 + 1 + assert minimal_polynomial(cos(pi/9), x) == 8*x**3 - 6*x - 1 + + ex = 2**Rational(1, 3)*exp(2*I*pi/3) + assert minimal_polynomial(ex, x) == x**3 - 2 + + raises(NotAlgebraic, lambda: minimal_polynomial(cos(pi*sqrt(2)), x)) + raises(NotAlgebraic, lambda: minimal_polynomial(sin(pi*sqrt(2)), x)) + raises(NotAlgebraic, lambda: minimal_polynomial(exp(1.618*I*pi), x)) + raises(NotAlgebraic, lambda: minimal_polynomial(exp(I*pi*sqrt(2)), x)) + + # issue 5934 + ex = 1/(-36000 - 7200*sqrt(5) + (12*sqrt(10)*sqrt(sqrt(5) + 5) + + 24*sqrt(10)*sqrt(-sqrt(5) + 5))**2) + 1 + raises(ZeroDivisionError, lambda: minimal_polynomial(ex, x)) + + ex = sqrt(1 + 2**Rational(1,3)) + sqrt(1 + 2**Rational(1,4)) + sqrt(2) + mp = minimal_polynomial(ex, x) + assert degree(mp) == 48 and mp.subs({x:0}) == -16630256576 + + ex = tan(pi/5, evaluate=False) + mp = minimal_polynomial(ex, x) + assert mp == x**4 - 10*x**2 + 5 + assert mp.subs(x, tan(pi/5)).is_zero + + ex = tan(pi/6, evaluate=False) + mp = minimal_polynomial(ex, x) + assert mp == 3*x**2 - 1 + assert mp.subs(x, tan(pi/6)).is_zero + + ex = tan(pi/10, evaluate=False) + mp = minimal_polynomial(ex, x) + assert mp == 5*x**4 - 10*x**2 + 1 + assert mp.subs(x, tan(pi/10)).is_zero + + raises(NotAlgebraic, lambda: minimal_polynomial(tan(pi*sqrt(2)), x)) + + +def test_minpoly_issue_7113(): + # see discussion in https://github.com/sympy/sympy/pull/2234 + from sympy.simplify.simplify import nsimplify + r = nsimplify(pi, tolerance=0.000000001) + mp = minimal_polynomial(r, x) + assert mp == 1768292677839237920489538677417507171630859375*x**109 - \ + 2734577732179183863586489182929671773182898498218854181690460140337930774573792597743853652058046464 + + +def test_minpoly_issue_23677(): + r1 = CRootOf(4000000*x**3 - 239960000*x**2 + 4782399900*x - 31663998001, 0) + r2 = CRootOf(4000000*x**3 - 239960000*x**2 + 4782399900*x - 31663998001, 1) + num = (7680000000000000000*r1**4*r2**4 - 614323200000000000000*r1**4*r2**3 + + 18458112576000000000000*r1**4*r2**2 - 246896663036160000000000*r1**4*r2 + + 1240473830323209600000000*r1**4 - 614323200000000000000*r1**3*r2**4 + - 1476464424954240000000000*r1**3*r2**2 - 99225501687553535904000000*r1**3 + + 18458112576000000000000*r1**2*r2**4 - 1476464424954240000000000*r1**2*r2**3 + - 593391458458356671712000000*r1**2*r2 + 2981354896834339226880720000*r1**2 + - 246896663036160000000000*r1*r2**4 - 593391458458356671712000000*r1*r2**2 + - 39878756418031796275267195200*r1 + 1240473830323209600000000*r2**4 + - 99225501687553535904000000*r2**3 + 2981354896834339226880720000*r2**2 - + 39878756418031796275267195200*r2 + 200361370275616536577343808012) + mp = (x**3 + 59426520028417434406408556687919*x**2 + + 1161475464966574421163316896737773190861975156439163671112508400*x + + 7467465541178623874454517208254940823818304424383315270991298807299003671748074773558707779600) + assert minimal_polynomial(num, x) == mp + + +def test_minpoly_issue_7574(): + ex = -(-1)**Rational(1, 3) + (-1)**Rational(2,3) + assert minimal_polynomial(ex, x) == x + 1 + + +def test_choose_factor(): + # Test that this does not enter an infinite loop: + bad_factors = [Poly(x-2, x), Poly(x+2, x)] + raises(NotImplementedError, lambda: _choose_factor(bad_factors, x, sqrt(3))) + + +def test_minpoly_fraction_field(): + assert minimal_polynomial(1/x, y) == -x*y + 1 + assert minimal_polynomial(1 / (x + 1), y) == (x + 1)*y - 1 + + assert minimal_polynomial(sqrt(x), y) == y**2 - x + assert minimal_polynomial(sqrt(x + 1), y) == y**2 - x - 1 + assert minimal_polynomial(sqrt(x) / x, y) == x*y**2 - 1 + assert minimal_polynomial(sqrt(2) * sqrt(x), y) == y**2 - 2 * x + assert minimal_polynomial(sqrt(2) + sqrt(x), y) == \ + y**4 + (-2*x - 4)*y**2 + x**2 - 4*x + 4 + + assert minimal_polynomial(x**Rational(1,3), y) == y**3 - x + assert minimal_polynomial(x**Rational(1,3) + sqrt(x), y) == \ + y**6 - 3*x*y**4 - 2*x*y**3 + 3*x**2*y**2 - 6*x**2*y - x**3 + x**2 + + assert minimal_polynomial(sqrt(x) / z, y) == z**2*y**2 - x + assert minimal_polynomial(sqrt(x) / (z + 1), y) == (z**2 + 2*z + 1)*y**2 - x + + assert minimal_polynomial(1/x, y, polys=True) == Poly(-x*y + 1, y, domain='ZZ(x)') + assert minimal_polynomial(1 / (x + 1), y, polys=True) == \ + Poly((x + 1)*y - 1, y, domain='ZZ(x)') + assert minimal_polynomial(sqrt(x), y, polys=True) == Poly(y**2 - x, y, domain='ZZ(x)') + assert minimal_polynomial(sqrt(x) / z, y, polys=True) == \ + Poly(z**2*y**2 - x, y, domain='ZZ(x, z)') + + # this is (sqrt(1 + x**3)/x).integrate(x).diff(x) - sqrt(1 + x**3)/x + a = sqrt(x)/sqrt(1 + x**(-3)) - sqrt(x**3 + 1)/x + 1/(x**Rational(5, 2)* \ + (1 + x**(-3))**Rational(3, 2)) + 1/(x**Rational(11, 2)*(1 + x**(-3))**Rational(3, 2)) + + assert minimal_polynomial(a, y) == y + + raises(NotAlgebraic, lambda: minimal_polynomial(exp(x), y)) + raises(GeneratorsError, lambda: minimal_polynomial(sqrt(x), x)) + raises(GeneratorsError, lambda: minimal_polynomial(sqrt(x) - y, x)) + raises(NotImplementedError, lambda: minimal_polynomial(sqrt(x), y, compose=False)) + +@slow +def test_minpoly_fraction_field_slow(): + assert minimal_polynomial(minimal_polynomial(sqrt(x**Rational(1,5) - 1), + y).subs(y, sqrt(x**Rational(1,5) - 1)), z) == z + +def test_minpoly_domain(): + assert minimal_polynomial(sqrt(2), x, domain=QQ.algebraic_field(sqrt(2))) == \ + x - sqrt(2) + assert minimal_polynomial(sqrt(8), x, domain=QQ.algebraic_field(sqrt(2))) == \ + x - 2*sqrt(2) + assert minimal_polynomial(sqrt(Rational(3,2)), x, + domain=QQ.algebraic_field(sqrt(2))) == 2*x**2 - 3 + + raises(NotAlgebraic, lambda: minimal_polynomial(y, x, domain=QQ)) + + +def test_issue_14831(): + a = -2*sqrt(2)*sqrt(12*sqrt(2) + 17) + assert minimal_polynomial(a, x) == x**2 + 16*x - 8 + e = (-3*sqrt(12*sqrt(2) + 17) + 12*sqrt(2) + + 17 - 2*sqrt(2)*sqrt(12*sqrt(2) + 17)) + assert minimal_polynomial(e, x) == x + + +def test_issue_18248(): + assert nonlinsolve([x*y**3-sqrt(2)/3, x*y**6-4/(9*(sqrt(3)))],x,y) == \ + FiniteSet((sqrt(3)/2, sqrt(6)/3), (sqrt(3)/2, -sqrt(6)/6 - sqrt(2)*I/2), + (sqrt(3)/2, -sqrt(6)/6 + sqrt(2)*I/2)) + + +def test_issue_13230(): + c1 = Circle(Point2D(3, sqrt(5)), 5) + c2 = Circle(Point2D(4, sqrt(7)), 6) + assert intersection(c1, c2) == [Point2D(-1 + (-sqrt(7) + sqrt(5))*(-2*sqrt(7)/29 + + 9*sqrt(5)/29 + sqrt(196*sqrt(35) + 1941)/29), -2*sqrt(7)/29 + 9*sqrt(5)/29 + + sqrt(196*sqrt(35) + 1941)/29), Point2D(-1 + (-sqrt(7) + sqrt(5))*(-sqrt(196*sqrt(35) + + 1941)/29 - 2*sqrt(7)/29 + 9*sqrt(5)/29), -sqrt(196*sqrt(35) + 1941)/29 - 2*sqrt(7)/29 + 9*sqrt(5)/29)] + +def test_issue_19760(): + e = 1/(sqrt(1 + sqrt(2)) - sqrt(2)*sqrt(1 + sqrt(2))) + 1 + mp_expected = x**4 - 4*x**3 + 4*x**2 - 2 + + for comp in (True, False): + mp = Poly(minimal_polynomial(e, compose=comp)) + assert mp(x) == mp_expected, "minimal_polynomial(e, compose=%s) = %s; %s expected" % (comp, mp(x), mp_expected) + + +def test_issue_20163(): + assert apart(1/(x**6+1), extension=[sqrt(3), I]) == \ + (sqrt(3) + I)/(2*x + sqrt(3) + I)/6 + \ + (sqrt(3) - I)/(2*x + sqrt(3) - I)/6 - \ + (sqrt(3) - I)/(2*x - sqrt(3) + I)/6 - \ + (sqrt(3) + I)/(2*x - sqrt(3) - I)/6 + \ + I/(x + I)/6 - I/(x - I)/6 + + +def test_issue_22559(): + alpha = AlgebraicNumber(sqrt(2)) + assert minimal_polynomial(alpha**3, x) == x**2 - 8 + + +def test_issue_22561(): + a = AlgebraicNumber(sqrt(2) + sqrt(3), [S(1) / 2, 0, S(-9) / 2, 0], gen=x) + assert a.as_expr() == sqrt(2) + assert minimal_polynomial(a, x) == x**2 - 2 + assert minimal_polynomial(a**3, x) == x**2 - 8 + + +def test_separate_sq_not_impl(): + raises(NotImplementedError, lambda: _separate_sq(x**(S(1)/3) + x)) + + +def test_minpoly_op_algebraic_element_not_impl(): + raises(NotImplementedError, + lambda: _minpoly_op_algebraic_element(Pow, sqrt(2), sqrt(3), x, QQ)) + + +def test_minpoly_groebner(): + assert _minpoly_groebner(S(2)/3, x, Poly) == 3*x - 2 + assert _minpoly_groebner( + (sqrt(2) + 3)*(sqrt(2) + 1), x, Poly) == x**2 - 10*x - 7 + assert _minpoly_groebner((sqrt(2) + 3)**(S(1)/3)*(sqrt(2) + 1)**(S(1)/3), + x, Poly) == x**6 - 10*x**3 - 7 + assert _minpoly_groebner((sqrt(2) + 3)**(-S(1)/3)*(sqrt(2) + 1)**(S(1)/3), + x, Poly) == 7*x**6 - 2*x**3 - 1 + raises(NotAlgebraic, lambda: _minpoly_groebner(pi**2, x, Poly)) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_modules.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_modules.py new file mode 100644 index 0000000000000000000000000000000000000000..f3c61c98e33d3c78e79eeed45efcfa1f74478645 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_modules.py @@ -0,0 +1,752 @@ +from sympy.abc import x, zeta +from sympy.polys import Poly, cyclotomic_poly +from sympy.polys.domains import FF, QQ, ZZ +from sympy.polys.matrices import DomainMatrix, DM +from sympy.polys.numberfields.exceptions import ( + ClosureFailure, MissingUnityError, StructureError +) +from sympy.polys.numberfields.modules import ( + Module, ModuleElement, ModuleEndomorphism, PowerBasis, PowerBasisElement, + find_min_poly, is_sq_maxrank_HNF, make_mod_elt, to_col, +) +from sympy.polys.numberfields.utilities import is_int +from sympy.polys.polyerrors import UnificationFailed +from sympy.testing.pytest import raises + + +def test_to_col(): + c = [1, 2, 3, 4] + m = to_col(c) + assert m.domain.is_ZZ + assert m.shape == (4, 1) + assert m.flat() == c + + +def test_Module_NotImplemented(): + M = Module() + raises(NotImplementedError, lambda: M.n) + raises(NotImplementedError, lambda: M.mult_tab()) + raises(NotImplementedError, lambda: M.represent(None)) + raises(NotImplementedError, lambda: M.starts_with_unity()) + raises(NotImplementedError, lambda: M.element_from_rational(QQ(2, 3))) + + +def test_Module_ancestors(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + D = B.submodule_from_matrix(5 * DomainMatrix.eye(4, ZZ)) + assert C.ancestors(include_self=True) == [A, B, C] + assert D.ancestors(include_self=True) == [A, B, D] + assert C.power_basis_ancestor() == A + assert C.nearest_common_ancestor(D) == B + M = Module() + assert M.power_basis_ancestor() is None + + +def test_Module_compat_col(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + col = to_col([1, 2, 3, 4]) + row = col.transpose() + assert A.is_compat_col(col) is True + assert A.is_compat_col(row) is False + assert A.is_compat_col(1) is False + assert A.is_compat_col(DomainMatrix.eye(3, ZZ)[:, 0]) is False + assert A.is_compat_col(DomainMatrix.eye(4, QQ)[:, 0]) is False + assert A.is_compat_col(DomainMatrix.eye(4, ZZ)[:, 0]) is True + + +def test_Module_call(): + T = Poly(cyclotomic_poly(5, x)) + B = PowerBasis(T) + assert B(0).col.flat() == [1, 0, 0, 0] + assert B(1).col.flat() == [0, 1, 0, 0] + col = DomainMatrix.eye(4, ZZ)[:, 2] + assert B(col).col == col + raises(ValueError, lambda: B(-1)) + + +def test_Module_starts_with_unity(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + assert A.starts_with_unity() is True + assert B.starts_with_unity() is False + + +def test_Module_basis_elements(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + basis = B.basis_elements() + bp = B.basis_element_pullbacks() + for i, (e, p) in enumerate(zip(basis, bp)): + c = [0] * 4 + assert e.module == B + assert p.module == A + c[i] = 1 + assert e == B(to_col(c)) + c[i] = 2 + assert p == A(to_col(c)) + + +def test_Module_zero(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + assert A.zero().col.flat() == [0, 0, 0, 0] + assert A.zero().module == A + assert B.zero().col.flat() == [0, 0, 0, 0] + assert B.zero().module == B + + +def test_Module_one(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + assert A.one().col.flat() == [1, 0, 0, 0] + assert A.one().module == A + assert B.one().col.flat() == [1, 0, 0, 0] + assert B.one().module == A + + +def test_Module_element_from_rational(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + rA = A.element_from_rational(QQ(22, 7)) + rB = B.element_from_rational(QQ(22, 7)) + assert rA.coeffs == [22, 0, 0, 0] + assert rA.denom == 7 + assert rA.module == A + assert rB.coeffs == [22, 0, 0, 0] + assert rB.denom == 7 + assert rB.module == A + + +def test_Module_submodule_from_gens(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + gens = [2*A(0), 2*A(1), 6*A(0), 6*A(1)] + B = A.submodule_from_gens(gens) + # Because the 3rd and 4th generators do not add anything new, we expect + # the cols of the matrix of B to just reproduce the first two gens: + M = gens[0].column().hstack(gens[1].column()) + assert B.matrix == M + # At least one generator must be provided: + raises(ValueError, lambda: A.submodule_from_gens([])) + # All generators must belong to A: + raises(ValueError, lambda: A.submodule_from_gens([3*A(0), B(0)])) + + +def test_Module_submodule_from_matrix(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + e = B(to_col([1, 2, 3, 4])) + f = e.to_parent() + assert f.col.flat() == [2, 4, 6, 8] + # Matrix must be over ZZ: + raises(ValueError, lambda: A.submodule_from_matrix(DomainMatrix.eye(4, QQ))) + # Number of rows of matrix must equal number of generators of module A: + raises(ValueError, lambda: A.submodule_from_matrix(2 * DomainMatrix.eye(5, ZZ))) + + +def test_Module_whole_submodule(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.whole_submodule() + e = B(to_col([1, 2, 3, 4])) + f = e.to_parent() + assert f.col.flat() == [1, 2, 3, 4] + e0, e1, e2, e3 = B(0), B(1), B(2), B(3) + assert e2 * e3 == e0 + assert e3 ** 2 == e1 + + +def test_PowerBasis_repr(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + assert repr(A) == 'PowerBasis(x**4 + x**3 + x**2 + x + 1)' + + +def test_PowerBasis_eq(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = PowerBasis(T) + assert A == B + + +def test_PowerBasis_mult_tab(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + M = A.mult_tab() + exp = {0: {0: [1, 0, 0, 0], 1: [0, 1, 0, 0], 2: [0, 0, 1, 0], 3: [0, 0, 0, 1]}, + 1: {1: [0, 0, 1, 0], 2: [0, 0, 0, 1], 3: [-1, -1, -1, -1]}, + 2: {2: [-1, -1, -1, -1], 3: [1, 0, 0, 0]}, + 3: {3: [0, 1, 0, 0]}} + # We get the table we expect: + assert M == exp + # And all entries are of expected type: + assert all(is_int(c) for u in M for v in M[u] for c in M[u][v]) + + +def test_PowerBasis_represent(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + col = to_col([1, 2, 3, 4]) + a = A(col) + assert A.represent(a) == col + b = A(col, denom=2) + raises(ClosureFailure, lambda: A.represent(b)) + + +def test_PowerBasis_element_from_poly(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + f = Poly(1 + 2*x) + g = Poly(x**4) + h = Poly(0, x) + assert A.element_from_poly(f).coeffs == [1, 2, 0, 0] + assert A.element_from_poly(g).coeffs == [-1, -1, -1, -1] + assert A.element_from_poly(h).coeffs == [0, 0, 0, 0] + + +def test_PowerBasis_element__conversions(): + k = QQ.cyclotomic_field(5) + L = QQ.cyclotomic_field(7) + B = PowerBasis(k) + + # ANP --> PowerBasisElement + a = k([QQ(1, 2), QQ(1, 3), 5, 7]) + e = B.element_from_ANP(a) + assert e.coeffs == [42, 30, 2, 3] + assert e.denom == 6 + + # PowerBasisElement --> ANP + assert e.to_ANP() == a + + # Cannot convert ANP from different field + d = L([QQ(1, 2), QQ(1, 3), 5, 7]) + raises(UnificationFailed, lambda: B.element_from_ANP(d)) + + # AlgebraicNumber --> PowerBasisElement + alpha = k.to_alg_num(a) + eps = B.element_from_alg_num(alpha) + assert eps.coeffs == [42, 30, 2, 3] + assert eps.denom == 6 + + # PowerBasisElement --> AlgebraicNumber + assert eps.to_alg_num() == alpha + + # Cannot convert AlgebraicNumber from different field + delta = L.to_alg_num(d) + raises(UnificationFailed, lambda: B.element_from_alg_num(delta)) + + # When we don't know the field: + C = PowerBasis(k.ext.minpoly) + # Can convert from AlgebraicNumber: + eps = C.element_from_alg_num(alpha) + assert eps.coeffs == [42, 30, 2, 3] + assert eps.denom == 6 + # But can't convert back: + raises(StructureError, lambda: eps.to_alg_num()) + + +def test_Submodule_repr(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ), denom=3) + assert repr(B) == 'Submodule[[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]/3' + + +def test_Submodule_reduced(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = A.submodule_from_matrix(6 * DomainMatrix.eye(4, ZZ), denom=3) + D = C.reduced() + assert D.denom == 1 and D == C == B + + +def test_Submodule_discard_before(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + B.compute_mult_tab() + C = B.discard_before(2) + assert C.parent == B.parent + assert B.is_sq_maxrank_HNF() and not C.is_sq_maxrank_HNF() + assert C.matrix == B.matrix[:, 2:] + assert C.mult_tab() == {0: {0: [-2, -2], 1: [0, 0]}, 1: {1: [0, 0]}} + + +def test_Submodule_QQ_matrix(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = A.submodule_from_matrix(6 * DomainMatrix.eye(4, ZZ), denom=3) + assert C.QQ_matrix == B.QQ_matrix + + +def test_Submodule_represent(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + a0 = A(to_col([6, 12, 18, 24])) + a1 = A(to_col([2, 4, 6, 8])) + a2 = A(to_col([1, 3, 5, 7])) + + b1 = B.represent(a1) + assert b1.flat() == [1, 2, 3, 4] + + c0 = C.represent(a0) + assert c0.flat() == [1, 2, 3, 4] + + Y = A.submodule_from_matrix(DomainMatrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + ], (3, 4), ZZ).transpose()) + + U = Poly(cyclotomic_poly(7, x)) + Z = PowerBasis(U) + z0 = Z(to_col([1, 2, 3, 4, 5, 6])) + + raises(ClosureFailure, lambda: Y.represent(A(3))) + raises(ClosureFailure, lambda: B.represent(a2)) + raises(ClosureFailure, lambda: B.represent(z0)) + + +def test_Submodule_is_compat_submodule(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + D = C.submodule_from_matrix(5 * DomainMatrix.eye(4, ZZ)) + assert B.is_compat_submodule(C) is True + assert B.is_compat_submodule(A) is False + assert B.is_compat_submodule(D) is False + + +def test_Submodule_eq(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = A.submodule_from_matrix(6 * DomainMatrix.eye(4, ZZ), denom=3) + assert C == B + + +def test_Submodule_add(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(DomainMatrix([ + [4, 0, 0, 0], + [0, 4, 0, 0], + ], (2, 4), ZZ).transpose(), denom=6) + C = A.submodule_from_matrix(DomainMatrix([ + [0, 10, 0, 0], + [0, 0, 7, 0], + ], (2, 4), ZZ).transpose(), denom=15) + D = A.submodule_from_matrix(DomainMatrix([ + [20, 0, 0, 0], + [ 0, 20, 0, 0], + [ 0, 0, 14, 0], + ], (3, 4), ZZ).transpose(), denom=30) + assert B + C == D + + U = Poly(cyclotomic_poly(7, x)) + Z = PowerBasis(U) + Y = Z.submodule_from_gens([Z(0), Z(1)]) + raises(TypeError, lambda: B + Y) + + +def test_Submodule_mul(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + C = A.submodule_from_matrix(DomainMatrix([ + [0, 10, 0, 0], + [0, 0, 7, 0], + ], (2, 4), ZZ).transpose(), denom=15) + C1 = A.submodule_from_matrix(DomainMatrix([ + [0, 20, 0, 0], + [0, 0, 14, 0], + ], (2, 4), ZZ).transpose(), denom=3) + C2 = A.submodule_from_matrix(DomainMatrix([ + [0, 0, 10, 0], + [0, 0, 0, 7], + ], (2, 4), ZZ).transpose(), denom=15) + C3_unred = A.submodule_from_matrix(DomainMatrix([ + [0, 0, 100, 0], + [0, 0, 0, 70], + [0, 0, 0, 70], + [-49, -49, -49, -49] + ], (4, 4), ZZ).transpose(), denom=225) + C3 = A.submodule_from_matrix(DomainMatrix([ + [4900, 4900, 0, 0], + [4410, 4410, 10, 0], + [2107, 2107, 7, 7] + ], (3, 4), ZZ).transpose(), denom=225) + assert C * 1 == C + assert C ** 1 == C + assert C * 10 == C1 + assert C * A(1) == C2 + assert C.mul(C, hnf=False) == C3_unred + assert C * C == C3 + assert C ** 2 == C3 + + +def test_Submodule_reduce_element(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.whole_submodule() + b = B(to_col([90, 84, 80, 75]), denom=120) + + C = B.submodule_from_matrix(DomainMatrix.eye(4, ZZ), denom=2) + b_bar_expected = B(to_col([30, 24, 20, 15]), denom=120) + b_bar = C.reduce_element(b) + assert b_bar == b_bar_expected + + C = B.submodule_from_matrix(DomainMatrix.eye(4, ZZ), denom=4) + b_bar_expected = B(to_col([0, 24, 20, 15]), denom=120) + b_bar = C.reduce_element(b) + assert b_bar == b_bar_expected + + C = B.submodule_from_matrix(DomainMatrix.eye(4, ZZ), denom=8) + b_bar_expected = B(to_col([0, 9, 5, 0]), denom=120) + b_bar = C.reduce_element(b) + assert b_bar == b_bar_expected + + a = A(to_col([1, 2, 3, 4])) + raises(NotImplementedError, lambda: C.reduce_element(a)) + + C = B.submodule_from_matrix(DomainMatrix([ + [5, 4, 3, 2], + [0, 8, 7, 6], + [0, 0,11,12], + [0, 0, 0, 1] + ], (4, 4), ZZ).transpose()) + raises(StructureError, lambda: C.reduce_element(b)) + + +def test_is_HNF(): + M = DM([ + [3, 2, 1], + [0, 2, 1], + [0, 0, 1] + ], ZZ) + M1 = DM([ + [3, 2, 1], + [0, -2, 1], + [0, 0, 1] + ], ZZ) + M2 = DM([ + [3, 2, 3], + [0, 2, 1], + [0, 0, 1] + ], ZZ) + assert is_sq_maxrank_HNF(M) is True + assert is_sq_maxrank_HNF(M1) is False + assert is_sq_maxrank_HNF(M2) is False + + +def test_make_mod_elt(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + col = to_col([1, 2, 3, 4]) + eA = make_mod_elt(A, col) + eB = make_mod_elt(B, col) + assert isinstance(eA, PowerBasisElement) + assert not isinstance(eB, PowerBasisElement) + + +def test_ModuleElement_repr(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([1, 2, 3, 4]), denom=2) + assert repr(e) == '[1, 2, 3, 4]/2' + + +def test_ModuleElement_reduced(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([2, 4, 6, 8]), denom=2) + f = e.reduced() + assert f.denom == 1 and f == e + + +def test_ModuleElement_reduced_mod_p(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([20, 40, 60, 80])) + f = e.reduced_mod_p(7) + assert f.coeffs == [-1, -2, -3, 3] + + +def test_ModuleElement_from_int_list(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + c = [1, 2, 3, 4] + assert ModuleElement.from_int_list(A, c).coeffs == c + + +def test_ModuleElement_len(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(0) + assert len(e) == 4 + + +def test_ModuleElement_column(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(0) + col1 = e.column() + assert col1 == e.col and col1 is not e.col + col2 = e.column(domain=FF(5)) + assert col2.domain.is_FF + + +def test_ModuleElement_QQ_col(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([1, 2, 3, 4]), denom=1) + f = A(to_col([3, 6, 9, 12]), denom=3) + assert e.QQ_col == f.QQ_col + + +def test_ModuleElement_to_ancestors(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + D = C.submodule_from_matrix(5 * DomainMatrix.eye(4, ZZ)) + eD = D(0) + eC = eD.to_parent() + eB = eD.to_ancestor(B) + eA = eD.over_power_basis() + assert eC.module is C and eC.coeffs == [5, 0, 0, 0] + assert eB.module is B and eB.coeffs == [15, 0, 0, 0] + assert eA.module is A and eA.coeffs == [30, 0, 0, 0] + + a = A(0) + raises(ValueError, lambda: a.to_parent()) + + +def test_ModuleElement_compatibility(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + D = B.submodule_from_matrix(5 * DomainMatrix.eye(4, ZZ)) + assert C(0).is_compat(C(1)) is True + assert C(0).is_compat(D(0)) is False + u, v = C(0).unify(D(0)) + assert u.module is B and v.module is B + assert C(C.represent(u)) == C(0) and D(D.represent(v)) == D(0) + + u, v = C(0).unify(C(1)) + assert u == C(0) and v == C(1) + + U = Poly(cyclotomic_poly(7, x)) + Z = PowerBasis(U) + raises(UnificationFailed, lambda: C(0).unify(Z(1))) + + +def test_ModuleElement_eq(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([1, 2, 3, 4]), denom=1) + f = A(to_col([3, 6, 9, 12]), denom=3) + assert e == f + + U = Poly(cyclotomic_poly(7, x)) + Z = PowerBasis(U) + assert e != Z(0) + assert e != 3.14 + + +def test_ModuleElement_equiv(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([1, 2, 3, 4]), denom=1) + f = A(to_col([3, 6, 9, 12]), denom=3) + assert e.equiv(f) + + C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + g = C(to_col([1, 2, 3, 4]), denom=1) + h = A(to_col([3, 6, 9, 12]), denom=1) + assert g.equiv(h) + assert C(to_col([5, 0, 0, 0]), denom=7).equiv(QQ(15, 7)) + + U = Poly(cyclotomic_poly(7, x)) + Z = PowerBasis(U) + raises(UnificationFailed, lambda: e.equiv(Z(0))) + + assert e.equiv(3.14) is False + + +def test_ModuleElement_add(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + e = A(to_col([1, 2, 3, 4]), denom=6) + f = A(to_col([5, 6, 7, 8]), denom=10) + g = C(to_col([1, 1, 1, 1]), denom=2) + assert e + f == A(to_col([10, 14, 18, 22]), denom=15) + assert e - f == A(to_col([-5, -4, -3, -2]), denom=15) + assert e + g == A(to_col([10, 11, 12, 13]), denom=6) + assert e + QQ(7, 10) == A(to_col([26, 10, 15, 20]), denom=30) + assert g + QQ(7, 10) == A(to_col([22, 15, 15, 15]), denom=10) + + U = Poly(cyclotomic_poly(7, x)) + Z = PowerBasis(U) + raises(TypeError, lambda: e + Z(0)) + raises(TypeError, lambda: e + 3.14) + + +def test_ModuleElement_mul(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + e = A(to_col([0, 2, 0, 0]), denom=3) + f = A(to_col([0, 0, 0, 7]), denom=5) + g = C(to_col([0, 0, 0, 1]), denom=2) + h = A(to_col([0, 0, 3, 1]), denom=7) + assert e * f == A(to_col([-14, -14, -14, -14]), denom=15) + assert e * g == A(to_col([-1, -1, -1, -1])) + assert e * h == A(to_col([-2, -2, -2, 4]), denom=21) + assert e * QQ(6, 5) == A(to_col([0, 4, 0, 0]), denom=5) + assert (g * QQ(10, 21)).equiv(A(to_col([0, 0, 0, 5]), denom=7)) + assert e // QQ(6, 5) == A(to_col([0, 5, 0, 0]), denom=9) + + U = Poly(cyclotomic_poly(7, x)) + Z = PowerBasis(U) + raises(TypeError, lambda: e * Z(0)) + raises(TypeError, lambda: e * 3.14) + raises(TypeError, lambda: e // 3.14) + raises(ZeroDivisionError, lambda: e // 0) + + +def test_ModuleElement_div(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + e = A(to_col([0, 2, 0, 0]), denom=3) + f = A(to_col([0, 0, 0, 7]), denom=5) + g = C(to_col([1, 1, 1, 1])) + assert e // f == 10*A(3)//21 + assert e // g == -2*A(2)//9 + assert 3 // g == -A(1) + + +def test_ModuleElement_pow(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + e = A(to_col([0, 2, 0, 0]), denom=3) + g = C(to_col([0, 0, 0, 1]), denom=2) + assert e ** 3 == A(to_col([0, 0, 0, 8]), denom=27) + assert g ** 2 == C(to_col([0, 3, 0, 0]), denom=4) + assert e ** 0 == A(to_col([1, 0, 0, 0])) + assert g ** 0 == A(to_col([1, 0, 0, 0])) + assert e ** 1 == e + assert g ** 1 == g + + +def test_ModuleElement_mod(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([1, 15, 8, 0]), denom=2) + assert e % 7 == A(to_col([1, 1, 8, 0]), denom=2) + assert e % QQ(1, 2) == A.zero() + assert e % QQ(1, 3) == A(to_col([1, 1, 0, 0]), denom=6) + + B = A.submodule_from_gens([A(0), 5*A(1), 3*A(2), A(3)]) + assert e % B == A(to_col([1, 5, 2, 0]), denom=2) + + C = B.whole_submodule() + raises(TypeError, lambda: e % C) + + +def test_PowerBasisElement_polys(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([1, 15, 8, 0]), denom=2) + assert e.numerator(x=zeta) == Poly(8 * zeta ** 2 + 15 * zeta + 1, domain=ZZ) + assert e.poly(x=zeta) == Poly(4 * zeta ** 2 + QQ(15, 2) * zeta + QQ(1, 2), domain=QQ) + + +def test_PowerBasisElement_norm(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + lam = A(to_col([1, -1, 0, 0])) + assert lam.norm() == 5 + + +def test_PowerBasisElement_inverse(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + e = A(to_col([1, 1, 1, 1])) + assert 2 // e == -2*A(1) + assert e ** -3 == -A(3) + + +def test_ModuleHomomorphism_matrix(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + phi = ModuleEndomorphism(A, lambda a: a ** 2) + M = phi.matrix() + assert M == DomainMatrix([ + [1, 0, -1, 0], + [0, 0, -1, 1], + [0, 1, -1, 0], + [0, 0, -1, 0] + ], (4, 4), ZZ) + + +def test_ModuleHomomorphism_kernel(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + phi = ModuleEndomorphism(A, lambda a: a ** 5) + N = phi.kernel() + assert N.n == 3 + + +def test_EndomorphismRing_represent(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + R = A.endomorphism_ring() + phi = R.inner_endomorphism(A(1)) + col = R.represent(phi) + assert col.transpose() == DomainMatrix([ + [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1] + ], (1, 16), ZZ) + + B = A.submodule_from_matrix(DomainMatrix.zeros((4, 0), ZZ)) + S = B.endomorphism_ring() + psi = S.inner_endomorphism(A(1)) + col = S.represent(psi) + assert col == DomainMatrix([], (0, 0), ZZ) + + raises(NotImplementedError, lambda: R.represent(3.14)) + + +def test_find_min_poly(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + powers = [] + m = find_min_poly(A(1), QQ, x=x, powers=powers) + assert m == Poly(T, domain=QQ) + assert len(powers) == 5 + + # powers list need not be passed + m = find_min_poly(A(1), QQ, x=x) + assert m == Poly(T, domain=QQ) + + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + raises(MissingUnityError, lambda: find_min_poly(B(1), QQ)) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_numbers.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_numbers.py new file mode 100644 index 0000000000000000000000000000000000000000..f8f350719cc740901a29d03e45ae9f3978446f31 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_numbers.py @@ -0,0 +1,202 @@ +"""Tests on algebraic numbers. """ + +from sympy.core.containers import Tuple +from sympy.core.numbers import (AlgebraicNumber, I, Rational) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.polys.polytools import Poly +from sympy.polys.numberfields.subfield import to_number_field +from sympy.polys.polyclasses import DMP +from sympy.polys.domains import QQ +from sympy.polys.rootoftools import CRootOf +from sympy.abc import x, y + + +def test_AlgebraicNumber(): + minpoly, root = x**2 - 2, sqrt(2) + + a = AlgebraicNumber(root, gen=x) + + assert a.rep == DMP([QQ(1), QQ(0)], QQ) + assert a.root == root + assert a.alias is None + assert a.minpoly == minpoly + assert a.is_number + + assert a.is_aliased is False + + assert a.coeffs() == [S.One, S.Zero] + assert a.native_coeffs() == [QQ(1), QQ(0)] + + a = AlgebraicNumber(root, gen=x, alias='y') + + assert a.rep == DMP([QQ(1), QQ(0)], QQ) + assert a.root == root + assert a.alias == Symbol('y') + assert a.minpoly == minpoly + assert a.is_number + + assert a.is_aliased is True + + a = AlgebraicNumber(root, gen=x, alias=Symbol('y')) + + assert a.rep == DMP([QQ(1), QQ(0)], QQ) + assert a.root == root + assert a.alias == Symbol('y') + assert a.minpoly == minpoly + assert a.is_number + + assert a.is_aliased is True + + assert AlgebraicNumber(sqrt(2), []).rep == DMP([], QQ) + assert AlgebraicNumber(sqrt(2), ()).rep == DMP([], QQ) + assert AlgebraicNumber(sqrt(2), (0, 0)).rep == DMP([], QQ) + + assert AlgebraicNumber(sqrt(2), [8]).rep == DMP([QQ(8)], QQ) + assert AlgebraicNumber(sqrt(2), [Rational(8, 3)]).rep == DMP([QQ(8, 3)], QQ) + + assert AlgebraicNumber(sqrt(2), [7, 3]).rep == DMP([QQ(7), QQ(3)], QQ) + assert AlgebraicNumber( + sqrt(2), [Rational(7, 9), Rational(3, 2)]).rep == DMP([QQ(7, 9), QQ(3, 2)], QQ) + + assert AlgebraicNumber(sqrt(2), [1, 2, 3]).rep == DMP([QQ(2), QQ(5)], QQ) + + a = AlgebraicNumber(AlgebraicNumber(root, gen=x), [1, 2]) + + assert a.rep == DMP([QQ(1), QQ(2)], QQ) + assert a.root == root + assert a.alias is None + assert a.minpoly == minpoly + assert a.is_number + + assert a.is_aliased is False + + assert a.coeffs() == [S.One, S(2)] + assert a.native_coeffs() == [QQ(1), QQ(2)] + + a = AlgebraicNumber((minpoly, root), [1, 2]) + + assert a.rep == DMP([QQ(1), QQ(2)], QQ) + assert a.root == root + assert a.alias is None + assert a.minpoly == minpoly + assert a.is_number + + assert a.is_aliased is False + + a = AlgebraicNumber((Poly(minpoly), root), [1, 2]) + + assert a.rep == DMP([QQ(1), QQ(2)], QQ) + assert a.root == root + assert a.alias is None + assert a.minpoly == minpoly + assert a.is_number + + assert a.is_aliased is False + + assert AlgebraicNumber( sqrt(3)).rep == DMP([ QQ(1), QQ(0)], QQ) + assert AlgebraicNumber(-sqrt(3)).rep == DMP([ QQ(1), QQ(0)], QQ) + + a = AlgebraicNumber(sqrt(2)) + b = AlgebraicNumber(sqrt(2)) + + assert a == b + + c = AlgebraicNumber(sqrt(2), gen=x) + + assert a == b + assert a == c + + a = AlgebraicNumber(sqrt(2), [1, 2]) + b = AlgebraicNumber(sqrt(2), [1, 3]) + + assert a != b and a != sqrt(2) + 3 + + assert (a == x) is False and (a != x) is True + + a = AlgebraicNumber(sqrt(2), [1, 0]) + b = AlgebraicNumber(sqrt(2), [1, 0], alias=y) + + assert a.as_poly(x) == Poly(x, domain='QQ') + assert b.as_poly() == Poly(y, domain='QQ') + + assert a.as_expr() == sqrt(2) + assert a.as_expr(x) == x + assert b.as_expr() == sqrt(2) + assert b.as_expr(x) == x + + a = AlgebraicNumber(sqrt(2), [2, 3]) + b = AlgebraicNumber(sqrt(2), [2, 3], alias=y) + + p = a.as_poly() + + assert p == Poly(2*p.gen + 3) + + assert a.as_poly(x) == Poly(2*x + 3, domain='QQ') + assert b.as_poly() == Poly(2*y + 3, domain='QQ') + + assert a.as_expr() == 2*sqrt(2) + 3 + assert a.as_expr(x) == 2*x + 3 + assert b.as_expr() == 2*sqrt(2) + 3 + assert b.as_expr(x) == 2*x + 3 + + a = AlgebraicNumber(sqrt(2)) + b = to_number_field(sqrt(2)) + assert a.args == b.args == (sqrt(2), Tuple(1, 0)) + b = AlgebraicNumber(sqrt(2), alias='alpha') + assert b.args == (sqrt(2), Tuple(1, 0), Symbol('alpha')) + + a = AlgebraicNumber(sqrt(2), [1, 2, 3]) + assert a.args == (sqrt(2), Tuple(1, 2, 3)) + + a = AlgebraicNumber(sqrt(2), [1, 2], "alpha") + b = AlgebraicNumber(a) + c = AlgebraicNumber(a, alias="gamma") + assert a == b + assert c.alias.name == "gamma" + + a = AlgebraicNumber(sqrt(2) + sqrt(3), [S(1)/2, 0, S(-9)/2, 0]) + b = AlgebraicNumber(a, [1, 0, 0]) + assert b.root == a.root + assert a.to_root() == sqrt(2) + assert b.to_root() == 2 + + a = AlgebraicNumber(2) + assert a.is_primitive_element is True + + +def test_to_algebraic_integer(): + a = AlgebraicNumber(sqrt(3), gen=x).to_algebraic_integer() + + assert a.minpoly == x**2 - 3 + assert a.root == sqrt(3) + assert a.rep == DMP([QQ(1), QQ(0)], QQ) + + a = AlgebraicNumber(2*sqrt(3), gen=x).to_algebraic_integer() + assert a.minpoly == x**2 - 12 + assert a.root == 2*sqrt(3) + assert a.rep == DMP([QQ(1), QQ(0)], QQ) + + a = AlgebraicNumber(sqrt(3)/2, gen=x).to_algebraic_integer() + + assert a.minpoly == x**2 - 12 + assert a.root == 2*sqrt(3) + assert a.rep == DMP([QQ(1), QQ(0)], QQ) + + a = AlgebraicNumber(sqrt(3)/2, [Rational(7, 19), 3], gen=x).to_algebraic_integer() + + assert a.minpoly == x**2 - 12 + assert a.root == 2*sqrt(3) + assert a.rep == DMP([QQ(7, 19), QQ(3)], QQ) + + +def test_AlgebraicNumber_to_root(): + assert AlgebraicNumber(sqrt(2)).to_root() == sqrt(2) + + zeta5_squared = AlgebraicNumber(CRootOf(x**5 - 1, 4), coeffs=[1, 0, 0]) + assert zeta5_squared.to_root() == CRootOf(x**4 + x**3 + x**2 + x + 1, 1) + + zeta3_squared = AlgebraicNumber(CRootOf(x**3 - 1, 2), coeffs=[1, 0, 0]) + assert zeta3_squared.to_root() == -S(1)/2 - sqrt(3)*I/2 + assert zeta3_squared.to_root(radicals=False) == CRootOf(x**2 + x + 1, 0) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_primes.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_primes.py new file mode 100644 index 0000000000000000000000000000000000000000..f121d60d272fe65345de773748828a8a67eb0028 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_primes.py @@ -0,0 +1,296 @@ +from math import prod + +from sympy import QQ, ZZ +from sympy.abc import x, theta +from sympy.ntheory import factorint +from sympy.ntheory.residue_ntheory import n_order +from sympy.polys import Poly, cyclotomic_poly +from sympy.polys.matrices import DomainMatrix +from sympy.polys.numberfields.basis import round_two +from sympy.polys.numberfields.exceptions import StructureError +from sympy.polys.numberfields.modules import PowerBasis, to_col +from sympy.polys.numberfields.primes import ( + prime_decomp, _two_elt_rep, + _check_formal_conditions_for_maximal_order, +) +from sympy.testing.pytest import raises + + +def test_check_formal_conditions_for_maximal_order(): + T = Poly(cyclotomic_poly(5, x)) + A = PowerBasis(T) + B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ)) + C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ)) + D = A.submodule_from_matrix(DomainMatrix.eye(4, ZZ)[:, :-1]) + # Is a direct submodule of a power basis, but lacks 1 as first generator: + raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(B)) + # Is not a direct submodule of a power basis: + raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(C)) + # Is direct submod of pow basis, and starts with 1, but not sq/max rank/HNF: + raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(D)) + + +def test_two_elt_rep(): + ell = 7 + T = Poly(cyclotomic_poly(ell)) + ZK, dK = round_two(T) + for p in [29, 13, 11, 5]: + P = prime_decomp(p, T) + for Pi in P: + # We have Pi in two-element representation, and, because we are + # looking at a cyclotomic field, this was computed by the "easy" + # method that just factors T mod p. We will now convert this to + # a set of Z-generators, then convert that back into a two-element + # rep. The latter need not be identical to the two-elt rep we + # already have, but it must have the same HNF. + H = p*ZK + Pi.alpha*ZK + gens = H.basis_element_pullbacks() + # Note: we could supply f = Pi.f, but prefer to test behavior without it. + b = _two_elt_rep(gens, ZK, p) + if b != Pi.alpha: + H2 = p*ZK + b*ZK + assert H2 == H + + +def test_valuation_at_prime_ideal(): + p = 7 + T = Poly(cyclotomic_poly(p)) + ZK, dK = round_two(T) + P = prime_decomp(p, T, dK=dK, ZK=ZK) + assert len(P) == 1 + P0 = P[0] + v = P0.valuation(p*ZK) + assert v == P0.e + # Test easy 0 case: + assert P0.valuation(5*ZK) == 0 + + +def test_decomp_1(): + # All prime decompositions in cyclotomic fields are in the "easy case," + # since the index is unity. + # Here we check the ramified prime. + T = Poly(cyclotomic_poly(7)) + raises(ValueError, lambda: prime_decomp(7)) + P = prime_decomp(7, T) + assert len(P) == 1 + P0 = P[0] + assert P0.e == 6 + assert P0.f == 1 + # Test powers: + assert P0**0 == P0.ZK + assert P0**1 == P0 + assert P0**6 == 7 * P0.ZK + + +def test_decomp_2(): + # More easy cyclotomic cases, but here we check unramified primes. + ell = 7 + T = Poly(cyclotomic_poly(ell)) + for p in [29, 13, 11, 5]: + f_exp = n_order(p, ell) + g_exp = (ell - 1) // f_exp + P = prime_decomp(p, T) + assert len(P) == g_exp + for Pi in P: + assert Pi.e == 1 + assert Pi.f == f_exp + + +def test_decomp_3(): + T = Poly(x ** 2 - 35) + rad = {} + ZK, dK = round_two(T, radicals=rad) + # 35 is 3 mod 4, so field disc is 4*5*7, and theory says each of the + # rational primes 2, 5, 7 should be the square of a prime ideal. + for p in [2, 5, 7]: + P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p)) + assert len(P) == 1 + assert P[0].e == 2 + assert P[0]**2 == p*ZK + + +def test_decomp_4(): + T = Poly(x ** 2 - 21) + rad = {} + ZK, dK = round_two(T, radicals=rad) + # 21 is 1 mod 4, so field disc is 3*7, and theory says the + # rational primes 3, 7 should be the square of a prime ideal. + for p in [3, 7]: + P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p)) + assert len(P) == 1 + assert P[0].e == 2 + assert P[0]**2 == p*ZK + + +def test_decomp_5(): + # Here is our first test of the "hard case" of prime decomposition. + # We work in a quadratic extension Q(sqrt(d)) where d is 1 mod 4, and + # we consider the factorization of the rational prime 2, which divides + # the index. + # Theory says the form of p's factorization depends on the residue of + # d mod 8, so we consider both cases, d = 1 mod 8 and d = 5 mod 8. + for d in [-7, -3]: + T = Poly(x ** 2 - d) + rad = {} + ZK, dK = round_two(T, radicals=rad) + p = 2 + P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p)) + if d % 8 == 1: + assert len(P) == 2 + assert all(P[i].e == 1 and P[i].f == 1 for i in range(2)) + assert prod(Pi**Pi.e for Pi in P) == p * ZK + else: + assert d % 8 == 5 + assert len(P) == 1 + assert P[0].e == 1 + assert P[0].f == 2 + assert P[0].as_submodule() == p * ZK + + +def test_decomp_6(): + # Another case where 2 divides the index. This is Dedekind's example of + # an essential discriminant divisor. (See Cohen, Exercise 6.10.) + T = Poly(x ** 3 + x ** 2 - 2 * x + 8) + rad = {} + ZK, dK = round_two(T, radicals=rad) + p = 2 + P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p)) + assert len(P) == 3 + assert all(Pi.e == Pi.f == 1 for Pi in P) + assert prod(Pi**Pi.e for Pi in P) == p*ZK + + +def test_decomp_7(): + # Try working through an AlgebraicField + T = Poly(x ** 3 + x ** 2 - 2 * x + 8) + K = QQ.alg_field_from_poly(T) + p = 2 + P = K.primes_above(p) + ZK = K.maximal_order() + assert len(P) == 3 + assert all(Pi.e == Pi.f == 1 for Pi in P) + assert prod(Pi**Pi.e for Pi in P) == p*ZK + + +def test_decomp_8(): + # This time we consider various cubics, and try factoring all primes + # dividing the index. + cases = ( + x ** 3 + 3 * x ** 2 - 4 * x + 4, + x ** 3 + 3 * x ** 2 + 3 * x - 3, + x ** 3 + 5 * x ** 2 - x + 3, + x ** 3 + 5 * x ** 2 - 5 * x - 5, + x ** 3 + 3 * x ** 2 + 5, + x ** 3 + 6 * x ** 2 + 3 * x - 1, + x ** 3 + 6 * x ** 2 + 4, + x ** 3 + 7 * x ** 2 + 7 * x - 7, + x ** 3 + 7 * x ** 2 - x + 5, + x ** 3 + 7 * x ** 2 - 5 * x + 5, + x ** 3 + 4 * x ** 2 - 3 * x + 7, + x ** 3 + 8 * x ** 2 + 5 * x - 1, + x ** 3 + 8 * x ** 2 - 2 * x + 6, + x ** 3 + 6 * x ** 2 - 3 * x + 8, + x ** 3 + 9 * x ** 2 + 6 * x - 8, + x ** 3 + 15 * x ** 2 - 9 * x + 13, + ) + def display(T, p, radical, P, I, J): + """Useful for inspection, when running test manually.""" + print('=' * 20) + print(T, p, radical) + for Pi in P: + print(f' ({Pi!r})') + print("I: ", I) + print("J: ", J) + print(f'Equal: {I == J}') + inspect = False + for g in cases: + T = Poly(g) + rad = {} + ZK, dK = round_two(T, radicals=rad) + dT = T.discriminant() + f_squared = dT // dK + F = factorint(f_squared) + for p in F: + radical = rad.get(p) + P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=radical) + I = prod(Pi**Pi.e for Pi in P) + J = p * ZK + if inspect: + display(T, p, radical, P, I, J) + assert I == J + + +def test_PrimeIdeal_eq(): + # `==` should fail on objects of different types, so even a completely + # inert PrimeIdeal should test unequal to the rational prime it divides. + T = Poly(cyclotomic_poly(7)) + P0 = prime_decomp(5, T)[0] + assert P0.f == 6 + assert P0.as_submodule() == 5 * P0.ZK + assert P0 != 5 + + +def test_PrimeIdeal_add(): + T = Poly(cyclotomic_poly(7)) + P0 = prime_decomp(7, T)[0] + # Adding ideals computes their GCD, so adding the ramified prime dividing + # 7 to 7 itself should reproduce this prime (as a submodule). + assert P0 + 7 * P0.ZK == P0.as_submodule() + + +def test_str(): + # Without alias: + k = QQ.alg_field_from_poly(Poly(x**2 + 7)) + frp = k.primes_above(2)[0] + assert str(frp) == '(2, 3*_x/2 + 1/2)' + + frp = k.primes_above(3)[0] + assert str(frp) == '(3)' + + # With alias: + k = QQ.alg_field_from_poly(Poly(x ** 2 + 7), alias='alpha') + frp = k.primes_above(2)[0] + assert str(frp) == '(2, 3*alpha/2 + 1/2)' + + frp = k.primes_above(3)[0] + assert str(frp) == '(3)' + + +def test_repr(): + T = Poly(x**2 + 7) + ZK, dK = round_two(T) + P = prime_decomp(2, T, dK=dK, ZK=ZK) + assert repr(P[0]) == '[ (2, (3*x + 1)/2) e=1, f=1 ]' + assert P[0].repr(field_gen=theta) == '[ (2, (3*theta + 1)/2) e=1, f=1 ]' + assert P[0].repr(field_gen=theta, just_gens=True) == '(2, (3*theta + 1)/2)' + + +def test_PrimeIdeal_reduce(): + k = QQ.alg_field_from_poly(Poly(x ** 3 + x ** 2 - 2 * x + 8)) + Zk = k.maximal_order() + P = k.primes_above(2) + frp = P[2] + + # reduce_element + a = Zk.parent(to_col([23, 20, 11]), denom=6) + a_bar_expected = Zk.parent(to_col([11, 5, 2]), denom=6) + a_bar = frp.reduce_element(a) + assert a_bar == a_bar_expected + + # reduce_ANP + a = k([QQ(11, 6), QQ(20, 6), QQ(23, 6)]) + a_bar_expected = k([QQ(2, 6), QQ(5, 6), QQ(11, 6)]) + a_bar = frp.reduce_ANP(a) + assert a_bar == a_bar_expected + + # reduce_alg_num + a = k.to_alg_num(a) + a_bar_expected = k.to_alg_num(a_bar_expected) + a_bar = frp.reduce_alg_num(a) + assert a_bar == a_bar_expected + + +def test_issue_23402(): + k = QQ.alg_field_from_poly(Poly(x ** 3 + x ** 2 - 2 * x + 8)) + P = k.primes_above(3) + assert P[0].alpha.equiv(0) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_subfield.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_subfield.py new file mode 100644 index 0000000000000000000000000000000000000000..b152dd684aa20034f9233eedb1866aac2639b5f9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_subfield.py @@ -0,0 +1,317 @@ +"""Tests for the subfield problem and allied problems. """ + +from sympy.core.numbers import (AlgebraicNumber, I, pi, Rational) +from sympy.core.singleton import S +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.external.gmpy import MPQ +from sympy.polys.numberfields.subfield import ( + is_isomorphism_possible, + field_isomorphism_pslq, + field_isomorphism, + primitive_element, + to_number_field, +) +from sympy.polys.domains import QQ +from sympy.polys.polyerrors import IsomorphismFailed +from sympy.polys.polytools import Poly +from sympy.polys.rootoftools import CRootOf +from sympy.testing.pytest import raises + +from sympy.abc import x + +Q = Rational + + +def test_field_isomorphism_pslq(): + a = AlgebraicNumber(I) + b = AlgebraicNumber(I*sqrt(3)) + + raises(NotImplementedError, lambda: field_isomorphism_pslq(a, b)) + + a = AlgebraicNumber(sqrt(2)) + b = AlgebraicNumber(sqrt(3)) + c = AlgebraicNumber(sqrt(7)) + d = AlgebraicNumber(sqrt(2) + sqrt(3)) + e = AlgebraicNumber(sqrt(2) + sqrt(3) + sqrt(7)) + + assert field_isomorphism_pslq(a, a) == [1, 0] + assert field_isomorphism_pslq(a, b) is None + assert field_isomorphism_pslq(a, c) is None + assert field_isomorphism_pslq(a, d) == [Q(1, 2), 0, -Q(9, 2), 0] + assert field_isomorphism_pslq( + a, e) == [Q(1, 80), 0, -Q(1, 2), 0, Q(59, 20), 0] + + assert field_isomorphism_pslq(b, a) is None + assert field_isomorphism_pslq(b, b) == [1, 0] + assert field_isomorphism_pslq(b, c) is None + assert field_isomorphism_pslq(b, d) == [-Q(1, 2), 0, Q(11, 2), 0] + assert field_isomorphism_pslq(b, e) == [-Q( + 3, 640), 0, Q(67, 320), 0, -Q(297, 160), 0, Q(313, 80), 0] + + assert field_isomorphism_pslq(c, a) is None + assert field_isomorphism_pslq(c, b) is None + assert field_isomorphism_pslq(c, c) == [1, 0] + assert field_isomorphism_pslq(c, d) is None + assert field_isomorphism_pslq(c, e) == [Q( + 3, 640), 0, -Q(71, 320), 0, Q(377, 160), 0, -Q(469, 80), 0] + + assert field_isomorphism_pslq(d, a) is None + assert field_isomorphism_pslq(d, b) is None + assert field_isomorphism_pslq(d, c) is None + assert field_isomorphism_pslq(d, d) == [1, 0] + assert field_isomorphism_pslq(d, e) == [-Q( + 3, 640), 0, Q(71, 320), 0, -Q(377, 160), 0, Q(549, 80), 0] + + assert field_isomorphism_pslq(e, a) is None + assert field_isomorphism_pslq(e, b) is None + assert field_isomorphism_pslq(e, c) is None + assert field_isomorphism_pslq(e, d) is None + assert field_isomorphism_pslq(e, e) == [1, 0] + + f = AlgebraicNumber(3*sqrt(2) + 8*sqrt(7) - 5) + + assert field_isomorphism_pslq( + f, e) == [Q(3, 80), 0, -Q(139, 80), 0, Q(347, 20), 0, -Q(761, 20), -5] + + +def test_field_isomorphism(): + assert field_isomorphism(3, sqrt(2)) == [3] + + assert field_isomorphism( I*sqrt(3), I*sqrt(3)/2) == [ 2, 0] + assert field_isomorphism(-I*sqrt(3), I*sqrt(3)/2) == [-2, 0] + + assert field_isomorphism( I*sqrt(3), -I*sqrt(3)/2) == [-2, 0] + assert field_isomorphism(-I*sqrt(3), -I*sqrt(3)/2) == [ 2, 0] + + assert field_isomorphism( 2*I*sqrt(3)/7, 5*I*sqrt(3)/3) == [ Rational(6, 35), 0] + assert field_isomorphism(-2*I*sqrt(3)/7, 5*I*sqrt(3)/3) == [Rational(-6, 35), 0] + + assert field_isomorphism( 2*I*sqrt(3)/7, -5*I*sqrt(3)/3) == [Rational(-6, 35), 0] + assert field_isomorphism(-2*I*sqrt(3)/7, -5*I*sqrt(3)/3) == [ Rational(6, 35), 0] + + assert field_isomorphism( + 2*I*sqrt(3)/7 + 27, 5*I*sqrt(3)/3) == [ Rational(6, 35), 27] + assert field_isomorphism( + -2*I*sqrt(3)/7 + 27, 5*I*sqrt(3)/3) == [Rational(-6, 35), 27] + + assert field_isomorphism( + 2*I*sqrt(3)/7 + 27, -5*I*sqrt(3)/3) == [Rational(-6, 35), 27] + assert field_isomorphism( + -2*I*sqrt(3)/7 + 27, -5*I*sqrt(3)/3) == [ Rational(6, 35), 27] + + p = AlgebraicNumber( sqrt(2) + sqrt(3)) + q = AlgebraicNumber(-sqrt(2) + sqrt(3)) + r = AlgebraicNumber( sqrt(2) - sqrt(3)) + s = AlgebraicNumber(-sqrt(2) - sqrt(3)) + + pos_coeffs = [ S.Half, S.Zero, Rational(-9, 2), S.Zero] + neg_coeffs = [Rational(-1, 2), S.Zero, Rational(9, 2), S.Zero] + + a = AlgebraicNumber(sqrt(2)) + + assert is_isomorphism_possible(a, p) is True + assert is_isomorphism_possible(a, q) is True + assert is_isomorphism_possible(a, r) is True + assert is_isomorphism_possible(a, s) is True + + assert field_isomorphism(a, p, fast=True) == pos_coeffs + assert field_isomorphism(a, q, fast=True) == neg_coeffs + assert field_isomorphism(a, r, fast=True) == pos_coeffs + assert field_isomorphism(a, s, fast=True) == neg_coeffs + + assert field_isomorphism(a, p, fast=False) == pos_coeffs + assert field_isomorphism(a, q, fast=False) == neg_coeffs + assert field_isomorphism(a, r, fast=False) == pos_coeffs + assert field_isomorphism(a, s, fast=False) == neg_coeffs + + a = AlgebraicNumber(-sqrt(2)) + + assert is_isomorphism_possible(a, p) is True + assert is_isomorphism_possible(a, q) is True + assert is_isomorphism_possible(a, r) is True + assert is_isomorphism_possible(a, s) is True + + assert field_isomorphism(a, p, fast=True) == neg_coeffs + assert field_isomorphism(a, q, fast=True) == pos_coeffs + assert field_isomorphism(a, r, fast=True) == neg_coeffs + assert field_isomorphism(a, s, fast=True) == pos_coeffs + + assert field_isomorphism(a, p, fast=False) == neg_coeffs + assert field_isomorphism(a, q, fast=False) == pos_coeffs + assert field_isomorphism(a, r, fast=False) == neg_coeffs + assert field_isomorphism(a, s, fast=False) == pos_coeffs + + pos_coeffs = [ S.Half, S.Zero, Rational(-11, 2), S.Zero] + neg_coeffs = [Rational(-1, 2), S.Zero, Rational(11, 2), S.Zero] + + a = AlgebraicNumber(sqrt(3)) + + assert is_isomorphism_possible(a, p) is True + assert is_isomorphism_possible(a, q) is True + assert is_isomorphism_possible(a, r) is True + assert is_isomorphism_possible(a, s) is True + + assert field_isomorphism(a, p, fast=True) == neg_coeffs + assert field_isomorphism(a, q, fast=True) == neg_coeffs + assert field_isomorphism(a, r, fast=True) == pos_coeffs + assert field_isomorphism(a, s, fast=True) == pos_coeffs + + assert field_isomorphism(a, p, fast=False) == neg_coeffs + assert field_isomorphism(a, q, fast=False) == neg_coeffs + assert field_isomorphism(a, r, fast=False) == pos_coeffs + assert field_isomorphism(a, s, fast=False) == pos_coeffs + + a = AlgebraicNumber(-sqrt(3)) + + assert is_isomorphism_possible(a, p) is True + assert is_isomorphism_possible(a, q) is True + assert is_isomorphism_possible(a, r) is True + assert is_isomorphism_possible(a, s) is True + + assert field_isomorphism(a, p, fast=True) == pos_coeffs + assert field_isomorphism(a, q, fast=True) == pos_coeffs + assert field_isomorphism(a, r, fast=True) == neg_coeffs + assert field_isomorphism(a, s, fast=True) == neg_coeffs + + assert field_isomorphism(a, p, fast=False) == pos_coeffs + assert field_isomorphism(a, q, fast=False) == pos_coeffs + assert field_isomorphism(a, r, fast=False) == neg_coeffs + assert field_isomorphism(a, s, fast=False) == neg_coeffs + + pos_coeffs = [ Rational(3, 2), S.Zero, Rational(-33, 2), -S(8)] + neg_coeffs = [Rational(-3, 2), S.Zero, Rational(33, 2), -S(8)] + + a = AlgebraicNumber(3*sqrt(3) - 8) + + assert is_isomorphism_possible(a, p) is True + assert is_isomorphism_possible(a, q) is True + assert is_isomorphism_possible(a, r) is True + assert is_isomorphism_possible(a, s) is True + + assert field_isomorphism(a, p, fast=True) == neg_coeffs + assert field_isomorphism(a, q, fast=True) == neg_coeffs + assert field_isomorphism(a, r, fast=True) == pos_coeffs + assert field_isomorphism(a, s, fast=True) == pos_coeffs + + assert field_isomorphism(a, p, fast=False) == neg_coeffs + assert field_isomorphism(a, q, fast=False) == neg_coeffs + assert field_isomorphism(a, r, fast=False) == pos_coeffs + assert field_isomorphism(a, s, fast=False) == pos_coeffs + + a = AlgebraicNumber(3*sqrt(2) + 2*sqrt(3) + 1) + + pos_1_coeffs = [ S.Half, S.Zero, Rational(-5, 2), S.One] + neg_5_coeffs = [Rational(-5, 2), S.Zero, Rational(49, 2), S.One] + pos_5_coeffs = [ Rational(5, 2), S.Zero, Rational(-49, 2), S.One] + neg_1_coeffs = [Rational(-1, 2), S.Zero, Rational(5, 2), S.One] + + assert is_isomorphism_possible(a, p) is True + assert is_isomorphism_possible(a, q) is True + assert is_isomorphism_possible(a, r) is True + assert is_isomorphism_possible(a, s) is True + + assert field_isomorphism(a, p, fast=True) == pos_1_coeffs + assert field_isomorphism(a, q, fast=True) == neg_5_coeffs + assert field_isomorphism(a, r, fast=True) == pos_5_coeffs + assert field_isomorphism(a, s, fast=True) == neg_1_coeffs + + assert field_isomorphism(a, p, fast=False) == pos_1_coeffs + assert field_isomorphism(a, q, fast=False) == neg_5_coeffs + assert field_isomorphism(a, r, fast=False) == pos_5_coeffs + assert field_isomorphism(a, s, fast=False) == neg_1_coeffs + + a = AlgebraicNumber(sqrt(2)) + b = AlgebraicNumber(sqrt(3)) + c = AlgebraicNumber(sqrt(7)) + + assert is_isomorphism_possible(a, b) is True + assert is_isomorphism_possible(b, a) is True + + assert is_isomorphism_possible(c, p) is False + + assert field_isomorphism(sqrt(2), sqrt(3), fast=True) is None + assert field_isomorphism(sqrt(3), sqrt(2), fast=True) is None + + assert field_isomorphism(sqrt(2), sqrt(3), fast=False) is None + assert field_isomorphism(sqrt(3), sqrt(2), fast=False) is None + + a = AlgebraicNumber(sqrt(2)) + b = AlgebraicNumber(2 ** (S(1) / 3)) + + assert is_isomorphism_possible(a, b) is False + assert field_isomorphism(a, b) is None + + +def test_primitive_element(): + assert primitive_element([sqrt(2)], x) == (x**2 - 2, [1]) + assert primitive_element( + [sqrt(2), sqrt(3)], x) == (x**4 - 10*x**2 + 1, [1, 1]) + + assert primitive_element([sqrt(2)], x, polys=True) == (Poly(x**2 - 2, domain='QQ'), [1]) + assert primitive_element([sqrt( + 2), sqrt(3)], x, polys=True) == (Poly(x**4 - 10*x**2 + 1, domain='QQ'), [1, 1]) + + assert primitive_element( + [sqrt(2)], x, ex=True) == (x**2 - 2, [1], [[1, 0]]) + assert primitive_element([sqrt(2), sqrt(3)], x, ex=True) == \ + (x**4 - 10*x**2 + 1, [1, 1], [[Q(1, 2), 0, -Q(9, 2), 0], [- + Q(1, 2), 0, Q(11, 2), 0]]) + + assert primitive_element( + [sqrt(2)], x, ex=True, polys=True) == (Poly(x**2 - 2, domain='QQ'), [1], [[1, 0]]) + assert primitive_element([sqrt(2), sqrt(3)], x, ex=True, polys=True) == \ + (Poly(x**4 - 10*x**2 + 1, domain='QQ'), [1, 1], [[Q(1, 2), 0, -Q(9, 2), + 0], [-Q(1, 2), 0, Q(11, 2), 0]]) + + assert primitive_element([sqrt(2)], polys=True) == (Poly(x**2 - 2), [1]) + + raises(ValueError, lambda: primitive_element([], x, ex=False)) + raises(ValueError, lambda: primitive_element([], x, ex=True)) + + # Issue 14117 + a, b = I*sqrt(2*sqrt(2) + 3), I*sqrt(-2*sqrt(2) + 3) + assert primitive_element([a, b, I], x) == (x**4 + 6*x**2 + 1, [1, 0, 0]) + + assert primitive_element([sqrt(2), 0], x) == (x**2 - 2, [1, 0]) + assert primitive_element([0, sqrt(2)], x) == (x**2 - 2, [1, 1]) + assert primitive_element([sqrt(2), 0], x, ex=True) == (x**2 - 2, [1, 0], [[MPQ(1,1), MPQ(0,1)], []]) + assert primitive_element([0, sqrt(2)], x, ex=True) == (x**2 - 2, [1, 1], [[], [MPQ(1,1), MPQ(0,1)]]) + + +def test_to_number_field(): + assert to_number_field(sqrt(2)) == AlgebraicNumber(sqrt(2)) + assert to_number_field( + [sqrt(2), sqrt(3)]) == AlgebraicNumber(sqrt(2) + sqrt(3)) + + a = AlgebraicNumber(sqrt(2) + sqrt(3), [S.Half, S.Zero, Rational(-9, 2), S.Zero]) + + assert to_number_field(sqrt(2), sqrt(2) + sqrt(3)) == a + assert to_number_field(sqrt(2), AlgebraicNumber(sqrt(2) + sqrt(3))) == a + + raises(IsomorphismFailed, lambda: to_number_field(sqrt(2), sqrt(3))) + + +def test_issue_22561(): + a = to_number_field(sqrt(2), sqrt(2) + sqrt(3)) + b = to_number_field(sqrt(2), sqrt(2) + sqrt(5)) + assert field_isomorphism(a, b) == [1, 0] + + +def test_issue_22736(): + a = CRootOf(x**4 + x**3 + x**2 + x + 1, -1) + a._reset() + b = exp(2*I*pi/5) + assert field_isomorphism(a, b) == [1, 0] + + +def test_issue_27798(): + # https://github.com/sympy/sympy/issues/27798 + a, b = CRootOf(49*x**3 - 49*x**2 + 14*x - 1, 2), CRootOf(49*x**3 - 49*x**2 + 14*x - 1, 0) + assert primitive_element([a, b], polys=True)[0].primitive()[0] == 1 + assert primitive_element([a, b], polys=True, ex=True)[0].primitive()[0] == 1 + + f1, f2 = QQ.algebraic_field(a), QQ.algebraic_field(b) + f3 = f1.unify(f2) + assert f3.mod.primitive()[0] == 1 + assert Poly(x, x, domain=f1) + Poly(x, x, domain=f2) == Poly(2*x, x, domain=f3) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_utilities.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_utilities.py new file mode 100644 index 0000000000000000000000000000000000000000..134853ef0c88045ef9cc7e215bb98db37041e63a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/tests/test_utilities.py @@ -0,0 +1,113 @@ +from sympy.abc import x +from sympy.core.numbers import (I, Rational) +from sympy.core.singleton import S +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.polys import Poly, cyclotomic_poly +from sympy.polys.domains import FF, QQ +from sympy.polys.matrices import DomainMatrix, DM +from sympy.polys.matrices.exceptions import DMRankError +from sympy.polys.numberfields.utilities import ( + AlgIntPowers, coeff_search, extract_fundamental_discriminant, + isolate, supplement_a_subspace, +) +from sympy.printing.lambdarepr import IntervalPrinter +from sympy.testing.pytest import raises + + +def test_AlgIntPowers_01(): + T = Poly(cyclotomic_poly(5)) + zeta_pow = AlgIntPowers(T) + raises(ValueError, lambda: zeta_pow[-1]) + for e in range(10): + a = e % 5 + if a < 4: + c = zeta_pow[e] + assert c[a] == 1 and all(c[i] == 0 for i in range(4) if i != a) + else: + assert zeta_pow[e] == [-1] * 4 + + +def test_AlgIntPowers_02(): + T = Poly(x**3 + 2*x**2 + 3*x + 4) + m = 7 + theta_pow = AlgIntPowers(T, m) + for e in range(10): + computed = theta_pow[e] + coeffs = (Poly(x)**e % T + Poly(x**3)).rep.to_list()[1:] + expected = [c % m for c in reversed(coeffs)] + assert computed == expected + + +def test_coeff_search(): + C = [] + search = coeff_search(2, 1) + for i, c in enumerate(search): + C.append(c) + if i == 12: + break + assert C == [[1, 1], [1, 0], [1, -1], [0, 1], [2, 2], [2, 1], [2, 0], [2, -1], [2, -2], [1, 2], [1, -2], [0, 2], [3, 3]] + + +def test_extract_fundamental_discriminant(): + # To extract, integer must be 0 or 1 mod 4. + raises(ValueError, lambda: extract_fundamental_discriminant(2)) + raises(ValueError, lambda: extract_fundamental_discriminant(3)) + # Try many cases, of different forms: + cases = ( + (0, {}, {0: 1}), + (1, {}, {}), + (8, {2: 3}, {}), + (-8, {2: 3, -1: 1}, {}), + (12, {2: 2, 3: 1}, {}), + (36, {}, {2: 1, 3: 1}), + (45, {5: 1}, {3: 1}), + (48, {2: 2, 3: 1}, {2: 1}), + (1125, {5: 1}, {3: 1, 5: 1}), + ) + for a, D_expected, F_expected in cases: + D, F = extract_fundamental_discriminant(a) + assert D == D_expected + assert F == F_expected + + +def test_supplement_a_subspace_1(): + M = DM([[1, 7, 0], [2, 3, 4]], QQ).transpose() + + # First supplement over QQ: + B = supplement_a_subspace(M) + assert B[:, :2] == M + assert B[:, 2] == DomainMatrix.eye(3, QQ).to_dense()[:, 0] + + # Now supplement over FF(7): + M = M.convert_to(FF(7)) + B = supplement_a_subspace(M) + assert B[:, :2] == M + # When we work mod 7, first col of M goes to [1, 0, 0], + # so the supplementary vector cannot equal this, as it did + # when we worked over QQ. Instead, we get the second std basis vector: + assert B[:, 2] == DomainMatrix.eye(3, FF(7)).to_dense()[:, 1] + + +def test_supplement_a_subspace_2(): + M = DM([[1, 0, 0], [2, 0, 0]], QQ).transpose() + with raises(DMRankError): + supplement_a_subspace(M) + + +def test_IntervalPrinter(): + ip = IntervalPrinter() + assert ip.doprint(x**Rational(1, 3)) == "x**(mpi('1/3'))" + assert ip.doprint(sqrt(x)) == "x**(mpi('1/2'))" + + +def test_isolate(): + assert isolate(1) == (1, 1) + assert isolate(S.Half) == (S.Half, S.Half) + + assert isolate(sqrt(2)) == (1, 2) + assert isolate(-sqrt(2)) == (-2, -1) + + assert isolate(sqrt(2), eps=Rational(1, 100)) == (Rational(24, 17), Rational(17, 12)) + assert isolate(-sqrt(2), eps=Rational(1, 100)) == (Rational(-17, 12), Rational(-24, 17)) + + raises(NotImplementedError, lambda: isolate(I)) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/numberfields/utilities.py b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/utilities.py new file mode 100644 index 0000000000000000000000000000000000000000..fe583efb440f02f1b16c38fb7d03621c1f97e83d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/polys/numberfields/utilities.py @@ -0,0 +1,474 @@ +"""Utilities for algebraic number theory. """ + +from sympy.core.sympify import sympify +from sympy.ntheory.factor_ import factorint +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.domains.integerring import ZZ +from sympy.polys.matrices.exceptions import DMRankError +from sympy.polys.numberfields.minpoly import minpoly +from sympy.printing.lambdarepr import IntervalPrinter +from sympy.utilities.decorator import public +from sympy.utilities.lambdify import lambdify + +from mpmath import mp + + +def is_rat(c): + r""" + Test whether an argument is of an acceptable type to be used as a rational + number. + + Explanation + =========== + + Returns ``True`` on any argument of type ``int``, :ref:`ZZ`, or :ref:`QQ`. + + See Also + ======== + + is_int + + """ + # ``c in QQ`` is too accepting (e.g. ``3.14 in QQ`` is ``True``), + # ``QQ.of_type(c)`` is too demanding (e.g. ``QQ.of_type(3)`` is ``False``). + # + # Meanwhile, if gmpy2 is installed then ``ZZ.of_type()`` accepts only + # ``mpz``, not ``int``, so we need another clause to ensure ``int`` is + # accepted. + return isinstance(c, int) or ZZ.of_type(c) or QQ.of_type(c) + + +def is_int(c): + r""" + Test whether an argument is of an acceptable type to be used as an integer. + + Explanation + =========== + + Returns ``True`` on any argument of type ``int`` or :ref:`ZZ`. + + See Also + ======== + + is_rat + + """ + # If gmpy2 is installed then ``ZZ.of_type()`` accepts only + # ``mpz``, not ``int``, so we need another clause to ensure ``int`` is + # accepted. + return isinstance(c, int) or ZZ.of_type(c) + + +def get_num_denom(c): + r""" + Given any argument on which :py:func:`~.is_rat` is ``True``, return the + numerator and denominator of this number. + + See Also + ======== + + is_rat + + """ + r = QQ(c) + return r.numerator, r.denominator + + +@public +def extract_fundamental_discriminant(a): + r""" + Extract a fundamental discriminant from an integer *a*. + + Explanation + =========== + + Given any rational integer *a* that is 0 or 1 mod 4, write $a = d f^2$, + where $d$ is either 1 or a fundamental discriminant, and return a pair + of dictionaries ``(D, F)`` giving the prime factorizations of $d$ and $f$ + respectively, in the same format returned by :py:func:`~.factorint`. + + A fundamental discriminant $d$ is different from unity, and is either + 1 mod 4 and squarefree, or is 0 mod 4 and such that $d/4$ is squarefree + and 2 or 3 mod 4. This is the same as being the discriminant of some + quadratic field. + + Examples + ======== + + >>> from sympy.polys.numberfields.utilities import extract_fundamental_discriminant + >>> print(extract_fundamental_discriminant(-432)) + ({3: 1, -1: 1}, {2: 2, 3: 1}) + + For comparison: + + >>> from sympy import factorint + >>> print(factorint(-432)) + {2: 4, 3: 3, -1: 1} + + Parameters + ========== + + a: int, must be 0 or 1 mod 4 + + Returns + ======= + + Pair ``(D, F)`` of dictionaries. + + Raises + ====== + + ValueError + If *a* is not 0 or 1 mod 4. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + (See Prop. 5.1.3) + + """ + if a % 4 not in [0, 1]: + raise ValueError('To extract fundamental discriminant, number must be 0 or 1 mod 4.') + if a == 0: + return {}, {0: 1} + if a == 1: + return {}, {} + a_factors = factorint(a) + D = {} + F = {} + # First pass: just make d squarefree, and a/d a perfect square. + # We'll count primes (and units! i.e. -1) that are 3 mod 4 and present in d. + num_3_mod_4 = 0 + for p, e in a_factors.items(): + if e % 2 == 1: + D[p] = 1 + if p % 4 == 3: + num_3_mod_4 += 1 + if e >= 3: + F[p] = (e - 1) // 2 + else: + F[p] = e // 2 + # Second pass: if d is cong. to 2 or 3 mod 4, then we must steal away + # another factor of 4 from f**2 and give it to d. + even = 2 in D + if even or num_3_mod_4 % 2 == 1: + e2 = F[2] + assert e2 > 0 + if e2 == 1: + del F[2] + else: + F[2] = e2 - 1 + D[2] = 3 if even else 2 + return D, F + + +@public +class AlgIntPowers: + r""" + Compute the powers of an algebraic integer. + + Explanation + =========== + + Given an algebraic integer $\theta$ by its monic irreducible polynomial + ``T`` over :ref:`ZZ`, this class computes representations of arbitrarily + high powers of $\theta$, as :ref:`ZZ`-linear combinations over + $\{1, \theta, \ldots, \theta^{n-1}\}$, where $n = \deg(T)$. + + The representations are computed using the linear recurrence relations for + powers of $\theta$, derived from the polynomial ``T``. See [1], Sec. 4.2.2. + + Optionally, the representations may be reduced with respect to a modulus. + + Examples + ======== + + >>> from sympy import Poly, cyclotomic_poly + >>> from sympy.polys.numberfields.utilities import AlgIntPowers + >>> T = Poly(cyclotomic_poly(5)) + >>> zeta_pow = AlgIntPowers(T) + >>> print(zeta_pow[0]) + [1, 0, 0, 0] + >>> print(zeta_pow[1]) + [0, 1, 0, 0] + >>> print(zeta_pow[4]) # doctest: +SKIP + [-1, -1, -1, -1] + >>> print(zeta_pow[24]) # doctest: +SKIP + [-1, -1, -1, -1] + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.* + + """ + + def __init__(self, T, modulus=None): + """ + Parameters + ========== + + T : :py:class:`~.Poly` + The monic irreducible polynomial over :ref:`ZZ` defining the + algebraic integer. + + modulus : int, None, optional + If not ``None``, all representations will be reduced w.r.t. this. + + """ + self.T = T + self.modulus = modulus + self.n = T.degree() + self.powers_n_and_up = [[-c % self for c in reversed(T.rep.to_list())][:-1]] + self.max_so_far = self.n + + def red(self, exp): + return exp if self.modulus is None else exp % self.modulus + + def __rmod__(self, other): + return self.red(other) + + def compute_up_through(self, e): + m = self.max_so_far + if e <= m: return + n = self.n + r = self.powers_n_and_up + c = r[0] + for k in range(m+1, e+1): + b = r[k-1-n][n-1] + r.append( + [c[0]*b % self] + [ + (r[k-1-n][i-1] + c[i]*b) % self for i in range(1, n) + ] + ) + self.max_so_far = e + + def get(self, e): + n = self.n + if e < 0: + raise ValueError('Exponent must be non-negative.') + elif e < n: + return [1 if i == e else 0 for i in range(n)] + else: + self.compute_up_through(e) + return self.powers_n_and_up[e - n] + + def __getitem__(self, item): + return self.get(item) + + +@public +def coeff_search(m, R): + r""" + Generate coefficients for searching through polynomials. + + Explanation + =========== + + Lead coeff is always non-negative. Explore all combinations with coeffs + bounded in absolute value before increasing the bound. Skip the all-zero + list, and skip any repeats. See examples. + + Examples + ======== + + >>> from sympy.polys.numberfields.utilities import coeff_search + >>> cs = coeff_search(2, 1) + >>> C = [next(cs) for i in range(13)] + >>> print(C) + [[1, 1], [1, 0], [1, -1], [0, 1], [2, 2], [2, 1], [2, 0], [2, -1], [2, -2], + [1, 2], [1, -2], [0, 2], [3, 3]] + + Parameters + ========== + + m : int + Length of coeff list. + R : int + Initial max abs val for coeffs (will increase as search proceeds). + + Returns + ======= + + generator + Infinite generator of lists of coefficients. + + """ + R0 = R + c = [R] * m + while True: + if R == R0 or R in c or -R in c: + yield c[:] + j = m - 1 + while c[j] == -R: + j -= 1 + c[j] -= 1 + for i in range(j + 1, m): + c[i] = R + for j in range(m): + if c[j] != 0: + break + else: + R += 1 + c = [R] * m + + +def supplement_a_subspace(M): + r""" + Extend a basis for a subspace to a basis for the whole space. + + Explanation + =========== + + Given an $n \times r$ matrix *M* of rank $r$ (so $r \leq n$), this function + computes an invertible $n \times n$ matrix $B$ such that the first $r$ + columns of $B$ equal *M*. + + This operation can be interpreted as a way of extending a basis for a + subspace, to give a basis for the whole space. + + To be precise, suppose you have an $n$-dimensional vector space $V$, with + basis $\{v_1, v_2, \ldots, v_n\}$, and an $r$-dimensional subspace $W$ of + $V$, spanned by a basis $\{w_1, w_2, \ldots, w_r\}$, where the $w_j$ are + given as linear combinations of the $v_i$. If the columns of *M* represent + the $w_j$ as such linear combinations, then the columns of the matrix $B$ + computed by this function give a new basis $\{u_1, u_2, \ldots, u_n\}$ for + $V$, again relative to the $\{v_i\}$ basis, and such that $u_j = w_j$ + for $1 \leq j \leq r$. + + Examples + ======== + + Note: The function works in terms of columns, so in these examples we + print matrix transposes in order to make the columns easier to inspect. + + >>> from sympy.polys.matrices import DM + >>> from sympy import QQ, FF + >>> from sympy.polys.numberfields.utilities import supplement_a_subspace + >>> M = DM([[1, 7, 0], [2, 3, 4]], QQ).transpose() + >>> print(supplement_a_subspace(M).to_Matrix().transpose()) + Matrix([[1, 7, 0], [2, 3, 4], [1, 0, 0]]) + + >>> M2 = M.convert_to(FF(7)) + >>> print(M2.to_Matrix().transpose()) + Matrix([[1, 0, 0], [2, 3, -3]]) + >>> print(supplement_a_subspace(M2).to_Matrix().transpose()) + Matrix([[1, 0, 0], [2, 3, -3], [0, 1, 0]]) + + Parameters + ========== + + M : :py:class:`~.DomainMatrix` + The columns give the basis for the subspace. + + Returns + ======= + + :py:class:`~.DomainMatrix` + This matrix is invertible and its first $r$ columns equal *M*. + + Raises + ====== + + DMRankError + If *M* was not of maximal rank. + + References + ========== + + .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory* + (See Sec. 2.3.2.) + + """ + n, r = M.shape + # Let In be the n x n identity matrix. + # Form the augmented matrix [M | In] and compute RREF. + Maug = M.hstack(M.eye(n, M.domain)) + R, pivots = Maug.rref() + if pivots[:r] != tuple(range(r)): + raise DMRankError('M was not of maximal rank') + # Let J be the n x r matrix equal to the first r columns of In. + # Since M is of rank r, RREF reduces [M | In] to [J | A], where A is the product of + # elementary matrices Ei corresp. to the row ops performed by RREF. Since the Ei are + # invertible, so is A. Let B = A^(-1). + A = R[:, r:] + B = A.inv() + # Then B is the desired matrix. It is invertible, since B^(-1) == A. + # And A * [M | In] == [J | A] + # => A * M == J + # => M == B * J == the first r columns of B. + return B + + +@public +def isolate(alg, eps=None, fast=False): + """ + Find a rational isolating interval for a real algebraic number. + + Examples + ======== + + >>> from sympy import isolate, sqrt, Rational + >>> print(isolate(sqrt(2))) # doctest: +SKIP + (1, 2) + >>> print(isolate(sqrt(2), eps=Rational(1, 100))) + (24/17, 17/12) + + Parameters + ========== + + alg : str, int, :py:class:`~.Expr` + The algebraic number to be isolated. Must be a real number, to use this + particular function. However, see also :py:meth:`.Poly.intervals`, + which isolates complex roots when you pass ``all=True``. + eps : positive element of :ref:`QQ`, None, optional (default=None) + Precision to be passed to :py:meth:`.Poly.refine_root` + fast : boolean, optional (default=False) + Say whether fast refinement procedure should be used. + (Will be passed to :py:meth:`.Poly.refine_root`.) + + Returns + ======= + + Pair of rational numbers defining an isolating interval for the given + algebraic number. + + See Also + ======== + + .Poly.intervals + + """ + alg = sympify(alg) + + if alg.is_Rational: + return (alg, alg) + elif not alg.is_real: + raise NotImplementedError( + "complex algebraic numbers are not supported") + + func = lambdify((), alg, modules="mpmath", printer=IntervalPrinter()) + + poly = minpoly(alg, polys=True) + intervals = poly.intervals(sqf=True) + + dps, done = mp.dps, False + + try: + while not done: + alg = func() + + for a, b in intervals: + if a <= alg.a and alg.b <= b: + done = True + break + else: + mp.dps *= 2 + finally: + mp.dps = dps + + if eps is not None: + a, b = poly.refine_root(a, b, eps=eps, fast=fast) + + return (a, b) diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b391aed40e7dc265b83f4b8370c9246efb8769c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densearith.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densearith.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad47af47d38a7e54f115d6fa6a0e201fe9aaea18 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densearith.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densebasic.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densebasic.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0066b78613a34051db911f567da3f5585d1c52e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densebasic.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densetools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densetools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a843d3fb27868d207485979b38e987a56c0cd96 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_densetools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_distributedmodules.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_distributedmodules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45fcf18a1b3658f937205167720f9171312bafc1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_distributedmodules.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_fields.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_fields.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4868edbdd536b5cddd82b50288e245536051681a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_fields.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_galoistools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_galoistools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..273cb44d309a00ae594912a0c7dcea23b12d6933 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_galoistools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_hypothesis.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_hypothesis.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae64aab5c0ec236ef588f09748a343a0c35f320a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_hypothesis.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_monomials.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_monomials.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..391d88c88d2b7c2d55c78b2e04cec85678c41a15 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_monomials.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_orthopolys.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_orthopolys.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32df2f2d1dce48d42b34e3ffc81e0327425708ee Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_orthopolys.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_partfrac.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_partfrac.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e930cbff54f4c5bf7661c984c8a18095b4393719 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_partfrac.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyfuncs.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyfuncs.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8664eee11ec2a28bc7a07d4c843535fd5e84b95c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyfuncs.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polymatrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polymatrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6a93025010ba51700372874f6bdcbc8a1295eed Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polymatrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyroots.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyroots.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7271ae74e224a8a7b1c23389a6a65acd9292513 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyroots.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyutils.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyutils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58c420d732f2e4dfb2c113bcd4e6cdbb95bb9d5d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_polyutils.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_puiseux.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_puiseux.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95ea7fb2c2aeb1e755ebffcde85ba7e4ecaeb8fb Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_puiseux.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_pythonrational.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_pythonrational.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3c7975d59b5e7156ecffad52749067d5d5ca364 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_pythonrational.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rationaltools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rationaltools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b010a14ca6ea07f1e13bbedc1b1414b810df6fd Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rationaltools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_ring_series.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_ring_series.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0cddf215e801b3246f7aadb47fc1a95af4766054 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_ring_series.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rings.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rings.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6611369838680178cc51f6e2a3fbeb5667ff0500 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rings.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rootoftools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rootoftools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c55449dc1ec5aacf9ae3099d25da5a22814428e9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_rootoftools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_solvers.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_solvers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bf72999374184458bdfcc841744fd6a1f07dd87 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_solvers.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_specialpolys.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_specialpolys.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e4769f114d7256af31d86949e4015a85b3a620c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_specialpolys.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_sqfreetools.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_sqfreetools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8489c418cc3a689b951f8a8794e65fe17a6cee57 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_sqfreetools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_subresultants_qq_zz.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_subresultants_qq_zz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7afa91c6582494e250bf1629671b08716de23200 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/polys/tests/__pycache__/test_subresultants_qq_zz.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__init__.py b/venv/lib/python3.10/site-packages/sympy/printing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..15dfaf70eb3777195b7c9a0930894bb2187bbb50 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/__init__.py @@ -0,0 +1,111 @@ +"""Printing subsystem""" + +from .pretty import pager_print, pretty, pretty_print, pprint, pprint_use_unicode, pprint_try_use_unicode + +from .latex import latex, print_latex, multiline_latex + +from .mathml import mathml, print_mathml + +from .python import python, print_python + +from .pycode import pycode + +from .codeprinter import print_ccode, print_fcode + +from .codeprinter import ccode, fcode, cxxcode, rust_code # noqa:F811 + +from .smtlib import smtlib_code + +from .glsl import glsl_code, print_glsl + +from .rcode import rcode, print_rcode + +from .jscode import jscode, print_jscode + +from .julia import julia_code + +from .mathematica import mathematica_code + +from .octave import octave_code + +from .gtk import print_gtk + +from .preview import preview + +from .repr import srepr + +from .tree import print_tree + +from .str import StrPrinter, sstr, sstrrepr + +from .tableform import TableForm + +from .dot import dotprint + +from .maple import maple_code, print_maple_code + +__all__ = [ + # sympy.printing.pretty + 'pager_print', 'pretty', 'pretty_print', 'pprint', 'pprint_use_unicode', + 'pprint_try_use_unicode', + + # sympy.printing.latex + 'latex', 'print_latex', 'multiline_latex', + + # sympy.printing.mathml + 'mathml', 'print_mathml', + + # sympy.printing.python + 'python', 'print_python', + + # sympy.printing.pycode + 'pycode', + + # sympy.printing.codeprinter + 'ccode', 'print_ccode', 'cxxcode', 'fcode', 'print_fcode', 'rust_code', + + # sympy.printing.smtlib + 'smtlib_code', + + # sympy.printing.glsl + 'glsl_code', 'print_glsl', + + # sympy.printing.rcode + 'rcode', 'print_rcode', + + # sympy.printing.jscode + 'jscode', 'print_jscode', + + # sympy.printing.julia + 'julia_code', + + # sympy.printing.mathematica + 'mathematica_code', + + # sympy.printing.octave + 'octave_code', + + # sympy.printing.gtk + 'print_gtk', + + # sympy.printing.preview + 'preview', + + # sympy.printing.repr + 'srepr', + + # sympy.printing.tree + 'print_tree', + + # sympy.printing.str + 'StrPrinter', 'sstr', 'sstrrepr', + + # sympy.printing.tableform + 'TableForm', + + # sympy.printing.dot + 'dotprint', + + # sympy.printing.maple + 'maple_code', 'print_maple_code', +] diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71913fc272505a64fac401357195953357aac538 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/aesaracode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/aesaracode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..247ed8aa8ec632bb42795b9c716e14468966ed69 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/aesaracode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/c.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/c.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79c39924267117fc4948066d715418145387929b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/c.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/codeprinter.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/codeprinter.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a11a633bd3a7edeb0972e6d3c628bdb3ca54ab0a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/codeprinter.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/conventions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/conventions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bfa0084216603fcbc2f00cacd0d4c35ffb9f804 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/conventions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/cxx.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/cxx.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..133bc16b16e97fdc24aa4b0742ecb16de9d685e3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/cxx.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/defaults.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/defaults.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97c9eb59eda3eb54c3ed21ec189fe42635584a0c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/defaults.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/dot.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/dot.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe970d5f55226515b456704c618ed588d90069a2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/dot.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/fortran.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/fortran.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1dd88000bdd24209ea334980233f3b48af3c38ad Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/fortran.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/glsl.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/glsl.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e3332296129e96ccd4206b65f3bc16ebf8e667c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/glsl.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/gtk.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/gtk.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bcf2573290aecee8df8cb8194d83aeca0f5892ef Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/gtk.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/jscode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/jscode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eba165b0b443e78383470232bbcad36e494ac276 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/jscode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/julia.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/julia.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79020ece836dfbc0c0ef998a0001974f127a79b7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/julia.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/lambdarepr.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/lambdarepr.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc0e9bad304d8034919d58b17a44fe1a35cdf3b2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/lambdarepr.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/llvmjitcode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/llvmjitcode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d95daf66be1976b8f550ae03fa700d9784c26742 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/llvmjitcode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/maple.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/maple.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..581b157d93bc42f022d23bd067f62ba4769cc296 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/maple.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/mathematica.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/mathematica.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97ebcd8b09ca56164028ff7235291e4a089d7cbe Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/mathematica.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/mathml.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/mathml.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a04aec60f7c716eb3c7871166151202af4f9ebe Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/mathml.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/numpy.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/numpy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c1c8a2e6d7640d196bf4521b95792e0abf4ac52 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/numpy.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/octave.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/octave.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffe6db2d4f45519e8240d186b0df664c45c5324d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/octave.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/precedence.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/precedence.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b76380c046ee2233989d6b7387acc22c943d2f0c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/precedence.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/preview.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/preview.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e32e768a987c9a0d03c28bbc4856a2efe1a8384e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/preview.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/printer.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/printer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19d4f45f421d46f91bc8ede45c89875e1392aa79 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/printer.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/pycode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/pycode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4430e7ffbc276cb1d65946a12b144749c77aaa90 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/pycode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/python.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/python.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d60fdea6c31433f899c76ee25a2d5a9eb91f7db2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/python.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/pytorch.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/pytorch.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75ceae1402797f3d3e0ce243639d85df1e887482 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/pytorch.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/rcode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/rcode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e36905f4f21085af498b996de84d19dc9d59efc6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/rcode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/repr.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/repr.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1a11bf73aaee072e37910764cdde5a112d9be09 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/repr.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/rust.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/rust.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc50d4a8c25626a2062f52480921bd1eac72c377 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/rust.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/smtlib.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/smtlib.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cb5e0b7912238540bb3a64ca1b903f8ab06b9c7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/smtlib.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/str.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/str.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89acf88604c1bf2134a3b5cee5fd15b5600a7c4a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/str.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tableform.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tableform.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2146eeca796430fc079a1cc4196afb23dfc06fbb Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tableform.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tensorflow.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tensorflow.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c06691c1a731bc4a15c61e4cb4bf477bb3ef28bf Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tensorflow.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/theanocode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/theanocode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f581ba366f6fc01f8add5c6e75e5fd246c75ca6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/theanocode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tree.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tree.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15feeef945eec9ee1f0af8ecafcba465d1623f15 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/__pycache__/tree.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/aesaracode.py b/venv/lib/python3.10/site-packages/sympy/printing/aesaracode.py new file mode 100644 index 0000000000000000000000000000000000000000..1e31c6940a86bd25ef8805420b84c22c5e08bca9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/aesaracode.py @@ -0,0 +1,563 @@ +from __future__ import annotations +import math +from typing import Any + +from sympy.external import import_module +from sympy.printing.printer import Printer +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import is_sequence +import sympy +from functools import partial + + +aesara = import_module('aesara') + +if aesara: + aes = aesara.scalar + aet = aesara.tensor + from aesara.tensor import nlinalg + from aesara.tensor.elemwise import Elemwise + from aesara.tensor.elemwise import DimShuffle + + # `true_divide` replaced `true_div` in Aesara 2.8.11 (released 2023) to + # match NumPy + # XXX: Remove this when not needed to support older versions. + true_divide = getattr(aet, 'true_divide', None) + if true_divide is None: + true_divide = aet.true_div + + mapping = { + sympy.Add: aet.add, + sympy.Mul: aet.mul, + sympy.Abs: aet.abs, + sympy.sign: aet.sgn, + sympy.ceiling: aet.ceil, + sympy.floor: aet.floor, + sympy.log: aet.log, + sympy.exp: aet.exp, + sympy.sqrt: aet.sqrt, + sympy.cos: aet.cos, + sympy.acos: aet.arccos, + sympy.sin: aet.sin, + sympy.asin: aet.arcsin, + sympy.tan: aet.tan, + sympy.atan: aet.arctan, + sympy.atan2: aet.arctan2, + sympy.cosh: aet.cosh, + sympy.acosh: aet.arccosh, + sympy.sinh: aet.sinh, + sympy.asinh: aet.arcsinh, + sympy.tanh: aet.tanh, + sympy.atanh: aet.arctanh, + sympy.re: aet.real, + sympy.im: aet.imag, + sympy.arg: aet.angle, + sympy.erf: aet.erf, + sympy.gamma: aet.gamma, + sympy.loggamma: aet.gammaln, + sympy.Pow: aet.pow, + sympy.Eq: aet.eq, + sympy.StrictGreaterThan: aet.gt, + sympy.StrictLessThan: aet.lt, + sympy.LessThan: aet.le, + sympy.GreaterThan: aet.ge, + sympy.And: aet.bitwise_and, # bitwise + sympy.Or: aet.bitwise_or, # bitwise + sympy.Not: aet.invert, # bitwise + sympy.Xor: aet.bitwise_xor, # bitwise + sympy.Max: aet.maximum, # Sympy accept >2 inputs, Aesara only 2 + sympy.Min: aet.minimum, # Sympy accept >2 inputs, Aesara only 2 + sympy.conjugate: aet.conj, + sympy.core.numbers.ImaginaryUnit: lambda:aet.complex(0,1), + # Matrices + sympy.MatAdd: Elemwise(aes.add), + sympy.HadamardProduct: Elemwise(aes.mul), + sympy.Trace: nlinalg.trace, + sympy.Determinant : nlinalg.det, + sympy.Inverse: nlinalg.matrix_inverse, + sympy.Transpose: DimShuffle((False, False), [1, 0]), + } + + +class AesaraPrinter(Printer): + """ + .. deprecated:: 1.14. + The ``Aesara Code printing`` is deprecated.See its documentation for + more information. See :ref:`deprecated-aesaraprinter` for details. + + Code printer which creates Aesara symbolic expression graphs. + + Parameters + ========== + + cache : dict + Cache dictionary to use. If None (default) will use + the global cache. To create a printer which does not depend on or alter + global state pass an empty dictionary. Note: the dictionary is not + copied on initialization of the printer and will be updated in-place, + so using the same dict object when creating multiple printers or making + multiple calls to :func:`.aesara_code` or :func:`.aesara_function` means + the cache is shared between all these applications. + + Attributes + ========== + + cache : dict + A cache of Aesara variables which have been created for SymPy + symbol-like objects (e.g. :class:`sympy.core.symbol.Symbol` or + :class:`sympy.matrices.expressions.MatrixSymbol`). This is used to + ensure that all references to a given symbol in an expression (or + multiple expressions) are printed as the same Aesara variable, which is + created only once. Symbols are differentiated only by name and type. The + format of the cache's contents should be considered opaque to the user. + """ + printmethod = "_aesara" + + def __init__(self, *args, **kwargs): + self.cache = kwargs.pop('cache', {}) + super().__init__(*args, **kwargs) + + def _get_key(self, s, name=None, dtype=None, broadcastable=None): + """ Get the cache key for a SymPy object. + + Parameters + ========== + + s : sympy.core.basic.Basic + SymPy object to get key for. + + name : str + Name of object, if it does not have a ``name`` attribute. + """ + + if name is None: + name = s.name + + return (name, type(s), s.args, dtype, broadcastable) + + def _get_or_create(self, s, name=None, dtype=None, broadcastable=None): + """ + Get the Aesara variable for a SymPy symbol from the cache, or create it + if it does not exist. + """ + + # Defaults + if name is None: + name = s.name + if dtype is None: + dtype = 'floatX' + if broadcastable is None: + broadcastable = () + + key = self._get_key(s, name, dtype=dtype, broadcastable=broadcastable) + + if key in self.cache: + return self.cache[key] + + value = aet.tensor(name=name, dtype=dtype, shape=broadcastable) + self.cache[key] = value + return value + + def _print_Symbol(self, s, **kwargs): + dtype = kwargs.get('dtypes', {}).get(s) + bc = kwargs.get('broadcastables', {}).get(s) + return self._get_or_create(s, dtype=dtype, broadcastable=bc) + + def _print_AppliedUndef(self, s, **kwargs): + name = str(type(s)) + '_' + str(s.args[0]) + dtype = kwargs.get('dtypes', {}).get(s) + bc = kwargs.get('broadcastables', {}).get(s) + return self._get_or_create(s, name=name, dtype=dtype, broadcastable=bc) + + def _print_Basic(self, expr, **kwargs): + op = mapping[type(expr)] + children = [self._print(arg, **kwargs) for arg in expr.args] + return op(*children) + + def _print_Number(self, n, **kwargs): + # Integers already taken care of below, interpret as float + return float(n.evalf()) + + def _print_MatrixSymbol(self, X, **kwargs): + dtype = kwargs.get('dtypes', {}).get(X) + return self._get_or_create(X, dtype=dtype, broadcastable=(None, None)) + + def _print_DenseMatrix(self, X, **kwargs): + if not hasattr(aet, 'stacklists'): + raise NotImplementedError( + "Matrix translation not yet supported in this version of Aesara") + + return aet.stacklists([ + [self._print(arg, **kwargs) for arg in L] + for L in X.tolist() + ]) + + _print_ImmutableMatrix = _print_ImmutableDenseMatrix = _print_DenseMatrix + + def _print_MatMul(self, expr, **kwargs): + children = [self._print(arg, **kwargs) for arg in expr.args] + result = children[0] + for child in children[1:]: + result = aet.dot(result, child) + return result + + def _print_MatPow(self, expr, **kwargs): + children = [self._print(arg, **kwargs) for arg in expr.args] + result = 1 + if isinstance(children[1], int) and children[1] > 0: + for i in range(children[1]): + result = aet.dot(result, children[0]) + else: + raise NotImplementedError('''Only non-negative integer + powers of matrices can be handled by Aesara at the moment''') + return result + + def _print_MatrixSlice(self, expr, **kwargs): + parent = self._print(expr.parent, **kwargs) + rowslice = self._print(slice(*expr.rowslice), **kwargs) + colslice = self._print(slice(*expr.colslice), **kwargs) + return parent[rowslice, colslice] + + def _print_BlockMatrix(self, expr, **kwargs): + nrows, ncols = expr.blocks.shape + blocks = [[self._print(expr.blocks[r, c], **kwargs) + for c in range(ncols)] + for r in range(nrows)] + return aet.join(0, *[aet.join(1, *row) for row in blocks]) + + + def _print_slice(self, expr, **kwargs): + return slice(*[self._print(i, **kwargs) + if isinstance(i, sympy.Basic) else i + for i in (expr.start, expr.stop, expr.step)]) + + def _print_Pi(self, expr, **kwargs): + return math.pi + + def _print_Piecewise(self, expr, **kwargs): + import numpy as np + e, cond = expr.args[0].args # First condition and corresponding value + + # Print conditional expression and value for first condition + p_cond = self._print(cond, **kwargs) + p_e = self._print(e, **kwargs) + + # One condition only + if len(expr.args) == 1: + # Return value if condition else NaN + return aet.switch(p_cond, p_e, np.nan) + + # Return value_1 if condition_1 else evaluate remaining conditions + p_remaining = self._print(sympy.Piecewise(*expr.args[1:]), **kwargs) + return aet.switch(p_cond, p_e, p_remaining) + + def _print_Rational(self, expr, **kwargs): + return true_divide(self._print(expr.p, **kwargs), + self._print(expr.q, **kwargs)) + + def _print_Integer(self, expr, **kwargs): + return expr.p + + def _print_factorial(self, expr, **kwargs): + return self._print(sympy.gamma(expr.args[0] + 1), **kwargs) + + def _print_Derivative(self, deriv, **kwargs): + from aesara.gradient import Rop + + rv = self._print(deriv.expr, **kwargs) + for var in deriv.variables: + var = self._print(var, **kwargs) + rv = Rop(rv, var, aet.ones_like(var)) + return rv + + def emptyPrinter(self, expr): + return expr + + def doprint(self, expr, dtypes=None, broadcastables=None): + """ Convert a SymPy expression to a Aesara graph variable. + + The ``dtypes`` and ``broadcastables`` arguments are used to specify the + data type, dimension, and broadcasting behavior of the Aesara variables + corresponding to the free symbols in ``expr``. Each is a mapping from + SymPy symbols to the value of the corresponding argument to + ``aesara.tensor.var.TensorVariable``. + + See the corresponding `documentation page`__ for more information on + broadcasting in Aesara. + + + .. __: https://aesara.readthedocs.io/en/latest/reference/tensor/shapes.html#broadcasting + + Parameters + ========== + + expr : sympy.core.expr.Expr + SymPy expression to print. + + dtypes : dict + Mapping from SymPy symbols to Aesara datatypes to use when creating + new Aesara variables for those symbols. Corresponds to the ``dtype`` + argument to ``aesara.tensor.var.TensorVariable``. Defaults to ``'floatX'`` + for symbols not included in the mapping. + + broadcastables : dict + Mapping from SymPy symbols to the value of the ``broadcastable`` + argument to ``aesara.tensor.var.TensorVariable`` to use when creating Aesara + variables for those symbols. Defaults to the empty tuple for symbols + not included in the mapping (resulting in a scalar). + + Returns + ======= + + aesara.graph.basic.Variable + A variable corresponding to the expression's value in a Aesara + symbolic expression graph. + + """ + if dtypes is None: + dtypes = {} + if broadcastables is None: + broadcastables = {} + + return self._print(expr, dtypes=dtypes, broadcastables=broadcastables) + + +global_cache: dict[Any, Any] = {} + + +def aesara_code(expr, cache=None, **kwargs): + """ + Convert a SymPy expression into a Aesara graph variable. + + Parameters + ========== + + expr : sympy.core.expr.Expr + SymPy expression object to convert. + + cache : dict + Cached Aesara variables (see :class:`AesaraPrinter.cache + `). Defaults to the module-level global cache. + + dtypes : dict + Passed to :meth:`.AesaraPrinter.doprint`. + + broadcastables : dict + Passed to :meth:`.AesaraPrinter.doprint`. + + Returns + ======= + + aesara.graph.basic.Variable + A variable corresponding to the expression's value in a Aesara symbolic + expression graph. + + """ + sympy_deprecation_warning( + """ + The aesara_code function is deprecated. + """, + deprecated_since_version="1.14", + active_deprecations_target='deprecated-aesaraprinter', + ) + + if not aesara: + raise ImportError("aesara is required for aesara_code") + + if cache is None: + cache = global_cache + + return AesaraPrinter(cache=cache, settings={}).doprint(expr, **kwargs) + + +def dim_handling(inputs, dim=None, dims=None, broadcastables=None): + r""" + Get value of ``broadcastables`` argument to :func:`.aesara_code` from + keyword arguments to :func:`.aesara_function`. + + Included for backwards compatibility. + + Parameters + ========== + + inputs + Sequence of input symbols. + + dim : int + Common number of dimensions for all inputs. Overrides other arguments + if given. + + dims : dict + Mapping from input symbols to number of dimensions. Overrides + ``broadcastables`` argument if given. + + broadcastables : dict + Explicit value of ``broadcastables`` argument to + :meth:`.AesaraPrinter.doprint`. If not None function will return this value unchanged. + + Returns + ======= + dict + Dictionary mapping elements of ``inputs`` to their "broadcastable" + values (tuple of ``bool``\ s). + """ + if dim is not None: + return dict.fromkeys(inputs, (False,) * dim) + + if dims is not None: + maxdim = max(dims.values()) + return { + s: (False,) * d + (True,) * (maxdim - d) + for s, d in dims.items() + } + + if broadcastables is not None: + return broadcastables + + return {} + + +def aesara_function(inputs, outputs, scalar=False, *, + dim=None, dims=None, broadcastables=None, **kwargs): + """ + Create a Aesara function from SymPy expressions. + + The inputs and outputs are converted to Aesara variables using + :func:`.aesara_code` and then passed to ``aesara.function``. + + Parameters + ========== + + inputs + Sequence of symbols which constitute the inputs of the function. + + outputs + Sequence of expressions which constitute the outputs(s) of the + function. The free symbols of each expression must be a subset of + ``inputs``. + + scalar : bool + Convert 0-dimensional arrays in output to scalars. This will return a + Python wrapper function around the Aesara function object. + + cache : dict + Cached Aesara variables (see :class:`AesaraPrinter.cache + `). Defaults to the module-level global cache. + + dtypes : dict + Passed to :meth:`.AesaraPrinter.doprint`. + + broadcastables : dict + Passed to :meth:`.AesaraPrinter.doprint`. + + dims : dict + Alternative to ``broadcastables`` argument. Mapping from elements of + ``inputs`` to integers indicating the dimension of their associated + arrays/tensors. Overrides ``broadcastables`` argument if given. + + dim : int + Another alternative to the ``broadcastables`` argument. Common number of + dimensions to use for all arrays/tensors. + ``aesara_function([x, y], [...], dim=2)`` is equivalent to using + ``broadcastables={x: (False, False), y: (False, False)}``. + + Returns + ======= + callable + A callable object which takes values of ``inputs`` as positional + arguments and returns an output array for each of the expressions + in ``outputs``. If ``outputs`` is a single expression the function will + return a Numpy array, if it is a list of multiple expressions the + function will return a list of arrays. See description of the ``squeeze`` + argument above for the behavior when a single output is passed in a list. + The returned object will either be an instance of + ``aesara.compile.function.types.Function`` or a Python wrapper + function around one. In both cases, the returned value will have a + ``aesara_function`` attribute which points to the return value of + ``aesara.function``. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.printing.aesaracode import aesara_function + + A simple function with one input and one output: + + >>> f1 = aesara_function([x], [x**2 - 1], scalar=True) + >>> f1(3) + 8.0 + + A function with multiple inputs and one output: + + >>> f2 = aesara_function([x, y, z], [(x**z + y**z)**(1/z)], scalar=True) + >>> f2(3, 4, 2) + 5.0 + + A function with multiple inputs and multiple outputs: + + >>> f3 = aesara_function([x, y], [x**2 + y**2, x**2 - y**2], scalar=True) + >>> f3(2, 3) + [13.0, -5.0] + + See also + ======== + + dim_handling + + """ + sympy_deprecation_warning( + """ + The aesara_function function is deprecated. + """, + deprecated_since_version="1.14", + active_deprecations_target='deprecated-aesaraprinter', + ) + + if not aesara: + raise ImportError("Aesara is required for aesara_function") + + # Pop off non-aesara keyword args + cache = kwargs.pop('cache', {}) + dtypes = kwargs.pop('dtypes', {}) + + broadcastables = dim_handling( + inputs, dim=dim, dims=dims, broadcastables=broadcastables, + ) + + # Print inputs/outputs + code = partial(aesara_code, cache=cache, dtypes=dtypes, + broadcastables=broadcastables) + tinputs = list(map(code, inputs)) + toutputs = list(map(code, outputs)) + + #fix constant expressions as variables + toutputs = [output if isinstance(output, aesara.graph.basic.Variable) else aet.as_tensor_variable(output) for output in toutputs] + + if len(toutputs) == 1: + toutputs = toutputs[0] + + # Compile aesara func + func = aesara.function(tinputs, toutputs, **kwargs) + + is_0d = [len(o.variable.broadcastable) == 0 for o in func.outputs] + + # No wrapper required + if not scalar or not any(is_0d): + func.aesara_function = func + return func + + # Create wrapper to convert 0-dimensional outputs to scalars + def wrapper(*args): + out = func(*args) + # out can be array(1.0) or [array(1.0), array(2.0)] + + if is_sequence(out): + return [o[()] if is_0d[i] else o for i, o in enumerate(out)] + else: + return out[()] + + wrapper.__wrapped__ = func + wrapper.__doc__ = func.__doc__ + wrapper.aesara_function = func + return wrapper diff --git a/venv/lib/python3.10/site-packages/sympy/printing/c.py b/venv/lib/python3.10/site-packages/sympy/printing/c.py new file mode 100644 index 0000000000000000000000000000000000000000..34c4b8f021073aeee7672248838557b5fa85fbae --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/c.py @@ -0,0 +1,747 @@ +""" +C code printer + +The C89CodePrinter & C99CodePrinter converts single SymPy expressions into +single C expressions, using the functions defined in math.h where possible. + +A complete code generator, which uses ccode extensively, can be found in +sympy.utilities.codegen. The codegen module can be used to generate complete +source code files that are compilable without further modifications. + + +""" + +from __future__ import annotations +from typing import Any + +from functools import wraps +from itertools import chain + +from sympy.core import S +from sympy.core.numbers import equal_valued, Float +from sympy.codegen.ast import ( + Assignment, Pointer, Variable, Declaration, Type, + real, complex_, integer, bool_, float32, float64, float80, + complex64, complex128, intc, value_const, pointer_const, + int8, int16, int32, int64, uint8, uint16, uint32, uint64, untyped, + none +) +from sympy.printing.codeprinter import CodePrinter, requires +from sympy.printing.precedence import precedence, PRECEDENCE +from sympy.sets.fancysets import Range + +# These are defined in the other file so we can avoid importing sympy.codegen +# from the top-level 'import sympy'. Export them here as well. +from sympy.printing.codeprinter import ccode, print_ccode # noqa:F401 + +# dictionary mapping SymPy function to (argument_conditions, C_function). +# Used in C89CodePrinter._print_Function(self) +known_functions_C89 = { + "Abs": [(lambda x: not x.is_integer, "fabs"), (lambda x: x.is_integer, "abs")], + "sin": "sin", + "cos": "cos", + "tan": "tan", + "asin": "asin", + "acos": "acos", + "atan": "atan", + "atan2": "atan2", + "exp": "exp", + "log": "log", + "log10": "log10", + "sinh": "sinh", + "cosh": "cosh", + "tanh": "tanh", + "floor": "floor", + "ceiling": "ceil", + "sqrt": "sqrt", # To enable automatic rewrites +} + +known_functions_C99 = dict(known_functions_C89, **{ + 'exp2': 'exp2', + 'expm1': 'expm1', + 'log2': 'log2', + 'log1p': 'log1p', + 'Cbrt': 'cbrt', + 'hypot': 'hypot', + 'fma': 'fma', + 'loggamma': 'lgamma', + 'erfc': 'erfc', + 'Max': 'fmax', + 'Min': 'fmin', + "asinh": "asinh", + "acosh": "acosh", + "atanh": "atanh", + "erf": "erf", + "gamma": "tgamma", +}) + +# These are the core reserved words in the C language. Taken from: +# https://en.cppreference.com/w/c/keyword + +reserved_words = [ + 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', + 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'int', + 'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static', + 'struct', 'entry', # never standardized, we'll leave it here anyway + 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while' +] + +reserved_words_c99 = ['inline', 'restrict'] + +def get_math_macros(): + """ Returns a dictionary with math-related macros from math.h/cmath + + Note that these macros are not strictly required by the C/C++-standard. + For MSVC they are enabled by defining "_USE_MATH_DEFINES" (preferably + via a compilation flag). + + Returns + ======= + + Dictionary mapping SymPy expressions to strings (macro names) + + """ + from sympy.codegen.cfunctions import log2, Sqrt + from sympy.functions.elementary.exponential import log + from sympy.functions.elementary.miscellaneous import sqrt + + return { + S.Exp1: 'M_E', + log2(S.Exp1): 'M_LOG2E', + 1/log(2): 'M_LOG2E', + log(2): 'M_LN2', + log(10): 'M_LN10', + S.Pi: 'M_PI', + S.Pi/2: 'M_PI_2', + S.Pi/4: 'M_PI_4', + 1/S.Pi: 'M_1_PI', + 2/S.Pi: 'M_2_PI', + 2/sqrt(S.Pi): 'M_2_SQRTPI', + 2/Sqrt(S.Pi): 'M_2_SQRTPI', + sqrt(2): 'M_SQRT2', + Sqrt(2): 'M_SQRT2', + 1/sqrt(2): 'M_SQRT1_2', + 1/Sqrt(2): 'M_SQRT1_2' + } + + +def _as_macro_if_defined(meth): + """ Decorator for printer methods + + When a Printer's method is decorated using this decorator the expressions printed + will first be looked for in the attribute ``math_macros``, and if present it will + print the macro name in ``math_macros`` followed by a type suffix for the type + ``real``. e.g. printing ``sympy.pi`` would print ``M_PIl`` if real is mapped to float80. + + """ + @wraps(meth) + def _meth_wrapper(self, expr, **kwargs): + if expr in self.math_macros: + return '%s%s' % (self.math_macros[expr], self._get_math_macro_suffix(real)) + else: + return meth(self, expr, **kwargs) + + return _meth_wrapper + + +class C89CodePrinter(CodePrinter): + """A printer to convert Python expressions to strings of C code""" + printmethod = "_ccode" + language = "C" + standard = "C89" + reserved_words = set(reserved_words) + + _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{ + 'precision': 17, + 'user_functions': {}, + 'contract': True, + 'dereference': set(), + 'error_on_reserved': False, + }) + + type_aliases = { + real: float64, + complex_: complex128, + integer: intc + } + + type_mappings: dict[Type, Any] = { + real: 'double', + intc: 'int', + float32: 'float', + float64: 'double', + integer: 'int', + bool_: 'bool', + int8: 'int8_t', + int16: 'int16_t', + int32: 'int32_t', + int64: 'int64_t', + uint8: 'int8_t', + uint16: 'int16_t', + uint32: 'int32_t', + uint64: 'int64_t', + } + + type_headers = { + bool_: {'stdbool.h'}, + int8: {'stdint.h'}, + int16: {'stdint.h'}, + int32: {'stdint.h'}, + int64: {'stdint.h'}, + uint8: {'stdint.h'}, + uint16: {'stdint.h'}, + uint32: {'stdint.h'}, + uint64: {'stdint.h'}, + } + + # Macros needed to be defined when using a Type + type_macros: dict[Type, tuple[str, ...]] = {} + + type_func_suffixes = { + float32: 'f', + float64: '', + float80: 'l' + } + + type_literal_suffixes = { + float32: 'F', + float64: '', + float80: 'L' + } + + type_math_macro_suffixes = { + float80: 'l' + } + + math_macros = None + + _ns = '' # namespace, C++ uses 'std::' + # known_functions-dict to copy + _kf: dict[str, Any] = known_functions_C89 + + def __init__(self, settings=None): + settings = settings or {} + if self.math_macros is None: + self.math_macros = settings.pop('math_macros', get_math_macros()) + self.type_aliases = dict(chain(self.type_aliases.items(), + settings.pop('type_aliases', {}).items())) + self.type_mappings = dict(chain(self.type_mappings.items(), + settings.pop('type_mappings', {}).items())) + self.type_headers = dict(chain(self.type_headers.items(), + settings.pop('type_headers', {}).items())) + self.type_macros = dict(chain(self.type_macros.items(), + settings.pop('type_macros', {}).items())) + self.type_func_suffixes = dict(chain(self.type_func_suffixes.items(), + settings.pop('type_func_suffixes', {}).items())) + self.type_literal_suffixes = dict(chain(self.type_literal_suffixes.items(), + settings.pop('type_literal_suffixes', {}).items())) + self.type_math_macro_suffixes = dict(chain(self.type_math_macro_suffixes.items(), + settings.pop('type_math_macro_suffixes', {}).items())) + super().__init__(settings) + self.known_functions = dict(self._kf, **settings.get('user_functions', {})) + self._dereference = set(settings.get('dereference', [])) + self.headers = set() + self.libraries = set() + self.macros = set() + + def _rate_index_position(self, p): + return p*5 + + def _get_statement(self, codestring): + """ Get code string as a statement - i.e. ending with a semicolon. """ + return codestring if codestring.endswith(';') else codestring + ';' + + def _get_comment(self, text): + return "/* {} */".format(text) + + def _declare_number_const(self, name, value): + type_ = self.type_aliases[real] + var = Variable(name, type=type_, value=value.evalf(type_.decimal_dig), attrs={value_const}) + decl = Declaration(var) + return self._get_statement(self._print(decl)) + + def _format_code(self, lines): + return self.indent_code(lines) + + def _traverse_matrix_indices(self, mat): + rows, cols = mat.shape + return ((i, j) for i in range(rows) for j in range(cols)) + + @_as_macro_if_defined + def _print_Mul(self, expr, **kwargs): + return super()._print_Mul(expr, **kwargs) + + @_as_macro_if_defined + def _print_Pow(self, expr): + if "Pow" in self.known_functions: + return self._print_Function(expr) + PREC = precedence(expr) + suffix = self._get_func_suffix(real) + if equal_valued(expr.exp, -1): + return '%s/%s' % (self._print_Float(Float(1.0)), self.parenthesize(expr.base, PREC)) + elif equal_valued(expr.exp, 0.5): + return '%ssqrt%s(%s)' % (self._ns, suffix, self._print(expr.base)) + elif expr.exp == S.One/3 and self.standard != 'C89': + return '%scbrt%s(%s)' % (self._ns, suffix, self._print(expr.base)) + else: + return '%spow%s(%s, %s)' % (self._ns, suffix, self._print(expr.base), + self._print(expr.exp)) + + def _print_Mod(self, expr): + num, den = expr.args + if num.is_integer and den.is_integer: + PREC = precedence(expr) + snum, sden = [self.parenthesize(arg, PREC) for arg in expr.args] + # % is remainder (same sign as numerator), not modulo (same sign as + # denominator), in C. Hence, % only works as modulo if both numbers + # have the same sign + if (num.is_nonnegative and den.is_nonnegative or + num.is_nonpositive and den.is_nonpositive): + return f"{snum} % {sden}" + return f"(({snum} % {sden}) + {sden}) % {sden}" + # Not guaranteed integer + return self._print_math_func(expr, known='fmod') + + def _print_Rational(self, expr): + p, q = int(expr.p), int(expr.q) + suffix = self._get_literal_suffix(real) + return '%d.0%s/%d.0%s' % (p, suffix, q, suffix) + + def _print_Indexed(self, expr): + # calculate index for 1d array + offset = getattr(expr.base, 'offset', S.Zero) + strides = getattr(expr.base, 'strides', None) + indices = expr.indices + + if strides is None or isinstance(strides, str): + dims = expr.shape + shift = S.One + temp = () + if strides == 'C' or strides is None: + traversal = reversed(range(expr.rank)) + indices = indices[::-1] + elif strides == 'F': + traversal = range(expr.rank) + + for i in traversal: + temp += (shift,) + shift *= dims[i] + strides = temp + flat_index = sum(x[0]*x[1] for x in zip(indices, strides)) + offset + return "%s[%s]" % (self._print(expr.base.label), + self._print(flat_index)) + + @_as_macro_if_defined + def _print_NumberSymbol(self, expr): + return super()._print_NumberSymbol(expr) + + def _print_Infinity(self, expr): + return 'HUGE_VAL' + + def _print_NegativeInfinity(self, expr): + return '-HUGE_VAL' + + def _print_Piecewise(self, expr): + if expr.args[-1].cond != True: + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + lines = [] + if expr.has(Assignment): + for i, (e, c) in enumerate(expr.args): + if i == 0: + lines.append("if (%s) {" % self._print(c)) + elif i == len(expr.args) - 1 and c == True: + lines.append("else {") + else: + lines.append("else if (%s) {" % self._print(c)) + code0 = self._print(e) + lines.append(code0) + lines.append("}") + return "\n".join(lines) + else: + # The piecewise was used in an expression, need to do inline + # operators. This has the downside that inline operators will + # not work for statements that span multiple lines (Matrix or + # Indexed expressions). + ecpairs = ["((%s) ? (\n%s\n)\n" % (self._print(c), + self._print(e)) + for e, c in expr.args[:-1]] + last_line = ": (\n%s\n)" % self._print(expr.args[-1].expr) + return ": ".join(ecpairs) + last_line + " ".join([")"*len(ecpairs)]) + + def _print_ITE(self, expr): + from sympy.functions import Piecewise + return self._print(expr.rewrite(Piecewise, deep=False)) + + def _print_MatrixElement(self, expr): + return "{}[{}]".format(self.parenthesize(expr.parent, PRECEDENCE["Atom"], + strict=True), expr.j + expr.i*expr.parent.shape[1]) + + def _print_Symbol(self, expr): + name = super()._print_Symbol(expr) + if expr in self._settings['dereference']: + return '(*{})'.format(name) + else: + return name + + def _print_Relational(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + op = expr.rel_op + return "{} {} {}".format(lhs_code, op, rhs_code) + + def _print_For(self, expr): + target = self._print(expr.target) + if isinstance(expr.iterable, Range): + start, stop, step = expr.iterable.args + else: + raise NotImplementedError("Only iterable currently supported is Range") + body = self._print(expr.body) + return ('for ({target} = {start}; {target} < {stop}; {target} += ' + '{step}) {{\n{body}\n}}').format(target=target, start=start, + stop=stop, step=step, body=body) + + def _print_sign(self, func): + return '((({0}) > 0) - (({0}) < 0))'.format(self._print(func.args[0])) + + def _print_Max(self, expr): + if "Max" in self.known_functions: + return self._print_Function(expr) + def inner_print_max(args): # The more natural abstraction of creating + if len(args) == 1: # and printing smaller Max objects is slow + return self._print(args[0]) # when there are many arguments. + half = len(args) // 2 + return "((%(a)s > %(b)s) ? %(a)s : %(b)s)" % { + 'a': inner_print_max(args[:half]), + 'b': inner_print_max(args[half:]) + } + return inner_print_max(expr.args) + + def _print_Min(self, expr): + if "Min" in self.known_functions: + return self._print_Function(expr) + def inner_print_min(args): # The more natural abstraction of creating + if len(args) == 1: # and printing smaller Min objects is slow + return self._print(args[0]) # when there are many arguments. + half = len(args) // 2 + return "((%(a)s < %(b)s) ? %(a)s : %(b)s)" % { + 'a': inner_print_min(args[:half]), + 'b': inner_print_min(args[half:]) + } + return inner_print_min(expr.args) + + def indent_code(self, code): + """Accepts a string of code or a list of code lines""" + + if isinstance(code, str): + code_lines = self.indent_code(code.splitlines(True)) + return ''.join(code_lines) + + tab = " " + inc_token = ('{', '(', '{\n', '(\n') + dec_token = ('}', ')') + + code = [line.lstrip(' \t') for line in code] + + increase = [int(any(map(line.endswith, inc_token))) for line in code] + decrease = [int(any(map(line.startswith, dec_token))) for line in code] + + pretty = [] + level = 0 + for n, line in enumerate(code): + if line in ('', '\n'): + pretty.append(line) + continue + level -= decrease[n] + pretty.append("%s%s" % (tab*level, line)) + level += increase[n] + return pretty + + def _get_func_suffix(self, type_): + return self.type_func_suffixes[self.type_aliases.get(type_, type_)] + + def _get_literal_suffix(self, type_): + return self.type_literal_suffixes[self.type_aliases.get(type_, type_)] + + def _get_math_macro_suffix(self, type_): + alias = self.type_aliases.get(type_, type_) + dflt = self.type_math_macro_suffixes.get(alias, '') + return self.type_math_macro_suffixes.get(type_, dflt) + + def _print_Tuple(self, expr): + return '{'+', '.join(self._print(e) for e in expr)+'}' + + _print_List = _print_Tuple + + def _print_Type(self, type_): + self.headers.update(self.type_headers.get(type_, set())) + self.macros.update(self.type_macros.get(type_, set())) + return self._print(self.type_mappings.get(type_, type_.name)) + + def _print_Declaration(self, decl): + from sympy.codegen.cnodes import restrict + var = decl.variable + val = var.value + if var.type == untyped: + raise ValueError("C does not support untyped variables") + + if isinstance(var, Pointer): + result = '{vc}{t} *{pc} {r}{s}'.format( + vc='const ' if value_const in var.attrs else '', + t=self._print(var.type), + pc=' const' if pointer_const in var.attrs else '', + r='restrict ' if restrict in var.attrs else '', + s=self._print(var.symbol) + ) + elif isinstance(var, Variable): + result = '{vc}{t} {s}'.format( + vc='const ' if value_const in var.attrs else '', + t=self._print(var.type), + s=self._print(var.symbol) + ) + else: + raise NotImplementedError("Unknown type of var: %s" % type(var)) + if val != None: # Must be "!= None", cannot be "is not None" + result += ' = %s' % self._print(val) + return result + + def _print_Float(self, flt): + type_ = self.type_aliases.get(real, real) + self.macros.update(self.type_macros.get(type_, set())) + suffix = self._get_literal_suffix(type_) + num = str(flt.evalf(type_.decimal_dig)) + if 'e' not in num and '.' not in num: + num += '.0' + num_parts = num.split('e') + num_parts[0] = num_parts[0].rstrip('0') + if num_parts[0].endswith('.'): + num_parts[0] += '0' + return 'e'.join(num_parts) + suffix + + @requires(headers={'stdbool.h'}) + def _print_BooleanTrue(self, expr): + return 'true' + + @requires(headers={'stdbool.h'}) + def _print_BooleanFalse(self, expr): + return 'false' + + def _print_Element(self, elem): + if elem.strides == None: # Must be "== None", cannot be "is None" + if elem.offset != None: # Must be "!= None", cannot be "is not None" + raise ValueError("Expected strides when offset is given") + idxs = ']['.join((self._print(arg) for arg in elem.indices)) + else: + global_idx = sum(i*s for i, s in zip(elem.indices, elem.strides)) + if elem.offset != None: # Must be "!= None", cannot be "is not None" + global_idx += elem.offset + idxs = self._print(global_idx) + + return "{symb}[{idxs}]".format( + symb=self._print(elem.symbol), + idxs=idxs + ) + + def _print_CodeBlock(self, expr): + """ Elements of code blocks printed as statements. """ + return '\n'.join([self._get_statement(self._print(i)) for i in expr.args]) + + def _print_While(self, expr): + return 'while ({condition}) {{\n{body}\n}}'.format(**expr.kwargs( + apply=lambda arg: self._print(arg))) + + def _print_Scope(self, expr): + return '{\n%s\n}' % self._print_CodeBlock(expr.body) + + @requires(headers={'stdio.h'}) + def _print_Print(self, expr): + if expr.file == none: + template = 'printf({fmt}, {pargs})' + else: + template = 'fprintf(%(out)s, {fmt}, {pargs})' % { + 'out': self._print(expr.file) + } + return template.format( + fmt="%s\n" if expr.format_string == none else self._print(expr.format_string), + pargs=', '.join((self._print(arg) for arg in expr.print_args)) + ) + + def _print_Stream(self, strm): + return strm.name + + def _print_FunctionPrototype(self, expr): + pars = ', '.join((self._print(Declaration(arg)) for arg in expr.parameters)) + return "%s %s(%s)" % ( + tuple((self._print(arg) for arg in (expr.return_type, expr.name))) + (pars,) + ) + + def _print_FunctionDefinition(self, expr): + return "%s%s" % (self._print_FunctionPrototype(expr), + self._print_Scope(expr)) + + def _print_Return(self, expr): + arg, = expr.args + return 'return %s' % self._print(arg) + + def _print_CommaOperator(self, expr): + return '(%s)' % ', '.join((self._print(arg) for arg in expr.args)) + + def _print_Label(self, expr): + if expr.body == none: + return '%s:' % str(expr.name) + if len(expr.body.args) == 1: + return '%s:\n%s' % (str(expr.name), self._print_CodeBlock(expr.body)) + return '%s:\n{\n%s\n}' % (str(expr.name), self._print_CodeBlock(expr.body)) + + def _print_goto(self, expr): + return 'goto %s' % expr.label.name + + def _print_PreIncrement(self, expr): + arg, = expr.args + return '++(%s)' % self._print(arg) + + def _print_PostIncrement(self, expr): + arg, = expr.args + return '(%s)++' % self._print(arg) + + def _print_PreDecrement(self, expr): + arg, = expr.args + return '--(%s)' % self._print(arg) + + def _print_PostDecrement(self, expr): + arg, = expr.args + return '(%s)--' % self._print(arg) + + def _print_struct(self, expr): + return "%(keyword)s %(name)s {\n%(lines)s}" % { + "keyword": expr.__class__.__name__, "name": expr.name, "lines": ';\n'.join( + [self._print(decl) for decl in expr.declarations] + ['']) + } + + def _print_BreakToken(self, _): + return 'break' + + def _print_ContinueToken(self, _): + return 'continue' + + _print_union = _print_struct + +class C99CodePrinter(C89CodePrinter): + standard = 'C99' + reserved_words = set(reserved_words + reserved_words_c99) + type_mappings=dict(chain(C89CodePrinter.type_mappings.items(), { + complex64: 'float complex', + complex128: 'double complex', + }.items())) + type_headers = dict(chain(C89CodePrinter.type_headers.items(), { + complex64: {'complex.h'}, + complex128: {'complex.h'} + }.items())) + + # known_functions-dict to copy + _kf: dict[str, Any] = known_functions_C99 + + # functions with versions with 'f' and 'l' suffixes: + _prec_funcs = ('fabs fmod remainder remquo fma fmax fmin fdim nan exp exp2' + ' expm1 log log10 log2 log1p pow sqrt cbrt hypot sin cos tan' + ' asin acos atan atan2 sinh cosh tanh asinh acosh atanh erf' + ' erfc tgamma lgamma ceil floor trunc round nearbyint rint' + ' frexp ldexp modf scalbn ilogb logb nextafter copysign').split() + + def _print_Infinity(self, expr): + return 'INFINITY' + + def _print_NegativeInfinity(self, expr): + return '-INFINITY' + + def _print_NaN(self, expr): + return 'NAN' + + # tgamma was already covered by 'known_functions' dict + + @requires(headers={'math.h'}, libraries={'m'}) + @_as_macro_if_defined + def _print_math_func(self, expr, nest=False, known=None): + if known is None: + known = self.known_functions[expr.__class__.__name__] + if not isinstance(known, str): + for cb, name in known: + if cb(*expr.args): + known = name + break + else: + raise ValueError("No matching printer") + try: + return known(self, *expr.args) + except TypeError: + suffix = self._get_func_suffix(real) if self._ns + known in self._prec_funcs else '' + + if nest: + args = self._print(expr.args[0]) + if len(expr.args) > 1: + paren_pile = '' + for curr_arg in expr.args[1:-1]: + paren_pile += ')' + args += ', {ns}{name}{suffix}({next}'.format( + ns=self._ns, + name=known, + suffix=suffix, + next = self._print(curr_arg) + ) + args += ', %s%s' % ( + self._print(expr.func(expr.args[-1])), + paren_pile + ) + else: + args = ', '.join((self._print(arg) for arg in expr.args)) + return '{ns}{name}{suffix}({args})'.format( + ns=self._ns, + name=known, + suffix=suffix, + args=args + ) + + def _print_Max(self, expr): + return self._print_math_func(expr, nest=True) + + def _print_Min(self, expr): + return self._print_math_func(expr, nest=True) + + def _get_loop_opening_ending(self, indices): + open_lines = [] + close_lines = [] + loopstart = "for (int %(var)s=%(start)s; %(var)s<%(end)s; %(var)s++){" # C99 + for i in indices: + # C arrays start at 0 and end at dimension-1 + open_lines.append(loopstart % { + 'var': self._print(i.label), + 'start': self._print(i.lower), + 'end': self._print(i.upper + 1)}) + close_lines.append("}") + return open_lines, close_lines + + +for k in ('Abs Sqrt exp exp2 expm1 log log10 log2 log1p Cbrt hypot fma' + ' loggamma sin cos tan asin acos atan atan2 sinh cosh tanh asinh acosh ' + 'atanh erf erfc loggamma gamma ceiling floor').split(): + setattr(C99CodePrinter, '_print_%s' % k, C99CodePrinter._print_math_func) + + +class C11CodePrinter(C99CodePrinter): + + @requires(headers={'stdalign.h'}) + def _print_alignof(self, expr): + arg, = expr.args + return 'alignof(%s)' % self._print(arg) + + +c_code_printers = { + 'c89': C89CodePrinter, + 'c99': C99CodePrinter, + 'c11': C11CodePrinter +} diff --git a/venv/lib/python3.10/site-packages/sympy/printing/codeprinter.py b/venv/lib/python3.10/site-packages/sympy/printing/codeprinter.py new file mode 100644 index 0000000000000000000000000000000000000000..1faaa0f054cbd8ff438b90e914808f720d2da90a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/codeprinter.py @@ -0,0 +1,1039 @@ +from __future__ import annotations +from typing import Any + +from functools import wraps + +from sympy.core import Add, Mul, Pow, S, sympify, Float +from sympy.core.basic import Basic +from sympy.core.expr import Expr, UnevaluatedExpr +from sympy.core.function import Lambda +from sympy.core.mul import _keep_coeff +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import re +from sympy.printing.str import StrPrinter +from sympy.printing.precedence import precedence, PRECEDENCE + + +class requires: + """ Decorator for registering requirements on print methods. """ + def __init__(self, **kwargs): + self._req = kwargs + + def __call__(self, method): + def _method_wrapper(self_, *args, **kwargs): + for k, v in self._req.items(): + getattr(self_, k).update(v) + return method(self_, *args, **kwargs) + return wraps(method)(_method_wrapper) + + +class AssignmentError(Exception): + """ + Raised if an assignment variable for a loop is missing. + """ + pass + +class PrintMethodNotImplementedError(NotImplementedError): + """ + Raised if a _print_* method is missing in the Printer. + """ + pass + +def _convert_python_lists(arg): + if isinstance(arg, list): + from sympy.codegen.abstract_nodes import List + return List(*(_convert_python_lists(e) for e in arg)) + elif isinstance(arg, tuple): + return tuple(_convert_python_lists(e) for e in arg) + else: + return arg + + +class CodePrinter(StrPrinter): + """ + The base class for code-printing subclasses. + """ + + _operators = { + 'and': '&&', + 'or': '||', + 'not': '!', + } + + _default_settings: dict[str, Any] = { + 'order': None, + 'full_prec': 'auto', + 'error_on_reserved': False, + 'reserved_word_suffix': '_', + 'human': True, + 'inline': False, + 'allow_unknown_functions': False, + 'strict': None # True or False; None => True if human == True + } + + # Functions which are "simple" to rewrite to other functions that + # may be supported + # function_to_rewrite : (function_to_rewrite_to, iterable_with_other_functions_required) + _rewriteable_functions = { + 'cot': ('tan', []), + 'csc': ('sin', []), + 'sec': ('cos', []), + 'acot': ('atan', []), + 'acsc': ('asin', []), + 'asec': ('acos', []), + 'coth': ('exp', []), + 'csch': ('exp', []), + 'sech': ('exp', []), + 'acoth': ('log', []), + 'acsch': ('log', []), + 'asech': ('log', []), + 'catalan': ('gamma', []), + 'fibonacci': ('sqrt', []), + 'lucas': ('sqrt', []), + 'beta': ('gamma', []), + 'sinc': ('sin', ['Piecewise']), + 'Mod': ('floor', []), + 'factorial': ('gamma', []), + 'factorial2': ('gamma', ['Piecewise']), + 'subfactorial': ('uppergamma', []), + 'RisingFactorial': ('gamma', ['Piecewise']), + 'FallingFactorial': ('gamma', ['Piecewise']), + 'binomial': ('gamma', []), + 'frac': ('floor', []), + 'Max': ('Piecewise', []), + 'Min': ('Piecewise', []), + 'Heaviside': ('Piecewise', []), + 'erf2': ('erf', []), + 'erfc': ('erf', []), + 'Li': ('li', []), + 'Ei': ('li', []), + 'dirichlet_eta': ('zeta', []), + 'riemann_xi': ('zeta', ['gamma']), + 'SingularityFunction': ('Piecewise', []), + } + + def __init__(self, settings=None): + super().__init__(settings=settings) + if self._settings.get('strict', True) == None: + # for backwards compatibility, human=False need not to throw: + self._settings['strict'] = self._settings.get('human', True) == True + if not hasattr(self, 'reserved_words'): + self.reserved_words = set() + + def _handle_UnevaluatedExpr(self, expr): + return expr.replace(re, lambda arg: arg if isinstance( + arg, UnevaluatedExpr) and arg.args[0].is_real else re(arg)) + + def doprint(self, expr, assign_to=None): + """ + Print the expression as code. + + Parameters + ---------- + expr : Expression + The expression to be printed. + + assign_to : Symbol, string, MatrixSymbol, list of strings or Symbols (optional) + If provided, the printed code will set the expression to a variable or multiple variables + with the name or names given in ``assign_to``. + """ + from sympy.matrices.expressions.matexpr import MatrixSymbol + from sympy.codegen.ast import CodeBlock, Assignment + + def _handle_assign_to(expr, assign_to): + if assign_to is None: + return sympify(expr) + if isinstance(assign_to, (list, tuple)): + if len(expr) != len(assign_to): + raise ValueError('Failed to assign an expression of length {} to {} variables'.format(len(expr), len(assign_to))) + return CodeBlock(*[_handle_assign_to(lhs, rhs) for lhs, rhs in zip(expr, assign_to)]) + if isinstance(assign_to, str): + if expr.is_Matrix: + assign_to = MatrixSymbol(assign_to, *expr.shape) + else: + assign_to = Symbol(assign_to) + elif not isinstance(assign_to, Basic): + raise TypeError("{} cannot assign to object of type {}".format( + type(self).__name__, type(assign_to))) + return Assignment(assign_to, expr) + + expr = _convert_python_lists(expr) + expr = _handle_assign_to(expr, assign_to) + + # Remove re(...) nodes due to UnevaluatedExpr.is_real always is None: + expr = self._handle_UnevaluatedExpr(expr) + + # keep a set of expressions that are not strictly translatable to Code + # and number constants that must be declared and initialized + self._not_supported = set() + self._number_symbols = set() + + lines = self._print(expr).splitlines() + + # format the output + if self._settings["human"]: + frontlines = [] + if self._not_supported: + frontlines.append(self._get_comment( + "Not supported in {}:".format(self.language))) + for expr in sorted(self._not_supported, key=str): + frontlines.append(self._get_comment(type(expr).__name__)) + for name, value in sorted(self._number_symbols, key=str): + frontlines.append(self._declare_number_const(name, value)) + lines = frontlines + lines + lines = self._format_code(lines) + result = "\n".join(lines) + else: + lines = self._format_code(lines) + num_syms = {(k, self._print(v)) for k, v in self._number_symbols} + result = (num_syms, self._not_supported, "\n".join(lines)) + self._not_supported = set() + self._number_symbols = set() + return result + + def _doprint_loops(self, expr, assign_to=None): + # Here we print an expression that contains Indexed objects, they + # correspond to arrays in the generated code. The low-level implementation + # involves looping over array elements and possibly storing results in temporary + # variables or accumulate it in the assign_to object. + + if self._settings.get('contract', True): + from sympy.tensor import get_contraction_structure + # Setup loops over non-dummy indices -- all terms need these + indices = self._get_expression_indices(expr, assign_to) + # Setup loops over dummy indices -- each term needs separate treatment + dummies = get_contraction_structure(expr) + else: + indices = [] + dummies = {None: (expr,)} + openloop, closeloop = self._get_loop_opening_ending(indices) + + # terms with no summations first + if None in dummies: + text = StrPrinter.doprint(self, Add(*dummies[None])) + else: + # If all terms have summations we must initialize array to Zero + text = StrPrinter.doprint(self, 0) + + # skip redundant assignments (where lhs == rhs) + lhs_printed = self._print(assign_to) + lines = [] + if text != lhs_printed: + lines.extend(openloop) + if assign_to is not None: + text = self._get_statement("%s = %s" % (lhs_printed, text)) + lines.append(text) + lines.extend(closeloop) + + # then terms with summations + for d in dummies: + if isinstance(d, tuple): + indices = self._sort_optimized(d, expr) + openloop_d, closeloop_d = self._get_loop_opening_ending( + indices) + + for term in dummies[d]: + if term in dummies and not ([list(f.keys()) for f in dummies[term]] + == [[None] for f in dummies[term]]): + # If one factor in the term has it's own internal + # contractions, those must be computed first. + # (temporary variables?) + raise NotImplementedError( + "FIXME: no support for contractions in factor yet") + else: + + # We need the lhs expression as an accumulator for + # the loops, i.e + # + # for (int d=0; d < dim; d++){ + # lhs[] = lhs[] + term[][d] + # } ^.................. the accumulator + # + # We check if the expression already contains the + # lhs, and raise an exception if it does, as that + # syntax is currently undefined. FIXME: What would be + # a good interpretation? + if assign_to is None: + raise AssignmentError( + "need assignment variable for loops") + if term.has(assign_to): + raise ValueError("FIXME: lhs present in rhs,\ + this is undefined in CodePrinter") + + lines.extend(openloop) + lines.extend(openloop_d) + text = "%s = %s" % (lhs_printed, StrPrinter.doprint( + self, assign_to + term)) + lines.append(self._get_statement(text)) + lines.extend(closeloop_d) + lines.extend(closeloop) + + return "\n".join(lines) + + def _get_expression_indices(self, expr, assign_to): + from sympy.tensor import get_indices + rinds, junk = get_indices(expr) + linds, junk = get_indices(assign_to) + + # support broadcast of scalar + if linds and not rinds: + rinds = linds + if rinds != linds: + raise ValueError("lhs indices must match non-dummy" + " rhs indices in %s" % expr) + + return self._sort_optimized(rinds, assign_to) + + def _sort_optimized(self, indices, expr): + + from sympy.tensor.indexed import Indexed + + if not indices: + return [] + + # determine optimized loop order by giving a score to each index + # the index with the highest score are put in the innermost loop. + score_table = {} + for i in indices: + score_table[i] = 0 + + arrays = expr.atoms(Indexed) + for arr in arrays: + for p, ind in enumerate(arr.indices): + try: + score_table[ind] += self._rate_index_position(p) + except KeyError: + pass + + return sorted(indices, key=lambda x: score_table[x]) + + def _rate_index_position(self, p): + """function to calculate score based on position among indices + + This method is used to sort loops in an optimized order, see + CodePrinter._sort_optimized() + """ + raise NotImplementedError("This function must be implemented by " + "subclass of CodePrinter.") + + def _get_statement(self, codestring): + """Formats a codestring with the proper line ending.""" + raise NotImplementedError("This function must be implemented by " + "subclass of CodePrinter.") + + def _get_comment(self, text): + """Formats a text string as a comment.""" + raise NotImplementedError("This function must be implemented by " + "subclass of CodePrinter.") + + def _declare_number_const(self, name, value): + """Declare a numeric constant at the top of a function""" + raise NotImplementedError("This function must be implemented by " + "subclass of CodePrinter.") + + def _format_code(self, lines): + """Take in a list of lines of code, and format them accordingly. + + This may include indenting, wrapping long lines, etc...""" + raise NotImplementedError("This function must be implemented by " + "subclass of CodePrinter.") + + def _get_loop_opening_ending(self, indices): + """Returns a tuple (open_lines, close_lines) containing lists + of codelines""" + raise NotImplementedError("This function must be implemented by " + "subclass of CodePrinter.") + + def _print_Dummy(self, expr): + if expr.name.startswith('Dummy_'): + return '_' + expr.name + else: + return '%s_%d' % (expr.name, expr.dummy_index) + + def _print_Idx(self, expr): + return self._print(expr.label) + + def _print_CodeBlock(self, expr): + return '\n'.join([self._print(i) for i in expr.args]) + + def _print_String(self, string): + return str(string) + + def _print_QuotedString(self, arg): + return '"%s"' % arg.text + + def _print_Comment(self, string): + return self._get_comment(str(string)) + + def _print_Assignment(self, expr): + from sympy.codegen.ast import Assignment + from sympy.functions.elementary.piecewise import Piecewise + from sympy.matrices.expressions.matexpr import MatrixSymbol + from sympy.tensor.indexed import IndexedBase + lhs = expr.lhs + rhs = expr.rhs + # We special case assignments that take multiple lines + if isinstance(expr.rhs, Piecewise): + # Here we modify Piecewise so each expression is now + # an Assignment, and then continue on the print. + expressions = [] + conditions = [] + for (e, c) in rhs.args: + expressions.append(Assignment(lhs, e)) + conditions.append(c) + temp = Piecewise(*zip(expressions, conditions)) + return self._print(temp) + elif isinstance(lhs, MatrixSymbol): + # Here we form an Assignment for each element in the array, + # printing each one. + lines = [] + for (i, j) in self._traverse_matrix_indices(lhs): + temp = Assignment(lhs[i, j], rhs[i, j]) + code0 = self._print(temp) + lines.append(code0) + return "\n".join(lines) + elif self._settings.get("contract", False) and (lhs.has(IndexedBase) or + rhs.has(IndexedBase)): + # Here we check if there is looping to be done, and if so + # print the required loops. + return self._doprint_loops(rhs, lhs) + else: + lhs_code = self._print(lhs) + rhs_code = self._print(rhs) + return self._get_statement("%s = %s" % (lhs_code, rhs_code)) + + def _print_AugmentedAssignment(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + return self._get_statement("{} {} {}".format( + *(self._print(arg) for arg in [lhs_code, expr.op, rhs_code]))) + + def _print_FunctionCall(self, expr): + return '%s(%s)' % ( + expr.name, + ', '.join((self._print(arg) for arg in expr.function_args))) + + def _print_Variable(self, expr): + return self._print(expr.symbol) + + def _print_Symbol(self, expr): + name = super()._print_Symbol(expr) + + if name in self.reserved_words: + if self._settings['error_on_reserved']: + msg = ('This expression includes the symbol "{}" which is a ' + 'reserved keyword in this language.') + raise ValueError(msg.format(name)) + return name + self._settings['reserved_word_suffix'] + else: + return name + + def _can_print(self, name): + """ Check if function ``name`` is either a known function or has its own + printing method. Used to check if rewriting is possible.""" + return name in self.known_functions or getattr(self, '_print_{}'.format(name), False) + + def _print_Function(self, expr): + if expr.func.__name__ in self.known_functions: + cond_func = self.known_functions[expr.func.__name__] + if isinstance(cond_func, str): + return "%s(%s)" % (cond_func, self.stringify(expr.args, ", ")) + else: + for cond, func in cond_func: + if cond(*expr.args): + break + if func is not None: + try: + return func(*[self.parenthesize(item, 0) for item in expr.args]) + except TypeError: + return "%s(%s)" % (func, self.stringify(expr.args, ", ")) + elif hasattr(expr, '_imp_') and isinstance(expr._imp_, Lambda): + # inlined function + return self._print(expr._imp_(*expr.args)) + elif expr.func.__name__ in self._rewriteable_functions: + # Simple rewrite to supported function possible + target_f, required_fs = self._rewriteable_functions[expr.func.__name__] + if self._can_print(target_f) and all(self._can_print(f) for f in required_fs): + return '(' + self._print(expr.rewrite(target_f)) + ')' + + if expr.is_Function and self._settings.get('allow_unknown_functions', False): + return '%s(%s)' % (self._print(expr.func), ', '.join(map(self._print, expr.args))) + else: + return self._print_not_supported(expr) + + _print_Expr = _print_Function + + def _print_Derivative(self, expr): + obj, *wrt_order_pairs = expr.args + for func_arg in obj.args: + if not func_arg.is_Symbol: + raise ValueError("%s._print_Derivative(...) only supports functions with symbols as arguments." % + self.__class__.__name__) + meth_name = '_print_Derivative_%s' % obj.func.__name__ + pmeth = getattr(self, meth_name, None) + if pmeth is None: + if self._settings.get('strict', False): + raise PrintMethodNotImplementedError( + f"Unsupported by {type(self)}: {type(expr)}" + + f"\nPrinter has no method: {meth_name}" + + "\nSet the printer option 'strict' to False in order to generate partially printed code." + ) + return self._print_not_supported(expr) + orders = dict(wrt_order_pairs) + seq_orders = [orders[arg] for arg in obj.args] + return pmeth(obj.args, seq_orders) + + # Don't inherit the str-printer method for Heaviside to the code printers + _print_Heaviside = None + + def _print_NumberSymbol(self, expr): + if self._settings.get("inline", False): + return self._print(Float(expr.evalf(self._settings["precision"]))) + else: + # A Number symbol that is not implemented here or with _printmethod + # is registered and evaluated + self._number_symbols.add((expr, + Float(expr.evalf(self._settings["precision"])))) + return str(expr) + + def _print_Catalan(self, expr): + return self._print_NumberSymbol(expr) + def _print_EulerGamma(self, expr): + return self._print_NumberSymbol(expr) + def _print_GoldenRatio(self, expr): + return self._print_NumberSymbol(expr) + def _print_TribonacciConstant(self, expr): + return self._print_NumberSymbol(expr) + def _print_Exp1(self, expr): + return self._print_NumberSymbol(expr) + def _print_Pi(self, expr): + return self._print_NumberSymbol(expr) + + def _print_And(self, expr): + PREC = precedence(expr) + return (" %s " % self._operators['and']).join(self.parenthesize(a, PREC) + for a in sorted(expr.args, key=default_sort_key)) + + def _print_Or(self, expr): + PREC = precedence(expr) + return (" %s " % self._operators['or']).join(self.parenthesize(a, PREC) + for a in sorted(expr.args, key=default_sort_key)) + + def _print_Xor(self, expr): + if self._operators.get('xor') is None: + return self._print(expr.to_nnf()) + PREC = precedence(expr) + return (" %s " % self._operators['xor']).join(self.parenthesize(a, PREC) + for a in expr.args) + + def _print_Equivalent(self, expr): + if self._operators.get('equivalent') is None: + return self._print(expr.to_nnf()) + PREC = precedence(expr) + return (" %s " % self._operators['equivalent']).join(self.parenthesize(a, PREC) + for a in expr.args) + + def _print_Not(self, expr): + PREC = precedence(expr) + return self._operators['not'] + self.parenthesize(expr.args[0], PREC) + + def _print_BooleanFunction(self, expr): + return self._print(expr.to_nnf()) + + def _print_isnan(self, arg): + return 'isnan(%s)' % self._print(*arg.args) + + def _print_isinf(self, arg): + return 'isinf(%s)' % self._print(*arg.args) + + def _print_Mul(self, expr): + + prec = precedence(expr) + + c, e = expr.as_coeff_Mul() + if c < 0: + expr = _keep_coeff(-c, e) + sign = "-" + else: + sign = "" + + a = [] # items in the numerator + b = [] # items that are in the denominator (if any) + + pow_paren = [] # Will collect all pow with more than one base element and exp = -1 + + if self.order not in ('old', 'none'): + args = expr.as_ordered_factors() + else: + # use make_args in case expr was something like -x -> x + args = Mul.make_args(expr) + + # Gather args for numerator/denominator + for item in args: + if item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative: + if item.exp != -1: + b.append(Pow(item.base, -item.exp, evaluate=False)) + else: + if len(item.args[0].args) != 1 and isinstance(item.base, Mul): # To avoid situations like #14160 + pow_paren.append(item) + b.append(Pow(item.base, -item.exp)) + else: + a.append(item) + + a = a or [S.One] + + if len(a) == 1 and sign == "-": + # Unary minus does not have a SymPy class, and hence there's no + # precedence weight associated with it, Python's unary minus has + # an operator precedence between multiplication and exponentiation, + # so we use this to compute a weight. + a_str = [self.parenthesize(a[0], 0.5*(PRECEDENCE["Pow"]+PRECEDENCE["Mul"]))] + else: + a_str = [self.parenthesize(x, prec) for x in a] + b_str = [self.parenthesize(x, prec) for x in b] + + # To parenthesize Pow with exp = -1 and having more than one Symbol + for item in pow_paren: + if item.base in b: + b_str[b.index(item.base)] = "(%s)" % b_str[b.index(item.base)] + + if not b: + return sign + '*'.join(a_str) + elif len(b) == 1: + return sign + '*'.join(a_str) + "/" + b_str[0] + else: + return sign + '*'.join(a_str) + "/(%s)" % '*'.join(b_str) + + def _print_not_supported(self, expr): + if self._settings.get('strict', False): + raise PrintMethodNotImplementedError( + f"Unsupported by {type(self)}: {type(expr)}" + + "\nSet the printer option 'strict' to False in order to generate partially printed code." + ) + try: + self._not_supported.add(expr) + except TypeError: + # not hashable + pass + return self.emptyPrinter(expr) + + # The following can not be simply translated into C or Fortran + _print_Basic = _print_not_supported + _print_ComplexInfinity = _print_not_supported + _print_ExprCondPair = _print_not_supported + _print_GeometryEntity = _print_not_supported + _print_Infinity = _print_not_supported + _print_Integral = _print_not_supported + _print_Interval = _print_not_supported + _print_AccumulationBounds = _print_not_supported + _print_Limit = _print_not_supported + _print_MatrixBase = _print_not_supported + _print_DeferredVector = _print_not_supported + _print_NaN = _print_not_supported + _print_NegativeInfinity = _print_not_supported + _print_Order = _print_not_supported + _print_RootOf = _print_not_supported + _print_RootsOf = _print_not_supported + _print_RootSum = _print_not_supported + _print_Uniform = _print_not_supported + _print_Unit = _print_not_supported + _print_Wild = _print_not_supported + _print_WildFunction = _print_not_supported + _print_Relational = _print_not_supported + + +# Code printer functions. These are included in this file so that they can be +# imported in the top-level __init__.py without importing the sympy.codegen +# module. + +def ccode(expr, assign_to=None, standard='c99', **settings): + """Converts an expr to a string of c code + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used as the name of the variable to which + the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of + line-wrapping, or for expressions that generate multi-line statements. + standard : str, optional + String specifying the standard. If your compiler supports a more modern + standard you may set this to 'c99' to allow the printer to use more math + functions. [default='c89']. + precision : integer, optional + The precision for numbers such as pi [default=17]. + user_functions : dict, optional + A dictionary where the keys are string representations of either + ``FunctionClass`` or ``UndefinedFunction`` instances and the values + are their desired C string representations. Alternatively, the + dictionary value can be a list of tuples i.e. [(argument_test, + cfunction_string)] or [(argument_test, cfunction_formater)]. See below + for examples. + dereference : iterable, optional + An iterable of symbols that should be dereferenced in the printed code + expression. These would be values passed by address to the function. + For example, if ``dereference=[a]``, the resulting code would print + ``(*a)`` instead of ``a``. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + + Examples + ======== + + >>> from sympy import ccode, symbols, Rational, sin, ceiling, Abs, Function + >>> x, tau = symbols("x, tau") + >>> expr = (2*tau)**Rational(7, 2) + >>> ccode(expr) + '8*M_SQRT2*pow(tau, 7.0/2.0)' + >>> ccode(expr, math_macros={}) + '8*sqrt(2)*pow(tau, 7.0/2.0)' + >>> ccode(sin(x), assign_to="s") + 's = sin(x);' + >>> from sympy.codegen.ast import real, float80 + >>> ccode(expr, type_aliases={real: float80}) + '8*M_SQRT2l*powl(tau, 7.0L/2.0L)' + + Simple custom printing can be defined for certain types by passing a + dictionary of {"type" : "function"} to the ``user_functions`` kwarg. + Alternatively, the dictionary value can be a list of tuples i.e. + [(argument_test, cfunction_string)]. + + >>> custom_functions = { + ... "ceiling": "CEIL", + ... "Abs": [(lambda x: not x.is_integer, "fabs"), + ... (lambda x: x.is_integer, "ABS")], + ... "func": "f" + ... } + >>> func = Function('func') + >>> ccode(func(Abs(x) + ceiling(x)), standard='C89', user_functions=custom_functions) + 'f(fabs(x) + CEIL(x))' + + or if the C-function takes a subset of the original arguments: + + >>> ccode(2**x + 3**x, standard='C99', user_functions={'Pow': [ + ... (lambda b, e: b == 2, lambda b, e: 'exp2(%s)' % e), + ... (lambda b, e: b != 2, 'pow')]}) + 'exp2(x) + pow(3, x)' + + ``Piecewise`` expressions are converted into conditionals. If an + ``assign_to`` variable is provided an if statement is created, otherwise + the ternary operator is used. Note that if the ``Piecewise`` lacks a + default term, represented by ``(expr, True)`` then an error will be thrown. + This is to prevent generating an expression that may not evaluate to + anything. + + >>> from sympy import Piecewise + >>> expr = Piecewise((x + 1, x > 0), (x, True)) + >>> print(ccode(expr, tau, standard='C89')) + if (x > 0) { + tau = x + 1; + } + else { + tau = x; + } + + Support for loops is provided through ``Indexed`` types. With + ``contract=True`` these expressions will be turned into loops, whereas + ``contract=False`` will just print the assignment expression that should be + looped over: + + >>> from sympy import Eq, IndexedBase, Idx + >>> len_y = 5 + >>> y = IndexedBase('y', shape=(len_y,)) + >>> t = IndexedBase('t', shape=(len_y,)) + >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) + >>> i = Idx('i', len_y-1) + >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) + >>> ccode(e.rhs, assign_to=e.lhs, contract=False, standard='C89') + 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);' + + Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions + must be provided to ``assign_to``. Note that any expression that can be + generated normally can also exist inside a Matrix: + + >>> from sympy import Matrix, MatrixSymbol + >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)]) + >>> A = MatrixSymbol('A', 3, 1) + >>> print(ccode(mat, A, standard='C89')) + A[0] = pow(x, 2); + if (x > 0) { + A[1] = x + 1; + } + else { + A[1] = x; + } + A[2] = sin(x); + """ + from sympy.printing.c import c_code_printers + return c_code_printers[standard.lower()](settings).doprint(expr, assign_to) + +def print_ccode(expr, **settings): + """Prints C representation of the given expression.""" + print(ccode(expr, **settings)) + +def fcode(expr, assign_to=None, **settings): + """Converts an expr to a string of fortran code + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used as the name of the variable to which + the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of + line-wrapping, or for expressions that generate multi-line statements. + precision : integer, optional + DEPRECATED. Use type_mappings instead. The precision for numbers such + as pi [default=17]. + user_functions : dict, optional + A dictionary where keys are ``FunctionClass`` instances and values are + their string representations. Alternatively, the dictionary value can + be a list of tuples i.e. [(argument_test, cfunction_string)]. See below + for examples. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + source_format : optional + The source format can be either 'fixed' or 'free'. [default='fixed'] + standard : integer, optional + The Fortran standard to be followed. This is specified as an integer. + Acceptable standards are 66, 77, 90, 95, 2003, and 2008. Default is 77. + Note that currently the only distinction internally is between + standards before 95, and those 95 and after. This may change later as + more features are added. + name_mangling : bool, optional + If True, then the variables that would become identical in + case-insensitive Fortran are mangled by appending different number + of ``_`` at the end. If False, SymPy Will not interfere with naming of + variables. [default=True] + + Examples + ======== + + >>> from sympy import fcode, symbols, Rational, sin, ceiling, floor + >>> x, tau = symbols("x, tau") + >>> fcode((2*tau)**Rational(7, 2)) + ' 8*sqrt(2.0d0)*tau**(7.0d0/2.0d0)' + >>> fcode(sin(x), assign_to="s") + ' s = sin(x)' + + Custom printing can be defined for certain types by passing a dictionary of + "type" : "function" to the ``user_functions`` kwarg. Alternatively, the + dictionary value can be a list of tuples i.e. [(argument_test, + cfunction_string)]. + + >>> custom_functions = { + ... "ceiling": "CEIL", + ... "floor": [(lambda x: not x.is_integer, "FLOOR1"), + ... (lambda x: x.is_integer, "FLOOR2")] + ... } + >>> fcode(floor(x) + ceiling(x), user_functions=custom_functions) + ' CEIL(x) + FLOOR1(x)' + + ``Piecewise`` expressions are converted into conditionals. If an + ``assign_to`` variable is provided an if statement is created, otherwise + the ternary operator is used. Note that if the ``Piecewise`` lacks a + default term, represented by ``(expr, True)`` then an error will be thrown. + This is to prevent generating an expression that may not evaluate to + anything. + + >>> from sympy import Piecewise + >>> expr = Piecewise((x + 1, x > 0), (x, True)) + >>> print(fcode(expr, tau)) + if (x > 0) then + tau = x + 1 + else + tau = x + end if + + Support for loops is provided through ``Indexed`` types. With + ``contract=True`` these expressions will be turned into loops, whereas + ``contract=False`` will just print the assignment expression that should be + looped over: + + >>> from sympy import Eq, IndexedBase, Idx + >>> len_y = 5 + >>> y = IndexedBase('y', shape=(len_y,)) + >>> t = IndexedBase('t', shape=(len_y,)) + >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) + >>> i = Idx('i', len_y-1) + >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) + >>> fcode(e.rhs, assign_to=e.lhs, contract=False) + ' Dy(i) = (y(i + 1) - y(i))/(t(i + 1) - t(i))' + + Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions + must be provided to ``assign_to``. Note that any expression that can be + generated normally can also exist inside a Matrix: + + >>> from sympy import Matrix, MatrixSymbol + >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)]) + >>> A = MatrixSymbol('A', 3, 1) + >>> print(fcode(mat, A)) + A(1, 1) = x**2 + if (x > 0) then + A(2, 1) = x + 1 + else + A(2, 1) = x + end if + A(3, 1) = sin(x) + """ + from sympy.printing.fortran import FCodePrinter + return FCodePrinter(settings).doprint(expr, assign_to) + + +def print_fcode(expr, **settings): + """Prints the Fortran representation of the given expression. + + See fcode for the meaning of the optional arguments. + """ + print(fcode(expr, **settings)) + +def cxxcode(expr, assign_to=None, standard='c++11', **settings): + """ C++ equivalent of :func:`~.ccode`. """ + from sympy.printing.cxx import cxx_code_printers + return cxx_code_printers[standard.lower()](settings).doprint(expr, assign_to) + + +def rust_code(expr, assign_to=None, **settings): + """Converts an expr to a string of Rust code + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used as the name of the variable to which + the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of + line-wrapping, or for expressions that generate multi-line statements. + precision : integer, optional + The precision for numbers such as pi [default=15]. + user_functions : dict, optional + A dictionary where the keys are string representations of either + ``FunctionClass`` or ``UndefinedFunction`` instances and the values + are their desired C string representations. Alternatively, the + dictionary value can be a list of tuples i.e. [(argument_test, + cfunction_string)]. See below for examples. + dereference : iterable, optional + An iterable of symbols that should be dereferenced in the printed code + expression. These would be values passed by address to the function. + For example, if ``dereference=[a]``, the resulting code would print + ``(*a)`` instead of ``a``. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + + Examples + ======== + + >>> from sympy import rust_code, symbols, Rational, sin, ceiling, Abs, Function + >>> x, tau = symbols("x, tau") + >>> rust_code((2*tau)**Rational(7, 2)) + '8.0*1.4142135623731*tau.powf(7_f64/2.0)' + >>> rust_code(sin(x), assign_to="s") + 's = x.sin();' + + Simple custom printing can be defined for certain types by passing a + dictionary of {"type" : "function"} to the ``user_functions`` kwarg. + Alternatively, the dictionary value can be a list of tuples i.e. + [(argument_test, cfunction_string)]. + + >>> custom_functions = { + ... "ceiling": "CEIL", + ... "Abs": [(lambda x: not x.is_integer, "fabs", 4), + ... (lambda x: x.is_integer, "ABS", 4)], + ... "func": "f" + ... } + >>> func = Function('func') + >>> rust_code(func(Abs(x) + ceiling(x)), user_functions=custom_functions) + '(fabs(x) + x.ceil()).f()' + + ``Piecewise`` expressions are converted into conditionals. If an + ``assign_to`` variable is provided an if statement is created, otherwise + the ternary operator is used. Note that if the ``Piecewise`` lacks a + default term, represented by ``(expr, True)`` then an error will be thrown. + This is to prevent generating an expression that may not evaluate to + anything. + + >>> from sympy import Piecewise + >>> expr = Piecewise((x + 1, x > 0), (x, True)) + >>> print(rust_code(expr, tau)) + tau = if (x > 0.0) { + x + 1 + } else { + x + }; + + Support for loops is provided through ``Indexed`` types. With + ``contract=True`` these expressions will be turned into loops, whereas + ``contract=False`` will just print the assignment expression that should be + looped over: + + >>> from sympy import Eq, IndexedBase, Idx + >>> len_y = 5 + >>> y = IndexedBase('y', shape=(len_y,)) + >>> t = IndexedBase('t', shape=(len_y,)) + >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) + >>> i = Idx('i', len_y-1) + >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) + >>> rust_code(e.rhs, assign_to=e.lhs, contract=False) + 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);' + + Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions + must be provided to ``assign_to``. Note that any expression that can be + generated normally can also exist inside a Matrix: + + >>> from sympy import Matrix, MatrixSymbol + >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)]) + >>> A = MatrixSymbol('A', 3, 1) + >>> print(rust_code(mat, A)) + A = [x.powi(2), if (x > 0.0) { + x + 1 + } else { + x + }, x.sin()]; + """ + from sympy.printing.rust import RustCodePrinter + printer = RustCodePrinter(settings) + expr = printer._rewrite_known_functions(expr) + if isinstance(expr, Expr): + for src_func, dst_func in printer.function_overrides.values(): + expr = expr.replace(src_func, dst_func) + return printer.doprint(expr, assign_to) + + +def print_rust_code(expr, **settings): + """Prints Rust representation of the given expression.""" + print(rust_code(expr, **settings)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/conventions.py b/venv/lib/python3.10/site-packages/sympy/printing/conventions.py new file mode 100644 index 0000000000000000000000000000000000000000..4f5545ae38511e0bb0366da5c9fb4e59156095c8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/conventions.py @@ -0,0 +1,88 @@ +""" +A few practical conventions common to all printers. +""" + +import re + +from collections.abc import Iterable +from sympy.core.function import Derivative + +_name_with_digits_p = re.compile(r'^([^\W\d_]+)(\d+)$', re.UNICODE) + + +def split_super_sub(text): + """Split a symbol name into a name, superscripts and subscripts + + The first part of the symbol name is considered to be its actual + 'name', followed by super- and subscripts. Each superscript is + preceded with a "^" character or by "__". Each subscript is preceded + by a "_" character. The three return values are the actual name, a + list with superscripts and a list with subscripts. + + Examples + ======== + + >>> from sympy.printing.conventions import split_super_sub + >>> split_super_sub('a_x^1') + ('a', ['1'], ['x']) + >>> split_super_sub('var_sub1__sup_sub2') + ('var', ['sup'], ['sub1', 'sub2']) + + """ + if not text: + return text, [], [] + + pos = 0 + name = None + supers = [] + subs = [] + while pos < len(text): + start = pos + 1 + if text[pos:pos + 2] == "__": + start += 1 + pos_hat = text.find("^", start) + if pos_hat < 0: + pos_hat = len(text) + pos_usc = text.find("_", start) + if pos_usc < 0: + pos_usc = len(text) + pos_next = min(pos_hat, pos_usc) + part = text[pos:pos_next] + pos = pos_next + if name is None: + name = part + elif part.startswith("^"): + supers.append(part[1:]) + elif part.startswith("__"): + supers.append(part[2:]) + elif part.startswith("_"): + subs.append(part[1:]) + else: + raise RuntimeError("This should never happen.") + + # Make a little exception when a name ends with digits, i.e. treat them + # as a subscript too. + m = _name_with_digits_p.match(name) + if m: + name, sub = m.groups() + subs.insert(0, sub) + + return name, supers, subs + + +def requires_partial(expr): + """Return whether a partial derivative symbol is required for printing + + This requires checking how many free variables there are, + filtering out the ones that are integers. Some expressions do not have + free variables. In that case, check its variable list explicitly to + get the context of the expression. + """ + + if isinstance(expr, Derivative): + return requires_partial(expr.expr) + + if not isinstance(expr.free_symbols, Iterable): + return len(set(expr.variables)) > 1 + + return sum(not s.is_integer for s in expr.free_symbols) > 1 diff --git a/venv/lib/python3.10/site-packages/sympy/printing/cxx.py b/venv/lib/python3.10/site-packages/sympy/printing/cxx.py new file mode 100644 index 0000000000000000000000000000000000000000..0ed4f468b866e1b44aba6ae94a85c740dd324689 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/cxx.py @@ -0,0 +1,181 @@ +""" +C++ code printer +""" + +from itertools import chain +from sympy.codegen.ast import Type, none +from .codeprinter import requires +from .c import C89CodePrinter, C99CodePrinter + +# These are defined in the other file so we can avoid importing sympy.codegen +# from the top-level 'import sympy'. Export them here as well. +from sympy.printing.codeprinter import cxxcode # noqa:F401 + +# from https://en.cppreference.com/w/cpp/keyword +reserved = { + 'C++98': [ + 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', 'bool', 'break', + 'case', 'catch,', 'char', 'class', 'compl', 'const', 'const_cast', + 'continue', 'default', 'delete', 'do', 'double', 'dynamic_cast', + 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', + 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', + 'namespace', 'new', 'not', 'not_eq', 'operator', 'or', 'or_eq', + 'private', 'protected', 'public', 'register', 'reinterpret_cast', + 'return', 'short', 'signed', 'sizeof', 'static', 'static_cast', + 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', + 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', + 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor', 'xor_eq' + ] +} + +reserved['C++11'] = reserved['C++98'][:] + [ + 'alignas', 'alignof', 'char16_t', 'char32_t', 'constexpr', 'decltype', + 'noexcept', 'nullptr', 'static_assert', 'thread_local' +] +reserved['C++17'] = reserved['C++11'][:] +reserved['C++17'].remove('register') +# TM TS: atomic_cancel, atomic_commit, atomic_noexcept, synchronized +# concepts TS: concept, requires +# module TS: import, module + + +_math_functions = { + 'C++98': { + 'Mod': 'fmod', + 'ceiling': 'ceil', + }, + 'C++11': { + 'gamma': 'tgamma', + }, + 'C++17': { + 'beta': 'beta', + 'Ei': 'expint', + 'zeta': 'riemann_zeta', + } +} + +# from https://en.cppreference.com/w/cpp/header/cmath +for k in ('Abs', 'exp', 'log', 'log10', 'sqrt', 'sin', 'cos', 'tan', # 'Pow' + 'asin', 'acos', 'atan', 'atan2', 'sinh', 'cosh', 'tanh', 'floor'): + _math_functions['C++98'][k] = k.lower() + + +for k in ('asinh', 'acosh', 'atanh', 'erf', 'erfc'): + _math_functions['C++11'][k] = k.lower() + + +def _attach_print_method(cls, sympy_name, func_name): + meth_name = '_print_%s' % sympy_name + if hasattr(cls, meth_name): + raise ValueError("Edit method (or subclass) instead of overwriting.") + def _print_method(self, expr): + return '{}{}({})'.format(self._ns, func_name, ', '.join(map(self._print, expr.args))) + _print_method.__doc__ = "Prints code for %s" % k + setattr(cls, meth_name, _print_method) + + +def _attach_print_methods(cls, cont): + for sympy_name, cxx_name in cont[cls.standard].items(): + _attach_print_method(cls, sympy_name, cxx_name) + + +class _CXXCodePrinterBase: + printmethod = "_cxxcode" + language = 'C++' + _ns = 'std::' # namespace + + def __init__(self, settings=None): + super().__init__(settings or {}) + + @requires(headers={'algorithm'}) + def _print_Max(self, expr): + from sympy.functions.elementary.miscellaneous import Max + if len(expr.args) == 1: + return self._print(expr.args[0]) + return "%smax(%s, %s)" % (self._ns, self._print(expr.args[0]), + self._print(Max(*expr.args[1:]))) + + @requires(headers={'algorithm'}) + def _print_Min(self, expr): + from sympy.functions.elementary.miscellaneous import Min + if len(expr.args) == 1: + return self._print(expr.args[0]) + return "%smin(%s, %s)" % (self._ns, self._print(expr.args[0]), + self._print(Min(*expr.args[1:]))) + + def _print_using(self, expr): + if expr.alias == none: + return 'using %s' % expr.type + else: + raise ValueError("C++98 does not support type aliases") + + def _print_Raise(self, rs): + arg, = rs.args + return 'throw %s' % self._print(arg) + + @requires(headers={'stdexcept'}) + def _print_RuntimeError_(self, re): + message, = re.args + return "%sruntime_error(%s)" % (self._ns, self._print(message)) + + +class CXX98CodePrinter(_CXXCodePrinterBase, C89CodePrinter): + standard = 'C++98' + reserved_words = set(reserved['C++98']) + + +# _attach_print_methods(CXX98CodePrinter, _math_functions) + + +class CXX11CodePrinter(_CXXCodePrinterBase, C99CodePrinter): + standard = 'C++11' + reserved_words = set(reserved['C++11']) + type_mappings = dict(chain( + CXX98CodePrinter.type_mappings.items(), + { + Type('int8'): ('int8_t', {'cstdint'}), + Type('int16'): ('int16_t', {'cstdint'}), + Type('int32'): ('int32_t', {'cstdint'}), + Type('int64'): ('int64_t', {'cstdint'}), + Type('uint8'): ('uint8_t', {'cstdint'}), + Type('uint16'): ('uint16_t', {'cstdint'}), + Type('uint32'): ('uint32_t', {'cstdint'}), + Type('uint64'): ('uint64_t', {'cstdint'}), + Type('complex64'): ('std::complex', {'complex'}), + Type('complex128'): ('std::complex', {'complex'}), + Type('bool'): ('bool', None), + }.items() + )) + + def _print_using(self, expr): + if expr.alias == none: + return super()._print_using(expr) + else: + return 'using %(alias)s = %(type)s' % expr.kwargs(apply=self._print) + +# _attach_print_methods(CXX11CodePrinter, _math_functions) + + +class CXX17CodePrinter(_CXXCodePrinterBase, C99CodePrinter): + standard = 'C++17' + reserved_words = set(reserved['C++17']) + + _kf = dict(C99CodePrinter._kf, **_math_functions['C++17']) + + def _print_beta(self, expr): + return self._print_math_func(expr) + + def _print_Ei(self, expr): + return self._print_math_func(expr) + + def _print_zeta(self, expr): + return self._print_math_func(expr) + + +# _attach_print_methods(CXX17CodePrinter, _math_functions) + +cxx_code_printers = { + 'c++98': CXX98CodePrinter, + 'c++11': CXX11CodePrinter, + 'c++17': CXX17CodePrinter +} diff --git a/venv/lib/python3.10/site-packages/sympy/printing/defaults.py b/venv/lib/python3.10/site-packages/sympy/printing/defaults.py new file mode 100644 index 0000000000000000000000000000000000000000..77a88d353fed4bd70496456ddd03cc429a4ba5e7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/defaults.py @@ -0,0 +1,5 @@ +from sympy.core._print_helpers import Printable + +# alias for compatibility +Printable.__module__ = __name__ +DefaultPrinting = Printable diff --git a/venv/lib/python3.10/site-packages/sympy/printing/dot.py b/venv/lib/python3.10/site-packages/sympy/printing/dot.py new file mode 100644 index 0000000000000000000000000000000000000000..c968fee389c16108b757b8fcad531ac6fa4ddb2f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/dot.py @@ -0,0 +1,294 @@ +from sympy.core.basic import Basic +from sympy.core.expr import Expr +from sympy.core.symbol import Symbol +from sympy.core.numbers import Integer, Rational, Float +from sympy.printing.repr import srepr + +__all__ = ['dotprint'] + +default_styles = ( + (Basic, {'color': 'blue', 'shape': 'ellipse'}), + (Expr, {'color': 'black'}) +) + +slotClasses = (Symbol, Integer, Rational, Float) +def purestr(x, with_args=False): + """A string that follows ```obj = type(obj)(*obj.args)``` exactly. + + Parameters + ========== + + with_args : boolean, optional + If ``True``, there will be a second argument for the return + value, which is a tuple containing ``purestr`` applied to each + of the subnodes. + + If ``False``, there will not be a second argument for the + return. + + Default is ``False`` + + Examples + ======== + + >>> from sympy import Float, Symbol, MatrixSymbol + >>> from sympy import Integer # noqa: F401 + >>> from sympy.core.symbol import Str # noqa: F401 + >>> from sympy.printing.dot import purestr + + Applying ``purestr`` for basic symbolic object: + >>> code = purestr(Symbol('x')) + >>> code + "Symbol('x')" + >>> eval(code) == Symbol('x') + True + + For basic numeric object: + >>> purestr(Float(2)) + "Float('2.0', precision=53)" + + For matrix symbol: + >>> code = purestr(MatrixSymbol('x', 2, 2)) + >>> code + "MatrixSymbol(Str('x'), Integer(2), Integer(2))" + >>> eval(code) == MatrixSymbol('x', 2, 2) + True + + With ``with_args=True``: + >>> purestr(Float(2), with_args=True) + ("Float('2.0', precision=53)", ()) + >>> purestr(MatrixSymbol('x', 2, 2), with_args=True) + ("MatrixSymbol(Str('x'), Integer(2), Integer(2))", + ("Str('x')", 'Integer(2)', 'Integer(2)')) + """ + sargs = () + if not isinstance(x, Basic): + rv = str(x) + elif not x.args: + rv = srepr(x) + else: + args = x.args + sargs = tuple(map(purestr, args)) + rv = "%s(%s)"%(type(x).__name__, ', '.join(sargs)) + if with_args: + rv = rv, sargs + return rv + + +def styleof(expr, styles=default_styles): + """ Merge style dictionaries in order + + Examples + ======== + + >>> from sympy import Symbol, Basic, Expr, S + >>> from sympy.printing.dot import styleof + >>> styles = [(Basic, {'color': 'blue', 'shape': 'ellipse'}), + ... (Expr, {'color': 'black'})] + + >>> styleof(Basic(S(1)), styles) + {'color': 'blue', 'shape': 'ellipse'} + + >>> x = Symbol('x') + >>> styleof(x + 1, styles) # this is an Expr + {'color': 'black', 'shape': 'ellipse'} + """ + style = {} + for typ, sty in styles: + if isinstance(expr, typ): + style.update(sty) + return style + + +def attrprint(d, delimiter=', '): + """ Print a dictionary of attributes + + Examples + ======== + + >>> from sympy.printing.dot import attrprint + >>> print(attrprint({'color': 'blue', 'shape': 'ellipse'})) + "color"="blue", "shape"="ellipse" + """ + return delimiter.join('"%s"="%s"'%item for item in sorted(d.items())) + + +def dotnode(expr, styles=default_styles, labelfunc=str, pos=(), repeat=True): + """ String defining a node + + Examples + ======== + + >>> from sympy.printing.dot import dotnode + >>> from sympy.abc import x + >>> print(dotnode(x)) + "Symbol('x')_()" ["color"="black", "label"="x", "shape"="ellipse"]; + """ + style = styleof(expr, styles) + + if isinstance(expr, Basic) and not expr.is_Atom: + label = str(expr.__class__.__name__) + else: + label = labelfunc(expr) + style['label'] = label + expr_str = purestr(expr) + if repeat: + expr_str += '_%s' % str(pos) + return '"%s" [%s];' % (expr_str, attrprint(style)) + + +def dotedges(expr, atom=lambda x: not isinstance(x, Basic), pos=(), repeat=True): + """ List of strings for all expr->expr.arg pairs + + See the docstring of dotprint for explanations of the options. + + Examples + ======== + + >>> from sympy.printing.dot import dotedges + >>> from sympy.abc import x + >>> for e in dotedges(x+2): + ... print(e) + "Add(Integer(2), Symbol('x'))_()" -> "Integer(2)_(0,)"; + "Add(Integer(2), Symbol('x'))_()" -> "Symbol('x')_(1,)"; + """ + if atom(expr): + return [] + else: + expr_str, arg_strs = purestr(expr, with_args=True) + if repeat: + expr_str += '_%s' % str(pos) + arg_strs = ['%s_%s' % (a, str(pos + (i,))) + for i, a in enumerate(arg_strs)] + return ['"%s" -> "%s";' % (expr_str, a) for a in arg_strs] + +template = \ +"""digraph{ + +# Graph style +%(graphstyle)s + +######### +# Nodes # +######### + +%(nodes)s + +######### +# Edges # +######### + +%(edges)s +}""" + +_graphstyle = {'rankdir': 'TD', 'ordering': 'out'} + +def dotprint(expr, + styles=default_styles, atom=lambda x: not isinstance(x, Basic), + maxdepth=None, repeat=True, labelfunc=str, **kwargs): + """DOT description of a SymPy expression tree + + Parameters + ========== + + styles : list of lists composed of (Class, mapping), optional + Styles for different classes. + + The default is + + .. code-block:: python + + ( + (Basic, {'color': 'blue', 'shape': 'ellipse'}), + (Expr, {'color': 'black'}) + ) + + atom : function, optional + Function used to determine if an arg is an atom. + + A good choice is ``lambda x: not x.args``. + + The default is ``lambda x: not isinstance(x, Basic)``. + + maxdepth : integer, optional + The maximum depth. + + The default is ``None``, meaning no limit. + + repeat : boolean, optional + Whether to use different nodes for common subexpressions. + + The default is ``True``. + + For example, for ``x + x*y`` with ``repeat=True``, it will have + two nodes for ``x``; with ``repeat=False``, it will have one + node. + + .. warning:: + Even if a node appears twice in the same object like ``x`` in + ``Pow(x, x)``, it will still only appear once. + Hence, with ``repeat=False``, the number of arrows out of an + object might not equal the number of args it has. + + labelfunc : function, optional + A function to create a label for a given leaf node. + + The default is ``str``. + + Another good option is ``srepr``. + + For example with ``str``, the leaf nodes of ``x + 1`` are labeled, + ``x`` and ``1``. With ``srepr``, they are labeled ``Symbol('x')`` + and ``Integer(1)``. + + **kwargs : optional + Additional keyword arguments are included as styles for the graph. + + Examples + ======== + + >>> from sympy import dotprint + >>> from sympy.abc import x + >>> print(dotprint(x+2)) # doctest: +NORMALIZE_WHITESPACE + digraph{ + + # Graph style + "ordering"="out" + "rankdir"="TD" + + ######### + # Nodes # + ######### + + "Add(Integer(2), Symbol('x'))_()" ["color"="black", "label"="Add", "shape"="ellipse"]; + "Integer(2)_(0,)" ["color"="black", "label"="2", "shape"="ellipse"]; + "Symbol('x')_(1,)" ["color"="black", "label"="x", "shape"="ellipse"]; + + ######### + # Edges # + ######### + + "Add(Integer(2), Symbol('x'))_()" -> "Integer(2)_(0,)"; + "Add(Integer(2), Symbol('x'))_()" -> "Symbol('x')_(1,)"; + } + + """ + # repeat works by adding a signature tuple to the end of each node for its + # position in the graph. For example, for expr = Add(x, Pow(x, 2)), the x in the + # Pow will have the tuple (1, 0), meaning it is expr.args[1].args[0]. + graphstyle = _graphstyle.copy() + graphstyle.update(kwargs) + + nodes = [] + edges = [] + def traverse(e, depth, pos=()): + nodes.append(dotnode(e, styles, labelfunc=labelfunc, pos=pos, repeat=repeat)) + if maxdepth and depth >= maxdepth: + return + edges.extend(dotedges(e, atom=atom, pos=pos, repeat=repeat)) + [traverse(arg, depth+1, pos + (i,)) for i, arg in enumerate(e.args) if not atom(arg)] + traverse(expr, 0) + + return template%{'graphstyle': attrprint(graphstyle, delimiter='\n'), + 'nodes': '\n'.join(nodes), + 'edges': '\n'.join(edges)} diff --git a/venv/lib/python3.10/site-packages/sympy/printing/fortran.py b/venv/lib/python3.10/site-packages/sympy/printing/fortran.py new file mode 100644 index 0000000000000000000000000000000000000000..7cea812d72ddcd3ccb56c7258f74e6fe3b8d5211 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/fortran.py @@ -0,0 +1,779 @@ +""" +Fortran code printer + +The FCodePrinter converts single SymPy expressions into single Fortran +expressions, using the functions defined in the Fortran 77 standard where +possible. Some useful pointers to Fortran can be found on wikipedia: + +https://en.wikipedia.org/wiki/Fortran + +Most of the code below is based on the "Professional Programmer\'s Guide to +Fortran77" by Clive G. Page: + +https://www.star.le.ac.uk/~cgp/prof77.html + +Fortran is a case-insensitive language. This might cause trouble because +SymPy is case sensitive. So, fcode adds underscores to variable names when +it is necessary to make them different for Fortran. +""" + +from __future__ import annotations +from typing import Any + +from collections import defaultdict +from itertools import chain +import string + +from sympy.codegen.ast import ( + Assignment, Declaration, Pointer, value_const, + float32, float64, float80, complex64, complex128, int8, int16, int32, + int64, intc, real, integer, bool_, complex_, none, stderr, stdout +) +from sympy.codegen.fnodes import ( + allocatable, isign, dsign, cmplx, merge, literal_dp, elemental, pure, + intent_in, intent_out, intent_inout +) +from sympy.core import S, Add, N, Float, Symbol +from sympy.core.function import Function +from sympy.core.numbers import equal_valued +from sympy.core.relational import Eq +from sympy.sets import Range +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import precedence, PRECEDENCE +from sympy.printing.printer import printer_context + +# These are defined in the other file so we can avoid importing sympy.codegen +# from the top-level 'import sympy'. Export them here as well. +from sympy.printing.codeprinter import fcode, print_fcode # noqa:F401 + +known_functions = { + "sin": "sin", + "cos": "cos", + "tan": "tan", + "asin": "asin", + "acos": "acos", + "atan": "atan", + "atan2": "atan2", + "sinh": "sinh", + "cosh": "cosh", + "tanh": "tanh", + "log": "log", + "exp": "exp", + "erf": "erf", + "Abs": "abs", + "conjugate": "conjg", + "Max": "max", + "Min": "min", +} + + +class FCodePrinter(CodePrinter): + """A printer to convert SymPy expressions to strings of Fortran code""" + printmethod = "_fcode" + language = "Fortran" + + type_aliases = { + integer: int32, + real: float64, + complex_: complex128, + } + + type_mappings = { + intc: 'integer(c_int)', + float32: 'real*4', # real(kind(0.e0)) + float64: 'real*8', # real(kind(0.d0)) + float80: 'real*10', # real(kind(????)) + complex64: 'complex*8', + complex128: 'complex*16', + int8: 'integer*1', + int16: 'integer*2', + int32: 'integer*4', + int64: 'integer*8', + bool_: 'logical' + } + + type_modules = { + intc: {'iso_c_binding': 'c_int'} + } + + _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{ + 'precision': 17, + 'user_functions': {}, + 'source_format': 'fixed', + 'contract': True, + 'standard': 77, + 'name_mangling': True, + }) + + _operators = { + 'and': '.and.', + 'or': '.or.', + 'xor': '.neqv.', + 'equivalent': '.eqv.', + 'not': '.not. ', + } + + _relationals = { + '!=': '/=', + } + + def __init__(self, settings=None): + if not settings: + settings = {} + self.mangled_symbols = {} # Dict showing mapping of all words + self.used_name = [] + self.type_aliases = dict(chain(self.type_aliases.items(), + settings.pop('type_aliases', {}).items())) + self.type_mappings = dict(chain(self.type_mappings.items(), + settings.pop('type_mappings', {}).items())) + super().__init__(settings) + self.known_functions = dict(known_functions) + userfuncs = settings.get('user_functions', {}) + self.known_functions.update(userfuncs) + # leading columns depend on fixed or free format + standards = {66, 77, 90, 95, 2003, 2008} + if self._settings['standard'] not in standards: + raise ValueError("Unknown Fortran standard: %s" % self._settings[ + 'standard']) + self.module_uses = defaultdict(set) # e.g.: use iso_c_binding, only: c_int + + @property + def _lead(self): + if self._settings['source_format'] == 'fixed': + return {'code': " ", 'cont': " @ ", 'comment': "C "} + elif self._settings['source_format'] == 'free': + return {'code': "", 'cont': " ", 'comment': "! "} + else: + raise ValueError("Unknown source format: %s" % self._settings['source_format']) + + def _print_Symbol(self, expr): + if self._settings['name_mangling'] == True: + if expr not in self.mangled_symbols: + name = expr.name + while name.lower() in self.used_name: + name += '_' + self.used_name.append(name.lower()) + if name == expr.name: + self.mangled_symbols[expr] = expr + else: + self.mangled_symbols[expr] = Symbol(name) + + expr = expr.xreplace(self.mangled_symbols) + + name = super()._print_Symbol(expr) + return name + + def _rate_index_position(self, p): + return -p*5 + + def _get_statement(self, codestring): + return codestring + + def _get_comment(self, text): + return "! {}".format(text) + + def _declare_number_const(self, name, value): + return "parameter ({} = {})".format(name, self._print(value)) + + def _print_NumberSymbol(self, expr): + # A Number symbol that is not implemented here or with _printmethod + # is registered and evaluated + self._number_symbols.add((expr, Float(expr.evalf(self._settings['precision'])))) + return str(expr) + + def _format_code(self, lines): + return self._wrap_fortran(self.indent_code(lines)) + + def _traverse_matrix_indices(self, mat): + rows, cols = mat.shape + return ((i, j) for j in range(cols) for i in range(rows)) + + def _get_loop_opening_ending(self, indices): + open_lines = [] + close_lines = [] + for i in indices: + # fortran arrays start at 1 and end at dimension + var, start, stop = map(self._print, + [i.label, i.lower + 1, i.upper + 1]) + open_lines.append("do %s = %s, %s" % (var, start, stop)) + close_lines.append("end do") + return open_lines, close_lines + + def _print_sign(self, expr): + from sympy.functions.elementary.complexes import Abs + arg, = expr.args + if arg.is_integer: + new_expr = merge(0, isign(1, arg), Eq(arg, 0)) + elif (arg.is_complex or arg.is_infinite): + new_expr = merge(cmplx(literal_dp(0), literal_dp(0)), arg/Abs(arg), Eq(Abs(arg), literal_dp(0))) + else: + new_expr = merge(literal_dp(0), dsign(literal_dp(1), arg), Eq(arg, literal_dp(0))) + return self._print(new_expr) + + + def _print_Piecewise(self, expr): + if expr.args[-1].cond != True: + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + lines = [] + if expr.has(Assignment): + for i, (e, c) in enumerate(expr.args): + if i == 0: + lines.append("if (%s) then" % self._print(c)) + elif i == len(expr.args) - 1 and c == True: + lines.append("else") + else: + lines.append("else if (%s) then" % self._print(c)) + lines.append(self._print(e)) + lines.append("end if") + return "\n".join(lines) + elif self._settings["standard"] >= 95: + # Only supported in F95 and newer: + # The piecewise was used in an expression, need to do inline + # operators. This has the downside that inline operators will + # not work for statements that span multiple lines (Matrix or + # Indexed expressions). + pattern = "merge({T}, {F}, {COND})" + code = self._print(expr.args[-1].expr) + terms = list(expr.args[:-1]) + while terms: + e, c = terms.pop() + expr = self._print(e) + cond = self._print(c) + code = pattern.format(T=expr, F=code, COND=cond) + return code + else: + # `merge` is not supported prior to F95 + raise NotImplementedError("Using Piecewise as an expression using " + "inline operators is not supported in " + "standards earlier than Fortran95.") + + def _print_MatrixElement(self, expr): + return "{}({}, {})".format(self.parenthesize(expr.parent, + PRECEDENCE["Atom"], strict=True), expr.i + 1, expr.j + 1) + + def _print_Add(self, expr): + # purpose: print complex numbers nicely in Fortran. + # collect the purely real and purely imaginary parts: + pure_real = [] + pure_imaginary = [] + mixed = [] + for arg in expr.args: + if arg.is_number and arg.is_real: + pure_real.append(arg) + elif arg.is_number and arg.is_imaginary: + pure_imaginary.append(arg) + else: + mixed.append(arg) + if pure_imaginary: + if mixed: + PREC = precedence(expr) + term = Add(*mixed) + t = self._print(term) + if t.startswith('-'): + sign = "-" + t = t[1:] + else: + sign = "+" + if precedence(term) < PREC: + t = "(%s)" % t + + return "cmplx(%s,%s) %s %s" % ( + self._print(Add(*pure_real)), + self._print(-S.ImaginaryUnit*Add(*pure_imaginary)), + sign, t, + ) + else: + return "cmplx(%s,%s)" % ( + self._print(Add(*pure_real)), + self._print(-S.ImaginaryUnit*Add(*pure_imaginary)), + ) + else: + return CodePrinter._print_Add(self, expr) + + def _print_Function(self, expr): + # All constant function args are evaluated as floats + prec = self._settings['precision'] + args = [N(a, prec) for a in expr.args] + eval_expr = expr.func(*args) + if not isinstance(eval_expr, Function): + return self._print(eval_expr) + else: + return CodePrinter._print_Function(self, expr.func(*args)) + + def _print_Mod(self, expr): + # NOTE : Fortran has the functions mod() and modulo(). modulo() behaves + # the same wrt to the sign of the arguments as Python and SymPy's + # modulus computations (% and Mod()) but is not available in Fortran 66 + # or Fortran 77, thus we raise an error. + if self._settings['standard'] in [66, 77]: + msg = ("Python % operator and SymPy's Mod() function are not " + "supported by Fortran 66 or 77 standards.") + raise NotImplementedError(msg) + else: + x, y = expr.args + return " modulo({}, {})".format(self._print(x), self._print(y)) + + def _print_ImaginaryUnit(self, expr): + # purpose: print complex numbers nicely in Fortran. + return "cmplx(0,1)" + + def _print_int(self, expr): + return str(expr) + + def _print_Mul(self, expr): + # purpose: print complex numbers nicely in Fortran. + if expr.is_number and expr.is_imaginary: + return "cmplx(0,%s)" % ( + self._print(-S.ImaginaryUnit*expr) + ) + else: + return CodePrinter._print_Mul(self, expr) + + def _print_Pow(self, expr): + PREC = precedence(expr) + if equal_valued(expr.exp, -1): + return '%s/%s' % ( + self._print(literal_dp(1)), + self.parenthesize(expr.base, PREC) + ) + elif equal_valued(expr.exp, 0.5): + if expr.base.is_integer: + # Fortran intrinsic sqrt() does not accept integer argument + if expr.base.is_Number: + return 'sqrt(%s.0d0)' % self._print(expr.base) + else: + return 'sqrt(dble(%s))' % self._print(expr.base) + else: + return 'sqrt(%s)' % self._print(expr.base) + else: + return CodePrinter._print_Pow(self, expr) + + def _print_Rational(self, expr): + p, q = int(expr.p), int(expr.q) + return "%d.0d0/%d.0d0" % (p, q) + + def _print_Float(self, expr): + printed = CodePrinter._print_Float(self, expr) + e = printed.find('e') + if e > -1: + return "%sd%s" % (printed[:e], printed[e + 1:]) + return "%sd0" % printed + + def _print_Relational(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + op = expr.rel_op + op = op if op not in self._relationals else self._relationals[op] + return "{} {} {}".format(lhs_code, op, rhs_code) + + def _print_Indexed(self, expr): + inds = [ self._print(i) for i in expr.indices ] + return "%s(%s)" % (self._print(expr.base.label), ", ".join(inds)) + + def _print_AugmentedAssignment(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + return self._get_statement("{0} = {0} {1} {2}".format( + self._print(lhs_code), self._print(expr.binop), self._print(rhs_code))) + + def _print_sum_(self, sm): + params = self._print(sm.array) + if sm.dim != None: # Must use '!= None', cannot use 'is not None' + params += ', ' + self._print(sm.dim) + if sm.mask != None: # Must use '!= None', cannot use 'is not None' + params += ', mask=' + self._print(sm.mask) + return '%s(%s)' % (sm.__class__.__name__.rstrip('_'), params) + + def _print_product_(self, prod): + return self._print_sum_(prod) + + def _print_Do(self, do): + excl = ['concurrent'] + if do.step == 1: + excl.append('step') + step = '' + else: + step = ', {step}' + + return ( + 'do {concurrent}{counter} = {first}, {last}'+step+'\n' + '{body}\n' + 'end do\n' + ).format( + concurrent='concurrent ' if do.concurrent else '', + **do.kwargs(apply=lambda arg: self._print(arg), exclude=excl) + ) + + def _print_ImpliedDoLoop(self, idl): + step = '' if idl.step == 1 else ', {step}' + return ('({expr}, {counter} = {first}, {last}'+step+')').format( + **idl.kwargs(apply=lambda arg: self._print(arg)) + ) + + def _print_For(self, expr): + target = self._print(expr.target) + if isinstance(expr.iterable, Range): + start, stop, step = expr.iterable.args + else: + raise NotImplementedError("Only iterable currently supported is Range") + body = self._print(expr.body) + return ('do {target} = {start}, {stop}, {step}\n' + '{body}\n' + 'end do').format(target=target, start=start, stop=stop - 1, + step=step, body=body) + + def _print_Type(self, type_): + type_ = self.type_aliases.get(type_, type_) + type_str = self.type_mappings.get(type_, type_.name) + module_uses = self.type_modules.get(type_) + if module_uses: + for k, v in module_uses: + self.module_uses[k].add(v) + return type_str + + def _print_Element(self, elem): + return '{symbol}({idxs})'.format( + symbol=self._print(elem.symbol), + idxs=', '.join((self._print(arg) for arg in elem.indices)) + ) + + def _print_Extent(self, ext): + return str(ext) + + def _print_Declaration(self, expr): + var = expr.variable + val = var.value + dim = var.attr_params('dimension') + intents = [intent in var.attrs for intent in (intent_in, intent_out, intent_inout)] + if intents.count(True) == 0: + intent = '' + elif intents.count(True) == 1: + intent = ', intent(%s)' % ['in', 'out', 'inout'][intents.index(True)] + else: + raise ValueError("Multiple intents specified for %s" % self) + + if isinstance(var, Pointer): + raise NotImplementedError("Pointers are not available by default in Fortran.") + if self._settings["standard"] >= 90: + result = '{t}{vc}{dim}{intent}{alloc} :: {s}'.format( + t=self._print(var.type), + vc=', parameter' if value_const in var.attrs else '', + dim=', dimension(%s)' % ', '.join((self._print(arg) for arg in dim)) if dim else '', + intent=intent, + alloc=', allocatable' if allocatable in var.attrs else '', + s=self._print(var.symbol) + ) + if val != None: # Must be "!= None", cannot be "is not None" + result += ' = %s' % self._print(val) + else: + if value_const in var.attrs or val: + raise NotImplementedError("F77 init./parameter statem. req. multiple lines.") + result = ' '.join((self._print(arg) for arg in [var.type, var.symbol])) + + return result + + + def _print_Infinity(self, expr): + return '(huge(%s) + 1)' % self._print(literal_dp(0)) + + def _print_While(self, expr): + return 'do while ({condition})\n{body}\nend do'.format(**expr.kwargs( + apply=lambda arg: self._print(arg))) + + def _print_BooleanTrue(self, expr): + return '.true.' + + def _print_BooleanFalse(self, expr): + return '.false.' + + def _pad_leading_columns(self, lines): + result = [] + for line in lines: + if line.startswith('!'): + result.append(self._lead['comment'] + line[1:].lstrip()) + else: + result.append(self._lead['code'] + line) + return result + + def _wrap_fortran(self, lines): + """Wrap long Fortran lines + + Argument: + lines -- a list of lines (without \\n character) + + A comment line is split at white space. Code lines are split with a more + complex rule to give nice results. + """ + # routine to find split point in a code line + my_alnum = set("_+-." + string.digits + string.ascii_letters) + my_white = set(" \t()") + + def split_pos_code(line, endpos): + if len(line) <= endpos: + return len(line) + pos = endpos + split = lambda pos: \ + (line[pos] in my_alnum and line[pos - 1] not in my_alnum) or \ + (line[pos] not in my_alnum and line[pos - 1] in my_alnum) or \ + (line[pos] in my_white and line[pos - 1] not in my_white) or \ + (line[pos] not in my_white and line[pos - 1] in my_white) + while not split(pos): + pos -= 1 + if pos == 0: + return endpos + return pos + # split line by line and add the split lines to result + result = [] + if self._settings['source_format'] == 'free': + trailing = ' &' + else: + trailing = '' + for line in lines: + if line.startswith(self._lead['comment']): + # comment line + if len(line) > 72: + pos = line.rfind(" ", 6, 72) + if pos == -1: + pos = 72 + hunk = line[:pos] + line = line[pos:].lstrip() + result.append(hunk) + while line: + pos = line.rfind(" ", 0, 66) + if pos == -1 or len(line) < 66: + pos = 66 + hunk = line[:pos] + line = line[pos:].lstrip() + result.append("%s%s" % (self._lead['comment'], hunk)) + else: + result.append(line) + elif line.startswith(self._lead['code']): + # code line + pos = split_pos_code(line, 72) + hunk = line[:pos].rstrip() + line = line[pos:].lstrip() + if line: + hunk += trailing + result.append(hunk) + while line: + pos = split_pos_code(line, 65) + hunk = line[:pos].rstrip() + line = line[pos:].lstrip() + if line: + hunk += trailing + result.append("%s%s" % (self._lead['cont'], hunk)) + else: + result.append(line) + return result + + def indent_code(self, code): + """Accepts a string of code or a list of code lines""" + if isinstance(code, str): + code_lines = self.indent_code(code.splitlines(True)) + return ''.join(code_lines) + + free = self._settings['source_format'] == 'free' + code = [ line.lstrip(' \t') for line in code ] + + inc_keyword = ('do ', 'if(', 'if ', 'do\n', 'else', 'program', 'interface') + dec_keyword = ('end do', 'enddo', 'end if', 'endif', 'else', 'end program', 'end interface') + + increase = [ int(any(map(line.startswith, inc_keyword))) + for line in code ] + decrease = [ int(any(map(line.startswith, dec_keyword))) + for line in code ] + continuation = [ int(any(map(line.endswith, ['&', '&\n']))) + for line in code ] + + level = 0 + cont_padding = 0 + tabwidth = 3 + new_code = [] + for i, line in enumerate(code): + if line in ('', '\n'): + new_code.append(line) + continue + level -= decrease[i] + + if free: + padding = " "*(level*tabwidth + cont_padding) + else: + padding = " "*level*tabwidth + + line = "%s%s" % (padding, line) + if not free: + line = self._pad_leading_columns([line])[0] + + new_code.append(line) + + if continuation[i]: + cont_padding = 2*tabwidth + else: + cont_padding = 0 + level += increase[i] + + if not free: + return self._wrap_fortran(new_code) + return new_code + + def _print_GoTo(self, goto): + if goto.expr: # computed goto + return "go to ({labels}), {expr}".format( + labels=', '.join((self._print(arg) for arg in goto.labels)), + expr=self._print(goto.expr) + ) + else: + lbl, = goto.labels + return "go to %s" % self._print(lbl) + + def _print_Program(self, prog): + return ( + "program {name}\n" + "{body}\n" + "end program\n" + ).format(**prog.kwargs(apply=lambda arg: self._print(arg))) + + def _print_Module(self, mod): + return ( + "module {name}\n" + "{declarations}\n" + "\ncontains\n\n" + "{definitions}\n" + "end module\n" + ).format(**mod.kwargs(apply=lambda arg: self._print(arg))) + + def _print_Stream(self, strm): + if strm.name == 'stdout' and self._settings["standard"] >= 2003: + self.module_uses['iso_c_binding'].add('stdint=>input_unit') + return 'input_unit' + elif strm.name == 'stderr' and self._settings["standard"] >= 2003: + self.module_uses['iso_c_binding'].add('stdint=>error_unit') + return 'error_unit' + else: + if strm.name == 'stdout': + return '*' + else: + return strm.name + + def _print_Print(self, ps): + if ps.format_string == none: # Must be '!= None', cannot be 'is not None' + template = "print {fmt}, {iolist}" + fmt = '*' + else: + template = 'write(%(out)s, fmt="{fmt}", advance="no"), {iolist}' % { + 'out': {stderr: '0', stdout: '6'}.get(ps.file, '*') + } + fmt = self._print(ps.format_string) + return template.format(fmt=fmt, iolist=', '.join( + (self._print(arg) for arg in ps.print_args))) + + def _print_Return(self, rs): + arg, = rs.args + return "{result_name} = {arg}".format( + result_name=self._context.get('result_name', 'sympy_result'), + arg=self._print(arg) + ) + + def _print_FortranReturn(self, frs): + arg, = frs.args + if arg: + return 'return %s' % self._print(arg) + else: + return 'return' + + def _head(self, entity, fp, **kwargs): + bind_C_params = fp.attr_params('bind_C') + if bind_C_params is None: + bind = '' + else: + bind = ' bind(C, name="%s")' % bind_C_params[0] if bind_C_params else ' bind(C)' + result_name = self._settings.get('result_name', None) + return ( + "{entity}{name}({arg_names}){result}{bind}\n" + "{arg_declarations}" + ).format( + entity=entity, + name=self._print(fp.name), + arg_names=', '.join([self._print(arg.symbol) for arg in fp.parameters]), + result=(' result(%s)' % result_name) if result_name else '', + bind=bind, + arg_declarations='\n'.join((self._print(Declaration(arg)) for arg in fp.parameters)) + ) + + def _print_FunctionPrototype(self, fp): + entity = "{} function ".format(self._print(fp.return_type)) + return ( + "interface\n" + "{function_head}\n" + "end function\n" + "end interface" + ).format(function_head=self._head(entity, fp)) + + def _print_FunctionDefinition(self, fd): + if elemental in fd.attrs: + prefix = 'elemental ' + elif pure in fd.attrs: + prefix = 'pure ' + else: + prefix = '' + + entity = "{} function ".format(self._print(fd.return_type)) + with printer_context(self, result_name=fd.name): + return ( + "{prefix}{function_head}\n" + "{body}\n" + "end function\n" + ).format( + prefix=prefix, + function_head=self._head(entity, fd), + body=self._print(fd.body) + ) + + def _print_Subroutine(self, sub): + return ( + '{subroutine_head}\n' + '{body}\n' + 'end subroutine\n' + ).format( + subroutine_head=self._head('subroutine ', sub), + body=self._print(sub.body) + ) + + def _print_SubroutineCall(self, scall): + return 'call {name}({args})'.format( + name=self._print(scall.name), + args=', '.join((self._print(arg) for arg in scall.subroutine_args)) + ) + + def _print_use_rename(self, rnm): + return "%s => %s" % tuple((self._print(arg) for arg in rnm.args)) + + def _print_use(self, use): + result = 'use %s' % self._print(use.namespace) + if use.rename != None: # Must be '!= None', cannot be 'is not None' + result += ', ' + ', '.join([self._print(rnm) for rnm in use.rename]) + if use.only != None: # Must be '!= None', cannot be 'is not None' + result += ', only: ' + ', '.join([self._print(nly) for nly in use.only]) + return result + + def _print_BreakToken(self, _): + return 'exit' + + def _print_ContinueToken(self, _): + return 'cycle' + + def _print_ArrayConstructor(self, ac): + fmtstr = "[%s]" if self._settings["standard"] >= 2003 else '(/%s/)' + return fmtstr % ', '.join((self._print(arg) for arg in ac.elements)) + + def _print_ArrayElement(self, elem): + return '{symbol}({idxs})'.format( + symbol=self._print(elem.name), + idxs=', '.join((self._print(arg) for arg in elem.indices)) + ) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/glsl.py b/venv/lib/python3.10/site-packages/sympy/printing/glsl.py new file mode 100644 index 0000000000000000000000000000000000000000..f98df8d46abec5f891b6bc9836a13ca69934275c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/glsl.py @@ -0,0 +1,548 @@ +from __future__ import annotations + +from sympy.core import Basic, S +from sympy.core.function import Lambda +from sympy.core.numbers import equal_valued +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import precedence +from functools import reduce + +known_functions = { + 'Abs': 'abs', + 'sin': 'sin', + 'cos': 'cos', + 'tan': 'tan', + 'acos': 'acos', + 'asin': 'asin', + 'atan': 'atan', + 'atan2': 'atan', + 'ceiling': 'ceil', + 'floor': 'floor', + 'sign': 'sign', + 'exp': 'exp', + 'log': 'log', + 'add': 'add', + 'sub': 'sub', + 'mul': 'mul', + 'pow': 'pow' +} + +class GLSLPrinter(CodePrinter): + """ + Rudimentary, generic GLSL printing tools. + + Additional settings: + 'use_operators': Boolean (should the printer use operators for +,-,*, or functions?) + """ + _not_supported: set[Basic] = set() + printmethod = "_glsl" + language = "GLSL" + + _default_settings = dict(CodePrinter._default_settings, **{ + 'use_operators': True, + 'zero': 0, + 'mat_nested': False, + 'mat_separator': ',\n', + 'mat_transpose': False, + 'array_type': 'float', + 'glsl_types': True, + + 'precision': 9, + 'user_functions': {}, + 'contract': True, + }) + + def __init__(self, settings={}): + CodePrinter.__init__(self, settings) + self.known_functions = dict(known_functions) + userfuncs = settings.get('user_functions', {}) + self.known_functions.update(userfuncs) + + def _rate_index_position(self, p): + return p*5 + + def _get_statement(self, codestring): + return "%s;" % codestring + + def _get_comment(self, text): + return "// {}".format(text) + + def _declare_number_const(self, name, value): + return "float {} = {};".format(name, value) + + def _format_code(self, lines): + return self.indent_code(lines) + + def indent_code(self, code): + """Accepts a string of code or a list of code lines""" + + if isinstance(code, str): + code_lines = self.indent_code(code.splitlines(True)) + return ''.join(code_lines) + + tab = " " + inc_token = ('{', '(', '{\n', '(\n') + dec_token = ('}', ')') + + code = [line.lstrip(' \t') for line in code] + + increase = [int(any(map(line.endswith, inc_token))) for line in code] + decrease = [int(any(map(line.startswith, dec_token))) for line in code] + + pretty = [] + level = 0 + for n, line in enumerate(code): + if line in ('', '\n'): + pretty.append(line) + continue + level -= decrease[n] + pretty.append("%s%s" % (tab*level, line)) + level += increase[n] + return pretty + + def _print_MatrixBase(self, mat): + mat_separator = self._settings['mat_separator'] + mat_transpose = self._settings['mat_transpose'] + column_vector = (mat.rows == 1) if mat_transpose else (mat.cols == 1) + A = mat.transpose() if mat_transpose != column_vector else mat + + glsl_types = self._settings['glsl_types'] + array_type = self._settings['array_type'] + array_size = A.cols*A.rows + array_constructor = "{}[{}]".format(array_type, array_size) + + if A.cols == 1: + return self._print(A[0]) + if A.rows <= 4 and A.cols <= 4 and glsl_types: + if A.rows == 1: + return "vec{}{}".format( + A.cols, A.table(self,rowstart='(',rowend=')') + ) + elif A.rows == A.cols: + return "mat{}({})".format( + A.rows, A.table(self,rowsep=', ', + rowstart='',rowend='') + ) + else: + return "mat{}x{}({})".format( + A.cols, A.rows, + A.table(self,rowsep=', ', + rowstart='',rowend='') + ) + elif S.One in A.shape: + return "{}({})".format( + array_constructor, + A.table(self,rowsep=mat_separator,rowstart='',rowend='') + ) + elif not self._settings['mat_nested']: + return "{}(\n{}\n) /* a {}x{} matrix */".format( + array_constructor, + A.table(self,rowsep=mat_separator,rowstart='',rowend=''), + A.rows, A.cols + ) + elif self._settings['mat_nested']: + return "{}[{}][{}](\n{}\n)".format( + array_type, A.rows, A.cols, + A.table(self,rowsep=mat_separator,rowstart='float[](',rowend=')') + ) + + def _print_SparseRepMatrix(self, mat): + # do not allow sparse matrices to be made dense + return self._print_not_supported(mat) + + def _traverse_matrix_indices(self, mat): + mat_transpose = self._settings['mat_transpose'] + if mat_transpose: + rows,cols = mat.shape + else: + cols,rows = mat.shape + return ((i, j) for i in range(cols) for j in range(rows)) + + def _print_MatrixElement(self, expr): + # print('begin _print_MatrixElement') + nest = self._settings['mat_nested'] + glsl_types = self._settings['glsl_types'] + mat_transpose = self._settings['mat_transpose'] + if mat_transpose: + cols,rows = expr.parent.shape + i,j = expr.j,expr.i + else: + rows,cols = expr.parent.shape + i,j = expr.i,expr.j + pnt = self._print(expr.parent) + if glsl_types and ((rows <= 4 and cols <=4) or nest): + return "{}[{}][{}]".format(pnt, i, j) + else: + return "{}[{}]".format(pnt, i + j*rows) + + def _print_list(self, expr): + l = ', '.join(self._print(item) for item in expr) + glsl_types = self._settings['glsl_types'] + array_type = self._settings['array_type'] + array_size = len(expr) + array_constructor = '{}[{}]'.format(array_type, array_size) + + if array_size <= 4 and glsl_types: + return 'vec{}({})'.format(array_size, l) + else: + return '{}({})'.format(array_constructor, l) + + _print_tuple = _print_list + _print_Tuple = _print_list + + def _get_loop_opening_ending(self, indices): + open_lines = [] + close_lines = [] + loopstart = "for (int %(varble)s=%(start)s; %(varble)s<%(end)s; %(varble)s++){" + for i in indices: + # GLSL arrays start at 0 and end at dimension-1 + open_lines.append(loopstart % { + 'varble': self._print(i.label), + 'start': self._print(i.lower), + 'end': self._print(i.upper + 1)}) + close_lines.append("}") + return open_lines, close_lines + + def _print_Function_with_args(self, func, func_args): + if func in self.known_functions: + cond_func = self.known_functions[func] + func = None + if isinstance(cond_func, str): + func = cond_func + else: + for cond, func in cond_func: + if cond(func_args): + break + if func is not None: + try: + return func(*[self.parenthesize(item, 0) for item in func_args]) + except TypeError: + return '{}({})'.format(func, self.stringify(func_args, ", ")) + elif isinstance(func, Lambda): + # inlined function + return self._print(func(*func_args)) + else: + return self._print_not_supported(func) + + def _print_Piecewise(self, expr): + from sympy.codegen.ast import Assignment + if expr.args[-1].cond != True: + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + lines = [] + if expr.has(Assignment): + for i, (e, c) in enumerate(expr.args): + if i == 0: + lines.append("if (%s) {" % self._print(c)) + elif i == len(expr.args) - 1 and c == True: + lines.append("else {") + else: + lines.append("else if (%s) {" % self._print(c)) + code0 = self._print(e) + lines.append(code0) + lines.append("}") + return "\n".join(lines) + else: + # The piecewise was used in an expression, need to do inline + # operators. This has the downside that inline operators will + # not work for statements that span multiple lines (Matrix or + # Indexed expressions). + ecpairs = ["((%s) ? (\n%s\n)\n" % (self._print(c), + self._print(e)) + for e, c in expr.args[:-1]] + last_line = ": (\n%s\n)" % self._print(expr.args[-1].expr) + return ": ".join(ecpairs) + last_line + " ".join([")"*len(ecpairs)]) + + def _print_Indexed(self, expr): + # calculate index for 1d array + dims = expr.shape + elem = S.Zero + offset = S.One + for i in reversed(range(expr.rank)): + elem += expr.indices[i]*offset + offset *= dims[i] + return "{}[{}]".format( + self._print(expr.base.label), + self._print(elem) + ) + + def _print_Pow(self, expr): + PREC = precedence(expr) + if equal_valued(expr.exp, -1): + return '1.0/%s' % (self.parenthesize(expr.base, PREC)) + elif equal_valued(expr.exp, 0.5): + return 'sqrt(%s)' % self._print(expr.base) + else: + try: + e = self._print(float(expr.exp)) + except TypeError: + e = self._print(expr.exp) + return self._print_Function_with_args('pow', ( + self._print(expr.base), + e + )) + + def _print_int(self, expr): + return str(float(expr)) + + def _print_Rational(self, expr): + return "{}.0/{}.0".format(expr.p, expr.q) + + def _print_Relational(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + op = expr.rel_op + return "{} {} {}".format(lhs_code, op, rhs_code) + + def _print_Add(self, expr, order=None): + if self._settings['use_operators']: + return CodePrinter._print_Add(self, expr, order=order) + + terms = expr.as_ordered_terms() + + def partition(p,l): + return reduce(lambda x, y: (x[0]+[y], x[1]) if p(y) else (x[0], x[1]+[y]), l, ([], [])) + def add(a,b): + return self._print_Function_with_args('add', (a, b)) + # return self.known_functions['add']+'(%s, %s)' % (a,b) + neg, pos = partition(lambda arg: arg.could_extract_minus_sign(), terms) + if pos: + s = pos = reduce(lambda a,b: add(a,b), (self._print(t) for t in pos)) + else: + s = pos = self._print(self._settings['zero']) + + if neg: + # sum the absolute values of the negative terms + neg = reduce(lambda a,b: add(a,b), (self._print(-n) for n in neg)) + # then subtract them from the positive terms + s = self._print_Function_with_args('sub', (pos,neg)) + # s = self.known_functions['sub']+'(%s, %s)' % (pos,neg) + return s + + def _print_Mul(self, expr, **kwargs): + if self._settings['use_operators']: + return CodePrinter._print_Mul(self, expr, **kwargs) + terms = expr.as_ordered_factors() + def mul(a,b): + # return self.known_functions['mul']+'(%s, %s)' % (a,b) + return self._print_Function_with_args('mul', (a,b)) + + s = reduce(lambda a,b: mul(a,b), (self._print(t) for t in terms)) + return s + +def glsl_code(expr,assign_to=None,**settings): + """Converts an expr to a string of GLSL code + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used for naming the variable or variables + to which the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol`` or ``Indexed`` type object. In cases where ``expr`` + would be printed as an array, a list of string or ``Symbol`` objects + can also be passed. + + This is helpful in case of line-wrapping, or for expressions that + generate multi-line statements. It can also be used to spread an array-like + expression into multiple assignments. + use_operators: bool, optional + If set to False, then *,/,+,- operators will be replaced with functions + mul, add, and sub, which must be implemented by the user, e.g. for + implementing non-standard rings or emulated quad/octal precision. + [default=True] + glsl_types: bool, optional + Set this argument to ``False`` in order to avoid using the ``vec`` and ``mat`` + types. The printer will instead use arrays (or nested arrays). + [default=True] + mat_nested: bool, optional + GLSL version 4.3 and above support nested arrays (arrays of arrays). Set this to ``True`` + to render matrices as nested arrays. + [default=False] + mat_separator: str, optional + By default, matrices are rendered with newlines using this separator, + making them easier to read, but less compact. By removing the newline + this option can be used to make them more vertically compact. + [default=',\n'] + mat_transpose: bool, optional + GLSL's matrix multiplication implementation assumes column-major indexing. + By default, this printer ignores that convention. Setting this option to + ``True`` transposes all matrix output. + [default=False] + array_type: str, optional + The GLSL array constructor type. + [default='float'] + precision : integer, optional + The precision for numbers such as pi [default=15]. + user_functions : dict, optional + A dictionary where keys are ``FunctionClass`` instances and values are + their string representations. Alternatively, the dictionary value can + be a list of tuples i.e. [(argument_test, js_function_string)]. See + below for examples. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + + Examples + ======== + + >>> from sympy import glsl_code, symbols, Rational, sin, ceiling, Abs + >>> x, tau = symbols("x, tau") + >>> glsl_code((2*tau)**Rational(7, 2)) + '8*sqrt(2)*pow(tau, 3.5)' + >>> glsl_code(sin(x), assign_to="float y") + 'float y = sin(x);' + + Various GLSL types are supported: + >>> from sympy import Matrix, glsl_code + >>> glsl_code(Matrix([1,2,3])) + 'vec3(1, 2, 3)' + + >>> glsl_code(Matrix([[1, 2],[3, 4]])) + 'mat2(1, 2, 3, 4)' + + Pass ``mat_transpose = True`` to switch to column-major indexing: + >>> glsl_code(Matrix([[1, 2],[3, 4]]), mat_transpose = True) + 'mat2(1, 3, 2, 4)' + + By default, larger matrices get collapsed into float arrays: + >>> print(glsl_code( Matrix([[1,2,3,4,5],[6,7,8,9,10]]) )) + float[10]( + 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10 + ) /* a 2x5 matrix */ + + The type of array constructor used to print GLSL arrays can be controlled + via the ``array_type`` parameter: + >>> glsl_code(Matrix([1,2,3,4,5]), array_type='int') + 'int[5](1, 2, 3, 4, 5)' + + Passing a list of strings or ``symbols`` to the ``assign_to`` parameter will yield + a multi-line assignment for each item in an array-like expression: + >>> x_struct_members = symbols('x.a x.b x.c x.d') + >>> print(glsl_code(Matrix([1,2,3,4]), assign_to=x_struct_members)) + x.a = 1; + x.b = 2; + x.c = 3; + x.d = 4; + + This could be useful in cases where it's desirable to modify members of a + GLSL ``Struct``. It could also be used to spread items from an array-like + expression into various miscellaneous assignments: + >>> misc_assignments = ('x[0]', 'x[1]', 'float y', 'float z') + >>> print(glsl_code(Matrix([1,2,3,4]), assign_to=misc_assignments)) + x[0] = 1; + x[1] = 2; + float y = 3; + float z = 4; + + Passing ``mat_nested = True`` instead prints out nested float arrays, which are + supported in GLSL 4.3 and above. + >>> mat = Matrix([ + ... [ 0, 1, 2], + ... [ 3, 4, 5], + ... [ 6, 7, 8], + ... [ 9, 10, 11], + ... [12, 13, 14]]) + >>> print(glsl_code( mat, mat_nested = True )) + float[5][3]( + float[]( 0, 1, 2), + float[]( 3, 4, 5), + float[]( 6, 7, 8), + float[]( 9, 10, 11), + float[](12, 13, 14) + ) + + + + Custom printing can be defined for certain types by passing a dictionary of + "type" : "function" to the ``user_functions`` kwarg. Alternatively, the + dictionary value can be a list of tuples i.e. [(argument_test, + js_function_string)]. + + >>> custom_functions = { + ... "ceiling": "CEIL", + ... "Abs": [(lambda x: not x.is_integer, "fabs"), + ... (lambda x: x.is_integer, "ABS")] + ... } + >>> glsl_code(Abs(x) + ceiling(x), user_functions=custom_functions) + 'fabs(x) + CEIL(x)' + + If further control is needed, addition, subtraction, multiplication and + division operators can be replaced with ``add``, ``sub``, and ``mul`` + functions. This is done by passing ``use_operators = False``: + + >>> x,y,z = symbols('x,y,z') + >>> glsl_code(x*(y+z), use_operators = False) + 'mul(x, add(y, z))' + >>> glsl_code(x*(y+z*(x-y)**z), use_operators = False) + 'mul(x, add(y, mul(z, pow(sub(x, y), z))))' + + ``Piecewise`` expressions are converted into conditionals. If an + ``assign_to`` variable is provided an if statement is created, otherwise + the ternary operator is used. Note that if the ``Piecewise`` lacks a + default term, represented by ``(expr, True)`` then an error will be thrown. + This is to prevent generating an expression that may not evaluate to + anything. + + >>> from sympy import Piecewise + >>> expr = Piecewise((x + 1, x > 0), (x, True)) + >>> print(glsl_code(expr, tau)) + if (x > 0) { + tau = x + 1; + } + else { + tau = x; + } + + Support for loops is provided through ``Indexed`` types. With + ``contract=True`` these expressions will be turned into loops, whereas + ``contract=False`` will just print the assignment expression that should be + looped over: + + >>> from sympy import Eq, IndexedBase, Idx + >>> len_y = 5 + >>> y = IndexedBase('y', shape=(len_y,)) + >>> t = IndexedBase('t', shape=(len_y,)) + >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) + >>> i = Idx('i', len_y-1) + >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) + >>> glsl_code(e.rhs, assign_to=e.lhs, contract=False) + 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);' + + >>> from sympy import Matrix, MatrixSymbol + >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)]) + >>> A = MatrixSymbol('A', 3, 1) + >>> print(glsl_code(mat, A)) + A[0][0] = pow(x, 2.0); + if (x > 0) { + A[1][0] = x + 1; + } + else { + A[1][0] = x; + } + A[2][0] = sin(x); + """ + return GLSLPrinter(settings).doprint(expr,assign_to) + +def print_glsl(expr, **settings): + """Prints the GLSL representation of the given expression. + + See GLSLPrinter init function for settings. + """ + print(glsl_code(expr, **settings)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/gtk.py b/venv/lib/python3.10/site-packages/sympy/printing/gtk.py new file mode 100644 index 0000000000000000000000000000000000000000..4123d7231c730bbde28e33f441470c28b21c78d0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/gtk.py @@ -0,0 +1,16 @@ +from sympy.printing.mathml import mathml +from sympy.utilities.mathml import c2p +import tempfile +import subprocess + + +def print_gtk(x, start_viewer=True): + """Print to Gtkmathview, a gtk widget capable of rendering MathML. + + Needs libgtkmathview-bin""" + with tempfile.NamedTemporaryFile('w') as file: + file.write(c2p(mathml(x), simple=True)) + file.flush() + + if start_viewer: + subprocess.check_call(('mathmlviewer', file.name)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/jscode.py b/venv/lib/python3.10/site-packages/sympy/printing/jscode.py new file mode 100644 index 0000000000000000000000000000000000000000..753eb3291dd719ff53b06584de8ebe76c4471a3f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/jscode.py @@ -0,0 +1,332 @@ +""" +Javascript code printer + +The JavascriptCodePrinter converts single SymPy expressions into single +Javascript expressions, using the functions defined in the Javascript +Math object where possible. + +""" + +from __future__ import annotations +from typing import Any + +from sympy.core import S +from sympy.core.numbers import equal_valued +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import precedence, PRECEDENCE + + +# dictionary mapping SymPy function to (argument_conditions, Javascript_function). +# Used in JavascriptCodePrinter._print_Function(self) +known_functions = { + 'Abs': 'Math.abs', + 'acos': 'Math.acos', + 'acosh': 'Math.acosh', + 'asin': 'Math.asin', + 'asinh': 'Math.asinh', + 'atan': 'Math.atan', + 'atan2': 'Math.atan2', + 'atanh': 'Math.atanh', + 'ceiling': 'Math.ceil', + 'cos': 'Math.cos', + 'cosh': 'Math.cosh', + 'exp': 'Math.exp', + 'floor': 'Math.floor', + 'log': 'Math.log', + 'Max': 'Math.max', + 'Min': 'Math.min', + 'sign': 'Math.sign', + 'sin': 'Math.sin', + 'sinh': 'Math.sinh', + 'tan': 'Math.tan', + 'tanh': 'Math.tanh', +} + + +class JavascriptCodePrinter(CodePrinter): + """"A Printer to convert Python expressions to strings of JavaScript code + """ + printmethod = '_javascript' + language = 'JavaScript' + + _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{ + 'precision': 17, + 'user_functions': {}, + 'contract': True, + }) + + def __init__(self, settings={}): + CodePrinter.__init__(self, settings) + self.known_functions = dict(known_functions) + userfuncs = settings.get('user_functions', {}) + self.known_functions.update(userfuncs) + + def _rate_index_position(self, p): + return p*5 + + def _get_statement(self, codestring): + return "%s;" % codestring + + def _get_comment(self, text): + return "// {}".format(text) + + def _declare_number_const(self, name, value): + return "var {} = {};".format(name, value.evalf(self._settings['precision'])) + + def _format_code(self, lines): + return self.indent_code(lines) + + def _traverse_matrix_indices(self, mat): + rows, cols = mat.shape + return ((i, j) for i in range(rows) for j in range(cols)) + + def _get_loop_opening_ending(self, indices): + open_lines = [] + close_lines = [] + loopstart = "for (var %(varble)s=%(start)s; %(varble)s<%(end)s; %(varble)s++){" + for i in indices: + # Javascript arrays start at 0 and end at dimension-1 + open_lines.append(loopstart % { + 'varble': self._print(i.label), + 'start': self._print(i.lower), + 'end': self._print(i.upper + 1)}) + close_lines.append("}") + return open_lines, close_lines + + def _print_Pow(self, expr): + PREC = precedence(expr) + if equal_valued(expr.exp, -1): + return '1/%s' % (self.parenthesize(expr.base, PREC)) + elif equal_valued(expr.exp, 0.5): + return 'Math.sqrt(%s)' % self._print(expr.base) + elif expr.exp == S.One/3: + return 'Math.cbrt(%s)' % self._print(expr.base) + else: + return 'Math.pow(%s, %s)' % (self._print(expr.base), + self._print(expr.exp)) + + def _print_Rational(self, expr): + p, q = int(expr.p), int(expr.q) + return '%d/%d' % (p, q) + + def _print_Mod(self, expr): + num, den = expr.args + PREC = precedence(expr) + snum, sden = [self.parenthesize(arg, PREC) for arg in expr.args] + # % is remainder (same sign as numerator), not modulo (same sign as + # denominator), in js. Hence, % only works as modulo if both numbers + # have the same sign + if (num.is_nonnegative and den.is_nonnegative or + num.is_nonpositive and den.is_nonpositive): + return f"{snum} % {sden}" + return f"(({snum} % {sden}) + {sden}) % {sden}" + + def _print_Relational(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + op = expr.rel_op + return "{} {} {}".format(lhs_code, op, rhs_code) + + def _print_Indexed(self, expr): + # calculate index for 1d array + dims = expr.shape + elem = S.Zero + offset = S.One + for i in reversed(range(expr.rank)): + elem += expr.indices[i]*offset + offset *= dims[i] + return "%s[%s]" % (self._print(expr.base.label), self._print(elem)) + + def _print_Exp1(self, expr): + return "Math.E" + + def _print_Pi(self, expr): + return 'Math.PI' + + def _print_Infinity(self, expr): + return 'Number.POSITIVE_INFINITY' + + def _print_NegativeInfinity(self, expr): + return 'Number.NEGATIVE_INFINITY' + + def _print_Piecewise(self, expr): + from sympy.codegen.ast import Assignment + if expr.args[-1].cond != True: + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + lines = [] + if expr.has(Assignment): + for i, (e, c) in enumerate(expr.args): + if i == 0: + lines.append("if (%s) {" % self._print(c)) + elif i == len(expr.args) - 1 and c == True: + lines.append("else {") + else: + lines.append("else if (%s) {" % self._print(c)) + code0 = self._print(e) + lines.append(code0) + lines.append("}") + return "\n".join(lines) + else: + # The piecewise was used in an expression, need to do inline + # operators. This has the downside that inline operators will + # not work for statements that span multiple lines (Matrix or + # Indexed expressions). + ecpairs = ["((%s) ? (\n%s\n)\n" % (self._print(c), self._print(e)) + for e, c in expr.args[:-1]] + last_line = ": (\n%s\n)" % self._print(expr.args[-1].expr) + return ": ".join(ecpairs) + last_line + " ".join([")"*len(ecpairs)]) + + def _print_MatrixElement(self, expr): + return "{}[{}]".format(self.parenthesize(expr.parent, + PRECEDENCE["Atom"], strict=True), + expr.j + expr.i*expr.parent.shape[1]) + + def indent_code(self, code): + """Accepts a string of code or a list of code lines""" + + if isinstance(code, str): + code_lines = self.indent_code(code.splitlines(True)) + return ''.join(code_lines) + + tab = " " + inc_token = ('{', '(', '{\n', '(\n') + dec_token = ('}', ')') + + code = [ line.lstrip(' \t') for line in code ] + + increase = [ int(any(map(line.endswith, inc_token))) for line in code ] + decrease = [ int(any(map(line.startswith, dec_token))) + for line in code ] + + pretty = [] + level = 0 + for n, line in enumerate(code): + if line in ('', '\n'): + pretty.append(line) + continue + level -= decrease[n] + pretty.append("%s%s" % (tab*level, line)) + level += increase[n] + return pretty + + +def jscode(expr, assign_to=None, **settings): + """Converts an expr to a string of javascript code + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used as the name of the variable to which + the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of + line-wrapping, or for expressions that generate multi-line statements. + precision : integer, optional + The precision for numbers such as pi [default=15]. + user_functions : dict, optional + A dictionary where keys are ``FunctionClass`` instances and values are + their string representations. Alternatively, the dictionary value can + be a list of tuples i.e. [(argument_test, js_function_string)]. See + below for examples. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + + Examples + ======== + + >>> from sympy import jscode, symbols, Rational, sin, ceiling, Abs + >>> x, tau = symbols("x, tau") + >>> jscode((2*tau)**Rational(7, 2)) + '8*Math.sqrt(2)*Math.pow(tau, 7/2)' + >>> jscode(sin(x), assign_to="s") + 's = Math.sin(x);' + + Custom printing can be defined for certain types by passing a dictionary of + "type" : "function" to the ``user_functions`` kwarg. Alternatively, the + dictionary value can be a list of tuples i.e. [(argument_test, + js_function_string)]. + + >>> custom_functions = { + ... "ceiling": "CEIL", + ... "Abs": [(lambda x: not x.is_integer, "fabs"), + ... (lambda x: x.is_integer, "ABS")] + ... } + >>> jscode(Abs(x) + ceiling(x), user_functions=custom_functions) + 'fabs(x) + CEIL(x)' + + ``Piecewise`` expressions are converted into conditionals. If an + ``assign_to`` variable is provided an if statement is created, otherwise + the ternary operator is used. Note that if the ``Piecewise`` lacks a + default term, represented by ``(expr, True)`` then an error will be thrown. + This is to prevent generating an expression that may not evaluate to + anything. + + >>> from sympy import Piecewise + >>> expr = Piecewise((x + 1, x > 0), (x, True)) + >>> print(jscode(expr, tau)) + if (x > 0) { + tau = x + 1; + } + else { + tau = x; + } + + Support for loops is provided through ``Indexed`` types. With + ``contract=True`` these expressions will be turned into loops, whereas + ``contract=False`` will just print the assignment expression that should be + looped over: + + >>> from sympy import Eq, IndexedBase, Idx + >>> len_y = 5 + >>> y = IndexedBase('y', shape=(len_y,)) + >>> t = IndexedBase('t', shape=(len_y,)) + >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) + >>> i = Idx('i', len_y-1) + >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) + >>> jscode(e.rhs, assign_to=e.lhs, contract=False) + 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);' + + Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions + must be provided to ``assign_to``. Note that any expression that can be + generated normally can also exist inside a Matrix: + + >>> from sympy import Matrix, MatrixSymbol + >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)]) + >>> A = MatrixSymbol('A', 3, 1) + >>> print(jscode(mat, A)) + A[0] = Math.pow(x, 2); + if (x > 0) { + A[1] = x + 1; + } + else { + A[1] = x; + } + A[2] = Math.sin(x); + """ + + return JavascriptCodePrinter(settings).doprint(expr, assign_to) + + +def print_jscode(expr, **settings): + """Prints the Javascript representation of the given expression. + + See jscode for the meaning of the optional arguments. + """ + print(jscode(expr, **settings)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/julia.py b/venv/lib/python3.10/site-packages/sympy/printing/julia.py new file mode 100644 index 0000000000000000000000000000000000000000..3ab815add4d87fe953f646409e3a7bb383b1bbc6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/julia.py @@ -0,0 +1,652 @@ +""" +Julia code printer + +The `JuliaCodePrinter` converts SymPy expressions into Julia expressions. + +A complete code generator, which uses `julia_code` extensively, can be found +in `sympy.utilities.codegen`. The `codegen` module can be used to generate +complete source code files. + +""" + +from __future__ import annotations +from typing import Any + +from sympy.core import Mul, Pow, S, Rational +from sympy.core.mul import _keep_coeff +from sympy.core.numbers import equal_valued +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import precedence, PRECEDENCE +from re import search + +# List of known functions. First, those that have the same name in +# SymPy and Julia. This is almost certainly incomplete! +known_fcns_src1 = ["sin", "cos", "tan", "cot", "sec", "csc", + "asin", "acos", "atan", "acot", "asec", "acsc", + "sinh", "cosh", "tanh", "coth", "sech", "csch", + "asinh", "acosh", "atanh", "acoth", "asech", "acsch", + "atan2", "sign", "floor", "log", "exp", + "cbrt", "sqrt", "erf", "erfc", "erfi", + "factorial", "gamma", "digamma", "trigamma", + "polygamma", "beta", + "airyai", "airyaiprime", "airybi", "airybiprime", + "besselj", "bessely", "besseli", "besselk", + "erfinv", "erfcinv"] +# These functions have different names ("SymPy": "Julia"), more +# generally a mapping to (argument_conditions, julia_function). +known_fcns_src2 = { + "Abs": "abs", + "ceiling": "ceil", + "conjugate": "conj", + "hankel1": "hankelh1", + "hankel2": "hankelh2", + "im": "imag", + "re": "real" +} + + +class JuliaCodePrinter(CodePrinter): + """ + A printer to convert expressions to strings of Julia code. + """ + printmethod = "_julia" + language = "Julia" + + _operators = { + 'and': '&&', + 'or': '||', + 'not': '!', + } + + _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{ + 'precision': 17, + 'user_functions': {}, + 'contract': True, + 'inline': True, + }) + # Note: contract is for expressing tensors as loops (if True), or just + # assignment (if False). FIXME: this should be looked a more carefully + # for Julia. + + def __init__(self, settings={}): + super().__init__(settings) + self.known_functions = dict(zip(known_fcns_src1, known_fcns_src1)) + self.known_functions.update(dict(known_fcns_src2)) + userfuncs = settings.get('user_functions', {}) + self.known_functions.update(userfuncs) + + + def _rate_index_position(self, p): + return p*5 + + + def _get_statement(self, codestring): + return "%s" % codestring + + + def _get_comment(self, text): + return "# {}".format(text) + + + def _declare_number_const(self, name, value): + return "const {} = {}".format(name, value) + + + def _format_code(self, lines): + return self.indent_code(lines) + + + def _traverse_matrix_indices(self, mat): + # Julia uses Fortran order (column-major) + rows, cols = mat.shape + return ((i, j) for j in range(cols) for i in range(rows)) + + + def _get_loop_opening_ending(self, indices): + open_lines = [] + close_lines = [] + for i in indices: + # Julia arrays start at 1 and end at dimension + var, start, stop = map(self._print, + [i.label, i.lower + 1, i.upper + 1]) + open_lines.append("for %s = %s:%s" % (var, start, stop)) + close_lines.append("end") + return open_lines, close_lines + + + def _print_Mul(self, expr): + # print complex numbers nicely in Julia + if (expr.is_number and expr.is_imaginary and + expr.as_coeff_Mul()[0].is_integer): + return "%sim" % self._print(-S.ImaginaryUnit*expr) + + # cribbed from str.py + prec = precedence(expr) + + c, e = expr.as_coeff_Mul() + if c < 0: + expr = _keep_coeff(-c, e) + sign = "-" + else: + sign = "" + + a = [] # items in the numerator + b = [] # items that are in the denominator (if any) + + pow_paren = [] # Will collect all pow with more than one base element and exp = -1 + + if self.order not in ('old', 'none'): + args = expr.as_ordered_factors() + else: + # use make_args in case expr was something like -x -> x + args = Mul.make_args(expr) + + # Gather args for numerator/denominator + for item in args: + if (item.is_commutative and item.is_Pow and item.exp.is_Rational + and item.exp.is_negative): + if item.exp != -1: + b.append(Pow(item.base, -item.exp, evaluate=False)) + else: + if len(item.args[0].args) != 1 and isinstance(item.base, Mul): # To avoid situations like #14160 + pow_paren.append(item) + b.append(Pow(item.base, -item.exp)) + elif item.is_Rational and item is not S.Infinity and item.p == 1: + # Save the Rational type in julia Unless the numerator is 1. + # For example: + # julia_code(Rational(3, 7)*x) --> (3 // 7) * x + # julia_code(x/3) --> x / 3 but not x * (1 // 3) + b.append(Rational(item.q)) + else: + a.append(item) + + a = a or [S.One] + + a_str = [self.parenthesize(x, prec) for x in a] + b_str = [self.parenthesize(x, prec) for x in b] + + # To parenthesize Pow with exp = -1 and having more than one Symbol + for item in pow_paren: + if item.base in b: + b_str[b.index(item.base)] = "(%s)" % b_str[b.index(item.base)] + + # from here it differs from str.py to deal with "*" and ".*" + def multjoin(a, a_str): + # here we probably are assuming the constants will come first + r = a_str[0] + for i in range(1, len(a)): + mulsym = '*' if a[i-1].is_number else '.*' + r = "%s %s %s" % (r, mulsym, a_str[i]) + return r + + if not b: + return sign + multjoin(a, a_str) + elif len(b) == 1: + divsym = '/' if b[0].is_number else './' + return "%s %s %s" % (sign+multjoin(a, a_str), divsym, b_str[0]) + else: + divsym = '/' if all(bi.is_number for bi in b) else './' + return "%s %s (%s)" % (sign + multjoin(a, a_str), divsym, multjoin(b, b_str)) + + def _print_Relational(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + op = expr.rel_op + return "{} {} {}".format(lhs_code, op, rhs_code) + + def _print_Pow(self, expr): + powsymbol = '^' if all(x.is_number for x in expr.args) else '.^' + + PREC = precedence(expr) + + if equal_valued(expr.exp, 0.5): + return "sqrt(%s)" % self._print(expr.base) + + if expr.is_commutative: + if equal_valued(expr.exp, -0.5): + sym = '/' if expr.base.is_number else './' + return "1 %s sqrt(%s)" % (sym, self._print(expr.base)) + if equal_valued(expr.exp, -1): + sym = '/' if expr.base.is_number else './' + return "1 %s %s" % (sym, self.parenthesize(expr.base, PREC)) + + return '%s %s %s' % (self.parenthesize(expr.base, PREC), powsymbol, + self.parenthesize(expr.exp, PREC)) + + + def _print_MatPow(self, expr): + PREC = precedence(expr) + return '%s ^ %s' % (self.parenthesize(expr.base, PREC), + self.parenthesize(expr.exp, PREC)) + + + def _print_Pi(self, expr): + if self._settings["inline"]: + return "pi" + else: + return super()._print_NumberSymbol(expr) + + + def _print_ImaginaryUnit(self, expr): + return "im" + + + def _print_Exp1(self, expr): + if self._settings["inline"]: + return "e" + else: + return super()._print_NumberSymbol(expr) + + + def _print_EulerGamma(self, expr): + if self._settings["inline"]: + return "eulergamma" + else: + return super()._print_NumberSymbol(expr) + + + def _print_Catalan(self, expr): + if self._settings["inline"]: + return "catalan" + else: + return super()._print_NumberSymbol(expr) + + + def _print_GoldenRatio(self, expr): + if self._settings["inline"]: + return "golden" + else: + return super()._print_NumberSymbol(expr) + + + def _print_Assignment(self, expr): + from sympy.codegen.ast import Assignment + from sympy.functions.elementary.piecewise import Piecewise + from sympy.tensor.indexed import IndexedBase + # Copied from codeprinter, but remove special MatrixSymbol treatment + lhs = expr.lhs + rhs = expr.rhs + # We special case assignments that take multiple lines + if not self._settings["inline"] and isinstance(expr.rhs, Piecewise): + # Here we modify Piecewise so each expression is now + # an Assignment, and then continue on the print. + expressions = [] + conditions = [] + for (e, c) in rhs.args: + expressions.append(Assignment(lhs, e)) + conditions.append(c) + temp = Piecewise(*zip(expressions, conditions)) + return self._print(temp) + if self._settings["contract"] and (lhs.has(IndexedBase) or + rhs.has(IndexedBase)): + # Here we check if there is looping to be done, and if so + # print the required loops. + return self._doprint_loops(rhs, lhs) + else: + lhs_code = self._print(lhs) + rhs_code = self._print(rhs) + return self._get_statement("%s = %s" % (lhs_code, rhs_code)) + + + def _print_Infinity(self, expr): + return 'Inf' + + + def _print_NegativeInfinity(self, expr): + return '-Inf' + + + def _print_NaN(self, expr): + return 'NaN' + + + def _print_list(self, expr): + return 'Any[' + ', '.join(self._print(a) for a in expr) + ']' + + + def _print_tuple(self, expr): + if len(expr) == 1: + return "(%s,)" % self._print(expr[0]) + else: + return "(%s)" % self.stringify(expr, ", ") + _print_Tuple = _print_tuple + + + def _print_BooleanTrue(self, expr): + return "true" + + + def _print_BooleanFalse(self, expr): + return "false" + + + def _print_bool(self, expr): + return str(expr).lower() + + + # Could generate quadrature code for definite Integrals? + #_print_Integral = _print_not_supported + + + def _print_MatrixBase(self, A): + # Handle zero dimensions: + if S.Zero in A.shape: + return 'zeros(%s, %s)' % (A.rows, A.cols) + elif (A.rows, A.cols) == (1, 1): + return "[%s]" % A[0, 0] + elif A.rows == 1: + return "[%s]" % A.table(self, rowstart='', rowend='', colsep=' ') + elif A.cols == 1: + # note .table would unnecessarily equispace the rows + return "[%s]" % ", ".join([self._print(a) for a in A]) + return "[%s]" % A.table(self, rowstart='', rowend='', + rowsep=';\n', colsep=' ') + + + def _print_SparseRepMatrix(self, A): + from sympy.matrices import Matrix + L = A.col_list() + # make row vectors of the indices and entries + I = Matrix([k[0] + 1 for k in L]) + J = Matrix([k[1] + 1 for k in L]) + AIJ = Matrix([k[2] for k in L]) + return "sparse(%s, %s, %s, %s, %s)" % (self._print(I), self._print(J), + self._print(AIJ), A.rows, A.cols) + + + def _print_MatrixElement(self, expr): + return self.parenthesize(expr.parent, PRECEDENCE["Atom"], strict=True) \ + + '[%s,%s]' % (expr.i + 1, expr.j + 1) + + + def _print_MatrixSlice(self, expr): + def strslice(x, lim): + l = x[0] + 1 + h = x[1] + step = x[2] + lstr = self._print(l) + hstr = 'end' if h == lim else self._print(h) + if step == 1: + if l == 1 and h == lim: + return ':' + if l == h: + return lstr + else: + return lstr + ':' + hstr + else: + return ':'.join((lstr, self._print(step), hstr)) + return (self._print(expr.parent) + '[' + + strslice(expr.rowslice, expr.parent.shape[0]) + ',' + + strslice(expr.colslice, expr.parent.shape[1]) + ']') + + + def _print_Indexed(self, expr): + inds = [ self._print(i) for i in expr.indices ] + return "%s[%s]" % (self._print(expr.base.label), ",".join(inds)) + + def _print_Identity(self, expr): + return "eye(%s)" % self._print(expr.shape[0]) + + def _print_HadamardProduct(self, expr): + return ' .* '.join([self.parenthesize(arg, precedence(expr)) + for arg in expr.args]) + + def _print_HadamardPower(self, expr): + PREC = precedence(expr) + return '.**'.join([ + self.parenthesize(expr.base, PREC), + self.parenthesize(expr.exp, PREC) + ]) + + def _print_Rational(self, expr): + if expr.q == 1: + return str(expr.p) + return "%s // %s" % (expr.p, expr.q) + + # Note: as of 2022, Julia doesn't have spherical Bessel functions + def _print_jn(self, expr): + from sympy.functions import sqrt, besselj + x = expr.argument + expr2 = sqrt(S.Pi/(2*x))*besselj(expr.order + S.Half, x) + return self._print(expr2) + + + def _print_yn(self, expr): + from sympy.functions import sqrt, bessely + x = expr.argument + expr2 = sqrt(S.Pi/(2*x))*bessely(expr.order + S.Half, x) + return self._print(expr2) + + def _print_sinc(self, expr): + # Julia has the normalized sinc function + return "sinc({})".format(self._print(expr.args[0] / S.Pi)) + + def _print_Piecewise(self, expr): + if expr.args[-1].cond != True: + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + lines = [] + if self._settings["inline"]: + # Express each (cond, expr) pair in a nested Horner form: + # (condition) .* (expr) + (not cond) .* () + # Expressions that result in multiple statements won't work here. + ecpairs = ["({}) ? ({}) :".format + (self._print(c), self._print(e)) + for e, c in expr.args[:-1]] + elast = " (%s)" % self._print(expr.args[-1].expr) + pw = "\n".join(ecpairs) + elast + # Note: current need these outer brackets for 2*pw. Would be + # nicer to teach parenthesize() to do this for us when needed! + return "(" + pw + ")" + else: + for i, (e, c) in enumerate(expr.args): + if i == 0: + lines.append("if (%s)" % self._print(c)) + elif i == len(expr.args) - 1 and c == True: + lines.append("else") + else: + lines.append("elseif (%s)" % self._print(c)) + code0 = self._print(e) + lines.append(code0) + if i == len(expr.args) - 1: + lines.append("end") + return "\n".join(lines) + + def _print_MatMul(self, expr): + c, m = expr.as_coeff_mmul() + + sign = "" + if c.is_number: + re, im = c.as_real_imag() + if im.is_zero and re.is_negative: + expr = _keep_coeff(-c, m) + sign = "-" + elif re.is_zero and im.is_negative: + expr = _keep_coeff(-c, m) + sign = "-" + + return sign + ' * '.join( + (self.parenthesize(arg, precedence(expr)) for arg in expr.args) + ) + + + def indent_code(self, code): + """Accepts a string of code or a list of code lines""" + + # code mostly copied from ccode + if isinstance(code, str): + code_lines = self.indent_code(code.splitlines(True)) + return ''.join(code_lines) + + tab = " " + inc_regex = ('^function ', '^if ', '^elseif ', '^else$', '^for ') + dec_regex = ('^end$', '^elseif ', '^else$') + + # pre-strip left-space from the code + code = [ line.lstrip(' \t') for line in code ] + + increase = [ int(any(search(re, line) for re in inc_regex)) + for line in code ] + decrease = [ int(any(search(re, line) for re in dec_regex)) + for line in code ] + + pretty = [] + level = 0 + for n, line in enumerate(code): + if line in ('', '\n'): + pretty.append(line) + continue + level -= decrease[n] + pretty.append("%s%s" % (tab*level, line)) + level += increase[n] + return pretty + + +def julia_code(expr, assign_to=None, **settings): + r"""Converts `expr` to a string of Julia code. + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used as the name of the variable to which + the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol``, or ``Indexed`` type. This can be helpful for + expressions that generate multi-line statements. + precision : integer, optional + The precision for numbers such as pi [default=16]. + user_functions : dict, optional + A dictionary where keys are ``FunctionClass`` instances and values are + their string representations. Alternatively, the dictionary value can + be a list of tuples i.e. [(argument_test, cfunction_string)]. See + below for examples. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + inline: bool, optional + If True, we try to create single-statement code instead of multiple + statements. [default=True]. + + Examples + ======== + + >>> from sympy import julia_code, symbols, sin, pi + >>> x = symbols('x') + >>> julia_code(sin(x).series(x).removeO()) + 'x .^ 5 / 120 - x .^ 3 / 6 + x' + + >>> from sympy import Rational, ceiling + >>> x, y, tau = symbols("x, y, tau") + >>> julia_code((2*tau)**Rational(7, 2)) + '8 * sqrt(2) * tau .^ (7 // 2)' + + Note that element-wise (Hadamard) operations are used by default between + symbols. This is because its possible in Julia to write "vectorized" + code. It is harmless if the values are scalars. + + >>> julia_code(sin(pi*x*y), assign_to="s") + 's = sin(pi * x .* y)' + + If you need a matrix product "*" or matrix power "^", you can specify the + symbol as a ``MatrixSymbol``. + + >>> from sympy import Symbol, MatrixSymbol + >>> n = Symbol('n', integer=True, positive=True) + >>> A = MatrixSymbol('A', n, n) + >>> julia_code(3*pi*A**3) + '(3 * pi) * A ^ 3' + + This class uses several rules to decide which symbol to use a product. + Pure numbers use "*", Symbols use ".*" and MatrixSymbols use "*". + A HadamardProduct can be used to specify componentwise multiplication ".*" + of two MatrixSymbols. There is currently there is no easy way to specify + scalar symbols, so sometimes the code might have some minor cosmetic + issues. For example, suppose x and y are scalars and A is a Matrix, then + while a human programmer might write "(x^2*y)*A^3", we generate: + + >>> julia_code(x**2*y*A**3) + '(x .^ 2 .* y) * A ^ 3' + + Matrices are supported using Julia inline notation. When using + ``assign_to`` with matrices, the name can be specified either as a string + or as a ``MatrixSymbol``. The dimensions must align in the latter case. + + >>> from sympy import Matrix, MatrixSymbol + >>> mat = Matrix([[x**2, sin(x), ceiling(x)]]) + >>> julia_code(mat, assign_to='A') + 'A = [x .^ 2 sin(x) ceil(x)]' + + ``Piecewise`` expressions are implemented with logical masking by default. + Alternatively, you can pass "inline=False" to use if-else conditionals. + Note that if the ``Piecewise`` lacks a default term, represented by + ``(expr, True)`` then an error will be thrown. This is to prevent + generating an expression that may not evaluate to anything. + + >>> from sympy import Piecewise + >>> pw = Piecewise((x + 1, x > 0), (x, True)) + >>> julia_code(pw, assign_to=tau) + 'tau = ((x > 0) ? (x + 1) : (x))' + + Note that any expression that can be generated normally can also exist + inside a Matrix: + + >>> mat = Matrix([[x**2, pw, sin(x)]]) + >>> julia_code(mat, assign_to='A') + 'A = [x .^ 2 ((x > 0) ? (x + 1) : (x)) sin(x)]' + + Custom printing can be defined for certain types by passing a dictionary of + "type" : "function" to the ``user_functions`` kwarg. Alternatively, the + dictionary value can be a list of tuples i.e., [(argument_test, + cfunction_string)]. This can be used to call a custom Julia function. + + >>> from sympy import Function + >>> f = Function('f') + >>> g = Function('g') + >>> custom_functions = { + ... "f": "existing_julia_fcn", + ... "g": [(lambda x: x.is_Matrix, "my_mat_fcn"), + ... (lambda x: not x.is_Matrix, "my_fcn")] + ... } + >>> mat = Matrix([[1, x]]) + >>> julia_code(f(x) + g(x) + g(mat), user_functions=custom_functions) + 'existing_julia_fcn(x) + my_fcn(x) + my_mat_fcn([1 x])' + + Support for loops is provided through ``Indexed`` types. With + ``contract=True`` these expressions will be turned into loops, whereas + ``contract=False`` will just print the assignment expression that should be + looped over: + + >>> from sympy import Eq, IndexedBase, Idx + >>> len_y = 5 + >>> y = IndexedBase('y', shape=(len_y,)) + >>> t = IndexedBase('t', shape=(len_y,)) + >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) + >>> i = Idx('i', len_y-1) + >>> e = Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) + >>> julia_code(e.rhs, assign_to=e.lhs, contract=False) + 'Dy[i] = (y[i + 1] - y[i]) ./ (t[i + 1] - t[i])' + """ + return JuliaCodePrinter(settings).doprint(expr, assign_to) + + +def print_julia_code(expr, **settings): + """Prints the Julia representation of the given expression. + + See `julia_code` for the meaning of the optional arguments. + """ + print(julia_code(expr, **settings)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/lambdarepr.py b/venv/lib/python3.10/site-packages/sympy/printing/lambdarepr.py new file mode 100644 index 0000000000000000000000000000000000000000..87fa0988d138d54d68ab8aef1bbc0f27b243b472 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/lambdarepr.py @@ -0,0 +1,251 @@ +from .pycode import ( + PythonCodePrinter, + MpmathPrinter, +) +from .numpy import NumPyPrinter # NumPyPrinter is imported for backward compatibility +from sympy.core.sorting import default_sort_key + + +__all__ = [ + 'PythonCodePrinter', + 'MpmathPrinter', # MpmathPrinter is published for backward compatibility + 'NumPyPrinter', + 'LambdaPrinter', + 'NumPyPrinter', + 'IntervalPrinter', + 'lambdarepr', +] + + +class LambdaPrinter(PythonCodePrinter): + """ + This printer converts expressions into strings that can be used by + lambdify. + """ + printmethod = "_lambdacode" + + + def _print_And(self, expr): + result = ['('] + for arg in sorted(expr.args, key=default_sort_key): + result.extend(['(', self._print(arg), ')']) + result.append(' and ') + result = result[:-1] + result.append(')') + return ''.join(result) + + def _print_Or(self, expr): + result = ['('] + for arg in sorted(expr.args, key=default_sort_key): + result.extend(['(', self._print(arg), ')']) + result.append(' or ') + result = result[:-1] + result.append(')') + return ''.join(result) + + def _print_Not(self, expr): + result = ['(', 'not (', self._print(expr.args[0]), '))'] + return ''.join(result) + + def _print_BooleanTrue(self, expr): + return "True" + + def _print_BooleanFalse(self, expr): + return "False" + + def _print_ITE(self, expr): + result = [ + '((', self._print(expr.args[1]), + ') if (', self._print(expr.args[0]), + ') else (', self._print(expr.args[2]), '))' + ] + return ''.join(result) + + def _print_NumberSymbol(self, expr): + return str(expr) + + def _print_Pow(self, expr, **kwargs): + # XXX Temporary workaround. Should Python math printer be + # isolated from PythonCodePrinter? + return super(PythonCodePrinter, self)._print_Pow(expr, **kwargs) + + +# numexpr works by altering the string passed to numexpr.evaluate +# rather than by populating a namespace. Thus a special printer... +class NumExprPrinter(LambdaPrinter): + # key, value pairs correspond to SymPy name and numexpr name + # functions not appearing in this dict will raise a TypeError + printmethod = "_numexprcode" + + _numexpr_functions = { + 'sin' : 'sin', + 'cos' : 'cos', + 'tan' : 'tan', + 'asin': 'arcsin', + 'acos': 'arccos', + 'atan': 'arctan', + 'atan2' : 'arctan2', + 'sinh' : 'sinh', + 'cosh' : 'cosh', + 'tanh' : 'tanh', + 'asinh': 'arcsinh', + 'acosh': 'arccosh', + 'atanh': 'arctanh', + 'ln' : 'log', + 'log': 'log', + 'exp': 'exp', + 'sqrt' : 'sqrt', + 'Abs' : 'abs', + 'conjugate' : 'conj', + 'im' : 'imag', + 're' : 'real', + 'where' : 'where', + 'complex' : 'complex', + 'contains' : 'contains', + } + + module = 'numexpr' + + def _print_ImaginaryUnit(self, expr): + return '1j' + + def _print_seq(self, seq, delimiter=', '): + # simplified _print_seq taken from pretty.py + s = [self._print(item) for item in seq] + if s: + return delimiter.join(s) + else: + return "" + + def _print_Function(self, e): + func_name = e.func.__name__ + + nstr = self._numexpr_functions.get(func_name, None) + if nstr is None: + # check for implemented_function + if hasattr(e, '_imp_'): + return "(%s)" % self._print(e._imp_(*e.args)) + else: + raise TypeError("numexpr does not support function '%s'" % + func_name) + return "%s(%s)" % (nstr, self._print_seq(e.args)) + + def _print_Piecewise(self, expr): + "Piecewise function printer" + exprs = [self._print(arg.expr) for arg in expr.args] + conds = [self._print(arg.cond) for arg in expr.args] + # If [default_value, True] is a (expr, cond) sequence in a Piecewise object + # it will behave the same as passing the 'default' kwarg to select() + # *as long as* it is the last element in expr.args. + # If this is not the case, it may be triggered prematurely. + ans = [] + parenthesis_count = 0 + is_last_cond_True = False + for cond, expr in zip(conds, exprs): + if cond == 'True': + ans.append(expr) + is_last_cond_True = True + break + else: + ans.append('where(%s, %s, ' % (cond, expr)) + parenthesis_count += 1 + if not is_last_cond_True: + # See https://github.com/pydata/numexpr/issues/298 + # + # simplest way to put a nan but raises + # 'RuntimeWarning: invalid value encountered in log' + # + # There are other ways to do this such as + # + # >>> import numexpr as ne + # >>> nan = float('nan') + # >>> ne.evaluate('where(x < 0, -1, nan)', {'x': [-1, 2, 3], 'nan':nan}) + # array([-1., nan, nan]) + # + # That needs to be handled in the lambdified function though rather + # than here in the printer. + ans.append('log(-1)') + return ''.join(ans) + ')' * parenthesis_count + + def _print_ITE(self, expr): + from sympy.functions.elementary.piecewise import Piecewise + return self._print(expr.rewrite(Piecewise)) + + def blacklisted(self, expr): + raise TypeError("numexpr cannot be used with %s" % + expr.__class__.__name__) + + # blacklist all Matrix printing + _print_SparseRepMatrix = \ + _print_MutableSparseMatrix = \ + _print_ImmutableSparseMatrix = \ + _print_Matrix = \ + _print_DenseMatrix = \ + _print_MutableDenseMatrix = \ + _print_ImmutableMatrix = \ + _print_ImmutableDenseMatrix = \ + blacklisted + # blacklist some Python expressions + _print_list = \ + _print_tuple = \ + _print_Tuple = \ + _print_dict = \ + _print_Dict = \ + blacklisted + + def _print_NumExprEvaluate(self, expr): + evaluate = self._module_format(self.module +".evaluate") + return "%s('%s', truediv=True)" % (evaluate, self._print(expr.expr)) + + def doprint(self, expr): + from sympy.codegen.ast import CodegenAST + from sympy.codegen.pynodes import NumExprEvaluate + if not isinstance(expr, CodegenAST): + expr = NumExprEvaluate(expr) + return super().doprint(expr) + + def _print_Return(self, expr): + from sympy.codegen.pynodes import NumExprEvaluate + r, = expr.args + if not isinstance(r, NumExprEvaluate): + expr = expr.func(NumExprEvaluate(r)) + return super()._print_Return(expr) + + def _print_Assignment(self, expr): + from sympy.codegen.pynodes import NumExprEvaluate + lhs, rhs, *args = expr.args + if not isinstance(rhs, NumExprEvaluate): + expr = expr.func(lhs, NumExprEvaluate(rhs), *args) + return super()._print_Assignment(expr) + + def _print_CodeBlock(self, expr): + from sympy.codegen.ast import CodegenAST + from sympy.codegen.pynodes import NumExprEvaluate + args = [ arg if isinstance(arg, CodegenAST) else NumExprEvaluate(arg) for arg in expr.args ] + return super()._print_CodeBlock(self, expr.func(*args)) + + +class IntervalPrinter(MpmathPrinter, LambdaPrinter): + """Use ``lambda`` printer but print numbers as ``mpi`` intervals. """ + + def _print_Integer(self, expr): + return "mpi('%s')" % super(PythonCodePrinter, self)._print_Integer(expr) + + def _print_Rational(self, expr): + return "mpi('%s')" % super(PythonCodePrinter, self)._print_Rational(expr) + + def _print_Half(self, expr): + return "mpi('%s')" % super(PythonCodePrinter, self)._print_Rational(expr) + + def _print_Pow(self, expr): + return super(MpmathPrinter, self)._print_Pow(expr, rational=True) + + +for k in NumExprPrinter._numexpr_functions: + setattr(NumExprPrinter, '_print_%s' % k, NumExprPrinter._print_Function) + +def lambdarepr(expr, **settings): + """ + Returns a string usable for lambdifying. + """ + return LambdaPrinter(settings).doprint(expr) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/latex.py b/venv/lib/python3.10/site-packages/sympy/printing/latex.py new file mode 100644 index 0000000000000000000000000000000000000000..724df719d560e001deb175649a3769703bdf5ca5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/latex.py @@ -0,0 +1,3318 @@ +""" +A Printer which converts an expression into its LaTeX equivalent. +""" +from __future__ import annotations +from typing import Any, Callable, TYPE_CHECKING + +import itertools + +from sympy.core import Add, Float, Mod, Mul, Number, S, Symbol, Expr +from sympy.core.alphabets import greeks +from sympy.core.containers import Tuple +from sympy.core.function import Function, AppliedUndef, Derivative +from sympy.core.operations import AssocOp +from sympy.core.power import Pow +from sympy.core.sorting import default_sort_key +from sympy.core.sympify import SympifyError +from sympy.logic.boolalg import true, BooleanTrue, BooleanFalse + + +# sympy.printing imports +from sympy.printing.precedence import precedence_traditional +from sympy.printing.printer import Printer, print_function +from sympy.printing.conventions import split_super_sub, requires_partial +from sympy.printing.precedence import precedence, PRECEDENCE + +from mpmath.libmp.libmpf import prec_to_dps, to_str as mlib_to_str + +from sympy.utilities.iterables import has_variety, sift + +import re + +if TYPE_CHECKING: + from sympy.tensor.array import NDimArray + from sympy.vector.basisdependent import BasisDependent + +# Hand-picked functions which can be used directly in both LaTeX and MathJax +# Complete list at +# https://docs.mathjax.org/en/latest/tex.html#supported-latex-commands +# This variable only contains those functions which SymPy uses. +accepted_latex_functions = ['arcsin', 'arccos', 'arctan', 'sin', 'cos', 'tan', + 'sinh', 'cosh', 'tanh', 'sqrt', 'ln', 'log', 'sec', + 'csc', 'cot', 'coth', 're', 'im', 'frac', 'root', + 'arg', + ] + +tex_greek_dictionary = { + 'Alpha': r'\mathrm{A}', + 'Beta': r'\mathrm{B}', + 'Gamma': r'\Gamma', + 'Delta': r'\Delta', + 'Epsilon': r'\mathrm{E}', + 'Zeta': r'\mathrm{Z}', + 'Eta': r'\mathrm{H}', + 'Theta': r'\Theta', + 'Iota': r'\mathrm{I}', + 'Kappa': r'\mathrm{K}', + 'Lambda': r'\Lambda', + 'Mu': r'\mathrm{M}', + 'Nu': r'\mathrm{N}', + 'Xi': r'\Xi', + 'omicron': 'o', + 'Omicron': r'\mathrm{O}', + 'Pi': r'\Pi', + 'Rho': r'\mathrm{P}', + 'Sigma': r'\Sigma', + 'Tau': r'\mathrm{T}', + 'Upsilon': r'\Upsilon', + 'Phi': r'\Phi', + 'Chi': r'\mathrm{X}', + 'Psi': r'\Psi', + 'Omega': r'\Omega', + 'lamda': r'\lambda', + 'Lamda': r'\Lambda', + 'khi': r'\chi', + 'Khi': r'\mathrm{X}', + 'varepsilon': r'\varepsilon', + 'varkappa': r'\varkappa', + 'varphi': r'\varphi', + 'varpi': r'\varpi', + 'varrho': r'\varrho', + 'varsigma': r'\varsigma', + 'vartheta': r'\vartheta', +} + +other_symbols = {'aleph', 'beth', 'daleth', 'gimel', 'ell', 'eth', 'hbar', + 'hslash', 'mho', 'wp'} + +# Variable name modifiers +modifier_dict: dict[str, Callable[[str], str]] = { + # Accents + 'mathring': lambda s: r'\mathring{'+s+r'}', + 'ddddot': lambda s: r'\ddddot{'+s+r'}', + 'dddot': lambda s: r'\dddot{'+s+r'}', + 'ddot': lambda s: r'\ddot{'+s+r'}', + 'dot': lambda s: r'\dot{'+s+r'}', + 'check': lambda s: r'\check{'+s+r'}', + 'breve': lambda s: r'\breve{'+s+r'}', + 'acute': lambda s: r'\acute{'+s+r'}', + 'grave': lambda s: r'\grave{'+s+r'}', + 'tilde': lambda s: r'\tilde{'+s+r'}', + 'hat': lambda s: r'\hat{'+s+r'}', + 'bar': lambda s: r'\bar{'+s+r'}', + 'vec': lambda s: r'\vec{'+s+r'}', + 'prime': lambda s: "{"+s+"}'", + 'prm': lambda s: "{"+s+"}'", + # Faces + 'bold': lambda s: r'\boldsymbol{'+s+r'}', + 'bm': lambda s: r'\boldsymbol{'+s+r'}', + 'cal': lambda s: r'\mathcal{'+s+r'}', + 'scr': lambda s: r'\mathscr{'+s+r'}', + 'frak': lambda s: r'\mathfrak{'+s+r'}', + # Brackets + 'norm': lambda s: r'\left\|{'+s+r'}\right\|', + 'avg': lambda s: r'\left\langle{'+s+r'}\right\rangle', + 'abs': lambda s: r'\left|{'+s+r'}\right|', + 'mag': lambda s: r'\left|{'+s+r'}\right|', +} + +greek_letters_set = frozenset(greeks) + +_between_two_numbers_p = ( + re.compile(r'[0-9][} ]*$'), # search + re.compile(r'(\d|\\frac{\d+}{\d+})'), # match +) + + +def latex_escape(s: str) -> str: + """ + Escape a string such that latex interprets it as plaintext. + + We cannot use verbatim easily with mathjax, so escaping is easier. + Rules from https://tex.stackexchange.com/a/34586/41112. + """ + s = s.replace('\\', r'\textbackslash') + for c in '&%$#_{}': + s = s.replace(c, '\\' + c) + s = s.replace('~', r'\textasciitilde') + s = s.replace('^', r'\textasciicircum') + return s + + +class LatexPrinter(Printer): + printmethod = "_latex" + + _default_settings: dict[str, Any] = { + "full_prec": False, + "fold_frac_powers": False, + "fold_func_brackets": False, + "fold_short_frac": None, + "inv_trig_style": "abbreviated", + "itex": False, + "ln_notation": False, + "long_frac_ratio": None, + "mat_delim": "[", + "mat_str": None, + "mode": "plain", + "mul_symbol": None, + "order": None, + "symbol_names": {}, + "root_notation": True, + "mat_symbol_style": "plain", + "imaginary_unit": "i", + "gothic_re_im": False, + "decimal_separator": "period", + "perm_cyclic": True, + "parenthesize_super": True, + "min": None, + "max": None, + "diff_operator": "d", + "adjoint_style": "dagger", + "disable_split_super_sub": False, + } + + def __init__(self, settings=None): + Printer.__init__(self, settings) + + if 'mode' in self._settings: + valid_modes = ['inline', 'plain', 'equation', + 'equation*'] + if self._settings['mode'] not in valid_modes: + raise ValueError("'mode' must be one of 'inline', 'plain', " + "'equation' or 'equation*'") + + if self._settings['fold_short_frac'] is None and \ + self._settings['mode'] == 'inline': + self._settings['fold_short_frac'] = True + + mul_symbol_table = { + None: r" ", + "ldot": r" \,.\, ", + "dot": r" \cdot ", + "times": r" \times " + } + try: + self._settings['mul_symbol_latex'] = \ + mul_symbol_table[self._settings['mul_symbol']] + except KeyError: + self._settings['mul_symbol_latex'] = \ + self._settings['mul_symbol'] + try: + self._settings['mul_symbol_latex_numbers'] = \ + mul_symbol_table[self._settings['mul_symbol'] or 'dot'] + except KeyError: + if (self._settings['mul_symbol'].strip() in + ['', ' ', '\\', '\\,', '\\:', '\\;', '\\quad']): + self._settings['mul_symbol_latex_numbers'] = \ + mul_symbol_table['dot'] + else: + self._settings['mul_symbol_latex_numbers'] = \ + self._settings['mul_symbol'] + + self._delim_dict = {'(': ')', '[': ']'} + + imaginary_unit_table = { + None: r"i", + "i": r"i", + "ri": r"\mathrm{i}", + "ti": r"\text{i}", + "j": r"j", + "rj": r"\mathrm{j}", + "tj": r"\text{j}", + } + imag_unit = self._settings['imaginary_unit'] + self._settings['imaginary_unit_latex'] = imaginary_unit_table.get(imag_unit, imag_unit) + + diff_operator_table = { + None: r"d", + "d": r"d", + "rd": r"\mathrm{d}", + "td": r"\text{d}", + } + diff_operator = self._settings['diff_operator'] + self._settings["diff_operator_latex"] = diff_operator_table.get(diff_operator, diff_operator) + + def _add_parens(self, s) -> str: + return r"\left({}\right)".format(s) + + # TODO: merge this with the above, which requires a lot of test changes + def _add_parens_lspace(self, s) -> str: + return r"\left( {}\right)".format(s) + + def parenthesize(self, item, level, is_neg=False, strict=False) -> str: + prec_val = precedence_traditional(item) + if is_neg and strict: + return self._add_parens(self._print(item)) + + if (prec_val < level) or ((not strict) and prec_val <= level): + return self._add_parens(self._print(item)) + else: + return self._print(item) + + def parenthesize_super(self, s): + """ + Protect superscripts in s + + If the parenthesize_super option is set, protect with parentheses, else + wrap in braces. + """ + if "^" in s: + if self._settings['parenthesize_super']: + return self._add_parens(s) + else: + return "{{{}}}".format(s) + return s + + def doprint(self, expr) -> str: + tex = Printer.doprint(self, expr) + + if self._settings['mode'] == 'plain': + return tex + elif self._settings['mode'] == 'inline': + return r"$%s$" % tex + elif self._settings['itex']: + return r"$$%s$$" % tex + else: + env_str = self._settings['mode'] + return r"\begin{%s}%s\end{%s}" % (env_str, tex, env_str) + + def _needs_brackets(self, expr) -> bool: + """ + Returns True if the expression needs to be wrapped in brackets when + printed, False otherwise. For example: a + b => True; a => False; + 10 => False; -10 => True. + """ + return not ((expr.is_Integer and expr.is_nonnegative) + or (expr.is_Atom and (expr is not S.NegativeOne + and expr.is_Rational is False))) + + def _needs_function_brackets(self, expr) -> bool: + """ + Returns True if the expression needs to be wrapped in brackets when + passed as an argument to a function, False otherwise. This is a more + liberal version of _needs_brackets, in that many expressions which need + to be wrapped in brackets when added/subtracted/raised to a power do + not need them when passed to a function. Such an example is a*b. + """ + if not self._needs_brackets(expr): + return False + else: + # Muls of the form a*b*c... can be folded + if expr.is_Mul and not self._mul_is_clean(expr): + return True + # Pows which don't need brackets can be folded + elif expr.is_Pow and not self._pow_is_clean(expr): + return True + # Add and Function always need brackets + elif expr.is_Add or expr.is_Function: + return True + else: + return False + + def _needs_mul_brackets(self, expr, first=False, last=False) -> bool: + """ + Returns True if the expression needs to be wrapped in brackets when + printed as part of a Mul, False otherwise. This is True for Add, + but also for some container objects that would not need brackets + when appearing last in a Mul, e.g. an Integral. ``last=True`` + specifies that this expr is the last to appear in a Mul. + ``first=True`` specifies that this expr is the first to appear in + a Mul. + """ + from sympy.concrete.products import Product + from sympy.concrete.summations import Sum + from sympy.integrals.integrals import Integral + + if expr.is_Mul: + if not first and expr.could_extract_minus_sign(): + return True + elif precedence_traditional(expr) < PRECEDENCE["Mul"]: + return True + elif expr.is_Relational: + return True + if expr.is_Piecewise: + return True + if any(expr.has(x) for x in (Mod,)): + return True + if (not last and + any(expr.has(x) for x in (Integral, Product, Sum))): + return True + + return False + + def _needs_add_brackets(self, expr) -> bool: + """ + Returns True if the expression needs to be wrapped in brackets when + printed as part of an Add, False otherwise. This is False for most + things. + """ + if expr.is_Relational: + return True + if any(expr.has(x) for x in (Mod,)): + return True + if expr.is_Add: + return True + return False + + def _mul_is_clean(self, expr) -> bool: + for arg in expr.args: + if arg.is_Function: + return False + return True + + def _pow_is_clean(self, expr) -> bool: + return not self._needs_brackets(expr.base) + + def _do_exponent(self, expr: str, exp): + if exp is not None: + return r"\left(%s\right)^{%s}" % (expr, exp) + else: + return expr + + def _print_Basic(self, expr): + name = self._deal_with_super_sub(expr.__class__.__name__) + if expr.args: + ls = [self._print(o) for o in expr.args] + s = r"\operatorname{{{}}}\left({}\right)" + return s.format(name, ", ".join(ls)) + else: + return r"\text{{{}}}".format(name) + + def _print_bool(self, e: bool | BooleanTrue | BooleanFalse): + return r"\text{%s}" % e + + _print_BooleanTrue = _print_bool + _print_BooleanFalse = _print_bool + + def _print_NoneType(self, e): + return r"\text{%s}" % e + + def _print_Add(self, expr, order=None): + terms = self._as_ordered_terms(expr, order=order) + + tex = "" + for i, term in enumerate(terms): + if i == 0: + pass + elif term.could_extract_minus_sign(): + tex += " - " + term = -term + else: + tex += " + " + term_tex = self._print(term) + if self._needs_add_brackets(term): + term_tex = r"\left(%s\right)" % term_tex + tex += term_tex + + return tex + + def _print_Cycle(self, expr): + from sympy.combinatorics.permutations import Permutation + if expr.size == 0: + return r"\left( \right)" + expr = Permutation(expr) + expr_perm = expr.cyclic_form + siz = expr.size + if expr.array_form[-1] == siz - 1: + expr_perm = expr_perm + [[siz - 1]] + term_tex = '' + for i in expr_perm: + term_tex += str(i).replace(',', r"\;") + term_tex = term_tex.replace('[', r"\left( ") + term_tex = term_tex.replace(']', r"\right)") + return term_tex + + def _print_Permutation(self, expr): + from sympy.combinatorics.permutations import Permutation + from sympy.utilities.exceptions import sympy_deprecation_warning + + perm_cyclic = Permutation.print_cyclic + if perm_cyclic is not None: + sympy_deprecation_warning( + f""" + Setting Permutation.print_cyclic is deprecated. Instead use + init_printing(perm_cyclic={perm_cyclic}). + """, + deprecated_since_version="1.6", + active_deprecations_target="deprecated-permutation-print_cyclic", + stacklevel=8, + ) + else: + perm_cyclic = self._settings.get("perm_cyclic", True) + + if perm_cyclic: + return self._print_Cycle(expr) + + if expr.size == 0: + return r"\left( \right)" + + lower = [self._print(arg) for arg in expr.array_form] + upper = [self._print(arg) for arg in range(len(lower))] + + row1 = " & ".join(upper) + row2 = " & ".join(lower) + mat = r" \\ ".join((row1, row2)) + return r"\begin{pmatrix} %s \end{pmatrix}" % mat + + + def _print_AppliedPermutation(self, expr): + perm, var = expr.args + return r"\sigma_{%s}(%s)" % (self._print(perm), self._print(var)) + + def _print_Float(self, expr): + # Based off of that in StrPrinter + dps = prec_to_dps(expr._prec) + strip = False if self._settings['full_prec'] else True + low = self._settings["min"] if "min" in self._settings else None + high = self._settings["max"] if "max" in self._settings else None + str_real = mlib_to_str(expr._mpf_, dps, strip_zeros=strip, min_fixed=low, max_fixed=high) + + # Must always have a mul symbol (as 2.5 10^{20} just looks odd) + # thus we use the number separator + separator = self._settings['mul_symbol_latex_numbers'] + + if 'e' in str_real: + (mant, exp) = str_real.split('e') + + if exp[0] == '+': + exp = exp[1:] + if self._settings['decimal_separator'] == 'comma': + mant = mant.replace('.','{,}') + + return r"%s%s10^{%s}" % (mant, separator, exp) + elif str_real == "+inf": + return r"\infty" + elif str_real == "-inf": + return r"- \infty" + else: + if self._settings['decimal_separator'] == 'comma': + str_real = str_real.replace('.','{,}') + return str_real + + def _print_Cross(self, expr): + vec1 = expr._expr1 + vec2 = expr._expr2 + return r"%s \times %s" % (self.parenthesize(vec1, PRECEDENCE['Mul']), + self.parenthesize(vec2, PRECEDENCE['Mul'])) + + def _print_Curl(self, expr): + vec = expr._expr + return r"\nabla\times %s" % self.parenthesize(vec, PRECEDENCE['Mul']) + + def _print_Divergence(self, expr): + vec = expr._expr + return r"\nabla\cdot %s" % self.parenthesize(vec, PRECEDENCE['Mul']) + + def _print_Dot(self, expr): + vec1 = expr._expr1 + vec2 = expr._expr2 + return r"%s \cdot %s" % (self.parenthesize(vec1, PRECEDENCE['Mul']), + self.parenthesize(vec2, PRECEDENCE['Mul'])) + + def _print_Gradient(self, expr): + func = expr._expr + return r"\nabla %s" % self.parenthesize(func, PRECEDENCE['Mul']) + + def _print_Laplacian(self, expr): + func = expr._expr + return r"\Delta %s" % self.parenthesize(func, PRECEDENCE['Mul']) + + def _print_Mul(self, expr: Expr): + from sympy.simplify import fraction + separator: str = self._settings['mul_symbol_latex'] + numbersep: str = self._settings['mul_symbol_latex_numbers'] + + def convert(expr) -> str: + if not expr.is_Mul: + return str(self._print(expr)) + else: + if self.order not in ('old', 'none'): + args = expr.as_ordered_factors() + else: + args = list(expr.args) + + # If there are quantities or prefixes, append them at the back. + units, nonunits = sift(args, lambda x: (hasattr(x, "_scale_factor") or hasattr(x, "is_physical_constant")) or + (isinstance(x, Pow) and + hasattr(x.base, "is_physical_constant")), binary=True) + prefixes, units = sift(units, lambda x: hasattr(x, "_scale_factor"), binary=True) + return convert_args(nonunits + prefixes + units) + + def convert_args(args) -> str: + _tex = last_term_tex = "" + + for i, term in enumerate(args): + term_tex = self._print(term) + if not (hasattr(term, "_scale_factor") or hasattr(term, "is_physical_constant")): + if self._needs_mul_brackets(term, first=(i == 0), + last=(i == len(args) - 1)): + term_tex = r"\left(%s\right)" % term_tex + + if _between_two_numbers_p[0].search(last_term_tex) and \ + _between_two_numbers_p[1].match(term_tex): + # between two numbers + _tex += numbersep + elif _tex: + _tex += separator + elif _tex: + _tex += separator + + _tex += term_tex + last_term_tex = term_tex + return _tex + + # Check for unevaluated Mul. In this case we need to make sure the + # identities are visible, multiple Rational factors are not combined + # etc so we display in a straight-forward form that fully preserves all + # args and their order. + # XXX: _print_Pow calls this routine with instances of Pow... + if isinstance(expr, Mul): + args = expr.args + if args[0] is S.One or any(isinstance(arg, Number) for arg in args[1:]): + return convert_args(args) + + include_parens = False + if expr.could_extract_minus_sign(): + expr = -expr + tex = "- " + if expr.is_Add: + tex += "(" + include_parens = True + else: + tex = "" + + numer, denom = fraction(expr, exact=True) + + if denom is S.One and Pow(1, -1, evaluate=False) not in expr.args: + # use the original expression here, since fraction() may have + # altered it when producing numer and denom + tex += convert(expr) + + else: + snumer = convert(numer) + sdenom = convert(denom) + ldenom = len(sdenom.split()) + ratio = self._settings['long_frac_ratio'] + if self._settings['fold_short_frac'] and ldenom <= 2 and \ + "^" not in sdenom: + # handle short fractions + if self._needs_mul_brackets(numer, last=False): + tex += r"\left(%s\right) / %s" % (snumer, sdenom) + else: + tex += r"%s / %s" % (snumer, sdenom) + elif ratio is not None and \ + len(snumer.split()) > ratio*ldenom: + # handle long fractions + if self._needs_mul_brackets(numer, last=True): + tex += r"\frac{1}{%s}%s\left(%s\right)" \ + % (sdenom, separator, snumer) + elif numer.is_Mul: + # split a long numerator + a = S.One + b = S.One + for x in numer.args: + if self._needs_mul_brackets(x, last=False) or \ + len(convert(a*x).split()) > ratio*ldenom or \ + (b.is_commutative is x.is_commutative is False): + b *= x + else: + a *= x + if self._needs_mul_brackets(b, last=True): + tex += r"\frac{%s}{%s}%s\left(%s\right)" \ + % (convert(a), sdenom, separator, convert(b)) + else: + tex += r"\frac{%s}{%s}%s%s" \ + % (convert(a), sdenom, separator, convert(b)) + else: + tex += r"\frac{1}{%s}%s%s" % (sdenom, separator, snumer) + else: + tex += r"\frac{%s}{%s}" % (snumer, sdenom) + + if include_parens: + tex += ")" + return tex + + def _print_AlgebraicNumber(self, expr): + if expr.is_aliased: + return self._print(expr.as_poly().as_expr()) + else: + return self._print(expr.as_expr()) + + def _print_PrimeIdeal(self, expr): + p = self._print(expr.p) + if expr.is_inert: + return rf'\left({p}\right)' + alpha = self._print(expr.alpha.as_expr()) + return rf'\left({p}, {alpha}\right)' + + def _print_Pow(self, expr: Pow): + # Treat x**Rational(1,n) as special case + if expr.exp.is_Rational: + p: int = expr.exp.p # type: ignore + q: int = expr.exp.q # type: ignore + if abs(p) == 1 and q != 1 and self._settings['root_notation']: + base = self._print(expr.base) + if q == 2: + tex = r"\sqrt{%s}" % base + elif self._settings['itex']: + tex = r"\root{%d}{%s}" % (q, base) + else: + tex = r"\sqrt[%d]{%s}" % (q, base) + if expr.exp.is_negative: + return r"\frac{1}{%s}" % tex + else: + return tex + elif self._settings['fold_frac_powers'] and q != 1: + base = self.parenthesize(expr.base, PRECEDENCE['Pow']) + # issue #12886: add parentheses for superscripts raised to powers + if expr.base.is_Symbol: + base = self.parenthesize_super(base) + if expr.base.is_Function: + return self._print(expr.base, exp="%s/%s" % (p, q)) + return r"%s^{%s/%s}" % (base, p, q) + elif expr.exp.is_negative and expr.base.is_commutative: + # special case for 1^(-x), issue 9216 + if expr.base == 1: + return r"%s^{%s}" % (expr.base, expr.exp) + # special case for (1/x)^(-y) and (-1/-x)^(-y), issue 20252 + if expr.base.is_Rational: + base_p: int = expr.base.p # type: ignore + base_q: int = expr.base.q # type: ignore + if base_p * base_q == abs(base_q): + if expr.exp == -1: + return r"\frac{1}{\frac{%s}{%s}}" % (base_p, base_q) + else: + return r"\frac{1}{(\frac{%s}{%s})^{%s}}" % (base_p, base_q, abs(expr.exp)) + # things like 1/x + return self._print_Mul(expr) + if expr.base.is_Function: + return self._print(expr.base, exp=self._print(expr.exp)) + tex = r"%s^{%s}" + return self._helper_print_standard_power(expr, tex) + + def _helper_print_standard_power(self, expr, template: str) -> str: + exp = self._print(expr.exp) + # issue #12886: add parentheses around superscripts raised + # to powers + base = self.parenthesize(expr.base, PRECEDENCE['Pow']) + if expr.base.is_Symbol: + base = self.parenthesize_super(base) + elif expr.base.is_Float: + base = r"{%s}" % base + elif (isinstance(expr.base, Derivative) + and base.startswith(r'\left(') + and re.match(r'\\left\(\\d?d?dot', base) + and base.endswith(r'\right)')): + # don't use parentheses around dotted derivative + base = base[6: -7] # remove outermost added parens + return template % (base, exp) + + def _print_UnevaluatedExpr(self, expr): + return self._print(expr.args[0]) + + def _print_Sum(self, expr): + if len(expr.limits) == 1: + tex = r"\sum_{%s=%s}^{%s} " % \ + tuple([self._print(i) for i in expr.limits[0]]) + else: + def _format_ineq(l): + return r"%s \leq %s \leq %s" % \ + tuple([self._print(s) for s in (l[1], l[0], l[2])]) + + tex = r"\sum_{\substack{%s}} " % \ + str.join('\\\\', [_format_ineq(l) for l in expr.limits]) + + if isinstance(expr.function, Add): + tex += r"\left(%s\right)" % self._print(expr.function) + else: + tex += self._print(expr.function) + + return tex + + def _print_Product(self, expr): + if len(expr.limits) == 1: + tex = r"\prod_{%s=%s}^{%s} " % \ + tuple([self._print(i) for i in expr.limits[0]]) + else: + def _format_ineq(l): + return r"%s \leq %s \leq %s" % \ + tuple([self._print(s) for s in (l[1], l[0], l[2])]) + + tex = r"\prod_{\substack{%s}} " % \ + str.join('\\\\', [_format_ineq(l) for l in expr.limits]) + + if isinstance(expr.function, Add): + tex += r"\left(%s\right)" % self._print(expr.function) + else: + tex += self._print(expr.function) + + return tex + + def _print_BasisDependent(self, expr: 'BasisDependent'): + from sympy.vector import Vector + + o1: list[str] = [] + if expr == expr.zero: + return expr.zero._latex_form + if isinstance(expr, Vector): + items = expr.separate().items() + else: + items = [(0, expr)] + + for system, vect in items: + inneritems = list(vect.components.items()) + inneritems.sort(key=lambda x: x[0].__str__()) + for k, v in inneritems: + if v == 1: + o1.append(' + ' + k._latex_form) + elif v == -1: + o1.append(' - ' + k._latex_form) + else: + arg_str = r'\left(' + self._print(v) + r'\right)' + o1.append(' + ' + arg_str + k._latex_form) + + outstr = (''.join(o1)) + if outstr[1] != '-': + outstr = outstr[3:] + else: + outstr = outstr[1:] + return outstr + + def _print_Indexed(self, expr): + tex_base = self._print(expr.base) + tex = '{'+tex_base+'}'+'_{%s}' % ','.join( + map(self._print, expr.indices)) + return tex + + def _print_IndexedBase(self, expr): + return self._print(expr.label) + + def _print_Idx(self, expr): + label = self._print(expr.label) + if expr.upper is not None: + upper = self._print(expr.upper) + if expr.lower is not None: + lower = self._print(expr.lower) + else: + lower = self._print(S.Zero) + interval = '{lower}\\mathrel{{..}}\\nobreak {upper}'.format( + lower = lower, upper = upper) + return '{{{label}}}_{{{interval}}}'.format( + label = label, interval = interval) + #if no bounds are defined this just prints the label + return label + + def _print_Derivative(self, expr): + if requires_partial(expr.expr): + diff_symbol = r'\partial' + else: + diff_symbol = self._settings["diff_operator_latex"] + + tex = "" + dim = 0 + for x, num in reversed(expr.variable_count): + dim += num + if num == 1: + tex += r"%s %s" % (diff_symbol, self._print(x)) + else: + tex += r"%s %s^{%s}" % (diff_symbol, + self.parenthesize_super(self._print(x)), + self._print(num)) + + if dim == 1: + tex = r"\frac{%s}{%s}" % (diff_symbol, tex) + else: + tex = r"\frac{%s^{%s}}{%s}" % (diff_symbol, self._print(dim), tex) + + if any(i.could_extract_minus_sign() for i in expr.args): + return r"%s %s" % (tex, self.parenthesize(expr.expr, + PRECEDENCE["Mul"], + is_neg=True, + strict=True)) + + return r"%s %s" % (tex, self.parenthesize(expr.expr, + PRECEDENCE["Mul"], + is_neg=False, + strict=True)) + + def _print_Subs(self, subs): + expr, old, new = subs.args + latex_expr = self._print(expr) + latex_old = (self._print(e) for e in old) + latex_new = (self._print(e) for e in new) + latex_subs = r'\\ '.join( + e[0] + '=' + e[1] for e in zip(latex_old, latex_new)) + return r'\left. %s \right|_{\substack{ %s }}' % (latex_expr, + latex_subs) + + def _print_Integral(self, expr): + tex, symbols = "", [] + diff_symbol = self._settings["diff_operator_latex"] + + # Only up to \iiiint exists + if len(expr.limits) <= 4 and all(len(lim) == 1 for lim in expr.limits): + # Use len(expr.limits)-1 so that syntax highlighters don't think + # \" is an escaped quote + tex = r"\i" + "i"*(len(expr.limits) - 1) + "nt" + symbols = [r"\, %s%s" % (diff_symbol, self._print(symbol[0])) + for symbol in expr.limits] + + else: + for lim in reversed(expr.limits): + symbol = lim[0] + tex += r"\int" + + if len(lim) > 1: + if self._settings['mode'] != 'inline' \ + and not self._settings['itex']: + tex += r"\limits" + + if len(lim) == 3: + tex += "_{%s}^{%s}" % (self._print(lim[1]), + self._print(lim[2])) + if len(lim) == 2: + tex += "^{%s}" % (self._print(lim[1])) + + symbols.insert(0, r"\, %s%s" % (diff_symbol, self._print(symbol))) + + return r"%s %s%s" % (tex, self.parenthesize(expr.function, + PRECEDENCE["Mul"], + is_neg=any(i.could_extract_minus_sign() for i in expr.args), + strict=True), + "".join(symbols)) + + def _print_Limit(self, expr): + e, z, z0, dir = expr.args + + tex = r"\lim_{%s \to " % self._print(z) + if str(dir) == '+-' or z0 in (S.Infinity, S.NegativeInfinity): + tex += r"%s}" % self._print(z0) + else: + tex += r"%s^%s}" % (self._print(z0), self._print(dir)) + + if isinstance(e, AssocOp): + return r"%s\left(%s\right)" % (tex, self._print(e)) + else: + return r"%s %s" % (tex, self._print(e)) + + def _hprint_Function(self, func: str) -> str: + r''' + Logic to decide how to render a function to latex + - if it is a recognized latex name, use the appropriate latex command + - if it is a single letter, excluding sub- and superscripts, just use that letter + - if it is a longer name, then put \operatorname{} around it and be + mindful of undercores in the name + ''' + func = self._deal_with_super_sub(func) + superscriptidx = func.find("^") + subscriptidx = func.find("_") + if func in accepted_latex_functions: + name = r"\%s" % func + elif len(func) == 1 or func.startswith('\\') or subscriptidx == 1 or superscriptidx == 1: + name = func + else: + if superscriptidx > 0 and subscriptidx > 0: + name = r"\operatorname{%s}%s" %( + func[:min(subscriptidx,superscriptidx)], + func[min(subscriptidx,superscriptidx):]) + elif superscriptidx > 0: + name = r"\operatorname{%s}%s" %( + func[:superscriptidx], + func[superscriptidx:]) + elif subscriptidx > 0: + name = r"\operatorname{%s}%s" %( + func[:subscriptidx], + func[subscriptidx:]) + else: + name = r"\operatorname{%s}" % func + return name + + def _print_Function(self, expr: Function, exp=None) -> str: + r''' + Render functions to LaTeX, handling functions that LaTeX knows about + e.g., sin, cos, ... by using the proper LaTeX command (\sin, \cos, ...). + For single-letter function names, render them as regular LaTeX math + symbols. For multi-letter function names that LaTeX does not know + about, (e.g., Li, sech) use \operatorname{} so that the function name + is rendered in Roman font and LaTeX handles spacing properly. + + expr is the expression involving the function + exp is an exponent + ''' + func = expr.func.__name__ + if hasattr(self, '_print_' + func) and \ + not isinstance(expr, AppliedUndef): + return getattr(self, '_print_' + func)(expr, exp) + else: + args = [str(self._print(arg)) for arg in expr.args] + # How inverse trig functions should be displayed, formats are: + # abbreviated: asin, full: arcsin, power: sin^-1 + inv_trig_style = self._settings['inv_trig_style'] + # If we are dealing with a power-style inverse trig function + inv_trig_power_case = False + # If it is applicable to fold the argument brackets + can_fold_brackets = self._settings['fold_func_brackets'] and \ + len(args) == 1 and \ + not self._needs_function_brackets(expr.args[0]) + + inv_trig_table = [ + "asin", "acos", "atan", + "acsc", "asec", "acot", + "asinh", "acosh", "atanh", + "acsch", "asech", "acoth", + ] + + # If the function is an inverse trig function, handle the style + if func in inv_trig_table: + if inv_trig_style == "abbreviated": + pass + elif inv_trig_style == "full": + func = ("ar" if func[-1] == "h" else "arc") + func[1:] + elif inv_trig_style == "power": + func = func[1:] + inv_trig_power_case = True + + # Can never fold brackets if we're raised to a power + if exp is not None: + can_fold_brackets = False + + if inv_trig_power_case: + if func in accepted_latex_functions: + name = r"\%s^{-1}" % func + else: + name = r"\operatorname{%s}^{-1}" % func + elif exp is not None: + func_tex = self._hprint_Function(func) + func_tex = self.parenthesize_super(func_tex) + name = r'%s^{%s}' % (func_tex, exp) + else: + name = self._hprint_Function(func) + + if can_fold_brackets: + if func in accepted_latex_functions: + # Wrap argument safely to avoid parse-time conflicts + # with the function name itself + name += r" {%s}" + else: + name += r"%s" + else: + name += r"{\left(%s \right)}" + + if inv_trig_power_case and exp is not None: + name += r"^{%s}" % exp + + return name % ",".join(args) + + def _print_UndefinedFunction(self, expr): + return self._hprint_Function(str(expr)) + + def _print_ElementwiseApplyFunction(self, expr): + return r"{%s}_{\circ}\left({%s}\right)" % ( + self._print(expr.function), + self._print(expr.expr), + ) + + @property + def _special_function_classes(self): + from sympy.functions.special.tensor_functions import KroneckerDelta + from sympy.functions.special.gamma_functions import gamma, lowergamma + from sympy.functions.special.beta_functions import beta + from sympy.functions.special.delta_functions import DiracDelta + from sympy.functions.special.error_functions import Chi + return {KroneckerDelta: r'\delta', + gamma: r'\Gamma', + lowergamma: r'\gamma', + beta: r'\operatorname{B}', + DiracDelta: r'\delta', + Chi: r'\operatorname{Chi}'} + + def _print_FunctionClass(self, expr): + for cls in self._special_function_classes: + if issubclass(expr, cls) and expr.__name__ == cls.__name__: + return self._special_function_classes[cls] + return self._hprint_Function(str(expr)) + + def _print_Lambda(self, expr): + symbols, expr = expr.args + + if len(symbols) == 1: + symbols = self._print(symbols[0]) + else: + symbols = self._print(tuple(symbols)) + + tex = r"\left( %s \mapsto %s \right)" % (symbols, self._print(expr)) + + return tex + + def _print_IdentityFunction(self, expr): + return r"\left( x \mapsto x \right)" + + def _hprint_variadic_function(self, expr, exp=None) -> str: + args = sorted(expr.args, key=default_sort_key) + texargs = [r"%s" % self._print(symbol) for symbol in args] + tex = r"\%s\left(%s\right)" % (str(expr.func).lower(), + ", ".join(texargs)) + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + _print_Min = _print_Max = _hprint_variadic_function + + def _print_floor(self, expr, exp=None): + tex = r"\left\lfloor{%s}\right\rfloor" % self._print(expr.args[0]) + + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + def _print_ceiling(self, expr, exp=None): + tex = r"\left\lceil{%s}\right\rceil" % self._print(expr.args[0]) + + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + def _print_log(self, expr, exp=None): + if not self._settings["ln_notation"]: + tex = r"\log{\left(%s \right)}" % self._print(expr.args[0]) + else: + tex = r"\ln{\left(%s \right)}" % self._print(expr.args[0]) + + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + def _print_Abs(self, expr, exp=None): + tex = r"\left|{%s}\right|" % self._print(expr.args[0]) + + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + def _print_re(self, expr, exp=None): + if self._settings['gothic_re_im']: + tex = r"\Re{%s}" % self.parenthesize(expr.args[0], PRECEDENCE['Atom']) + else: + tex = r"\operatorname{{re}}{{{}}}".format(self.parenthesize(expr.args[0], PRECEDENCE['Atom'])) + + return self._do_exponent(tex, exp) + + def _print_im(self, expr, exp=None): + if self._settings['gothic_re_im']: + tex = r"\Im{%s}" % self.parenthesize(expr.args[0], PRECEDENCE['Atom']) + else: + tex = r"\operatorname{{im}}{{{}}}".format(self.parenthesize(expr.args[0], PRECEDENCE['Atom'])) + + return self._do_exponent(tex, exp) + + def _print_Not(self, e): + from sympy.logic.boolalg import (Equivalent, Implies) + if isinstance(e.args[0], Equivalent): + return self._print_Equivalent(e.args[0], r"\not\Leftrightarrow") + if isinstance(e.args[0], Implies): + return self._print_Implies(e.args[0], r"\not\Rightarrow") + if (e.args[0].is_Boolean): + return r"\neg \left(%s\right)" % self._print(e.args[0]) + else: + return r"\neg %s" % self._print(e.args[0]) + + def _print_LogOp(self, args, char): + arg = args[0] + if arg.is_Boolean and not arg.is_Not: + tex = r"\left(%s\right)" % self._print(arg) + else: + tex = r"%s" % self._print(arg) + + for arg in args[1:]: + if arg.is_Boolean and not arg.is_Not: + tex += r" %s \left(%s\right)" % (char, self._print(arg)) + else: + tex += r" %s %s" % (char, self._print(arg)) + + return tex + + def _print_And(self, e): + args = sorted(e.args, key=default_sort_key) + return self._print_LogOp(args, r"\wedge") + + def _print_Or(self, e): + args = sorted(e.args, key=default_sort_key) + return self._print_LogOp(args, r"\vee") + + def _print_Xor(self, e): + args = sorted(e.args, key=default_sort_key) + return self._print_LogOp(args, r"\veebar") + + def _print_Implies(self, e, altchar=None): + return self._print_LogOp(e.args, altchar or r"\Rightarrow") + + def _print_Equivalent(self, e, altchar=None): + args = sorted(e.args, key=default_sort_key) + return self._print_LogOp(args, altchar or r"\Leftrightarrow") + + def _print_conjugate(self, expr, exp=None): + tex = r"\overline{%s}" % self._print(expr.args[0]) + + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + def _print_polar_lift(self, expr, exp=None): + func = r"\operatorname{polar\_lift}" + arg = r"{\left(%s \right)}" % self._print(expr.args[0]) + + if exp is not None: + return r"%s^{%s}%s" % (func, exp, arg) + else: + return r"%s%s" % (func, arg) + + def _print_ExpBase(self, expr, exp=None): + # TODO should exp_polar be printed differently? + # what about exp_polar(0), exp_polar(1)? + tex = r"e^{%s}" % self._print(expr.args[0]) + return self._do_exponent(tex, exp) + + def _print_Exp1(self, expr, exp=None): + return "e" + + def _print_elliptic_k(self, expr, exp=None): + tex = r"\left(%s\right)" % self._print(expr.args[0]) + if exp is not None: + return r"K^{%s}%s" % (exp, tex) + else: + return r"K%s" % tex + + def _print_elliptic_f(self, expr, exp=None): + tex = r"\left(%s\middle| %s\right)" % \ + (self._print(expr.args[0]), self._print(expr.args[1])) + if exp is not None: + return r"F^{%s}%s" % (exp, tex) + else: + return r"F%s" % tex + + def _print_elliptic_e(self, expr, exp=None): + if len(expr.args) == 2: + tex = r"\left(%s\middle| %s\right)" % \ + (self._print(expr.args[0]), self._print(expr.args[1])) + else: + tex = r"\left(%s\right)" % self._print(expr.args[0]) + if exp is not None: + return r"E^{%s}%s" % (exp, tex) + else: + return r"E%s" % tex + + def _print_elliptic_pi(self, expr, exp=None): + if len(expr.args) == 3: + tex = r"\left(%s; %s\middle| %s\right)" % \ + (self._print(expr.args[0]), self._print(expr.args[1]), + self._print(expr.args[2])) + else: + tex = r"\left(%s\middle| %s\right)" % \ + (self._print(expr.args[0]), self._print(expr.args[1])) + if exp is not None: + return r"\Pi^{%s}%s" % (exp, tex) + else: + return r"\Pi%s" % tex + + def _print_beta(self, expr, exp=None): + x = expr.args[0] + # Deal with unevaluated single argument beta + y = expr.args[0] if len(expr.args) == 1 else expr.args[1] + tex = rf"\left({x}, {y}\right)" + + if exp is not None: + return r"\operatorname{B}^{%s}%s" % (exp, tex) + else: + return r"\operatorname{B}%s" % tex + + def _print_betainc(self, expr, exp=None, operator='B'): + largs = [self._print(arg) for arg in expr.args] + tex = r"\left(%s, %s\right)" % (largs[0], largs[1]) + + if exp is not None: + return r"\operatorname{%s}_{(%s, %s)}^{%s}%s" % (operator, largs[2], largs[3], exp, tex) + else: + return r"\operatorname{%s}_{(%s, %s)}%s" % (operator, largs[2], largs[3], tex) + + def _print_betainc_regularized(self, expr, exp=None): + return self._print_betainc(expr, exp, operator='I') + + def _print_uppergamma(self, expr, exp=None): + tex = r"\left(%s, %s\right)" % (self._print(expr.args[0]), + self._print(expr.args[1])) + + if exp is not None: + return r"\Gamma^{%s}%s" % (exp, tex) + else: + return r"\Gamma%s" % tex + + def _print_lowergamma(self, expr, exp=None): + tex = r"\left(%s, %s\right)" % (self._print(expr.args[0]), + self._print(expr.args[1])) + + if exp is not None: + return r"\gamma^{%s}%s" % (exp, tex) + else: + return r"\gamma%s" % tex + + def _hprint_one_arg_func(self, expr, exp=None) -> str: + tex = r"\left(%s\right)" % self._print(expr.args[0]) + + if exp is not None: + return r"%s^{%s}%s" % (self._print(expr.func), exp, tex) + else: + return r"%s%s" % (self._print(expr.func), tex) + + _print_gamma = _hprint_one_arg_func + + def _print_Chi(self, expr, exp=None): + tex = r"\left(%s\right)" % self._print(expr.args[0]) + + if exp is not None: + return r"\operatorname{Chi}^{%s}%s" % (exp, tex) + else: + return r"\operatorname{Chi}%s" % tex + + def _print_expint(self, expr, exp=None): + tex = r"\left(%s\right)" % self._print(expr.args[1]) + nu = self._print(expr.args[0]) + + if exp is not None: + return r"\operatorname{E}_{%s}^{%s}%s" % (nu, exp, tex) + else: + return r"\operatorname{E}_{%s}%s" % (nu, tex) + + def _print_fresnels(self, expr, exp=None): + tex = r"\left(%s\right)" % self._print(expr.args[0]) + + if exp is not None: + return r"S^{%s}%s" % (exp, tex) + else: + return r"S%s" % tex + + def _print_fresnelc(self, expr, exp=None): + tex = r"\left(%s\right)" % self._print(expr.args[0]) + + if exp is not None: + return r"C^{%s}%s" % (exp, tex) + else: + return r"C%s" % tex + + def _print_subfactorial(self, expr, exp=None): + tex = r"!%s" % self.parenthesize(expr.args[0], PRECEDENCE["Func"]) + + if exp is not None: + return r"\left(%s\right)^{%s}" % (tex, exp) + else: + return tex + + def _print_factorial(self, expr, exp=None): + tex = r"%s!" % self.parenthesize(expr.args[0], PRECEDENCE["Func"]) + + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + def _print_factorial2(self, expr, exp=None): + tex = r"%s!!" % self.parenthesize(expr.args[0], PRECEDENCE["Func"]) + + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + def _print_binomial(self, expr, exp=None): + tex = r"{\binom{%s}{%s}}" % (self._print(expr.args[0]), + self._print(expr.args[1])) + + if exp is not None: + return r"%s^{%s}" % (tex, exp) + else: + return tex + + def _print_RisingFactorial(self, expr, exp=None): + n, k = expr.args + base = r"%s" % self.parenthesize(n, PRECEDENCE['Func']) + + tex = r"{%s}^{\left(%s\right)}" % (base, self._print(k)) + + return self._do_exponent(tex, exp) + + def _print_FallingFactorial(self, expr, exp=None): + n, k = expr.args + sub = r"%s" % self.parenthesize(k, PRECEDENCE['Func']) + + tex = r"{\left(%s\right)}_{%s}" % (self._print(n), sub) + + return self._do_exponent(tex, exp) + + def _hprint_BesselBase(self, expr, exp, sym: str) -> str: + tex = r"%s" % (sym) + + need_exp = False + if exp is not None: + if tex.find('^') == -1: + tex = r"%s^{%s}" % (tex, exp) + else: + need_exp = True + + tex = r"%s_{%s}\left(%s\right)" % (tex, self._print(expr.order), + self._print(expr.argument)) + + if need_exp: + tex = self._do_exponent(tex, exp) + return tex + + def _hprint_vec(self, vec) -> str: + if not vec: + return "" + s = "" + for i in vec[:-1]: + s += "%s, " % self._print(i) + s += self._print(vec[-1]) + return s + + def _print_besselj(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'J') + + def _print_besseli(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'I') + + def _print_besselk(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'K') + + def _print_bessely(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'Y') + + def _print_yn(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'y') + + def _print_jn(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'j') + + def _print_hankel1(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'H^{(1)}') + + def _print_hankel2(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'H^{(2)}') + + def _print_hn1(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'h^{(1)}') + + def _print_hn2(self, expr, exp=None): + return self._hprint_BesselBase(expr, exp, 'h^{(2)}') + + def _hprint_airy(self, expr, exp=None, notation="") -> str: + tex = r"\left(%s\right)" % self._print(expr.args[0]) + + if exp is not None: + return r"%s^{%s}%s" % (notation, exp, tex) + else: + return r"%s%s" % (notation, tex) + + def _hprint_airy_prime(self, expr, exp=None, notation="") -> str: + tex = r"\left(%s\right)" % self._print(expr.args[0]) + + if exp is not None: + return r"{%s^\prime}^{%s}%s" % (notation, exp, tex) + else: + return r"%s^\prime%s" % (notation, tex) + + def _print_airyai(self, expr, exp=None): + return self._hprint_airy(expr, exp, 'Ai') + + def _print_airybi(self, expr, exp=None): + return self._hprint_airy(expr, exp, 'Bi') + + def _print_airyaiprime(self, expr, exp=None): + return self._hprint_airy_prime(expr, exp, 'Ai') + + def _print_airybiprime(self, expr, exp=None): + return self._hprint_airy_prime(expr, exp, 'Bi') + + def _print_hyper(self, expr, exp=None): + tex = r"{{}_{%s}F_{%s}\left(\begin{matrix} %s \\ %s \end{matrix}" \ + r"\middle| {%s} \right)}" % \ + (self._print(len(expr.ap)), self._print(len(expr.bq)), + self._hprint_vec(expr.ap), self._hprint_vec(expr.bq), + self._print(expr.argument)) + + if exp is not None: + tex = r"{%s}^{%s}" % (tex, exp) + return tex + + def _print_meijerg(self, expr, exp=None): + tex = r"{G_{%s, %s}^{%s, %s}\left(\begin{matrix} %s & %s \\" \ + r"%s & %s \end{matrix} \middle| {%s} \right)}" % \ + (self._print(len(expr.ap)), self._print(len(expr.bq)), + self._print(len(expr.bm)), self._print(len(expr.an)), + self._hprint_vec(expr.an), self._hprint_vec(expr.aother), + self._hprint_vec(expr.bm), self._hprint_vec(expr.bother), + self._print(expr.argument)) + + if exp is not None: + tex = r"{%s}^{%s}" % (tex, exp) + return tex + + def _print_dirichlet_eta(self, expr, exp=None): + tex = r"\left(%s\right)" % self._print(expr.args[0]) + if exp is not None: + return r"\eta^{%s}%s" % (exp, tex) + return r"\eta%s" % tex + + def _print_zeta(self, expr, exp=None): + if len(expr.args) == 2: + tex = r"\left(%s, %s\right)" % tuple(map(self._print, expr.args)) + else: + tex = r"\left(%s\right)" % self._print(expr.args[0]) + if exp is not None: + return r"\zeta^{%s}%s" % (exp, tex) + return r"\zeta%s" % tex + + def _print_stieltjes(self, expr, exp=None): + if len(expr.args) == 2: + tex = r"_{%s}\left(%s\right)" % tuple(map(self._print, expr.args)) + else: + tex = r"_{%s}" % self._print(expr.args[0]) + if exp is not None: + return r"\gamma%s^{%s}" % (tex, exp) + return r"\gamma%s" % tex + + def _print_lerchphi(self, expr, exp=None): + tex = r"\left(%s, %s, %s\right)" % tuple(map(self._print, expr.args)) + if exp is None: + return r"\Phi%s" % tex + return r"\Phi^{%s}%s" % (exp, tex) + + def _print_polylog(self, expr, exp=None): + s, z = map(self._print, expr.args) + tex = r"\left(%s\right)" % z + if exp is None: + return r"\operatorname{Li}_{%s}%s" % (s, tex) + return r"\operatorname{Li}_{%s}^{%s}%s" % (s, exp, tex) + + def _print_jacobi(self, expr, exp=None): + n, a, b, x = map(self._print, expr.args) + tex = r"P_{%s}^{\left(%s,%s\right)}\left(%s\right)" % (n, a, b, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_gegenbauer(self, expr, exp=None): + n, a, x = map(self._print, expr.args) + tex = r"C_{%s}^{\left(%s\right)}\left(%s\right)" % (n, a, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_chebyshevt(self, expr, exp=None): + n, x = map(self._print, expr.args) + tex = r"T_{%s}\left(%s\right)" % (n, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_chebyshevu(self, expr, exp=None): + n, x = map(self._print, expr.args) + tex = r"U_{%s}\left(%s\right)" % (n, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_legendre(self, expr, exp=None): + n, x = map(self._print, expr.args) + tex = r"P_{%s}\left(%s\right)" % (n, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_assoc_legendre(self, expr, exp=None): + n, a, x = map(self._print, expr.args) + tex = r"P_{%s}^{\left(%s\right)}\left(%s\right)" % (n, a, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_hermite(self, expr, exp=None): + n, x = map(self._print, expr.args) + tex = r"H_{%s}\left(%s\right)" % (n, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_laguerre(self, expr, exp=None): + n, x = map(self._print, expr.args) + tex = r"L_{%s}\left(%s\right)" % (n, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_assoc_laguerre(self, expr, exp=None): + n, a, x = map(self._print, expr.args) + tex = r"L_{%s}^{\left(%s\right)}\left(%s\right)" % (n, a, x) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_Ynm(self, expr, exp=None): + n, m, theta, phi = map(self._print, expr.args) + tex = r"Y_{%s}^{%s}\left(%s,%s\right)" % (n, m, theta, phi) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def _print_Znm(self, expr, exp=None): + n, m, theta, phi = map(self._print, expr.args) + tex = r"Z_{%s}^{%s}\left(%s,%s\right)" % (n, m, theta, phi) + if exp is not None: + tex = r"\left(" + tex + r"\right)^{%s}" % (exp) + return tex + + def __print_mathieu_functions(self, character, args, prime=False, exp=None): + a, q, z = map(self._print, args) + sup = r"^{\prime}" if prime else "" + exp = "" if not exp else "^{%s}" % exp + return r"%s%s\left(%s, %s, %s\right)%s" % (character, sup, a, q, z, exp) + + def _print_mathieuc(self, expr, exp=None): + return self.__print_mathieu_functions("C", expr.args, exp=exp) + + def _print_mathieus(self, expr, exp=None): + return self.__print_mathieu_functions("S", expr.args, exp=exp) + + def _print_mathieucprime(self, expr, exp=None): + return self.__print_mathieu_functions("C", expr.args, prime=True, exp=exp) + + def _print_mathieusprime(self, expr, exp=None): + return self.__print_mathieu_functions("S", expr.args, prime=True, exp=exp) + + def _print_Rational(self, expr): + if expr.q != 1: + sign = "" + p = expr.p + if expr.p < 0: + sign = "- " + p = -p + if self._settings['fold_short_frac']: + return r"%s%d / %d" % (sign, p, expr.q) + return r"%s\frac{%d}{%d}" % (sign, p, expr.q) + else: + return self._print(expr.p) + + def _print_Order(self, expr): + s = self._print(expr.expr) + if expr.point and any(p != S.Zero for p in expr.point) or \ + len(expr.variables) > 1: + s += '; ' + if len(expr.variables) > 1: + s += self._print(expr.variables) + elif expr.variables: + s += self._print(expr.variables[0]) + s += r'\rightarrow ' + if len(expr.point) > 1: + s += self._print(expr.point) + else: + s += self._print(expr.point[0]) + return r"O\left(%s\right)" % s + + def _print_Symbol(self, expr: Symbol, style='plain'): + name: str = self._settings['symbol_names'].get(expr) + if name is not None: + return name + + return self._deal_with_super_sub(expr.name, style=style) + + _print_RandomSymbol = _print_Symbol + + def _split_super_sub(self, name: str) -> tuple[str, list[str], list[str]]: + if name is None or '{' in name: + return (name, [], []) + elif self._settings["disable_split_super_sub"]: + name, supers, subs = (name.replace('_', '\\_').replace('^', '\\^'), [], []) + else: + name, supers, subs = split_super_sub(name) + name = translate(name) + supers = [translate(sup) for sup in supers] + subs = [translate(sub) for sub in subs] + return (name, supers, subs) + + def _deal_with_super_sub(self, string: str, style='plain') -> str: + name, supers, subs = self._split_super_sub(string) + + # apply the style only to the name + if style == 'bold': + name = "\\mathbf{{{}}}".format(name) + + # glue all items together: + if supers: + name += "^{%s}" % " ".join(supers) + if subs: + name += "_{%s}" % " ".join(subs) + + return name + + def _print_Relational(self, expr): + if self._settings['itex']: + gt = r"\gt" + lt = r"\lt" + else: + gt = ">" + lt = "<" + + charmap = { + "==": "=", + ">": gt, + "<": lt, + ">=": r"\geq", + "<=": r"\leq", + "!=": r"\neq", + } + + return "%s %s %s" % (self._print(expr.lhs), + charmap[expr.rel_op], self._print(expr.rhs)) + + def _print_Piecewise(self, expr): + ecpairs = [r"%s & \text{for}\: %s" % (self._print(e), self._print(c)) + for e, c in expr.args[:-1]] + if expr.args[-1].cond == true: + ecpairs.append(r"%s & \text{otherwise}" % + self._print(expr.args[-1].expr)) + else: + ecpairs.append(r"%s & \text{for}\: %s" % + (self._print(expr.args[-1].expr), + self._print(expr.args[-1].cond))) + tex = r"\begin{cases} %s \end{cases}" + return tex % r" \\".join(ecpairs) + + def _print_matrix_contents(self, expr): + lines = [] + + for line in range(expr.rows): # horrible, should be 'rows' + lines.append(" & ".join([self._print(i) for i in expr[line, :]])) + + mat_str = self._settings['mat_str'] + if mat_str is None: + if self._settings['mode'] == 'inline': + mat_str = 'smallmatrix' + else: + if (expr.cols <= 10) is True: + mat_str = 'matrix' + else: + mat_str = 'array' + + out_str = r'\begin{%MATSTR%}%s\end{%MATSTR%}' + out_str = out_str.replace('%MATSTR%', mat_str) + if mat_str == 'array': + out_str = out_str.replace('%s', '{' + 'c'*expr.cols + '}%s') + return out_str % r"\\".join(lines) + + def _print_MatrixBase(self, expr): + out_str = self._print_matrix_contents(expr) + if self._settings['mat_delim']: + left_delim = self._settings['mat_delim'] + right_delim = self._delim_dict[left_delim] + out_str = r'\left' + left_delim + out_str + \ + r'\right' + right_delim + return out_str + + def _print_MatrixElement(self, expr): + matrix_part = self.parenthesize(expr.parent, PRECEDENCE['Atom'], strict=True) + index_part = f"{self._print(expr.i)},{self._print(expr.j)}" + return f"{{{matrix_part}}}_{{{index_part}}}" + + def _print_MatrixSlice(self, expr): + def latexslice(x, dim): + x = list(x) + if x[2] == 1: + del x[2] + if x[0] == 0: + x[0] = None + if x[1] == dim: + x[1] = None + return ':'.join(self._print(xi) if xi is not None else '' for xi in x) + return (self.parenthesize(expr.parent, PRECEDENCE["Atom"], strict=True) + r'\left[' + + latexslice(expr.rowslice, expr.parent.rows) + ', ' + + latexslice(expr.colslice, expr.parent.cols) + r'\right]') + + def _print_BlockMatrix(self, expr): + return self._print(expr.blocks) + + def _print_Transpose(self, expr): + mat = expr.arg + from sympy.matrices import MatrixSymbol, BlockMatrix + if (not isinstance(mat, MatrixSymbol) and + not isinstance(mat, BlockMatrix) and mat.is_MatrixExpr): + return r"\left(%s\right)^{T}" % self._print(mat) + else: + s = self.parenthesize(mat, precedence_traditional(expr), True) + if '^' in s: + return r"\left(%s\right)^{T}" % s + else: + return "%s^{T}" % s + + def _print_Trace(self, expr): + mat = expr.arg + return r"\operatorname{tr}\left(%s \right)" % self._print(mat) + + def _print_Adjoint(self, expr): + style_to_latex = { + "dagger" : r"\dagger", + "star" : r"\ast", + "hermitian": r"\mathsf{H}" + } + adjoint_style = style_to_latex.get(self._settings["adjoint_style"], r"\dagger") + mat = expr.arg + from sympy.matrices import MatrixSymbol, BlockMatrix + if (not isinstance(mat, MatrixSymbol) and + not isinstance(mat, BlockMatrix) and mat.is_MatrixExpr): + return r"\left(%s\right)^{%s}" % (self._print(mat), adjoint_style) + else: + s = self.parenthesize(mat, precedence_traditional(expr), True) + if '^' in s: + return r"\left(%s\right)^{%s}" % (s, adjoint_style) + else: + return r"%s^{%s}" % (s, adjoint_style) + + def _print_MatMul(self, expr): + from sympy import MatMul + + # Parenthesize nested MatMul but not other types of Mul objects: + parens = lambda x: self._print(x) if isinstance(x, Mul) and not isinstance(x, MatMul) else \ + self.parenthesize(x, precedence_traditional(expr), False) + + args = list(expr.args) + if expr.could_extract_minus_sign(): + if args[0] == -1: + args = args[1:] + else: + args[0] = -args[0] + return '- ' + ' '.join(map(parens, args)) + else: + return ' '.join(map(parens, args)) + + def _print_DotProduct(self, expr): + level = precedence_traditional(expr) + left, right = expr.args + return rf"{self.parenthesize(left, level)} \cdot {self.parenthesize(right, level)}" + + def _print_Determinant(self, expr): + mat = expr.arg + if mat.is_MatrixExpr: + from sympy.matrices.expressions.blockmatrix import BlockMatrix + if isinstance(mat, BlockMatrix): + return r"\left|{%s}\right|" % self._print_matrix_contents(mat.blocks) + return r"\left|{%s}\right|" % self._print(mat) + return r"\left|{%s}\right|" % self._print_matrix_contents(mat) + + + def _print_Mod(self, expr, exp=None): + if exp is not None: + return r'\left(%s \bmod %s\right)^{%s}' % \ + (self.parenthesize(expr.args[0], PRECEDENCE['Mul'], + strict=True), + self.parenthesize(expr.args[1], PRECEDENCE['Mul'], + strict=True), + exp) + return r'%s \bmod %s' % (self.parenthesize(expr.args[0], + PRECEDENCE['Mul'], + strict=True), + self.parenthesize(expr.args[1], + PRECEDENCE['Mul'], + strict=True)) + + def _print_HadamardProduct(self, expr): + args = expr.args + prec = PRECEDENCE['Pow'] + parens = self.parenthesize + + return r' \circ '.join( + (parens(arg, prec, strict=True) for arg in args)) + + def _print_HadamardPower(self, expr): + if precedence_traditional(expr.exp) < PRECEDENCE["Mul"]: + template = r"%s^{\circ \left({%s}\right)}" + else: + template = r"%s^{\circ {%s}}" + return self._helper_print_standard_power(expr, template) + + def _print_KroneckerProduct(self, expr): + args = expr.args + prec = PRECEDENCE['Pow'] + parens = self.parenthesize + + return r' \otimes '.join( + (parens(arg, prec, strict=True) for arg in args)) + + def _print_MatPow(self, expr): + base, exp = expr.base, expr.exp + from sympy.matrices import MatrixSymbol + if not isinstance(base, MatrixSymbol) and base.is_MatrixExpr: + return "\\left(%s\\right)^{%s}" % (self._print(base), + self._print(exp)) + else: + base_str = self._print(base) + if '^' in base_str: + return r"\left(%s\right)^{%s}" % (base_str, self._print(exp)) + else: + return "%s^{%s}" % (base_str, self._print(exp)) + + def _print_MatrixSymbol(self, expr): + return self._print_Symbol(expr, style=self._settings[ + 'mat_symbol_style']) + + def _print_ZeroMatrix(self, Z): + return "0" if self._settings[ + 'mat_symbol_style'] == 'plain' else r"\mathbf{0}" + + def _print_OneMatrix(self, O): + return "1" if self._settings[ + 'mat_symbol_style'] == 'plain' else r"\mathbf{1}" + + def _print_Identity(self, I): + return r"\mathbb{I}" if self._settings[ + 'mat_symbol_style'] == 'plain' else r"\mathbf{I}" + + def _print_PermutationMatrix(self, P): + perm_str = self._print(P.args[0]) + return "P_{%s}" % perm_str + + def _print_NDimArray(self, expr: NDimArray): + + if expr.rank() == 0: + return self._print(expr[()]) + + mat_str = self._settings['mat_str'] + if mat_str is None: + if self._settings['mode'] == 'inline': + mat_str = 'smallmatrix' + else: + if (expr.rank() == 0) or (expr.shape[-1] <= 10): + mat_str = 'matrix' + else: + mat_str = 'array' + block_str = r'\begin{%MATSTR%}%s\end{%MATSTR%}' + block_str = block_str.replace('%MATSTR%', mat_str) + if mat_str == 'array': + block_str = block_str.replace('%s', '{' + 'c'*expr.shape[0] + '}%s') + + if self._settings['mat_delim']: + left_delim: str = self._settings['mat_delim'] + right_delim = self._delim_dict[left_delim] + block_str = r'\left' + left_delim + block_str + \ + r'\right' + right_delim + + if expr.rank() == 0: + return block_str % "" + + level_str: list[list[str]] = [[] for i in range(expr.rank() + 1)] + shape_ranges = [list(range(i)) for i in expr.shape] + for outer_i in itertools.product(*shape_ranges): + level_str[-1].append(self._print(expr[outer_i])) + even = True + for back_outer_i in range(expr.rank()-1, -1, -1): + if len(level_str[back_outer_i+1]) < expr.shape[back_outer_i]: + break + if even: + level_str[back_outer_i].append( + r" & ".join(level_str[back_outer_i+1])) + else: + level_str[back_outer_i].append( + block_str % (r"\\".join(level_str[back_outer_i+1]))) + if len(level_str[back_outer_i+1]) == 1: + level_str[back_outer_i][-1] = r"\left[" + \ + level_str[back_outer_i][-1] + r"\right]" + even = not even + level_str[back_outer_i+1] = [] + + out_str = level_str[0][0] + + if expr.rank() % 2 == 1: + out_str = block_str % out_str + + return out_str + + def _printer_tensor_indices(self, name, indices, index_map: dict): + out_str = self._print(name) + last_valence = None + prev_map = None + for index in indices: + new_valence = index.is_up + if ((index in index_map) or prev_map) and \ + last_valence == new_valence: + out_str += "," + if last_valence != new_valence: + if last_valence is not None: + out_str += "}" + if index.is_up: + out_str += "{}^{" + else: + out_str += "{}_{" + out_str += self._print(index.args[0]) + if index in index_map: + out_str += "=" + out_str += self._print(index_map[index]) + prev_map = True + else: + prev_map = False + last_valence = new_valence + if last_valence is not None: + out_str += "}" + return out_str + + def _print_Tensor(self, expr): + name = expr.args[0].args[0] + indices = expr.get_indices() + return self._printer_tensor_indices(name, indices, {}) + + def _print_TensorElement(self, expr): + name = expr.expr.args[0].args[0] + indices = expr.expr.get_indices() + index_map = expr.index_map + return self._printer_tensor_indices(name, indices, index_map) + + def _print_TensMul(self, expr): + # prints expressions like "A(a)", "3*A(a)", "(1+x)*A(a)" + sign, args = expr._get_args_for_traditional_printer() + return sign + "".join( + [self.parenthesize(arg, precedence(expr)) for arg in args] + ) + + def _print_TensAdd(self, expr): + a = [] + args = expr.args + for x in args: + a.append(self.parenthesize(x, precedence(expr))) + a.sort() + s = ' + '.join(a) + s = s.replace('+ -', '- ') + return s + + def _print_TensorIndex(self, expr): + return "{}%s{%s}" % ( + "^" if expr.is_up else "_", + self._print(expr.args[0]) + ) + + def _print_PartialDerivative(self, expr): + if len(expr.variables) == 1: + return r"\frac{\partial}{\partial {%s}}{%s}" % ( + self._print(expr.variables[0]), + self.parenthesize(expr.expr, PRECEDENCE["Mul"], False) + ) + else: + return r"\frac{\partial^{%s}}{%s}{%s}" % ( + len(expr.variables), + " ".join([r"\partial {%s}" % self._print(i) for i in expr.variables]), + self.parenthesize(expr.expr, PRECEDENCE["Mul"], False) + ) + + def _print_ArraySymbol(self, expr): + return self._print(expr.name) + + def _print_ArrayElement(self, expr): + return "{{%s}_{%s}}" % ( + self.parenthesize(expr.name, PRECEDENCE["Func"], True), + ", ".join([f"{self._print(i)}" for i in expr.indices])) + + def _print_UniversalSet(self, expr): + return r"\mathbb{U}" + + def _print_frac(self, expr, exp=None): + if exp is None: + return r"\operatorname{frac}{\left(%s\right)}" % self._print(expr.args[0]) + else: + return r"\operatorname{frac}{\left(%s\right)}^{%s}" % ( + self._print(expr.args[0]), exp) + + def _print_tuple(self, expr): + if self._settings['decimal_separator'] == 'comma': + sep = ";" + elif self._settings['decimal_separator'] == 'period': + sep = "," + else: + raise ValueError('Unknown Decimal Separator') + + if len(expr) == 1: + # 1-tuple needs a trailing separator + return self._add_parens_lspace(self._print(expr[0]) + sep) + else: + return self._add_parens_lspace( + (sep + r" \ ").join([self._print(i) for i in expr])) + + def _print_TensorProduct(self, expr): + elements = [self._print(a) for a in expr.args] + return r' \otimes '.join(elements) + + def _print_WedgeProduct(self, expr): + elements = [self._print(a) for a in expr.args] + return r' \wedge '.join(elements) + + def _print_Tuple(self, expr): + return self._print_tuple(expr) + + def _print_list(self, expr): + if self._settings['decimal_separator'] == 'comma': + return r"\left[ %s\right]" % \ + r"; \ ".join([self._print(i) for i in expr]) + elif self._settings['decimal_separator'] == 'period': + return r"\left[ %s\right]" % \ + r", \ ".join([self._print(i) for i in expr]) + else: + raise ValueError('Unknown Decimal Separator') + + + def _print_dict(self, d): + keys = sorted(d.keys(), key=default_sort_key) + items = [] + + for key in keys: + val = d[key] + items.append("%s : %s" % (self._print(key), self._print(val))) + + return r"\left\{ %s\right\}" % r", \ ".join(items) + + def _print_Dict(self, expr): + return self._print_dict(expr) + + def _print_DiracDelta(self, expr, exp=None): + if len(expr.args) == 1 or expr.args[1] == 0: + tex = r"\delta\left(%s\right)" % self._print(expr.args[0]) + else: + tex = r"\delta^{\left( %s \right)}\left( %s \right)" % ( + self._print(expr.args[1]), self._print(expr.args[0])) + if exp: + tex = r"\left(%s\right)^{%s}" % (tex, exp) + return tex + + def _print_SingularityFunction(self, expr, exp=None): + shift = self._print(expr.args[0] - expr.args[1]) + power = self._print(expr.args[2]) + tex = r"{\left\langle %s \right\rangle}^{%s}" % (shift, power) + if exp is not None: + tex = r"{\left({\langle %s \rangle}^{%s}\right)}^{%s}" % (shift, power, exp) + return tex + + def _print_Heaviside(self, expr, exp=None): + pargs = ', '.join(self._print(arg) for arg in expr.pargs) + tex = r"\theta\left(%s\right)" % pargs + if exp: + tex = r"\left(%s\right)^{%s}" % (tex, exp) + return tex + + def _print_KroneckerDelta(self, expr, exp=None): + i = self._print(expr.args[0]) + j = self._print(expr.args[1]) + if expr.args[0].is_Atom and expr.args[1].is_Atom: + tex = r'\delta_{%s %s}' % (i, j) + else: + tex = r'\delta_{%s, %s}' % (i, j) + if exp is not None: + tex = r'\left(%s\right)^{%s}' % (tex, exp) + return tex + + def _print_LeviCivita(self, expr, exp=None): + indices = map(self._print, expr.args) + if all(x.is_Atom for x in expr.args): + tex = r'\varepsilon_{%s}' % " ".join(indices) + else: + tex = r'\varepsilon_{%s}' % ", ".join(indices) + if exp: + tex = r'\left(%s\right)^{%s}' % (tex, exp) + return tex + + def _print_RandomDomain(self, d): + if hasattr(d, 'as_boolean'): + return '\\text{Domain: }' + self._print(d.as_boolean()) + elif hasattr(d, 'set'): + return ('\\text{Domain: }' + self._print(d.symbols) + ' \\in ' + + self._print(d.set)) + elif hasattr(d, 'symbols'): + return '\\text{Domain on }' + self._print(d.symbols) + else: + return self._print(None) + + def _print_FiniteSet(self, s): + items = sorted(s.args, key=default_sort_key) + return self._print_set(items) + + def _print_set(self, s): + items = sorted(s, key=default_sort_key) + if self._settings['decimal_separator'] == 'comma': + items = "; ".join(map(self._print, items)) + elif self._settings['decimal_separator'] == 'period': + items = ", ".join(map(self._print, items)) + else: + raise ValueError('Unknown Decimal Separator') + return r"\left\{%s\right\}" % items + + + _print_frozenset = _print_set + + def _print_Range(self, s): + def _print_symbolic_range(): + # Symbolic Range that cannot be resolved + if s.args[0] == 0: + if s.args[2] == 1: + cont = self._print(s.args[1]) + else: + cont = ", ".join(self._print(arg) for arg in s.args) + else: + if s.args[2] == 1: + cont = ", ".join(self._print(arg) for arg in s.args[:2]) + else: + cont = ", ".join(self._print(arg) for arg in s.args) + + return(f"\\text{{Range}}\\left({cont}\\right)") + + dots = object() + + if s.start.is_infinite and s.stop.is_infinite: + if s.step.is_positive: + printset = dots, -1, 0, 1, dots + else: + printset = dots, 1, 0, -1, dots + elif s.start.is_infinite: + printset = dots, s[-1] - s.step, s[-1] + elif s.stop.is_infinite: + it = iter(s) + printset = next(it), next(it), dots + elif s.is_empty is not None: + if (s.size < 4) == True: + printset = tuple(s) + elif s.is_iterable: + it = iter(s) + printset = next(it), next(it), dots, s[-1] + else: + return _print_symbolic_range() + else: + return _print_symbolic_range() + return (r"\left\{" + + r", ".join(self._print(el) if el is not dots else r'\ldots' for el in printset) + + r"\right\}") + + def __print_number_polynomial(self, expr, letter, exp=None): + if len(expr.args) == 2: + if exp is not None: + return r"%s_{%s}^{%s}\left(%s\right)" % (letter, + self._print(expr.args[0]), exp, + self._print(expr.args[1])) + return r"%s_{%s}\left(%s\right)" % (letter, + self._print(expr.args[0]), self._print(expr.args[1])) + + tex = r"%s_{%s}" % (letter, self._print(expr.args[0])) + if exp is not None: + tex = r"%s^{%s}" % (tex, exp) + return tex + + def _print_bernoulli(self, expr, exp=None): + return self.__print_number_polynomial(expr, "B", exp) + + def _print_genocchi(self, expr, exp=None): + return self.__print_number_polynomial(expr, "G", exp) + + def _print_bell(self, expr, exp=None): + if len(expr.args) == 3: + tex1 = r"B_{%s, %s}" % (self._print(expr.args[0]), + self._print(expr.args[1])) + tex2 = r"\left(%s\right)" % r", ".join(self._print(el) for + el in expr.args[2]) + if exp is not None: + tex = r"%s^{%s}%s" % (tex1, exp, tex2) + else: + tex = tex1 + tex2 + return tex + return self.__print_number_polynomial(expr, "B", exp) + + def _print_fibonacci(self, expr, exp=None): + return self.__print_number_polynomial(expr, "F", exp) + + def _print_lucas(self, expr, exp=None): + tex = r"L_{%s}" % self._print(expr.args[0]) + if exp is not None: + tex = r"%s^{%s}" % (tex, exp) + return tex + + def _print_tribonacci(self, expr, exp=None): + return self.__print_number_polynomial(expr, "T", exp) + + def _print_mobius(self, expr, exp=None): + if exp is None: + return r'\mu\left(%s\right)' % self._print(expr.args[0]) + return r'\mu^{%s}\left(%s\right)' % (exp, self._print(expr.args[0])) + + def _print_SeqFormula(self, s): + dots = object() + if len(s.start.free_symbols) > 0 or len(s.stop.free_symbols) > 0: + return r"\left\{%s\right\}_{%s=%s}^{%s}" % ( + self._print(s.formula), + self._print(s.variables[0]), + self._print(s.start), + self._print(s.stop) + ) + if s.start is S.NegativeInfinity: + stop = s.stop + printset = (dots, s.coeff(stop - 3), s.coeff(stop - 2), + s.coeff(stop - 1), s.coeff(stop)) + elif s.stop is S.Infinity or s.length > 4: + printset = s[:4] + printset.append(dots) + else: + printset = tuple(s) + + return (r"\left[" + + r", ".join(self._print(el) if el is not dots else r'\ldots' for el in printset) + + r"\right]") + + _print_SeqPer = _print_SeqFormula + _print_SeqAdd = _print_SeqFormula + _print_SeqMul = _print_SeqFormula + + def _print_Interval(self, i): + if i.start == i.end: + return r"\left\{%s\right\}" % self._print(i.start) + + else: + if i.left_open: + left = '(' + else: + left = '[' + + if i.right_open: + right = ')' + else: + right = ']' + + return r"\left%s%s, %s\right%s" % \ + (left, self._print(i.start), self._print(i.end), right) + + def _print_AccumulationBounds(self, i): + return r"\left\langle %s, %s\right\rangle" % \ + (self._print(i.min), self._print(i.max)) + + def _print_Union(self, u): + prec = precedence_traditional(u) + args_str = [self.parenthesize(i, prec) for i in u.args] + return r" \cup ".join(args_str) + + def _print_Complement(self, u): + prec = precedence_traditional(u) + args_str = [self.parenthesize(i, prec) for i in u.args] + return r" \setminus ".join(args_str) + + def _print_Intersection(self, u): + prec = precedence_traditional(u) + args_str = [self.parenthesize(i, prec) for i in u.args] + return r" \cap ".join(args_str) + + def _print_SymmetricDifference(self, u): + prec = precedence_traditional(u) + args_str = [self.parenthesize(i, prec) for i in u.args] + return r" \triangle ".join(args_str) + + def _print_ProductSet(self, p): + prec = precedence_traditional(p) + if len(p.sets) >= 1 and not has_variety(p.sets): + return self.parenthesize(p.sets[0], prec) + "^{%d}" % len(p.sets) + return r" \times ".join( + self.parenthesize(set, prec) for set in p.sets) + + def _print_EmptySet(self, e): + return r"\emptyset" + + def _print_Naturals(self, n): + return r"\mathbb{N}" + + def _print_Naturals0(self, n): + return r"\mathbb{N}_0" + + def _print_Integers(self, i): + return r"\mathbb{Z}" + + def _print_Rationals(self, i): + return r"\mathbb{Q}" + + def _print_Reals(self, i): + return r"\mathbb{R}" + + def _print_Complexes(self, i): + return r"\mathbb{C}" + + def _print_ImageSet(self, s): + expr = s.lamda.expr + sig = s.lamda.signature + xys = ((self._print(x), self._print(y)) for x, y in zip(sig, s.base_sets)) + xinys = r", ".join(r"%s \in %s" % xy for xy in xys) + return r"\left\{%s\; \middle|\; %s\right\}" % (self._print(expr), xinys) + + def _print_ConditionSet(self, s): + vars_print = ', '.join([self._print(var) for var in Tuple(s.sym)]) + if s.base_set is S.UniversalSet: + return r"\left\{%s\; \middle|\; %s \right\}" % \ + (vars_print, self._print(s.condition)) + + return r"\left\{%s\; \middle|\; %s \in %s \wedge %s \right\}" % ( + vars_print, + vars_print, + self._print(s.base_set), + self._print(s.condition)) + + def _print_PowerSet(self, expr): + arg_print = self._print(expr.args[0]) + return r"\mathcal{{P}}\left({}\right)".format(arg_print) + + def _print_ComplexRegion(self, s): + vars_print = ', '.join([self._print(var) for var in s.variables]) + return r"\left\{%s\; \middle|\; %s \in %s \right\}" % ( + self._print(s.expr), + vars_print, + self._print(s.sets)) + + def _print_Contains(self, e): + return r"%s \in %s" % tuple(self._print(a) for a in e.args) + + def _print_FourierSeries(self, s): + if s.an.formula is S.Zero and s.bn.formula is S.Zero: + return self._print(s.a0) + return self._print_Add(s.truncate()) + r' + \ldots' + + def _print_FormalPowerSeries(self, s): + return self._print_Add(s.infinite) + + def _print_FiniteField(self, expr): + return r"\mathbb{F}_{%s}" % expr.mod + + def _print_IntegerRing(self, expr): + return r"\mathbb{Z}" + + def _print_RationalField(self, expr): + return r"\mathbb{Q}" + + def _print_RealField(self, expr): + return r"\mathbb{R}" + + def _print_ComplexField(self, expr): + return r"\mathbb{C}" + + def _print_PolynomialRing(self, expr): + domain = self._print(expr.domain) + symbols = ", ".join(map(self._print, expr.symbols)) + return r"%s\left[%s\right]" % (domain, symbols) + + def _print_FractionField(self, expr): + domain = self._print(expr.domain) + symbols = ", ".join(map(self._print, expr.symbols)) + return r"%s\left(%s\right)" % (domain, symbols) + + def _print_PolynomialRingBase(self, expr): + domain = self._print(expr.domain) + symbols = ", ".join(map(self._print, expr.symbols)) + inv = "" + if not expr.is_Poly: + inv = r"S_<^{-1}" + return r"%s%s\left[%s\right]" % (inv, domain, symbols) + + def _print_Poly(self, poly): + cls = poly.__class__.__name__ + terms = [] + for monom, coeff in poly.terms(): + s_monom = '' + for i, exp in enumerate(monom): + if exp > 0: + if exp == 1: + s_monom += self._print(poly.gens[i]) + else: + s_monom += self._print(pow(poly.gens[i], exp)) + + if coeff.is_Add: + if s_monom: + s_coeff = r"\left(%s\right)" % self._print(coeff) + else: + s_coeff = self._print(coeff) + else: + if s_monom: + if coeff is S.One: + terms.extend(['+', s_monom]) + continue + + if coeff is S.NegativeOne: + terms.extend(['-', s_monom]) + continue + + s_coeff = self._print(coeff) + + if not s_monom: + s_term = s_coeff + else: + s_term = s_coeff + " " + s_monom + + if s_term.startswith('-'): + terms.extend(['-', s_term[1:]]) + else: + terms.extend(['+', s_term]) + + if terms[0] in ('-', '+'): + modifier = terms.pop(0) + + if modifier == '-': + terms[0] = '-' + terms[0] + + expr = ' '.join(terms) + gens = list(map(self._print, poly.gens)) + domain = "domain=%s" % self._print(poly.get_domain()) + + args = ", ".join([expr] + gens + [domain]) + if cls in accepted_latex_functions: + tex = r"\%s {\left(%s \right)}" % (cls, args) + else: + tex = r"\operatorname{%s}{\left( %s \right)}" % (cls, args) + + return tex + + def _print_ComplexRootOf(self, root): + cls = root.__class__.__name__ + if cls == "ComplexRootOf": + cls = "CRootOf" + expr = self._print(root.expr) + index = root.index + if cls in accepted_latex_functions: + return r"\%s {\left(%s, %d\right)}" % (cls, expr, index) + else: + return r"\operatorname{%s} {\left(%s, %d\right)}" % (cls, expr, + index) + + def _print_RootSum(self, expr): + cls = expr.__class__.__name__ + args = [self._print(expr.expr)] + + if expr.fun is not S.IdentityFunction: + args.append(self._print(expr.fun)) + + if cls in accepted_latex_functions: + return r"\%s {\left(%s\right)}" % (cls, ", ".join(args)) + else: + return r"\operatorname{%s} {\left(%s\right)}" % (cls, + ", ".join(args)) + + def _print_OrdinalOmega(self, expr): + return r"\omega" + + def _print_OmegaPower(self, expr): + exp, mul = expr.args + if mul != 1: + if exp != 1: + return r"{} \omega^{{{}}}".format(mul, exp) + else: + return r"{} \omega".format(mul) + else: + if exp != 1: + return r"\omega^{{{}}}".format(exp) + else: + return r"\omega" + + def _print_Ordinal(self, expr): + return " + ".join([self._print(arg) for arg in expr.args]) + + def _print_PolyElement(self, poly): + mul_symbol = self._settings['mul_symbol_latex'] + return poly.str(self, PRECEDENCE, "{%s}^{%d}", mul_symbol) + + def _print_FracElement(self, frac): + if frac.denom == 1: + return self._print(frac.numer) + else: + numer = self._print(frac.numer) + denom = self._print(frac.denom) + return r"\frac{%s}{%s}" % (numer, denom) + + def _print_euler(self, expr, exp=None): + m, x = (expr.args[0], None) if len(expr.args) == 1 else expr.args + tex = r"E_{%s}" % self._print(m) + if exp is not None: + tex = r"%s^{%s}" % (tex, exp) + if x is not None: + tex = r"%s\left(%s\right)" % (tex, self._print(x)) + return tex + + def _print_catalan(self, expr, exp=None): + tex = r"C_{%s}" % self._print(expr.args[0]) + if exp is not None: + tex = r"%s^{%s}" % (tex, exp) + return tex + + def _print_UnifiedTransform(self, expr, s, inverse=False): + return r"\mathcal{{{}}}{}_{{{}}}\left[{}\right]\left({}\right)".format(s, '^{-1}' if inverse else '', self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2])) + + def _print_MellinTransform(self, expr): + return self._print_UnifiedTransform(expr, 'M') + + def _print_InverseMellinTransform(self, expr): + return self._print_UnifiedTransform(expr, 'M', True) + + def _print_LaplaceTransform(self, expr): + return self._print_UnifiedTransform(expr, 'L') + + def _print_InverseLaplaceTransform(self, expr): + return self._print_UnifiedTransform(expr, 'L', True) + + def _print_FourierTransform(self, expr): + return self._print_UnifiedTransform(expr, 'F') + + def _print_InverseFourierTransform(self, expr): + return self._print_UnifiedTransform(expr, 'F', True) + + def _print_SineTransform(self, expr): + return self._print_UnifiedTransform(expr, 'SIN') + + def _print_InverseSineTransform(self, expr): + return self._print_UnifiedTransform(expr, 'SIN', True) + + def _print_CosineTransform(self, expr): + return self._print_UnifiedTransform(expr, 'COS') + + def _print_InverseCosineTransform(self, expr): + return self._print_UnifiedTransform(expr, 'COS', True) + + def _print_DMP(self, p): + try: + if p.ring is not None: + # TODO incorporate order + return self._print(p.ring.to_sympy(p)) + except SympifyError: + pass + return self._print(repr(p)) + + def _print_DMF(self, p): + return self._print_DMP(p) + + def _print_Object(self, object): + return self._print(Symbol(object.name)) + + def _print_LambertW(self, expr, exp=None): + arg0 = self._print(expr.args[0]) + exp = r"^{%s}" % (exp,) if exp is not None else "" + if len(expr.args) == 1: + result = r"W%s\left(%s\right)" % (exp, arg0) + else: + arg1 = self._print(expr.args[1]) + result = "W{0}_{{{1}}}\\left({2}\\right)".format(exp, arg1, arg0) + return result + + def _print_Expectation(self, expr): + return r"\operatorname{{E}}\left[{}\right]".format(self._print(expr.args[0])) + + def _print_Variance(self, expr): + return r"\operatorname{{Var}}\left({}\right)".format(self._print(expr.args[0])) + + def _print_Covariance(self, expr): + return r"\operatorname{{Cov}}\left({}\right)".format(", ".join(self._print(arg) for arg in expr.args)) + + def _print_Probability(self, expr): + return r"\operatorname{{P}}\left({}\right)".format(self._print(expr.args[0])) + + def _print_Morphism(self, morphism): + domain = self._print(morphism.domain) + codomain = self._print(morphism.codomain) + return "%s\\rightarrow %s" % (domain, codomain) + + def _print_TransferFunction(self, expr): + num, den = self._print(expr.num), self._print(expr.den) + return r"\frac{%s}{%s}" % (num, den) + + def _print_Series(self, expr): + args = list(expr.args) + parens = lambda x: self.parenthesize(x, precedence_traditional(expr), + False) + return ' '.join(map(parens, args)) + + def _print_MIMOSeries(self, expr): + from sympy.physics.control.lti import MIMOParallel + args = list(expr.args)[::-1] + parens = lambda x: self.parenthesize(x, precedence_traditional(expr), + False) if isinstance(x, MIMOParallel) else self._print(x) + return r"\cdot".join(map(parens, args)) + + def _print_Parallel(self, expr): + return ' + '.join(map(self._print, expr.args)) + + def _print_MIMOParallel(self, expr): + return ' + '.join(map(self._print, expr.args)) + + def _print_Feedback(self, expr): + from sympy.physics.control import TransferFunction, Series + + num, tf = expr.sys1, TransferFunction(1, 1, expr.var) + num_arg_list = list(num.args) if isinstance(num, Series) else [num] + den_arg_list = list(expr.sys2.args) if \ + isinstance(expr.sys2, Series) else [expr.sys2] + den_term_1 = tf + + if isinstance(num, Series) and isinstance(expr.sys2, Series): + den_term_2 = Series(*num_arg_list, *den_arg_list) + elif isinstance(num, Series) and isinstance(expr.sys2, TransferFunction): + if expr.sys2 == tf: + den_term_2 = Series(*num_arg_list) + else: + den_term_2 = tf, Series(*num_arg_list, expr.sys2) + elif isinstance(num, TransferFunction) and isinstance(expr.sys2, Series): + if num == tf: + den_term_2 = Series(*den_arg_list) + else: + den_term_2 = Series(num, *den_arg_list) + else: + if num == tf: + den_term_2 = Series(*den_arg_list) + elif expr.sys2 == tf: + den_term_2 = Series(*num_arg_list) + else: + den_term_2 = Series(*num_arg_list, *den_arg_list) + + numer = self._print(num) + denom_1 = self._print(den_term_1) + denom_2 = self._print(den_term_2) + _sign = "+" if expr.sign == -1 else "-" + + return r"\frac{%s}{%s %s %s}" % (numer, denom_1, _sign, denom_2) + + def _print_MIMOFeedback(self, expr): + from sympy.physics.control import MIMOSeries + inv_mat = self._print(MIMOSeries(expr.sys2, expr.sys1)) + sys1 = self._print(expr.sys1) + _sign = "+" if expr.sign == -1 else "-" + return r"\left(I_{\tau} %s %s\right)^{-1} \cdot %s" % (_sign, inv_mat, sys1) + + def _print_TransferFunctionMatrix(self, expr): + mat = self._print(expr._expr_mat) + return r"%s_\tau" % mat + + def _print_DFT(self, expr): + return r"\text{{{}}}_{{{}}}".format(expr.__class__.__name__, expr.n) + _print_IDFT = _print_DFT + + def _print_NamedMorphism(self, morphism): + pretty_name = self._print(Symbol(morphism.name)) + pretty_morphism = self._print_Morphism(morphism) + return "%s:%s" % (pretty_name, pretty_morphism) + + def _print_IdentityMorphism(self, morphism): + from sympy.categories import NamedMorphism + return self._print_NamedMorphism(NamedMorphism( + morphism.domain, morphism.codomain, "id")) + + def _print_CompositeMorphism(self, morphism): + # All components of the morphism have names and it is thus + # possible to build the name of the composite. + component_names_list = [self._print(Symbol(component.name)) for + component in morphism.components] + component_names_list.reverse() + component_names = "\\circ ".join(component_names_list) + ":" + + pretty_morphism = self._print_Morphism(morphism) + return component_names + pretty_morphism + + def _print_Category(self, morphism): + return r"\mathbf{{{}}}".format(self._print(Symbol(morphism.name))) + + def _print_Diagram(self, diagram): + if not diagram.premises: + # This is an empty diagram. + return self._print(S.EmptySet) + + latex_result = self._print(diagram.premises) + if diagram.conclusions: + latex_result += "\\Longrightarrow %s" % \ + self._print(diagram.conclusions) + + return latex_result + + def _print_DiagramGrid(self, grid): + latex_result = "\\begin{array}{%s}\n" % ("c" * grid.width) + + for i in range(grid.height): + for j in range(grid.width): + if grid[i, j]: + latex_result += latex(grid[i, j]) + latex_result += " " + if j != grid.width - 1: + latex_result += "& " + + if i != grid.height - 1: + latex_result += "\\\\" + latex_result += "\n" + + latex_result += "\\end{array}\n" + return latex_result + + def _print_FreeModule(self, M): + return '{{{}}}^{{{}}}'.format(self._print(M.ring), self._print(M.rank)) + + def _print_FreeModuleElement(self, m): + # Print as row vector for convenience, for now. + return r"\left[ {} \right]".format(",".join( + '{' + self._print(x) + '}' for x in m)) + + def _print_SubModule(self, m): + gens = [[self._print(m.ring.to_sympy(x)) for x in g] for g in m.gens] + curly = lambda o: r"{" + o + r"}" + square = lambda o: r"\left[ " + o + r" \right]" + gens_latex = ",".join(curly(square(",".join(curly(x) for x in g))) for g in gens) + return r"\left\langle {} \right\rangle".format(gens_latex) + + def _print_SubQuotientModule(self, m): + gens_latex = ",".join(["{" + self._print(g) + "}" for g in m.gens]) + return r"\left\langle {} \right\rangle".format(gens_latex) + + def _print_ModuleImplementedIdeal(self, m): + gens = [m.ring.to_sympy(x) for [x] in m._module.gens] + gens_latex = ",".join('{' + self._print(x) + '}' for x in gens) + return r"\left\langle {} \right\rangle".format(gens_latex) + + def _print_Quaternion(self, expr): + # TODO: This expression is potentially confusing, + # shall we print it as `Quaternion( ... )`? + s = [self.parenthesize(i, PRECEDENCE["Mul"], strict=True) + for i in expr.args] + a = [s[0]] + [i+" "+j for i, j in zip(s[1:], "ijk")] + return " + ".join(a) + + def _print_QuotientRing(self, R): + # TODO nicer fractions for few generators... + return r"\frac{{{}}}{{{}}}".format(self._print(R.ring), + self._print(R.base_ideal)) + + def _print_QuotientRingElement(self, x): + x_latex = self._print(x.ring.to_sympy(x)) + return r"{{{}}} + {{{}}}".format(x_latex, + self._print(x.ring.base_ideal)) + + def _print_QuotientModuleElement(self, m): + data = [m.module.ring.to_sympy(x) for x in m.data] + data_latex = r"\left[ {} \right]".format(",".join( + '{' + self._print(x) + '}' for x in data)) + return r"{{{}}} + {{{}}}".format(data_latex, + self._print(m.module.killed_module)) + + def _print_QuotientModule(self, M): + # TODO nicer fractions for few generators... + return r"\frac{{{}}}{{{}}}".format(self._print(M.base), + self._print(M.killed_module)) + + def _print_MatrixHomomorphism(self, h): + return r"{{{}}} : {{{}}} \to {{{}}}".format(self._print(h._sympy_matrix()), + self._print(h.domain), self._print(h.codomain)) + + def _print_Manifold(self, manifold): + name, supers, subs = self._split_super_sub(manifold.name.name) + + name = r'\text{%s}' % name + if supers: + name += "^{%s}" % " ".join(supers) + if subs: + name += "_{%s}" % " ".join(subs) + + return name + + def _print_Patch(self, patch): + return r'\text{%s}_{%s}' % (self._print(patch.name), self._print(patch.manifold)) + + def _print_CoordSystem(self, coordsys): + return r'\text{%s}^{\text{%s}}_{%s}' % ( + self._print(coordsys.name), self._print(coordsys.patch.name), self._print(coordsys.manifold) + ) + + def _print_CovarDerivativeOp(self, cvd): + return r'\mathbb{\nabla}_{%s}' % self._print(cvd._wrt) + + def _print_BaseScalarField(self, field): + string = field._coord_sys.symbols[field._index].name + return r'\mathbf{{{}}}'.format(self._print(Symbol(string))) + + def _print_BaseVectorField(self, field): + string = field._coord_sys.symbols[field._index].name + return r'\partial_{{{}}}'.format(self._print(Symbol(string))) + + def _print_Differential(self, diff): + field = diff._form_field + if hasattr(field, '_coord_sys'): + string = field._coord_sys.symbols[field._index].name + return r'\operatorname{{d}}{}'.format(self._print(Symbol(string))) + else: + string = self._print(field) + return r'\operatorname{{d}}\left({}\right)'.format(string) + + def _print_Tr(self, p): + # TODO: Handle indices + contents = self._print(p.args[0]) + return r'\operatorname{{tr}}\left({}\right)'.format(contents) + + def _print_totient(self, expr, exp=None): + if exp is not None: + return r'\left(\phi\left(%s\right)\right)^{%s}' % \ + (self._print(expr.args[0]), exp) + return r'\phi\left(%s\right)' % self._print(expr.args[0]) + + def _print_reduced_totient(self, expr, exp=None): + if exp is not None: + return r'\left(\lambda\left(%s\right)\right)^{%s}' % \ + (self._print(expr.args[0]), exp) + return r'\lambda\left(%s\right)' % self._print(expr.args[0]) + + def _print_divisor_sigma(self, expr, exp=None): + if len(expr.args) == 2: + tex = r"_%s\left(%s\right)" % tuple(map(self._print, + (expr.args[1], expr.args[0]))) + else: + tex = r"\left(%s\right)" % self._print(expr.args[0]) + if exp is not None: + return r"\sigma^{%s}%s" % (exp, tex) + return r"\sigma%s" % tex + + def _print_udivisor_sigma(self, expr, exp=None): + if len(expr.args) == 2: + tex = r"_%s\left(%s\right)" % tuple(map(self._print, + (expr.args[1], expr.args[0]))) + else: + tex = r"\left(%s\right)" % self._print(expr.args[0]) + if exp is not None: + return r"\sigma^*^{%s}%s" % (exp, tex) + return r"\sigma^*%s" % tex + + def _print_primenu(self, expr, exp=None): + if exp is not None: + return r'\left(\nu\left(%s\right)\right)^{%s}' % \ + (self._print(expr.args[0]), exp) + return r'\nu\left(%s\right)' % self._print(expr.args[0]) + + def _print_primeomega(self, expr, exp=None): + if exp is not None: + return r'\left(\Omega\left(%s\right)\right)^{%s}' % \ + (self._print(expr.args[0]), exp) + return r'\Omega\left(%s\right)' % self._print(expr.args[0]) + + def _print_Str(self, s): + return str(s.name) + + def _print_float(self, expr): + return self._print(Float(expr)) + + def _print_int(self, expr): + return str(expr) + + def _print_mpz(self, expr): + return str(expr) + + def _print_mpq(self, expr): + return str(expr) + + def _print_fmpz(self, expr): + return str(expr) + + def _print_fmpq(self, expr): + return str(expr) + + def _print_Predicate(self, expr): + return r"\operatorname{{Q}}_{{\text{{{}}}}}".format(latex_escape(str(expr.name))) + + def _print_AppliedPredicate(self, expr): + pred = expr.function + args = expr.arguments + pred_latex = self._print(pred) + args_latex = ', '.join([self._print(a) for a in args]) + return '%s(%s)' % (pred_latex, args_latex) + + def emptyPrinter(self, expr): + # default to just printing as monospace, like would normally be shown + s = super().emptyPrinter(expr) + + return r"\mathtt{\text{%s}}" % latex_escape(s) + + +def translate(s: str) -> str: + r''' + Check for a modifier ending the string. If present, convert the + modifier to latex and translate the rest recursively. + + Given a description of a Greek letter or other special character, + return the appropriate latex. + + Let everything else pass as given. + + >>> from sympy.printing.latex import translate + >>> translate('alphahatdotprime') + "{\\dot{\\hat{\\alpha}}}'" + ''' + # Process the rest + tex = tex_greek_dictionary.get(s) + if tex: + return tex + elif s.lower() in greek_letters_set: + return "\\" + s.lower() + elif s in other_symbols: + return "\\" + s + else: + # Process modifiers, if any, and recurse + for key in sorted(modifier_dict.keys(), key=len, reverse=True): + if s.lower().endswith(key) and len(s) > len(key): + return modifier_dict[key](translate(s[:-len(key)])) + return s + + + +@print_function(LatexPrinter) +def latex(expr, **settings): + r"""Convert the given expression to LaTeX string representation. + + Parameters + ========== + full_prec: boolean, optional + If set to True, a floating point number is printed with full precision. + fold_frac_powers : boolean, optional + Emit ``^{p/q}`` instead of ``^{\frac{p}{q}}`` for fractional powers. + fold_func_brackets : boolean, optional + Fold function brackets where applicable. + fold_short_frac : boolean, optional + Emit ``p / q`` instead of ``\frac{p}{q}`` when the denominator is + simple enough (at most two terms and no powers). The default value is + ``True`` for inline mode, ``False`` otherwise. + inv_trig_style : string, optional + How inverse trig functions should be displayed. Can be one of + ``'abbreviated'``, ``'full'``, or ``'power'``. Defaults to + ``'abbreviated'``. + itex : boolean, optional + Specifies if itex-specific syntax is used, including emitting + ``$$...$$``. + ln_notation : boolean, optional + If set to ``True``, ``\ln`` is used instead of default ``\log``. + long_frac_ratio : float or None, optional + The allowed ratio of the width of the numerator to the width of the + denominator before the printer breaks off long fractions. If ``None`` + (the default value), long fractions are not broken up. + mat_delim : string, optional + The delimiter to wrap around matrices. Can be one of ``'['``, ``'('``, + or the empty string ``''``. Defaults to ``'['``. + mat_str : string, optional + Which matrix environment string to emit. ``'smallmatrix'``, + ``'matrix'``, ``'array'``, etc. Defaults to ``'smallmatrix'`` for + inline mode, ``'matrix'`` for matrices of no more than 10 columns, and + ``'array'`` otherwise. + mode: string, optional + Specifies how the generated code will be delimited. ``mode`` can be one + of ``'plain'``, ``'inline'``, ``'equation'`` or ``'equation*'``. If + ``mode`` is set to ``'plain'``, then the resulting code will not be + delimited at all (this is the default). If ``mode`` is set to + ``'inline'`` then inline LaTeX ``$...$`` will be used. If ``mode`` is + set to ``'equation'`` or ``'equation*'``, the resulting code will be + enclosed in the ``equation`` or ``equation*`` environment (remember to + import ``amsmath`` for ``equation*``), unless the ``itex`` option is + set. In the latter case, the ``$$...$$`` syntax is used. + mul_symbol : string or None, optional + The symbol to use for multiplication. Can be one of ``None``, + ``'ldot'``, ``'dot'``, or ``'times'``. + order: string, optional + Any of the supported monomial orderings (currently ``'lex'``, + ``'grlex'``, or ``'grevlex'``), ``'old'``, and ``'none'``. This + parameter does nothing for `~.Mul` objects. Setting order to ``'old'`` + uses the compatibility ordering for ``~.Add`` defined in Printer. For + very large expressions, set the ``order`` keyword to ``'none'`` if + speed is a concern. + symbol_names : dictionary of strings mapped to symbols, optional + Dictionary of symbols and the custom strings they should be emitted as. + root_notation : boolean, optional + If set to ``False``, exponents of the form 1/n are printed in fractonal + form. Default is ``True``, to print exponent in root form. + mat_symbol_style : string, optional + Can be either ``'plain'`` (default) or ``'bold'``. If set to + ``'bold'``, a `~.MatrixSymbol` A will be printed as ``\mathbf{A}``, + otherwise as ``A``. + imaginary_unit : string, optional + String to use for the imaginary unit. Defined options are ``'i'`` + (default) and ``'j'``. Adding ``r`` or ``t`` in front gives ``\mathrm`` + or ``\text``, so ``'ri'`` leads to ``\mathrm{i}`` which gives + `\mathrm{i}`. + gothic_re_im : boolean, optional + If set to ``True``, `\Re` and `\Im` is used for ``re`` and ``im``, respectively. + The default is ``False`` leading to `\operatorname{re}` and `\operatorname{im}`. + decimal_separator : string, optional + Specifies what separator to use to separate the whole and fractional parts of a + floating point number as in `2.5` for the default, ``period`` or `2{,}5` + when ``comma`` is specified. Lists, sets, and tuple are printed with semicolon + separating the elements when ``comma`` is chosen. For example, [1; 2; 3] when + ``comma`` is chosen and [1,2,3] for when ``period`` is chosen. + parenthesize_super : boolean, optional + If set to ``False``, superscripted expressions will not be parenthesized when + powered. Default is ``True``, which parenthesizes the expression when powered. + min: Integer or None, optional + Sets the lower bound for the exponent to print floating point numbers in + fixed-point format. + max: Integer or None, optional + Sets the upper bound for the exponent to print floating point numbers in + fixed-point format. + diff_operator: string, optional + String to use for differential operator. Default is ``'d'``, to print in italic + form. ``'rd'``, ``'td'`` are shortcuts for ``\mathrm{d}`` and ``\text{d}``. + adjoint_style: string, optional + String to use for the adjoint symbol. Defined options are ``'dagger'`` + (default),``'star'``, and ``'hermitian'``. + + Notes + ===== + + Not using a print statement for printing, results in double backslashes for + latex commands since that's the way Python escapes backslashes in strings. + + >>> from sympy import latex, Rational + >>> from sympy.abc import tau + >>> latex((2*tau)**Rational(7,2)) + '8 \\sqrt{2} \\tau^{\\frac{7}{2}}' + >>> print(latex((2*tau)**Rational(7,2))) + 8 \sqrt{2} \tau^{\frac{7}{2}} + + Examples + ======== + + >>> from sympy import latex, pi, sin, asin, Integral, Matrix, Rational, log + >>> from sympy.abc import x, y, mu, r, tau + + Basic usage: + + >>> print(latex((2*tau)**Rational(7,2))) + 8 \sqrt{2} \tau^{\frac{7}{2}} + + ``mode`` and ``itex`` options: + + >>> print(latex((2*mu)**Rational(7,2), mode='plain')) + 8 \sqrt{2} \mu^{\frac{7}{2}} + >>> print(latex((2*tau)**Rational(7,2), mode='inline')) + $8 \sqrt{2} \tau^{7 / 2}$ + >>> print(latex((2*mu)**Rational(7,2), mode='equation*')) + \begin{equation*}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation*} + >>> print(latex((2*mu)**Rational(7,2), mode='equation')) + \begin{equation}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation} + >>> print(latex((2*mu)**Rational(7,2), mode='equation', itex=True)) + $$8 \sqrt{2} \mu^{\frac{7}{2}}$$ + >>> print(latex((2*mu)**Rational(7,2), mode='plain')) + 8 \sqrt{2} \mu^{\frac{7}{2}} + >>> print(latex((2*tau)**Rational(7,2), mode='inline')) + $8 \sqrt{2} \tau^{7 / 2}$ + >>> print(latex((2*mu)**Rational(7,2), mode='equation*')) + \begin{equation*}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation*} + >>> print(latex((2*mu)**Rational(7,2), mode='equation')) + \begin{equation}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation} + >>> print(latex((2*mu)**Rational(7,2), mode='equation', itex=True)) + $$8 \sqrt{2} \mu^{\frac{7}{2}}$$ + + Fraction options: + + >>> print(latex((2*tau)**Rational(7,2), fold_frac_powers=True)) + 8 \sqrt{2} \tau^{7/2} + >>> print(latex((2*tau)**sin(Rational(7,2)))) + \left(2 \tau\right)^{\sin{\left(\frac{7}{2} \right)}} + >>> print(latex((2*tau)**sin(Rational(7,2)), fold_func_brackets=True)) + \left(2 \tau\right)^{\sin {\frac{7}{2}}} + >>> print(latex(3*x**2/y)) + \frac{3 x^{2}}{y} + >>> print(latex(3*x**2/y, fold_short_frac=True)) + 3 x^{2} / y + >>> print(latex(Integral(r, r)/2/pi, long_frac_ratio=2)) + \frac{\int r\, dr}{2 \pi} + >>> print(latex(Integral(r, r)/2/pi, long_frac_ratio=0)) + \frac{1}{2 \pi} \int r\, dr + + Multiplication options: + + >>> print(latex((2*tau)**sin(Rational(7,2)), mul_symbol="times")) + \left(2 \times \tau\right)^{\sin{\left(\frac{7}{2} \right)}} + + Trig options: + + >>> print(latex(asin(Rational(7,2)))) + \operatorname{asin}{\left(\frac{7}{2} \right)} + >>> print(latex(asin(Rational(7,2)), inv_trig_style="full")) + \arcsin{\left(\frac{7}{2} \right)} + >>> print(latex(asin(Rational(7,2)), inv_trig_style="power")) + \sin^{-1}{\left(\frac{7}{2} \right)} + + Matrix options: + + >>> print(latex(Matrix(2, 1, [x, y]))) + \left[\begin{matrix}x\\y\end{matrix}\right] + >>> print(latex(Matrix(2, 1, [x, y]), mat_str = "array")) + \left[\begin{array}{c}x\\y\end{array}\right] + >>> print(latex(Matrix(2, 1, [x, y]), mat_delim="(")) + \left(\begin{matrix}x\\y\end{matrix}\right) + + Custom printing of symbols: + + >>> print(latex(x**2, symbol_names={x: 'x_i'})) + x_i^{2} + + Logarithms: + + >>> print(latex(log(10))) + \log{\left(10 \right)} + >>> print(latex(log(10), ln_notation=True)) + \ln{\left(10 \right)} + + ``latex()`` also supports the builtin container types :class:`list`, + :class:`tuple`, and :class:`dict`: + + >>> print(latex([2/x, y], mode='inline')) + $\left[ 2 / x, \ y\right]$ + + Unsupported types are rendered as monospaced plaintext: + + >>> print(latex(int)) + \mathtt{\text{}} + >>> print(latex("plain % text")) + \mathtt{\text{plain \% text}} + + See :ref:`printer_method_example` for an example of how to override + this behavior for your own types by implementing ``_latex``. + + .. versionchanged:: 1.7.0 + Unsupported types no longer have their ``str`` representation treated as valid latex. + + """ + return LatexPrinter(settings).doprint(expr) + + +def print_latex(expr, **settings): + """Prints LaTeX representation of the given expression. Takes the same + settings as ``latex()``.""" + + print(latex(expr, **settings)) + + +def multiline_latex(lhs, rhs, terms_per_line=1, environment="align*", use_dots=False, **settings): + r""" + This function generates a LaTeX equation with a multiline right-hand side + in an ``align*``, ``eqnarray`` or ``IEEEeqnarray`` environment. + + Parameters + ========== + + lhs : Expr + Left-hand side of equation + + rhs : Expr + Right-hand side of equation + + terms_per_line : integer, optional + Number of terms per line to print. Default is 1. + + environment : "string", optional + Which LaTeX wnvironment to use for the output. Options are "align*" + (default), "eqnarray", and "IEEEeqnarray". + + use_dots : boolean, optional + If ``True``, ``\\dots`` is added to the end of each line. Default is ``False``. + + Examples + ======== + + >>> from sympy import multiline_latex, symbols, sin, cos, exp, log, I + >>> x, y, alpha = symbols('x y alpha') + >>> expr = sin(alpha*y) + exp(I*alpha) - cos(log(y)) + >>> print(multiline_latex(x, expr)) + \begin{align*} + x = & e^{i \alpha} \\ + & + \sin{\left(\alpha y \right)} \\ + & - \cos{\left(\log{\left(y \right)} \right)} + \end{align*} + + Using at most two terms per line: + >>> print(multiline_latex(x, expr, 2)) + \begin{align*} + x = & e^{i \alpha} + \sin{\left(\alpha y \right)} \\ + & - \cos{\left(\log{\left(y \right)} \right)} + \end{align*} + + Using ``eqnarray`` and dots: + >>> print(multiline_latex(x, expr, terms_per_line=2, environment="eqnarray", use_dots=True)) + \begin{eqnarray} + x & = & e^{i \alpha} + \sin{\left(\alpha y \right)} \dots\nonumber\\ + & & - \cos{\left(\log{\left(y \right)} \right)} + \end{eqnarray} + + Using ``IEEEeqnarray``: + >>> print(multiline_latex(x, expr, environment="IEEEeqnarray")) + \begin{IEEEeqnarray}{rCl} + x & = & e^{i \alpha} \nonumber\\ + & & + \sin{\left(\alpha y \right)} \nonumber\\ + & & - \cos{\left(\log{\left(y \right)} \right)} + \end{IEEEeqnarray} + + Notes + ===== + + All optional parameters from ``latex`` can also be used. + + """ + + # Based on code from https://github.com/sympy/sympy/issues/3001 + l = LatexPrinter(**settings) + if environment == "eqnarray": + result = r'\begin{eqnarray}' + '\n' + first_term = '& = &' + nonumber = r'\nonumber' + end_term = '\n\\end{eqnarray}' + doubleet = True + elif environment == "IEEEeqnarray": + result = r'\begin{IEEEeqnarray}{rCl}' + '\n' + first_term = '& = &' + nonumber = r'\nonumber' + end_term = '\n\\end{IEEEeqnarray}' + doubleet = True + elif environment == "align*": + result = r'\begin{align*}' + '\n' + first_term = '= &' + nonumber = '' + end_term = '\n\\end{align*}' + doubleet = False + else: + raise ValueError("Unknown environment: {}".format(environment)) + dots = '' + if use_dots: + dots=r'\dots' + terms = rhs.as_ordered_terms() + n_terms = len(terms) + term_count = 1 + for i in range(n_terms): + term = terms[i] + term_start = '' + term_end = '' + sign = '+' + if term_count > terms_per_line: + if doubleet: + term_start = '& & ' + else: + term_start = '& ' + term_count = 1 + if term_count == terms_per_line: + # End of line + if i < n_terms-1: + # There are terms remaining + term_end = dots + nonumber + r'\\' + '\n' + else: + term_end = '' + + if term.as_ordered_factors()[0] == -1: + term = -1*term + sign = r'-' + if i == 0: # beginning + if sign == '+': + sign = '' + result += r'{:s} {:s}{:s} {:s} {:s}'.format(l.doprint(lhs), + first_term, sign, l.doprint(term), term_end) + else: + result += r'{:s}{:s} {:s} {:s}'.format(term_start, sign, + l.doprint(term), term_end) + term_count += 1 + result += end_term + return result diff --git a/venv/lib/python3.10/site-packages/sympy/printing/llvmjitcode.py b/venv/lib/python3.10/site-packages/sympy/printing/llvmjitcode.py new file mode 100644 index 0000000000000000000000000000000000000000..0e657f3b854be62fb4b6b4be96f82579b718f6ca --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/llvmjitcode.py @@ -0,0 +1,490 @@ +''' +Use llvmlite to create executable functions from SymPy expressions + +This module requires llvmlite (https://github.com/numba/llvmlite). +''' + +import ctypes + +from sympy.external import import_module +from sympy.printing.printer import Printer +from sympy.core.singleton import S +from sympy.tensor.indexed import IndexedBase +from sympy.utilities.decorator import doctest_depends_on + +llvmlite = import_module('llvmlite') +if llvmlite: + ll = import_module('llvmlite.ir').ir + llvm = import_module('llvmlite.binding').binding + llvm.initialize() + llvm.initialize_native_target() + llvm.initialize_native_asmprinter() + + +__doctest_requires__ = {('llvm_callable'): ['llvmlite']} + + +class LLVMJitPrinter(Printer): + '''Convert expressions to LLVM IR''' + def __init__(self, module, builder, fn, *args, **kwargs): + self.func_arg_map = kwargs.pop("func_arg_map", {}) + if not llvmlite: + raise ImportError("llvmlite is required for LLVMJITPrinter") + super().__init__(*args, **kwargs) + self.fp_type = ll.DoubleType() + self.module = module + self.builder = builder + self.fn = fn + self.ext_fn = {} # keep track of wrappers to external functions + self.tmp_var = {} + + def _add_tmp_var(self, name, value): + self.tmp_var[name] = value + + def _print_Number(self, n): + return ll.Constant(self.fp_type, float(n)) + + def _print_Integer(self, expr): + return ll.Constant(self.fp_type, float(expr.p)) + + def _print_Symbol(self, s): + val = self.tmp_var.get(s) + if not val: + # look up parameter with name s + val = self.func_arg_map.get(s) + if not val: + raise LookupError("Symbol not found: %s" % s) + return val + + def _print_Pow(self, expr): + base0 = self._print(expr.base) + if expr.exp == S.NegativeOne: + return self.builder.fdiv(ll.Constant(self.fp_type, 1.0), base0) + if expr.exp == S.Half: + fn = self.ext_fn.get("sqrt") + if not fn: + fn_type = ll.FunctionType(self.fp_type, [self.fp_type]) + fn = ll.Function(self.module, fn_type, "sqrt") + self.ext_fn["sqrt"] = fn + return self.builder.call(fn, [base0], "sqrt") + if expr.exp == 2: + return self.builder.fmul(base0, base0) + + exp0 = self._print(expr.exp) + fn = self.ext_fn.get("pow") + if not fn: + fn_type = ll.FunctionType(self.fp_type, [self.fp_type, self.fp_type]) + fn = ll.Function(self.module, fn_type, "pow") + self.ext_fn["pow"] = fn + return self.builder.call(fn, [base0, exp0], "pow") + + def _print_Mul(self, expr): + nodes = [self._print(a) for a in expr.args] + e = nodes[0] + for node in nodes[1:]: + e = self.builder.fmul(e, node) + return e + + def _print_Add(self, expr): + nodes = [self._print(a) for a in expr.args] + e = nodes[0] + for node in nodes[1:]: + e = self.builder.fadd(e, node) + return e + + # TODO - assumes all called functions take one double precision argument. + # Should have a list of math library functions to validate this. + def _print_Function(self, expr): + name = expr.func.__name__ + e0 = self._print(expr.args[0]) + fn = self.ext_fn.get(name) + if not fn: + fn_type = ll.FunctionType(self.fp_type, [self.fp_type]) + fn = ll.Function(self.module, fn_type, name) + self.ext_fn[name] = fn + return self.builder.call(fn, [e0], name) + + def emptyPrinter(self, expr): + raise TypeError("Unsupported type for LLVM JIT conversion: %s" + % type(expr)) + + +# Used when parameters are passed by array. Often used in callbacks to +# handle a variable number of parameters. +class LLVMJitCallbackPrinter(LLVMJitPrinter): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _print_Indexed(self, expr): + array, idx = self.func_arg_map[expr.base] + offset = int(expr.indices[0].evalf()) + array_ptr = self.builder.gep(array, [ll.Constant(ll.IntType(32), offset)]) + fp_array_ptr = self.builder.bitcast(array_ptr, ll.PointerType(self.fp_type)) + value = self.builder.load(fp_array_ptr) + return value + + def _print_Symbol(self, s): + val = self.tmp_var.get(s) + if val: + return val + + array, idx = self.func_arg_map.get(s, [None, 0]) + if not array: + raise LookupError("Symbol not found: %s" % s) + array_ptr = self.builder.gep(array, [ll.Constant(ll.IntType(32), idx)]) + fp_array_ptr = self.builder.bitcast(array_ptr, + ll.PointerType(self.fp_type)) + value = self.builder.load(fp_array_ptr) + return value + + +# ensure lifetime of the execution engine persists (else call to compiled +# function will seg fault) +exe_engines = [] + +# ensure names for generated functions are unique +link_names = set() +current_link_suffix = 0 + + +class LLVMJitCode: + def __init__(self, signature): + self.signature = signature + self.fp_type = ll.DoubleType() + self.module = ll.Module('mod1') + self.fn = None + self.llvm_arg_types = [] + self.llvm_ret_type = self.fp_type + self.param_dict = {} # map symbol name to LLVM function argument + self.link_name = '' + + def _from_ctype(self, ctype): + if ctype == ctypes.c_int: + return ll.IntType(32) + if ctype == ctypes.c_double: + return self.fp_type + if ctype == ctypes.POINTER(ctypes.c_double): + return ll.PointerType(self.fp_type) + if ctype == ctypes.c_void_p: + return ll.PointerType(ll.IntType(32)) + if ctype == ctypes.py_object: + return ll.PointerType(ll.IntType(32)) + + print("Unhandled ctype = %s" % str(ctype)) + + def _create_args(self, func_args): + """Create types for function arguments""" + self.llvm_ret_type = self._from_ctype(self.signature.ret_type) + self.llvm_arg_types = \ + [self._from_ctype(a) for a in self.signature.arg_ctypes] + + def _create_function_base(self): + """Create function with name and type signature""" + global current_link_suffix + default_link_name = 'jit_func' + current_link_suffix += 1 + self.link_name = default_link_name + str(current_link_suffix) + link_names.add(self.link_name) + + fn_type = ll.FunctionType(self.llvm_ret_type, self.llvm_arg_types) + self.fn = ll.Function(self.module, fn_type, name=self.link_name) + + def _create_param_dict(self, func_args): + """Mapping of symbolic values to function arguments""" + for i, a in enumerate(func_args): + self.fn.args[i].name = str(a) + self.param_dict[a] = self.fn.args[i] + + def _create_function(self, expr): + """Create function body and return LLVM IR""" + bb_entry = self.fn.append_basic_block('entry') + builder = ll.IRBuilder(bb_entry) + + lj = LLVMJitPrinter(self.module, builder, self.fn, + func_arg_map=self.param_dict) + + ret = self._convert_expr(lj, expr) + lj.builder.ret(self._wrap_return(lj, ret)) + + strmod = str(self.module) + return strmod + + def _wrap_return(self, lj, vals): + # Return a single double if there is one return value, + # else return a tuple of doubles. + + # Don't wrap return value in this case + if self.signature.ret_type == ctypes.c_double: + return vals[0] + + # Use this instead of a real PyObject* + void_ptr = ll.PointerType(ll.IntType(32)) + + # Create a wrapped double: PyObject* PyFloat_FromDouble(double v) + wrap_type = ll.FunctionType(void_ptr, [self.fp_type]) + wrap_fn = ll.Function(lj.module, wrap_type, "PyFloat_FromDouble") + + wrapped_vals = [lj.builder.call(wrap_fn, [v]) for v in vals] + if len(vals) == 1: + final_val = wrapped_vals[0] + else: + # Create a tuple: PyObject* PyTuple_Pack(Py_ssize_t n, ...) + + # This should be Py_ssize_t + tuple_arg_types = [ll.IntType(32)] + + tuple_arg_types.extend([void_ptr]*len(vals)) + tuple_type = ll.FunctionType(void_ptr, tuple_arg_types) + tuple_fn = ll.Function(lj.module, tuple_type, "PyTuple_Pack") + + tuple_args = [ll.Constant(ll.IntType(32), len(wrapped_vals))] + tuple_args.extend(wrapped_vals) + + final_val = lj.builder.call(tuple_fn, tuple_args) + + return final_val + + def _convert_expr(self, lj, expr): + try: + # Match CSE return data structure. + if len(expr) == 2: + tmp_exprs = expr[0] + final_exprs = expr[1] + if len(final_exprs) != 1 and self.signature.ret_type == ctypes.c_double: + raise NotImplementedError("Return of multiple expressions not supported for this callback") + for name, e in tmp_exprs: + val = lj._print(e) + lj._add_tmp_var(name, val) + except TypeError: + final_exprs = [expr] + + vals = [lj._print(e) for e in final_exprs] + + return vals + + def _compile_function(self, strmod): + llmod = llvm.parse_assembly(strmod) + + pmb = llvm.create_pass_manager_builder() + pmb.opt_level = 2 + pass_manager = llvm.create_module_pass_manager() + pmb.populate(pass_manager) + + pass_manager.run(llmod) + + target_machine = \ + llvm.Target.from_default_triple().create_target_machine() + exe_eng = llvm.create_mcjit_compiler(llmod, target_machine) + exe_eng.finalize_object() + exe_engines.append(exe_eng) + + if False: + print("Assembly") + print(target_machine.emit_assembly(llmod)) + + fptr = exe_eng.get_function_address(self.link_name) + + return fptr + + +class LLVMJitCodeCallback(LLVMJitCode): + def __init__(self, signature): + super().__init__(signature) + + def _create_param_dict(self, func_args): + for i, a in enumerate(func_args): + if isinstance(a, IndexedBase): + self.param_dict[a] = (self.fn.args[i], i) + self.fn.args[i].name = str(a) + else: + self.param_dict[a] = (self.fn.args[self.signature.input_arg], + i) + + def _create_function(self, expr): + """Create function body and return LLVM IR""" + bb_entry = self.fn.append_basic_block('entry') + builder = ll.IRBuilder(bb_entry) + + lj = LLVMJitCallbackPrinter(self.module, builder, self.fn, + func_arg_map=self.param_dict) + + ret = self._convert_expr(lj, expr) + + if self.signature.ret_arg: + output_fp_ptr = builder.bitcast(self.fn.args[self.signature.ret_arg], + ll.PointerType(self.fp_type)) + for i, val in enumerate(ret): + index = ll.Constant(ll.IntType(32), i) + output_array_ptr = builder.gep(output_fp_ptr, [index]) + builder.store(val, output_array_ptr) + builder.ret(ll.Constant(ll.IntType(32), 0)) # return success + else: + lj.builder.ret(self._wrap_return(lj, ret)) + + strmod = str(self.module) + return strmod + + +class CodeSignature: + def __init__(self, ret_type): + self.ret_type = ret_type + self.arg_ctypes = [] + + # Input argument array element index + self.input_arg = 0 + + # For the case output value is referenced through a parameter rather + # than the return value + self.ret_arg = None + + +def _llvm_jit_code(args, expr, signature, callback_type): + """Create a native code function from a SymPy expression""" + if callback_type is None: + jit = LLVMJitCode(signature) + else: + jit = LLVMJitCodeCallback(signature) + + jit._create_args(args) + jit._create_function_base() + jit._create_param_dict(args) + strmod = jit._create_function(expr) + if False: + print("LLVM IR") + print(strmod) + fptr = jit._compile_function(strmod) + return fptr + + +@doctest_depends_on(modules=('llvmlite', 'scipy')) +def llvm_callable(args, expr, callback_type=None): + '''Compile function from a SymPy expression + + Expressions are evaluated using double precision arithmetic. + Some single argument math functions (exp, sin, cos, etc.) are supported + in expressions. + + Parameters + ========== + + args : List of Symbol + Arguments to the generated function. Usually the free symbols in + the expression. Currently each one is assumed to convert to + a double precision scalar. + expr : Expr, or (Replacements, Expr) as returned from 'cse' + Expression to compile. + callback_type : string + Create function with signature appropriate to use as a callback. + Currently supported: + 'scipy.integrate' + 'scipy.integrate.test' + 'cubature' + + Returns + ======= + + Compiled function that can evaluate the expression. + + Examples + ======== + + >>> import sympy.printing.llvmjitcode as jit + >>> from sympy.abc import a + >>> e = a*a + a + 1 + >>> e1 = jit.llvm_callable([a], e) + >>> e.subs(a, 1.1) # Evaluate via substitution + 3.31000000000000 + >>> e1(1.1) # Evaluate using JIT-compiled code + 3.3100000000000005 + + + Callbacks for integration functions can be JIT compiled. + + >>> import sympy.printing.llvmjitcode as jit + >>> from sympy.abc import a + >>> from sympy import integrate + >>> from scipy.integrate import quad + >>> e = a*a + >>> e1 = jit.llvm_callable([a], e, callback_type='scipy.integrate') + >>> integrate(e, (a, 0.0, 2.0)) + 2.66666666666667 + >>> quad(e1, 0.0, 2.0)[0] + 2.66666666666667 + + The 'cubature' callback is for the Python wrapper around the + cubature package ( https://github.com/saullocastro/cubature ) + and ( http://ab-initio.mit.edu/wiki/index.php/Cubature ) + + There are two signatures for the SciPy integration callbacks. + The first ('scipy.integrate') is the function to be passed to the + integration routine, and will pass the signature checks. + The second ('scipy.integrate.test') is only useful for directly calling + the function using ctypes variables. It will not pass the signature checks + for scipy.integrate. + + The return value from the cse module can also be compiled. This + can improve the performance of the compiled function. If multiple + expressions are given to cse, the compiled function returns a tuple. + The 'cubature' callback handles multiple expressions (set `fdim` + to match in the integration call.) + + >>> import sympy.printing.llvmjitcode as jit + >>> from sympy import cse + >>> from sympy.abc import x,y + >>> e1 = x*x + y*y + >>> e2 = 4*(x*x + y*y) + 8.0 + >>> after_cse = cse([e1,e2]) + >>> after_cse + ([(x0, x**2), (x1, y**2)], [x0 + x1, 4*x0 + 4*x1 + 8.0]) + >>> j1 = jit.llvm_callable([x,y], after_cse) + >>> j1(1.0, 2.0) + (5.0, 28.0) + ''' + + if not llvmlite: + raise ImportError("llvmlite is required for llvmjitcode") + + signature = CodeSignature(ctypes.py_object) + + arg_ctypes = [] + if callback_type is None: + for _ in args: + arg_ctype = ctypes.c_double + arg_ctypes.append(arg_ctype) + elif callback_type in ('scipy.integrate', 'scipy.integrate.test'): + signature.ret_type = ctypes.c_double + arg_ctypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_double)] + arg_ctypes_formal = [ctypes.c_int, ctypes.c_double] + signature.input_arg = 1 + elif callback_type == 'cubature': + arg_ctypes = [ctypes.c_int, + ctypes.POINTER(ctypes.c_double), + ctypes.c_void_p, + ctypes.c_int, + ctypes.POINTER(ctypes.c_double) + ] + signature.ret_type = ctypes.c_int + signature.input_arg = 1 + signature.ret_arg = 4 + else: + raise ValueError("Unknown callback type: %s" % callback_type) + + signature.arg_ctypes = arg_ctypes + + fptr = _llvm_jit_code(args, expr, signature, callback_type) + + if callback_type and callback_type == 'scipy.integrate': + arg_ctypes = arg_ctypes_formal + + # PYFUNCTYPE holds the GIL which is needed to prevent a segfault when + # calling PyFloat_FromDouble on Python 3.10. Probably it is better to use + # ctypes.c_double when returning a float rather than using ctypes.py_object + # and returning a PyFloat from inside the jitted function (i.e. let ctypes + # handle the conversion from double to PyFloat). + if signature.ret_type == ctypes.py_object: + FUNCTYPE = ctypes.PYFUNCTYPE + else: + FUNCTYPE = ctypes.CFUNCTYPE + + cfunc = FUNCTYPE(signature.ret_type, *arg_ctypes)(fptr) + return cfunc diff --git a/venv/lib/python3.10/site-packages/sympy/printing/maple.py b/venv/lib/python3.10/site-packages/sympy/printing/maple.py new file mode 100644 index 0000000000000000000000000000000000000000..2c937cd262ab7f3ee5f32b3f4b5eb5633bc6bb3c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/maple.py @@ -0,0 +1,311 @@ +""" +Maple code printer + +The MapleCodePrinter converts single SymPy expressions into single +Maple expressions, using the functions defined in the Maple objects where possible. + + +FIXME: This module is still under actively developed. Some functions may be not completed. +""" + +from sympy.core import S +from sympy.core.numbers import Integer, IntegerConstant, equal_valued +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import precedence, PRECEDENCE + +import sympy + +_known_func_same_name = ( + 'sin', 'cos', 'tan', 'sec', 'csc', 'cot', 'sinh', 'cosh', 'tanh', 'sech', + 'csch', 'coth', 'exp', 'floor', 'factorial', 'bernoulli', 'euler', + 'fibonacci', 'gcd', 'lcm', 'conjugate', 'Ci', 'Chi', 'Ei', 'Li', 'Si', 'Shi', + 'erf', 'erfc', 'harmonic', 'LambertW', + 'sqrt', # For automatic rewrites +) + +known_functions = { + # SymPy -> Maple + 'Abs': 'abs', + 'log': 'ln', + 'asin': 'arcsin', + 'acos': 'arccos', + 'atan': 'arctan', + 'asec': 'arcsec', + 'acsc': 'arccsc', + 'acot': 'arccot', + 'asinh': 'arcsinh', + 'acosh': 'arccosh', + 'atanh': 'arctanh', + 'asech': 'arcsech', + 'acsch': 'arccsch', + 'acoth': 'arccoth', + 'ceiling': 'ceil', + 'Max' : 'max', + 'Min' : 'min', + + 'factorial2': 'doublefactorial', + 'RisingFactorial': 'pochhammer', + 'besseli': 'BesselI', + 'besselj': 'BesselJ', + 'besselk': 'BesselK', + 'bessely': 'BesselY', + 'hankelh1': 'HankelH1', + 'hankelh2': 'HankelH2', + 'airyai': 'AiryAi', + 'airybi': 'AiryBi', + 'appellf1': 'AppellF1', + 'fresnelc': 'FresnelC', + 'fresnels': 'FresnelS', + 'lerchphi' : 'LerchPhi', +} + +for _func in _known_func_same_name: + known_functions[_func] = _func + +number_symbols = { + # SymPy -> Maple + S.Pi: 'Pi', + S.Exp1: 'exp(1)', + S.Catalan: 'Catalan', + S.EulerGamma: 'gamma', + S.GoldenRatio: '(1/2 + (1/2)*sqrt(5))' +} + +spec_relational_ops = { + # SymPy -> Maple + '==': '=', + '!=': '<>' +} + +not_supported_symbol = [ + S.ComplexInfinity +] + +class MapleCodePrinter(CodePrinter): + """ + Printer which converts a SymPy expression into a maple code. + """ + printmethod = "_maple" + language = "maple" + + _operators = { + 'and': 'and', + 'or': 'or', + 'not': 'not ', + } + + _default_settings = dict(CodePrinter._default_settings, **{ + 'inline': True, + 'allow_unknown_functions': True, + }) + + def __init__(self, settings=None): + if settings is None: + settings = {} + super().__init__(settings) + self.known_functions = dict(known_functions) + userfuncs = settings.get('user_functions', {}) + self.known_functions.update(userfuncs) + + def _get_statement(self, codestring): + return "%s;" % codestring + + def _get_comment(self, text): + return "# {}".format(text) + + def _declare_number_const(self, name, value): + return "{} := {};".format(name, + value.evalf(self._settings['precision'])) + + def _format_code(self, lines): + return lines + + def _print_tuple(self, expr): + return self._print(list(expr)) + + def _print_Tuple(self, expr): + return self._print(list(expr)) + + def _print_Assignment(self, expr): + lhs = self._print(expr.lhs) + rhs = self._print(expr.rhs) + return "{lhs} := {rhs}".format(lhs=lhs, rhs=rhs) + + def _print_Pow(self, expr, **kwargs): + PREC = precedence(expr) + if equal_valued(expr.exp, -1): + return '1/%s' % (self.parenthesize(expr.base, PREC)) + elif equal_valued(expr.exp, 0.5): + return 'sqrt(%s)' % self._print(expr.base) + elif equal_valued(expr.exp, -0.5): + return '1/sqrt(%s)' % self._print(expr.base) + else: + return '{base}^{exp}'.format( + base=self.parenthesize(expr.base, PREC), + exp=self.parenthesize(expr.exp, PREC)) + + def _print_Piecewise(self, expr): + if (expr.args[-1].cond is not True) and (expr.args[-1].cond != S.BooleanTrue): + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + _coup_list = [ + ("{c}, {e}".format(c=self._print(c), + e=self._print(e)) if c is not True and c is not S.BooleanTrue else "{e}".format( + e=self._print(e))) + for e, c in expr.args] + _inbrace = ', '.join(_coup_list) + return 'piecewise({_inbrace})'.format(_inbrace=_inbrace) + + def _print_Rational(self, expr): + p, q = int(expr.p), int(expr.q) + return "{p}/{q}".format(p=str(p), q=str(q)) + + def _print_Relational(self, expr): + PREC=precedence(expr) + lhs_code = self.parenthesize(expr.lhs, PREC) + rhs_code = self.parenthesize(expr.rhs, PREC) + op = expr.rel_op + if op in spec_relational_ops: + op = spec_relational_ops[op] + return "{lhs} {rel_op} {rhs}".format(lhs=lhs_code, rel_op=op, rhs=rhs_code) + + def _print_NumberSymbol(self, expr): + return number_symbols[expr] + + def _print_NegativeInfinity(self, expr): + return '-infinity' + + def _print_Infinity(self, expr): + return 'infinity' + + def _print_BooleanTrue(self, expr): + return "true" + + def _print_BooleanFalse(self, expr): + return "false" + + def _print_bool(self, expr): + return 'true' if expr else 'false' + + def _print_NaN(self, expr): + return 'undefined' + + def _get_matrix(self, expr, sparse=False): + if S.Zero in expr.shape: + _strM = 'Matrix([], storage = {storage})'.format( + storage='sparse' if sparse else 'rectangular') + else: + _strM = 'Matrix({list}, storage = {storage})'.format( + list=self._print(expr.tolist()), + storage='sparse' if sparse else 'rectangular') + return _strM + + def _print_MatrixElement(self, expr): + return "{parent}[{i_maple}, {j_maple}]".format( + parent=self.parenthesize(expr.parent, PRECEDENCE["Atom"], strict=True), + i_maple=self._print(expr.i + 1), + j_maple=self._print(expr.j + 1)) + + def _print_MatrixBase(self, expr): + return self._get_matrix(expr, sparse=False) + + def _print_SparseRepMatrix(self, expr): + return self._get_matrix(expr, sparse=True) + + def _print_Identity(self, expr): + if isinstance(expr.rows, (Integer, IntegerConstant)): + return self._print(sympy.SparseMatrix(expr)) + else: + return "Matrix({var_size}, shape = identity)".format(var_size=self._print(expr.rows)) + + def _print_MatMul(self, expr): + PREC=precedence(expr) + _fact_list = list(expr.args) + _const = None + if not isinstance(_fact_list[0], (sympy.MatrixBase, sympy.MatrixExpr, + sympy.MatrixSlice, sympy.MatrixSymbol)): + _const, _fact_list = _fact_list[0], _fact_list[1:] + + if _const is None or _const == 1: + return '.'.join(self.parenthesize(_m, PREC) for _m in _fact_list) + else: + return '{c}*{m}'.format(c=_const, m='.'.join(self.parenthesize(_m, PREC) for _m in _fact_list)) + + def _print_MatPow(self, expr): + # This function requires LinearAlgebra Function in Maple + return 'MatrixPower({A}, {n})'.format(A=self._print(expr.base), n=self._print(expr.exp)) + + def _print_HadamardProduct(self, expr): + PREC = precedence(expr) + _fact_list = list(expr.args) + return '*'.join(self.parenthesize(_m, PREC) for _m in _fact_list) + + def _print_Derivative(self, expr): + _f, (_var, _order) = expr.args + + if _order != 1: + _second_arg = '{var}${order}'.format(var=self._print(_var), + order=self._print(_order)) + else: + _second_arg = '{var}'.format(var=self._print(_var)) + return 'diff({func_expr}, {sec_arg})'.format(func_expr=self._print(_f), sec_arg=_second_arg) + + +def maple_code(expr, assign_to=None, **settings): + r"""Converts ``expr`` to a string of Maple code. + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used as the name of the variable to which + the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol``, or ``Indexed`` type. This can be helpful for + expressions that generate multi-line statements. + precision : integer, optional + The precision for numbers such as pi [default=16]. + user_functions : dict, optional + A dictionary where keys are ``FunctionClass`` instances and values are + their string representations. Alternatively, the dictionary value can + be a list of tuples i.e. [(argument_test, cfunction_string)]. See + below for examples. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + inline: bool, optional + If True, we try to create single-statement code instead of multiple + statements. [default=True]. + + """ + return MapleCodePrinter(settings).doprint(expr, assign_to) + + +def print_maple_code(expr, **settings): + """Prints the Maple representation of the given expression. + + See :func:`maple_code` for the meaning of the optional arguments. + + Examples + ======== + + >>> from sympy import print_maple_code, symbols + >>> x, y = symbols('x y') + >>> print_maple_code(x, assign_to=y) + y := x + """ + print(maple_code(expr, **settings)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/mathematica.py b/venv/lib/python3.10/site-packages/sympy/printing/mathematica.py new file mode 100644 index 0000000000000000000000000000000000000000..064925ec1a9a477d7509110c57311239bac9fcaf --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/mathematica.py @@ -0,0 +1,353 @@ +""" +Mathematica code printer +""" + +from __future__ import annotations +from typing import Any + +from sympy.core import Basic, Expr, Float +from sympy.core.sorting import default_sort_key + +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import precedence + +# Used in MCodePrinter._print_Function(self) +known_functions = { + "exp": [(lambda x: True, "Exp")], + "log": [(lambda x: True, "Log")], + "sin": [(lambda x: True, "Sin")], + "cos": [(lambda x: True, "Cos")], + "tan": [(lambda x: True, "Tan")], + "cot": [(lambda x: True, "Cot")], + "sec": [(lambda x: True, "Sec")], + "csc": [(lambda x: True, "Csc")], + "asin": [(lambda x: True, "ArcSin")], + "acos": [(lambda x: True, "ArcCos")], + "atan": [(lambda x: True, "ArcTan")], + "acot": [(lambda x: True, "ArcCot")], + "asec": [(lambda x: True, "ArcSec")], + "acsc": [(lambda x: True, "ArcCsc")], + "sinh": [(lambda x: True, "Sinh")], + "cosh": [(lambda x: True, "Cosh")], + "tanh": [(lambda x: True, "Tanh")], + "coth": [(lambda x: True, "Coth")], + "sech": [(lambda x: True, "Sech")], + "csch": [(lambda x: True, "Csch")], + "asinh": [(lambda x: True, "ArcSinh")], + "acosh": [(lambda x: True, "ArcCosh")], + "atanh": [(lambda x: True, "ArcTanh")], + "acoth": [(lambda x: True, "ArcCoth")], + "asech": [(lambda x: True, "ArcSech")], + "acsch": [(lambda x: True, "ArcCsch")], + "sinc": [(lambda x: True, "Sinc")], + "conjugate": [(lambda x: True, "Conjugate")], + "Max": [(lambda *x: True, "Max")], + "Min": [(lambda *x: True, "Min")], + "erf": [(lambda x: True, "Erf")], + "erf2": [(lambda *x: True, "Erf")], + "erfc": [(lambda x: True, "Erfc")], + "erfi": [(lambda x: True, "Erfi")], + "erfinv": [(lambda x: True, "InverseErf")], + "erfcinv": [(lambda x: True, "InverseErfc")], + "erf2inv": [(lambda *x: True, "InverseErf")], + "expint": [(lambda *x: True, "ExpIntegralE")], + "Ei": [(lambda x: True, "ExpIntegralEi")], + "fresnelc": [(lambda x: True, "FresnelC")], + "fresnels": [(lambda x: True, "FresnelS")], + "gamma": [(lambda x: True, "Gamma")], + "uppergamma": [(lambda *x: True, "Gamma")], + "polygamma": [(lambda *x: True, "PolyGamma")], + "loggamma": [(lambda x: True, "LogGamma")], + "beta": [(lambda *x: True, "Beta")], + "Ci": [(lambda x: True, "CosIntegral")], + "Si": [(lambda x: True, "SinIntegral")], + "Chi": [(lambda x: True, "CoshIntegral")], + "Shi": [(lambda x: True, "SinhIntegral")], + "li": [(lambda x: True, "LogIntegral")], + "factorial": [(lambda x: True, "Factorial")], + "factorial2": [(lambda x: True, "Factorial2")], + "subfactorial": [(lambda x: True, "Subfactorial")], + "catalan": [(lambda x: True, "CatalanNumber")], + "harmonic": [(lambda *x: True, "HarmonicNumber")], + "lucas": [(lambda x: True, "LucasL")], + "RisingFactorial": [(lambda *x: True, "Pochhammer")], + "FallingFactorial": [(lambda *x: True, "FactorialPower")], + "laguerre": [(lambda *x: True, "LaguerreL")], + "assoc_laguerre": [(lambda *x: True, "LaguerreL")], + "hermite": [(lambda *x: True, "HermiteH")], + "jacobi": [(lambda *x: True, "JacobiP")], + "gegenbauer": [(lambda *x: True, "GegenbauerC")], + "chebyshevt": [(lambda *x: True, "ChebyshevT")], + "chebyshevu": [(lambda *x: True, "ChebyshevU")], + "legendre": [(lambda *x: True, "LegendreP")], + "assoc_legendre": [(lambda *x: True, "LegendreP")], + "mathieuc": [(lambda *x: True, "MathieuC")], + "mathieus": [(lambda *x: True, "MathieuS")], + "mathieucprime": [(lambda *x: True, "MathieuCPrime")], + "mathieusprime": [(lambda *x: True, "MathieuSPrime")], + "stieltjes": [(lambda x: True, "StieltjesGamma")], + "elliptic_e": [(lambda *x: True, "EllipticE")], + "elliptic_f": [(lambda *x: True, "EllipticE")], + "elliptic_k": [(lambda x: True, "EllipticK")], + "elliptic_pi": [(lambda *x: True, "EllipticPi")], + "zeta": [(lambda *x: True, "Zeta")], + "dirichlet_eta": [(lambda x: True, "DirichletEta")], + "riemann_xi": [(lambda x: True, "RiemannXi")], + "besseli": [(lambda *x: True, "BesselI")], + "besselj": [(lambda *x: True, "BesselJ")], + "besselk": [(lambda *x: True, "BesselK")], + "bessely": [(lambda *x: True, "BesselY")], + "hankel1": [(lambda *x: True, "HankelH1")], + "hankel2": [(lambda *x: True, "HankelH2")], + "airyai": [(lambda x: True, "AiryAi")], + "airybi": [(lambda x: True, "AiryBi")], + "airyaiprime": [(lambda x: True, "AiryAiPrime")], + "airybiprime": [(lambda x: True, "AiryBiPrime")], + "polylog": [(lambda *x: True, "PolyLog")], + "lerchphi": [(lambda *x: True, "LerchPhi")], + "gcd": [(lambda *x: True, "GCD")], + "lcm": [(lambda *x: True, "LCM")], + "jn": [(lambda *x: True, "SphericalBesselJ")], + "yn": [(lambda *x: True, "SphericalBesselY")], + "hyper": [(lambda *x: True, "HypergeometricPFQ")], + "meijerg": [(lambda *x: True, "MeijerG")], + "appellf1": [(lambda *x: True, "AppellF1")], + "DiracDelta": [(lambda x: True, "DiracDelta")], + "Heaviside": [(lambda x: True, "HeavisideTheta")], + "KroneckerDelta": [(lambda *x: True, "KroneckerDelta")], + "sqrt": [(lambda x: True, "Sqrt")], # For automatic rewrites +} + + +class MCodePrinter(CodePrinter): + """A printer to convert Python expressions to + strings of the Wolfram's Mathematica code + """ + printmethod = "_mcode" + language = "Wolfram Language" + + _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{ + 'precision': 15, + 'user_functions': {}, + }) + + _number_symbols: set[tuple[Expr, Float]] = set() + _not_supported: set[Basic] = set() + + def __init__(self, settings={}): + """Register function mappings supplied by user""" + CodePrinter.__init__(self, settings) + self.known_functions = dict(known_functions) + userfuncs = settings.get('user_functions', {}).copy() + for k, v in userfuncs.items(): + if not isinstance(v, list): + userfuncs[k] = [(lambda *x: True, v)] + self.known_functions.update(userfuncs) + + def _format_code(self, lines): + return lines + + def _print_Pow(self, expr): + PREC = precedence(expr) + return '%s^%s' % (self.parenthesize(expr.base, PREC), + self.parenthesize(expr.exp, PREC)) + + def _print_Mul(self, expr): + PREC = precedence(expr) + c, nc = expr.args_cnc() + res = super()._print_Mul(expr.func(*c)) + if nc: + res += '*' + res += '**'.join(self.parenthesize(a, PREC) for a in nc) + return res + + def _print_Relational(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + op = expr.rel_op + return "{} {} {}".format(lhs_code, op, rhs_code) + + # Primitive numbers + def _print_Zero(self, expr): + return '0' + + def _print_One(self, expr): + return '1' + + def _print_NegativeOne(self, expr): + return '-1' + + def _print_Half(self, expr): + return '1/2' + + def _print_ImaginaryUnit(self, expr): + return 'I' + + + # Infinity and invalid numbers + def _print_Infinity(self, expr): + return 'Infinity' + + def _print_NegativeInfinity(self, expr): + return '-Infinity' + + def _print_ComplexInfinity(self, expr): + return 'ComplexInfinity' + + def _print_NaN(self, expr): + return 'Indeterminate' + + + # Mathematical constants + def _print_Exp1(self, expr): + return 'E' + + def _print_Pi(self, expr): + return 'Pi' + + def _print_GoldenRatio(self, expr): + return 'GoldenRatio' + + def _print_TribonacciConstant(self, expr): + expanded = expr.expand(func=True) + PREC = precedence(expr) + return self.parenthesize(expanded, PREC) + + def _print_EulerGamma(self, expr): + return 'EulerGamma' + + def _print_Catalan(self, expr): + return 'Catalan' + + + def _print_list(self, expr): + return '{' + ', '.join(self.doprint(a) for a in expr) + '}' + _print_tuple = _print_list + _print_Tuple = _print_list + + def _print_ImmutableDenseMatrix(self, expr): + return self.doprint(expr.tolist()) + + def _print_ImmutableSparseMatrix(self, expr): + + def print_rule(pos, val): + return '{} -> {}'.format( + self.doprint((pos[0]+1, pos[1]+1)), self.doprint(val)) + + def print_data(): + items = sorted(expr.todok().items(), key=default_sort_key) + return '{' + \ + ', '.join(print_rule(k, v) for k, v in items) + \ + '}' + + def print_dims(): + return self.doprint(expr.shape) + + return 'SparseArray[{}, {}]'.format(print_data(), print_dims()) + + def _print_ImmutableDenseNDimArray(self, expr): + return self.doprint(expr.tolist()) + + def _print_ImmutableSparseNDimArray(self, expr): + def print_string_list(string_list): + return '{' + ', '.join(a for a in string_list) + '}' + + def to_mathematica_index(*args): + """Helper function to change Python style indexing to + Pathematica indexing. + + Python indexing (0, 1 ... n-1) + -> Mathematica indexing (1, 2 ... n) + """ + return tuple(i + 1 for i in args) + + def print_rule(pos, val): + """Helper function to print a rule of Mathematica""" + return '{} -> {}'.format(self.doprint(pos), self.doprint(val)) + + def print_data(): + """Helper function to print data part of Mathematica + sparse array. + + It uses the fourth notation ``SparseArray[data,{d1,d2,...}]`` + from + https://reference.wolfram.com/language/ref/SparseArray.html + + ``data`` must be formatted with rule. + """ + return print_string_list( + [print_rule( + to_mathematica_index(*(expr._get_tuple_index(key))), + value) + for key, value in sorted(expr._sparse_array.items())] + ) + + def print_dims(): + """Helper function to print dimensions part of Mathematica + sparse array. + + It uses the fourth notation ``SparseArray[data,{d1,d2,...}]`` + from + https://reference.wolfram.com/language/ref/SparseArray.html + """ + return self.doprint(expr.shape) + + return 'SparseArray[{}, {}]'.format(print_data(), print_dims()) + + def _print_Function(self, expr): + if expr.func.__name__ in self.known_functions: + cond_mfunc = self.known_functions[expr.func.__name__] + for cond, mfunc in cond_mfunc: + if cond(*expr.args): + return "%s[%s]" % (mfunc, self.stringify(expr.args, ", ")) + elif expr.func.__name__ in self._rewriteable_functions: + # Simple rewrite to supported function possible + target_f, required_fs = self._rewriteable_functions[expr.func.__name__] + if self._can_print(target_f) and all(self._can_print(f) for f in required_fs): + return self._print(expr.rewrite(target_f)) + return expr.func.__name__ + "[%s]" % self.stringify(expr.args, ", ") + + _print_MinMaxBase = _print_Function + + def _print_LambertW(self, expr): + if len(expr.args) == 1: + return "ProductLog[{}]".format(self._print(expr.args[0])) + return "ProductLog[{}, {}]".format( + self._print(expr.args[1]), self._print(expr.args[0])) + + def _print_atan2(self, expr): + return "ArcTan[{}, {}]".format( + self._print(expr.args[1]), self._print(expr.args[0])) + + def _print_Integral(self, expr): + if len(expr.variables) == 1 and not expr.limits[0][1:]: + args = [expr.args[0], expr.variables[0]] + else: + args = expr.args + return "Hold[Integrate[" + ', '.join(self.doprint(a) for a in args) + "]]" + + def _print_Sum(self, expr): + return "Hold[Sum[" + ', '.join(self.doprint(a) for a in expr.args) + "]]" + + def _print_Derivative(self, expr): + dexpr = expr.expr + dvars = [i[0] if i[1] == 1 else i for i in expr.variable_count] + return "Hold[D[" + ', '.join(self.doprint(a) for a in [dexpr] + dvars) + "]]" + + + def _get_comment(self, text): + return "(* {} *)".format(text) + + +def mathematica_code(expr, **settings): + r"""Converts an expr to a string of the Wolfram Mathematica code + + Examples + ======== + + >>> from sympy import mathematica_code as mcode, symbols, sin + >>> x = symbols('x') + >>> mcode(sin(x).series(x).removeO()) + '(1/120)*x^5 - 1/6*x^3 + x' + """ + return MCodePrinter(settings).doprint(expr) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/mathml.py b/venv/lib/python3.10/site-packages/sympy/printing/mathml.py new file mode 100644 index 0000000000000000000000000000000000000000..4dff74cd64b17036d3ff2a766253c9af850f088d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/mathml.py @@ -0,0 +1,2157 @@ +""" +A MathML printer. +""" + +from __future__ import annotations +from typing import Any + +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.core.sympify import sympify +from sympy.printing.conventions import split_super_sub, requires_partial +from sympy.printing.precedence import \ + precedence_traditional, PRECEDENCE, PRECEDENCE_TRADITIONAL +from sympy.printing.pretty.pretty_symbology import greek_unicode +from sympy.printing.printer import Printer, print_function + +from mpmath.libmp import prec_to_dps, repr_dps, to_str as mlib_to_str + + +class MathMLPrinterBase(Printer): + """Contains common code required for MathMLContentPrinter and + MathMLPresentationPrinter. + """ + + _default_settings: dict[str, Any] = { + "order": None, + "encoding": "utf-8", + "fold_frac_powers": False, + "fold_func_brackets": False, + "fold_short_frac": None, + "inv_trig_style": "abbreviated", + "ln_notation": False, + "long_frac_ratio": None, + "mat_delim": "[", + "mat_symbol_style": "plain", + "mul_symbol": None, + "root_notation": True, + "symbol_names": {}, + "mul_symbol_mathml_numbers": '·', + "disable_split_super_sub": False, + } + + def __init__(self, settings=None): + Printer.__init__(self, settings) + from xml.dom.minidom import Document, Text + + self.dom = Document() + + # Workaround to allow strings to remain unescaped + # Based on + # https://stackoverflow.com/questions/38015864/python-xml-dom-minidom-\ + # please-dont-escape-my-strings/38041194 + class RawText(Text): + def writexml(self, writer, indent='', addindent='', newl=''): + if self.data: + writer.write('{}{}{}'.format(indent, self.data, newl)) + + def createRawTextNode(data): + r = RawText() + r.data = data + r.ownerDocument = self.dom + return r + + self.dom.createTextNode = createRawTextNode + + def doprint(self, expr): + """ + Prints the expression as MathML. + """ + mathML = Printer._print(self, expr) + unistr = mathML.toxml() + xmlbstr = unistr.encode('ascii', 'xmlcharrefreplace') + res = xmlbstr.decode() + return res + + def _split_super_sub(self, name): + if self._settings["disable_split_super_sub"]: + return (name, [], []) + else: + return split_super_sub(name) + + +class MathMLContentPrinter(MathMLPrinterBase): + """Prints an expression to the Content MathML markup language. + + References: https://www.w3.org/TR/MathML2/chapter4.html + """ + printmethod = "_mathml_content" + + def mathml_tag(self, e): + """Returns the MathML tag for an expression.""" + translate = { + 'Add': 'plus', + 'Mul': 'times', + 'Derivative': 'diff', + 'Number': 'cn', + 'int': 'cn', + 'Pow': 'power', + 'Max': 'max', + 'Min': 'min', + 'Abs': 'abs', + 'And': 'and', + 'Or': 'or', + 'Xor': 'xor', + 'Not': 'not', + 'Implies': 'implies', + 'Symbol': 'ci', + 'MatrixSymbol': 'ci', + 'RandomSymbol': 'ci', + 'Integral': 'int', + 'Sum': 'sum', + 'sin': 'sin', + 'cos': 'cos', + 'tan': 'tan', + 'cot': 'cot', + 'csc': 'csc', + 'sec': 'sec', + 'sinh': 'sinh', + 'cosh': 'cosh', + 'tanh': 'tanh', + 'coth': 'coth', + 'csch': 'csch', + 'sech': 'sech', + 'asin': 'arcsin', + 'asinh': 'arcsinh', + 'acos': 'arccos', + 'acosh': 'arccosh', + 'atan': 'arctan', + 'atanh': 'arctanh', + 'atan2': 'arctan', + 'acot': 'arccot', + 'acoth': 'arccoth', + 'asec': 'arcsec', + 'asech': 'arcsech', + 'acsc': 'arccsc', + 'acsch': 'arccsch', + 'log': 'ln', + 'Equality': 'eq', + 'Unequality': 'neq', + 'GreaterThan': 'geq', + 'LessThan': 'leq', + 'StrictGreaterThan': 'gt', + 'StrictLessThan': 'lt', + 'Union': 'union', + 'Intersection': 'intersect', + } + + for cls in e.__class__.__mro__: + n = cls.__name__ + if n in translate: + return translate[n] + # Not found in the MRO set + n = e.__class__.__name__ + return n.lower() + + def _print_Mul(self, expr): + + if expr.could_extract_minus_sign(): + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('minus')) + x.appendChild(self._print_Mul(-expr)) + return x + + from sympy.simplify import fraction + numer, denom = fraction(expr) + + if denom is not S.One: + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('divide')) + x.appendChild(self._print(numer)) + x.appendChild(self._print(denom)) + return x + + coeff, terms = expr.as_coeff_mul() + if coeff is S.One and len(terms) == 1: + # XXX since the negative coefficient has been handled, I don't + # think a coeff of 1 can remain + return self._print(terms[0]) + + if self.order != 'old': + terms = Mul._from_args(terms).as_ordered_factors() + + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('times')) + if coeff != 1: + x.appendChild(self._print(coeff)) + for term in terms: + x.appendChild(self._print(term)) + return x + + def _print_Add(self, expr, order=None): + args = self._as_ordered_terms(expr, order=order) + lastProcessed = self._print(args[0]) + plusNodes = [] + for arg in args[1:]: + if arg.could_extract_minus_sign(): + # use minus + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('minus')) + x.appendChild(lastProcessed) + x.appendChild(self._print(-arg)) + # invert expression since this is now minused + lastProcessed = x + if arg == args[-1]: + plusNodes.append(lastProcessed) + else: + plusNodes.append(lastProcessed) + lastProcessed = self._print(arg) + if arg == args[-1]: + plusNodes.append(self._print(arg)) + if len(plusNodes) == 1: + return lastProcessed + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('plus')) + while plusNodes: + x.appendChild(plusNodes.pop(0)) + return x + + def _print_Piecewise(self, expr): + if expr.args[-1].cond != True: + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + root = self.dom.createElement('piecewise') + for i, (e, c) in enumerate(expr.args): + if i == len(expr.args) - 1 and c == True: + piece = self.dom.createElement('otherwise') + piece.appendChild(self._print(e)) + else: + piece = self.dom.createElement('piece') + piece.appendChild(self._print(e)) + piece.appendChild(self._print(c)) + root.appendChild(piece) + return root + + def _print_MatrixBase(self, m): + x = self.dom.createElement('matrix') + for i in range(m.rows): + x_r = self.dom.createElement('matrixrow') + for j in range(m.cols): + x_r.appendChild(self._print(m[i, j])) + x.appendChild(x_r) + return x + + def _print_Rational(self, e): + if e.q == 1: + # don't divide + x = self.dom.createElement('cn') + x.appendChild(self.dom.createTextNode(str(e.p))) + return x + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('divide')) + # numerator + xnum = self.dom.createElement('cn') + xnum.appendChild(self.dom.createTextNode(str(e.p))) + # denominator + xdenom = self.dom.createElement('cn') + xdenom.appendChild(self.dom.createTextNode(str(e.q))) + x.appendChild(xnum) + x.appendChild(xdenom) + return x + + def _print_Limit(self, e): + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement(self.mathml_tag(e))) + + x_1 = self.dom.createElement('bvar') + x_2 = self.dom.createElement('lowlimit') + x_1.appendChild(self._print(e.args[1])) + x_2.appendChild(self._print(e.args[2])) + + x.appendChild(x_1) + x.appendChild(x_2) + x.appendChild(self._print(e.args[0])) + return x + + def _print_ImaginaryUnit(self, e): + return self.dom.createElement('imaginaryi') + + def _print_EulerGamma(self, e): + return self.dom.createElement('eulergamma') + + def _print_GoldenRatio(self, e): + """We use unicode #x3c6 for Greek letter phi as defined here + https://www.w3.org/2003/entities/2007doc/isogrk1.html""" + x = self.dom.createElement('cn') + x.appendChild(self.dom.createTextNode("\N{GREEK SMALL LETTER PHI}")) + return x + + def _print_Exp1(self, e): + return self.dom.createElement('exponentiale') + + def _print_Pi(self, e): + return self.dom.createElement('pi') + + def _print_Infinity(self, e): + return self.dom.createElement('infinity') + + def _print_NaN(self, e): + return self.dom.createElement('notanumber') + + def _print_EmptySet(self, e): + return self.dom.createElement('emptyset') + + def _print_BooleanTrue(self, e): + return self.dom.createElement('true') + + def _print_BooleanFalse(self, e): + return self.dom.createElement('false') + + def _print_NegativeInfinity(self, e): + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('minus')) + x.appendChild(self.dom.createElement('infinity')) + return x + + def _print_Integral(self, e): + def lime_recur(limits): + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement(self.mathml_tag(e))) + bvar_elem = self.dom.createElement('bvar') + bvar_elem.appendChild(self._print(limits[0][0])) + x.appendChild(bvar_elem) + + if len(limits[0]) == 3: + low_elem = self.dom.createElement('lowlimit') + low_elem.appendChild(self._print(limits[0][1])) + x.appendChild(low_elem) + up_elem = self.dom.createElement('uplimit') + up_elem.appendChild(self._print(limits[0][2])) + x.appendChild(up_elem) + if len(limits[0]) == 2: + up_elem = self.dom.createElement('uplimit') + up_elem.appendChild(self._print(limits[0][1])) + x.appendChild(up_elem) + if len(limits) == 1: + x.appendChild(self._print(e.function)) + else: + x.appendChild(lime_recur(limits[1:])) + return x + + limits = list(e.limits) + limits.reverse() + return lime_recur(limits) + + def _print_Sum(self, e): + # Printer can be shared because Sum and Integral have the + # same internal representation. + return self._print_Integral(e) + + def _print_Symbol(self, sym): + ci = self.dom.createElement(self.mathml_tag(sym)) + + def join(items): + if len(items) > 1: + mrow = self.dom.createElement('mml:mrow') + for i, item in enumerate(items): + if i > 0: + mo = self.dom.createElement('mml:mo') + mo.appendChild(self.dom.createTextNode(" ")) + mrow.appendChild(mo) + mi = self.dom.createElement('mml:mi') + mi.appendChild(self.dom.createTextNode(item)) + mrow.appendChild(mi) + return mrow + else: + mi = self.dom.createElement('mml:mi') + mi.appendChild(self.dom.createTextNode(items[0])) + return mi + + # translate name, supers and subs to unicode characters + def translate(s): + if s in greek_unicode: + return greek_unicode.get(s) + else: + return s + + name, supers, subs = self._split_super_sub(sym.name) + name = translate(name) + supers = [translate(sup) for sup in supers] + subs = [translate(sub) for sub in subs] + + mname = self.dom.createElement('mml:mi') + mname.appendChild(self.dom.createTextNode(name)) + if not supers: + if not subs: + ci.appendChild(self.dom.createTextNode(name)) + else: + msub = self.dom.createElement('mml:msub') + msub.appendChild(mname) + msub.appendChild(join(subs)) + ci.appendChild(msub) + else: + if not subs: + msup = self.dom.createElement('mml:msup') + msup.appendChild(mname) + msup.appendChild(join(supers)) + ci.appendChild(msup) + else: + msubsup = self.dom.createElement('mml:msubsup') + msubsup.appendChild(mname) + msubsup.appendChild(join(subs)) + msubsup.appendChild(join(supers)) + ci.appendChild(msubsup) + return ci + + _print_MatrixSymbol = _print_Symbol + _print_RandomSymbol = _print_Symbol + + def _print_Pow(self, e): + # Here we use root instead of power if the exponent is the reciprocal + # of an integer + if (self._settings['root_notation'] and e.exp.is_Rational + and e.exp.p == 1): + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('root')) + if e.exp.q != 2: + xmldeg = self.dom.createElement('degree') + xmlcn = self.dom.createElement('cn') + xmlcn.appendChild(self.dom.createTextNode(str(e.exp.q))) + xmldeg.appendChild(xmlcn) + x.appendChild(xmldeg) + x.appendChild(self._print(e.base)) + return x + + x = self.dom.createElement('apply') + x_1 = self.dom.createElement(self.mathml_tag(e)) + x.appendChild(x_1) + x.appendChild(self._print(e.base)) + x.appendChild(self._print(e.exp)) + return x + + def _print_Number(self, e): + x = self.dom.createElement(self.mathml_tag(e)) + x.appendChild(self.dom.createTextNode(str(e))) + return x + + def _print_Float(self, e): + x = self.dom.createElement(self.mathml_tag(e)) + repr_e = mlib_to_str(e._mpf_, repr_dps(e._prec)) + x.appendChild(self.dom.createTextNode(repr_e)) + return x + + def _print_Derivative(self, e): + x = self.dom.createElement('apply') + diff_symbol = self.mathml_tag(e) + if requires_partial(e.expr): + diff_symbol = 'partialdiff' + x.appendChild(self.dom.createElement(diff_symbol)) + x_1 = self.dom.createElement('bvar') + + for sym, times in reversed(e.variable_count): + x_1.appendChild(self._print(sym)) + if times > 1: + degree = self.dom.createElement('degree') + degree.appendChild(self._print(sympify(times))) + x_1.appendChild(degree) + + x.appendChild(x_1) + x.appendChild(self._print(e.expr)) + return x + + def _print_Function(self, e): + x = self.dom.createElement("apply") + x.appendChild(self.dom.createElement(self.mathml_tag(e))) + for arg in e.args: + x.appendChild(self._print(arg)) + return x + + def _print_Basic(self, e): + x = self.dom.createElement(self.mathml_tag(e)) + for arg in e.args: + x.appendChild(self._print(arg)) + return x + + def _print_AssocOp(self, e): + x = self.dom.createElement('apply') + x_1 = self.dom.createElement(self.mathml_tag(e)) + x.appendChild(x_1) + for arg in e.args: + x.appendChild(self._print(arg)) + return x + + def _print_Relational(self, e): + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement(self.mathml_tag(e))) + x.appendChild(self._print(e.lhs)) + x.appendChild(self._print(e.rhs)) + return x + + def _print_list(self, seq): + """MathML reference for the element: + https://www.w3.org/TR/MathML2/chapter4.html#contm.list""" + dom_element = self.dom.createElement('list') + for item in seq: + dom_element.appendChild(self._print(item)) + return dom_element + + def _print_int(self, p): + dom_element = self.dom.createElement(self.mathml_tag(p)) + dom_element.appendChild(self.dom.createTextNode(str(p))) + return dom_element + + _print_Implies = _print_AssocOp + _print_Not = _print_AssocOp + _print_Xor = _print_AssocOp + + def _print_FiniteSet(self, e): + x = self.dom.createElement('set') + for arg in e.args: + x.appendChild(self._print(arg)) + return x + + def _print_Complement(self, e): + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('setdiff')) + for arg in e.args: + x.appendChild(self._print(arg)) + return x + + def _print_ProductSet(self, e): + x = self.dom.createElement('apply') + x.appendChild(self.dom.createElement('cartesianproduct')) + for arg in e.args: + x.appendChild(self._print(arg)) + return x + + def _print_Lambda(self, e): + # MathML reference for the lambda element: + # https://www.w3.org/TR/MathML2/chapter4.html#id.4.2.1.7 + x = self.dom.createElement(self.mathml_tag(e)) + for arg in e.signature: + x_1 = self.dom.createElement('bvar') + x_1.appendChild(self._print(arg)) + x.appendChild(x_1) + x.appendChild(self._print(e.expr)) + return x + + # XXX Symmetric difference is not supported for MathML content printers. + + +class MathMLPresentationPrinter(MathMLPrinterBase): + """Prints an expression to the Presentation MathML markup language. + + References: https://www.w3.org/TR/MathML2/chapter3.html + """ + printmethod = "_mathml_presentation" + + def mathml_tag(self, e): + """Returns the MathML tag for an expression.""" + translate = { + 'Number': 'mn', + 'Limit': '→', + 'Derivative': 'ⅆ', + 'int': 'mn', + 'Symbol': 'mi', + 'Integral': '∫', + 'Sum': '∑', + 'sin': 'sin', + 'cos': 'cos', + 'tan': 'tan', + 'cot': 'cot', + 'asin': 'arcsin', + 'asinh': 'arcsinh', + 'acos': 'arccos', + 'acosh': 'arccosh', + 'atan': 'arctan', + 'atanh': 'arctanh', + 'acot': 'arccot', + 'atan2': 'arctan', + 'Equality': '=', + 'Unequality': '≠', + 'GreaterThan': '≥', + 'LessThan': '≤', + 'StrictGreaterThan': '>', + 'StrictLessThan': '<', + 'lerchphi': 'Φ', + 'zeta': 'ζ', + 'dirichlet_eta': 'η', + 'elliptic_k': 'Κ', + 'lowergamma': 'γ', + 'uppergamma': 'Γ', + 'gamma': 'Γ', + 'totient': 'ϕ', + 'reduced_totient': 'λ', + 'primenu': 'ν', + 'primeomega': 'Ω', + 'fresnels': 'S', + 'fresnelc': 'C', + 'LambertW': 'W', + 'Heaviside': 'Θ', + 'BooleanTrue': 'True', + 'BooleanFalse': 'False', + 'NoneType': 'None', + 'mathieus': 'S', + 'mathieuc': 'C', + 'mathieusprime': 'S′', + 'mathieucprime': 'C′', + 'Lambda': 'lambda', + } + + def mul_symbol_selection(): + if (self._settings["mul_symbol"] is None or + self._settings["mul_symbol"] == 'None'): + return '⁢' + elif self._settings["mul_symbol"] == 'times': + return '×' + elif self._settings["mul_symbol"] == 'dot': + return '·' + elif self._settings["mul_symbol"] == 'ldot': + return '․' + elif not isinstance(self._settings["mul_symbol"], str): + raise TypeError + else: + return self._settings["mul_symbol"] + for cls in e.__class__.__mro__: + n = cls.__name__ + if n in translate: + return translate[n] + # Not found in the MRO set + if e.__class__.__name__ == "Mul": + return mul_symbol_selection() + n = e.__class__.__name__ + return n.lower() + + def _l_paren(self): + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('(')) + return mo + + def _r_paren(self): + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode(')')) + return mo + + def _l_brace(self): + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('{')) + return mo + + def _r_brace(self): + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('}')) + return mo + + def _comma(self): + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode(',')) + return mo + + def _bar(self): + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('|')) + return mo + + def _semicolon(self): + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode(';')) + return mo + + def _paren_comma_separated(self, *args): + mrow = self.dom.createElement('mrow') + mrow.appendChild(self._l_paren()) + for i, arg in enumerate(args): + if i: + mrow.appendChild(self._comma()) + mrow.appendChild(self._print(arg)) + mrow.appendChild(self._r_paren()) + return mrow + + def _paren_bar_separated(self, *args): + mrow = self.dom.createElement('mrow') + mrow.appendChild(self._l_paren()) + for i, arg in enumerate(args): + if i: + mrow.appendChild(self._bar()) + mrow.appendChild(self._print(arg)) + mrow.appendChild(self._r_paren()) + return mrow + + def parenthesize(self, item, level, strict=False): + prec_val = precedence_traditional(item) + if (prec_val < level) or ((not strict) and prec_val <= level): + mrow = self.dom.createElement('mrow') + mrow.appendChild(self._l_paren()) + mrow.appendChild(self._print(item)) + mrow.appendChild(self._r_paren()) + return mrow + return self._print(item) + + def _print_Mul(self, expr): + + def multiply(expr, mrow): + from sympy.simplify import fraction + numer, denom = fraction(expr) + if denom is not S.One: + frac = self.dom.createElement('mfrac') + if self._settings["fold_short_frac"] and len(str(expr)) < 7: + frac.setAttribute('bevelled', 'true') + xnum = self._print(numer) + xden = self._print(denom) + frac.appendChild(xnum) + frac.appendChild(xden) + mrow.appendChild(frac) + return mrow + + coeff, terms = expr.as_coeff_mul() + if coeff is S.One and len(terms) == 1: + mrow.appendChild(self._print(terms[0])) + return mrow + if self.order != 'old': + terms = Mul._from_args(terms).as_ordered_factors() + + if coeff != 1: + x = self._print(coeff) + y = self.dom.createElement('mo') + y.appendChild(self.dom.createTextNode(self.mathml_tag(expr))) + mrow.appendChild(x) + mrow.appendChild(y) + for term in terms: + mrow.appendChild(self.parenthesize(term, PRECEDENCE['Mul'])) + if not term == terms[-1]: + y = self.dom.createElement('mo') + y.appendChild(self.dom.createTextNode(self.mathml_tag(expr))) + mrow.appendChild(y) + return mrow + mrow = self.dom.createElement('mrow') + if expr.could_extract_minus_sign(): + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode('-')) + mrow.appendChild(x) + mrow = multiply(-expr, mrow) + else: + mrow = multiply(expr, mrow) + + return mrow + + def _print_Add(self, expr, order=None): + mrow = self.dom.createElement('mrow') + args = self._as_ordered_terms(expr, order=order) + mrow.appendChild(self._print(args[0])) + for arg in args[1:]: + if arg.could_extract_minus_sign(): + # use minus + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode('-')) + y = self._print(-arg) + # invert expression since this is now minused + else: + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode('+')) + y = self._print(arg) + mrow.appendChild(x) + mrow.appendChild(y) + + return mrow + + def _print_MatrixBase(self, m): + table = self.dom.createElement('mtable') + for i in range(m.rows): + x = self.dom.createElement('mtr') + for j in range(m.cols): + y = self.dom.createElement('mtd') + y.appendChild(self._print(m[i, j])) + x.appendChild(y) + table.appendChild(x) + mat_delim = self._settings["mat_delim"] + if mat_delim == '': + return table + left = self.dom.createElement('mo') + right = self.dom.createElement('mo') + if mat_delim == "[": + left.appendChild(self.dom.createTextNode("[")) + right.appendChild(self.dom.createTextNode("]")) + else: + left.appendChild(self.dom.createTextNode("(")) + right.appendChild(self.dom.createTextNode(")")) + mrow = self.dom.createElement('mrow') + mrow.appendChild(left) + mrow.appendChild(table) + mrow.appendChild(right) + return mrow + + def _get_printed_Rational(self, e, folded=None): + if e.p < 0: + p = -e.p + else: + p = e.p + x = self.dom.createElement('mfrac') + if folded or self._settings["fold_short_frac"]: + x.setAttribute('bevelled', 'true') + x.appendChild(self._print(p)) + x.appendChild(self._print(e.q)) + if e.p < 0: + mrow = self.dom.createElement('mrow') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('-')) + mrow.appendChild(mo) + mrow.appendChild(x) + return mrow + else: + return x + + def _print_Rational(self, e): + if e.q == 1: + # don't divide + return self._print(e.p) + + return self._get_printed_Rational(e, self._settings["fold_short_frac"]) + + def _print_Limit(self, e): + mrow = self.dom.createElement('mrow') + munder = self.dom.createElement('munder') + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode('lim')) + + x = self.dom.createElement('mrow') + x_1 = self._print(e.args[1]) + arrow = self.dom.createElement('mo') + arrow.appendChild(self.dom.createTextNode(self.mathml_tag(e))) + x_2 = self._print(e.args[2]) + x.appendChild(x_1) + x.appendChild(arrow) + x.appendChild(x_2) + + munder.appendChild(mi) + munder.appendChild(x) + mrow.appendChild(munder) + mrow.appendChild(self._print(e.args[0])) + + return mrow + + def _print_ImaginaryUnit(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('ⅈ')) + return x + + def _print_GoldenRatio(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('Φ')) + return x + + def _print_Exp1(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('ⅇ')) + return x + + def _print_Pi(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('π')) + return x + + def _print_Infinity(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('∞')) + return x + + def _print_NegativeInfinity(self, e): + mrow = self.dom.createElement('mrow') + y = self.dom.createElement('mo') + y.appendChild(self.dom.createTextNode('-')) + x = self._print_Infinity(e) + mrow.appendChild(y) + mrow.appendChild(x) + return mrow + + def _print_HBar(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('ℏ')) + return x + + def _print_EulerGamma(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('γ')) + return x + + def _print_TribonacciConstant(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('TribonacciConstant')) + return x + + def _print_Dagger(self, e): + msup = self.dom.createElement('msup') + msup.appendChild(self._print(e.args[0])) + msup.appendChild(self.dom.createTextNode('†')) + return msup + + def _print_Contains(self, e): + mrow = self.dom.createElement('mrow') + mrow.appendChild(self._print(e.args[0])) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('∈')) + mrow.appendChild(mo) + mrow.appendChild(self._print(e.args[1])) + return mrow + + def _print_HilbertSpace(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('ℋ')) + return x + + def _print_ComplexSpace(self, e): + msup = self.dom.createElement('msup') + msup.appendChild(self.dom.createTextNode('𝒞')) + msup.appendChild(self._print(e.args[0])) + return msup + + def _print_FockSpace(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('ℱ')) + return x + + + def _print_Integral(self, expr): + intsymbols = {1: "∫", 2: "∬", 3: "∭"} + + mrow = self.dom.createElement('mrow') + if len(expr.limits) <= 3 and all(len(lim) == 1 for lim in expr.limits): + # Only up to three-integral signs exists + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode(intsymbols[len(expr.limits)])) + mrow.appendChild(mo) + else: + # Either more than three or limits provided + for lim in reversed(expr.limits): + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode(intsymbols[1])) + if len(lim) == 1: + mrow.appendChild(mo) + if len(lim) == 2: + msup = self.dom.createElement('msup') + msup.appendChild(mo) + msup.appendChild(self._print(lim[1])) + mrow.appendChild(msup) + if len(lim) == 3: + msubsup = self.dom.createElement('msubsup') + msubsup.appendChild(mo) + msubsup.appendChild(self._print(lim[1])) + msubsup.appendChild(self._print(lim[2])) + mrow.appendChild(msubsup) + # print function + mrow.appendChild(self.parenthesize(expr.function, PRECEDENCE["Mul"], + strict=True)) + # print integration variables + for lim in reversed(expr.limits): + d = self.dom.createElement('mo') + d.appendChild(self.dom.createTextNode('ⅆ')) + mrow.appendChild(d) + mrow.appendChild(self._print(lim[0])) + return mrow + + def _print_Sum(self, e): + limits = list(e.limits) + subsup = self.dom.createElement('munderover') + low_elem = self._print(limits[0][1]) + up_elem = self._print(limits[0][2]) + summand = self.dom.createElement('mo') + summand.appendChild(self.dom.createTextNode(self.mathml_tag(e))) + + low = self.dom.createElement('mrow') + var = self._print(limits[0][0]) + equal = self.dom.createElement('mo') + equal.appendChild(self.dom.createTextNode('=')) + low.appendChild(var) + low.appendChild(equal) + low.appendChild(low_elem) + + subsup.appendChild(summand) + subsup.appendChild(low) + subsup.appendChild(up_elem) + + mrow = self.dom.createElement('mrow') + mrow.appendChild(subsup) + mrow.appendChild(self.parenthesize(e.function, precedence_traditional(e))) + return mrow + + def _print_Symbol(self, sym, style='plain'): + def join(items): + if len(items) > 1: + mrow = self.dom.createElement('mrow') + for i, item in enumerate(items): + if i > 0: + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode(" ")) + mrow.appendChild(mo) + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(item)) + mrow.appendChild(mi) + return mrow + else: + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(items[0])) + return mi + + # translate name, supers and subs to unicode characters + def translate(s): + if s in greek_unicode: + return greek_unicode.get(s) + else: + return s + + name, supers, subs = self._split_super_sub(sym.name) + name = translate(name) + supers = [translate(sup) for sup in supers] + subs = [translate(sub) for sub in subs] + + mname = self.dom.createElement('mi') + mname.appendChild(self.dom.createTextNode(name)) + if len(supers) == 0: + if len(subs) == 0: + x = mname + else: + x = self.dom.createElement('msub') + x.appendChild(mname) + x.appendChild(join(subs)) + else: + if len(subs) == 0: + x = self.dom.createElement('msup') + x.appendChild(mname) + x.appendChild(join(supers)) + else: + x = self.dom.createElement('msubsup') + x.appendChild(mname) + x.appendChild(join(subs)) + x.appendChild(join(supers)) + # Set bold font? + if style == 'bold': + x.setAttribute('mathvariant', 'bold') + return x + + def _print_MatrixSymbol(self, sym): + return self._print_Symbol(sym, + style=self._settings['mat_symbol_style']) + + _print_RandomSymbol = _print_Symbol + + def _print_conjugate(self, expr): + enc = self.dom.createElement('menclose') + enc.setAttribute('notation', 'top') + enc.appendChild(self._print(expr.args[0])) + return enc + + def _print_operator_after(self, op, expr): + row = self.dom.createElement('mrow') + row.appendChild(self.parenthesize(expr, PRECEDENCE["Func"])) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode(op)) + row.appendChild(mo) + return row + + def _print_factorial(self, expr): + return self._print_operator_after('!', expr.args[0]) + + def _print_factorial2(self, expr): + return self._print_operator_after('!!', expr.args[0]) + + def _print_binomial(self, expr): + frac = self.dom.createElement('mfrac') + frac.setAttribute('linethickness', '0') + frac.appendChild(self._print(expr.args[0])) + frac.appendChild(self._print(expr.args[1])) + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_paren()) + brac.appendChild(frac) + brac.appendChild(self._r_paren()) + return brac + + def _print_Pow(self, e): + # Here we use root instead of power if the exponent is the + # reciprocal of an integer + if (e.exp.is_Rational and abs(e.exp.p) == 1 and e.exp.q != 1 and + self._settings['root_notation']): + if e.exp.q == 2: + x = self.dom.createElement('msqrt') + x.appendChild(self._print(e.base)) + if e.exp.q != 2: + x = self.dom.createElement('mroot') + x.appendChild(self._print(e.base)) + x.appendChild(self._print(e.exp.q)) + if e.exp.p == -1: + frac = self.dom.createElement('mfrac') + frac.appendChild(self._print(1)) + frac.appendChild(x) + return frac + else: + return x + + if e.exp.is_Rational and e.exp.q != 1: + if e.exp.is_negative: + top = self.dom.createElement('mfrac') + top.appendChild(self._print(1)) + x = self.dom.createElement('msup') + x.appendChild(self.parenthesize(e.base, PRECEDENCE['Pow'])) + x.appendChild(self._get_printed_Rational(-e.exp, + self._settings['fold_frac_powers'])) + top.appendChild(x) + return top + else: + x = self.dom.createElement('msup') + x.appendChild(self.parenthesize(e.base, PRECEDENCE['Pow'])) + x.appendChild(self._get_printed_Rational(e.exp, + self._settings['fold_frac_powers'])) + return x + + if e.exp.is_negative: + top = self.dom.createElement('mfrac') + top.appendChild(self._print(1)) + if e.exp == -1: + top.appendChild(self._print(e.base)) + else: + x = self.dom.createElement('msup') + x.appendChild(self.parenthesize(e.base, PRECEDENCE['Pow'])) + x.appendChild(self._print(-e.exp)) + top.appendChild(x) + return top + + x = self.dom.createElement('msup') + x.appendChild(self.parenthesize(e.base, PRECEDENCE['Pow'])) + x.appendChild(self._print(e.exp)) + return x + + def _print_Number(self, e): + x = self.dom.createElement(self.mathml_tag(e)) + x.appendChild(self.dom.createTextNode(str(e))) + return x + + def _print_AccumulationBounds(self, i): + left = self.dom.createElement('mo') + left.appendChild(self.dom.createTextNode('\u27e8')) + right = self.dom.createElement('mo') + right.appendChild(self.dom.createTextNode('\u27e9')) + brac = self.dom.createElement('mrow') + brac.appendChild(left) + brac.appendChild(self._print(i.min)) + brac.appendChild(self._comma()) + brac.appendChild(self._print(i.max)) + brac.appendChild(right) + return brac + + def _print_Derivative(self, e): + + if requires_partial(e.expr): + d = '∂' + else: + d = self.mathml_tag(e) + + # Determine denominator + m = self.dom.createElement('mrow') + dim = 0 # Total diff dimension, for numerator + for sym, num in reversed(e.variable_count): + dim += num + if num >= 2: + x = self.dom.createElement('msup') + xx = self.dom.createElement('mo') + xx.appendChild(self.dom.createTextNode(d)) + x.appendChild(xx) + x.appendChild(self._print(num)) + else: + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode(d)) + m.appendChild(x) + y = self._print(sym) + m.appendChild(y) + + mnum = self.dom.createElement('mrow') + if dim >= 2: + x = self.dom.createElement('msup') + xx = self.dom.createElement('mo') + xx.appendChild(self.dom.createTextNode(d)) + x.appendChild(xx) + x.appendChild(self._print(dim)) + else: + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode(d)) + + mnum.appendChild(x) + mrow = self.dom.createElement('mrow') + frac = self.dom.createElement('mfrac') + frac.appendChild(mnum) + frac.appendChild(m) + mrow.appendChild(frac) + + # Print function + mrow.appendChild(self._print(e.expr)) + + return mrow + + def _print_Function(self, e): + x = self.dom.createElement('mi') + if self.mathml_tag(e) == 'log' and self._settings["ln_notation"]: + x.appendChild(self.dom.createTextNode('ln')) + else: + x.appendChild(self.dom.createTextNode(self.mathml_tag(e))) + mrow = self.dom.createElement('mrow') + mrow.appendChild(x) + mrow.appendChild(self._paren_comma_separated(*e.args)) + return mrow + + def _print_Float(self, expr): + # Based off of that in StrPrinter + dps = prec_to_dps(expr._prec) + str_real = mlib_to_str(expr._mpf_, dps, strip_zeros=True) + + # Must always have a mul symbol (as 2.5 10^{20} just looks odd) + # thus we use the number separator + separator = self._settings['mul_symbol_mathml_numbers'] + mrow = self.dom.createElement('mrow') + if 'e' in str_real: + (mant, exp) = str_real.split('e') + + if exp[0] == '+': + exp = exp[1:] + + mn = self.dom.createElement('mn') + mn.appendChild(self.dom.createTextNode(mant)) + mrow.appendChild(mn) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode(separator)) + mrow.appendChild(mo) + msup = self.dom.createElement('msup') + mn = self.dom.createElement('mn') + mn.appendChild(self.dom.createTextNode("10")) + msup.appendChild(mn) + mn = self.dom.createElement('mn') + mn.appendChild(self.dom.createTextNode(exp)) + msup.appendChild(mn) + mrow.appendChild(msup) + return mrow + elif str_real == "+inf": + return self._print_Infinity(None) + elif str_real == "-inf": + return self._print_NegativeInfinity(None) + else: + mn = self.dom.createElement('mn') + mn.appendChild(self.dom.createTextNode(str_real)) + return mn + + def _print_polylog(self, expr): + mrow = self.dom.createElement('mrow') + m = self.dom.createElement('msub') + + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode('Li')) + m.appendChild(mi) + m.appendChild(self._print(expr.args[0])) + mrow.appendChild(m) + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_paren()) + brac.appendChild(self._print(expr.args[1])) + brac.appendChild(self._r_paren()) + mrow.appendChild(brac) + return mrow + + def _print_Basic(self, e): + mrow = self.dom.createElement('mrow') + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(self.mathml_tag(e))) + mrow.appendChild(mi) + mrow.appendChild(self._paren_comma_separated(*e.args)) + return mrow + + def _print_Tuple(self, e): + return self._paren_comma_separated(*e.args) + + def _print_Interval(self, i): + right = self.dom.createElement('mo') + if i.right_open: + right.appendChild(self.dom.createTextNode(')')) + else: + right.appendChild(self.dom.createTextNode(']')) + left = self.dom.createElement('mo') + if i.left_open: + left.appendChild(self.dom.createTextNode('(')) + else: + left.appendChild(self.dom.createTextNode('[')) + mrow = self.dom.createElement('mrow') + mrow.appendChild(left) + mrow.appendChild(self._print(i.start)) + mrow.appendChild(self._comma()) + mrow.appendChild(self._print(i.end)) + mrow.appendChild(right) + return mrow + + def _print_Abs(self, expr, exp=None): + mrow = self.dom.createElement('mrow') + mrow.appendChild(self._bar()) + mrow.appendChild(self._print(expr.args[0])) + mrow.appendChild(self._bar()) + return mrow + + _print_Determinant = _print_Abs + + def _print_re_im(self, c, expr): + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_paren()) + brac.appendChild(self._print(expr)) + brac.appendChild(self._r_paren()) + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(c)) + mrow = self.dom.createElement('mrow') + mrow.appendChild(mi) + mrow.appendChild(brac) + return mrow + + def _print_re(self, expr, exp=None): + return self._print_re_im('\u211C', expr.args[0]) + + def _print_im(self, expr, exp=None): + return self._print_re_im('\u2111', expr.args[0]) + + def _print_AssocOp(self, e): + mrow = self.dom.createElement('mrow') + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(self.mathml_tag(e))) + mrow.appendChild(mi) + for arg in e.args: + mrow.appendChild(self._print(arg)) + return mrow + + def _print_SetOp(self, expr, symbol, prec): + mrow = self.dom.createElement('mrow') + mrow.appendChild(self.parenthesize(expr.args[0], prec)) + for arg in expr.args[1:]: + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode(symbol)) + y = self.parenthesize(arg, prec) + mrow.appendChild(x) + mrow.appendChild(y) + return mrow + + def _print_Union(self, expr): + prec = PRECEDENCE_TRADITIONAL['Union'] + return self._print_SetOp(expr, '∪', prec) + + def _print_Intersection(self, expr): + prec = PRECEDENCE_TRADITIONAL['Intersection'] + return self._print_SetOp(expr, '∩', prec) + + def _print_Complement(self, expr): + prec = PRECEDENCE_TRADITIONAL['Complement'] + return self._print_SetOp(expr, '∖', prec) + + def _print_SymmetricDifference(self, expr): + prec = PRECEDENCE_TRADITIONAL['SymmetricDifference'] + return self._print_SetOp(expr, '∆', prec) + + def _print_ProductSet(self, expr): + prec = PRECEDENCE_TRADITIONAL['ProductSet'] + return self._print_SetOp(expr, '×', prec) + + def _print_FiniteSet(self, s): + return self._print_set(s.args) + + def _print_set(self, s): + items = sorted(s, key=default_sort_key) + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_brace()) + for i, item in enumerate(items): + if i: + brac.appendChild(self._comma()) + brac.appendChild(self._print(item)) + brac.appendChild(self._r_brace()) + return brac + + _print_frozenset = _print_set + + def _print_LogOp(self, args, symbol): + mrow = self.dom.createElement('mrow') + if args[0].is_Boolean and not args[0].is_Not: + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_paren()) + brac.appendChild(self._print(args[0])) + brac.appendChild(self._r_paren()) + mrow.appendChild(brac) + else: + mrow.appendChild(self._print(args[0])) + for arg in args[1:]: + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode(symbol)) + if arg.is_Boolean and not arg.is_Not: + y = self.dom.createElement('mrow') + y.appendChild(self._l_paren()) + y.appendChild(self._print(arg)) + y.appendChild(self._r_paren()) + else: + y = self._print(arg) + mrow.appendChild(x) + mrow.appendChild(y) + return mrow + + def _print_BasisDependent(self, expr): + from sympy.vector import Vector + + if expr == expr.zero: + # Not clear if this is ever called + return self._print(expr.zero) + if isinstance(expr, Vector): + items = expr.separate().items() + else: + items = [(0, expr)] + + mrow = self.dom.createElement('mrow') + for system, vect in items: + inneritems = list(vect.components.items()) + inneritems.sort(key = lambda x:x[0].__str__()) + for i, (k, v) in enumerate(inneritems): + if v == 1: + if i: # No + for first item + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('+')) + mrow.appendChild(mo) + mrow.appendChild(self._print(k)) + elif v == -1: + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('-')) + mrow.appendChild(mo) + mrow.appendChild(self._print(k)) + else: + if i: # No + for first item + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('+')) + mrow.appendChild(mo) + mbrac = self.dom.createElement('mrow') + mbrac.appendChild(self._l_paren()) + mbrac.appendChild(self._print(v)) + mbrac.appendChild(self._r_paren()) + mrow.appendChild(mbrac) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('⁢')) + mrow.appendChild(mo) + mrow.appendChild(self._print(k)) + return mrow + + + def _print_And(self, expr): + args = sorted(expr.args, key=default_sort_key) + return self._print_LogOp(args, '∧') + + def _print_Or(self, expr): + args = sorted(expr.args, key=default_sort_key) + return self._print_LogOp(args, '∨') + + def _print_Xor(self, expr): + args = sorted(expr.args, key=default_sort_key) + return self._print_LogOp(args, '⊻') + + def _print_Implies(self, expr): + return self._print_LogOp(expr.args, '⇒') + + def _print_Equivalent(self, expr): + args = sorted(expr.args, key=default_sort_key) + return self._print_LogOp(args, '⇔') + + def _print_Not(self, e): + mrow = self.dom.createElement('mrow') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('¬')) + mrow.appendChild(mo) + if (e.args[0].is_Boolean): + x = self.dom.createElement('mrow') + x.appendChild(self._l_paren()) + x.appendChild(self._print(e.args[0])) + x.appendChild(self._r_paren()) + else: + x = self._print(e.args[0]) + mrow.appendChild(x) + return mrow + + def _print_bool(self, e): + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(self.mathml_tag(e))) + return mi + + _print_BooleanTrue = _print_bool + _print_BooleanFalse = _print_bool + + def _print_NoneType(self, e): + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(self.mathml_tag(e))) + return mi + + def _print_Range(self, s): + dots = "\u2026" + if s.start.is_infinite and s.stop.is_infinite: + if s.step.is_positive: + printset = dots, -1, 0, 1, dots + else: + printset = dots, 1, 0, -1, dots + elif s.start.is_infinite: + printset = dots, s[-1] - s.step, s[-1] + elif s.stop.is_infinite: + it = iter(s) + printset = next(it), next(it), dots + elif len(s) > 4: + it = iter(s) + printset = next(it), next(it), dots, s[-1] + else: + printset = tuple(s) + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_brace()) + for i, el in enumerate(printset): + if i: + brac.appendChild(self._comma()) + if el == dots: + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(dots)) + brac.appendChild(mi) + else: + brac.appendChild(self._print(el)) + brac.appendChild(self._r_brace()) + return brac + + def _hprint_variadic_function(self, expr): + args = sorted(expr.args, key=default_sort_key) + mrow = self.dom.createElement('mrow') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode((str(expr.func)).lower())) + mrow.appendChild(mo) + mrow.appendChild(self._paren_comma_separated(*args)) + return mrow + + _print_Min = _print_Max = _hprint_variadic_function + + def _print_exp(self, expr): + msup = self.dom.createElement('msup') + msup.appendChild(self._print_Exp1(None)) + msup.appendChild(self._print(expr.args[0])) + return msup + + def _print_Relational(self, e): + mrow = self.dom.createElement('mrow') + mrow.appendChild(self._print(e.lhs)) + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode(self.mathml_tag(e))) + mrow.appendChild(x) + mrow.appendChild(self._print(e.rhs)) + return mrow + + def _print_int(self, p): + dom_element = self.dom.createElement(self.mathml_tag(p)) + dom_element.appendChild(self.dom.createTextNode(str(p))) + return dom_element + + def _print_BaseScalar(self, e): + msub = self.dom.createElement('msub') + index, system = e._id + mi = self.dom.createElement('mi') + mi.setAttribute('mathvariant', 'bold') + mi.appendChild(self.dom.createTextNode(system._variable_names[index])) + msub.appendChild(mi) + mi = self.dom.createElement('mi') + mi.setAttribute('mathvariant', 'bold') + mi.appendChild(self.dom.createTextNode(system._name)) + msub.appendChild(mi) + return msub + + def _print_BaseVector(self, e): + msub = self.dom.createElement('msub') + index, system = e._id + mover = self.dom.createElement('mover') + mi = self.dom.createElement('mi') + mi.setAttribute('mathvariant', 'bold') + mi.appendChild(self.dom.createTextNode(system._vector_names[index])) + mover.appendChild(mi) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('^')) + mover.appendChild(mo) + msub.appendChild(mover) + mi = self.dom.createElement('mi') + mi.setAttribute('mathvariant', 'bold') + mi.appendChild(self.dom.createTextNode(system._name)) + msub.appendChild(mi) + return msub + + def _print_VectorZero(self, e): + mover = self.dom.createElement('mover') + mi = self.dom.createElement('mi') + mi.setAttribute('mathvariant', 'bold') + mi.appendChild(self.dom.createTextNode("0")) + mover.appendChild(mi) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('^')) + mover.appendChild(mo) + return mover + + def _print_Cross(self, expr): + mrow = self.dom.createElement('mrow') + vec1 = expr._expr1 + vec2 = expr._expr2 + mrow.appendChild(self.parenthesize(vec1, PRECEDENCE['Mul'])) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('×')) + mrow.appendChild(mo) + mrow.appendChild(self.parenthesize(vec2, PRECEDENCE['Mul'])) + return mrow + + def _print_Curl(self, expr): + mrow = self.dom.createElement('mrow') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('∇')) + mrow.appendChild(mo) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('×')) + mrow.appendChild(mo) + mrow.appendChild(self.parenthesize(expr._expr, PRECEDENCE['Mul'])) + return mrow + + def _print_Divergence(self, expr): + mrow = self.dom.createElement('mrow') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('∇')) + mrow.appendChild(mo) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('·')) + mrow.appendChild(mo) + mrow.appendChild(self.parenthesize(expr._expr, PRECEDENCE['Mul'])) + return mrow + + def _print_Dot(self, expr): + mrow = self.dom.createElement('mrow') + vec1 = expr._expr1 + vec2 = expr._expr2 + mrow.appendChild(self.parenthesize(vec1, PRECEDENCE['Mul'])) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('·')) + mrow.appendChild(mo) + mrow.appendChild(self.parenthesize(vec2, PRECEDENCE['Mul'])) + return mrow + + def _print_Gradient(self, expr): + mrow = self.dom.createElement('mrow') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('∇')) + mrow.appendChild(mo) + mrow.appendChild(self.parenthesize(expr._expr, PRECEDENCE['Mul'])) + return mrow + + def _print_Laplacian(self, expr): + mrow = self.dom.createElement('mrow') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('∆')) + mrow.appendChild(mo) + mrow.appendChild(self.parenthesize(expr._expr, PRECEDENCE['Mul'])) + return mrow + + def _print_Integers(self, e): + x = self.dom.createElement('mi') + x.setAttribute('mathvariant', 'normal') + x.appendChild(self.dom.createTextNode('ℤ')) + return x + + def _print_Complexes(self, e): + x = self.dom.createElement('mi') + x.setAttribute('mathvariant', 'normal') + x.appendChild(self.dom.createTextNode('ℂ')) + return x + + def _print_Reals(self, e): + x = self.dom.createElement('mi') + x.setAttribute('mathvariant', 'normal') + x.appendChild(self.dom.createTextNode('ℝ')) + return x + + def _print_Naturals(self, e): + x = self.dom.createElement('mi') + x.setAttribute('mathvariant', 'normal') + x.appendChild(self.dom.createTextNode('ℕ')) + return x + + def _print_Naturals0(self, e): + sub = self.dom.createElement('msub') + x = self.dom.createElement('mi') + x.setAttribute('mathvariant', 'normal') + x.appendChild(self.dom.createTextNode('ℕ')) + sub.appendChild(x) + sub.appendChild(self._print(S.Zero)) + return sub + + def _print_SingularityFunction(self, expr): + shift = expr.args[0] - expr.args[1] + power = expr.args[2] + left = self.dom.createElement('mo') + left.appendChild(self.dom.createTextNode('\u27e8')) + right = self.dom.createElement('mo') + right.appendChild(self.dom.createTextNode('\u27e9')) + brac = self.dom.createElement('mrow') + brac.appendChild(left) + brac.appendChild(self._print(shift)) + brac.appendChild(right) + sup = self.dom.createElement('msup') + sup.appendChild(brac) + sup.appendChild(self._print(power)) + return sup + + def _print_NaN(self, e): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('NaN')) + return x + + def _print_number_function(self, e, name): + # Print name_arg[0] for one argument or name_arg[0](arg[1]) + # for more than one argument + sub = self.dom.createElement('msub') + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode(name)) + sub.appendChild(mi) + sub.appendChild(self._print(e.args[0])) + if len(e.args) == 1: + return sub + mrow = self.dom.createElement('mrow') + mrow.appendChild(sub) + mrow.appendChild(self._paren_comma_separated(*e.args[1:])) + return mrow + + def _print_bernoulli(self, e): + return self._print_number_function(e, 'B') + + _print_bell = _print_bernoulli + + def _print_catalan(self, e): + return self._print_number_function(e, 'C') + + def _print_euler(self, e): + return self._print_number_function(e, 'E') + + def _print_fibonacci(self, e): + return self._print_number_function(e, 'F') + + def _print_lucas(self, e): + return self._print_number_function(e, 'L') + + def _print_stieltjes(self, e): + return self._print_number_function(e, 'γ') + + def _print_tribonacci(self, e): + return self._print_number_function(e, 'T') + + def _print_ComplexInfinity(self, e): + x = self.dom.createElement('mover') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('∞')) + x.appendChild(mo) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('~')) + x.appendChild(mo) + return x + + def _print_EmptySet(self, e): + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode('∅')) + return x + + def _print_UniversalSet(self, e): + x = self.dom.createElement('mo') + x.appendChild(self.dom.createTextNode('𝕌')) + return x + + def _print_Adjoint(self, expr): + from sympy.matrices import MatrixSymbol + mat = expr.arg + sup = self.dom.createElement('msup') + if not isinstance(mat, MatrixSymbol): + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_paren()) + brac.appendChild(self._print(mat)) + brac.appendChild(self._r_paren()) + sup.appendChild(brac) + else: + sup.appendChild(self._print(mat)) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('†')) + sup.appendChild(mo) + return sup + + def _print_Transpose(self, expr): + from sympy.matrices import MatrixSymbol + mat = expr.arg + sup = self.dom.createElement('msup') + if not isinstance(mat, MatrixSymbol): + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_paren()) + brac.appendChild(self._print(mat)) + brac.appendChild(self._r_paren()) + sup.appendChild(brac) + else: + sup.appendChild(self._print(mat)) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('T')) + sup.appendChild(mo) + return sup + + def _print_Inverse(self, expr): + from sympy.matrices import MatrixSymbol + mat = expr.arg + sup = self.dom.createElement('msup') + if not isinstance(mat, MatrixSymbol): + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_paren()) + brac.appendChild(self._print(mat)) + brac.appendChild(self._r_paren()) + sup.appendChild(brac) + else: + sup.appendChild(self._print(mat)) + sup.appendChild(self._print(-1)) + return sup + + def _print_MatMul(self, expr): + from sympy.matrices.expressions.matmul import MatMul + + x = self.dom.createElement('mrow') + args = expr.args + if isinstance(args[0], Mul): + args = args[0].as_ordered_factors() + list(args[1:]) + else: + args = list(args) + + if isinstance(expr, MatMul) and expr.could_extract_minus_sign(): + if args[0] == -1: + args = args[1:] + else: + args[0] = -args[0] + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('-')) + x.appendChild(mo) + + for arg in args[:-1]: + x.appendChild(self.parenthesize(arg, precedence_traditional(expr), + False)) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('⁢')) + x.appendChild(mo) + x.appendChild(self.parenthesize(args[-1], precedence_traditional(expr), + False)) + return x + + def _print_MatPow(self, expr): + from sympy.matrices import MatrixSymbol + base, exp = expr.base, expr.exp + sup = self.dom.createElement('msup') + if not isinstance(base, MatrixSymbol): + brac = self.dom.createElement('mrow') + brac.appendChild(self._l_paren()) + brac.appendChild(self._print(base)) + brac.appendChild(self._r_paren()) + sup.appendChild(brac) + else: + sup.appendChild(self._print(base)) + sup.appendChild(self._print(exp)) + return sup + + def _print_HadamardProduct(self, expr): + x = self.dom.createElement('mrow') + args = expr.args + for arg in args[:-1]: + x.appendChild( + self.parenthesize(arg, precedence_traditional(expr), False)) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('∘')) + x.appendChild(mo) + x.appendChild( + self.parenthesize(args[-1], precedence_traditional(expr), False)) + return x + + def _print_ZeroMatrix(self, Z): + x = self.dom.createElement('mn') + x.appendChild(self.dom.createTextNode('𝟘')) + return x + + def _print_OneMatrix(self, Z): + x = self.dom.createElement('mn') + x.appendChild(self.dom.createTextNode('𝟙')) + return x + + def _print_Identity(self, I): + x = self.dom.createElement('mi') + x.appendChild(self.dom.createTextNode('𝕀')) + return x + + def _print_floor(self, e): + left = self.dom.createElement('mo') + left.appendChild(self.dom.createTextNode('\u230A')) + right = self.dom.createElement('mo') + right.appendChild(self.dom.createTextNode('\u230B')) + mrow = self.dom.createElement('mrow') + mrow.appendChild(left) + mrow.appendChild(self._print(e.args[0])) + mrow.appendChild(right) + return mrow + + def _print_ceiling(self, e): + left = self.dom.createElement('mo') + left.appendChild(self.dom.createTextNode('\u2308')) + right = self.dom.createElement('mo') + right.appendChild(self.dom.createTextNode('\u2309')) + mrow = self.dom.createElement('mrow') + mrow.appendChild(left) + mrow.appendChild(self._print(e.args[0])) + mrow.appendChild(right) + return mrow + + def _print_Lambda(self, e): + mrow = self.dom.createElement('mrow') + symbols = e.args[0] + if len(symbols) == 1: + symbols = self._print(symbols[0]) + else: + symbols = self._print(symbols) + mrow.appendChild(self._l_paren()) + mrow.appendChild(symbols) + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('↦')) + mrow.appendChild(mo) + mrow.appendChild(self._print(e.args[1])) + mrow.appendChild(self._r_paren()) + return mrow + + def _print_tuple(self, e): + return self._paren_comma_separated(*e) + + def _print_IndexedBase(self, e): + return self._print(e.label) + + def _print_Indexed(self, e): + x = self.dom.createElement('msub') + x.appendChild(self._print(e.base)) + if len(e.indices) == 1: + x.appendChild(self._print(e.indices[0])) + return x + x.appendChild(self._print(e.indices)) + return x + + def _print_MatrixElement(self, e): + x = self.dom.createElement('msub') + x.appendChild(self.parenthesize(e.parent, PRECEDENCE["Atom"], strict = True)) + brac = self.dom.createElement('mrow') + for i, arg in enumerate(e.indices): + if i: + brac.appendChild(self._comma()) + brac.appendChild(self._print(arg)) + x.appendChild(brac) + return x + + def _print_elliptic_f(self, e): + x = self.dom.createElement('mrow') + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode('𝖥')) + x.appendChild(mi) + x.appendChild(self._paren_bar_separated(*e.args)) + return x + + def _print_elliptic_e(self, e): + x = self.dom.createElement('mrow') + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode('𝖤')) + x.appendChild(mi) + x.appendChild(self._paren_bar_separated(*e.args)) + return x + + def _print_elliptic_pi(self, e): + x = self.dom.createElement('mrow') + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode('𝛱')) + x.appendChild(mi) + y = self.dom.createElement('mrow') + y.appendChild(self._l_paren()) + if len(e.args) == 2: + n, m = e.args + y.appendChild(self._print(n)) + y.appendChild(self._bar()) + y.appendChild(self._print(m)) + else: + n, m, z = e.args + y.appendChild(self._print(n)) + y.appendChild(self._semicolon()) + y.appendChild(self._print(m)) + y.appendChild(self._bar()) + y.appendChild(self._print(z)) + y.appendChild(self._r_paren()) + x.appendChild(y) + return x + + def _print_Ei(self, e): + x = self.dom.createElement('mrow') + mi = self.dom.createElement('mi') + mi.appendChild(self.dom.createTextNode('Ei')) + x.appendChild(mi) + x.appendChild(self._print(e.args)) + return x + + def _print_expint(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msub') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('E')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + x.appendChild(y) + x.appendChild(self._print(e.args[1:])) + return x + + def _print_jacobi(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msubsup') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('P')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + y.appendChild(self._print(e.args[1:3])) + x.appendChild(y) + x.appendChild(self._print(e.args[3:])) + return x + + def _print_gegenbauer(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msubsup') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('C')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + y.appendChild(self._print(e.args[1:2])) + x.appendChild(y) + x.appendChild(self._print(e.args[2:])) + return x + + def _print_chebyshevt(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msub') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('T')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + x.appendChild(y) + x.appendChild(self._print(e.args[1:])) + return x + + def _print_chebyshevu(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msub') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('U')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + x.appendChild(y) + x.appendChild(self._print(e.args[1:])) + return x + + def _print_legendre(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msub') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('P')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + x.appendChild(y) + x.appendChild(self._print(e.args[1:])) + return x + + def _print_assoc_legendre(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msubsup') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('P')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + y.appendChild(self._print(e.args[1:2])) + x.appendChild(y) + x.appendChild(self._print(e.args[2:])) + return x + + def _print_laguerre(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msub') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('L')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + x.appendChild(y) + x.appendChild(self._print(e.args[1:])) + return x + + def _print_assoc_laguerre(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msubsup') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('L')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + y.appendChild(self._print(e.args[1:2])) + x.appendChild(y) + x.appendChild(self._print(e.args[2:])) + return x + + def _print_hermite(self, e): + x = self.dom.createElement('mrow') + y = self.dom.createElement('msub') + mo = self.dom.createElement('mo') + mo.appendChild(self.dom.createTextNode('H')) + y.appendChild(mo) + y.appendChild(self._print(e.args[0])) + x.appendChild(y) + x.appendChild(self._print(e.args[1:])) + return x + + +@print_function(MathMLPrinterBase) +def mathml(expr, printer='content', **settings): + """Returns the MathML representation of expr. If printer is presentation + then prints Presentation MathML else prints content MathML. + """ + if printer == 'presentation': + return MathMLPresentationPrinter(settings).doprint(expr) + else: + return MathMLContentPrinter(settings).doprint(expr) + + +def print_mathml(expr, printer='content', **settings): + """ + Prints a pretty representation of the MathML code for expr. If printer is + presentation then prints Presentation MathML else prints content MathML. + + Examples + ======== + + >>> ## + >>> from sympy import print_mathml + >>> from sympy.abc import x + >>> print_mathml(x+1) #doctest: +NORMALIZE_WHITESPACE + + + x + 1 + + >>> print_mathml(x+1, printer='presentation') + + x + + + 1 + + + """ + if printer == 'presentation': + s = MathMLPresentationPrinter(settings) + else: + s = MathMLContentPrinter(settings) + xml = s._print(sympify(expr)) + pretty_xml = xml.toprettyxml() + + print(pretty_xml) + + +# For backward compatibility +MathMLPrinter = MathMLContentPrinter diff --git a/venv/lib/python3.10/site-packages/sympy/printing/numpy.py b/venv/lib/python3.10/site-packages/sympy/printing/numpy.py new file mode 100644 index 0000000000000000000000000000000000000000..1ff68454bb287bc0a1d2dfc1fe68fb05b3c22a74 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/numpy.py @@ -0,0 +1,541 @@ +from sympy.core import S +from sympy.core.function import Lambda +from sympy.core.power import Pow +from .pycode import PythonCodePrinter, _known_functions_math, _print_known_const, _print_known_func, _unpack_integral_limits, ArrayPrinter +from .codeprinter import CodePrinter + + +_not_in_numpy = 'erf erfc factorial gamma loggamma'.split() +_in_numpy = [(k, v) for k, v in _known_functions_math.items() if k not in _not_in_numpy] +_known_functions_numpy = dict(_in_numpy, **{ + 'acos': 'arccos', + 'acosh': 'arccosh', + 'asin': 'arcsin', + 'asinh': 'arcsinh', + 'atan': 'arctan', + 'atan2': 'arctan2', + 'atanh': 'arctanh', + 'exp2': 'exp2', + 'sign': 'sign', + 'logaddexp': 'logaddexp', + 'logaddexp2': 'logaddexp2', + 'isinf': 'isinf', + 'isnan': 'isnan', + +}) +_known_constants_numpy = { + 'Exp1': 'e', + 'Pi': 'pi', + 'EulerGamma': 'euler_gamma', + 'NaN': 'nan', + 'Infinity': 'inf', +} + +_numpy_known_functions = {k: 'numpy.' + v for k, v in _known_functions_numpy.items()} +_numpy_known_constants = {k: 'numpy.' + v for k, v in _known_constants_numpy.items()} + +class NumPyPrinter(ArrayPrinter, PythonCodePrinter): + """ + Numpy printer which handles vectorized piecewise functions, + logical operators, etc. + """ + + _module = 'numpy' + _kf = _numpy_known_functions + _kc = _numpy_known_constants + + def __init__(self, settings=None): + """ + `settings` is passed to CodePrinter.__init__() + `module` specifies the array module to use, currently 'NumPy', 'CuPy' + or 'JAX'. + """ + self.language = "Python with {}".format(self._module) + self.printmethod = "_{}code".format(self._module) + + self._kf = {**PythonCodePrinter._kf, **self._kf} + + super().__init__(settings=settings) + + + def _print_seq(self, seq): + "General sequence printer: converts to tuple" + # Print tuples here instead of lists because numba supports + # tuples in nopython mode. + delimiter=', ' + return '({},)'.format(delimiter.join(self._print(item) for item in seq)) + + def _print_NegativeInfinity(self, expr): + return '-' + self._print(S.Infinity) + + def _print_MatMul(self, expr): + "Matrix multiplication printer" + if expr.as_coeff_matrices()[0] is not S.One: + expr_list = expr.as_coeff_matrices()[1]+[(expr.as_coeff_matrices()[0])] + return '({})'.format(').dot('.join(self._print(i) for i in expr_list)) + return '({})'.format(').dot('.join(self._print(i) for i in expr.args)) + + def _print_MatPow(self, expr): + "Matrix power printer" + return '{}({}, {})'.format(self._module_format(self._module + '.linalg.matrix_power'), + self._print(expr.args[0]), self._print(expr.args[1])) + + def _print_Inverse(self, expr): + "Matrix inverse printer" + return '{}({})'.format(self._module_format(self._module + '.linalg.inv'), + self._print(expr.args[0])) + + def _print_DotProduct(self, expr): + # DotProduct allows any shape order, but numpy.dot does matrix + # multiplication, so we have to make sure it gets 1 x n by n x 1. + arg1, arg2 = expr.args + if arg1.shape[0] != 1: + arg1 = arg1.T + if arg2.shape[1] != 1: + arg2 = arg2.T + + return "%s(%s, %s)" % (self._module_format(self._module + '.dot'), + self._print(arg1), + self._print(arg2)) + + def _print_MatrixSolve(self, expr): + return "%s(%s, %s)" % (self._module_format(self._module + '.linalg.solve'), + self._print(expr.matrix), + self._print(expr.vector)) + + def _print_ZeroMatrix(self, expr): + return '{}({})'.format(self._module_format(self._module + '.zeros'), + self._print(expr.shape)) + + def _print_OneMatrix(self, expr): + return '{}({})'.format(self._module_format(self._module + '.ones'), + self._print(expr.shape)) + + def _print_FunctionMatrix(self, expr): + from sympy.abc import i, j + lamda = expr.lamda + if not isinstance(lamda, Lambda): + lamda = Lambda((i, j), lamda(i, j)) + return '{}(lambda {}: {}, {})'.format(self._module_format(self._module + '.fromfunction'), + ', '.join(self._print(arg) for arg in lamda.args[0]), + self._print(lamda.args[1]), self._print(expr.shape)) + + def _print_HadamardProduct(self, expr): + func = self._module_format(self._module + '.multiply') + return ''.join('{}({}, '.format(func, self._print(arg)) \ + for arg in expr.args[:-1]) + "{}{}".format(self._print(expr.args[-1]), + ')' * (len(expr.args) - 1)) + + def _print_KroneckerProduct(self, expr): + func = self._module_format(self._module + '.kron') + return ''.join('{}({}, '.format(func, self._print(arg)) \ + for arg in expr.args[:-1]) + "{}{}".format(self._print(expr.args[-1]), + ')' * (len(expr.args) - 1)) + + def _print_Adjoint(self, expr): + return '{}({}({}))'.format( + self._module_format(self._module + '.conjugate'), + self._module_format(self._module + '.transpose'), + self._print(expr.args[0])) + + def _print_DiagonalOf(self, expr): + vect = '{}({})'.format( + self._module_format(self._module + '.diag'), + self._print(expr.arg)) + return '{}({}, (-1, 1))'.format( + self._module_format(self._module + '.reshape'), vect) + + def _print_DiagMatrix(self, expr): + return '{}({})'.format(self._module_format(self._module + '.diagflat'), + self._print(expr.args[0])) + + def _print_DiagonalMatrix(self, expr): + return '{}({}, {}({}, {}))'.format(self._module_format(self._module + '.multiply'), + self._print(expr.arg), self._module_format(self._module + '.eye'), + self._print(expr.shape[0]), self._print(expr.shape[1])) + + def _print_Piecewise(self, expr): + "Piecewise function printer" + from sympy.logic.boolalg import ITE, simplify_logic + def print_cond(cond): + """ Problem having an ITE in the cond. """ + if cond.has(ITE): + return self._print(simplify_logic(cond)) + else: + return self._print(cond) + exprs = '[{}]'.format(','.join(self._print(arg.expr) for arg in expr.args)) + conds = '[{}]'.format(','.join(print_cond(arg.cond) for arg in expr.args)) + # If [default_value, True] is a (expr, cond) sequence in a Piecewise object + # it will behave the same as passing the 'default' kwarg to select() + # *as long as* it is the last element in expr.args. + # If this is not the case, it may be triggered prematurely. + return '{}({}, {}, default={})'.format( + self._module_format(self._module + '.select'), conds, exprs, + self._print(S.NaN)) + + def _print_Relational(self, expr): + "Relational printer for Equality and Unequality" + op = { + '==' :'equal', + '!=' :'not_equal', + '<' :'less', + '<=' :'less_equal', + '>' :'greater', + '>=' :'greater_equal', + } + if expr.rel_op in op: + lhs = self._print(expr.lhs) + rhs = self._print(expr.rhs) + return '{op}({lhs}, {rhs})'.format(op=self._module_format(self._module + '.'+op[expr.rel_op]), + lhs=lhs, rhs=rhs) + return super()._print_Relational(expr) + + def _print_And(self, expr): + "Logical And printer" + # We have to override LambdaPrinter because it uses Python 'and' keyword. + # If LambdaPrinter didn't define it, we could use StrPrinter's + # version of the function and add 'logical_and' to NUMPY_TRANSLATIONS. + return '{}.reduce(({}))'.format(self._module_format(self._module + '.logical_and'), ','.join(self._print(i) for i in expr.args)) + + def _print_Or(self, expr): + "Logical Or printer" + # We have to override LambdaPrinter because it uses Python 'or' keyword. + # If LambdaPrinter didn't define it, we could use StrPrinter's + # version of the function and add 'logical_or' to NUMPY_TRANSLATIONS. + return '{}.reduce(({}))'.format(self._module_format(self._module + '.logical_or'), ','.join(self._print(i) for i in expr.args)) + + def _print_Not(self, expr): + "Logical Not printer" + # We have to override LambdaPrinter because it uses Python 'not' keyword. + # If LambdaPrinter didn't define it, we would still have to define our + # own because StrPrinter doesn't define it. + return '{}({})'.format(self._module_format(self._module + '.logical_not'), ','.join(self._print(i) for i in expr.args)) + + def _print_Pow(self, expr, rational=False): + # XXX Workaround for negative integer power error + if expr.exp.is_integer and expr.exp.is_negative: + expr = Pow(expr.base, expr.exp.evalf(), evaluate=False) + return self._hprint_Pow(expr, rational=rational, sqrt=self._module + '.sqrt') + + def _helper_minimum_maximum(self, op: str, *args): + if len(args) == 0: + raise NotImplementedError(f"Need at least one argument for {op}") + elif len(args) == 1: + return self._print(args[0]) + _reduce = self._module_format('functools.reduce') + s_args = [self._print(arg) for arg in args] + return f"{_reduce}({op}, [{', '.join(s_args)}])" + + def _print_Min(self, expr): + return self._print_minimum(expr) + + def _print_amin(self, expr): + return '{}({}, axis={})'.format(self._module_format(self._module + '.amin'), self._print(expr.array), self._print(expr.axis)) + + def _print_minimum(self, expr): + op = self._module_format(self._module + '.minimum') + return self._helper_minimum_maximum(op, *expr.args) + + def _print_Max(self, expr): + return self._print_maximum(expr) + + def _print_amax(self, expr): + return '{}({}, axis={})'.format(self._module_format(self._module + '.amax'), self._print(expr.array), self._print(expr.axis)) + + def _print_maximum(self, expr): + op = self._module_format(self._module + '.maximum') + return self._helper_minimum_maximum(op, *expr.args) + + def _print_arg(self, expr): + return "%s(%s)" % (self._module_format(self._module + '.angle'), self._print(expr.args[0])) + + def _print_im(self, expr): + return "%s(%s)" % (self._module_format(self._module + '.imag'), self._print(expr.args[0])) + + def _print_Mod(self, expr): + return "%s(%s)" % (self._module_format(self._module + '.mod'), ', '.join( + (self._print(arg) for arg in expr.args))) + + def _print_re(self, expr): + return "%s(%s)" % (self._module_format(self._module + '.real'), self._print(expr.args[0])) + + def _print_sinc(self, expr): + return "%s(%s)" % (self._module_format(self._module + '.sinc'), self._print(expr.args[0]/S.Pi)) + + def _print_MatrixBase(self, expr): + if 0 in expr.shape: + func = self._module_format(f'{self._module}.{self._zeros}') + return f"{func}({self._print(expr.shape)})" + func = self.known_functions.get(expr.__class__.__name__, None) + if func is None: + func = self._module_format(f'{self._module}.array') + return "%s(%s)" % (func, self._print(expr.tolist())) + + def _print_Identity(self, expr): + shape = expr.shape + if all(dim.is_Integer for dim in shape): + return "%s(%s)" % (self._module_format(self._module + '.eye'), self._print(expr.shape[0])) + else: + raise NotImplementedError("Symbolic matrix dimensions are not yet supported for identity matrices") + + def _print_BlockMatrix(self, expr): + return '{}({})'.format(self._module_format(self._module + '.block'), + self._print(expr.args[0].tolist())) + + def _print_NDimArray(self, expr): + if expr.rank() == 0: + func = self._module_format(f'{self._module}.array') + return f"{func}({self._print(expr[()])})" + if 0 in expr.shape: + func = self._module_format(f'{self._module}.{self._zeros}') + return f"{func}({self._print(expr.shape)})" + func = self._module_format(f'{self._module}.array') + return f"{func}({self._print(expr.tolist())})" + + _add = "add" + _einsum = "einsum" + _transpose = "transpose" + _ones = "ones" + _zeros = "zeros" + + _print_lowergamma = CodePrinter._print_not_supported + _print_uppergamma = CodePrinter._print_not_supported + _print_fresnelc = CodePrinter._print_not_supported + _print_fresnels = CodePrinter._print_not_supported + +for func in _numpy_known_functions: + setattr(NumPyPrinter, f'_print_{func}', _print_known_func) + +for const in _numpy_known_constants: + setattr(NumPyPrinter, f'_print_{const}', _print_known_const) + + +_known_functions_scipy_special = { + 'Ei': 'expi', + 'erf': 'erf', + 'erfc': 'erfc', + 'besselj': 'jv', + 'bessely': 'yv', + 'besseli': 'iv', + 'besselk': 'kv', + 'cosm1': 'cosm1', + 'powm1': 'powm1', + 'factorial': 'factorial', + 'gamma': 'gamma', + 'loggamma': 'gammaln', + 'digamma': 'psi', + 'polygamma': 'polygamma', + 'RisingFactorial': 'poch', + 'jacobi': 'eval_jacobi', + 'gegenbauer': 'eval_gegenbauer', + 'chebyshevt': 'eval_chebyt', + 'chebyshevu': 'eval_chebyu', + 'legendre': 'eval_legendre', + 'hermite': 'eval_hermite', + 'laguerre': 'eval_laguerre', + 'assoc_laguerre': 'eval_genlaguerre', + 'beta': 'beta', + 'LambertW' : 'lambertw', +} + +_known_constants_scipy_constants = { + 'GoldenRatio': 'golden_ratio', + 'Pi': 'pi', +} +_scipy_known_functions = {k : "scipy.special." + v for k, v in _known_functions_scipy_special.items()} +_scipy_known_constants = {k : "scipy.constants." + v for k, v in _known_constants_scipy_constants.items()} + +class SciPyPrinter(NumPyPrinter): + + _kf = {**NumPyPrinter._kf, **_scipy_known_functions} + _kc = {**NumPyPrinter._kc, **_scipy_known_constants} + + def __init__(self, settings=None): + super().__init__(settings=settings) + self.language = "Python with SciPy and NumPy" + + def _print_SparseRepMatrix(self, expr): + i, j, data = [], [], [] + for (r, c), v in expr.todok().items(): + i.append(r) + j.append(c) + data.append(v) + + return "{name}(({data}, ({i}, {j})), shape={shape})".format( + name=self._module_format('scipy.sparse.coo_matrix'), + data=data, i=i, j=j, shape=expr.shape + ) + + _print_ImmutableSparseMatrix = _print_SparseRepMatrix + + # SciPy's lpmv has a different order of arguments from assoc_legendre + def _print_assoc_legendre(self, expr): + return "{0}({2}, {1}, {3})".format( + self._module_format('scipy.special.lpmv'), + self._print(expr.args[0]), + self._print(expr.args[1]), + self._print(expr.args[2])) + + def _print_lowergamma(self, expr): + return "{0}({2})*{1}({2}, {3})".format( + self._module_format('scipy.special.gamma'), + self._module_format('scipy.special.gammainc'), + self._print(expr.args[0]), + self._print(expr.args[1])) + + def _print_uppergamma(self, expr): + return "{0}({2})*{1}({2}, {3})".format( + self._module_format('scipy.special.gamma'), + self._module_format('scipy.special.gammaincc'), + self._print(expr.args[0]), + self._print(expr.args[1])) + + def _print_betainc(self, expr): + betainc = self._module_format('scipy.special.betainc') + beta = self._module_format('scipy.special.beta') + args = [self._print(arg) for arg in expr.args] + return f"({betainc}({args[0]}, {args[1]}, {args[3]}) - {betainc}({args[0]}, {args[1]}, {args[2]})) \ + * {beta}({args[0]}, {args[1]})" + + def _print_betainc_regularized(self, expr): + return "{0}({1}, {2}, {4}) - {0}({1}, {2}, {3})".format( + self._module_format('scipy.special.betainc'), + self._print(expr.args[0]), + self._print(expr.args[1]), + self._print(expr.args[2]), + self._print(expr.args[3])) + + def _print_fresnels(self, expr): + return "{}({})[0]".format( + self._module_format("scipy.special.fresnel"), + self._print(expr.args[0])) + + def _print_fresnelc(self, expr): + return "{}({})[1]".format( + self._module_format("scipy.special.fresnel"), + self._print(expr.args[0])) + + def _print_airyai(self, expr): + return "{}({})[0]".format( + self._module_format("scipy.special.airy"), + self._print(expr.args[0])) + + def _print_airyaiprime(self, expr): + return "{}({})[1]".format( + self._module_format("scipy.special.airy"), + self._print(expr.args[0])) + + def _print_airybi(self, expr): + return "{}({})[2]".format( + self._module_format("scipy.special.airy"), + self._print(expr.args[0])) + + def _print_airybiprime(self, expr): + return "{}({})[3]".format( + self._module_format("scipy.special.airy"), + self._print(expr.args[0])) + + def _print_bernoulli(self, expr): + # scipy's bernoulli is inconsistent with SymPy's so rewrite + return self._print(expr._eval_rewrite_as_zeta(*expr.args)) + + def _print_harmonic(self, expr): + return self._print(expr._eval_rewrite_as_zeta(*expr.args)) + + def _print_Integral(self, e): + integration_vars, limits = _unpack_integral_limits(e) + + if len(limits) == 1: + # nicer (but not necessary) to prefer quad over nquad for 1D case + module_str = self._module_format("scipy.integrate.quad") + limit_str = "%s, %s" % tuple(map(self._print, limits[0])) + else: + module_str = self._module_format("scipy.integrate.nquad") + limit_str = "({})".format(", ".join( + "(%s, %s)" % tuple(map(self._print, l)) for l in limits)) + + return "{}(lambda {}: {}, {})[0]".format( + module_str, + ", ".join(map(self._print, integration_vars)), + self._print(e.args[0]), + limit_str) + + def _print_Si(self, expr): + return "{}({})[0]".format( + self._module_format("scipy.special.sici"), + self._print(expr.args[0])) + + def _print_Ci(self, expr): + return "{}({})[1]".format( + self._module_format("scipy.special.sici"), + self._print(expr.args[0])) + +for func in _scipy_known_functions: + setattr(SciPyPrinter, f'_print_{func}', _print_known_func) + +for const in _scipy_known_constants: + setattr(SciPyPrinter, f'_print_{const}', _print_known_const) + + +_cupy_known_functions = {k : "cupy." + v for k, v in _known_functions_numpy.items()} +_cupy_known_constants = {k : "cupy." + v for k, v in _known_constants_numpy.items()} + +class CuPyPrinter(NumPyPrinter): + """ + CuPy printer which handles vectorized piecewise functions, + logical operators, etc. + """ + + _module = 'cupy' + _kf = _cupy_known_functions + _kc = _cupy_known_constants + + def __init__(self, settings=None): + super().__init__(settings=settings) + +for func in _cupy_known_functions: + setattr(CuPyPrinter, f'_print_{func}', _print_known_func) + +for const in _cupy_known_constants: + setattr(CuPyPrinter, f'_print_{const}', _print_known_const) + + +_jax_known_functions = {k: 'jax.numpy.' + v for k, v in _known_functions_numpy.items()} +_jax_known_constants = {k: 'jax.numpy.' + v for k, v in _known_constants_numpy.items()} + +class JaxPrinter(NumPyPrinter): + """ + JAX printer which handles vectorized piecewise functions, + logical operators, etc. + """ + _module = "jax.numpy" + + _kf = _jax_known_functions + _kc = _jax_known_constants + + def __init__(self, settings=None): + super().__init__(settings=settings) + self.printmethod = '_jaxcode' + + # These need specific override to allow for the lack of "jax.numpy.reduce" + def _print_And(self, expr): + "Logical And printer" + return "{}({}.asarray([{}]), axis=0)".format( + self._module_format(self._module + ".all"), + self._module_format(self._module), + ",".join(self._print(i) for i in expr.args), + ) + + def _print_Or(self, expr): + "Logical Or printer" + return "{}({}.asarray([{}]), axis=0)".format( + self._module_format(self._module + ".any"), + self._module_format(self._module), + ",".join(self._print(i) for i in expr.args), + ) + +for func in _jax_known_functions: + setattr(JaxPrinter, f'_print_{func}', _print_known_func) + +for const in _jax_known_constants: + setattr(JaxPrinter, f'_print_{const}', _print_known_const) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/octave.py b/venv/lib/python3.10/site-packages/sympy/printing/octave.py new file mode 100644 index 0000000000000000000000000000000000000000..2cf2d6a5754668d7a95ef5dc7b27b4864756a9e5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/octave.py @@ -0,0 +1,711 @@ +""" +Octave (and Matlab) code printer + +The `OctaveCodePrinter` converts SymPy expressions into Octave expressions. +It uses a subset of the Octave language for Matlab compatibility. + +A complete code generator, which uses `octave_code` extensively, can be found +in `sympy.utilities.codegen`. The `codegen` module can be used to generate +complete source code files. + +""" + +from __future__ import annotations +from typing import Any + +from sympy.core import Mul, Pow, S, Rational +from sympy.core.mul import _keep_coeff +from sympy.core.numbers import equal_valued +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import precedence, PRECEDENCE +from re import search + +# List of known functions. First, those that have the same name in +# SymPy and Octave. This is almost certainly incomplete! +known_fcns_src1 = ["sin", "cos", "tan", "cot", "sec", "csc", + "asin", "acos", "acot", "atan", "atan2", "asec", "acsc", + "sinh", "cosh", "tanh", "coth", "csch", "sech", + "asinh", "acosh", "atanh", "acoth", "asech", "acsch", + "erfc", "erfi", "erf", "erfinv", "erfcinv", + "besseli", "besselj", "besselk", "bessely", + "bernoulli", "beta", "euler", "exp", "factorial", "floor", + "fresnelc", "fresnels", "gamma", "harmonic", "log", + "polylog", "sign", "zeta", "legendre"] + +# These functions have different names ("SymPy": "Octave"), more +# generally a mapping to (argument_conditions, octave_function). +known_fcns_src2 = { + "Abs": "abs", + "arg": "angle", # arg/angle ok in Octave but only angle in Matlab + "binomial": "bincoeff", + "ceiling": "ceil", + "chebyshevu": "chebyshevU", + "chebyshevt": "chebyshevT", + "Chi": "coshint", + "Ci": "cosint", + "conjugate": "conj", + "DiracDelta": "dirac", + "Heaviside": "heaviside", + "im": "imag", + "laguerre": "laguerreL", + "LambertW": "lambertw", + "li": "logint", + "loggamma": "gammaln", + "Max": "max", + "Min": "min", + "Mod": "mod", + "polygamma": "psi", + "re": "real", + "RisingFactorial": "pochhammer", + "Shi": "sinhint", + "Si": "sinint", +} + + +class OctaveCodePrinter(CodePrinter): + """ + A printer to convert expressions to strings of Octave/Matlab code. + """ + printmethod = "_octave" + language = "Octave" + + _operators = { + 'and': '&', + 'or': '|', + 'not': '~', + } + + _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{ + 'precision': 17, + 'user_functions': {}, + 'contract': True, + 'inline': True, + }) + # Note: contract is for expressing tensors as loops (if True), or just + # assignment (if False). FIXME: this should be looked a more carefully + # for Octave. + + + def __init__(self, settings={}): + super().__init__(settings) + self.known_functions = dict(zip(known_fcns_src1, known_fcns_src1)) + self.known_functions.update(dict(known_fcns_src2)) + userfuncs = settings.get('user_functions', {}) + self.known_functions.update(userfuncs) + + + def _rate_index_position(self, p): + return p*5 + + + def _get_statement(self, codestring): + return "%s;" % codestring + + + def _get_comment(self, text): + return "% {}".format(text) + + + def _declare_number_const(self, name, value): + return "{} = {};".format(name, value) + + + def _format_code(self, lines): + return self.indent_code(lines) + + + def _traverse_matrix_indices(self, mat): + # Octave uses Fortran order (column-major) + rows, cols = mat.shape + return ((i, j) for j in range(cols) for i in range(rows)) + + + def _get_loop_opening_ending(self, indices): + open_lines = [] + close_lines = [] + for i in indices: + # Octave arrays start at 1 and end at dimension + var, start, stop = map(self._print, + [i.label, i.lower + 1, i.upper + 1]) + open_lines.append("for %s = %s:%s" % (var, start, stop)) + close_lines.append("end") + return open_lines, close_lines + + + def _print_Mul(self, expr): + # print complex numbers nicely in Octave + if (expr.is_number and expr.is_imaginary and + (S.ImaginaryUnit*expr).is_Integer): + return "%si" % self._print(-S.ImaginaryUnit*expr) + + # cribbed from str.py + prec = precedence(expr) + + c, e = expr.as_coeff_Mul() + if c < 0: + expr = _keep_coeff(-c, e) + sign = "-" + else: + sign = "" + + a = [] # items in the numerator + b = [] # items that are in the denominator (if any) + + pow_paren = [] # Will collect all pow with more than one base element and exp = -1 + + if self.order not in ('old', 'none'): + args = expr.as_ordered_factors() + else: + # use make_args in case expr was something like -x -> x + args = Mul.make_args(expr) + + # Gather args for numerator/denominator + for item in args: + if (item.is_commutative and item.is_Pow and item.exp.is_Rational + and item.exp.is_negative): + if item.exp != -1: + b.append(Pow(item.base, -item.exp, evaluate=False)) + else: + if len(item.args[0].args) != 1 and isinstance(item.base, Mul): # To avoid situations like #14160 + pow_paren.append(item) + b.append(Pow(item.base, -item.exp)) + elif item.is_Rational and item is not S.Infinity: + if item.p != 1: + a.append(Rational(item.p)) + if item.q != 1: + b.append(Rational(item.q)) + else: + a.append(item) + + a = a or [S.One] + + a_str = [self.parenthesize(x, prec) for x in a] + b_str = [self.parenthesize(x, prec) for x in b] + + # To parenthesize Pow with exp = -1 and having more than one Symbol + for item in pow_paren: + if item.base in b: + b_str[b.index(item.base)] = "(%s)" % b_str[b.index(item.base)] + + # from here it differs from str.py to deal with "*" and ".*" + def multjoin(a, a_str): + # here we probably are assuming the constants will come first + r = a_str[0] + for i in range(1, len(a)): + mulsym = '*' if a[i-1].is_number else '.*' + r = r + mulsym + a_str[i] + return r + + if not b: + return sign + multjoin(a, a_str) + elif len(b) == 1: + divsym = '/' if b[0].is_number else './' + return sign + multjoin(a, a_str) + divsym + b_str[0] + else: + divsym = '/' if all(bi.is_number for bi in b) else './' + return (sign + multjoin(a, a_str) + + divsym + "(%s)" % multjoin(b, b_str)) + + def _print_Relational(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + op = expr.rel_op + return "{} {} {}".format(lhs_code, op, rhs_code) + + def _print_Pow(self, expr): + powsymbol = '^' if all(x.is_number for x in expr.args) else '.^' + + PREC = precedence(expr) + + if equal_valued(expr.exp, 0.5): + return "sqrt(%s)" % self._print(expr.base) + + if expr.is_commutative: + if equal_valued(expr.exp, -0.5): + sym = '/' if expr.base.is_number else './' + return "1" + sym + "sqrt(%s)" % self._print(expr.base) + if equal_valued(expr.exp, -1): + sym = '/' if expr.base.is_number else './' + return "1" + sym + "%s" % self.parenthesize(expr.base, PREC) + + return '%s%s%s' % (self.parenthesize(expr.base, PREC), powsymbol, + self.parenthesize(expr.exp, PREC)) + + + def _print_MatPow(self, expr): + PREC = precedence(expr) + return '%s^%s' % (self.parenthesize(expr.base, PREC), + self.parenthesize(expr.exp, PREC)) + + def _print_MatrixSolve(self, expr): + PREC = precedence(expr) + return "%s \\ %s" % (self.parenthesize(expr.matrix, PREC), + self.parenthesize(expr.vector, PREC)) + + def _print_Pi(self, expr): + return 'pi' + + + def _print_ImaginaryUnit(self, expr): + return "1i" + + + def _print_Exp1(self, expr): + return "exp(1)" + + + def _print_GoldenRatio(self, expr): + # FIXME: how to do better, e.g., for octave_code(2*GoldenRatio)? + #return self._print((1+sqrt(S(5)))/2) + return "(1+sqrt(5))/2" + + + def _print_Assignment(self, expr): + from sympy.codegen.ast import Assignment + from sympy.functions.elementary.piecewise import Piecewise + from sympy.tensor.indexed import IndexedBase + # Copied from codeprinter, but remove special MatrixSymbol treatment + lhs = expr.lhs + rhs = expr.rhs + # We special case assignments that take multiple lines + if not self._settings["inline"] and isinstance(expr.rhs, Piecewise): + # Here we modify Piecewise so each expression is now + # an Assignment, and then continue on the print. + expressions = [] + conditions = [] + for (e, c) in rhs.args: + expressions.append(Assignment(lhs, e)) + conditions.append(c) + temp = Piecewise(*zip(expressions, conditions)) + return self._print(temp) + if self._settings["contract"] and (lhs.has(IndexedBase) or + rhs.has(IndexedBase)): + # Here we check if there is looping to be done, and if so + # print the required loops. + return self._doprint_loops(rhs, lhs) + else: + lhs_code = self._print(lhs) + rhs_code = self._print(rhs) + return self._get_statement("%s = %s" % (lhs_code, rhs_code)) + + + def _print_Infinity(self, expr): + return 'inf' + + + def _print_NegativeInfinity(self, expr): + return '-inf' + + + def _print_NaN(self, expr): + return 'NaN' + + + def _print_list(self, expr): + return '{' + ', '.join(self._print(a) for a in expr) + '}' + _print_tuple = _print_list + _print_Tuple = _print_list + _print_List = _print_list + + + def _print_BooleanTrue(self, expr): + return "true" + + + def _print_BooleanFalse(self, expr): + return "false" + + + def _print_bool(self, expr): + return str(expr).lower() + + + # Could generate quadrature code for definite Integrals? + #_print_Integral = _print_not_supported + + + def _print_MatrixBase(self, A): + # Handle zero dimensions: + if (A.rows, A.cols) == (0, 0): + return '[]' + elif S.Zero in A.shape: + return 'zeros(%s, %s)' % (A.rows, A.cols) + elif (A.rows, A.cols) == (1, 1): + # Octave does not distinguish between scalars and 1x1 matrices + return self._print(A[0, 0]) + return "[%s]" % "; ".join(" ".join([self._print(a) for a in A[r, :]]) + for r in range(A.rows)) + + + def _print_SparseRepMatrix(self, A): + from sympy.matrices import Matrix + L = A.col_list() + # make row vectors of the indices and entries + I = Matrix([[k[0] + 1 for k in L]]) + J = Matrix([[k[1] + 1 for k in L]]) + AIJ = Matrix([[k[2] for k in L]]) + return "sparse(%s, %s, %s, %s, %s)" % (self._print(I), self._print(J), + self._print(AIJ), A.rows, A.cols) + + + def _print_MatrixElement(self, expr): + return self.parenthesize(expr.parent, PRECEDENCE["Atom"], strict=True) \ + + '(%s, %s)' % (expr.i + 1, expr.j + 1) + + + def _print_MatrixSlice(self, expr): + def strslice(x, lim): + l = x[0] + 1 + h = x[1] + step = x[2] + lstr = self._print(l) + hstr = 'end' if h == lim else self._print(h) + if step == 1: + if l == 1 and h == lim: + return ':' + if l == h: + return lstr + else: + return lstr + ':' + hstr + else: + return ':'.join((lstr, self._print(step), hstr)) + return (self._print(expr.parent) + '(' + + strslice(expr.rowslice, expr.parent.shape[0]) + ', ' + + strslice(expr.colslice, expr.parent.shape[1]) + ')') + + + def _print_Indexed(self, expr): + inds = [ self._print(i) for i in expr.indices ] + return "%s(%s)" % (self._print(expr.base.label), ", ".join(inds)) + + + def _print_KroneckerDelta(self, expr): + prec = PRECEDENCE["Pow"] + return "double(%s == %s)" % tuple(self.parenthesize(x, prec) + for x in expr.args) + + def _print_HadamardProduct(self, expr): + return '.*'.join([self.parenthesize(arg, precedence(expr)) + for arg in expr.args]) + + def _print_HadamardPower(self, expr): + PREC = precedence(expr) + return '.**'.join([ + self.parenthesize(expr.base, PREC), + self.parenthesize(expr.exp, PREC) + ]) + + def _print_Identity(self, expr): + shape = expr.shape + if len(shape) == 2 and shape[0] == shape[1]: + shape = [shape[0]] + s = ", ".join(self._print(n) for n in shape) + return "eye(" + s + ")" + + def _print_lowergamma(self, expr): + # Octave implements regularized incomplete gamma function + return "(gammainc({1}, {0}).*gamma({0}))".format( + self._print(expr.args[0]), self._print(expr.args[1])) + + + def _print_uppergamma(self, expr): + return "(gammainc({1}, {0}, 'upper').*gamma({0}))".format( + self._print(expr.args[0]), self._print(expr.args[1])) + + + def _print_sinc(self, expr): + #Note: Divide by pi because Octave implements normalized sinc function. + return "sinc(%s)" % self._print(expr.args[0]/S.Pi) + + + def _print_hankel1(self, expr): + return "besselh(%s, 1, %s)" % (self._print(expr.order), + self._print(expr.argument)) + + + def _print_hankel2(self, expr): + return "besselh(%s, 2, %s)" % (self._print(expr.order), + self._print(expr.argument)) + + + # Note: as of 2015, Octave doesn't have spherical Bessel functions + def _print_jn(self, expr): + from sympy.functions import sqrt, besselj + x = expr.argument + expr2 = sqrt(S.Pi/(2*x))*besselj(expr.order + S.Half, x) + return self._print(expr2) + + + def _print_yn(self, expr): + from sympy.functions import sqrt, bessely + x = expr.argument + expr2 = sqrt(S.Pi/(2*x))*bessely(expr.order + S.Half, x) + return self._print(expr2) + + + def _print_airyai(self, expr): + return "airy(0, %s)" % self._print(expr.args[0]) + + + def _print_airyaiprime(self, expr): + return "airy(1, %s)" % self._print(expr.args[0]) + + + def _print_airybi(self, expr): + return "airy(2, %s)" % self._print(expr.args[0]) + + + def _print_airybiprime(self, expr): + return "airy(3, %s)" % self._print(expr.args[0]) + + + def _print_expint(self, expr): + mu, x = expr.args + if mu != 1: + return self._print_not_supported(expr) + return "expint(%s)" % self._print(x) + + + def _one_or_two_reversed_args(self, expr): + assert len(expr.args) <= 2 + return '{name}({args})'.format( + name=self.known_functions[expr.__class__.__name__], + args=", ".join([self._print(x) for x in reversed(expr.args)]) + ) + + + _print_DiracDelta = _print_LambertW = _one_or_two_reversed_args + + + def _nested_binary_math_func(self, expr): + return '{name}({arg1}, {arg2})'.format( + name=self.known_functions[expr.__class__.__name__], + arg1=self._print(expr.args[0]), + arg2=self._print(expr.func(*expr.args[1:])) + ) + + _print_Max = _print_Min = _nested_binary_math_func + + + def _print_Piecewise(self, expr): + if expr.args[-1].cond != True: + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + lines = [] + if self._settings["inline"]: + # Express each (cond, expr) pair in a nested Horner form: + # (condition) .* (expr) + (not cond) .* () + # Expressions that result in multiple statements won't work here. + ecpairs = ["({0}).*({1}) + (~({0})).*(".format + (self._print(c), self._print(e)) + for e, c in expr.args[:-1]] + elast = "%s" % self._print(expr.args[-1].expr) + pw = " ...\n".join(ecpairs) + elast + ")"*len(ecpairs) + # Note: current need these outer brackets for 2*pw. Would be + # nicer to teach parenthesize() to do this for us when needed! + return "(" + pw + ")" + else: + for i, (e, c) in enumerate(expr.args): + if i == 0: + lines.append("if (%s)" % self._print(c)) + elif i == len(expr.args) - 1 and c == True: + lines.append("else") + else: + lines.append("elseif (%s)" % self._print(c)) + code0 = self._print(e) + lines.append(code0) + if i == len(expr.args) - 1: + lines.append("end") + return "\n".join(lines) + + + def _print_zeta(self, expr): + if len(expr.args) == 1: + return "zeta(%s)" % self._print(expr.args[0]) + else: + # Matlab two argument zeta is not equivalent to SymPy's + return self._print_not_supported(expr) + + + def indent_code(self, code): + """Accepts a string of code or a list of code lines""" + + # code mostly copied from ccode + if isinstance(code, str): + code_lines = self.indent_code(code.splitlines(True)) + return ''.join(code_lines) + + tab = " " + inc_regex = ('^function ', '^if ', '^elseif ', '^else$', '^for ') + dec_regex = ('^end$', '^elseif ', '^else$') + + # pre-strip left-space from the code + code = [ line.lstrip(' \t') for line in code ] + + increase = [ int(any(search(re, line) for re in inc_regex)) + for line in code ] + decrease = [ int(any(search(re, line) for re in dec_regex)) + for line in code ] + + pretty = [] + level = 0 + for n, line in enumerate(code): + if line in ('', '\n'): + pretty.append(line) + continue + level -= decrease[n] + pretty.append("%s%s" % (tab*level, line)) + level += increase[n] + return pretty + + +def octave_code(expr, assign_to=None, **settings): + r"""Converts `expr` to a string of Octave (or Matlab) code. + + The string uses a subset of the Octave language for Matlab compatibility. + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used as the name of the variable to which + the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol``, or ``Indexed`` type. This can be helpful for + expressions that generate multi-line statements. + precision : integer, optional + The precision for numbers such as pi [default=16]. + user_functions : dict, optional + A dictionary where keys are ``FunctionClass`` instances and values are + their string representations. Alternatively, the dictionary value can + be a list of tuples i.e. [(argument_test, cfunction_string)]. See + below for examples. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + inline: bool, optional + If True, we try to create single-statement code instead of multiple + statements. [default=True]. + + Examples + ======== + + >>> from sympy import octave_code, symbols, sin, pi + >>> x = symbols('x') + >>> octave_code(sin(x).series(x).removeO()) + 'x.^5/120 - x.^3/6 + x' + + >>> from sympy import Rational, ceiling + >>> x, y, tau = symbols("x, y, tau") + >>> octave_code((2*tau)**Rational(7, 2)) + '8*sqrt(2)*tau.^(7/2)' + + Note that element-wise (Hadamard) operations are used by default between + symbols. This is because its very common in Octave to write "vectorized" + code. It is harmless if the values are scalars. + + >>> octave_code(sin(pi*x*y), assign_to="s") + 's = sin(pi*x.*y);' + + If you need a matrix product "*" or matrix power "^", you can specify the + symbol as a ``MatrixSymbol``. + + >>> from sympy import Symbol, MatrixSymbol + >>> n = Symbol('n', integer=True, positive=True) + >>> A = MatrixSymbol('A', n, n) + >>> octave_code(3*pi*A**3) + '(3*pi)*A^3' + + This class uses several rules to decide which symbol to use a product. + Pure numbers use "*", Symbols use ".*" and MatrixSymbols use "*". + A HadamardProduct can be used to specify componentwise multiplication ".*" + of two MatrixSymbols. There is currently there is no easy way to specify + scalar symbols, so sometimes the code might have some minor cosmetic + issues. For example, suppose x and y are scalars and A is a Matrix, then + while a human programmer might write "(x^2*y)*A^3", we generate: + + >>> octave_code(x**2*y*A**3) + '(x.^2.*y)*A^3' + + Matrices are supported using Octave inline notation. When using + ``assign_to`` with matrices, the name can be specified either as a string + or as a ``MatrixSymbol``. The dimensions must align in the latter case. + + >>> from sympy import Matrix, MatrixSymbol + >>> mat = Matrix([[x**2, sin(x), ceiling(x)]]) + >>> octave_code(mat, assign_to='A') + 'A = [x.^2 sin(x) ceil(x)];' + + ``Piecewise`` expressions are implemented with logical masking by default. + Alternatively, you can pass "inline=False" to use if-else conditionals. + Note that if the ``Piecewise`` lacks a default term, represented by + ``(expr, True)`` then an error will be thrown. This is to prevent + generating an expression that may not evaluate to anything. + + >>> from sympy import Piecewise + >>> pw = Piecewise((x + 1, x > 0), (x, True)) + >>> octave_code(pw, assign_to=tau) + 'tau = ((x > 0).*(x + 1) + (~(x > 0)).*(x));' + + Note that any expression that can be generated normally can also exist + inside a Matrix: + + >>> mat = Matrix([[x**2, pw, sin(x)]]) + >>> octave_code(mat, assign_to='A') + 'A = [x.^2 ((x > 0).*(x + 1) + (~(x > 0)).*(x)) sin(x)];' + + Custom printing can be defined for certain types by passing a dictionary of + "type" : "function" to the ``user_functions`` kwarg. Alternatively, the + dictionary value can be a list of tuples i.e., [(argument_test, + cfunction_string)]. This can be used to call a custom Octave function. + + >>> from sympy import Function + >>> f = Function('f') + >>> g = Function('g') + >>> custom_functions = { + ... "f": "existing_octave_fcn", + ... "g": [(lambda x: x.is_Matrix, "my_mat_fcn"), + ... (lambda x: not x.is_Matrix, "my_fcn")] + ... } + >>> mat = Matrix([[1, x]]) + >>> octave_code(f(x) + g(x) + g(mat), user_functions=custom_functions) + 'existing_octave_fcn(x) + my_fcn(x) + my_mat_fcn([1 x])' + + Support for loops is provided through ``Indexed`` types. With + ``contract=True`` these expressions will be turned into loops, whereas + ``contract=False`` will just print the assignment expression that should be + looped over: + + >>> from sympy import Eq, IndexedBase, Idx + >>> len_y = 5 + >>> y = IndexedBase('y', shape=(len_y,)) + >>> t = IndexedBase('t', shape=(len_y,)) + >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) + >>> i = Idx('i', len_y-1) + >>> e = Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) + >>> octave_code(e.rhs, assign_to=e.lhs, contract=False) + 'Dy(i) = (y(i + 1) - y(i))./(t(i + 1) - t(i));' + """ + return OctaveCodePrinter(settings).doprint(expr, assign_to) + + +def print_octave_code(expr, **settings): + """Prints the Octave (or Matlab) representation of the given expression. + + See `octave_code` for the meaning of the optional arguments. + """ + print(octave_code(expr, **settings)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/precedence.py b/venv/lib/python3.10/site-packages/sympy/printing/precedence.py new file mode 100644 index 0000000000000000000000000000000000000000..d22d5746aeee51bddcf273d4575c30c3c27db71a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/precedence.py @@ -0,0 +1,180 @@ +"""A module providing information about the necessity of brackets""" + + +# Default precedence values for some basic types +PRECEDENCE = { + "Lambda": 1, + "Xor": 10, + "Or": 20, + "And": 30, + "Relational": 35, + "Add": 40, + "Mul": 50, + "Pow": 60, + "Func": 70, + "Not": 100, + "Atom": 1000, + "BitwiseOr": 36, + "BitwiseXor": 37, + "BitwiseAnd": 38 +} + +# A dictionary assigning precedence values to certain classes. These values are +# treated like they were inherited, so not every single class has to be named +# here. +# Do not use this with printers other than StrPrinter +PRECEDENCE_VALUES = { + "Equivalent": PRECEDENCE["Xor"], + "Xor": PRECEDENCE["Xor"], + "Implies": PRECEDENCE["Xor"], + "Or": PRECEDENCE["Or"], + "And": PRECEDENCE["And"], + "Add": PRECEDENCE["Add"], + "Pow": PRECEDENCE["Pow"], + "Relational": PRECEDENCE["Relational"], + "Sub": PRECEDENCE["Add"], + "Not": PRECEDENCE["Not"], + "Function" : PRECEDENCE["Func"], + "NegativeInfinity": PRECEDENCE["Add"], + "MatAdd": PRECEDENCE["Add"], + "MatPow": PRECEDENCE["Pow"], + "MatrixSolve": PRECEDENCE["Mul"], + "Mod": PRECEDENCE["Mul"], + "TensAdd": PRECEDENCE["Add"], + # As soon as `TensMul` is a subclass of `Mul`, remove this: + "TensMul": PRECEDENCE["Mul"], + "HadamardProduct": PRECEDENCE["Mul"], + "HadamardPower": PRECEDENCE["Pow"], + "KroneckerProduct": PRECEDENCE["Mul"], + "Equality": PRECEDENCE["Mul"], + "Unequality": PRECEDENCE["Mul"], +} + +# Sometimes it's not enough to assign a fixed precedence value to a +# class. Then a function can be inserted in this dictionary that takes +# an instance of this class as argument and returns the appropriate +# precedence value. + +# Precedence functions + + +def precedence_Mul(item): + from sympy.core.function import Function + if any(hasattr(arg, 'precedence') and isinstance(arg, Function) and + arg.precedence < PRECEDENCE["Mul"] for arg in item.args): + return PRECEDENCE["Mul"] + + if item.could_extract_minus_sign(): + return PRECEDENCE["Add"] + return PRECEDENCE["Mul"] + + +def precedence_Rational(item): + if item.p < 0: + return PRECEDENCE["Add"] + return PRECEDENCE["Mul"] + + +def precedence_Integer(item): + if item.p < 0: + return PRECEDENCE["Add"] + return PRECEDENCE["Atom"] + + +def precedence_Float(item): + if item < 0: + return PRECEDENCE["Add"] + return PRECEDENCE["Atom"] + + +def precedence_PolyElement(item): + if item.is_generator: + return PRECEDENCE["Atom"] + elif item.is_ground: + return precedence(item.coeff(1)) + elif item.is_term: + return PRECEDENCE["Mul"] + else: + return PRECEDENCE["Add"] + + +def precedence_FracElement(item): + if item.denom == 1: + return precedence_PolyElement(item.numer) + else: + return PRECEDENCE["Mul"] + + +def precedence_UnevaluatedExpr(item): + return precedence(item.args[0]) - 0.5 + + +PRECEDENCE_FUNCTIONS = { + "Integer": precedence_Integer, + "Mul": precedence_Mul, + "Rational": precedence_Rational, + "Float": precedence_Float, + "PolyElement": precedence_PolyElement, + "FracElement": precedence_FracElement, + "UnevaluatedExpr": precedence_UnevaluatedExpr, +} + + +def precedence(item): + """Returns the precedence of a given object. + + This is the precedence for StrPrinter. + """ + if hasattr(item, "precedence"): + return item.precedence + if not isinstance(item, type): + for i in type(item).mro(): + n = i.__name__ + if n in PRECEDENCE_FUNCTIONS: + return PRECEDENCE_FUNCTIONS[n](item) + elif n in PRECEDENCE_VALUES: + return PRECEDENCE_VALUES[n] + return PRECEDENCE["Atom"] + + +PRECEDENCE_TRADITIONAL = PRECEDENCE.copy() +PRECEDENCE_TRADITIONAL['Integral'] = PRECEDENCE["Mul"] +PRECEDENCE_TRADITIONAL['Sum'] = PRECEDENCE["Mul"] +PRECEDENCE_TRADITIONAL['Product'] = PRECEDENCE["Mul"] +PRECEDENCE_TRADITIONAL['Limit'] = PRECEDENCE["Mul"] +PRECEDENCE_TRADITIONAL['Derivative'] = PRECEDENCE["Mul"] +PRECEDENCE_TRADITIONAL['TensorProduct'] = PRECEDENCE["Mul"] +PRECEDENCE_TRADITIONAL['Transpose'] = PRECEDENCE["Pow"] +PRECEDENCE_TRADITIONAL['Adjoint'] = PRECEDENCE["Pow"] +PRECEDENCE_TRADITIONAL['Dot'] = PRECEDENCE["Mul"] - 1 +PRECEDENCE_TRADITIONAL['Cross'] = PRECEDENCE["Mul"] - 1 +PRECEDENCE_TRADITIONAL['Gradient'] = PRECEDENCE["Mul"] - 1 +PRECEDENCE_TRADITIONAL['Divergence'] = PRECEDENCE["Mul"] - 1 +PRECEDENCE_TRADITIONAL['Curl'] = PRECEDENCE["Mul"] - 1 +PRECEDENCE_TRADITIONAL['Laplacian'] = PRECEDENCE["Mul"] - 1 +PRECEDENCE_TRADITIONAL['Union'] = PRECEDENCE['Xor'] +PRECEDENCE_TRADITIONAL['Intersection'] = PRECEDENCE['Xor'] +PRECEDENCE_TRADITIONAL['Complement'] = PRECEDENCE['Xor'] +PRECEDENCE_TRADITIONAL['SymmetricDifference'] = PRECEDENCE['Xor'] +PRECEDENCE_TRADITIONAL['ProductSet'] = PRECEDENCE['Xor'] +PRECEDENCE_TRADITIONAL['DotProduct'] = PRECEDENCE_TRADITIONAL['Dot'] + + +def precedence_traditional(item): + """Returns the precedence of a given object according to the + traditional rules of mathematics. + + This is the precedence for the LaTeX and pretty printer. + """ + # Integral, Sum, Product, Limit have the precedence of Mul in LaTeX, + # the precedence of Atom for other printers: + from sympy.core.expr import UnevaluatedExpr + + if isinstance(item, UnevaluatedExpr): + return precedence_traditional(item.args[0]) + + n = item.__class__.__name__ + if n in PRECEDENCE_TRADITIONAL: + return PRECEDENCE_TRADITIONAL[n] + + return precedence(item) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/__init__.py b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cbabc649152a3c353a37225d342064634fbb5805 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__init__.py @@ -0,0 +1,12 @@ +"""ASCII-ART 2D pretty-printer""" + +from .pretty import (pretty, pretty_print, pprint, pprint_use_unicode, + pprint_try_use_unicode, pager_print) + +# if unicode output is available -- let's use it +pprint_try_use_unicode() + +__all__ = [ + 'pretty', 'pretty_print', 'pprint', 'pprint_use_unicode', + 'pprint_try_use_unicode', 'pager_print', +] diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1c79a7d255c2b4d5f3ea256641f0a75e8b159ae Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/pretty.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/pretty.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb04be62ed9f0f46522d432433f332daebfc20b6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/pretty.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/pretty_symbology.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/pretty_symbology.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d461b9ad0ebb22e5e4e3ae0afc60f2c36cd985bf Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/pretty_symbology.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/stringpict.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/stringpict.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdbd3d71f9089b7fcfbb0bc112af33eff4c6bc84 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/pretty/__pycache__/stringpict.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/pretty.py b/venv/lib/python3.10/site-packages/sympy/printing/pretty/pretty.py new file mode 100644 index 0000000000000000000000000000000000000000..b945f009119b24fc95e8452d91359957baba26a8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/pretty/pretty.py @@ -0,0 +1,2937 @@ +import itertools + +from sympy.core import S +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.function import Function +from sympy.core.mul import Mul +from sympy.core.numbers import Number, Rational +from sympy.core.power import Pow +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Symbol +from sympy.core.sympify import SympifyError +from sympy.printing.conventions import requires_partial +from sympy.printing.precedence import PRECEDENCE, precedence, precedence_traditional +from sympy.printing.printer import Printer, print_function +from sympy.printing.str import sstr +from sympy.utilities.iterables import has_variety +from sympy.utilities.exceptions import sympy_deprecation_warning + +from sympy.printing.pretty.stringpict import prettyForm, stringPict +from sympy.printing.pretty.pretty_symbology import hobj, vobj, xobj, \ + xsym, pretty_symbol, pretty_atom, pretty_use_unicode, greek_unicode, U, \ + pretty_try_use_unicode, annotated, is_subscriptable_in_unicode, center_pad, root as nth_root + +# rename for usage from outside +pprint_use_unicode = pretty_use_unicode +pprint_try_use_unicode = pretty_try_use_unicode + + +class PrettyPrinter(Printer): + """Printer, which converts an expression into 2D ASCII-art figure.""" + printmethod = "_pretty" + + _default_settings = { + "order": None, + "full_prec": "auto", + "use_unicode": None, + "wrap_line": True, + "num_columns": None, + "use_unicode_sqrt_char": True, + "root_notation": True, + "mat_symbol_style": "plain", + "imaginary_unit": "i", + "perm_cyclic": True + } + + def __init__(self, settings=None): + Printer.__init__(self, settings) + + if not isinstance(self._settings['imaginary_unit'], str): + raise TypeError("'imaginary_unit' must a string, not {}".format(self._settings['imaginary_unit'])) + elif self._settings['imaginary_unit'] not in ("i", "j"): + raise ValueError("'imaginary_unit' must be either 'i' or 'j', not '{}'".format(self._settings['imaginary_unit'])) + + def emptyPrinter(self, expr): + return prettyForm(str(expr)) + + @property + def _use_unicode(self): + if self._settings['use_unicode']: + return True + else: + return pretty_use_unicode() + + def doprint(self, expr): + return self._print(expr).render(**self._settings) + + # empty op so _print(stringPict) returns the same + def _print_stringPict(self, e): + return e + + def _print_basestring(self, e): + return prettyForm(e) + + def _print_atan2(self, e): + pform = prettyForm(*self._print_seq(e.args).parens()) + pform = prettyForm(*pform.left('atan2')) + return pform + + def _print_Symbol(self, e, bold_name=False): + symb = pretty_symbol(e.name, bold_name) + return prettyForm(symb) + _print_RandomSymbol = _print_Symbol + def _print_MatrixSymbol(self, e): + return self._print_Symbol(e, self._settings['mat_symbol_style'] == "bold") + + def _print_Float(self, e): + # we will use StrPrinter's Float printer, but we need to handle the + # full_prec ourselves, according to the self._print_level + full_prec = self._settings["full_prec"] + if full_prec == "auto": + full_prec = self._print_level == 1 + return prettyForm(sstr(e, full_prec=full_prec)) + + def _print_Cross(self, e): + vec1 = e._expr1 + vec2 = e._expr2 + pform = self._print(vec2) + pform = prettyForm(*pform.left('(')) + pform = prettyForm(*pform.right(')')) + pform = prettyForm(*pform.left(self._print(U('MULTIPLICATION SIGN')))) + pform = prettyForm(*pform.left(')')) + pform = prettyForm(*pform.left(self._print(vec1))) + pform = prettyForm(*pform.left('(')) + return pform + + def _print_Curl(self, e): + vec = e._expr + pform = self._print(vec) + pform = prettyForm(*pform.left('(')) + pform = prettyForm(*pform.right(')')) + pform = prettyForm(*pform.left(self._print(U('MULTIPLICATION SIGN')))) + pform = prettyForm(*pform.left(self._print(U('NABLA')))) + return pform + + def _print_Divergence(self, e): + vec = e._expr + pform = self._print(vec) + pform = prettyForm(*pform.left('(')) + pform = prettyForm(*pform.right(')')) + pform = prettyForm(*pform.left(self._print(U('DOT OPERATOR')))) + pform = prettyForm(*pform.left(self._print(U('NABLA')))) + return pform + + def _print_Dot(self, e): + vec1 = e._expr1 + vec2 = e._expr2 + pform = self._print(vec2) + pform = prettyForm(*pform.left('(')) + pform = prettyForm(*pform.right(')')) + pform = prettyForm(*pform.left(self._print(U('DOT OPERATOR')))) + pform = prettyForm(*pform.left(')')) + pform = prettyForm(*pform.left(self._print(vec1))) + pform = prettyForm(*pform.left('(')) + return pform + + def _print_Gradient(self, e): + func = e._expr + pform = self._print(func) + pform = prettyForm(*pform.left('(')) + pform = prettyForm(*pform.right(')')) + pform = prettyForm(*pform.left(self._print(U('NABLA')))) + return pform + + def _print_Laplacian(self, e): + func = e._expr + pform = self._print(func) + pform = prettyForm(*pform.left('(')) + pform = prettyForm(*pform.right(')')) + pform = prettyForm(*pform.left(self._print(U('INCREMENT')))) + return pform + + def _print_Atom(self, e): + try: + # print atoms like Exp1 or Pi + return prettyForm(pretty_atom(e.__class__.__name__, printer=self)) + except KeyError: + return self.emptyPrinter(e) + + # Infinity inherits from Number, so we have to override _print_XXX order + _print_Infinity = _print_Atom + _print_NegativeInfinity = _print_Atom + _print_EmptySet = _print_Atom + _print_Naturals = _print_Atom + _print_Naturals0 = _print_Atom + _print_Integers = _print_Atom + _print_Rationals = _print_Atom + _print_Complexes = _print_Atom + + _print_EmptySequence = _print_Atom + + def _print_Reals(self, e): + if self._use_unicode: + return self._print_Atom(e) + else: + inf_list = ['-oo', 'oo'] + return self._print_seq(inf_list, '(', ')') + + def _print_subfactorial(self, e): + x = e.args[0] + pform = self._print(x) + # Add parentheses if needed + if not ((x.is_Integer and x.is_nonnegative) or x.is_Symbol): + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.left('!')) + return pform + + def _print_factorial(self, e): + x = e.args[0] + pform = self._print(x) + # Add parentheses if needed + if not ((x.is_Integer and x.is_nonnegative) or x.is_Symbol): + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.right('!')) + return pform + + def _print_factorial2(self, e): + x = e.args[0] + pform = self._print(x) + # Add parentheses if needed + if not ((x.is_Integer and x.is_nonnegative) or x.is_Symbol): + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.right('!!')) + return pform + + def _print_binomial(self, e): + n, k = e.args + + n_pform = self._print(n) + k_pform = self._print(k) + + bar = ' '*max(n_pform.width(), k_pform.width()) + + pform = prettyForm(*k_pform.above(bar)) + pform = prettyForm(*pform.above(n_pform)) + pform = prettyForm(*pform.parens('(', ')')) + + pform.baseline = (pform.baseline + 1)//2 + + return pform + + def _print_Relational(self, e): + op = prettyForm(' ' + xsym(e.rel_op) + ' ') + + l = self._print(e.lhs) + r = self._print(e.rhs) + pform = prettyForm(*stringPict.next(l, op, r), binding=prettyForm.OPEN) + return pform + + def _print_Not(self, e): + from sympy.logic.boolalg import (Equivalent, Implies) + if self._use_unicode: + arg = e.args[0] + pform = self._print(arg) + if isinstance(arg, Equivalent): + return self._print_Equivalent(arg, altchar=pretty_atom('NotEquiv')) + if isinstance(arg, Implies): + return self._print_Implies(arg, altchar=pretty_atom('NotArrow')) + + if arg.is_Boolean and not arg.is_Not: + pform = prettyForm(*pform.parens()) + + return prettyForm(*pform.left(pretty_atom('Not'))) + else: + return self._print_Function(e) + + def __print_Boolean(self, e, char, sort=True): + args = e.args + if sort: + args = sorted(e.args, key=default_sort_key) + arg = args[0] + pform = self._print(arg) + + if arg.is_Boolean and not arg.is_Not: + pform = prettyForm(*pform.parens()) + + for arg in args[1:]: + pform_arg = self._print(arg) + + if arg.is_Boolean and not arg.is_Not: + pform_arg = prettyForm(*pform_arg.parens()) + + pform = prettyForm(*pform.right(' %s ' % char)) + pform = prettyForm(*pform.right(pform_arg)) + + return pform + + def _print_And(self, e): + if self._use_unicode: + return self.__print_Boolean(e, pretty_atom('And')) + else: + return self._print_Function(e, sort=True) + + def _print_Or(self, e): + if self._use_unicode: + return self.__print_Boolean(e, pretty_atom('Or')) + else: + return self._print_Function(e, sort=True) + + def _print_Xor(self, e): + if self._use_unicode: + return self.__print_Boolean(e, pretty_atom("Xor")) + else: + return self._print_Function(e, sort=True) + + def _print_Nand(self, e): + if self._use_unicode: + return self.__print_Boolean(e, pretty_atom('Nand')) + else: + return self._print_Function(e, sort=True) + + def _print_Nor(self, e): + if self._use_unicode: + return self.__print_Boolean(e, pretty_atom('Nor')) + else: + return self._print_Function(e, sort=True) + + def _print_Implies(self, e, altchar=None): + if self._use_unicode: + return self.__print_Boolean(e, altchar or pretty_atom('Arrow'), sort=False) + else: + return self._print_Function(e) + + def _print_Equivalent(self, e, altchar=None): + if self._use_unicode: + return self.__print_Boolean(e, altchar or pretty_atom('Equiv')) + else: + return self._print_Function(e, sort=True) + + def _print_conjugate(self, e): + pform = self._print(e.args[0]) + return prettyForm( *pform.above( hobj('_', pform.width())) ) + + def _print_Abs(self, e): + pform = self._print(e.args[0]) + pform = prettyForm(*pform.parens('|', '|')) + return pform + + def _print_floor(self, e): + if self._use_unicode: + pform = self._print(e.args[0]) + pform = prettyForm(*pform.parens('lfloor', 'rfloor')) + return pform + else: + return self._print_Function(e) + + def _print_ceiling(self, e): + if self._use_unicode: + pform = self._print(e.args[0]) + pform = prettyForm(*pform.parens('lceil', 'rceil')) + return pform + else: + return self._print_Function(e) + + def _print_Derivative(self, deriv): + if requires_partial(deriv.expr) and self._use_unicode: + deriv_symbol = U('PARTIAL DIFFERENTIAL') + else: + deriv_symbol = r'd' + x = None + count_total_deriv = 0 + + for sym, num in reversed(deriv.variable_count): + s = self._print(sym) + ds = prettyForm(*s.left(deriv_symbol)) + count_total_deriv += num + + if (not num.is_Integer) or (num > 1): + ds = ds**prettyForm(str(num)) + + if x is None: + x = ds + else: + x = prettyForm(*x.right(' ')) + x = prettyForm(*x.right(ds)) + + f = prettyForm( + binding=prettyForm.FUNC, *self._print(deriv.expr).parens()) + + pform = prettyForm(deriv_symbol) + + if (count_total_deriv > 1) != False: + pform = pform**prettyForm(str(count_total_deriv)) + + pform = prettyForm(*pform.below(stringPict.LINE, x)) + pform.baseline = pform.baseline + 1 + pform = prettyForm(*stringPict.next(pform, f)) + pform.binding = prettyForm.MUL + + return pform + + def _print_Cycle(self, dc): + from sympy.combinatorics.permutations import Permutation, Cycle + # for Empty Cycle + if dc == Cycle(): + cyc = stringPict('') + return prettyForm(*cyc.parens()) + + dc_list = Permutation(dc.list()).cyclic_form + # for Identity Cycle + if dc_list == []: + cyc = self._print(dc.size - 1) + return prettyForm(*cyc.parens()) + + cyc = stringPict('') + for i in dc_list: + l = self._print(str(tuple(i)).replace(',', '')) + cyc = prettyForm(*cyc.right(l)) + return cyc + + def _print_Permutation(self, expr): + from sympy.combinatorics.permutations import Permutation, Cycle + + perm_cyclic = Permutation.print_cyclic + if perm_cyclic is not None: + sympy_deprecation_warning( + f""" + Setting Permutation.print_cyclic is deprecated. Instead use + init_printing(perm_cyclic={perm_cyclic}). + """, + deprecated_since_version="1.6", + active_deprecations_target="deprecated-permutation-print_cyclic", + stacklevel=7, + ) + else: + perm_cyclic = self._settings.get("perm_cyclic", True) + + if perm_cyclic: + return self._print_Cycle(Cycle(expr)) + + lower = expr.array_form + upper = list(range(len(lower))) + + result = stringPict('') + first = True + for u, l in zip(upper, lower): + s1 = self._print(u) + s2 = self._print(l) + col = prettyForm(*s1.below(s2)) + if first: + first = False + else: + col = prettyForm(*col.left(" ")) + result = prettyForm(*result.right(col)) + return prettyForm(*result.parens()) + + + def _print_Integral(self, integral): + f = integral.function + + # Add parentheses if arg involves addition of terms and + # create a pretty form for the argument + prettyF = self._print(f) + # XXX generalize parens + if f.is_Add: + prettyF = prettyForm(*prettyF.parens()) + + # dx dy dz ... + arg = prettyF + for x in integral.limits: + prettyArg = self._print(x[0]) + # XXX qparens (parens if needs-parens) + if prettyArg.width() > 1: + prettyArg = prettyForm(*prettyArg.parens()) + + arg = prettyForm(*arg.right(' d', prettyArg)) + + # \int \int \int ... + firstterm = True + s = None + for lim in integral.limits: + # Create bar based on the height of the argument + h = arg.height() + H = h + 2 + + # XXX hack! + ascii_mode = not self._use_unicode + if ascii_mode: + H += 2 + + vint = vobj('int', H) + + # Construct the pretty form with the integral sign and the argument + pform = prettyForm(vint) + pform.baseline = arg.baseline + ( + H - h)//2 # covering the whole argument + + if len(lim) > 1: + # Create pretty forms for endpoints, if definite integral. + # Do not print empty endpoints. + if len(lim) == 2: + prettyA = prettyForm("") + prettyB = self._print(lim[1]) + if len(lim) == 3: + prettyA = self._print(lim[1]) + prettyB = self._print(lim[2]) + + if ascii_mode: # XXX hack + # Add spacing so that endpoint can more easily be + # identified with the correct integral sign + spc = max(1, 3 - prettyB.width()) + prettyB = prettyForm(*prettyB.left(' ' * spc)) + + spc = max(1, 4 - prettyA.width()) + prettyA = prettyForm(*prettyA.right(' ' * spc)) + + pform = prettyForm(*pform.above(prettyB)) + pform = prettyForm(*pform.below(prettyA)) + + if not ascii_mode: # XXX hack + pform = prettyForm(*pform.right(' ')) + + if firstterm: + s = pform # first term + firstterm = False + else: + s = prettyForm(*s.left(pform)) + + pform = prettyForm(*arg.left(s)) + pform.binding = prettyForm.MUL + return pform + + def _print_Product(self, expr): + func = expr.term + pretty_func = self._print(func) + + horizontal_chr = xobj('_', 1) + corner_chr = xobj('_', 1) + vertical_chr = xobj('|', 1) + + if self._use_unicode: + # use unicode corners + horizontal_chr = xobj('-', 1) + corner_chr = xobj('UpTack', 1) + + func_height = pretty_func.height() + + first = True + max_upper = 0 + sign_height = 0 + + for lim in expr.limits: + pretty_lower, pretty_upper = self.__print_SumProduct_Limits(lim) + + width = (func_height + 2) * 5 // 3 - 2 + sign_lines = [horizontal_chr + corner_chr + (horizontal_chr * (width-2)) + corner_chr + horizontal_chr] + for _ in range(func_height + 1): + sign_lines.append(' ' + vertical_chr + (' ' * (width-2)) + vertical_chr + ' ') + + pretty_sign = stringPict('') + pretty_sign = prettyForm(*pretty_sign.stack(*sign_lines)) + + + max_upper = max(max_upper, pretty_upper.height()) + + if first: + sign_height = pretty_sign.height() + + pretty_sign = prettyForm(*pretty_sign.above(pretty_upper)) + pretty_sign = prettyForm(*pretty_sign.below(pretty_lower)) + + if first: + pretty_func.baseline = 0 + first = False + + height = pretty_sign.height() + padding = stringPict('') + padding = prettyForm(*padding.stack(*[' ']*(height - 1))) + pretty_sign = prettyForm(*pretty_sign.right(padding)) + + pretty_func = prettyForm(*pretty_sign.right(pretty_func)) + + pretty_func.baseline = max_upper + sign_height//2 + pretty_func.binding = prettyForm.MUL + return pretty_func + + def __print_SumProduct_Limits(self, lim): + def print_start(lhs, rhs): + op = prettyForm(' ' + xsym("==") + ' ') + l = self._print(lhs) + r = self._print(rhs) + pform = prettyForm(*stringPict.next(l, op, r)) + return pform + + prettyUpper = self._print(lim[2]) + prettyLower = print_start(lim[0], lim[1]) + return prettyLower, prettyUpper + + def _print_Sum(self, expr): + ascii_mode = not self._use_unicode + + def asum(hrequired, lower, upper, use_ascii): + def adjust(s, wid=None, how='<^>'): + if not wid or len(s) > wid: + return s + need = wid - len(s) + if how in ('<^>', "<") or how not in list('<^>'): + return s + ' '*need + half = need//2 + lead = ' '*half + if how == ">": + return " "*need + s + return lead + s + ' '*(need - len(lead)) + + h = max(hrequired, 2) + d = h//2 + w = d + 1 + more = hrequired % 2 + + lines = [] + if use_ascii: + lines.append("_"*(w) + ' ') + lines.append(r"\%s`" % (' '*(w - 1))) + for i in range(1, d): + lines.append('%s\\%s' % (' '*i, ' '*(w - i))) + if more: + lines.append('%s)%s' % (' '*(d), ' '*(w - d))) + for i in reversed(range(1, d)): + lines.append('%s/%s' % (' '*i, ' '*(w - i))) + lines.append("/" + "_"*(w - 1) + ',') + return d, h + more, lines, more + else: + w = w + more + d = d + more + vsum = vobj('sum', 4) + lines.append("_"*(w)) + for i in range(0, d): + lines.append('%s%s%s' % (' '*i, vsum[2], ' '*(w - i - 1))) + for i in reversed(range(0, d)): + lines.append('%s%s%s' % (' '*i, vsum[4], ' '*(w - i - 1))) + lines.append(vsum[8]*(w)) + return d, h + 2*more, lines, more + + f = expr.function + + prettyF = self._print(f) + + if f.is_Add: # add parens + prettyF = prettyForm(*prettyF.parens()) + + H = prettyF.height() + 2 + + # \sum \sum \sum ... + first = True + max_upper = 0 + sign_height = 0 + + for lim in expr.limits: + prettyLower, prettyUpper = self.__print_SumProduct_Limits(lim) + + max_upper = max(max_upper, prettyUpper.height()) + + # Create sum sign based on the height of the argument + d, h, slines, adjustment = asum( + H, prettyLower.width(), prettyUpper.width(), ascii_mode) + prettySign = stringPict('') + prettySign = prettyForm(*prettySign.stack(*slines)) + + if first: + sign_height = prettySign.height() + + prettySign = prettyForm(*prettySign.above(prettyUpper)) + prettySign = prettyForm(*prettySign.below(prettyLower)) + + if first: + # change F baseline so it centers on the sign + prettyF.baseline -= d - (prettyF.height()//2 - + prettyF.baseline) + first = False + + # put padding to the right + pad = stringPict('') + pad = prettyForm(*pad.stack(*[' ']*h)) + prettySign = prettyForm(*prettySign.right(pad)) + # put the present prettyF to the right + prettyF = prettyForm(*prettySign.right(prettyF)) + + # adjust baseline of ascii mode sigma with an odd height so that it is + # exactly through the center + ascii_adjustment = ascii_mode if not adjustment else 0 + prettyF.baseline = max_upper + sign_height//2 + ascii_adjustment + + prettyF.binding = prettyForm.MUL + return prettyF + + def _print_Limit(self, l): + e, z, z0, dir = l.args + + E = self._print(e) + if precedence(e) <= PRECEDENCE["Mul"]: + E = prettyForm(*E.parens('(', ')')) + Lim = prettyForm('lim') + + LimArg = self._print(z) + if self._use_unicode: + LimArg = prettyForm(*LimArg.right(f"{xobj('-', 1)}{pretty_atom('Arrow')}")) + else: + LimArg = prettyForm(*LimArg.right('->')) + LimArg = prettyForm(*LimArg.right(self._print(z0))) + + if str(dir) == '+-' or z0 in (S.Infinity, S.NegativeInfinity): + dir = "" + else: + if self._use_unicode: + dir = pretty_atom('SuperscriptPlus') if str(dir) == "+" else pretty_atom('SuperscriptMinus') + + LimArg = prettyForm(*LimArg.right(self._print(dir))) + + Lim = prettyForm(*Lim.below(LimArg)) + Lim = prettyForm(*Lim.right(E), binding=prettyForm.MUL) + + return Lim + + def _print_matrix_contents(self, e): + """ + This method factors out what is essentially grid printing. + """ + M = e # matrix + Ms = {} # i,j -> pretty(M[i,j]) + for i in range(M.rows): + for j in range(M.cols): + Ms[i, j] = self._print(M[i, j]) + + # h- and v- spacers + hsep = 2 + vsep = 1 + + # max width for columns + maxw = [-1] * M.cols + + for j in range(M.cols): + maxw[j] = max([Ms[i, j].width() for i in range(M.rows)] or [0]) + + # drawing result + D = None + + for i in range(M.rows): + + D_row = None + for j in range(M.cols): + s = Ms[i, j] + + # reshape s to maxw + # XXX this should be generalized, and go to stringPict.reshape ? + assert s.width() <= maxw[j] + + # hcenter it, +0.5 to the right 2 + # ( it's better to align formula starts for say 0 and r ) + # XXX this is not good in all cases -- maybe introduce vbaseline? + left, right = center_pad(s.width(), maxw[j]) + + s = prettyForm(*s.right(right)) + s = prettyForm(*s.left(left)) + + # we don't need vcenter cells -- this is automatically done in + # a pretty way because when their baselines are taking into + # account in .right() + + if D_row is None: + D_row = s # first box in a row + continue + + D_row = prettyForm(*D_row.right(' '*hsep)) # h-spacer + D_row = prettyForm(*D_row.right(s)) + + if D is None: + D = D_row # first row in a picture + continue + + # v-spacer + for _ in range(vsep): + D = prettyForm(*D.below(' ')) + + D = prettyForm(*D.below(D_row)) + + if D is None: + D = prettyForm('') # Empty Matrix + + return D + + def _print_MatrixBase(self, e, lparens='[', rparens=']'): + D = self._print_matrix_contents(e) + D.baseline = D.height()//2 + D = prettyForm(*D.parens(lparens, rparens)) + return D + + def _print_Determinant(self, e): + mat = e.arg + if mat.is_MatrixExpr: + from sympy.matrices.expressions.blockmatrix import BlockMatrix + if isinstance(mat, BlockMatrix): + return self._print_MatrixBase(mat.blocks, lparens='|', rparens='|') + D = self._print(mat) + D.baseline = D.height()//2 + return prettyForm(*D.parens('|', '|')) + else: + return self._print_MatrixBase(mat, lparens='|', rparens='|') + + def _print_TensorProduct(self, expr): + # This should somehow share the code with _print_WedgeProduct: + if self._use_unicode: + circled_times = "\u2297" + else: + circled_times = ".*" + return self._print_seq(expr.args, None, None, circled_times, + parenthesize=lambda x: precedence_traditional(x) <= PRECEDENCE["Mul"]) + + def _print_WedgeProduct(self, expr): + # This should somehow share the code with _print_TensorProduct: + if self._use_unicode: + wedge_symbol = "\u2227" + else: + wedge_symbol = '/\\' + return self._print_seq(expr.args, None, None, wedge_symbol, + parenthesize=lambda x: precedence_traditional(x) <= PRECEDENCE["Mul"]) + + def _print_Trace(self, e): + D = self._print(e.arg) + D = prettyForm(*D.parens('(',')')) + D.baseline = D.height()//2 + D = prettyForm(*D.left('\n'*(0) + 'tr')) + return D + + + def _print_MatrixElement(self, expr): + from sympy.matrices import MatrixSymbol + if (isinstance(expr.parent, MatrixSymbol) + and expr.i.is_number and expr.j.is_number): + return self._print( + Symbol(expr.parent.name + '_%d%d' % (expr.i, expr.j))) + else: + prettyFunc = self._print(expr.parent) + prettyFunc = prettyForm(*prettyFunc.parens()) + prettyIndices = self._print_seq((expr.i, expr.j), delimiter=', ' + ).parens(left='[', right=']')[0] + pform = prettyForm(binding=prettyForm.FUNC, + *stringPict.next(prettyFunc, prettyIndices)) + + # store pform parts so it can be reassembled e.g. when powered + pform.prettyFunc = prettyFunc + pform.prettyArgs = prettyIndices + + return pform + + + def _print_MatrixSlice(self, m): + # XXX works only for applied functions + from sympy.matrices import MatrixSymbol + prettyFunc = self._print(m.parent) + if not isinstance(m.parent, MatrixSymbol): + prettyFunc = prettyForm(*prettyFunc.parens()) + def ppslice(x, dim): + x = list(x) + if x[2] == 1: + del x[2] + if x[0] == 0: + x[0] = '' + if x[1] == dim: + x[1] = '' + return prettyForm(*self._print_seq(x, delimiter=':')) + prettyArgs = self._print_seq((ppslice(m.rowslice, m.parent.rows), + ppslice(m.colslice, m.parent.cols)), delimiter=', ').parens(left='[', right=']')[0] + + pform = prettyForm( + binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs)) + + # store pform parts so it can be reassembled e.g. when powered + pform.prettyFunc = prettyFunc + pform.prettyArgs = prettyArgs + + return pform + + def _print_Transpose(self, expr): + mat = expr.arg + pform = self._print(mat) + from sympy.matrices import MatrixSymbol, BlockMatrix + if (not isinstance(mat, MatrixSymbol) and + not isinstance(mat, BlockMatrix) and mat.is_MatrixExpr): + pform = prettyForm(*pform.parens()) + pform = pform**(prettyForm('T')) + return pform + + def _print_Adjoint(self, expr): + mat = expr.arg + pform = self._print(mat) + if self._use_unicode: + dag = prettyForm(pretty_atom('Dagger')) + else: + dag = prettyForm('+') + from sympy.matrices import MatrixSymbol, BlockMatrix + if (not isinstance(mat, MatrixSymbol) and + not isinstance(mat, BlockMatrix) and mat.is_MatrixExpr): + pform = prettyForm(*pform.parens()) + pform = pform**dag + return pform + + def _print_BlockMatrix(self, B): + if B.blocks.shape == (1, 1): + return self._print(B.blocks[0, 0]) + return self._print(B.blocks) + + def _print_MatAdd(self, expr): + s = None + for item in expr.args: + pform = self._print(item) + if s is None: + s = pform # First element + else: + coeff = item.as_coeff_mmul()[0] + if S(coeff).could_extract_minus_sign(): + s = prettyForm(*stringPict.next(s, ' ')) + pform = self._print(item) + else: + s = prettyForm(*stringPict.next(s, ' + ')) + s = prettyForm(*stringPict.next(s, pform)) + + return s + + def _print_MatMul(self, expr): + args = list(expr.args) + from sympy.matrices.expressions.hadamard import HadamardProduct + from sympy.matrices.expressions.kronecker import KroneckerProduct + from sympy.matrices.expressions.matadd import MatAdd + for i, a in enumerate(args): + if (isinstance(a, (Add, MatAdd, HadamardProduct, KroneckerProduct)) + and len(expr.args) > 1): + args[i] = prettyForm(*self._print(a).parens()) + else: + args[i] = self._print(a) + + return prettyForm.__mul__(*args) + + def _print_Identity(self, expr): + if self._use_unicode: + return prettyForm(pretty_atom('IdentityMatrix')) + else: + return prettyForm('I') + + def _print_ZeroMatrix(self, expr): + if self._use_unicode: + return prettyForm(pretty_atom('ZeroMatrix')) + else: + return prettyForm('0') + + def _print_OneMatrix(self, expr): + if self._use_unicode: + return prettyForm(pretty_atom("OneMatrix")) + else: + return prettyForm('1') + + def _print_DotProduct(self, expr): + args = list(expr.args) + + for i, a in enumerate(args): + args[i] = self._print(a) + return prettyForm.__mul__(*args) + + def _print_MatPow(self, expr): + pform = self._print(expr.base) + from sympy.matrices import MatrixSymbol + if not isinstance(expr.base, MatrixSymbol) and expr.base.is_MatrixExpr: + pform = prettyForm(*pform.parens()) + pform = pform**(self._print(expr.exp)) + return pform + + def _print_HadamardProduct(self, expr): + from sympy.matrices.expressions.hadamard import HadamardProduct + from sympy.matrices.expressions.matadd import MatAdd + from sympy.matrices.expressions.matmul import MatMul + if self._use_unicode: + delim = pretty_atom('Ring') + else: + delim = '.*' + return self._print_seq(expr.args, None, None, delim, + parenthesize=lambda x: isinstance(x, (MatAdd, MatMul, HadamardProduct))) + + def _print_HadamardPower(self, expr): + # from sympy import MatAdd, MatMul + if self._use_unicode: + circ = pretty_atom('Ring') + else: + circ = self._print('.') + pretty_base = self._print(expr.base) + pretty_exp = self._print(expr.exp) + if precedence(expr.exp) < PRECEDENCE["Mul"]: + pretty_exp = prettyForm(*pretty_exp.parens()) + pretty_circ_exp = prettyForm( + binding=prettyForm.LINE, + *stringPict.next(circ, pretty_exp) + ) + return pretty_base**pretty_circ_exp + + def _print_KroneckerProduct(self, expr): + from sympy.matrices.expressions.matadd import MatAdd + from sympy.matrices.expressions.matmul import MatMul + if self._use_unicode: + delim = f" {pretty_atom('TensorProduct')} " + else: + delim = ' x ' + return self._print_seq(expr.args, None, None, delim, + parenthesize=lambda x: isinstance(x, (MatAdd, MatMul))) + + def _print_FunctionMatrix(self, X): + D = self._print(X.lamda.expr) + D = prettyForm(*D.parens('[', ']')) + return D + + def _print_TransferFunction(self, expr): + if not expr.num == 1: + num, den = expr.num, expr.den + res = Mul(num, Pow(den, -1, evaluate=False), evaluate=False) + return self._print_Mul(res) + else: + return self._print(1)/self._print(expr.den) + + def _print_Series(self, expr): + args = list(expr.args) + for i, a in enumerate(expr.args): + args[i] = prettyForm(*self._print(a).parens()) + return prettyForm.__mul__(*args) + + def _print_MIMOSeries(self, expr): + from sympy.physics.control.lti import MIMOParallel + args = list(expr.args) + pretty_args = [] + for a in reversed(args): + if (isinstance(a, MIMOParallel) and len(expr.args) > 1): + expression = self._print(a) + expression.baseline = expression.height()//2 + pretty_args.append(prettyForm(*expression.parens())) + else: + expression = self._print(a) + expression.baseline = expression.height()//2 + pretty_args.append(expression) + return prettyForm.__mul__(*pretty_args) + + def _print_Parallel(self, expr): + s = None + for item in expr.args: + pform = self._print(item) + if s is None: + s = pform # First element + else: + s = prettyForm(*stringPict.next(s)) + s.baseline = s.height()//2 + s = prettyForm(*stringPict.next(s, ' + ')) + s = prettyForm(*stringPict.next(s, pform)) + return s + + def _print_MIMOParallel(self, expr): + from sympy.physics.control.lti import TransferFunctionMatrix + s = None + for item in expr.args: + pform = self._print(item) + if s is None: + s = pform # First element + else: + s = prettyForm(*stringPict.next(s)) + s.baseline = s.height()//2 + s = prettyForm(*stringPict.next(s, ' + ')) + if isinstance(item, TransferFunctionMatrix): + s.baseline = s.height() - 1 + s = prettyForm(*stringPict.next(s, pform)) + # s.baseline = s.height()//2 + return s + + def _print_Feedback(self, expr): + from sympy.physics.control import TransferFunction, Series + + num, tf = expr.sys1, TransferFunction(1, 1, expr.var) + num_arg_list = list(num.args) if isinstance(num, Series) else [num] + den_arg_list = list(expr.sys2.args) if \ + isinstance(expr.sys2, Series) else [expr.sys2] + + if isinstance(num, Series) and isinstance(expr.sys2, Series): + den = Series(*num_arg_list, *den_arg_list) + elif isinstance(num, Series) and isinstance(expr.sys2, TransferFunction): + if expr.sys2 == tf: + den = Series(*num_arg_list) + else: + den = Series(*num_arg_list, expr.sys2) + elif isinstance(num, TransferFunction) and isinstance(expr.sys2, Series): + if num == tf: + den = Series(*den_arg_list) + else: + den = Series(num, *den_arg_list) + else: + if num == tf: + den = Series(*den_arg_list) + elif expr.sys2 == tf: + den = Series(*num_arg_list) + else: + den = Series(*num_arg_list, *den_arg_list) + + denom = prettyForm(*stringPict.next(self._print(tf))) + denom.baseline = denom.height()//2 + denom = prettyForm(*stringPict.next(denom, ' + ')) if expr.sign == -1 \ + else prettyForm(*stringPict.next(denom, ' - ')) + denom = prettyForm(*stringPict.next(denom, self._print(den))) + + return self._print(num)/denom + + def _print_MIMOFeedback(self, expr): + from sympy.physics.control import MIMOSeries, TransferFunctionMatrix + + inv_mat = self._print(MIMOSeries(expr.sys2, expr.sys1)) + plant = self._print(expr.sys1) + _feedback = prettyForm(*stringPict.next(inv_mat)) + _feedback = prettyForm(*stringPict.right("I + ", _feedback)) if expr.sign == -1 \ + else prettyForm(*stringPict.right("I - ", _feedback)) + _feedback = prettyForm(*stringPict.parens(_feedback)) + _feedback.baseline = 0 + _feedback = prettyForm(*stringPict.right(_feedback, '-1 ')) + _feedback.baseline = _feedback.height()//2 + _feedback = prettyForm.__mul__(_feedback, prettyForm(" ")) + if isinstance(expr.sys1, TransferFunctionMatrix): + _feedback.baseline = _feedback.height() - 1 + _feedback = prettyForm(*stringPict.next(_feedback, plant)) + return _feedback + + def _print_TransferFunctionMatrix(self, expr): + mat = self._print(expr._expr_mat) + mat.baseline = mat.height() - 1 + subscript = greek_unicode['tau'] if self._use_unicode else r'{t}' + mat = prettyForm(*mat.right(subscript)) + return mat + + def _print_StateSpace(self, expr): + from sympy.matrices.expressions.blockmatrix import BlockMatrix + A = expr._A + B = expr._B + C = expr._C + D = expr._D + mat = BlockMatrix([[A, B], [C, D]]) + return self._print(mat.blocks) + + def _print_BasisDependent(self, expr): + from sympy.vector import Vector + + if not self._use_unicode: + raise NotImplementedError("ASCII pretty printing of BasisDependent is not implemented") + + if expr == expr.zero: + return prettyForm(expr.zero._pretty_form) + o1 = [] + vectstrs = [] + if isinstance(expr, Vector): + items = expr.separate().items() + else: + items = [(0, expr)] + for system, vect in items: + inneritems = list(vect.components.items()) + inneritems.sort(key = lambda x: x[0].__str__()) + for k, v in inneritems: + #if the coef of the basis vector is 1 + #we skip the 1 + if v == 1: + o1.append("" + + k._pretty_form) + #Same for -1 + elif v == -1: + o1.append("(-1) " + + k._pretty_form) + #For a general expr + else: + #We always wrap the measure numbers in + #parentheses + arg_str = self._print( + v).parens()[0] + + o1.append(arg_str + ' ' + k._pretty_form) + vectstrs.append(k._pretty_form) + + #outstr = u("").join(o1) + if o1[0].startswith(" + "): + o1[0] = o1[0][3:] + elif o1[0].startswith(" "): + o1[0] = o1[0][1:] + #Fixing the newlines + lengths = [] + strs = [''] + flag = [] + for i, partstr in enumerate(o1): + flag.append(0) + # XXX: What is this hack? + if '\n' in partstr: + tempstr = partstr + tempstr = tempstr.replace(vectstrs[i], '') + if xobj(')_ext', 1) in tempstr: # If scalar is a fraction + for paren in range(len(tempstr)): + flag[i] = 1 + if tempstr[paren] == xobj(')_ext', 1) and tempstr[paren + 1] == '\n': + # We want to place the vector string after all the right parentheses, because + # otherwise, the vector will be in the middle of the string + tempstr = tempstr[:paren] + xobj(')_ext', 1)\ + + ' ' + vectstrs[i] + tempstr[paren + 1:] + break + elif xobj(')_lower_hook', 1) in tempstr: + # We want to place the vector string after all the right parentheses, because + # otherwise, the vector will be in the middle of the string. For this reason, + # we insert the vector string at the rightmost index. + index = tempstr.rfind(xobj(')_lower_hook', 1)) + if index != -1: # then this character was found in this string + flag[i] = 1 + tempstr = tempstr[:index] + xobj(')_lower_hook', 1)\ + + ' ' + vectstrs[i] + tempstr[index + 1:] + o1[i] = tempstr + + o1 = [x.split('\n') for x in o1] + n_newlines = max(len(x) for x in o1) # Width of part in its pretty form + + if 1 in flag: # If there was a fractional scalar + for i, parts in enumerate(o1): + if len(parts) == 1: # If part has no newline + parts.insert(0, ' ' * (len(parts[0]))) + flag[i] = 1 + + for i, parts in enumerate(o1): + lengths.append(len(parts[flag[i]])) + for j in range(n_newlines): + if j+1 <= len(parts): + if j >= len(strs): + strs.append(' ' * (sum(lengths[:-1]) + + 3*(len(lengths)-1))) + if j == flag[i]: + strs[flag[i]] += parts[flag[i]] + ' + ' + else: + strs[j] += parts[j] + ' '*(lengths[-1] - + len(parts[j])+ + 3) + else: + if j >= len(strs): + strs.append(' ' * (sum(lengths[:-1]) + + 3*(len(lengths)-1))) + strs[j] += ' '*(lengths[-1]+3) + + return prettyForm('\n'.join([s[:-3] for s in strs])) + + def _print_NDimArray(self, expr): + from sympy.matrices.immutable import ImmutableMatrix + + if expr.rank() == 0: + return self._print(expr[()]) + + level_str = [[]] + [[] for i in range(expr.rank())] + shape_ranges = [list(range(i)) for i in expr.shape] + # leave eventual matrix elements unflattened + mat = lambda x: ImmutableMatrix(x, evaluate=False) + for outer_i in itertools.product(*shape_ranges): + level_str[-1].append(expr[outer_i]) + even = True + for back_outer_i in range(expr.rank()-1, -1, -1): + if len(level_str[back_outer_i+1]) < expr.shape[back_outer_i]: + break + if even: + level_str[back_outer_i].append(level_str[back_outer_i+1]) + else: + level_str[back_outer_i].append(mat( + level_str[back_outer_i+1])) + if len(level_str[back_outer_i + 1]) == 1: + level_str[back_outer_i][-1] = mat( + [[level_str[back_outer_i][-1]]]) + even = not even + level_str[back_outer_i+1] = [] + + out_expr = level_str[0][0] + if expr.rank() % 2 == 1: + out_expr = mat([out_expr]) + + return self._print(out_expr) + + def _printer_tensor_indices(self, name, indices, index_map={}): + center = stringPict(name) + top = stringPict(" "*center.width()) + bot = stringPict(" "*center.width()) + + last_valence = None + prev_map = None + + for index in indices: + indpic = self._print(index.args[0]) + if ((index in index_map) or prev_map) and last_valence == index.is_up: + if index.is_up: + top = prettyForm(*stringPict.next(top, ",")) + else: + bot = prettyForm(*stringPict.next(bot, ",")) + if index in index_map: + indpic = prettyForm(*stringPict.next(indpic, "=")) + indpic = prettyForm(*stringPict.next(indpic, self._print(index_map[index]))) + prev_map = True + else: + prev_map = False + if index.is_up: + top = stringPict(*top.right(indpic)) + center = stringPict(*center.right(" "*indpic.width())) + bot = stringPict(*bot.right(" "*indpic.width())) + else: + bot = stringPict(*bot.right(indpic)) + center = stringPict(*center.right(" "*indpic.width())) + top = stringPict(*top.right(" "*indpic.width())) + last_valence = index.is_up + + pict = prettyForm(*center.above(top)) + pict = prettyForm(*pict.below(bot)) + return pict + + def _print_Tensor(self, expr): + name = expr.args[0].name + indices = expr.get_indices() + return self._printer_tensor_indices(name, indices) + + def _print_TensorElement(self, expr): + name = expr.expr.args[0].name + indices = expr.expr.get_indices() + index_map = expr.index_map + return self._printer_tensor_indices(name, indices, index_map) + + def _print_TensMul(self, expr): + sign, args = expr._get_args_for_traditional_printer() + args = [ + prettyForm(*self._print(i).parens()) if + precedence_traditional(i) < PRECEDENCE["Mul"] else self._print(i) + for i in args + ] + pform = prettyForm.__mul__(*args) + if sign: + return prettyForm(*pform.left(sign)) + else: + return pform + + def _print_TensAdd(self, expr): + args = [ + prettyForm(*self._print(i).parens()) if + precedence_traditional(i) < PRECEDENCE["Mul"] else self._print(i) + for i in expr.args + ] + return prettyForm.__add__(*args) + + def _print_TensorIndex(self, expr): + sym = expr.args[0] + if not expr.is_up: + sym = -sym + return self._print(sym) + + def _print_PartialDerivative(self, deriv): + if self._use_unicode: + deriv_symbol = U('PARTIAL DIFFERENTIAL') + else: + deriv_symbol = r'd' + x = None + + for variable in reversed(deriv.variables): + s = self._print(variable) + ds = prettyForm(*s.left(deriv_symbol)) + + if x is None: + x = ds + else: + x = prettyForm(*x.right(' ')) + x = prettyForm(*x.right(ds)) + + f = prettyForm( + binding=prettyForm.FUNC, *self._print(deriv.expr).parens()) + + pform = prettyForm(deriv_symbol) + + if len(deriv.variables) > 1: + pform = pform**self._print(len(deriv.variables)) + + pform = prettyForm(*pform.below(stringPict.LINE, x)) + pform.baseline = pform.baseline + 1 + pform = prettyForm(*stringPict.next(pform, f)) + pform.binding = prettyForm.MUL + + return pform + + def _print_Piecewise(self, pexpr): + + P = {} + for n, ec in enumerate(pexpr.args): + P[n, 0] = self._print(ec.expr) + if ec.cond == True: + P[n, 1] = prettyForm('otherwise') + else: + P[n, 1] = prettyForm( + *prettyForm('for ').right(self._print(ec.cond))) + hsep = 2 + vsep = 1 + len_args = len(pexpr.args) + + # max widths + maxw = [max(P[i, j].width() for i in range(len_args)) + for j in range(2)] + + # FIXME: Refactor this code and matrix into some tabular environment. + # drawing result + D = None + + for i in range(len_args): + D_row = None + for j in range(2): + p = P[i, j] + assert p.width() <= maxw[j] + + wdelta = maxw[j] - p.width() + wleft = wdelta // 2 + wright = wdelta - wleft + + p = prettyForm(*p.right(' '*wright)) + p = prettyForm(*p.left(' '*wleft)) + + if D_row is None: + D_row = p + continue + + D_row = prettyForm(*D_row.right(' '*hsep)) # h-spacer + D_row = prettyForm(*D_row.right(p)) + if D is None: + D = D_row # first row in a picture + continue + + # v-spacer + for _ in range(vsep): + D = prettyForm(*D.below(' ')) + + D = prettyForm(*D.below(D_row)) + + D = prettyForm(*D.parens('{', '')) + D.baseline = D.height()//2 + D.binding = prettyForm.OPEN + return D + + def _print_ITE(self, ite): + from sympy.functions.elementary.piecewise import Piecewise + return self._print(ite.rewrite(Piecewise)) + + def _hprint_vec(self, v): + D = None + + for a in v: + p = a + if D is None: + D = p + else: + D = prettyForm(*D.right(', ')) + D = prettyForm(*D.right(p)) + if D is None: + D = stringPict(' ') + + return D + + def _hprint_vseparator(self, p1, p2, left=None, right=None, delimiter='', ifascii_nougly=False): + if ifascii_nougly and not self._use_unicode: + return self._print_seq((p1, '|', p2), left=left, right=right, + delimiter=delimiter, ifascii_nougly=True) + tmp = self._print_seq((p1, p2,), left=left, right=right, delimiter=delimiter) + sep = stringPict(vobj('|', tmp.height()), baseline=tmp.baseline) + return self._print_seq((p1, sep, p2), left=left, right=right, + delimiter=delimiter) + + def _print_hyper(self, e): + # FIXME refactor Matrix, Piecewise, and this into a tabular environment + ap = [self._print(a) for a in e.ap] + bq = [self._print(b) for b in e.bq] + + P = self._print(e.argument) + P.baseline = P.height()//2 + + # Drawing result - first create the ap, bq vectors + D = None + for v in [ap, bq]: + D_row = self._hprint_vec(v) + if D is None: + D = D_row # first row in a picture + else: + D = prettyForm(*D.below(' ')) + D = prettyForm(*D.below(D_row)) + + # make sure that the argument `z' is centred vertically + D.baseline = D.height()//2 + + # insert horizontal separator + P = prettyForm(*P.left(' ')) + D = prettyForm(*D.right(' ')) + + # insert separating `|` + D = self._hprint_vseparator(D, P) + + # add parens + D = prettyForm(*D.parens('(', ')')) + + # create the F symbol + above = D.height()//2 - 1 + below = D.height() - above - 1 + + sz, t, b, add, img = annotated('F') + F = prettyForm('\n' * (above - t) + img + '\n' * (below - b), + baseline=above + sz) + add = (sz + 1)//2 + + F = prettyForm(*F.left(self._print(len(e.ap)))) + F = prettyForm(*F.right(self._print(len(e.bq)))) + F.baseline = above + add + + D = prettyForm(*F.right(' ', D)) + + return D + + def _print_meijerg(self, e): + # FIXME refactor Matrix, Piecewise, and this into a tabular environment + + v = {} + v[(0, 0)] = [self._print(a) for a in e.an] + v[(0, 1)] = [self._print(a) for a in e.aother] + v[(1, 0)] = [self._print(b) for b in e.bm] + v[(1, 1)] = [self._print(b) for b in e.bother] + + P = self._print(e.argument) + P.baseline = P.height()//2 + + vp = {} + for idx in v: + vp[idx] = self._hprint_vec(v[idx]) + + for i in range(2): + maxw = max(vp[(0, i)].width(), vp[(1, i)].width()) + for j in range(2): + s = vp[(j, i)] + left = (maxw - s.width()) // 2 + right = maxw - left - s.width() + s = prettyForm(*s.left(' ' * left)) + s = prettyForm(*s.right(' ' * right)) + vp[(j, i)] = s + + D1 = prettyForm(*vp[(0, 0)].right(' ', vp[(0, 1)])) + D1 = prettyForm(*D1.below(' ')) + D2 = prettyForm(*vp[(1, 0)].right(' ', vp[(1, 1)])) + D = prettyForm(*D1.below(D2)) + + # make sure that the argument `z' is centred vertically + D.baseline = D.height()//2 + + # insert horizontal separator + P = prettyForm(*P.left(' ')) + D = prettyForm(*D.right(' ')) + + # insert separating `|` + D = self._hprint_vseparator(D, P) + + # add parens + D = prettyForm(*D.parens('(', ')')) + + # create the G symbol + above = D.height()//2 - 1 + below = D.height() - above - 1 + + sz, t, b, add, img = annotated('G') + F = prettyForm('\n' * (above - t) + img + '\n' * (below - b), + baseline=above + sz) + + pp = self._print(len(e.ap)) + pq = self._print(len(e.bq)) + pm = self._print(len(e.bm)) + pn = self._print(len(e.an)) + + def adjust(p1, p2): + diff = p1.width() - p2.width() + if diff == 0: + return p1, p2 + elif diff > 0: + return p1, prettyForm(*p2.left(' '*diff)) + else: + return prettyForm(*p1.left(' '*-diff)), p2 + pp, pm = adjust(pp, pm) + pq, pn = adjust(pq, pn) + pu = prettyForm(*pm.right(', ', pn)) + pl = prettyForm(*pp.right(', ', pq)) + + ht = F.baseline - above - 2 + if ht > 0: + pu = prettyForm(*pu.below('\n'*ht)) + p = prettyForm(*pu.below(pl)) + + F.baseline = above + F = prettyForm(*F.right(p)) + + F.baseline = above + add + + D = prettyForm(*F.right(' ', D)) + + return D + + def _print_ExpBase(self, e): + # TODO should exp_polar be printed differently? + # what about exp_polar(0), exp_polar(1)? + base = prettyForm(pretty_atom('Exp1', 'e')) + return base ** self._print(e.args[0]) + + def _print_Exp1(self, e): + return prettyForm(pretty_atom('Exp1', 'e')) + + def _print_Function(self, e, sort=False, func_name=None, left='(', + right=')'): + # optional argument func_name for supplying custom names + # XXX works only for applied functions + return self._helper_print_function(e.func, e.args, sort=sort, func_name=func_name, left=left, right=right) + + def _print_mathieuc(self, e): + return self._print_Function(e, func_name='C') + + def _print_mathieus(self, e): + return self._print_Function(e, func_name='S') + + def _print_mathieucprime(self, e): + return self._print_Function(e, func_name="C'") + + def _print_mathieusprime(self, e): + return self._print_Function(e, func_name="S'") + + def _helper_print_function(self, func, args, sort=False, func_name=None, + delimiter=', ', elementwise=False, left='(', + right=')'): + if sort: + args = sorted(args, key=default_sort_key) + + if not func_name and hasattr(func, "__name__"): + func_name = func.__name__ + + if func_name: + prettyFunc = self._print(Symbol(func_name)) + else: + prettyFunc = prettyForm(*self._print(func).parens()) + + if elementwise: + if self._use_unicode: + circ = pretty_atom('Modifier Letter Low Ring') + else: + circ = '.' + circ = self._print(circ) + prettyFunc = prettyForm( + binding=prettyForm.LINE, + *stringPict.next(prettyFunc, circ) + ) + + prettyArgs = prettyForm(*self._print_seq(args, delimiter=delimiter).parens( + left=left, right=right)) + + pform = prettyForm( + binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs)) + + # store pform parts so it can be reassembled e.g. when powered + pform.prettyFunc = prettyFunc + pform.prettyArgs = prettyArgs + + return pform + + def _print_ElementwiseApplyFunction(self, e): + func = e.function + arg = e.expr + args = [arg] + return self._helper_print_function(func, args, delimiter="", elementwise=True) + + @property + def _special_function_classes(self): + from sympy.functions.special.tensor_functions import KroneckerDelta + from sympy.functions.special.gamma_functions import gamma, lowergamma + from sympy.functions.special.zeta_functions import lerchphi + from sympy.functions.special.beta_functions import beta + from sympy.functions.special.delta_functions import DiracDelta + from sympy.functions.special.error_functions import Chi + return {KroneckerDelta: [greek_unicode['delta'], 'delta'], + gamma: [greek_unicode['Gamma'], 'Gamma'], + lerchphi: [greek_unicode['Phi'], 'lerchphi'], + lowergamma: [greek_unicode['gamma'], 'gamma'], + beta: [greek_unicode['Beta'], 'B'], + DiracDelta: [greek_unicode['delta'], 'delta'], + Chi: ['Chi', 'Chi']} + + def _print_FunctionClass(self, expr): + for cls in self._special_function_classes: + if issubclass(expr, cls) and expr.__name__ == cls.__name__: + if self._use_unicode: + return prettyForm(self._special_function_classes[cls][0]) + else: + return prettyForm(self._special_function_classes[cls][1]) + func_name = expr.__name__ + return prettyForm(pretty_symbol(func_name)) + + def _print_GeometryEntity(self, expr): + # GeometryEntity is based on Tuple but should not print like a Tuple + return self.emptyPrinter(expr) + + def _print_polylog(self, e): + subscript = self._print(e.args[0]) + if self._use_unicode and is_subscriptable_in_unicode(subscript): + return self._print_Function(Function('Li_%s' % subscript)(e.args[1])) + return self._print_Function(e) + + def _print_lerchphi(self, e): + func_name = greek_unicode['Phi'] if self._use_unicode else 'lerchphi' + return self._print_Function(e, func_name=func_name) + + def _print_dirichlet_eta(self, e): + func_name = greek_unicode['eta'] if self._use_unicode else 'dirichlet_eta' + return self._print_Function(e, func_name=func_name) + + def _print_Heaviside(self, e): + func_name = greek_unicode['theta'] if self._use_unicode else 'Heaviside' + if e.args[1] is S.Half: + pform = prettyForm(*self._print(e.args[0]).parens()) + pform = prettyForm(*pform.left(func_name)) + return pform + else: + return self._print_Function(e, func_name=func_name) + + def _print_fresnels(self, e): + return self._print_Function(e, func_name="S") + + def _print_fresnelc(self, e): + return self._print_Function(e, func_name="C") + + def _print_airyai(self, e): + return self._print_Function(e, func_name="Ai") + + def _print_airybi(self, e): + return self._print_Function(e, func_name="Bi") + + def _print_airyaiprime(self, e): + return self._print_Function(e, func_name="Ai'") + + def _print_airybiprime(self, e): + return self._print_Function(e, func_name="Bi'") + + def _print_LambertW(self, e): + return self._print_Function(e, func_name="W") + + def _print_Covariance(self, e): + return self._print_Function(e, func_name="Cov") + + def _print_Variance(self, e): + return self._print_Function(e, func_name="Var") + + def _print_Probability(self, e): + return self._print_Function(e, func_name="P") + + def _print_Expectation(self, e): + return self._print_Function(e, func_name="E", left='[', right=']') + + def _print_Lambda(self, e): + expr = e.expr + sig = e.signature + if self._use_unicode: + arrow = f" {pretty_atom('ArrowFromBar')} " + else: + arrow = " -> " + if len(sig) == 1 and sig[0].is_symbol: + sig = sig[0] + var_form = self._print(sig) + + return prettyForm(*stringPict.next(var_form, arrow, self._print(expr)), binding=8) + + def _print_Order(self, expr): + pform = self._print(expr.expr) + if (expr.point and any(p != S.Zero for p in expr.point)) or \ + len(expr.variables) > 1: + pform = prettyForm(*pform.right("; ")) + if len(expr.variables) > 1: + pform = prettyForm(*pform.right(self._print(expr.variables))) + elif len(expr.variables): + pform = prettyForm(*pform.right(self._print(expr.variables[0]))) + if self._use_unicode: + pform = prettyForm(*pform.right(f" {pretty_atom('Arrow')} ")) + else: + pform = prettyForm(*pform.right(" -> ")) + if len(expr.point) > 1: + pform = prettyForm(*pform.right(self._print(expr.point))) + else: + pform = prettyForm(*pform.right(self._print(expr.point[0]))) + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.left("O")) + return pform + + def _print_SingularityFunction(self, e): + if self._use_unicode: + shift = self._print(e.args[0]-e.args[1]) + n = self._print(e.args[2]) + base = prettyForm("<") + base = prettyForm(*base.right(shift)) + base = prettyForm(*base.right(">")) + pform = base**n + return pform + else: + n = self._print(e.args[2]) + shift = self._print(e.args[0]-e.args[1]) + base = self._print_seq(shift, "<", ">", ' ') + return base**n + + def _print_beta(self, e): + func_name = greek_unicode['Beta'] if self._use_unicode else 'B' + return self._print_Function(e, func_name=func_name) + + def _print_betainc(self, e): + func_name = "B'" + return self._print_Function(e, func_name=func_name) + + def _print_betainc_regularized(self, e): + func_name = 'I' + return self._print_Function(e, func_name=func_name) + + def _print_gamma(self, e): + func_name = greek_unicode['Gamma'] if self._use_unicode else 'Gamma' + return self._print_Function(e, func_name=func_name) + + def _print_uppergamma(self, e): + func_name = greek_unicode['Gamma'] if self._use_unicode else 'Gamma' + return self._print_Function(e, func_name=func_name) + + def _print_lowergamma(self, e): + func_name = greek_unicode['gamma'] if self._use_unicode else 'lowergamma' + return self._print_Function(e, func_name=func_name) + + def _print_DiracDelta(self, e): + if self._use_unicode: + if len(e.args) == 2: + a = prettyForm(greek_unicode['delta']) + b = self._print(e.args[1]) + b = prettyForm(*b.parens()) + c = self._print(e.args[0]) + c = prettyForm(*c.parens()) + pform = a**b + pform = prettyForm(*pform.right(' ')) + pform = prettyForm(*pform.right(c)) + return pform + pform = self._print(e.args[0]) + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.left(greek_unicode['delta'])) + return pform + else: + return self._print_Function(e) + + def _print_expint(self, e): + subscript = self._print(e.args[0]) + if self._use_unicode and is_subscriptable_in_unicode(subscript): + return self._print_Function(Function('E_%s' % subscript)(e.args[1])) + return self._print_Function(e) + + def _print_Chi(self, e): + # This needs a special case since otherwise it comes out as greek + # letter chi... + prettyFunc = prettyForm("Chi") + prettyArgs = prettyForm(*self._print_seq(e.args).parens()) + + pform = prettyForm( + binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs)) + + # store pform parts so it can be reassembled e.g. when powered + pform.prettyFunc = prettyFunc + pform.prettyArgs = prettyArgs + + return pform + + def _print_elliptic_e(self, e): + pforma0 = self._print(e.args[0]) + if len(e.args) == 1: + pform = pforma0 + else: + pforma1 = self._print(e.args[1]) + pform = self._hprint_vseparator(pforma0, pforma1) + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.left('E')) + return pform + + def _print_elliptic_k(self, e): + pform = self._print(e.args[0]) + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.left('K')) + return pform + + def _print_elliptic_f(self, e): + pforma0 = self._print(e.args[0]) + pforma1 = self._print(e.args[1]) + pform = self._hprint_vseparator(pforma0, pforma1) + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.left('F')) + return pform + + def _print_elliptic_pi(self, e): + name = greek_unicode['Pi'] if self._use_unicode else 'Pi' + pforma0 = self._print(e.args[0]) + pforma1 = self._print(e.args[1]) + if len(e.args) == 2: + pform = self._hprint_vseparator(pforma0, pforma1) + else: + pforma2 = self._print(e.args[2]) + pforma = self._hprint_vseparator(pforma1, pforma2, ifascii_nougly=False) + pforma = prettyForm(*pforma.left('; ')) + pform = prettyForm(*pforma.left(pforma0)) + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.left(name)) + return pform + + def _print_GoldenRatio(self, expr): + if self._use_unicode: + return prettyForm(pretty_symbol('phi')) + return self._print(Symbol("GoldenRatio")) + + def _print_EulerGamma(self, expr): + if self._use_unicode: + return prettyForm(pretty_symbol('gamma')) + return self._print(Symbol("EulerGamma")) + + def _print_Catalan(self, expr): + return self._print(Symbol("G")) + + def _print_Mod(self, expr): + pform = self._print(expr.args[0]) + if pform.binding > prettyForm.MUL: + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.right(' mod ')) + pform = prettyForm(*pform.right(self._print(expr.args[1]))) + pform.binding = prettyForm.OPEN + return pform + + def _print_Add(self, expr, order=None): + terms = self._as_ordered_terms(expr, order=order) + pforms, indices = [], [] + + def pretty_negative(pform, index): + """Prepend a minus sign to a pretty form. """ + #TODO: Move this code to prettyForm + if index == 0: + if pform.height() > 1: + pform_neg = '- ' + else: + pform_neg = '-' + else: + pform_neg = ' - ' + + if (pform.binding > prettyForm.NEG + or pform.binding == prettyForm.ADD): + p = stringPict(*pform.parens()) + else: + p = pform + p = stringPict.next(pform_neg, p) + # Lower the binding to NEG, even if it was higher. Otherwise, it + # will print as a + ( - (b)), instead of a - (b). + return prettyForm(binding=prettyForm.NEG, *p) + + for i, term in enumerate(terms): + if term.is_Mul and term.could_extract_minus_sign(): + coeff, other = term.as_coeff_mul(rational=False) + if coeff == -1: + negterm = Mul(*other, evaluate=False) + else: + negterm = Mul(-coeff, *other, evaluate=False) + pform = self._print(negterm) + pforms.append(pretty_negative(pform, i)) + elif term.is_Rational and term.q > 1: + pforms.append(None) + indices.append(i) + elif term.is_Number and term < 0: + pform = self._print(-term) + pforms.append(pretty_negative(pform, i)) + elif term.is_Relational: + pforms.append(prettyForm(*self._print(term).parens())) + else: + pforms.append(self._print(term)) + + if indices: + large = True + + for pform in pforms: + if pform is not None and pform.height() > 1: + break + else: + large = False + + for i in indices: + term, negative = terms[i], False + + if term < 0: + term, negative = -term, True + + if large: + pform = prettyForm(str(term.p))/prettyForm(str(term.q)) + else: + pform = self._print(term) + + if negative: + pform = pretty_negative(pform, i) + + pforms[i] = pform + + return prettyForm.__add__(*pforms) + + def _print_Mul(self, product): + from sympy.physics.units import Quantity + + # Check for unevaluated Mul. In this case we need to make sure the + # identities are visible, multiple Rational factors are not combined + # etc so we display in a straight-forward form that fully preserves all + # args and their order. + args = product.args + if args[0] is S.One or any(isinstance(arg, Number) for arg in args[1:]): + strargs = list(map(self._print, args)) + # XXX: This is a hack to work around the fact that + # prettyForm.__mul__ absorbs a leading -1 in the args. Probably it + # would be better to fix this in prettyForm.__mul__ instead. + negone = strargs[0] == '-1' + if negone: + strargs[0] = prettyForm('1', 0, 0) + obj = prettyForm.__mul__(*strargs) + if negone: + obj = prettyForm('-' + obj.s, obj.baseline, obj.binding) + return obj + + a = [] # items in the numerator + b = [] # items that are in the denominator (if any) + + if self.order not in ('old', 'none'): + args = product.as_ordered_factors() + else: + args = list(product.args) + + # If quantities are present append them at the back + args = sorted(args, key=lambda x: isinstance(x, Quantity) or + (isinstance(x, Pow) and isinstance(x.base, Quantity))) + + # Gather terms for numerator/denominator + for item in args: + if item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative: + if item.exp != -1: + b.append(Pow(item.base, -item.exp, evaluate=False)) + else: + b.append(Pow(item.base, -item.exp)) + elif item.is_Rational and item is not S.Infinity: + if item.p != 1: + a.append( Rational(item.p) ) + if item.q != 1: + b.append( Rational(item.q) ) + else: + a.append(item) + + # Convert to pretty forms. Parentheses are added by `__mul__`. + a = [self._print(ai) for ai in a] + b = [self._print(bi) for bi in b] + + # Construct a pretty form + if len(b) == 0: + return prettyForm.__mul__(*a) + else: + if len(a) == 0: + a.append( self._print(S.One) ) + return prettyForm.__mul__(*a)/prettyForm.__mul__(*b) + + # A helper function for _print_Pow to print x**(1/n) + def _print_nth_root(self, base, root): + bpretty = self._print(base) + + # In very simple cases, use a single-char root sign + if (self._settings['use_unicode_sqrt_char'] and self._use_unicode + and root == 2 and bpretty.height() == 1 + and (bpretty.width() == 1 + or (base.is_Integer and base.is_nonnegative))): + return prettyForm(*bpretty.left(nth_root[2])) + + # Construct root sign, start with the \/ shape + _zZ = xobj('/', 1) + rootsign = xobj('\\', 1) + _zZ + # Constructing the number to put on root + rpretty = self._print(root) + # roots look bad if they are not a single line + if rpretty.height() != 1: + return self._print(base)**self._print(1/root) + # If power is half, no number should appear on top of root sign + exp = '' if root == 2 else str(rpretty).ljust(2) + if len(exp) > 2: + rootsign = ' '*(len(exp) - 2) + rootsign + # Stack the exponent + rootsign = stringPict(exp + '\n' + rootsign) + rootsign.baseline = 0 + # Diagonal: length is one less than height of base + linelength = bpretty.height() - 1 + diagonal = stringPict('\n'.join( + ' '*(linelength - i - 1) + _zZ + ' '*i + for i in range(linelength) + )) + # Put baseline just below lowest line: next to exp + diagonal.baseline = linelength - 1 + # Make the root symbol + rootsign = prettyForm(*rootsign.right(diagonal)) + # Det the baseline to match contents to fix the height + # but if the height of bpretty is one, the rootsign must be one higher + rootsign.baseline = max(1, bpretty.baseline) + #build result + s = prettyForm(hobj('_', 2 + bpretty.width())) + s = prettyForm(*bpretty.above(s)) + s = prettyForm(*s.left(rootsign)) + return s + + def _print_Pow(self, power): + from sympy.simplify.simplify import fraction + b, e = power.as_base_exp() + if power.is_commutative: + if e is S.NegativeOne: + return prettyForm("1")/self._print(b) + n, d = fraction(e) + if n is S.One and d.is_Atom and not e.is_Integer and (e.is_Rational or d.is_Symbol) \ + and self._settings['root_notation']: + return self._print_nth_root(b, d) + if e.is_Rational and e < 0: + return prettyForm("1")/self._print(Pow(b, -e, evaluate=False)) + + if b.is_Relational: + return prettyForm(*self._print(b).parens()).__pow__(self._print(e)) + + return self._print(b)**self._print(e) + + def _print_UnevaluatedExpr(self, expr): + return self._print(expr.args[0]) + + def __print_numer_denom(self, p, q): + if q == 1: + if p < 0: + return prettyForm(str(p), binding=prettyForm.NEG) + else: + return prettyForm(str(p)) + elif abs(p) >= 10 and abs(q) >= 10: + # If more than one digit in numer and denom, print larger fraction + if p < 0: + return prettyForm(str(p), binding=prettyForm.NEG)/prettyForm(str(q)) + # Old printing method: + #pform = prettyForm(str(-p))/prettyForm(str(q)) + #return prettyForm(binding=prettyForm.NEG, *pform.left('- ')) + else: + return prettyForm(str(p))/prettyForm(str(q)) + else: + return None + + def _print_Rational(self, expr): + result = self.__print_numer_denom(expr.p, expr.q) + + if result is not None: + return result + else: + return self.emptyPrinter(expr) + + def _print_Fraction(self, expr): + result = self.__print_numer_denom(expr.numerator, expr.denominator) + + if result is not None: + return result + else: + return self.emptyPrinter(expr) + + def _print_ProductSet(self, p): + if len(p.sets) >= 1 and not has_variety(p.sets): + return self._print(p.sets[0]) ** self._print(len(p.sets)) + else: + prod_char = pretty_atom('Multiplication') if self._use_unicode else 'x' + return self._print_seq(p.sets, None, None, ' %s ' % prod_char, + parenthesize=lambda set: set.is_Union or + set.is_Intersection or set.is_ProductSet) + + def _print_FiniteSet(self, s): + items = sorted(s.args, key=default_sort_key) + return self._print_seq(items, '{', '}', ', ' ) + + def _print_Range(self, s): + + if self._use_unicode: + dots = pretty_atom('Dots') + else: + dots = '...' + + if s.start.is_infinite and s.stop.is_infinite: + if s.step.is_positive: + printset = dots, -1, 0, 1, dots + else: + printset = dots, 1, 0, -1, dots + elif s.start.is_infinite: + printset = dots, s[-1] - s.step, s[-1] + elif s.stop.is_infinite: + it = iter(s) + printset = next(it), next(it), dots + elif len(s) > 4: + it = iter(s) + printset = next(it), next(it), dots, s[-1] + else: + printset = tuple(s) + + return self._print_seq(printset, '{', '}', ', ' ) + + def _print_Interval(self, i): + if i.start == i.end: + return self._print_seq(i.args[:1], '{', '}') + + else: + if i.left_open: + left = '(' + else: + left = '[' + + if i.right_open: + right = ')' + else: + right = ']' + + return self._print_seq(i.args[:2], left, right) + + def _print_AccumulationBounds(self, i): + left = '<' + right = '>' + + return self._print_seq(i.args[:2], left, right) + + def _print_Intersection(self, u): + + delimiter = ' %s ' % pretty_atom('Intersection', 'n') + + return self._print_seq(u.args, None, None, delimiter, + parenthesize=lambda set: set.is_ProductSet or + set.is_Union or set.is_Complement) + + def _print_Union(self, u): + + union_delimiter = ' %s ' % pretty_atom('Union', 'U') + + return self._print_seq(u.args, None, None, union_delimiter, + parenthesize=lambda set: set.is_ProductSet or + set.is_Intersection or set.is_Complement) + + def _print_SymmetricDifference(self, u): + if not self._use_unicode: + raise NotImplementedError("ASCII pretty printing of SymmetricDifference is not implemented") + + sym_delimeter = ' %s ' % pretty_atom('SymmetricDifference') + + return self._print_seq(u.args, None, None, sym_delimeter) + + def _print_Complement(self, u): + + delimiter = r' \ ' + + return self._print_seq(u.args, None, None, delimiter, + parenthesize=lambda set: set.is_ProductSet or set.is_Intersection + or set.is_Union) + + def _print_ImageSet(self, ts): + if self._use_unicode: + inn = pretty_atom("SmallElementOf") + else: + inn = 'in' + fun = ts.lamda + sets = ts.base_sets + signature = fun.signature + expr = self._print(fun.expr) + + # TODO: the stuff to the left of the | and the stuff to the right of + # the | should have independent baselines, that way something like + # ImageSet(Lambda(x, 1/x**2), S.Naturals) prints the "x in N" part + # centered on the right instead of aligned with the fraction bar on + # the left. The same also applies to ConditionSet and ComplexRegion + if len(signature) == 1: + S = self._print_seq((signature[0], inn, sets[0]), + delimiter=' ') + return self._hprint_vseparator(expr, S, + left='{', right='}', + ifascii_nougly=True, delimiter=' ') + else: + pargs = tuple(j for var, setv in zip(signature, sets) for j in + (var, ' ', inn, ' ', setv, ", ")) + S = self._print_seq(pargs[:-1], delimiter='') + return self._hprint_vseparator(expr, S, + left='{', right='}', + ifascii_nougly=True, delimiter=' ') + + def _print_ConditionSet(self, ts): + if self._use_unicode: + inn = pretty_atom('SmallElementOf') + # using _and because and is a keyword and it is bad practice to + # overwrite them + _and = pretty_atom('And') + else: + inn = 'in' + _and = 'and' + + variables = self._print_seq(Tuple(ts.sym)) + as_expr = getattr(ts.condition, 'as_expr', None) + if as_expr is not None: + cond = self._print(ts.condition.as_expr()) + else: + cond = self._print(ts.condition) + if self._use_unicode: + cond = self._print(cond) + cond = prettyForm(*cond.parens()) + + if ts.base_set is S.UniversalSet: + return self._hprint_vseparator(variables, cond, left="{", + right="}", ifascii_nougly=True, + delimiter=' ') + + base = self._print(ts.base_set) + C = self._print_seq((variables, inn, base, _and, cond), + delimiter=' ') + return self._hprint_vseparator(variables, C, left="{", right="}", + ifascii_nougly=True, delimiter=' ') + + def _print_ComplexRegion(self, ts): + if self._use_unicode: + inn = pretty_atom('SmallElementOf') + else: + inn = 'in' + variables = self._print_seq(ts.variables) + expr = self._print(ts.expr) + prodsets = self._print(ts.sets) + + C = self._print_seq((variables, inn, prodsets), + delimiter=' ') + return self._hprint_vseparator(expr, C, left="{", right="}", + ifascii_nougly=True, delimiter=' ') + + def _print_Contains(self, e): + var, set = e.args + if self._use_unicode: + el = f" {pretty_atom('ElementOf')} " + return prettyForm(*stringPict.next(self._print(var), + el, self._print(set)), binding=8) + else: + return prettyForm(sstr(e)) + + def _print_FourierSeries(self, s): + if s.an.formula is S.Zero and s.bn.formula is S.Zero: + return self._print(s.a0) + if self._use_unicode: + dots = pretty_atom('Dots') + else: + dots = '...' + return self._print_Add(s.truncate()) + self._print(dots) + + def _print_FormalPowerSeries(self, s): + return self._print_Add(s.infinite) + + def _print_SetExpr(self, se): + pretty_set = prettyForm(*self._print(se.set).parens()) + pretty_name = self._print(Symbol("SetExpr")) + return prettyForm(*pretty_name.right(pretty_set)) + + def _print_SeqFormula(self, s): + if self._use_unicode: + dots = pretty_atom('Dots') + else: + dots = '...' + + if len(s.start.free_symbols) > 0 or len(s.stop.free_symbols) > 0: + raise NotImplementedError("Pretty printing of sequences with symbolic bound not implemented") + + if s.start is S.NegativeInfinity: + stop = s.stop + printset = (dots, s.coeff(stop - 3), s.coeff(stop - 2), + s.coeff(stop - 1), s.coeff(stop)) + elif s.stop is S.Infinity or s.length > 4: + printset = s[:4] + printset.append(dots) + printset = tuple(printset) + else: + printset = tuple(s) + return self._print_list(printset) + + _print_SeqPer = _print_SeqFormula + _print_SeqAdd = _print_SeqFormula + _print_SeqMul = _print_SeqFormula + + def _print_seq(self, seq, left=None, right=None, delimiter=', ', + parenthesize=lambda x: False, ifascii_nougly=True): + + pforms = [] + for item in seq: + pform = self._print(item) + if parenthesize(item): + pform = prettyForm(*pform.parens()) + if pforms: + pforms.append(delimiter) + pforms.append(pform) + + if not pforms: + s = stringPict('') + else: + s = prettyForm(*stringPict.next(*pforms)) + + s = prettyForm(*s.parens(left, right, ifascii_nougly=ifascii_nougly)) + return s + + def join(self, delimiter, args): + pform = None + + for arg in args: + if pform is None: + pform = arg + else: + pform = prettyForm(*pform.right(delimiter)) + pform = prettyForm(*pform.right(arg)) + + if pform is None: + return prettyForm("") + else: + return pform + + def _print_list(self, l): + return self._print_seq(l, '[', ']') + + def _print_tuple(self, t): + if len(t) == 1: + ptuple = prettyForm(*stringPict.next(self._print(t[0]), ',')) + return prettyForm(*ptuple.parens('(', ')', ifascii_nougly=True)) + else: + return self._print_seq(t, '(', ')') + + def _print_Tuple(self, expr): + return self._print_tuple(expr) + + def _print_dict(self, d): + keys = sorted(d.keys(), key=default_sort_key) + items = [] + + for k in keys: + K = self._print(k) + V = self._print(d[k]) + s = prettyForm(*stringPict.next(K, ': ', V)) + + items.append(s) + + return self._print_seq(items, '{', '}') + + def _print_Dict(self, d): + return self._print_dict(d) + + def _print_set(self, s): + if not s: + return prettyForm('set()') + items = sorted(s, key=default_sort_key) + pretty = self._print_seq(items) + pretty = prettyForm(*pretty.parens('{', '}', ifascii_nougly=True)) + return pretty + + def _print_frozenset(self, s): + if not s: + return prettyForm('frozenset()') + items = sorted(s, key=default_sort_key) + pretty = self._print_seq(items) + pretty = prettyForm(*pretty.parens('{', '}', ifascii_nougly=True)) + pretty = prettyForm(*pretty.parens('(', ')', ifascii_nougly=True)) + pretty = prettyForm(*stringPict.next(type(s).__name__, pretty)) + return pretty + + def _print_UniversalSet(self, s): + if self._use_unicode: + return prettyForm(pretty_atom('Universe')) + else: + return prettyForm('UniversalSet') + + def _print_PolyRing(self, ring): + return prettyForm(sstr(ring)) + + def _print_FracField(self, field): + return prettyForm(sstr(field)) + + def _print_FreeGroupElement(self, elm): + return prettyForm(str(elm)) + + def _print_PolyElement(self, poly): + return prettyForm(sstr(poly)) + + def _print_FracElement(self, frac): + return prettyForm(sstr(frac)) + + def _print_AlgebraicNumber(self, expr): + if expr.is_aliased: + return self._print(expr.as_poly().as_expr()) + else: + return self._print(expr.as_expr()) + + def _print_ComplexRootOf(self, expr): + args = [self._print_Add(expr.expr, order='lex'), expr.index] + pform = prettyForm(*self._print_seq(args).parens()) + pform = prettyForm(*pform.left('CRootOf')) + return pform + + def _print_RootSum(self, expr): + args = [self._print_Add(expr.expr, order='lex')] + + if expr.fun is not S.IdentityFunction: + args.append(self._print(expr.fun)) + + pform = prettyForm(*self._print_seq(args).parens()) + pform = prettyForm(*pform.left('RootSum')) + + return pform + + def _print_FiniteField(self, expr): + if self._use_unicode: + form = f"{pretty_atom('Integers')}_%d" + else: + form = 'GF(%d)' + + return prettyForm(pretty_symbol(form % expr.mod)) + + def _print_IntegerRing(self, expr): + if self._use_unicode: + return prettyForm(pretty_atom('Integers')) + else: + return prettyForm('ZZ') + + def _print_RationalField(self, expr): + if self._use_unicode: + return prettyForm(pretty_atom('Rationals')) + else: + return prettyForm('QQ') + + def _print_RealField(self, domain): + if self._use_unicode: + prefix = pretty_atom("Reals") + else: + prefix = 'RR' + + if domain.has_default_precision: + return prettyForm(prefix) + else: + return self._print(pretty_symbol(prefix + "_" + str(domain.precision))) + + def _print_ComplexField(self, domain): + if self._use_unicode: + prefix = pretty_atom('Complexes') + else: + prefix = 'CC' + + if domain.has_default_precision: + return prettyForm(prefix) + else: + return self._print(pretty_symbol(prefix + "_" + str(domain.precision))) + + def _print_PolynomialRing(self, expr): + args = list(expr.symbols) + + if not expr.order.is_default: + order = prettyForm(*prettyForm("order=").right(self._print(expr.order))) + args.append(order) + + pform = self._print_seq(args, '[', ']') + pform = prettyForm(*pform.left(self._print(expr.domain))) + + return pform + + def _print_FractionField(self, expr): + args = list(expr.symbols) + + if not expr.order.is_default: + order = prettyForm(*prettyForm("order=").right(self._print(expr.order))) + args.append(order) + + pform = self._print_seq(args, '(', ')') + pform = prettyForm(*pform.left(self._print(expr.domain))) + + return pform + + def _print_PolynomialRingBase(self, expr): + g = expr.symbols + if str(expr.order) != str(expr.default_order): + g = g + ("order=" + str(expr.order),) + pform = self._print_seq(g, '[', ']') + pform = prettyForm(*pform.left(self._print(expr.domain))) + + return pform + + def _print_GroebnerBasis(self, basis): + exprs = [ self._print_Add(arg, order=basis.order) + for arg in basis.exprs ] + exprs = prettyForm(*self.join(", ", exprs).parens(left="[", right="]")) + + gens = [ self._print(gen) for gen in basis.gens ] + + domain = prettyForm( + *prettyForm("domain=").right(self._print(basis.domain))) + order = prettyForm( + *prettyForm("order=").right(self._print(basis.order))) + + pform = self.join(", ", [exprs] + gens + [domain, order]) + + pform = prettyForm(*pform.parens()) + pform = prettyForm(*pform.left(basis.__class__.__name__)) + + return pform + + def _print_Subs(self, e): + pform = self._print(e.expr) + pform = prettyForm(*pform.parens()) + + h = pform.height() if pform.height() > 1 else 2 + rvert = stringPict(vobj('|', h), baseline=pform.baseline) + pform = prettyForm(*pform.right(rvert)) + + b = pform.baseline + pform.baseline = pform.height() - 1 + pform = prettyForm(*pform.right(self._print_seq([ + self._print_seq((self._print(v[0]), xsym('=='), self._print(v[1])), + delimiter='') for v in zip(e.variables, e.point) ]))) + + pform.baseline = b + return pform + + def _print_number_function(self, e, name): + # Print name_arg[0] for one argument or name_arg[0](arg[1]) + # for more than one argument + pform = prettyForm(name) + arg = self._print(e.args[0]) + pform_arg = prettyForm(" "*arg.width()) + pform_arg = prettyForm(*pform_arg.below(arg)) + pform = prettyForm(*pform.right(pform_arg)) + if len(e.args) == 1: + return pform + m, x = e.args + # TODO: copy-pasted from _print_Function: can we do better? + prettyFunc = pform + prettyArgs = prettyForm(*self._print_seq([x]).parens()) + pform = prettyForm( + binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs)) + pform.prettyFunc = prettyFunc + pform.prettyArgs = prettyArgs + return pform + + def _print_euler(self, e): + return self._print_number_function(e, "E") + + def _print_catalan(self, e): + return self._print_number_function(e, "C") + + def _print_bernoulli(self, e): + return self._print_number_function(e, "B") + + _print_bell = _print_bernoulli + + def _print_lucas(self, e): + return self._print_number_function(e, "L") + + def _print_fibonacci(self, e): + return self._print_number_function(e, "F") + + def _print_tribonacci(self, e): + return self._print_number_function(e, "T") + + def _print_stieltjes(self, e): + if self._use_unicode: + return self._print_number_function(e, greek_unicode['gamma']) + else: + return self._print_number_function(e, "stieltjes") + + def _print_KroneckerDelta(self, e): + pform = self._print(e.args[0]) + pform = prettyForm(*pform.right(prettyForm(','))) + pform = prettyForm(*pform.right(self._print(e.args[1]))) + if self._use_unicode: + a = stringPict(pretty_symbol('delta')) + else: + a = stringPict('d') + b = pform + top = stringPict(*b.left(' '*a.width())) + bot = stringPict(*a.right(' '*b.width())) + return prettyForm(binding=prettyForm.POW, *bot.below(top)) + + def _print_RandomDomain(self, d): + if hasattr(d, 'as_boolean'): + pform = self._print('Domain: ') + pform = prettyForm(*pform.right(self._print(d.as_boolean()))) + return pform + elif hasattr(d, 'set'): + pform = self._print('Domain: ') + pform = prettyForm(*pform.right(self._print(d.symbols))) + pform = prettyForm(*pform.right(self._print(' in '))) + pform = prettyForm(*pform.right(self._print(d.set))) + return pform + elif hasattr(d, 'symbols'): + pform = self._print('Domain on ') + pform = prettyForm(*pform.right(self._print(d.symbols))) + return pform + else: + return self._print(None) + + def _print_DMP(self, p): + try: + if p.ring is not None: + # TODO incorporate order + return self._print(p.ring.to_sympy(p)) + except SympifyError: + pass + return self._print(repr(p)) + + def _print_DMF(self, p): + return self._print_DMP(p) + + def _print_Object(self, object): + return self._print(pretty_symbol(object.name)) + + def _print_Morphism(self, morphism): + arrow = xsym("-->") + + domain = self._print(morphism.domain) + codomain = self._print(morphism.codomain) + tail = domain.right(arrow, codomain)[0] + + return prettyForm(tail) + + def _print_NamedMorphism(self, morphism): + pretty_name = self._print(pretty_symbol(morphism.name)) + pretty_morphism = self._print_Morphism(morphism) + return prettyForm(pretty_name.right(":", pretty_morphism)[0]) + + def _print_IdentityMorphism(self, morphism): + from sympy.categories import NamedMorphism + return self._print_NamedMorphism( + NamedMorphism(morphism.domain, morphism.codomain, "id")) + + def _print_CompositeMorphism(self, morphism): + + circle = xsym(".") + + # All components of the morphism have names and it is thus + # possible to build the name of the composite. + component_names_list = [pretty_symbol(component.name) for + component in morphism.components] + component_names_list.reverse() + component_names = circle.join(component_names_list) + ":" + + pretty_name = self._print(component_names) + pretty_morphism = self._print_Morphism(morphism) + return prettyForm(pretty_name.right(pretty_morphism)[0]) + + def _print_Category(self, category): + return self._print(pretty_symbol(category.name)) + + def _print_Diagram(self, diagram): + if not diagram.premises: + # This is an empty diagram. + return self._print(S.EmptySet) + + pretty_result = self._print(diagram.premises) + if diagram.conclusions: + results_arrow = " %s " % xsym("==>") + + pretty_conclusions = self._print(diagram.conclusions)[0] + pretty_result = pretty_result.right( + results_arrow, pretty_conclusions) + + return prettyForm(pretty_result[0]) + + def _print_DiagramGrid(self, grid): + from sympy.matrices import Matrix + matrix = Matrix([[grid[i, j] if grid[i, j] else Symbol(" ") + for j in range(grid.width)] + for i in range(grid.height)]) + return self._print_matrix_contents(matrix) + + def _print_FreeModuleElement(self, m): + # Print as row vector for convenience, for now. + return self._print_seq(m, '[', ']') + + def _print_SubModule(self, M): + gens = [[M.ring.to_sympy(g) for g in gen] for gen in M.gens] + return self._print_seq(gens, '<', '>') + + def _print_FreeModule(self, M): + return self._print(M.ring)**self._print(M.rank) + + def _print_ModuleImplementedIdeal(self, M): + sym = M.ring.to_sympy + return self._print_seq([sym(x) for [x] in M._module.gens], '<', '>') + + def _print_QuotientRing(self, R): + return self._print(R.ring) / self._print(R.base_ideal) + + def _print_QuotientRingElement(self, R): + return self._print(R.ring.to_sympy(R)) + self._print(R.ring.base_ideal) + + def _print_QuotientModuleElement(self, m): + return self._print(m.data) + self._print(m.module.killed_module) + + def _print_QuotientModule(self, M): + return self._print(M.base) / self._print(M.killed_module) + + def _print_MatrixHomomorphism(self, h): + matrix = self._print(h._sympy_matrix()) + matrix.baseline = matrix.height() // 2 + pform = prettyForm(*matrix.right(' : ', self._print(h.domain), + ' %s> ' % hobj('-', 2), self._print(h.codomain))) + return pform + + def _print_Manifold(self, manifold): + return self._print(manifold.name) + + def _print_Patch(self, patch): + return self._print(patch.name) + + def _print_CoordSystem(self, coords): + return self._print(coords.name) + + def _print_BaseScalarField(self, field): + string = field._coord_sys.symbols[field._index].name + return self._print(pretty_symbol(string)) + + def _print_BaseVectorField(self, field): + s = U('PARTIAL DIFFERENTIAL') + '_' + field._coord_sys.symbols[field._index].name + return self._print(pretty_symbol(s)) + + def _print_Differential(self, diff): + if self._use_unicode: + d = pretty_atom('Differential') + else: + d = 'd' + field = diff._form_field + if hasattr(field, '_coord_sys'): + string = field._coord_sys.symbols[field._index].name + return self._print(d + ' ' + pretty_symbol(string)) + else: + pform = self._print(field) + pform = prettyForm(*pform.parens()) + return prettyForm(*pform.left(d)) + + def _print_Tr(self, p): + #TODO: Handle indices + pform = self._print(p.args[0]) + pform = prettyForm(*pform.left('%s(' % (p.__class__.__name__))) + pform = prettyForm(*pform.right(')')) + return pform + + def _print_primenu(self, e): + pform = self._print(e.args[0]) + pform = prettyForm(*pform.parens()) + if self._use_unicode: + pform = prettyForm(*pform.left(greek_unicode['nu'])) + else: + pform = prettyForm(*pform.left('nu')) + return pform + + def _print_primeomega(self, e): + pform = self._print(e.args[0]) + pform = prettyForm(*pform.parens()) + if self._use_unicode: + pform = prettyForm(*pform.left(greek_unicode['Omega'])) + else: + pform = prettyForm(*pform.left('Omega')) + return pform + + def _print_Quantity(self, e): + if e.name.name == 'degree': + if self._use_unicode: + pform = self._print(pretty_atom('Degree')) + else: + pform = self._print(chr(176)) + return pform + else: + return self.emptyPrinter(e) + + def _print_AssignmentBase(self, e): + + op = prettyForm(' ' + xsym(e.op) + ' ') + + l = self._print(e.lhs) + r = self._print(e.rhs) + pform = prettyForm(*stringPict.next(l, op, r)) + return pform + + def _print_Str(self, s): + return self._print(s.name) + + +@print_function(PrettyPrinter) +def pretty(expr, **settings): + """Returns a string containing the prettified form of expr. + + For information on keyword arguments see pretty_print function. + + """ + pp = PrettyPrinter(settings) + + # XXX: this is an ugly hack, but at least it works + use_unicode = pp._settings['use_unicode'] + uflag = pretty_use_unicode(use_unicode) + + try: + return pp.doprint(expr) + finally: + pretty_use_unicode(uflag) + + +def pretty_print(expr, **kwargs): + """Prints expr in pretty form. + + pprint is just a shortcut for this function. + + Parameters + ========== + + expr : expression + The expression to print. + + wrap_line : bool, optional (default=True) + Line wrapping enabled/disabled. + + num_columns : int or None, optional (default=None) + Number of columns before line breaking (default to None which reads + the terminal width), useful when using SymPy without terminal. + + use_unicode : bool or None, optional (default=None) + Use unicode characters, such as the Greek letter pi instead of + the string pi. + + full_prec : bool or string, optional (default="auto") + Use full precision. + + order : bool or string, optional (default=None) + Set to 'none' for long expressions if slow; default is None. + + use_unicode_sqrt_char : bool, optional (default=True) + Use compact single-character square root symbol (when unambiguous). + + root_notation : bool, optional (default=True) + Set to 'False' for printing exponents of the form 1/n in fractional form. + By default exponent is printed in root form. + + mat_symbol_style : string, optional (default="plain") + Set to "bold" for printing MatrixSymbols using a bold mathematical symbol face. + By default the standard face is used. + + imaginary_unit : string, optional (default="i") + Letter to use for imaginary unit when use_unicode is True. + Can be "i" (default) or "j". + """ + print(pretty(expr, **kwargs)) + +pprint = pretty_print + + +def pager_print(expr, **settings): + """Prints expr using the pager, in pretty form. + + This invokes a pager command using pydoc. Lines are not wrapped + automatically. This routine is meant to be used with a pager that allows + sideways scrolling, like ``less -S``. + + Parameters are the same as for ``pretty_print``. If you wish to wrap lines, + pass ``num_columns=None`` to auto-detect the width of the terminal. + + """ + from pydoc import pager + from locale import getpreferredencoding + if 'num_columns' not in settings: + settings['num_columns'] = 500000 # disable line wrap + pager(pretty(expr, **settings).encode(getpreferredencoding())) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/pretty_symbology.py b/venv/lib/python3.10/site-packages/sympy/printing/pretty/pretty_symbology.py new file mode 100644 index 0000000000000000000000000000000000000000..bdb6ec556c6ed7b15dfcddcfc3da189102d5395b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/pretty/pretty_symbology.py @@ -0,0 +1,731 @@ +"""Symbolic primitives + unicode/ASCII abstraction for pretty.py""" + +import sys +import warnings +from string import ascii_lowercase, ascii_uppercase +import unicodedata + +unicode_warnings = '' + +def U(name): + """ + Get a unicode character by name or, None if not found. + + This exists because older versions of Python use older unicode databases. + """ + try: + return unicodedata.lookup(name) + except KeyError: + global unicode_warnings + unicode_warnings += 'No \'%s\' in unicodedata\n' % name + return None + +from sympy.printing.conventions import split_super_sub +from sympy.core.alphabets import greeks +from sympy.utilities.exceptions import sympy_deprecation_warning + +# prefix conventions when constructing tables +# L - LATIN i +# G - GREEK beta +# D - DIGIT 0 +# S - SYMBOL + + + +__all__ = ['greek_unicode', 'sub', 'sup', 'xsym', 'vobj', 'hobj', 'pretty_symbol', + 'annotated', 'center_pad', 'center'] + + +_use_unicode = False + + +def pretty_use_unicode(flag=None): + """Set whether pretty-printer should use unicode by default""" + global _use_unicode, unicode_warnings + if flag is None: + return _use_unicode + + if flag and unicode_warnings: + # print warnings (if any) on first unicode usage + warnings.warn(unicode_warnings) + unicode_warnings = '' + + use_unicode_prev = _use_unicode + _use_unicode = flag + return use_unicode_prev + + +def pretty_try_use_unicode(): + """See if unicode output is available and leverage it if possible""" + + encoding = getattr(sys.stdout, 'encoding', None) + + # this happens when e.g. stdout is redirected through a pipe, or is + # e.g. a cStringIO.StringO + if encoding is None: + return # sys.stdout has no encoding + + symbols = [] + + # see if we can represent greek alphabet + symbols += greek_unicode.values() + + # and atoms + symbols += atoms_table.values() + + for s in symbols: + if s is None: + return # common symbols not present! + + try: + s.encode(encoding) + except UnicodeEncodeError: + return + + # all the characters were present and encodable + pretty_use_unicode(True) + + +def xstr(*args): + sympy_deprecation_warning( + """ + The sympy.printing.pretty.pretty_symbology.xstr() function is + deprecated. Use str() instead. + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-pretty-printing-functions" + ) + return str(*args) + +# GREEK +g = lambda l: U('GREEK SMALL LETTER %s' % l.upper()) +G = lambda l: U('GREEK CAPITAL LETTER %s' % l.upper()) + +greek_letters = list(greeks) # make a copy +# deal with Unicode's funny spelling of lambda +greek_letters[greek_letters.index('lambda')] = 'lamda' + +# {} greek letter -> (g,G) +greek_unicode = {L: g(L) for L in greek_letters} +greek_unicode.update((L[0].upper() + L[1:], G(L)) for L in greek_letters) + +# aliases +greek_unicode['lambda'] = greek_unicode['lamda'] +greek_unicode['Lambda'] = greek_unicode['Lamda'] +greek_unicode['varsigma'] = '\N{GREEK SMALL LETTER FINAL SIGMA}' + +# BOLD +b = lambda l: U('MATHEMATICAL BOLD SMALL %s' % l.upper()) +B = lambda l: U('MATHEMATICAL BOLD CAPITAL %s' % l.upper()) + +bold_unicode = {l: b(l) for l in ascii_lowercase} +bold_unicode.update((L, B(L)) for L in ascii_uppercase) + +# GREEK BOLD +gb = lambda l: U('MATHEMATICAL BOLD SMALL %s' % l.upper()) +GB = lambda l: U('MATHEMATICAL BOLD CAPITAL %s' % l.upper()) + +greek_bold_letters = list(greeks) # make a copy, not strictly required here +# deal with Unicode's funny spelling of lambda +greek_bold_letters[greek_bold_letters.index('lambda')] = 'lamda' + +# {} greek letter -> (g,G) +greek_bold_unicode = {L: g(L) for L in greek_bold_letters} +greek_bold_unicode.update((L[0].upper() + L[1:], G(L)) for L in greek_bold_letters) +greek_bold_unicode['lambda'] = greek_unicode['lamda'] +greek_bold_unicode['Lambda'] = greek_unicode['Lamda'] +greek_bold_unicode['varsigma'] = '\N{MATHEMATICAL BOLD SMALL FINAL SIGMA}' + +digit_2txt = { + '0': 'ZERO', + '1': 'ONE', + '2': 'TWO', + '3': 'THREE', + '4': 'FOUR', + '5': 'FIVE', + '6': 'SIX', + '7': 'SEVEN', + '8': 'EIGHT', + '9': 'NINE', +} + +symb_2txt = { + '+': 'PLUS SIGN', + '-': 'MINUS', + '=': 'EQUALS SIGN', + '(': 'LEFT PARENTHESIS', + ')': 'RIGHT PARENTHESIS', + '[': 'LEFT SQUARE BRACKET', + ']': 'RIGHT SQUARE BRACKET', + '{': 'LEFT CURLY BRACKET', + '}': 'RIGHT CURLY BRACKET', + + # non-std + '{}': 'CURLY BRACKET', + 'sum': 'SUMMATION', + 'int': 'INTEGRAL', +} + +# SUBSCRIPT & SUPERSCRIPT +LSUB = lambda letter: U('LATIN SUBSCRIPT SMALL LETTER %s' % letter.upper()) +GSUB = lambda letter: U('GREEK SUBSCRIPT SMALL LETTER %s' % letter.upper()) +DSUB = lambda digit: U('SUBSCRIPT %s' % digit_2txt[digit]) +SSUB = lambda symb: U('SUBSCRIPT %s' % symb_2txt[symb]) + +LSUP = lambda letter: U('SUPERSCRIPT LATIN SMALL LETTER %s' % letter.upper()) +DSUP = lambda digit: U('SUPERSCRIPT %s' % digit_2txt[digit]) +SSUP = lambda symb: U('SUPERSCRIPT %s' % symb_2txt[symb]) + +sub = {} # symb -> subscript symbol +sup = {} # symb -> superscript symbol + +# latin subscripts +for l in 'aeioruvxhklmnpst': + sub[l] = LSUB(l) + +for l in 'in': + sup[l] = LSUP(l) + +for gl in ['beta', 'gamma', 'rho', 'phi', 'chi']: + sub[gl] = GSUB(gl) + +for d in [str(i) for i in range(10)]: + sub[d] = DSUB(d) + sup[d] = DSUP(d) + +for s in '+-=()': + sub[s] = SSUB(s) + sup[s] = SSUP(s) + +# Variable modifiers +# TODO: Make brackets adjust to height of contents +modifier_dict = { + # Accents + 'mathring': lambda s: center_accent(s, '\N{COMBINING RING ABOVE}'), + 'ddddot': lambda s: center_accent(s, '\N{COMBINING FOUR DOTS ABOVE}'), + 'dddot': lambda s: center_accent(s, '\N{COMBINING THREE DOTS ABOVE}'), + 'ddot': lambda s: center_accent(s, '\N{COMBINING DIAERESIS}'), + 'dot': lambda s: center_accent(s, '\N{COMBINING DOT ABOVE}'), + 'check': lambda s: center_accent(s, '\N{COMBINING CARON}'), + 'breve': lambda s: center_accent(s, '\N{COMBINING BREVE}'), + 'acute': lambda s: center_accent(s, '\N{COMBINING ACUTE ACCENT}'), + 'grave': lambda s: center_accent(s, '\N{COMBINING GRAVE ACCENT}'), + 'tilde': lambda s: center_accent(s, '\N{COMBINING TILDE}'), + 'hat': lambda s: center_accent(s, '\N{COMBINING CIRCUMFLEX ACCENT}'), + 'bar': lambda s: center_accent(s, '\N{COMBINING OVERLINE}'), + 'vec': lambda s: center_accent(s, '\N{COMBINING RIGHT ARROW ABOVE}'), + 'prime': lambda s: s+'\N{PRIME}', + 'prm': lambda s: s+'\N{PRIME}', + # # Faces -- these are here for some compatibility with latex printing + # 'bold': lambda s: s, + # 'bm': lambda s: s, + # 'cal': lambda s: s, + # 'scr': lambda s: s, + # 'frak': lambda s: s, + # Brackets + 'norm': lambda s: '\N{DOUBLE VERTICAL LINE}'+s+'\N{DOUBLE VERTICAL LINE}', + 'avg': lambda s: '\N{MATHEMATICAL LEFT ANGLE BRACKET}'+s+'\N{MATHEMATICAL RIGHT ANGLE BRACKET}', + 'abs': lambda s: '\N{VERTICAL LINE}'+s+'\N{VERTICAL LINE}', + 'mag': lambda s: '\N{VERTICAL LINE}'+s+'\N{VERTICAL LINE}', +} + +# VERTICAL OBJECTS +HUP = lambda symb: U('%s UPPER HOOK' % symb_2txt[symb]) +CUP = lambda symb: U('%s UPPER CORNER' % symb_2txt[symb]) +MID = lambda symb: U('%s MIDDLE PIECE' % symb_2txt[symb]) +EXT = lambda symb: U('%s EXTENSION' % symb_2txt[symb]) +HLO = lambda symb: U('%s LOWER HOOK' % symb_2txt[symb]) +CLO = lambda symb: U('%s LOWER CORNER' % symb_2txt[symb]) +TOP = lambda symb: U('%s TOP' % symb_2txt[symb]) +BOT = lambda symb: U('%s BOTTOM' % symb_2txt[symb]) + +# {} '(' -> (extension, start, end, middle) 1-character +_xobj_unicode = { + + # vertical symbols + # (( ext, top, bot, mid ), c1) + '(': (( EXT('('), HUP('('), HLO('(') ), '('), + ')': (( EXT(')'), HUP(')'), HLO(')') ), ')'), + '[': (( EXT('['), CUP('['), CLO('[') ), '['), + ']': (( EXT(']'), CUP(']'), CLO(']') ), ']'), + '{': (( EXT('{}'), HUP('{'), HLO('{'), MID('{') ), '{'), + '}': (( EXT('{}'), HUP('}'), HLO('}'), MID('}') ), '}'), + '|': U('BOX DRAWINGS LIGHT VERTICAL'), + 'Tee': U('BOX DRAWINGS LIGHT UP AND HORIZONTAL'), + 'UpTack': U('BOX DRAWINGS LIGHT DOWN AND HORIZONTAL'), + 'corner_up_centre' + '(_ext': U('LEFT PARENTHESIS EXTENSION'), + ')_ext': U('RIGHT PARENTHESIS EXTENSION'), + '(_lower_hook': U('LEFT PARENTHESIS LOWER HOOK'), + ')_lower_hook': U('RIGHT PARENTHESIS LOWER HOOK'), + '(_upper_hook': U('LEFT PARENTHESIS UPPER HOOK'), + ')_upper_hook': U('RIGHT PARENTHESIS UPPER HOOK'), + '<': ((U('BOX DRAWINGS LIGHT VERTICAL'), + U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT'), + U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT')), '<'), + + '>': ((U('BOX DRAWINGS LIGHT VERTICAL'), + U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT'), + U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT')), '>'), + + 'lfloor': (( EXT('['), EXT('['), CLO('[') ), U('LEFT FLOOR')), + 'rfloor': (( EXT(']'), EXT(']'), CLO(']') ), U('RIGHT FLOOR')), + 'lceil': (( EXT('['), CUP('['), EXT('[') ), U('LEFT CEILING')), + 'rceil': (( EXT(']'), CUP(']'), EXT(']') ), U('RIGHT CEILING')), + + 'int': (( EXT('int'), U('TOP HALF INTEGRAL'), U('BOTTOM HALF INTEGRAL') ), U('INTEGRAL')), + 'sum': (( U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT'), '_', U('OVERLINE'), U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT')), U('N-ARY SUMMATION')), + + # horizontal objects + #'-': '-', + '-': U('BOX DRAWINGS LIGHT HORIZONTAL'), + '_': U('LOW LINE'), + # We used to use this, but LOW LINE looks better for roots, as it's a + # little lower (i.e., it lines up with the / perfectly. But perhaps this + # one would still be wanted for some cases? + # '_': U('HORIZONTAL SCAN LINE-9'), + + # diagonal objects '\' & '/' ? + '/': U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT'), + '\\': U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT'), +} + +_xobj_ascii = { + # vertical symbols + # (( ext, top, bot, mid ), c1) + '(': (( '|', '/', '\\' ), '('), + ')': (( '|', '\\', '/' ), ')'), + +# XXX this looks ugly +# '[': (( '|', '-', '-' ), '['), +# ']': (( '|', '-', '-' ), ']'), +# XXX not so ugly :( + '[': (( '[', '[', '[' ), '['), + ']': (( ']', ']', ']' ), ']'), + + '{': (( '|', '/', '\\', '<' ), '{'), + '}': (( '|', '\\', '/', '>' ), '}'), + '|': '|', + + '<': (( '|', '/', '\\' ), '<'), + '>': (( '|', '\\', '/' ), '>'), + + 'int': ( ' | ', ' /', '/ ' ), + + # horizontal objects + '-': '-', + '_': '_', + + # diagonal objects '\' & '/' ? + '/': '/', + '\\': '\\', +} + + +def xobj(symb, length): + """Construct spatial object of given length. + + return: [] of equal-length strings + """ + + if length <= 0: + raise ValueError("Length should be greater than 0") + + # TODO robustify when no unicodedat available + if _use_unicode: + _xobj = _xobj_unicode + else: + _xobj = _xobj_ascii + + vinfo = _xobj[symb] + + c1 = top = bot = mid = None + + if not isinstance(vinfo, tuple): # 1 entry + ext = vinfo + else: + if isinstance(vinfo[0], tuple): # (vlong), c1 + vlong = vinfo[0] + c1 = vinfo[1] + else: # (vlong), c1 + vlong = vinfo + + ext = vlong[0] + + try: + top = vlong[1] + bot = vlong[2] + mid = vlong[3] + except IndexError: + pass + + if c1 is None: + c1 = ext + if top is None: + top = ext + if bot is None: + bot = ext + if mid is not None: + if (length % 2) == 0: + # even height, but we have to print it somehow anyway... + # XXX is it ok? + length += 1 + + else: + mid = ext + + if length == 1: + return c1 + + res = [] + next = (length - 2)//2 + nmid = (length - 2) - next*2 + + res += [top] + res += [ext]*next + res += [mid]*nmid + res += [ext]*next + res += [bot] + + return res + + +def vobj(symb, height): + """Construct vertical object of a given height + + see: xobj + """ + return '\n'.join( xobj(symb, height) ) + + +def hobj(symb, width): + """Construct horizontal object of a given width + + see: xobj + """ + return ''.join( xobj(symb, width) ) + +# RADICAL +# n -> symbol +root = { + 2: U('SQUARE ROOT'), # U('RADICAL SYMBOL BOTTOM') + 3: U('CUBE ROOT'), + 4: U('FOURTH ROOT'), +} + + +# RATIONAL +VF = lambda txt: U('VULGAR FRACTION %s' % txt) + +# (p,q) -> symbol +frac = { + (1, 2): VF('ONE HALF'), + (1, 3): VF('ONE THIRD'), + (2, 3): VF('TWO THIRDS'), + (1, 4): VF('ONE QUARTER'), + (3, 4): VF('THREE QUARTERS'), + (1, 5): VF('ONE FIFTH'), + (2, 5): VF('TWO FIFTHS'), + (3, 5): VF('THREE FIFTHS'), + (4, 5): VF('FOUR FIFTHS'), + (1, 6): VF('ONE SIXTH'), + (5, 6): VF('FIVE SIXTHS'), + (1, 8): VF('ONE EIGHTH'), + (3, 8): VF('THREE EIGHTHS'), + (5, 8): VF('FIVE EIGHTHS'), + (7, 8): VF('SEVEN EIGHTHS'), +} + + +# atom symbols +_xsym = { + '==': ('=', '='), + '<': ('<', '<'), + '>': ('>', '>'), + '<=': ('<=', U('LESS-THAN OR EQUAL TO')), + '>=': ('>=', U('GREATER-THAN OR EQUAL TO')), + '!=': ('!=', U('NOT EQUAL TO')), + ':=': (':=', ':='), + '+=': ('+=', '+='), + '-=': ('-=', '-='), + '*=': ('*=', '*='), + '/=': ('/=', '/='), + '%=': ('%=', '%='), + '*': ('*', U('DOT OPERATOR')), + '-->': ('-->', U('EM DASH') + U('EM DASH') + + U('BLACK RIGHT-POINTING TRIANGLE') if U('EM DASH') + and U('BLACK RIGHT-POINTING TRIANGLE') else None), + '==>': ('==>', U('BOX DRAWINGS DOUBLE HORIZONTAL') + + U('BOX DRAWINGS DOUBLE HORIZONTAL') + + U('BLACK RIGHT-POINTING TRIANGLE') if + U('BOX DRAWINGS DOUBLE HORIZONTAL') and + U('BOX DRAWINGS DOUBLE HORIZONTAL') and + U('BLACK RIGHT-POINTING TRIANGLE') else None), + '.': ('*', U('RING OPERATOR')), +} + + +def xsym(sym): + """get symbology for a 'character'""" + op = _xsym[sym] + + if _use_unicode: + return op[1] + else: + return op[0] + + +# SYMBOLS + +atoms_table = { + # class how-to-display + 'Exp1': U('SCRIPT SMALL E'), + 'Pi': U('GREEK SMALL LETTER PI'), + 'Infinity': U('INFINITY'), + 'NegativeInfinity': U('INFINITY') and ('-' + U('INFINITY')), # XXX what to do here + #'ImaginaryUnit': U('GREEK SMALL LETTER IOTA'), + #'ImaginaryUnit': U('MATHEMATICAL ITALIC SMALL I'), + 'ImaginaryUnit': U('DOUBLE-STRUCK ITALIC SMALL I'), + 'EmptySet': U('EMPTY SET'), + 'Naturals': U('DOUBLE-STRUCK CAPITAL N'), + 'Naturals0': (U('DOUBLE-STRUCK CAPITAL N') and + (U('DOUBLE-STRUCK CAPITAL N') + + U('SUBSCRIPT ZERO'))), + 'Integers': U('DOUBLE-STRUCK CAPITAL Z'), + 'Rationals': U('DOUBLE-STRUCK CAPITAL Q'), + 'Reals': U('DOUBLE-STRUCK CAPITAL R'), + 'Complexes': U('DOUBLE-STRUCK CAPITAL C'), + 'Universe': U('MATHEMATICAL DOUBLE-STRUCK CAPITAL U'), + 'IdentityMatrix': U('MATHEMATICAL DOUBLE-STRUCK CAPITAL I'), + 'ZeroMatrix': U('MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO'), + 'OneMatrix': U('MATHEMATICAL DOUBLE-STRUCK DIGIT ONE'), + 'Differential': U('DOUBLE-STRUCK ITALIC SMALL D'), + 'Union': U('UNION'), + 'ElementOf': U('ELEMENT OF'), + 'SmallElementOf': U('SMALL ELEMENT OF'), + 'SymmetricDifference': U('INCREMENT'), + 'Intersection': U('INTERSECTION'), + 'Ring': U('RING OPERATOR'), + 'Multiplication': U('MULTIPLICATION SIGN'), + 'TensorProduct': U('N-ARY CIRCLED TIMES OPERATOR'), + 'Dots': U('HORIZONTAL ELLIPSIS'), + 'Modifier Letter Low Ring':U('Modifier Letter Low Ring'), + 'EmptySequence': 'EmptySequence', + 'SuperscriptPlus': U('SUPERSCRIPT PLUS SIGN'), + 'SuperscriptMinus': U('SUPERSCRIPT MINUS'), + 'Dagger': U('DAGGER'), + 'Degree': U('DEGREE SIGN'), + #Logic Symbols + 'And': U('LOGICAL AND'), + 'Or': U('LOGICAL OR'), + 'Not': U('NOT SIGN'), + 'Nor': U('NOR'), + 'Nand': U('NAND'), + 'Xor': U('XOR'), + 'Equiv': U('LEFT RIGHT DOUBLE ARROW'), + 'NotEquiv': U('LEFT RIGHT DOUBLE ARROW WITH STROKE'), + 'Implies': U('LEFT RIGHT DOUBLE ARROW'), + 'NotImplies': U('LEFT RIGHT DOUBLE ARROW WITH STROKE'), + 'Arrow': U('RIGHTWARDS ARROW'), + 'ArrowFromBar': U('RIGHTWARDS ARROW FROM BAR'), + 'NotArrow': U('RIGHTWARDS ARROW WITH STROKE'), + 'Tautology': U('BOX DRAWINGS LIGHT UP AND HORIZONTAL'), + 'Contradiction': U('BOX DRAWINGS LIGHT DOWN AND HORIZONTAL') +} + + +def pretty_atom(atom_name, default=None, printer=None): + """return pretty representation of an atom""" + if _use_unicode: + if printer is not None and atom_name == 'ImaginaryUnit' and printer._settings['imaginary_unit'] == 'j': + return U('DOUBLE-STRUCK ITALIC SMALL J') + else: + return atoms_table[atom_name] + else: + if default is not None: + return default + + raise KeyError('only unicode') # send it default printer + + +def pretty_symbol(symb_name, bold_name=False): + """return pretty representation of a symbol""" + # let's split symb_name into symbol + index + # UC: beta1 + # UC: f_beta + + if not _use_unicode: + return symb_name + + name, sups, subs = split_super_sub(symb_name) + + def translate(s, bold_name) : + if bold_name: + gG = greek_bold_unicode.get(s) + else: + gG = greek_unicode.get(s) + if gG is not None: + return gG + for key in sorted(modifier_dict.keys(), key=lambda k:len(k), reverse=True) : + if s.lower().endswith(key) and len(s)>len(key): + return modifier_dict[key](translate(s[:-len(key)], bold_name)) + if bold_name: + return ''.join([bold_unicode[c] for c in s]) + return s + + name = translate(name, bold_name) + + # Let's prettify sups/subs. If it fails at one of them, pretty sups/subs are + # not used at all. + def pretty_list(l, mapping): + result = [] + for s in l: + pretty = mapping.get(s) + if pretty is None: + try: # match by separate characters + pretty = ''.join([mapping[c] for c in s]) + except (TypeError, KeyError): + return None + result.append(pretty) + return result + + pretty_sups = pretty_list(sups, sup) + if pretty_sups is not None: + pretty_subs = pretty_list(subs, sub) + else: + pretty_subs = None + + # glue the results into one string + if pretty_subs is None: # nice formatting of sups/subs did not work + if subs: + name += '_'+'_'.join([translate(s, bold_name) for s in subs]) + if sups: + name += '__'+'__'.join([translate(s, bold_name) for s in sups]) + return name + else: + sups_result = ' '.join(pretty_sups) + subs_result = ' '.join(pretty_subs) + + return ''.join([name, sups_result, subs_result]) + + +def annotated(letter): + """ + Return a stylised drawing of the letter ``letter``, together with + information on how to put annotations (super- and subscripts to the + left and to the right) on it. + + See pretty.py functions _print_meijerg, _print_hyper on how to use this + information. + """ + ucode_pics = { + 'F': (2, 0, 2, 0, '\N{BOX DRAWINGS LIGHT DOWN AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\n' + '\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\n' + '\N{BOX DRAWINGS LIGHT UP}'), + 'G': (3, 0, 3, 1, '\N{BOX DRAWINGS LIGHT ARC DOWN AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\N{BOX DRAWINGS LIGHT ARC DOWN AND LEFT}\n' + '\N{BOX DRAWINGS LIGHT VERTICAL}\N{BOX DRAWINGS LIGHT RIGHT}\N{BOX DRAWINGS LIGHT DOWN AND LEFT}\n' + '\N{BOX DRAWINGS LIGHT ARC UP AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\N{BOX DRAWINGS LIGHT ARC UP AND LEFT}') + } + ascii_pics = { + 'F': (3, 0, 3, 0, ' _\n|_\n|\n'), + 'G': (3, 0, 3, 1, ' __\n/__\n\\_|') + } + + if _use_unicode: + return ucode_pics[letter] + else: + return ascii_pics[letter] + +_remove_combining = dict.fromkeys(list(range(ord('\N{COMBINING GRAVE ACCENT}'), ord('\N{COMBINING LATIN SMALL LETTER X}'))) + + list(range(ord('\N{COMBINING LEFT HARPOON ABOVE}'), ord('\N{COMBINING ASTERISK ABOVE}')))) + +def is_combining(sym): + """Check whether symbol is a unicode modifier. """ + + return ord(sym) in _remove_combining + + +def center_accent(string, accent): + """ + Returns a string with accent inserted on the middle character. Useful to + put combining accents on symbol names, including multi-character names. + + Parameters + ========== + + string : string + The string to place the accent in. + accent : string + The combining accent to insert + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Combining_character + .. [2] https://en.wikipedia.org/wiki/Combining_Diacritical_Marks + + """ + + # Accent is placed on the previous character, although it may not always look + # like that depending on console + midpoint = len(string) // 2 + 1 + firstpart = string[:midpoint] + secondpart = string[midpoint:] + return firstpart + accent + secondpart + + +def line_width(line): + """Unicode combining symbols (modifiers) are not ever displayed as + separate symbols and thus should not be counted + """ + return len(line.translate(_remove_combining)) + + +def is_subscriptable_in_unicode(subscript): + """ + Checks whether a string is subscriptable in unicode or not. + + Parameters + ========== + + subscript: the string which needs to be checked + + Examples + ======== + + >>> from sympy.printing.pretty.pretty_symbology import is_subscriptable_in_unicode + >>> is_subscriptable_in_unicode('abc') + False + >>> is_subscriptable_in_unicode('123') + True + + """ + return all(character in sub for character in subscript) + + +def center_pad(wstring, wtarget, fillchar=' '): + """ + Return the padding strings necessary to center a string of + wstring characters wide in a wtarget wide space. + + The line_width wstring should always be less or equal to wtarget + or else a ValueError will be raised. + """ + if wstring > wtarget: + raise ValueError('not enough space for string') + wdelta = wtarget - wstring + + wleft = wdelta // 2 # favor left '1 ' + wright = wdelta - wleft + + left = fillchar * wleft + right = fillchar * wright + + return left, right + + +def center(string, width, fillchar=' '): + """Return a centered string of length determined by `line_width` + that uses `fillchar` for padding. + """ + left, right = center_pad(line_width(string), width, fillchar) + return ''.join([left, string, right]) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/stringpict.py b/venv/lib/python3.10/site-packages/sympy/printing/pretty/stringpict.py new file mode 100644 index 0000000000000000000000000000000000000000..b6055f09c83b2abbe0c492991aaee4dff5b34f49 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/pretty/stringpict.py @@ -0,0 +1,537 @@ +"""Prettyprinter by Jurjen Bos. +(I hate spammers: mail me at pietjepuk314 at the reverse of ku.oc.oohay). +All objects have a method that create a "stringPict", +that can be used in the str method for pretty printing. + +Updates by Jason Gedge (email at cs mun ca) + - terminal_string() method + - minor fixes and changes (mostly to prettyForm) + +TODO: + - Allow left/center/right alignment options for above/below and + top/center/bottom alignment options for left/right +""" + +import shutil + +from .pretty_symbology import hobj, vobj, xsym, xobj, pretty_use_unicode, line_width, center +from sympy.utilities.exceptions import sympy_deprecation_warning + +_GLOBAL_WRAP_LINE = None + +class stringPict: + """An ASCII picture. + The pictures are represented as a list of equal length strings. + """ + #special value for stringPict.below + LINE = 'line' + + def __init__(self, s, baseline=0): + """Initialize from string. + Multiline strings are centered. + """ + self.s = s + #picture is a string that just can be printed + self.picture = stringPict.equalLengths(s.splitlines()) + #baseline is the line number of the "base line" + self.baseline = baseline + self.binding = None + + @staticmethod + def equalLengths(lines): + # empty lines + if not lines: + return [''] + + width = max(line_width(line) for line in lines) + return [center(line, width) for line in lines] + + def height(self): + """The height of the picture in characters.""" + return len(self.picture) + + def width(self): + """The width of the picture in characters.""" + return line_width(self.picture[0]) + + @staticmethod + def next(*args): + """Put a string of stringPicts next to each other. + Returns string, baseline arguments for stringPict. + """ + #convert everything to stringPicts + objects = [] + for arg in args: + if isinstance(arg, str): + arg = stringPict(arg) + objects.append(arg) + + #make a list of pictures, with equal height and baseline + newBaseline = max(obj.baseline for obj in objects) + newHeightBelowBaseline = max( + obj.height() - obj.baseline + for obj in objects) + newHeight = newBaseline + newHeightBelowBaseline + + pictures = [] + for obj in objects: + oneEmptyLine = [' '*obj.width()] + basePadding = newBaseline - obj.baseline + totalPadding = newHeight - obj.height() + pictures.append( + oneEmptyLine * basePadding + + obj.picture + + oneEmptyLine * (totalPadding - basePadding)) + + result = [''.join(lines) for lines in zip(*pictures)] + return '\n'.join(result), newBaseline + + def right(self, *args): + r"""Put pictures next to this one. + Returns string, baseline arguments for stringPict. + (Multiline) strings are allowed, and are given a baseline of 0. + + Examples + ======== + + >>> from sympy.printing.pretty.stringpict import stringPict + >>> print(stringPict("10").right(" + ",stringPict("1\r-\r2",1))[0]) + 1 + 10 + - + 2 + + """ + return stringPict.next(self, *args) + + def left(self, *args): + """Put pictures (left to right) at left. + Returns string, baseline arguments for stringPict. + """ + return stringPict.next(*(args + (self,))) + + @staticmethod + def stack(*args): + """Put pictures on top of each other, + from top to bottom. + Returns string, baseline arguments for stringPict. + The baseline is the baseline of the second picture. + Everything is centered. + Baseline is the baseline of the second picture. + Strings are allowed. + The special value stringPict.LINE is a row of '-' extended to the width. + """ + #convert everything to stringPicts; keep LINE + objects = [] + for arg in args: + if arg is not stringPict.LINE and isinstance(arg, str): + arg = stringPict(arg) + objects.append(arg) + + #compute new width + newWidth = max( + obj.width() + for obj in objects + if obj is not stringPict.LINE) + + lineObj = stringPict(hobj('-', newWidth)) + + #replace LINE with proper lines + for i, obj in enumerate(objects): + if obj is stringPict.LINE: + objects[i] = lineObj + + #stack the pictures, and center the result + newPicture = [center(line, newWidth) for obj in objects for line in obj.picture] + newBaseline = objects[0].height() + objects[1].baseline + return '\n'.join(newPicture), newBaseline + + def below(self, *args): + """Put pictures under this picture. + Returns string, baseline arguments for stringPict. + Baseline is baseline of top picture + + Examples + ======== + + >>> from sympy.printing.pretty.stringpict import stringPict + >>> print(stringPict("x+3").below( + ... stringPict.LINE, '3')[0]) #doctest: +NORMALIZE_WHITESPACE + x+3 + --- + 3 + + """ + s, baseline = stringPict.stack(self, *args) + return s, self.baseline + + def above(self, *args): + """Put pictures above this picture. + Returns string, baseline arguments for stringPict. + Baseline is baseline of bottom picture. + """ + string, baseline = stringPict.stack(*(args + (self,))) + baseline = len(string.splitlines()) - self.height() + self.baseline + return string, baseline + + def parens(self, left='(', right=')', ifascii_nougly=False): + """Put parentheses around self. + Returns string, baseline arguments for stringPict. + + left or right can be None or empty string which means 'no paren from + that side' + """ + h = self.height() + b = self.baseline + + # XXX this is a hack -- ascii parens are ugly! + if ifascii_nougly and not pretty_use_unicode(): + h = 1 + b = 0 + + res = self + + if left: + lparen = stringPict(vobj(left, h), baseline=b) + res = stringPict(*lparen.right(self)) + if right: + rparen = stringPict(vobj(right, h), baseline=b) + res = stringPict(*res.right(rparen)) + + return ('\n'.join(res.picture), res.baseline) + + def leftslash(self): + """Precede object by a slash of the proper size. + """ + # XXX not used anywhere ? + height = max( + self.baseline, + self.height() - 1 - self.baseline)*2 + 1 + slash = '\n'.join( + ' '*(height - i - 1) + xobj('/', 1) + ' '*i + for i in range(height) + ) + return self.left(stringPict(slash, height//2)) + + def root(self, n=None): + """Produce a nice root symbol. + Produces ugly results for big n inserts. + """ + # XXX not used anywhere + # XXX duplicate of root drawing in pretty.py + #put line over expression + result = self.above('_'*self.width()) + #construct right half of root symbol + height = self.height() + slash = '\n'.join( + ' ' * (height - i - 1) + '/' + ' ' * i + for i in range(height) + ) + slash = stringPict(slash, height - 1) + #left half of root symbol + if height > 2: + downline = stringPict('\\ \n \\', 1) + else: + downline = stringPict('\\') + #put n on top, as low as possible + if n is not None and n.width() > downline.width(): + downline = downline.left(' '*(n.width() - downline.width())) + downline = downline.above(n) + #build root symbol + root = downline.right(slash) + #glue it on at the proper height + #normally, the root symbel is as high as self + #which is one less than result + #this moves the root symbol one down + #if the root became higher, the baseline has to grow too + root.baseline = result.baseline - result.height() + root.height() + return result.left(root) + + def render(self, * args, **kwargs): + """Return the string form of self. + + Unless the argument line_break is set to False, it will + break the expression in a form that can be printed + on the terminal without being broken up. + """ + if _GLOBAL_WRAP_LINE is not None: + kwargs["wrap_line"] = _GLOBAL_WRAP_LINE + + if kwargs["wrap_line"] is False: + return "\n".join(self.picture) + + if kwargs["num_columns"] is not None: + # Read the argument num_columns if it is not None + ncols = kwargs["num_columns"] + else: + # Attempt to get a terminal width + ncols = self.terminal_width() + + if ncols <= 0: + ncols = 80 + + # If smaller than the terminal width, no need to correct + if self.width() <= ncols: + return type(self.picture[0])(self) + + """ + Break long-lines in a visually pleasing format. + without overflow indicators | with overflow indicators + | 2 2 3 | | 2 2 3 ↪| + |6*x *y + 4*x*y + | |6*x *y + 4*x*y + ↪| + | | | | + | 3 4 4 | |↪ 3 4 4 | + |4*y*x + x + y | |↪ 4*y*x + x + y | + |a*c*e + a*c*f + a*d | |a*c*e + a*c*f + a*d ↪| + |*e + a*d*f + b*c*e | | | + |+ b*c*f + b*d*e + b | |↪ *e + a*d*f + b*c* ↪| + |*d*f | | | + | | |↪ e + b*c*f + b*d*e ↪| + | | | | + | | |↪ + b*d*f | + """ + + overflow_first = "" + if kwargs["use_unicode"] or pretty_use_unicode(): + overflow_start = "\N{RIGHTWARDS ARROW WITH HOOK} " + overflow_end = " \N{RIGHTWARDS ARROW WITH HOOK}" + else: + overflow_start = "> " + overflow_end = " >" + + def chunks(line): + """Yields consecutive chunks of line_width ncols""" + prefix = overflow_first + width, start = line_width(prefix + overflow_end), 0 + for i, x in enumerate(line): + wx = line_width(x) + # Only flush the screen when the current character overflows. + # This way, combining marks can be appended even when width == ncols. + if width + wx > ncols: + yield prefix + line[start:i] + overflow_end + prefix = overflow_start + width, start = line_width(prefix + overflow_end), i + width += wx + yield prefix + line[start:] + + # Concurrently assemble chunks of all lines into individual screens + pictures = zip(*map(chunks, self.picture)) + + # Join lines of each screen into sub-pictures + pictures = ["\n".join(picture) for picture in pictures] + + # Add spacers between sub-pictures + return "\n\n".join(pictures) + + def terminal_width(self): + """Return the terminal width if possible, otherwise return 0. + """ + size = shutil.get_terminal_size(fallback=(0, 0)) + return size.columns + + def __eq__(self, o): + if isinstance(o, str): + return '\n'.join(self.picture) == o + elif isinstance(o, stringPict): + return o.picture == self.picture + return False + + def __hash__(self): + return super().__hash__() + + def __str__(self): + return '\n'.join(self.picture) + + def __repr__(self): + return "stringPict(%r,%d)" % ('\n'.join(self.picture), self.baseline) + + def __getitem__(self, index): + return self.picture[index] + + def __len__(self): + return len(self.s) + + +class prettyForm(stringPict): + """ + Extension of the stringPict class that knows about basic math applications, + optimizing double minus signs. + + "Binding" is interpreted as follows:: + + ATOM this is an atom: never needs to be parenthesized + FUNC this is a function application: parenthesize if added (?) + DIV this is a division: make wider division if divided + POW this is a power: only parenthesize if exponent + MUL this is a multiplication: parenthesize if powered + ADD this is an addition: parenthesize if multiplied or powered + NEG this is a negative number: optimize if added, parenthesize if + multiplied or powered + OPEN this is an open object: parenthesize if added, multiplied, or + powered (example: Piecewise) + """ + ATOM, FUNC, DIV, POW, MUL, ADD, NEG, OPEN = range(8) + + def __init__(self, s, baseline=0, binding=0, unicode=None): + """Initialize from stringPict and binding power.""" + stringPict.__init__(self, s, baseline) + self.binding = binding + if unicode is not None: + sympy_deprecation_warning( + """ + The unicode argument to prettyForm is deprecated. Only the s + argument (the first positional argument) should be passed. + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-pretty-printing-functions") + self._unicode = unicode or s + + @property + def unicode(self): + sympy_deprecation_warning( + """ + The prettyForm.unicode attribute is deprecated. Use the + prettyForm.s attribute instead. + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-pretty-printing-functions") + return self._unicode + + # Note: code to handle subtraction is in _print_Add + + def __add__(self, *others): + """Make a pretty addition. + Addition of negative numbers is simplified. + """ + arg = self + if arg.binding > prettyForm.NEG: + arg = stringPict(*arg.parens()) + result = [arg] + for arg in others: + #add parentheses for weak binders + if arg.binding > prettyForm.NEG: + arg = stringPict(*arg.parens()) + #use existing minus sign if available + if arg.binding != prettyForm.NEG: + result.append(' + ') + result.append(arg) + return prettyForm(binding=prettyForm.ADD, *stringPict.next(*result)) + + def __truediv__(self, den, slashed=False): + """Make a pretty division; stacked or slashed. + """ + if slashed: + raise NotImplementedError("Can't do slashed fraction yet") + num = self + if num.binding == prettyForm.DIV: + num = stringPict(*num.parens()) + if den.binding == prettyForm.DIV: + den = stringPict(*den.parens()) + + if num.binding==prettyForm.NEG: + num = num.right(" ")[0] + + return prettyForm(binding=prettyForm.DIV, *stringPict.stack( + num, + stringPict.LINE, + den)) + + def __mul__(self, *others): + """Make a pretty multiplication. + Parentheses are needed around +, - and neg. + """ + quantity = { + 'degree': "\N{DEGREE SIGN}" + } + + if len(others) == 0: + return self # We aren't actually multiplying... So nothing to do here. + + # add parens on args that need them + arg = self + if arg.binding > prettyForm.MUL and arg.binding != prettyForm.NEG: + arg = stringPict(*arg.parens()) + result = [arg] + for arg in others: + if arg.picture[0] not in quantity.values(): + result.append(xsym('*')) + #add parentheses for weak binders + if arg.binding > prettyForm.MUL and arg.binding != prettyForm.NEG: + arg = stringPict(*arg.parens()) + result.append(arg) + + len_res = len(result) + for i in range(len_res): + if i < len_res - 1 and result[i] == '-1' and result[i + 1] == xsym('*'): + # substitute -1 by -, like in -1*x -> -x + result.pop(i) + result.pop(i) + result.insert(i, '-') + if result[0][0] == '-': + # if there is a - sign in front of all + # This test was failing to catch a prettyForm.__mul__(prettyForm("-1", 0, 6)) being negative + bin = prettyForm.NEG + if result[0] == '-': + right = result[1] + if right.picture[right.baseline][0] == '-': + result[0] = '- ' + else: + bin = prettyForm.MUL + return prettyForm(binding=bin, *stringPict.next(*result)) + + def __repr__(self): + return "prettyForm(%r,%d,%d)" % ( + '\n'.join(self.picture), + self.baseline, + self.binding) + + def __pow__(self, b): + """Make a pretty power. + """ + a = self + use_inline_func_form = False + if b.binding == prettyForm.POW: + b = stringPict(*b.parens()) + if a.binding > prettyForm.FUNC: + a = stringPict(*a.parens()) + elif a.binding == prettyForm.FUNC: + # heuristic for when to use inline power + if b.height() > 1: + a = stringPict(*a.parens()) + else: + use_inline_func_form = True + + if use_inline_func_form: + # 2 + # sin + + (x) + b.baseline = a.prettyFunc.baseline + b.height() + func = stringPict(*a.prettyFunc.right(b)) + return prettyForm(*func.right(a.prettyArgs)) + else: + # 2 <-- top + # (x+y) <-- bot + top = stringPict(*b.left(' '*a.width())) + bot = stringPict(*a.right(' '*b.width())) + + return prettyForm(binding=prettyForm.POW, *bot.above(top)) + + simpleFunctions = ["sin", "cos", "tan"] + + @staticmethod + def apply(function, *args): + """Functions of one or more variables. + """ + if function in prettyForm.simpleFunctions: + #simple function: use only space if possible + assert len( + args) == 1, "Simple function %s must have 1 argument" % function + arg = args[0].__pretty__() + if arg.binding <= prettyForm.DIV: + #optimization: no parentheses necessary + return prettyForm(binding=prettyForm.FUNC, *arg.left(function + ' ')) + argumentList = [] + for arg in args: + argumentList.append(',') + argumentList.append(arg.__pretty__()) + argumentList = stringPict(*stringPict.next(*argumentList[1:])) + argumentList = stringPict(*argumentList.parens()) + return prettyForm(binding=prettyForm.ATOM, *argumentList.left(function)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/printing/pretty/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/pretty/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df823bdf01e83c3ad5f6b66a5bd0b858a62602b6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/pretty/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pretty/tests/test_pretty.py b/venv/lib/python3.10/site-packages/sympy/printing/pretty/tests/test_pretty.py new file mode 100644 index 0000000000000000000000000000000000000000..1cca79bd1dc5c3ba81483c8fe2e87c35926d1b94 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/pretty/tests/test_pretty.py @@ -0,0 +1,7972 @@ +# -*- coding: utf-8 -*- +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import (Dict, Tuple) +from sympy.core.function import (Derivative, Function, Lambda, Subs) +from sympy.core.mul import Mul +from sympy.core import (EulerGamma, GoldenRatio, Catalan) +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.power import Pow +from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import conjugate +from sympy.functions.elementary.exponential import LambertW +from sympy.functions.special.bessel import (airyai, airyaiprime, airybi, airybiprime) +from sympy.functions.special.delta_functions import Heaviside +from sympy.functions.special.error_functions import (fresnelc, fresnels) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.functions.special.zeta_functions import dirichlet_eta +from sympy.geometry.line import (Ray, Segment) +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import (And, Equivalent, ITE, Implies, Nand, Nor, Not, Or, Xor) +from sympy.matrices.dense import (Matrix, diag) +from sympy.matrices.expressions.slice import MatrixSlice +from sympy.matrices.expressions.trace import Trace +from sympy.polys.domains.finitefield import FF +from sympy.polys.domains.integerring import ZZ +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.domains.realfield import RR +from sympy.polys.orderings import (grlex, ilex) +from sympy.polys.polytools import groebner +from sympy.polys.rootoftools import (RootSum, rootof) +from sympy.series.formal import fps +from sympy.series.fourier import fourier_series +from sympy.series.limits import Limit +from sympy.series.order import O +from sympy.series.sequences import (SeqAdd, SeqFormula, SeqMul, SeqPer) +from sympy.sets.contains import Contains +from sympy.sets.fancysets import Range +from sympy.sets.sets import (Complement, FiniteSet, Intersection, Interval, Union) +from sympy.codegen.ast import (Assignment, AddAugmentedAssignment, + SubAugmentedAssignment, MulAugmentedAssignment, DivAugmentedAssignment, ModAugmentedAssignment) +from sympy.core.expr import UnevaluatedExpr +from sympy.physics.quantum.trace import Tr + +from sympy.functions import (Abs, Chi, Ci, Ei, KroneckerDelta, + Piecewise, Shi, Si, atan2, beta, binomial, catalan, ceiling, cos, + euler, exp, expint, factorial, factorial2, floor, gamma, hyper, log, + meijerg, sin, sqrt, subfactorial, tan, uppergamma, lerchphi, polylog, + elliptic_k, elliptic_f, elliptic_e, elliptic_pi, DiracDelta, bell, + bernoulli, fibonacci, tribonacci, lucas, stieltjes, mathieuc, mathieus, + mathieusprime, mathieucprime) + +from sympy.matrices import (Adjoint, Inverse, MatrixSymbol, Transpose, + KroneckerProduct, BlockMatrix, OneMatrix, ZeroMatrix) +from sympy.matrices.expressions import hadamard_power + +from sympy.physics import mechanics +from sympy.physics.control.lti import (TransferFunction, Feedback, TransferFunctionMatrix, + Series, Parallel, MIMOSeries, MIMOParallel, MIMOFeedback, StateSpace) +from sympy.physics.units import joule, degree +from sympy.printing.pretty import pprint, pretty as xpretty +from sympy.printing.pretty.pretty_symbology import center_accent, is_combining, center +from sympy.sets.conditionset import ConditionSet + +from sympy.sets import ImageSet, ProductSet +from sympy.sets.setexpr import SetExpr +from sympy.stats.crv_types import Normal +from sympy.stats.symbolic_probability import (Covariance, Expectation, + Probability, Variance) +from sympy.tensor.array import (ImmutableDenseNDimArray, ImmutableSparseNDimArray, + MutableDenseNDimArray, MutableSparseNDimArray, tensorproduct) +from sympy.tensor.functions import TensorProduct +from sympy.tensor.tensor import (TensorIndexType, tensor_indices, TensorHead, + TensorElement, tensor_heads) + +from sympy.testing.pytest import raises, _both_exp_pow, warns_deprecated_sympy + +from sympy.vector import CoordSys3D, Gradient, Curl, Divergence, Dot, Cross, Laplacian + + + +import sympy as sym +class lowergamma(sym.lowergamma): + pass # testing notation inheritance by a subclass with same name + +a, b, c, d, x, y, z, k, n, s, p = symbols('a,b,c,d,x,y,z,k,n,s,p') +f = Function("f") +th = Symbol('theta') +ph = Symbol('phi') + +""" +Expressions whose pretty-printing is tested here: +(A '#' to the right of an expression indicates that its various acceptable +orderings are accounted for by the tests.) + + +BASIC EXPRESSIONS: + +oo +(x**2) +1/x +y*x**-2 +x**Rational(-5,2) +(-2)**x +Pow(3, 1, evaluate=False) +(x**2 + x + 1) # +1-x # +1-2*x # +x/y +-x/y +(x+2)/y # +(1+x)*y #3 +-5*x/(x+10) # correct placement of negative sign +1 - Rational(3,2)*(x+1) +-(-x + 5)*(-x - 2*sqrt(2) + 5) - (-y + 5)*(-y + 5) # issue 5524 + + +ORDERING: + +x**2 + x + 1 +1 - x +1 - 2*x +2*x**4 + y**2 - x**2 + y**3 + + +RELATIONAL: + +Eq(x, y) +Lt(x, y) +Gt(x, y) +Le(x, y) +Ge(x, y) +Ne(x/(y+1), y**2) # + + +RATIONAL NUMBERS: + +y*x**-2 +y**Rational(3,2) * x**Rational(-5,2) +sin(x)**3/tan(x)**2 + + +FUNCTIONS (ABS, CONJ, EXP, FUNCTION BRACES, FACTORIAL, FLOOR, CEILING): + +(2*x + exp(x)) # +Abs(x) +Abs(x/(x**2+1)) # +Abs(1 / (y - Abs(x))) +factorial(n) +factorial(2*n) +subfactorial(n) +subfactorial(2*n) +factorial(factorial(factorial(n))) +factorial(n+1) # +conjugate(x) +conjugate(f(x+1)) # +f(x) +f(x, y) +f(x/(y+1), y) # +f(x**x**x**x**x**x) +sin(x)**2 +conjugate(a+b*I) +conjugate(exp(a+b*I)) +conjugate( f(1 + conjugate(f(x))) ) # +f(x/(y+1), y) # denom of first arg +floor(1 / (y - floor(x))) +ceiling(1 / (y - ceiling(x))) + + +SQRT: + +sqrt(2) +2**Rational(1,3) +2**Rational(1,1000) +sqrt(x**2 + 1) +(1 + sqrt(5))**Rational(1,3) +2**(1/x) +sqrt(2+pi) +(2+(1+x**2)/(2+x))**Rational(1,4)+(1+x**Rational(1,1000))/sqrt(3+x**2) + + +DERIVATIVES: + +Derivative(log(x), x, evaluate=False) +Derivative(log(x), x, evaluate=False) + x # +Derivative(log(x) + x**2, x, y, evaluate=False) +Derivative(2*x*y, y, x, evaluate=False) + x**2 # +beta(alpha).diff(alpha) + + +INTEGRALS: + +Integral(log(x), x) +Integral(x**2, x) +Integral((sin(x))**2 / (tan(x))**2) +Integral(x**(2**x), x) +Integral(x**2, (x,1,2)) +Integral(x**2, (x,Rational(1,2),10)) +Integral(x**2*y**2, x,y) +Integral(x**2, (x, None, 1)) +Integral(x**2, (x, 1, None)) +Integral(sin(th)/cos(ph), (th,0,pi), (ph, 0, 2*pi)) + + +MATRICES: + +Matrix([[x**2+1, 1], [y, x+y]]) # +Matrix([[x/y, y, th], [0, exp(I*k*ph), 1]]) + + +PIECEWISE: + +Piecewise((x,x<1),(x**2,True)) + +ITE: + +ITE(x, y, z) + +SEQUENCES (TUPLES, LISTS, DICTIONARIES): + +() +[] +{} +(1/x,) +[x**2, 1/x, x, y, sin(th)**2/cos(ph)**2] +(x**2, 1/x, x, y, sin(th)**2/cos(ph)**2) +{x: sin(x)} +{1/x: 1/y, x: sin(x)**2} # +[x**2] +(x**2,) +{x**2: 1} + + +LIMITS: + +Limit(x, x, oo) +Limit(x**2, x, 0) +Limit(1/x, x, 0) +Limit(sin(x)/x, x, 0) + + +UNITS: + +joule => kg*m**2/s + + +SUBS: + +Subs(f(x), x, ph**2) +Subs(f(x).diff(x), x, 0) +Subs(f(x).diff(x)/y, (x, y), (0, Rational(1, 2))) + + +ORDER: + +O(1) +O(1/x) +O(x**2 + y**2) + +""" + + +def pretty(expr, order=None): + """ASCII pretty-printing""" + return xpretty(expr, order=order, use_unicode=False, wrap_line=False) + + +def upretty(expr, order=None): + """Unicode pretty-printing""" + return xpretty(expr, order=order, use_unicode=True, wrap_line=False) + + +def test_pretty_ascii_str(): + assert pretty( 'xxx' ) == 'xxx' + assert pretty( "xxx" ) == 'xxx' + assert pretty( 'xxx\'xxx' ) == 'xxx\'xxx' + assert pretty( 'xxx"xxx' ) == 'xxx\"xxx' + assert pretty( 'xxx\"xxx' ) == 'xxx\"xxx' + assert pretty( "xxx'xxx" ) == 'xxx\'xxx' + assert pretty( "xxx\'xxx" ) == 'xxx\'xxx' + assert pretty( "xxx\"xxx" ) == 'xxx\"xxx' + assert pretty( "xxx\"xxx\'xxx" ) == 'xxx"xxx\'xxx' + assert pretty( "xxx\nxxx" ) == 'xxx\nxxx' + + +def test_pretty_unicode_str(): + assert pretty( 'xxx' ) == 'xxx' + assert pretty( 'xxx' ) == 'xxx' + assert pretty( 'xxx\'xxx' ) == 'xxx\'xxx' + assert pretty( 'xxx"xxx' ) == 'xxx\"xxx' + assert pretty( 'xxx\"xxx' ) == 'xxx\"xxx' + assert pretty( "xxx'xxx" ) == 'xxx\'xxx' + assert pretty( "xxx\'xxx" ) == 'xxx\'xxx' + assert pretty( "xxx\"xxx" ) == 'xxx\"xxx' + assert pretty( "xxx\"xxx\'xxx" ) == 'xxx"xxx\'xxx' + assert pretty( "xxx\nxxx" ) == 'xxx\nxxx' + + +def test_upretty_greek(): + assert upretty( oo ) == '∞' + assert upretty( Symbol('alpha^+_1') ) == 'α⁺₁' + assert upretty( Symbol('beta') ) == 'β' + assert upretty(Symbol('lambda')) == 'λ' + + +def test_upretty_multiindex(): + assert upretty( Symbol('beta12') ) == 'β₁₂' + assert upretty( Symbol('Y00') ) == 'Y₀₀' + assert upretty( Symbol('Y_00') ) == 'Y₀₀' + assert upretty( Symbol('F^+-') ) == 'F⁺⁻' + + +def test_upretty_sub_super(): + assert upretty( Symbol('beta_1_2') ) == 'β₁ ₂' + assert upretty( Symbol('beta^1^2') ) == 'β¹ ²' + assert upretty( Symbol('beta_1^2') ) == 'β²₁' + assert upretty( Symbol('beta_10_20') ) == 'β₁₀ ₂₀' + assert upretty( Symbol('beta_ax_gamma^i') ) == 'βⁱₐₓ ᵧ' + assert upretty( Symbol("F^1^2_3_4") ) == 'F¹ ²₃ ₄' + assert upretty( Symbol("F_1_2^3^4") ) == 'F³ ⁴₁ ₂' + assert upretty( Symbol("F_1_2_3_4") ) == 'F₁ ₂ ₃ ₄' + assert upretty( Symbol("F^1^2^3^4") ) == 'F¹ ² ³ ⁴' + + +def test_upretty_subs_missing_in_24(): + assert upretty( Symbol('F_beta') ) == 'Fᵦ' + assert upretty( Symbol('F_gamma') ) == 'Fᵧ' + assert upretty( Symbol('F_rho') ) == 'Fᵨ' + assert upretty( Symbol('F_phi') ) == 'Fᵩ' + assert upretty( Symbol('F_chi') ) == 'Fᵪ' + + assert upretty( Symbol('F_a') ) == 'Fₐ' + assert upretty( Symbol('F_e') ) == 'Fₑ' + assert upretty( Symbol('F_i') ) == 'Fᵢ' + assert upretty( Symbol('F_o') ) == 'Fₒ' + assert upretty( Symbol('F_u') ) == 'Fᵤ' + assert upretty( Symbol('F_r') ) == 'Fᵣ' + assert upretty( Symbol('F_v') ) == 'Fᵥ' + assert upretty( Symbol('F_x') ) == 'Fₓ' + + +def test_missing_in_2X_issue_9047(): + assert upretty( Symbol('F_h') ) == 'Fₕ' + assert upretty( Symbol('F_k') ) == 'Fₖ' + assert upretty( Symbol('F_l') ) == 'Fₗ' + assert upretty( Symbol('F_m') ) == 'Fₘ' + assert upretty( Symbol('F_n') ) == 'Fₙ' + assert upretty( Symbol('F_p') ) == 'Fₚ' + assert upretty( Symbol('F_s') ) == 'Fₛ' + assert upretty( Symbol('F_t') ) == 'Fₜ' + + +def test_upretty_modifiers(): + # Accents + assert upretty( Symbol('Fmathring') ) == 'F̊' + assert upretty( Symbol('Fddddot') ) == 'F⃜' + assert upretty( Symbol('Fdddot') ) == 'F⃛' + assert upretty( Symbol('Fddot') ) == 'F̈' + assert upretty( Symbol('Fdot') ) == 'Ḟ' + assert upretty( Symbol('Fcheck') ) == 'F̌' + assert upretty( Symbol('Fbreve') ) == 'F̆' + assert upretty( Symbol('Facute') ) == 'F́' + assert upretty( Symbol('Fgrave') ) == 'F̀' + assert upretty( Symbol('Ftilde') ) == 'F̃' + assert upretty( Symbol('Fhat') ) == 'F̂' + assert upretty( Symbol('Fbar') ) == 'F̅' + assert upretty( Symbol('Fvec') ) == 'F⃗' + assert upretty( Symbol('Fprime') ) == 'F′' + assert upretty( Symbol('Fprm') ) == 'F′' + # No faces are actually implemented, but test to make sure the modifiers are stripped + assert upretty( Symbol('Fbold') ) == 'Fbold' + assert upretty( Symbol('Fbm') ) == 'Fbm' + assert upretty( Symbol('Fcal') ) == 'Fcal' + assert upretty( Symbol('Fscr') ) == 'Fscr' + assert upretty( Symbol('Ffrak') ) == 'Ffrak' + # Brackets + assert upretty( Symbol('Fnorm') ) == '‖F‖' + assert upretty( Symbol('Favg') ) == '⟨F⟩' + assert upretty( Symbol('Fabs') ) == '|F|' + assert upretty( Symbol('Fmag') ) == '|F|' + # Combinations + assert upretty( Symbol('xvecdot') ) == 'x⃗̇' + assert upretty( Symbol('xDotVec') ) == 'ẋ⃗' + assert upretty( Symbol('xHATNorm') ) == '‖x̂‖' + assert upretty( Symbol('xMathring_yCheckPRM__zbreveAbs') ) == 'x̊_y̌′__|z̆|' + assert upretty( Symbol('alphadothat_nVECDOT__tTildePrime') ) == 'α̇̂_n⃗̇__t̃′' + assert upretty( Symbol('x_dot') ) == 'x_dot' + assert upretty( Symbol('x__dot') ) == 'x__dot' + + +def test_pretty_Cycle(): + from sympy.combinatorics.permutations import Cycle + assert pretty(Cycle(1, 2)) == '(1 2)' + assert pretty(Cycle(2)) == '(2)' + assert pretty(Cycle(1, 3)(4, 5)) == '(1 3)(4 5)' + assert pretty(Cycle()) == '()' + + +def test_pretty_Permutation(): + from sympy.combinatorics.permutations import Permutation + p1 = Permutation(1, 2)(3, 4) + assert xpretty(p1, perm_cyclic=True, use_unicode=True) == "(1 2)(3 4)" + assert xpretty(p1, perm_cyclic=True, use_unicode=False) == "(1 2)(3 4)" + assert xpretty(p1, perm_cyclic=False, use_unicode=True) == \ + '⎛0 1 2 3 4⎞\n'\ + '⎝0 2 1 4 3⎠' + assert xpretty(p1, perm_cyclic=False, use_unicode=False) == \ + "/0 1 2 3 4\\\n"\ + "\\0 2 1 4 3/" + + with warns_deprecated_sympy(): + old_print_cyclic = Permutation.print_cyclic + Permutation.print_cyclic = False + assert xpretty(p1, use_unicode=True) == \ + '⎛0 1 2 3 4⎞\n'\ + '⎝0 2 1 4 3⎠' + assert xpretty(p1, use_unicode=False) == \ + "/0 1 2 3 4\\\n"\ + "\\0 2 1 4 3/" + Permutation.print_cyclic = old_print_cyclic + + +def test_pretty_basic(): + assert pretty( -Rational(1)/2 ) == '-1/2' + assert pretty( -Rational(13)/22 ) == \ +"""\ +-13 \n\ +----\n\ + 22 \ +""" + expr = oo + ascii_str = \ +"""\ +oo\ +""" + ucode_str = \ +"""\ +∞\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (x**2) + ascii_str = \ +"""\ + 2\n\ +x \ +""" + ucode_str = \ +"""\ + 2\n\ +x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = 1/x + ascii_str = \ +"""\ +1\n\ +-\n\ +x\ +""" + ucode_str = \ +"""\ +1\n\ +─\n\ +x\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + # not the same as 1/x + expr = x**-1.0 + ascii_str = \ +"""\ + -1.0\n\ +x \ +""" + ucode_str = \ +"""\ + -1.0\n\ +x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + # see issue #2860 + expr = Pow(S(2), -1.0, evaluate=False) + ascii_str = \ +"""\ + -1.0\n\ +2 \ +""" + ucode_str = \ +"""\ + -1.0\n\ +2 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = y*x**-2 + ascii_str = \ +"""\ +y \n\ +--\n\ + 2\n\ +x \ +""" + ucode_str = \ +"""\ +y \n\ +──\n\ + 2\n\ +x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + #see issue #14033 + expr = x**Rational(1, 3) + ascii_str = \ +"""\ + 1/3\n\ +x \ +""" + ucode_str = \ +"""\ + 1/3\n\ +x \ +""" + assert xpretty(expr, use_unicode=False, wrap_line=False,\ + root_notation = False) == ascii_str + assert xpretty(expr, use_unicode=True, wrap_line=False,\ + root_notation = False) == ucode_str + + expr = x**Rational(-5, 2) + ascii_str = \ +"""\ + 1 \n\ +----\n\ + 5/2\n\ +x \ +""" + ucode_str = \ +"""\ + 1 \n\ +────\n\ + 5/2\n\ +x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (-2)**x + ascii_str = \ +"""\ + x\n\ +(-2) \ +""" + ucode_str = \ +"""\ + x\n\ +(-2) \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + # See issue 4923 + expr = Pow(3, 1, evaluate=False) + ascii_str = \ +"""\ + 1\n\ +3 \ +""" + ucode_str = \ +"""\ + 1\n\ +3 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (x**2 + x + 1) + ascii_str_1 = \ +"""\ + 2\n\ +1 + x + x \ +""" + ascii_str_2 = \ +"""\ + 2 \n\ +x + x + 1\ +""" + ascii_str_3 = \ +"""\ + 2 \n\ +x + 1 + x\ +""" + ucode_str_1 = \ +"""\ + 2\n\ +1 + x + x \ +""" + ucode_str_2 = \ +"""\ + 2 \n\ +x + x + 1\ +""" + ucode_str_3 = \ +"""\ + 2 \n\ +x + 1 + x\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2, ascii_str_3] + assert upretty(expr) in [ucode_str_1, ucode_str_2, ucode_str_3] + + expr = 1 - x + ascii_str_1 = \ +"""\ +1 - x\ +""" + ascii_str_2 = \ +"""\ +-x + 1\ +""" + ucode_str_1 = \ +"""\ +1 - x\ +""" + ucode_str_2 = \ +"""\ +-x + 1\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = 1 - 2*x + ascii_str_1 = \ +"""\ +1 - 2*x\ +""" + ascii_str_2 = \ +"""\ +-2*x + 1\ +""" + ucode_str_1 = \ +"""\ +1 - 2⋅x\ +""" + ucode_str_2 = \ +"""\ +-2⋅x + 1\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = x/y + ascii_str = \ +"""\ +x\n\ +-\n\ +y\ +""" + ucode_str = \ +"""\ +x\n\ +─\n\ +y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = -x/y + ascii_str = \ +"""\ +-x \n\ +---\n\ + y \ +""" + ucode_str = \ +"""\ +-x \n\ +───\n\ + y \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (x + 2)/y + ascii_str_1 = \ +"""\ +2 + x\n\ +-----\n\ + y \ +""" + ascii_str_2 = \ +"""\ +x + 2\n\ +-----\n\ + y \ +""" + ucode_str_1 = \ +"""\ +2 + x\n\ +─────\n\ + y \ +""" + ucode_str_2 = \ +"""\ +x + 2\n\ +─────\n\ + y \ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = (1 + x)*y + ascii_str_1 = \ +"""\ +y*(1 + x)\ +""" + ascii_str_2 = \ +"""\ +(1 + x)*y\ +""" + ascii_str_3 = \ +"""\ +y*(x + 1)\ +""" + ucode_str_1 = \ +"""\ +y⋅(1 + x)\ +""" + ucode_str_2 = \ +"""\ +(1 + x)⋅y\ +""" + ucode_str_3 = \ +"""\ +y⋅(x + 1)\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2, ascii_str_3] + assert upretty(expr) in [ucode_str_1, ucode_str_2, ucode_str_3] + + # Test for correct placement of the negative sign + expr = -5*x/(x + 10) + ascii_str_1 = \ +"""\ +-5*x \n\ +------\n\ +10 + x\ +""" + ascii_str_2 = \ +"""\ +-5*x \n\ +------\n\ +x + 10\ +""" + ucode_str_1 = \ +"""\ +-5⋅x \n\ +──────\n\ +10 + x\ +""" + ucode_str_2 = \ +"""\ +-5⋅x \n\ +──────\n\ +x + 10\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = -S.Half - 3*x + ascii_str = \ +"""\ +-3*x - 1/2\ +""" + ucode_str = \ +"""\ +-3⋅x - 1/2\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = S.Half - 3*x + ascii_str = \ +"""\ +1/2 - 3*x\ +""" + ucode_str = \ +"""\ +1/2 - 3⋅x\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = -S.Half - 3*x/2 + ascii_str = \ +"""\ + 3*x 1\n\ +- --- - -\n\ + 2 2\ +""" + ucode_str = \ +"""\ + 3⋅x 1\n\ +- ─── - ─\n\ + 2 2\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = S.Half - 3*x/2 + ascii_str = \ +"""\ +1 3*x\n\ +- - ---\n\ +2 2 \ +""" + ucode_str = \ +"""\ +1 3⋅x\n\ +─ - ───\n\ +2 2 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_negative_fractions(): + expr = -x/y + ascii_str =\ +"""\ +-x \n\ +---\n\ + y \ +""" + ucode_str =\ +"""\ +-x \n\ +───\n\ + y \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = -x*z/y + ascii_str =\ +"""\ +-x*z \n\ +-----\n\ + y \ +""" + ucode_str =\ +"""\ +-x⋅z \n\ +─────\n\ + y \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = x**2/y + ascii_str =\ +"""\ + 2\n\ +x \n\ +--\n\ +y \ +""" + ucode_str =\ +"""\ + 2\n\ +x \n\ +──\n\ +y \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = -x**2/y + ascii_str =\ +"""\ + 2 \n\ +-x \n\ +----\n\ + y \ +""" + ucode_str =\ +"""\ + 2 \n\ +-x \n\ +────\n\ + y \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = -x/(y*z) + ascii_str =\ +"""\ +-x \n\ +---\n\ +y*z\ +""" + ucode_str =\ +"""\ +-x \n\ +───\n\ +y⋅z\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = -a/y**2 + ascii_str =\ +"""\ +-a \n\ +---\n\ + 2 \n\ +y \ +""" + ucode_str =\ +"""\ +-a \n\ +───\n\ + 2 \n\ +y \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = y**(-a/b) + ascii_str =\ +"""\ + -a \n\ + ---\n\ + b \n\ +y \ +""" + ucode_str =\ +"""\ + -a \n\ + ───\n\ + b \n\ +y \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = -1/y**2 + ascii_str =\ +"""\ +-1 \n\ +---\n\ + 2 \n\ +y \ +""" + ucode_str =\ +"""\ +-1 \n\ +───\n\ + 2 \n\ +y \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = -10/b**2 + ascii_str =\ +"""\ +-10 \n\ +----\n\ + 2 \n\ + b \ +""" + ucode_str =\ +"""\ +-10 \n\ +────\n\ + 2 \n\ + b \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + expr = Rational(-200, 37) + ascii_str =\ +"""\ +-200 \n\ +-----\n\ + 37 \ +""" + ucode_str =\ +"""\ +-200 \n\ +─────\n\ + 37 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_Mul(): + expr = Mul(0, 1, evaluate=False) + assert pretty(expr) == "0*1" + assert upretty(expr) == "0⋅1" + expr = Mul(1, 0, evaluate=False) + assert pretty(expr) == "1*0" + assert upretty(expr) == "1⋅0" + expr = Mul(1, 1, evaluate=False) + assert pretty(expr) == "1*1" + assert upretty(expr) == "1⋅1" + expr = Mul(1, 1, 1, evaluate=False) + assert pretty(expr) == "1*1*1" + assert upretty(expr) == "1⋅1⋅1" + expr = Mul(1, 2, evaluate=False) + assert pretty(expr) == "1*2" + assert upretty(expr) == "1⋅2" + expr = Add(0, 1, evaluate=False) + assert pretty(expr) == "0 + 1" + assert upretty(expr) == "0 + 1" + expr = Mul(1, 1, 2, evaluate=False) + assert pretty(expr) == "1*1*2" + assert upretty(expr) == "1⋅1⋅2" + expr = Add(0, 0, 1, evaluate=False) + assert pretty(expr) == "0 + 0 + 1" + assert upretty(expr) == "0 + 0 + 1" + expr = Mul(1, -1, evaluate=False) + assert pretty(expr) == "1*-1" + assert upretty(expr) == "1⋅-1" + expr = Mul(1.0, x, evaluate=False) + assert pretty(expr) == "1.0*x" + assert upretty(expr) == "1.0⋅x" + expr = Mul(1, 1, 2, 3, x, evaluate=False) + assert pretty(expr) == "1*1*2*3*x" + assert upretty(expr) == "1⋅1⋅2⋅3⋅x" + expr = Mul(-1, 1, evaluate=False) + assert pretty(expr) == "-1*1" + assert upretty(expr) == "-1⋅1" + expr = Mul(4, 3, 2, 1, 0, y, x, evaluate=False) + assert pretty(expr) == "4*3*2*1*0*y*x" + assert upretty(expr) == "4⋅3⋅2⋅1⋅0⋅y⋅x" + expr = Mul(4, 3, 2, 1+z, 0, y, x, evaluate=False) + assert pretty(expr) == "4*3*2*(z + 1)*0*y*x" + assert upretty(expr) == "4⋅3⋅2⋅(z + 1)⋅0⋅y⋅x" + expr = Mul(Rational(2, 3), Rational(5, 7), evaluate=False) + assert pretty(expr) == "2/3*5/7" + assert upretty(expr) == "2/3⋅5/7" + expr = Mul(x + y, Rational(1, 2), evaluate=False) + assert pretty(expr) == "(x + y)*1/2" + assert upretty(expr) == "(x + y)⋅1/2" + expr = Mul(Rational(1, 2), x + y, evaluate=False) + assert pretty(expr) == "x + y\n-----\n 2 " + assert upretty(expr) == "x + y\n─────\n 2 " + expr = Mul(S.One, x + y, evaluate=False) + assert pretty(expr) == "1*(x + y)" + assert upretty(expr) == "1⋅(x + y)" + expr = Mul(x - y, S.One, evaluate=False) + assert pretty(expr) == "(x - y)*1" + assert upretty(expr) == "(x - y)⋅1" + expr = Mul(Rational(1, 2), x - y, S.One, x + y, evaluate=False) + assert pretty(expr) == "1/2*(x - y)*1*(x + y)" + assert upretty(expr) == "1/2⋅(x - y)⋅1⋅(x + y)" + expr = Mul(x + y, Rational(3, 4), S.One, y - z, evaluate=False) + assert pretty(expr) == "(x + y)*3/4*1*(y - z)" + assert upretty(expr) == "(x + y)⋅3/4⋅1⋅(y - z)" + expr = Mul(x + y, Rational(1, 1), Rational(3, 4), Rational(5, 6),evaluate=False) + assert pretty(expr) == "(x + y)*1*3/4*5/6" + assert upretty(expr) == "(x + y)⋅1⋅3/4⋅5/6" + expr = Mul(Rational(3, 4), x + y, S.One, y - z, evaluate=False) + assert pretty(expr) == "3/4*(x + y)*1*(y - z)" + assert upretty(expr) == "3/4⋅(x + y)⋅1⋅(y - z)" + + +def test_issue_5524(): + assert pretty(-(-x + 5)*(-x - 2*sqrt(2) + 5) - (-y + 5)*(-y + 5)) == \ +"""\ + 2 / ___ \\\n\ +- (5 - y) + (x - 5)*\\-x - 2*\\/ 2 + 5/\ +""" + + assert upretty(-(-x + 5)*(-x - 2*sqrt(2) + 5) - (-y + 5)*(-y + 5)) == \ +"""\ + 2 \n\ +- (5 - y) + (x - 5)⋅(-x - 2⋅√2 + 5)\ +""" + + +def test_pretty_ordering(): + assert pretty(x**2 + x + 1, order='lex') == \ +"""\ + 2 \n\ +x + x + 1\ +""" + assert pretty(x**2 + x + 1, order='rev-lex') == \ +"""\ + 2\n\ +1 + x + x \ +""" + assert pretty(1 - x, order='lex') == '-x + 1' + assert pretty(1 - x, order='rev-lex') == '1 - x' + + assert pretty(1 - 2*x, order='lex') == '-2*x + 1' + assert pretty(1 - 2*x, order='rev-lex') == '1 - 2*x' + + f = 2*x**4 + y**2 - x**2 + y**3 + assert pretty(f, order=None) == \ +"""\ + 4 2 3 2\n\ +2*x - x + y + y \ +""" + assert pretty(f, order='lex') == \ +"""\ + 4 2 3 2\n\ +2*x - x + y + y \ +""" + assert pretty(f, order='rev-lex') == \ +"""\ + 2 3 2 4\n\ +y + y - x + 2*x \ +""" + + expr = x - x**3/6 + x**5/120 + O(x**6) + ascii_str = \ +"""\ + 3 5 \n\ + x x / 6\\\n\ +x - -- + --- + O\\x /\n\ + 6 120 \ +""" + ucode_str = \ +"""\ + 3 5 \n\ + x x ⎛ 6⎞\n\ +x - ── + ─── + O⎝x ⎠\n\ + 6 120 \ +""" + assert pretty(expr, order=None) == ascii_str + assert upretty(expr, order=None) == ucode_str + + assert pretty(expr, order='lex') == ascii_str + assert upretty(expr, order='lex') == ucode_str + + assert pretty(expr, order='rev-lex') == ascii_str + assert upretty(expr, order='rev-lex') == ucode_str + + +def test_EulerGamma(): + assert pretty(EulerGamma) == str(EulerGamma) == "EulerGamma" + assert upretty(EulerGamma) == "γ" + + +def test_GoldenRatio(): + assert pretty(GoldenRatio) == str(GoldenRatio) == "GoldenRatio" + assert upretty(GoldenRatio) == "φ" + + +def test_Catalan(): + assert pretty(Catalan) == upretty(Catalan) == "G" + + +def test_pretty_relational(): + expr = Eq(x, y) + ascii_str = \ +"""\ +x = y\ +""" + ucode_str = \ +"""\ +x = y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Lt(x, y) + ascii_str = \ +"""\ +x < y\ +""" + ucode_str = \ +"""\ +x < y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Gt(x, y) + ascii_str = \ +"""\ +x > y\ +""" + ucode_str = \ +"""\ +x > y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Le(x, y) + ascii_str = \ +"""\ +x <= y\ +""" + ucode_str = \ +"""\ +x ≤ y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Ge(x, y) + ascii_str = \ +"""\ +x >= y\ +""" + ucode_str = \ +"""\ +x ≥ y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Ne(x/(y + 1), y**2) + ascii_str_1 = \ +"""\ + x 2\n\ +----- != y \n\ +1 + y \ +""" + ascii_str_2 = \ +"""\ + x 2\n\ +----- != y \n\ +y + 1 \ +""" + ucode_str_1 = \ +"""\ + x 2\n\ +───── ≠ y \n\ +1 + y \ +""" + ucode_str_2 = \ +"""\ + x 2\n\ +───── ≠ y \n\ +y + 1 \ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + +def test_Assignment(): + expr = Assignment(x, y) + ascii_str = \ +"""\ +x := y\ +""" + ucode_str = \ +"""\ +x := y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_AugmentedAssignment(): + expr = AddAugmentedAssignment(x, y) + ascii_str = \ +"""\ +x += y\ +""" + ucode_str = \ +"""\ +x += y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = SubAugmentedAssignment(x, y) + ascii_str = \ +"""\ +x -= y\ +""" + ucode_str = \ +"""\ +x -= y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = MulAugmentedAssignment(x, y) + ascii_str = \ +"""\ +x *= y\ +""" + ucode_str = \ +"""\ +x *= y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = DivAugmentedAssignment(x, y) + ascii_str = \ +"""\ +x /= y\ +""" + ucode_str = \ +"""\ +x /= y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = ModAugmentedAssignment(x, y) + ascii_str = \ +"""\ +x %= y\ +""" + ucode_str = \ +"""\ +x %= y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_rational(): + expr = y*x**-2 + ascii_str = \ +"""\ +y \n\ +--\n\ + 2\n\ +x \ +""" + ucode_str = \ +"""\ +y \n\ +──\n\ + 2\n\ +x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = y**Rational(3, 2) * x**Rational(-5, 2) + ascii_str = \ +"""\ + 3/2\n\ +y \n\ +----\n\ + 5/2\n\ +x \ +""" + ucode_str = \ +"""\ + 3/2\n\ +y \n\ +────\n\ + 5/2\n\ +x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = sin(x)**3/tan(x)**2 + ascii_str = \ +"""\ + 3 \n\ +sin (x)\n\ +-------\n\ + 2 \n\ +tan (x)\ +""" + ucode_str = \ +"""\ + 3 \n\ +sin (x)\n\ +───────\n\ + 2 \n\ +tan (x)\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +@_both_exp_pow +def test_pretty_functions(): + """Tests for Abs, conjugate, exp, function braces, and factorial.""" + expr = (2*x + exp(x)) + ascii_str_1 = \ +"""\ + x\n\ +2*x + e \ +""" + ascii_str_2 = \ +"""\ + x \n\ +e + 2*x\ +""" + ucode_str_1 = \ +"""\ + x\n\ +2⋅x + ℯ \ +""" + ucode_str_2 = \ +"""\ + x \n\ +ℯ + 2⋅x\ +""" + ucode_str_3 = \ +"""\ + x \n\ +ℯ + 2⋅x\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2, ucode_str_3] + + expr = Abs(x) + ascii_str = \ +"""\ +|x|\ +""" + ucode_str = \ +"""\ +│x│\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Abs(x/(x**2 + 1)) + ascii_str_1 = \ +"""\ +| x |\n\ +|------|\n\ +| 2|\n\ +|1 + x |\ +""" + ascii_str_2 = \ +"""\ +| x |\n\ +|------|\n\ +| 2 |\n\ +|x + 1|\ +""" + ucode_str_1 = \ +"""\ +│ x │\n\ +│──────│\n\ +│ 2│\n\ +│1 + x │\ +""" + ucode_str_2 = \ +"""\ +│ x │\n\ +│──────│\n\ +│ 2 │\n\ +│x + 1│\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = Abs(1 / (y - Abs(x))) + ascii_str = \ +"""\ + 1 \n\ +---------\n\ +|y - |x||\ +""" + ucode_str = \ +"""\ + 1 \n\ +─────────\n\ +│y - │x││\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + n = Symbol('n', integer=True) + expr = factorial(n) + ascii_str = \ +"""\ +n!\ +""" + ucode_str = \ +"""\ +n!\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = factorial(2*n) + ascii_str = \ +"""\ +(2*n)!\ +""" + ucode_str = \ +"""\ +(2⋅n)!\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = factorial(factorial(factorial(n))) + ascii_str = \ +"""\ +((n!)!)!\ +""" + ucode_str = \ +"""\ +((n!)!)!\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = factorial(n + 1) + ascii_str_1 = \ +"""\ +(1 + n)!\ +""" + ascii_str_2 = \ +"""\ +(n + 1)!\ +""" + ucode_str_1 = \ +"""\ +(1 + n)!\ +""" + ucode_str_2 = \ +"""\ +(n + 1)!\ +""" + + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = subfactorial(n) + ascii_str = \ +"""\ +!n\ +""" + ucode_str = \ +"""\ +!n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = subfactorial(2*n) + ascii_str = \ +"""\ +!(2*n)\ +""" + ucode_str = \ +"""\ +!(2⋅n)\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + n = Symbol('n', integer=True) + expr = factorial2(n) + ascii_str = \ +"""\ +n!!\ +""" + ucode_str = \ +"""\ +n!!\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = factorial2(2*n) + ascii_str = \ +"""\ +(2*n)!!\ +""" + ucode_str = \ +"""\ +(2⋅n)!!\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = factorial2(factorial2(factorial2(n))) + ascii_str = \ +"""\ +((n!!)!!)!!\ +""" + ucode_str = \ +"""\ +((n!!)!!)!!\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = factorial2(n + 1) + ascii_str_1 = \ +"""\ +(1 + n)!!\ +""" + ascii_str_2 = \ +"""\ +(n + 1)!!\ +""" + ucode_str_1 = \ +"""\ +(1 + n)!!\ +""" + ucode_str_2 = \ +"""\ +(n + 1)!!\ +""" + + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = 2*binomial(n, k) + ascii_str = \ +"""\ + /n\\\n\ +2*| |\n\ + \\k/\ +""" + ucode_str = \ +"""\ + ⎛n⎞\n\ +2⋅⎜ ⎟\n\ + ⎝k⎠\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = 2*binomial(2*n, k) + ascii_str = \ +"""\ + /2*n\\\n\ +2*| |\n\ + \\ k /\ +""" + ucode_str = \ +"""\ + ⎛2⋅n⎞\n\ +2⋅⎜ ⎟\n\ + ⎝ k ⎠\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = 2*binomial(n**2, k) + ascii_str = \ +"""\ + / 2\\\n\ + |n |\n\ +2*| |\n\ + \\k /\ +""" + ucode_str = \ +"""\ + ⎛ 2⎞\n\ + ⎜n ⎟\n\ +2⋅⎜ ⎟\n\ + ⎝k ⎠\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = catalan(n) + ascii_str = \ +"""\ +C \n\ + n\ +""" + ucode_str = \ +"""\ +C \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = catalan(n) + ascii_str = \ +"""\ +C \n\ + n\ +""" + ucode_str = \ +"""\ +C \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = bell(n) + ascii_str = \ +"""\ +B \n\ + n\ +""" + ucode_str = \ +"""\ +B \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = bernoulli(n) + ascii_str = \ +"""\ +B \n\ + n\ +""" + ucode_str = \ +"""\ +B \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = bernoulli(n, x) + ascii_str = \ +"""\ +B (x)\n\ + n \ +""" + ucode_str = \ +"""\ +B (x)\n\ + n \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = fibonacci(n) + ascii_str = \ +"""\ +F \n\ + n\ +""" + ucode_str = \ +"""\ +F \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = lucas(n) + ascii_str = \ +"""\ +L \n\ + n\ +""" + ucode_str = \ +"""\ +L \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = tribonacci(n) + ascii_str = \ +"""\ +T \n\ + n\ +""" + ucode_str = \ +"""\ +T \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = stieltjes(n) + ascii_str = \ +"""\ +stieltjes \n\ + n\ +""" + ucode_str = \ +"""\ +γ \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = stieltjes(n, x) + ascii_str = \ +"""\ +stieltjes (x)\n\ + n \ +""" + ucode_str = \ +"""\ +γ (x)\n\ + n \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = mathieuc(x, y, z) + ascii_str = 'C(x, y, z)' + ucode_str = 'C(x, y, z)' + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = mathieus(x, y, z) + ascii_str = 'S(x, y, z)' + ucode_str = 'S(x, y, z)' + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = mathieucprime(x, y, z) + ascii_str = "C'(x, y, z)" + ucode_str = "C'(x, y, z)" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = mathieusprime(x, y, z) + ascii_str = "S'(x, y, z)" + ucode_str = "S'(x, y, z)" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = conjugate(x) + ascii_str = \ +"""\ +_\n\ +x\ +""" + ucode_str = \ +"""\ +_\n\ +x\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + f = Function('f') + expr = conjugate(f(x + 1)) + ascii_str_1 = \ +"""\ +________\n\ +f(1 + x)\ +""" + ascii_str_2 = \ +"""\ +________\n\ +f(x + 1)\ +""" + ucode_str_1 = \ +"""\ +________\n\ +f(1 + x)\ +""" + ucode_str_2 = \ +"""\ +________\n\ +f(x + 1)\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = f(x) + ascii_str = \ +"""\ +f(x)\ +""" + ucode_str = \ +"""\ +f(x)\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = f(x, y) + ascii_str = \ +"""\ +f(x, y)\ +""" + ucode_str = \ +"""\ +f(x, y)\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = f(x/(y + 1), y) + ascii_str_1 = \ +"""\ + / x \\\n\ +f|-----, y|\n\ + \\1 + y /\ +""" + ascii_str_2 = \ +"""\ + / x \\\n\ +f|-----, y|\n\ + \\y + 1 /\ +""" + ucode_str_1 = \ +"""\ + ⎛ x ⎞\n\ +f⎜─────, y⎟\n\ + ⎝1 + y ⎠\ +""" + ucode_str_2 = \ +"""\ + ⎛ x ⎞\n\ +f⎜─────, y⎟\n\ + ⎝y + 1 ⎠\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = f(x**x**x**x**x**x) + ascii_str = \ +"""\ + / / / / / x\\\\\\\\\\ + | | | | \\x /|||| + | | | \\x /||| + | | \\x /|| + | \\x /| +f\\x /\ +""" + ucode_str = \ +"""\ + ⎛ ⎛ ⎛ ⎛ ⎛ x⎞⎞⎞⎞⎞ + ⎜ ⎜ ⎜ ⎜ ⎝x ⎠⎟⎟⎟⎟ + ⎜ ⎜ ⎜ ⎝x ⎠⎟⎟⎟ + ⎜ ⎜ ⎝x ⎠⎟⎟ + ⎜ ⎝x ⎠⎟ +f⎝x ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = sin(x)**2 + ascii_str = \ +"""\ + 2 \n\ +sin (x)\ +""" + ucode_str = \ +"""\ + 2 \n\ +sin (x)\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = conjugate(a + b*I) + ascii_str = \ +"""\ +_ _\n\ +a - I*b\ +""" + ucode_str = \ +"""\ +_ _\n\ +a - ⅈ⋅b\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = conjugate(exp(a + b*I)) + ascii_str = \ +"""\ + _ _\n\ + a - I*b\n\ +e \ +""" + ucode_str = \ +"""\ + _ _\n\ + a - ⅈ⋅b\n\ +ℯ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = conjugate( f(1 + conjugate(f(x))) ) + ascii_str_1 = \ +"""\ +___________\n\ + / ____\\\n\ +f\\1 + f(x)/\ +""" + ascii_str_2 = \ +"""\ +___________\n\ + /____ \\\n\ +f\\f(x) + 1/\ +""" + ucode_str_1 = \ +"""\ +___________\n\ + ⎛ ____⎞\n\ +f⎝1 + f(x)⎠\ +""" + ucode_str_2 = \ +"""\ +___________\n\ + ⎛____ ⎞\n\ +f⎝f(x) + 1⎠\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = f(x/(y + 1), y) + ascii_str_1 = \ +"""\ + / x \\\n\ +f|-----, y|\n\ + \\1 + y /\ +""" + ascii_str_2 = \ +"""\ + / x \\\n\ +f|-----, y|\n\ + \\y + 1 /\ +""" + ucode_str_1 = \ +"""\ + ⎛ x ⎞\n\ +f⎜─────, y⎟\n\ + ⎝1 + y ⎠\ +""" + ucode_str_2 = \ +"""\ + ⎛ x ⎞\n\ +f⎜─────, y⎟\n\ + ⎝y + 1 ⎠\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = floor(1 / (y - floor(x))) + ascii_str = \ +"""\ + / 1 \\\n\ +floor|------------|\n\ + \\y - floor(x)/\ +""" + ucode_str = \ +"""\ +⎢ 1 ⎥\n\ +⎢───────⎥\n\ +⎣y - ⌊x⌋⎦\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = ceiling(1 / (y - ceiling(x))) + ascii_str = \ +"""\ + / 1 \\\n\ +ceiling|--------------|\n\ + \\y - ceiling(x)/\ +""" + ucode_str = \ +"""\ +⎡ 1 ⎤\n\ +⎢───────⎥\n\ +⎢y - ⌈x⌉⎥\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = euler(n) + ascii_str = \ +"""\ +E \n\ + n\ +""" + ucode_str = \ +"""\ +E \n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = euler(1/(1 + 1/(1 + 1/n))) + ascii_str = \ +"""\ +E \n\ + 1 \n\ + ---------\n\ + 1 \n\ + 1 + -----\n\ + 1\n\ + 1 + -\n\ + n\ +""" + + ucode_str = \ +"""\ +E \n\ + 1 \n\ + ─────────\n\ + 1 \n\ + 1 + ─────\n\ + 1\n\ + 1 + ─\n\ + n\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = euler(n, x) + ascii_str = \ +"""\ +E (x)\n\ + n \ +""" + ucode_str = \ +"""\ +E (x)\n\ + n \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = euler(n, x/2) + ascii_str = \ +"""\ + /x\\\n\ +E |-|\n\ + n\\2/\ +""" + ucode_str = \ +"""\ + ⎛x⎞\n\ +E ⎜─⎟\n\ + n⎝2⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_sqrt(): + expr = sqrt(2) + ascii_str = \ +"""\ + ___\n\ +\\/ 2 \ +""" + ucode_str = \ +"√2" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = 2**Rational(1, 3) + ascii_str = \ +"""\ +3 ___\n\ +\\/ 2 \ +""" + ucode_str = \ +"""\ +3 ___\n\ +╲╱ 2 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = 2**Rational(1, 1000) + ascii_str = \ +"""\ +1000___\n\ + \\/ 2 \ +""" + ucode_str = \ +"""\ +1000___\n\ + ╲╱ 2 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = sqrt(x**2 + 1) + ascii_str = \ +"""\ + ________\n\ + / 2 \n\ +\\/ x + 1 \ +""" + ucode_str = \ +"""\ + ________\n\ + ╱ 2 \n\ +╲╱ x + 1 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (1 + sqrt(5))**Rational(1, 3) + ascii_str = \ +"""\ + ___________\n\ +3 / ___ \n\ +\\/ 1 + \\/ 5 \ +""" + ucode_str = \ +"""\ +3 ________\n\ +╲╱ 1 + √5 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = 2**(1/x) + ascii_str = \ +"""\ +x ___\n\ +\\/ 2 \ +""" + ucode_str = \ +"""\ +x ___\n\ +╲╱ 2 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = sqrt(2 + pi) + ascii_str = \ +"""\ + ________\n\ +\\/ 2 + pi \ +""" + ucode_str = \ +"""\ + _______\n\ +╲╱ 2 + π \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (2 + ( + 1 + x**2)/(2 + x))**Rational(1, 4) + (1 + x**Rational(1, 1000))/sqrt(3 + x**2) + ascii_str = \ +"""\ + ____________ \n\ + / 2 1000___ \n\ + / x + 1 \\/ x + 1\n\ +4 / 2 + ------ + -----------\n\ +\\/ x + 2 ________\n\ + / 2 \n\ + \\/ x + 3 \ +""" + ucode_str = \ +"""\ + ____________ \n\ + ╱ 2 1000___ \n\ + ╱ x + 1 ╲╱ x + 1\n\ +4 ╱ 2 + ────── + ───────────\n\ +╲╱ x + 2 ________\n\ + ╱ 2 \n\ + ╲╱ x + 3 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_sqrt_char_knob(): + # See PR #9234. + expr = sqrt(2) + ucode_str1 = \ +"""\ + ___\n\ +╲╱ 2 \ +""" + ucode_str2 = \ +"√2" + assert xpretty(expr, use_unicode=True, + use_unicode_sqrt_char=False) == ucode_str1 + assert xpretty(expr, use_unicode=True, + use_unicode_sqrt_char=True) == ucode_str2 + + +def test_pretty_sqrt_longsymbol_no_sqrt_char(): + # Do not use unicode sqrt char for long symbols (see PR #9234). + expr = sqrt(Symbol('C1')) + ucode_str = \ +"""\ + ____\n\ +╲╱ C₁ \ +""" + assert upretty(expr) == ucode_str + + +def test_pretty_KroneckerDelta(): + x, y = symbols("x, y") + expr = KroneckerDelta(x, y) + ascii_str = \ +"""\ +d \n\ + x,y\ +""" + ucode_str = \ +"""\ +δ \n\ + x,y\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_product(): + n, m, k, l = symbols('n m k l') + f = symbols('f', cls=Function) + expr = Product(f((n/3)**2), (n, k**2, l)) + + unicode_str = \ +"""\ + l \n\ +─┬──────┬─ \n\ + │ │ ⎛ 2⎞\n\ + │ │ ⎜n ⎟\n\ + │ │ f⎜──⎟\n\ + │ │ ⎝9 ⎠\n\ + │ │ \n\ + 2 \n\ + n = k """ + ascii_str = \ +"""\ + l \n\ +__________ \n\ + | | / 2\\\n\ + | | |n |\n\ + | | f|--|\n\ + | | \\9 /\n\ + | | \n\ + 2 \n\ + n = k """ + + expr = Product(f((n/3)**2), (n, k**2, l), (l, 1, m)) + + unicode_str = \ +"""\ + m l \n\ +─┬──────┬─ ─┬──────┬─ \n\ + │ │ │ │ ⎛ 2⎞\n\ + │ │ │ │ ⎜n ⎟\n\ + │ │ │ │ f⎜──⎟\n\ + │ │ │ │ ⎝9 ⎠\n\ + │ │ │ │ \n\ + l = 1 2 \n\ + n = k """ + ascii_str = \ +"""\ + m l \n\ +__________ __________ \n\ + | | | | / 2\\\n\ + | | | | |n |\n\ + | | | | f|--|\n\ + | | | | \\9 /\n\ + | | | | \n\ + l = 1 2 \n\ + n = k """ + + assert pretty(expr) == ascii_str + assert upretty(expr) == unicode_str + + +def test_pretty_Lambda(): + # S.IdentityFunction is a special case + expr = Lambda(y, y) + assert pretty(expr) == "x -> x" + assert upretty(expr) == "x ↦ x" + + expr = Lambda(x, x+1) + assert pretty(expr) == "x -> x + 1" + assert upretty(expr) == "x ↦ x + 1" + + expr = Lambda(x, x**2) + ascii_str = \ +"""\ + 2\n\ +x -> x \ +""" + ucode_str = \ +"""\ + 2\n\ +x ↦ x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Lambda(x, x**2)**2 + ascii_str = \ +"""\ + 2 +/ 2\\ \n\ +\\x -> x / \ +""" + ucode_str = \ +"""\ + 2 +⎛ 2⎞ \n\ +⎝x ↦ x ⎠ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Lambda((x, y), x) + ascii_str = "(x, y) -> x" + ucode_str = "(x, y) ↦ x" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Lambda((x, y), x**2) + ascii_str = \ +"""\ + 2\n\ +(x, y) -> x \ +""" + ucode_str = \ +"""\ + 2\n\ +(x, y) ↦ x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Lambda(((x, y),), x**2) + ascii_str = \ +"""\ + 2\n\ +((x, y),) -> x \ +""" + ucode_str = \ +"""\ + 2\n\ +((x, y),) ↦ x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_TransferFunction(): + tf1 = TransferFunction(s - 1, s + 1, s) + assert upretty(tf1) == "s - 1\n─────\ns + 1" + tf2 = TransferFunction(2*s + 1, 3 - p, s) + assert upretty(tf2) == "2⋅s + 1\n───────\n 3 - p " + tf3 = TransferFunction(p, p + 1, p) + assert upretty(tf3) == " p \n─────\np + 1" + + +def test_pretty_Series(): + tf1 = TransferFunction(x + y, x - 2*y, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(x**2 + y, y - x, y) + tf4 = TransferFunction(2, 3, y) + + tfm1 = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]]) + tfm2 = TransferFunctionMatrix([[tf3], [-tf4]]) + tfm3 = TransferFunctionMatrix([[tf1, -tf2, -tf3], [tf3, -tf4, tf2]]) + tfm4 = TransferFunctionMatrix([[tf1, tf2], [tf3, -tf4], [-tf2, -tf1]]) + tfm5 = TransferFunctionMatrix([[-tf2, -tf1], [tf4, -tf3], [tf1, tf2]]) + + expected1 = \ +"""\ + ⎛ 2 ⎞\n\ +⎛ x + y ⎞ ⎜x + y⎟\n\ +⎜───────⎟⋅⎜──────⎟\n\ +⎝x - 2⋅y⎠ ⎝-x + y⎠\ +""" + expected2 = \ +"""\ +⎛-x + y⎞ ⎛-x - y ⎞\n\ +⎜──────⎟⋅⎜───────⎟\n\ +⎝x + y ⎠ ⎝x - 2⋅y⎠\ +""" + expected3 = \ +"""\ +⎛ 2 ⎞ \n\ +⎜x + y⎟ ⎛ x + y ⎞ ⎛-x - y x - y⎞\n\ +⎜──────⎟⋅⎜───────⎟⋅⎜─────── + ─────⎟\n\ +⎝-x + y⎠ ⎝x - 2⋅y⎠ ⎝x - 2⋅y x + y⎠\ +""" + expected4 = \ +"""\ + ⎛ 2 ⎞\n\ +⎛ x + y x - y⎞ ⎜x - y x + y⎟\n\ +⎜─────── + ─────⎟⋅⎜───── + ──────⎟\n\ +⎝x - 2⋅y x + y⎠ ⎝x + y -x + y⎠\ +""" + expected5 = \ +"""\ +⎡ x + y x - y⎤ ⎡ 2 ⎤ \n\ +⎢─────── ─────⎥ ⎢x + y⎥ \n\ +⎢x - 2⋅y x + y⎥ ⎢──────⎥ \n\ +⎢ ⎥ ⎢-x + y⎥ \n\ +⎢ 2 ⎥ ⋅⎢ ⎥ \n\ +⎢x + y 2 ⎥ ⎢ -2 ⎥ \n\ +⎢────── ─ ⎥ ⎢ ─── ⎥ \n\ +⎣-x + y 3 ⎦τ ⎣ 3 ⎦τ\ +""" + expected6 = \ +"""\ + ⎛⎡ x + y x - y ⎤ ⎡ x - y x + y ⎤ ⎞\n\ + ⎜⎢─────── ───── ⎥ ⎢ ───── ───────⎥ ⎟\n\ +⎡ x + y x - y⎤ ⎡ 2 ⎤ ⎜⎢x - 2⋅y x + y ⎥ ⎢ x + y x - 2⋅y⎥ ⎟\n\ +⎢─────── ─────⎥ ⎢ x + y -x + y - x - y⎥ ⎜⎢ ⎥ ⎢ ⎥ ⎟\n\ +⎢x - 2⋅y x + y⎥ ⎢─────── ────── ────────⎥ ⎜⎢ 2 ⎥ ⎢ 2 ⎥ ⎟\n\ +⎢ ⎥ ⎢x - 2⋅y x + y -x + y ⎥ ⎜⎢x + y -2 ⎥ ⎢ -2 x + y ⎥ ⎟\n\ +⎢ 2 ⎥ ⋅⎢ ⎥ ⋅⎜⎢────── ─── ⎥ + ⎢ ─── ────── ⎥ ⎟\n\ +⎢x + y 2 ⎥ ⎢ 2 ⎥ ⎜⎢-x + y 3 ⎥ ⎢ 3 -x + y ⎥ ⎟\n\ +⎢────── ─ ⎥ ⎢x + y -2 x - y ⎥ ⎜⎢ ⎥ ⎢ ⎥ ⎟\n\ +⎣-x + y 3 ⎦τ ⎢────── ─── ───── ⎥ ⎜⎢-x + y -x - y ⎥ ⎢-x - y -x + y ⎥ ⎟\n\ + ⎣-x + y 3 x + y ⎦τ ⎜⎢────── ───────⎥ ⎢─────── ────── ⎥ ⎟\n\ + ⎝⎣x + y x - 2⋅y⎦τ ⎣x - 2⋅y x + y ⎦τ⎠\ +""" + + assert upretty(Series(tf1, tf3)) == expected1 + assert upretty(Series(-tf2, -tf1)) == expected2 + assert upretty(Series(tf3, tf1, Parallel(-tf1, tf2))) == expected3 + assert upretty(Series(Parallel(tf1, tf2), Parallel(tf2, tf3))) == expected4 + assert upretty(MIMOSeries(tfm2, tfm1)) == expected5 + assert upretty(MIMOSeries(MIMOParallel(tfm4, -tfm5), tfm3, tfm1)) == expected6 + + +def test_pretty_Parallel(): + tf1 = TransferFunction(x + y, x - 2*y, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(x**2 + y, y - x, y) + tf4 = TransferFunction(y**2 - x, x**3 + x, y) + + tfm1 = TransferFunctionMatrix([[tf1, tf2], [tf3, -tf4], [-tf2, -tf1]]) + tfm2 = TransferFunctionMatrix([[-tf2, -tf1], [tf4, -tf3], [tf1, tf2]]) + tfm3 = TransferFunctionMatrix([[-tf1, tf2], [-tf3, tf4], [tf2, tf1]]) + tfm4 = TransferFunctionMatrix([[-tf1, -tf2], [-tf3, -tf4]]) + + expected1 = \ +"""\ + x + y x - y\n\ +─────── + ─────\n\ +x - 2⋅y x + y\ +""" + expected2 = \ +"""\ +-x + y -x - y \n\ +────── + ─────── +x + y x - 2⋅y\ +""" + expected3 = \ +"""\ + 2 \n\ +x + y x + y ⎛-x - y ⎞ ⎛x - y⎞ +────── + ─────── + ⎜───────⎟⋅⎜─────⎟ +-x + y x - 2⋅y ⎝x - 2⋅y⎠ ⎝x + y⎠\ +""" + + expected4 = \ +"""\ + ⎛ 2 ⎞\n\ +⎛ x + y ⎞ ⎛x - y⎞ ⎛x - y⎞ ⎜x + y⎟\n\ +⎜───────⎟⋅⎜─────⎟ + ⎜─────⎟⋅⎜──────⎟\n\ +⎝x - 2⋅y⎠ ⎝x + y⎠ ⎝x + y⎠ ⎝-x + y⎠\ +""" + expected5 = \ +"""\ +⎡ x + y -x + y ⎤ ⎡ x - y x + y ⎤ ⎡ x + y x - y ⎤ \n\ +⎢─────── ────── ⎥ ⎢ ───── ───────⎥ ⎢─────── ───── ⎥ \n\ +⎢x - 2⋅y x + y ⎥ ⎢ x + y x - 2⋅y⎥ ⎢x - 2⋅y x + y ⎥ \n\ +⎢ ⎥ ⎢ ⎥ ⎢ ⎥ \n\ +⎢ 2 2 ⎥ ⎢ 2 2 ⎥ ⎢ 2 2 ⎥ \n\ +⎢x + y x - y ⎥ ⎢x - y x + y ⎥ ⎢x + y x - y ⎥ \n\ +⎢────── ────── ⎥ + ⎢────── ────── ⎥ + ⎢────── ────── ⎥ \n\ +⎢-x + y 3 ⎥ ⎢ 3 -x + y ⎥ ⎢-x + y 3 ⎥ \n\ +⎢ x + x ⎥ ⎢x + x ⎥ ⎢ x + x ⎥ \n\ +⎢ ⎥ ⎢ ⎥ ⎢ ⎥ \n\ +⎢-x + y -x - y ⎥ ⎢-x - y -x + y ⎥ ⎢-x + y -x - y ⎥ \n\ +⎢────── ───────⎥ ⎢─────── ────── ⎥ ⎢────── ───────⎥ \n\ +⎣x + y x - 2⋅y⎦τ ⎣x - 2⋅y x + y ⎦τ ⎣x + y x - 2⋅y⎦τ\ +""" + expected6 = \ +"""\ +⎡ x - y x + y ⎤ ⎡-x + y -x - y ⎤ \n\ +⎢ ───── ───────⎥ ⎢────── ─────── ⎥ \n\ +⎢ x + y x - 2⋅y⎥ ⎡-x - y -x + y⎤ ⎢x + y x - 2⋅y ⎥ \n\ +⎢ ⎥ ⎢─────── ──────⎥ ⎢ ⎥ \n\ +⎢ 2 2 ⎥ ⎢x - 2⋅y x + y ⎥ ⎢ 2 2 ⎥ \n\ +⎢x - y x + y ⎥ ⎢ ⎥ ⎢-x + y - x - y⎥ \n\ +⎢────── ────── ⎥ ⋅⎢ 2 2⎥ + ⎢─────── ────────⎥ \n\ +⎢ 3 -x + y ⎥ ⎢- x - y x - y ⎥ ⎢ 3 -x + y ⎥ \n\ +⎢x + x ⎥ ⎢──────── ──────⎥ ⎢x + x ⎥ \n\ +⎢ ⎥ ⎢ -x + y 3 ⎥ ⎢ ⎥ \n\ +⎢-x - y -x + y ⎥ ⎣ x + x⎦τ ⎢ x + y x - y ⎥ \n\ +⎢─────── ────── ⎥ ⎢─────── ───── ⎥ \n\ +⎣x - 2⋅y x + y ⎦τ ⎣x - 2⋅y x + y ⎦τ\ +""" + assert upretty(Parallel(tf1, tf2)) == expected1 + assert upretty(Parallel(-tf2, -tf1)) == expected2 + assert upretty(Parallel(tf3, tf1, Series(-tf1, tf2))) == expected3 + assert upretty(Parallel(Series(tf1, tf2), Series(tf2, tf3))) == expected4 + assert upretty(MIMOParallel(-tfm3, -tfm2, tfm1)) == expected5 + assert upretty(MIMOParallel(MIMOSeries(tfm4, -tfm2), tfm2)) == expected6 + + +def test_pretty_Feedback(): + tf = TransferFunction(1, 1, y) + tf1 = TransferFunction(x + y, x - 2*y, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(y**2 - 2*y + 1, y + 5, y) + tf4 = TransferFunction(x - 2*y**3, x + y, x) + tf5 = TransferFunction(1 - x, x - y, y) + tf6 = TransferFunction(2, 2, x) + expected1 = \ +"""\ + ⎛1⎞ \n\ + ⎜─⎟ \n\ + ⎝1⎠ \n\ +─────────────\n\ +1 ⎛ x + y ⎞\n\ +─ + ⎜───────⎟\n\ +1 ⎝x - 2⋅y⎠\ +""" + expected2 = \ +"""\ + ⎛1⎞ \n\ + ⎜─⎟ \n\ + ⎝1⎠ \n\ +────────────────────────────────────\n\ + ⎛ 2 ⎞\n\ +1 ⎛x - y⎞ ⎛ x + y ⎞ ⎜y - 2⋅y + 1⎟\n\ +─ + ⎜─────⎟⋅⎜───────⎟⋅⎜────────────⎟\n\ +1 ⎝x + y⎠ ⎝x - 2⋅y⎠ ⎝ y + 5 ⎠\ +""" + expected3 = \ +"""\ + ⎛ x + y ⎞ \n\ + ⎜───────⎟ \n\ + ⎝x - 2⋅y⎠ \n\ +────────────────────────────────────────────\n\ + ⎛ 2 ⎞ \n\ +1 ⎛ x + y ⎞ ⎛x - y⎞ ⎜y - 2⋅y + 1⎟ ⎛1 - x⎞\n\ +─ + ⎜───────⎟⋅⎜─────⎟⋅⎜────────────⎟⋅⎜─────⎟\n\ +1 ⎝x - 2⋅y⎠ ⎝x + y⎠ ⎝ y + 5 ⎠ ⎝x - y⎠\ +""" + expected4 = \ +"""\ + ⎛ x + y ⎞ ⎛x - y⎞ \n\ + ⎜───────⎟⋅⎜─────⎟ \n\ + ⎝x - 2⋅y⎠ ⎝x + y⎠ \n\ +─────────────────────\n\ +1 ⎛ x + y ⎞ ⎛x - y⎞\n\ +─ + ⎜───────⎟⋅⎜─────⎟\n\ +1 ⎝x - 2⋅y⎠ ⎝x + y⎠\ +""" + expected5 = \ +"""\ + ⎛ x + y ⎞ ⎛x - y⎞ \n\ + ⎜───────⎟⋅⎜─────⎟ \n\ + ⎝x - 2⋅y⎠ ⎝x + y⎠ \n\ +─────────────────────────────\n\ +1 ⎛ x + y ⎞ ⎛x - y⎞ ⎛1 - x⎞\n\ +─ + ⎜───────⎟⋅⎜─────⎟⋅⎜─────⎟\n\ +1 ⎝x - 2⋅y⎠ ⎝x + y⎠ ⎝x - y⎠\ +""" + expected6 = \ +"""\ + ⎛ 2 ⎞ \n\ + ⎜y - 2⋅y + 1⎟ ⎛1 - x⎞ \n\ + ⎜────────────⎟⋅⎜─────⎟ \n\ + ⎝ y + 5 ⎠ ⎝x - y⎠ \n\ +────────────────────────────────────────────\n\ + ⎛ 2 ⎞ \n\ +1 ⎜y - 2⋅y + 1⎟ ⎛1 - x⎞ ⎛x - y⎞ ⎛ x + y ⎞\n\ +─ + ⎜────────────⎟⋅⎜─────⎟⋅⎜─────⎟⋅⎜───────⎟\n\ +1 ⎝ y + 5 ⎠ ⎝x - y⎠ ⎝x + y⎠ ⎝x - 2⋅y⎠\ +""" + expected7 = \ +"""\ + ⎛ 3⎞ \n\ + ⎜x - 2⋅y ⎟ \n\ + ⎜────────⎟ \n\ + ⎝ x + y ⎠ \n\ +──────────────────\n\ + ⎛ 3⎞ \n\ +1 ⎜x - 2⋅y ⎟ ⎛2⎞\n\ +─ + ⎜────────⎟⋅⎜─⎟\n\ +1 ⎝ x + y ⎠ ⎝2⎠\ +""" + expected8 = \ +"""\ + ⎛1 - x⎞ \n\ + ⎜─────⎟ \n\ + ⎝x - y⎠ \n\ +───────────\n\ +1 ⎛1 - x⎞\n\ +─ + ⎜─────⎟\n\ +1 ⎝x - y⎠\ +""" + expected9 = \ +"""\ + ⎛ x + y ⎞ ⎛x - y⎞ \n\ + ⎜───────⎟⋅⎜─────⎟ \n\ + ⎝x - 2⋅y⎠ ⎝x + y⎠ \n\ +─────────────────────────────\n\ +1 ⎛ x + y ⎞ ⎛x - y⎞ ⎛1 - x⎞\n\ +─ - ⎜───────⎟⋅⎜─────⎟⋅⎜─────⎟\n\ +1 ⎝x - 2⋅y⎠ ⎝x + y⎠ ⎝x - y⎠\ +""" + expected10 = \ +"""\ + ⎛1 - x⎞ \n\ + ⎜─────⎟ \n\ + ⎝x - y⎠ \n\ +───────────\n\ +1 ⎛1 - x⎞\n\ +─ - ⎜─────⎟\n\ +1 ⎝x - y⎠\ +""" + assert upretty(Feedback(tf, tf1)) == expected1 + assert upretty(Feedback(tf, tf2*tf1*tf3)) == expected2 + assert upretty(Feedback(tf1, tf2*tf3*tf5)) == expected3 + assert upretty(Feedback(tf1*tf2, tf)) == expected4 + assert upretty(Feedback(tf1*tf2, tf5)) == expected5 + assert upretty(Feedback(tf3*tf5, tf2*tf1)) == expected6 + assert upretty(Feedback(tf4, tf6)) == expected7 + assert upretty(Feedback(tf5, tf)) == expected8 + + assert upretty(Feedback(tf1*tf2, tf5, 1)) == expected9 + assert upretty(Feedback(tf5, tf, 1)) == expected10 + + +def test_pretty_MIMOFeedback(): + tf1 = TransferFunction(x + y, x - 2*y, y) + tf2 = TransferFunction(x - y, x + y, y) + tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]]) + tfm_2 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf2]]) + tfm_3 = TransferFunctionMatrix([[tf1, tf1], [tf2, tf2]]) + + expected1 = \ +"""\ +⎛ ⎡ x + y x - y ⎤ ⎡ x - y x + y ⎤ ⎞-1 ⎡ x + y x - y ⎤ \n\ +⎜ ⎢─────── ───── ⎥ ⎢ ───── ───────⎥ ⎟ ⎢─────── ───── ⎥ \n\ +⎜ ⎢x - 2⋅y x + y ⎥ ⎢ x + y x - 2⋅y⎥ ⎟ ⎢x - 2⋅y x + y ⎥ \n\ +⎜I - ⎢ ⎥ ⋅⎢ ⎥ ⎟ ⋅ ⎢ ⎥ \n\ +⎜ ⎢ x - y x + y ⎥ ⎢ x + y x - y ⎥ ⎟ ⎢ x - y x + y ⎥ \n\ +⎜ ⎢ ───── ───────⎥ ⎢─────── ───── ⎥ ⎟ ⎢ ───── ───────⎥ \n\ +⎝ ⎣ x + y x - 2⋅y⎦τ ⎣x - 2⋅y x + y ⎦τ⎠ ⎣ x + y x - 2⋅y⎦τ\ +""" + expected2 = \ +"""\ +⎛ ⎡ x + y x - y ⎤ ⎡ x - y x + y ⎤ ⎡ x + y x + y ⎤ ⎞-1 ⎡ x + y x - y ⎤ ⎡ x - y x + y ⎤ \n\ +⎜ ⎢─────── ───── ⎥ ⎢ ───── ───────⎥ ⎢─────── ───────⎥ ⎟ ⎢─────── ───── ⎥ ⎢ ───── ───────⎥ \n\ +⎜ ⎢x - 2⋅y x + y ⎥ ⎢ x + y x - 2⋅y⎥ ⎢x - 2⋅y x - 2⋅y⎥ ⎟ ⎢x - 2⋅y x + y ⎥ ⎢ x + y x - 2⋅y⎥ \n\ +⎜I + ⎢ ⎥ ⋅⎢ ⎥ ⋅⎢ ⎥ ⎟ ⋅ ⎢ ⎥ ⋅⎢ ⎥ \n\ +⎜ ⎢ x - y x + y ⎥ ⎢ x + y x - y ⎥ ⎢ x - y x - y ⎥ ⎟ ⎢ x - y x + y ⎥ ⎢ x + y x - y ⎥ \n\ +⎜ ⎢ ───── ───────⎥ ⎢─────── ───── ⎥ ⎢ ───── ───── ⎥ ⎟ ⎢ ───── ───────⎥ ⎢─────── ───── ⎥ \n\ +⎝ ⎣ x + y x - 2⋅y⎦τ ⎣x - 2⋅y x + y ⎦τ ⎣ x + y x + y ⎦τ⎠ ⎣ x + y x - 2⋅y⎦τ ⎣x - 2⋅y x + y ⎦τ\ +""" + + assert upretty(MIMOFeedback(tfm_1, tfm_2, 1)) == \ + expected1 # Positive MIMOFeedback + assert upretty(MIMOFeedback(tfm_1*tfm_2, tfm_3)) == \ + expected2 # Negative MIMOFeedback (Default) + + +def test_pretty_TransferFunctionMatrix(): + tf1 = TransferFunction(x + y, x - 2*y, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(y**2 - 2*y + 1, y + 5, y) + tf4 = TransferFunction(y, x**2 + x + 1, y) + tf5 = TransferFunction(1 - x, x - y, y) + tf6 = TransferFunction(2, 2, y) + expected1 = \ +"""\ +⎡ x + y ⎤ \n\ +⎢───────⎥ \n\ +⎢x - 2⋅y⎥ \n\ +⎢ ⎥ \n\ +⎢ x - y ⎥ \n\ +⎢ ───── ⎥ \n\ +⎣ x + y ⎦τ\ +""" + expected2 = \ +"""\ +⎡ x + y ⎤ \n\ +⎢ ─────── ⎥ \n\ +⎢ x - 2⋅y ⎥ \n\ +⎢ ⎥ \n\ +⎢ x - y ⎥ \n\ +⎢ ───── ⎥ \n\ +⎢ x + y ⎥ \n\ +⎢ ⎥ \n\ +⎢ 2 ⎥ \n\ +⎢- y + 2⋅y - 1⎥ \n\ +⎢──────────────⎥ \n\ +⎣ y + 5 ⎦τ\ +""" + expected3 = \ +"""\ +⎡ x + y x - y ⎤ \n\ +⎢ ─────── ───── ⎥ \n\ +⎢ x - 2⋅y x + y ⎥ \n\ +⎢ ⎥ \n\ +⎢ 2 ⎥ \n\ +⎢y - 2⋅y + 1 y ⎥ \n\ +⎢──────────── ──────────⎥ \n\ +⎢ y + 5 2 ⎥ \n\ +⎢ x + x + 1⎥ \n\ +⎢ ⎥ \n\ +⎢ 1 - x 2 ⎥ \n\ +⎢ ───── ─ ⎥ \n\ +⎣ x - y 2 ⎦τ\ +""" + expected4 = \ +"""\ +⎡ x - y x + y y ⎤ \n\ +⎢ ───── ─────── ──────────⎥ \n\ +⎢ x + y x - 2⋅y 2 ⎥ \n\ +⎢ x + x + 1⎥ \n\ +⎢ ⎥ \n\ +⎢ 2 ⎥ \n\ +⎢- y + 2⋅y - 1 x - 1 -2 ⎥ \n\ +⎢────────────── ───── ─── ⎥ \n\ +⎣ y + 5 x - y 2 ⎦τ\ +""" + expected5 = \ +"""\ +⎡ x + y x - y x + y y ⎤ \n\ +⎢───────⋅───── ─────── ──────────⎥ \n\ +⎢x - 2⋅y x + y x - 2⋅y 2 ⎥ \n\ +⎢ x + x + 1⎥ \n\ +⎢ ⎥ \n\ +⎢ 1 - x 2 x + y -2 ⎥ \n\ +⎢ ───── + ─ ─────── ─── ⎥ \n\ +⎣ x - y 2 x - 2⋅y 2 ⎦τ\ +""" + + assert upretty(TransferFunctionMatrix([[tf1], [tf2]])) == expected1 + assert upretty(TransferFunctionMatrix([[tf1], [tf2], [-tf3]])) == expected2 + assert upretty(TransferFunctionMatrix([[tf1, tf2], [tf3, tf4], [tf5, tf6]])) == expected3 + assert upretty(TransferFunctionMatrix([[tf2, tf1, tf4], [-tf3, -tf5, -tf6]])) == expected4 + assert upretty(TransferFunctionMatrix([[Series(tf2, tf1), tf1, tf4], [Parallel(tf6, tf5), tf1, -tf6]])) == \ + expected5 + + +def test_pretty_StateSpace(): + ss1 = StateSpace(Matrix([a]), Matrix([b]), Matrix([c]), Matrix([d])) + A = Matrix([[0, 1], [1, 0]]) + B = Matrix([1, 0]) + C = Matrix([[0, 1]]) + D = Matrix([0]) + ss2 = StateSpace(A, B, C, D) + ss3 = StateSpace(Matrix([[-1.5, -2], [1, 0]]), + Matrix([[0.5, 0], [0, 1]]), + Matrix([[0, 1], [0, 2]]), + Matrix([[2, 2], [1, 1]])) + + expected1 = \ +"""\ +⎡[a] [b]⎤\n\ +⎢ ⎥\n\ +⎣[c] [d]⎦\ +""" + expected2 = \ +"""\ +⎡⎡0 1⎤ ⎡1⎤⎤\n\ +⎢⎢ ⎥ ⎢ ⎥⎥\n\ +⎢⎣1 0⎦ ⎣0⎦⎥\n\ +⎢ ⎥\n\ +⎣[0 1] [0]⎦\ +""" + expected3 = \ +"""\ +⎡⎡-1.5 -2⎤ ⎡0.5 0⎤⎤\n\ +⎢⎢ ⎥ ⎢ ⎥⎥\n\ +⎢⎣ 1 0 ⎦ ⎣ 0 1⎦⎥\n\ +⎢ ⎥\n\ +⎢ ⎡0 1⎤ ⎡2 2⎤ ⎥\n\ +⎢ ⎢ ⎥ ⎢ ⎥ ⎥\n\ +⎣ ⎣0 2⎦ ⎣1 1⎦ ⎦\ +""" + + assert upretty(ss1) == expected1 + assert upretty(ss2) == expected2 + assert upretty(ss3) == expected3 + +def test_pretty_order(): + expr = O(1) + ascii_str = \ +"""\ +O(1)\ +""" + ucode_str = \ +"""\ +O(1)\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = O(1/x) + ascii_str = \ +"""\ + /1\\\n\ +O|-|\n\ + \\x/\ +""" + ucode_str = \ +"""\ + ⎛1⎞\n\ +O⎜─⎟\n\ + ⎝x⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = O(x**2 + y**2) + ascii_str = \ +"""\ + / 2 2 \\\n\ +O\\x + y ; (x, y) -> (0, 0)/\ +""" + ucode_str = \ +"""\ + ⎛ 2 2 ⎞\n\ +O⎝x + y ; (x, y) → (0, 0)⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = O(1, (x, oo)) + ascii_str = \ +"""\ +O(1; x -> oo)\ +""" + ucode_str = \ +"""\ +O(1; x → ∞)\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = O(1/x, (x, oo)) + ascii_str = \ +"""\ + /1 \\\n\ +O|-; x -> oo|\n\ + \\x /\ +""" + ucode_str = \ +"""\ + ⎛1 ⎞\n\ +O⎜─; x → ∞⎟\n\ + ⎝x ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = O(x**2 + y**2, (x, oo), (y, oo)) + ascii_str = \ +"""\ + / 2 2 \\\n\ +O\\x + y ; (x, y) -> (oo, oo)/\ +""" + ucode_str = \ +"""\ + ⎛ 2 2 ⎞\n\ +O⎝x + y ; (x, y) → (∞, ∞)⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_derivatives(): + # Simple + expr = Derivative(log(x), x, evaluate=False) + ascii_str = \ +"""\ +d \n\ +--(log(x))\n\ +dx \ +""" + ucode_str = \ +"""\ +d \n\ +──(log(x))\n\ +dx \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Derivative(log(x), x, evaluate=False) + x + ascii_str_1 = \ +"""\ + d \n\ +x + --(log(x))\n\ + dx \ +""" + ascii_str_2 = \ +"""\ +d \n\ +--(log(x)) + x\n\ +dx \ +""" + ucode_str_1 = \ +"""\ + d \n\ +x + ──(log(x))\n\ + dx \ +""" + ucode_str_2 = \ +"""\ +d \n\ +──(log(x)) + x\n\ +dx \ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + # basic partial derivatives + expr = Derivative(log(x + y) + x, x) + ascii_str_1 = \ +"""\ +d \n\ +--(log(x + y) + x)\n\ +dx \ +""" + ascii_str_2 = \ +"""\ +d \n\ +--(x + log(x + y))\n\ +dx \ +""" + ucode_str_1 = \ +"""\ +∂ \n\ +──(log(x + y) + x)\n\ +∂x \ +""" + ucode_str_2 = \ +"""\ +∂ \n\ +──(x + log(x + y))\n\ +∂x \ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2], upretty(expr) + + # Multiple symbols + expr = Derivative(log(x) + x**2, x, y) + ascii_str_1 = \ +"""\ + 2 \n\ + d / 2\\\n\ +-----\\log(x) + x /\n\ +dy dx \ +""" + ascii_str_2 = \ +"""\ + 2 \n\ + d / 2 \\\n\ +-----\\x + log(x)/\n\ +dy dx \ +""" + ascii_str_3 = \ +"""\ + 2 \n\ + d / 2 \\\n\ +-----\\x + log(x)/\n\ +dy dx \ +""" + ucode_str_1 = \ +"""\ + 2 \n\ + d ⎛ 2⎞\n\ +─────⎝log(x) + x ⎠\n\ +dy dx \ +""" + ucode_str_2 = \ +"""\ + 2 \n\ + d ⎛ 2 ⎞\n\ +─────⎝x + log(x)⎠\n\ +dy dx \ +""" + ucode_str_3 = \ +"""\ + 2 \n\ + d ⎛ 2 ⎞\n\ +─────⎝x + log(x)⎠\n\ +dy dx \ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2, ascii_str_3] + assert upretty(expr) in [ucode_str_1, ucode_str_2, ucode_str_3] + + expr = Derivative(2*x*y, y, x) + x**2 + ascii_str_1 = \ +"""\ + 2 \n\ + d 2\n\ +-----(2*x*y) + x \n\ +dx dy \ +""" + ascii_str_2 = \ +"""\ + 2 \n\ + 2 d \n\ +x + -----(2*x*y)\n\ + dx dy \ +""" + ascii_str_3 = \ +"""\ + 2 \n\ + 2 d \n\ +x + -----(2*x*y)\n\ + dx dy \ +""" + ucode_str_1 = \ +"""\ + 2 \n\ + ∂ 2\n\ +─────(2⋅x⋅y) + x \n\ +∂x ∂y \ +""" + ucode_str_2 = \ +"""\ + 2 \n\ + 2 ∂ \n\ +x + ─────(2⋅x⋅y)\n\ + ∂x ∂y \ +""" + ucode_str_3 = \ +"""\ + 2 \n\ + 2 ∂ \n\ +x + ─────(2⋅x⋅y)\n\ + ∂x ∂y \ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2, ascii_str_3] + assert upretty(expr) in [ucode_str_1, ucode_str_2, ucode_str_3] + + expr = Derivative(2*x*y, x, x) + ascii_str = \ +"""\ + 2 \n\ +d \n\ +---(2*x*y)\n\ + 2 \n\ +dx \ +""" + ucode_str = \ +"""\ + 2 \n\ +∂ \n\ +───(2⋅x⋅y)\n\ + 2 \n\ +∂x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Derivative(2*x*y, x, 17) + ascii_str = \ +"""\ + 17 \n\ +d \n\ +----(2*x*y)\n\ + 17 \n\ +dx \ +""" + ucode_str = \ +"""\ + 17 \n\ +∂ \n\ +────(2⋅x⋅y)\n\ + 17 \n\ +∂x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Derivative(2*x*y, x, x, y) + ascii_str = \ +"""\ + 3 \n\ + d \n\ +------(2*x*y)\n\ + 2 \n\ +dy dx \ +""" + ucode_str = \ +"""\ + 3 \n\ + ∂ \n\ +──────(2⋅x⋅y)\n\ + 2 \n\ +∂y ∂x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + # Greek letters + alpha = Symbol('alpha') + beta = Function('beta') + expr = beta(alpha).diff(alpha) + ascii_str = \ +"""\ + d \n\ +------(beta(alpha))\n\ +dalpha \ +""" + ucode_str = \ +"""\ +d \n\ +──(β(α))\n\ +dα \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Derivative(f(x), (x, n)) + + ascii_str = \ +"""\ + n \n\ +d \n\ +---(f(x))\n\ + n \n\ +dx \ +""" + ucode_str = \ +"""\ + n \n\ +d \n\ +───(f(x))\n\ + n \n\ +dx \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_integrals(): + expr = Integral(log(x), x) + ascii_str = \ +"""\ + / \n\ + | \n\ + | log(x) dx\n\ + | \n\ +/ \ +""" + ucode_str = \ +"""\ +⌠ \n\ +⎮ log(x) dx\n\ +⌡ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Integral(x**2, x) + ascii_str = \ +"""\ + / \n\ + | \n\ + | 2 \n\ + | x dx\n\ + | \n\ +/ \ +""" + ucode_str = \ +"""\ +⌠ \n\ +⎮ 2 \n\ +⎮ x dx\n\ +⌡ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Integral((sin(x))**2 / (tan(x))**2) + ascii_str = \ +"""\ + / \n\ + | \n\ + | 2 \n\ + | sin (x) \n\ + | ------- dx\n\ + | 2 \n\ + | tan (x) \n\ + | \n\ +/ \ +""" + ucode_str = \ +"""\ +⌠ \n\ +⎮ 2 \n\ +⎮ sin (x) \n\ +⎮ ─────── dx\n\ +⎮ 2 \n\ +⎮ tan (x) \n\ +⌡ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Integral(x**(2**x), x) + ascii_str = \ +"""\ + / \n\ + | \n\ + | / x\\ \n\ + | \\2 / \n\ + | x dx\n\ + | \n\ +/ \ +""" + ucode_str = \ +"""\ +⌠ \n\ +⎮ ⎛ x⎞ \n\ +⎮ ⎝2 ⎠ \n\ +⎮ x dx\n\ +⌡ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Integral(x**2, (x, 1, 2)) + ascii_str = \ +"""\ + 2 \n\ + / \n\ + | \n\ + | 2 \n\ + | x dx\n\ + | \n\ +/ \n\ +1 \ +""" + ucode_str = \ +"""\ +2 \n\ +⌠ \n\ +⎮ 2 \n\ +⎮ x dx\n\ +⌡ \n\ +1 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Integral(x**2, (x, Rational(1, 2), 10)) + ascii_str = \ +"""\ + 10 \n\ + / \n\ + | \n\ + | 2 \n\ + | x dx\n\ + | \n\ +/ \n\ +1/2 \ +""" + ucode_str = \ +"""\ +10 \n\ +⌠ \n\ +⎮ 2 \n\ +⎮ x dx\n\ +⌡ \n\ +1/2 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Integral(x**2*y**2, x, y) + ascii_str = \ +"""\ + / / \n\ + | | \n\ + | | 2 2 \n\ + | | x *y dx dy\n\ + | | \n\ +/ / \ +""" + ucode_str = \ +"""\ +⌠ ⌠ \n\ +⎮ ⎮ 2 2 \n\ +⎮ ⎮ x ⋅y dx dy\n\ +⌡ ⌡ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Integral(sin(th)/cos(ph), (th, 0, pi), (ph, 0, 2*pi)) + ascii_str = \ +"""\ + 2*pi pi \n\ + / / \n\ + | | \n\ + | | sin(theta) \n\ + | | ---------- d(theta) d(phi)\n\ + | | cos(phi) \n\ + | | \n\ + / / \n\ +0 0 \ +""" + ucode_str = \ +"""\ +2⋅π π \n\ + ⌠ ⌠ \n\ + ⎮ ⎮ sin(θ) \n\ + ⎮ ⎮ ────── dθ dφ\n\ + ⎮ ⎮ cos(φ) \n\ + ⌡ ⌡ \n\ + 0 0 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_matrix(): + # Empty Matrix + expr = Matrix() + ascii_str = "[]" + unicode_str = "[]" + assert pretty(expr) == ascii_str + assert upretty(expr) == unicode_str + expr = Matrix(2, 0, lambda i, j: 0) + ascii_str = "[]" + unicode_str = "[]" + assert pretty(expr) == ascii_str + assert upretty(expr) == unicode_str + expr = Matrix(0, 2, lambda i, j: 0) + ascii_str = "[]" + unicode_str = "[]" + assert pretty(expr) == ascii_str + assert upretty(expr) == unicode_str + expr = Matrix([[x**2 + 1, 1], [y, x + y]]) + ascii_str_1 = \ +"""\ +[ 2 ] +[1 + x 1 ] +[ ] +[ y x + y]\ +""" + ascii_str_2 = \ +"""\ +[ 2 ] +[x + 1 1 ] +[ ] +[ y x + y]\ +""" + ucode_str_1 = \ +"""\ +⎡ 2 ⎤ +⎢1 + x 1 ⎥ +⎢ ⎥ +⎣ y x + y⎦\ +""" + ucode_str_2 = \ +"""\ +⎡ 2 ⎤ +⎢x + 1 1 ⎥ +⎢ ⎥ +⎣ y x + y⎦\ +""" + assert pretty(expr) in [ascii_str_1, ascii_str_2] + assert upretty(expr) in [ucode_str_1, ucode_str_2] + + expr = Matrix([[x/y, y, th], [0, exp(I*k*ph), 1]]) + ascii_str = \ +"""\ +[x ] +[- y theta] +[y ] +[ ] +[ I*k*phi ] +[0 e 1 ]\ +""" + ucode_str = \ +"""\ +⎡x ⎤ +⎢─ y θ⎥ +⎢y ⎥ +⎢ ⎥ +⎢ ⅈ⋅k⋅φ ⎥ +⎣0 ℯ 1⎦\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + unicode_str = \ +"""\ +⎡v̇_msc_00 0 0 ⎤ +⎢ ⎥ +⎢ 0 v̇_msc_01 0 ⎥ +⎢ ⎥ +⎣ 0 0 v̇_msc_02⎦\ +""" + + expr = diag(*MatrixSymbol('vdot_msc',1,3)) + assert upretty(expr) == unicode_str + + +def test_pretty_ndim_arrays(): + x, y, z, w = symbols("x y z w") + + for ArrayType in (ImmutableDenseNDimArray, ImmutableSparseNDimArray, MutableDenseNDimArray, MutableSparseNDimArray): + # Basic: scalar array + M = ArrayType(x) + + assert pretty(M) == "x" + assert upretty(M) == "x" + + M = ArrayType([[1/x, y], [z, w]]) + M1 = ArrayType([1/x, y, z]) + + M2 = tensorproduct(M1, M) + M3 = tensorproduct(M, M) + + ascii_str = \ +"""\ +[1 ]\n\ +[- y]\n\ +[x ]\n\ +[ ]\n\ +[z w]\ +""" + ucode_str = \ +"""\ +⎡1 ⎤\n\ +⎢─ y⎥\n\ +⎢x ⎥\n\ +⎢ ⎥\n\ +⎣z w⎦\ +""" + assert pretty(M) == ascii_str + assert upretty(M) == ucode_str + + ascii_str = \ +"""\ +[1 ]\n\ +[- y z]\n\ +[x ]\ +""" + ucode_str = \ +"""\ +⎡1 ⎤\n\ +⎢─ y z⎥\n\ +⎣x ⎦\ +""" + assert pretty(M1) == ascii_str + assert upretty(M1) == ucode_str + + ascii_str = \ +"""\ +[[1 y] ]\n\ +[[-- -] [z ]]\n\ +[[ 2 x] [ y 2 ] [- y*z]]\n\ +[[x ] [ - y ] [x ]]\n\ +[[ ] [ x ] [ ]]\n\ +[[z w] [ ] [ 2 ]]\n\ +[[- -] [y*z w*y] [z w*z]]\n\ +[[x x] ]\ +""" + ucode_str = \ +"""\ +⎡⎡1 y⎤ ⎤\n\ +⎢⎢── ─⎥ ⎡z ⎤⎥\n\ +⎢⎢ 2 x⎥ ⎡ y 2 ⎤ ⎢─ y⋅z⎥⎥\n\ +⎢⎢x ⎥ ⎢ ─ y ⎥ ⎢x ⎥⎥\n\ +⎢⎢ ⎥ ⎢ x ⎥ ⎢ ⎥⎥\n\ +⎢⎢z w⎥ ⎢ ⎥ ⎢ 2 ⎥⎥\n\ +⎢⎢─ ─⎥ ⎣y⋅z w⋅y⎦ ⎣z w⋅z⎦⎥\n\ +⎣⎣x x⎦ ⎦\ +""" + assert pretty(M2) == ascii_str + assert upretty(M2) == ucode_str + + ascii_str = \ +"""\ +[ [1 y] ]\n\ +[ [-- -] ]\n\ +[ [ 2 x] [ y 2 ]]\n\ +[ [x ] [ - y ]]\n\ +[ [ ] [ x ]]\n\ +[ [z w] [ ]]\n\ +[ [- -] [y*z w*y]]\n\ +[ [x x] ]\n\ +[ ]\n\ +[[z ] [ w ]]\n\ +[[- y*z] [ - w*y]]\n\ +[[x ] [ x ]]\n\ +[[ ] [ ]]\n\ +[[ 2 ] [ 2 ]]\n\ +[[z w*z] [w*z w ]]\ +""" + ucode_str = \ +"""\ +⎡ ⎡1 y⎤ ⎤\n\ +⎢ ⎢── ─⎥ ⎥\n\ +⎢ ⎢ 2 x⎥ ⎡ y 2 ⎤⎥\n\ +⎢ ⎢x ⎥ ⎢ ─ y ⎥⎥\n\ +⎢ ⎢ ⎥ ⎢ x ⎥⎥\n\ +⎢ ⎢z w⎥ ⎢ ⎥⎥\n\ +⎢ ⎢─ ─⎥ ⎣y⋅z w⋅y⎦⎥\n\ +⎢ ⎣x x⎦ ⎥\n\ +⎢ ⎥\n\ +⎢⎡z ⎤ ⎡ w ⎤⎥\n\ +⎢⎢─ y⋅z⎥ ⎢ ─ w⋅y⎥⎥\n\ +⎢⎢x ⎥ ⎢ x ⎥⎥\n\ +⎢⎢ ⎥ ⎢ ⎥⎥\n\ +⎢⎢ 2 ⎥ ⎢ 2 ⎥⎥\n\ +⎣⎣z w⋅z⎦ ⎣w⋅z w ⎦⎦\ +""" + assert pretty(M3) == ascii_str + assert upretty(M3) == ucode_str + + Mrow = ArrayType([[x, y, 1 / z]]) + Mcolumn = ArrayType([[x], [y], [1 / z]]) + Mcol2 = ArrayType([Mcolumn.tolist()]) + + ascii_str = \ +"""\ +[[ 1]]\n\ +[[x y -]]\n\ +[[ z]]\ +""" + ucode_str = \ +"""\ +⎡⎡ 1⎤⎤\n\ +⎢⎢x y ─⎥⎥\n\ +⎣⎣ z⎦⎦\ +""" + assert pretty(Mrow) == ascii_str + assert upretty(Mrow) == ucode_str + + ascii_str = \ +"""\ +[x]\n\ +[ ]\n\ +[y]\n\ +[ ]\n\ +[1]\n\ +[-]\n\ +[z]\ +""" + ucode_str = \ +"""\ +⎡x⎤\n\ +⎢ ⎥\n\ +⎢y⎥\n\ +⎢ ⎥\n\ +⎢1⎥\n\ +⎢─⎥\n\ +⎣z⎦\ +""" + assert pretty(Mcolumn) == ascii_str + assert upretty(Mcolumn) == ucode_str + + ascii_str = \ +"""\ +[[x]]\n\ +[[ ]]\n\ +[[y]]\n\ +[[ ]]\n\ +[[1]]\n\ +[[-]]\n\ +[[z]]\ +""" + ucode_str = \ +"""\ +⎡⎡x⎤⎤\n\ +⎢⎢ ⎥⎥\n\ +⎢⎢y⎥⎥\n\ +⎢⎢ ⎥⎥\n\ +⎢⎢1⎥⎥\n\ +⎢⎢─⎥⎥\n\ +⎣⎣z⎦⎦\ +""" + assert pretty(Mcol2) == ascii_str + assert upretty(Mcol2) == ucode_str + + +def test_tensor_TensorProduct(): + A = MatrixSymbol("A", 3, 3) + B = MatrixSymbol("B", 3, 3) + assert upretty(TensorProduct(A, B)) == "A\u2297B" + assert upretty(TensorProduct(A, B, A)) == "A\u2297B\u2297A" + + +def test_diffgeom_print_WedgeProduct(): + from sympy.diffgeom.rn import R2 + from sympy.diffgeom import WedgeProduct + wp = WedgeProduct(R2.dx, R2.dy) + assert upretty(wp) == "ⅆ x∧ⅆ y" + assert pretty(wp) == r"d x/\d y" + + +def test_Adjoint(): + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + assert pretty(Adjoint(X)) == " +\nX " + assert pretty(Adjoint(X + Y)) == " +\n(X + Y) " + assert pretty(Adjoint(X) + Adjoint(Y)) == " + +\nX + Y " + assert pretty(Adjoint(X*Y)) == " +\n(X*Y) " + assert pretty(Adjoint(Y)*Adjoint(X)) == " + +\nY *X " + assert pretty(Adjoint(X**2)) == " +\n/ 2\\ \n\\X / " + assert pretty(Adjoint(X)**2) == " 2\n/ +\\ \n\\X / " + assert pretty(Adjoint(Inverse(X))) == " +\n/ -1\\ \n\\X / " + assert pretty(Inverse(Adjoint(X))) == " -1\n/ +\\ \n\\X / " + assert pretty(Adjoint(Transpose(X))) == " +\n/ T\\ \n\\X / " + assert pretty(Transpose(Adjoint(X))) == " T\n/ +\\ \n\\X / " + assert upretty(Adjoint(X)) == " †\nX " + assert upretty(Adjoint(X + Y)) == " †\n(X + Y) " + assert upretty(Adjoint(X) + Adjoint(Y)) == " † †\nX + Y " + assert upretty(Adjoint(X*Y)) == " †\n(X⋅Y) " + assert upretty(Adjoint(Y)*Adjoint(X)) == " † †\nY ⋅X " + assert upretty(Adjoint(X**2)) == \ + " †\n⎛ 2⎞ \n⎝X ⎠ " + assert upretty(Adjoint(X)**2) == \ + " 2\n⎛ †⎞ \n⎝X ⎠ " + assert upretty(Adjoint(Inverse(X))) == \ + " †\n⎛ -1⎞ \n⎝X ⎠ " + assert upretty(Inverse(Adjoint(X))) == \ + " -1\n⎛ †⎞ \n⎝X ⎠ " + assert upretty(Adjoint(Transpose(X))) == \ + " †\n⎛ T⎞ \n⎝X ⎠ " + assert upretty(Transpose(Adjoint(X))) == \ + " T\n⎛ †⎞ \n⎝X ⎠ " + m = Matrix(((1, 2), (3, 4))) + assert upretty(Adjoint(m)) == \ + ' †\n'\ + '⎡1 2⎤ \n'\ + '⎢ ⎥ \n'\ + '⎣3 4⎦ ' + assert upretty(Adjoint(m+X)) == \ + ' †\n'\ + '⎛⎡1 2⎤ ⎞ \n'\ + '⎜⎢ ⎥ + X⎟ \n'\ + '⎝⎣3 4⎦ ⎠ ' + assert upretty(Adjoint(BlockMatrix(((OneMatrix(2, 2), X), + (m, ZeroMatrix(2, 2)))))) == \ + ' †\n'\ + '⎡ 𝟙 X⎤ \n'\ + '⎢ ⎥ \n'\ + '⎢⎡1 2⎤ ⎥ \n'\ + '⎢⎢ ⎥ 𝟘⎥ \n'\ + '⎣⎣3 4⎦ ⎦ ' + + +def test_Transpose(): + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + assert pretty(Transpose(X)) == " T\nX " + assert pretty(Transpose(X + Y)) == " T\n(X + Y) " + assert pretty(Transpose(X) + Transpose(Y)) == " T T\nX + Y " + assert pretty(Transpose(X*Y)) == " T\n(X*Y) " + assert pretty(Transpose(Y)*Transpose(X)) == " T T\nY *X " + assert pretty(Transpose(X**2)) == " T\n/ 2\\ \n\\X / " + assert pretty(Transpose(X)**2) == " 2\n/ T\\ \n\\X / " + assert pretty(Transpose(Inverse(X))) == " T\n/ -1\\ \n\\X / " + assert pretty(Inverse(Transpose(X))) == " -1\n/ T\\ \n\\X / " + assert upretty(Transpose(X)) == " T\nX " + assert upretty(Transpose(X + Y)) == " T\n(X + Y) " + assert upretty(Transpose(X) + Transpose(Y)) == " T T\nX + Y " + assert upretty(Transpose(X*Y)) == " T\n(X⋅Y) " + assert upretty(Transpose(Y)*Transpose(X)) == " T T\nY ⋅X " + assert upretty(Transpose(X**2)) == \ + " T\n⎛ 2⎞ \n⎝X ⎠ " + assert upretty(Transpose(X)**2) == \ + " 2\n⎛ T⎞ \n⎝X ⎠ " + assert upretty(Transpose(Inverse(X))) == \ + " T\n⎛ -1⎞ \n⎝X ⎠ " + assert upretty(Inverse(Transpose(X))) == \ + " -1\n⎛ T⎞ \n⎝X ⎠ " + m = Matrix(((1, 2), (3, 4))) + assert upretty(Transpose(m)) == \ + ' T\n'\ + '⎡1 2⎤ \n'\ + '⎢ ⎥ \n'\ + '⎣3 4⎦ ' + assert upretty(Transpose(m+X)) == \ + ' T\n'\ + '⎛⎡1 2⎤ ⎞ \n'\ + '⎜⎢ ⎥ + X⎟ \n'\ + '⎝⎣3 4⎦ ⎠ ' + assert upretty(Transpose(BlockMatrix(((OneMatrix(2, 2), X), + (m, ZeroMatrix(2, 2)))))) == \ + ' T\n'\ + '⎡ 𝟙 X⎤ \n'\ + '⎢ ⎥ \n'\ + '⎢⎡1 2⎤ ⎥ \n'\ + '⎢⎢ ⎥ 𝟘⎥ \n'\ + '⎣⎣3 4⎦ ⎦ ' + + +def test_pretty_Trace_issue_9044(): + X = Matrix([[1, 2], [3, 4]]) + Y = Matrix([[2, 4], [6, 8]]) + ascii_str_1 = \ +"""\ + /[1 2]\\ +tr|[ ]| + \\[3 4]/\ +""" + ucode_str_1 = \ +"""\ + ⎛⎡1 2⎤⎞ +tr⎜⎢ ⎥⎟ + ⎝⎣3 4⎦⎠\ +""" + ascii_str_2 = \ +"""\ + /[1 2]\\ /[2 4]\\ +tr|[ ]| + tr|[ ]| + \\[3 4]/ \\[6 8]/\ +""" + ucode_str_2 = \ +"""\ + ⎛⎡1 2⎤⎞ ⎛⎡2 4⎤⎞ +tr⎜⎢ ⎥⎟ + tr⎜⎢ ⎥⎟ + ⎝⎣3 4⎦⎠ ⎝⎣6 8⎦⎠\ +""" + assert pretty(Trace(X)) == ascii_str_1 + assert upretty(Trace(X)) == ucode_str_1 + + assert pretty(Trace(X) + Trace(Y)) == ascii_str_2 + assert upretty(Trace(X) + Trace(Y)) == ucode_str_2 + + +def test_MatrixSlice(): + n = Symbol('n', integer=True) + x, y, z, w, t, = symbols('x y z w t') + X = MatrixSymbol('X', n, n) + Y = MatrixSymbol('Y', 10, 10) + Z = MatrixSymbol('Z', 10, 10) + + expr = MatrixSlice(X, (None, None, None), (None, None, None)) + assert pretty(expr) == upretty(expr) == 'X[:, :]' + expr = X[x:x + 1, y:y + 1] + assert pretty(expr) == upretty(expr) == 'X[x:x + 1, y:y + 1]' + expr = X[x:x + 1:2, y:y + 1:2] + assert pretty(expr) == upretty(expr) == 'X[x:x + 1:2, y:y + 1:2]' + expr = X[:x, y:] + assert pretty(expr) == upretty(expr) == 'X[:x, y:]' + expr = X[:x, y:] + assert pretty(expr) == upretty(expr) == 'X[:x, y:]' + expr = X[x:, :y] + assert pretty(expr) == upretty(expr) == 'X[x:, :y]' + expr = X[x:y, z:w] + assert pretty(expr) == upretty(expr) == 'X[x:y, z:w]' + expr = X[x:y:t, w:t:x] + assert pretty(expr) == upretty(expr) == 'X[x:y:t, w:t:x]' + expr = X[x::y, t::w] + assert pretty(expr) == upretty(expr) == 'X[x::y, t::w]' + expr = X[:x:y, :t:w] + assert pretty(expr) == upretty(expr) == 'X[:x:y, :t:w]' + expr = X[::x, ::y] + assert pretty(expr) == upretty(expr) == 'X[::x, ::y]' + expr = MatrixSlice(X, (0, None, None), (0, None, None)) + assert pretty(expr) == upretty(expr) == 'X[:, :]' + expr = MatrixSlice(X, (None, n, None), (None, n, None)) + assert pretty(expr) == upretty(expr) == 'X[:, :]' + expr = MatrixSlice(X, (0, n, None), (0, n, None)) + assert pretty(expr) == upretty(expr) == 'X[:, :]' + expr = MatrixSlice(X, (0, n, 2), (0, n, 2)) + assert pretty(expr) == upretty(expr) == 'X[::2, ::2]' + expr = X[1:2:3, 4:5:6] + assert pretty(expr) == upretty(expr) == 'X[1:2:3, 4:5:6]' + expr = X[1:3:5, 4:6:8] + assert pretty(expr) == upretty(expr) == 'X[1:3:5, 4:6:8]' + expr = X[1:10:2] + assert pretty(expr) == upretty(expr) == 'X[1:10:2, :]' + expr = Y[:5, 1:9:2] + assert pretty(expr) == upretty(expr) == 'Y[:5, 1:9:2]' + expr = Y[:5, 1:10:2] + assert pretty(expr) == upretty(expr) == 'Y[:5, 1::2]' + expr = Y[5, :5:2] + assert pretty(expr) == upretty(expr) == 'Y[5:6, :5:2]' + expr = X[0:1, 0:1] + assert pretty(expr) == upretty(expr) == 'X[:1, :1]' + expr = X[0:1:2, 0:1:2] + assert pretty(expr) == upretty(expr) == 'X[:1:2, :1:2]' + expr = (Y + Z)[2:, 2:] + assert pretty(expr) == upretty(expr) == '(Y + Z)[2:, 2:]' + + +def test_MatrixExpressions(): + n = Symbol('n', integer=True) + X = MatrixSymbol('X', n, n) + + assert pretty(X) == upretty(X) == "X" + + # Apply function elementwise (`ElementwiseApplyFunc`): + + expr = (X.T*X).applyfunc(sin) + + ascii_str = """\ + / T \\\n\ +(d -> sin(d)).\\X *X/\ +""" + ucode_str = """\ + ⎛ T ⎞\n\ +(d ↦ sin(d))˳⎝X ⋅X⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + lamda = Lambda(x, 1/x) + expr = (n*X).applyfunc(lamda) + ascii_str = """\ +/ 1\\ \n\ +|x -> -|.(n*X)\n\ +\\ x/ \ +""" + ucode_str = """\ +⎛ 1⎞ \n\ +⎜x ↦ ─⎟˳(n⋅X)\n\ +⎝ x⎠ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_dotproduct(): + from sympy.matrices.expressions.dotproduct import DotProduct + n = symbols("n", integer=True) + A = MatrixSymbol('A', n, 1) + B = MatrixSymbol('B', n, 1) + C = Matrix(1, 3, [1, 2, 3]) + D = Matrix(1, 3, [1, 3, 4]) + + assert pretty(DotProduct(A, B)) == "A*B" + assert pretty(DotProduct(C, D)) == "[1 2 3]*[1 3 4]" + assert upretty(DotProduct(A, B)) == "A⋅B" + assert upretty(DotProduct(C, D)) == "[1 2 3]⋅[1 3 4]" + + +def test_pretty_Determinant(): + from sympy.matrices import Determinant, Inverse, BlockMatrix, OneMatrix, ZeroMatrix + m = Matrix(((1, 2), (3, 4))) + assert upretty(Determinant(m)) == '│1 2│\n│ │\n│3 4│' + assert upretty(Determinant(Inverse(m))) == \ + '│ -1│\n'\ + '│⎡1 2⎤ │\n'\ + '│⎢ ⎥ │\n'\ + '│⎣3 4⎦ │' + X = MatrixSymbol('X', 2, 2) + assert upretty(Determinant(X)) == '│X│' + assert upretty(Determinant(X + m)) == \ + '│⎡1 2⎤ │\n'\ + '│⎢ ⎥ + X│\n'\ + '│⎣3 4⎦ │' + assert upretty(Determinant(BlockMatrix(((OneMatrix(2, 2), X), + (m, ZeroMatrix(2, 2)))))) == \ + '│ 𝟙 X│\n'\ + '│ │\n'\ + '│⎡1 2⎤ │\n'\ + '│⎢ ⎥ 𝟘│\n'\ + '│⎣3 4⎦ │' + + +def test_pretty_piecewise(): + expr = Piecewise((x, x < 1), (x**2, True)) + ascii_str = \ +"""\ +/x for x < 1\n\ +| \n\ +< 2 \n\ +|x otherwise\n\ +\\ \ +""" + ucode_str = \ +"""\ +⎧x for x < 1\n\ +⎪ \n\ +⎨ 2 \n\ +⎪x otherwise\n\ +⎩ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = -Piecewise((x, x < 1), (x**2, True)) + ascii_str = \ +"""\ + //x for x < 1\\\n\ + || |\n\ +-|< 2 |\n\ + ||x otherwise|\n\ + \\\\ /\ +""" + ucode_str = \ +"""\ + ⎛⎧x for x < 1⎞\n\ + ⎜⎪ ⎟\n\ +-⎜⎨ 2 ⎟\n\ + ⎜⎪x otherwise⎟\n\ + ⎝⎩ ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = x + Piecewise((x, x > 0), (y, True)) + Piecewise((x/y, x < 2), + (y**2, x > 2), (1, True)) + 1 + ascii_str = \ +"""\ + //x \\ \n\ + ||- for x < 2| \n\ + ||y | \n\ + //x for x > 0\\ || | \n\ +x + |< | + |< 2 | + 1\n\ + \\\\y otherwise/ ||y for x > 2| \n\ + || | \n\ + ||1 otherwise| \n\ + \\\\ / \ +""" + ucode_str = \ +"""\ + ⎛⎧x ⎞ \n\ + ⎜⎪─ for x < 2⎟ \n\ + ⎜⎪y ⎟ \n\ + ⎛⎧x for x > 0⎞ ⎜⎪ ⎟ \n\ +x + ⎜⎨ ⎟ + ⎜⎨ 2 ⎟ + 1\n\ + ⎝⎩y otherwise⎠ ⎜⎪y for x > 2⎟ \n\ + ⎜⎪ ⎟ \n\ + ⎜⎪1 otherwise⎟ \n\ + ⎝⎩ ⎠ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = x - Piecewise((x, x > 0), (y, True)) + Piecewise((x/y, x < 2), + (y**2, x > 2), (1, True)) + 1 + ascii_str = \ +"""\ + //x \\ \n\ + ||- for x < 2| \n\ + ||y | \n\ + //x for x > 0\\ || | \n\ +x - |< | + |< 2 | + 1\n\ + \\\\y otherwise/ ||y for x > 2| \n\ + || | \n\ + ||1 otherwise| \n\ + \\\\ / \ +""" + ucode_str = \ +"""\ + ⎛⎧x ⎞ \n\ + ⎜⎪─ for x < 2⎟ \n\ + ⎜⎪y ⎟ \n\ + ⎛⎧x for x > 0⎞ ⎜⎪ ⎟ \n\ +x - ⎜⎨ ⎟ + ⎜⎨ 2 ⎟ + 1\n\ + ⎝⎩y otherwise⎠ ⎜⎪y for x > 2⎟ \n\ + ⎜⎪ ⎟ \n\ + ⎜⎪1 otherwise⎟ \n\ + ⎝⎩ ⎠ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = x*Piecewise((x, x > 0), (y, True)) + ascii_str = \ +"""\ + //x for x > 0\\\n\ +x*|< |\n\ + \\\\y otherwise/\ +""" + ucode_str = \ +"""\ + ⎛⎧x for x > 0⎞\n\ +x⋅⎜⎨ ⎟\n\ + ⎝⎩y otherwise⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Piecewise((x, x > 0), (y, True))*Piecewise((x/y, x < 2), (y**2, x > + 2), (1, True)) + ascii_str = \ +"""\ + //x \\\n\ + ||- for x < 2|\n\ + ||y |\n\ +//x for x > 0\\ || |\n\ +|< |*|< 2 |\n\ +\\\\y otherwise/ ||y for x > 2|\n\ + || |\n\ + ||1 otherwise|\n\ + \\\\ /\ +""" + ucode_str = \ +"""\ + ⎛⎧x ⎞\n\ + ⎜⎪─ for x < 2⎟\n\ + ⎜⎪y ⎟\n\ +⎛⎧x for x > 0⎞ ⎜⎪ ⎟\n\ +⎜⎨ ⎟⋅⎜⎨ 2 ⎟\n\ +⎝⎩y otherwise⎠ ⎜⎪y for x > 2⎟\n\ + ⎜⎪ ⎟\n\ + ⎜⎪1 otherwise⎟\n\ + ⎝⎩ ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = -Piecewise((x, x > 0), (y, True))*Piecewise((x/y, x < 2), (y**2, x + > 2), (1, True)) + ascii_str = \ +"""\ + //x \\\n\ + ||- for x < 2|\n\ + ||y |\n\ + //x for x > 0\\ || |\n\ +-|< |*|< 2 |\n\ + \\\\y otherwise/ ||y for x > 2|\n\ + || |\n\ + ||1 otherwise|\n\ + \\\\ /\ +""" + ucode_str = \ +"""\ + ⎛⎧x ⎞\n\ + ⎜⎪─ for x < 2⎟\n\ + ⎜⎪y ⎟\n\ + ⎛⎧x for x > 0⎞ ⎜⎪ ⎟\n\ +-⎜⎨ ⎟⋅⎜⎨ 2 ⎟\n\ + ⎝⎩y otherwise⎠ ⎜⎪y for x > 2⎟\n\ + ⎜⎪ ⎟\n\ + ⎜⎪1 otherwise⎟\n\ + ⎝⎩ ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Piecewise((0, Abs(1/y) < 1), (1, Abs(y) < 1), (y*meijerg(((2, 1), + ()), ((), (1, 0)), 1/y), True)) + ascii_str = \ +"""\ +/ 1 \n\ +| 0 for --- < 1\n\ +| |y| \n\ +| \n\ +< 1 for |y| < 1\n\ +| \n\ +| __0, 2 /1, 2 | 1\\ \n\ +|y*/__ | | -| otherwise \n\ +\\ \\_|2, 2 \\ 0, 1 | y/ \ +""" + ucode_str = \ +"""\ +⎧ 1 \n\ +⎪ 0 for ─── < 1\n\ +⎪ │y│ \n\ +⎪ \n\ +⎨ 1 for │y│ < 1\n\ +⎪ \n\ +⎪ ╭─╮0, 2 ⎛1, 2 │ 1⎞ \n\ +⎪y⋅│╶┐ ⎜ │ ─⎟ otherwise \n\ +⎩ ╰─╯2, 2 ⎝ 0, 1 │ y⎠ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + # XXX: We have to use evaluate=False here because Piecewise._eval_power + # denests the power. + expr = Pow(Piecewise((x, x > 0), (y, True)), 2, evaluate=False) + ascii_str = \ +"""\ + 2\n\ +//x for x > 0\\ \n\ +|< | \n\ +\\\\y otherwise/ \ +""" + ucode_str = \ +"""\ + 2\n\ +⎛⎧x for x > 0⎞ \n\ +⎜⎨ ⎟ \n\ +⎝⎩y otherwise⎠ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_ITE(): + expr = ITE(x, y, z) + assert pretty(expr) == ( + '/y for x \n' + '< \n' + '\\z otherwise' + ) + assert upretty(expr) == """\ +⎧y for x \n\ +⎨ \n\ +⎩z otherwise\ +""" + + +def test_pretty_seq(): + expr = () + ascii_str = \ +"""\ +()\ +""" + ucode_str = \ +"""\ +()\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = [] + ascii_str = \ +"""\ +[]\ +""" + ucode_str = \ +"""\ +[]\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = {} + expr_2 = {} + ascii_str = \ +"""\ +{}\ +""" + ucode_str = \ +"""\ +{}\ +""" + assert pretty(expr) == ascii_str + assert pretty(expr_2) == ascii_str + assert upretty(expr) == ucode_str + assert upretty(expr_2) == ucode_str + + expr = (1/x,) + ascii_str = \ +"""\ + 1 \n\ +(-,)\n\ + x \ +""" + ucode_str = \ +"""\ +⎛1 ⎞\n\ +⎜─,⎟\n\ +⎝x ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = [x**2, 1/x, x, y, sin(th)**2/cos(ph)**2] + ascii_str = \ +"""\ + 2 \n\ + 2 1 sin (theta) \n\ +[x , -, x, y, -----------]\n\ + x 2 \n\ + cos (phi) \ +""" + ucode_str = \ +"""\ +⎡ 2 ⎤\n\ +⎢ 2 1 sin (θ)⎥\n\ +⎢x , ─, x, y, ───────⎥\n\ +⎢ x 2 ⎥\n\ +⎣ cos (φ)⎦\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (x**2, 1/x, x, y, sin(th)**2/cos(ph)**2) + ascii_str = \ +"""\ + 2 \n\ + 2 1 sin (theta) \n\ +(x , -, x, y, -----------)\n\ + x 2 \n\ + cos (phi) \ +""" + ucode_str = \ +"""\ +⎛ 2 ⎞\n\ +⎜ 2 1 sin (θ)⎟\n\ +⎜x , ─, x, y, ───────⎟\n\ +⎜ x 2 ⎟\n\ +⎝ cos (φ)⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Tuple(x**2, 1/x, x, y, sin(th)**2/cos(ph)**2) + ascii_str = \ +"""\ + 2 \n\ + 2 1 sin (theta) \n\ +(x , -, x, y, -----------)\n\ + x 2 \n\ + cos (phi) \ +""" + ucode_str = \ +"""\ +⎛ 2 ⎞\n\ +⎜ 2 1 sin (θ)⎟\n\ +⎜x , ─, x, y, ───────⎟\n\ +⎜ x 2 ⎟\n\ +⎝ cos (φ)⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = {x: sin(x)} + expr_2 = Dict({x: sin(x)}) + ascii_str = \ +"""\ +{x: sin(x)}\ +""" + ucode_str = \ +"""\ +{x: sin(x)}\ +""" + assert pretty(expr) == ascii_str + assert pretty(expr_2) == ascii_str + assert upretty(expr) == ucode_str + assert upretty(expr_2) == ucode_str + + expr = {1/x: 1/y, x: sin(x)**2} + expr_2 = Dict({1/x: 1/y, x: sin(x)**2}) + ascii_str = \ +"""\ + 1 1 2 \n\ +{-: -, x: sin (x)}\n\ + x y \ +""" + ucode_str = \ +"""\ +⎧1 1 2 ⎫\n\ +⎨─: ─, x: sin (x)⎬\n\ +⎩x y ⎭\ +""" + assert pretty(expr) == ascii_str + assert pretty(expr_2) == ascii_str + assert upretty(expr) == ucode_str + assert upretty(expr_2) == ucode_str + + # There used to be a bug with pretty-printing sequences of even height. + expr = [x**2] + ascii_str = \ +"""\ + 2 \n\ +[x ]\ +""" + ucode_str = \ +"""\ +⎡ 2⎤\n\ +⎣x ⎦\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (x**2,) + ascii_str = \ +"""\ + 2 \n\ +(x ,)\ +""" + ucode_str = \ +"""\ +⎛ 2 ⎞\n\ +⎝x ,⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Tuple(x**2) + ascii_str = \ +"""\ + 2 \n\ +(x ,)\ +""" + ucode_str = \ +"""\ +⎛ 2 ⎞\n\ +⎝x ,⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = {x**2: 1} + expr_2 = Dict({x**2: 1}) + ascii_str = \ +"""\ + 2 \n\ +{x : 1}\ +""" + ucode_str = \ +"""\ +⎧ 2 ⎫\n\ +⎨x : 1⎬\n\ +⎩ ⎭\ +""" + assert pretty(expr) == ascii_str + assert pretty(expr_2) == ascii_str + assert upretty(expr) == ucode_str + assert upretty(expr_2) == ucode_str + + +def test_any_object_in_sequence(): + # Cf. issue 5306 + b1 = Basic() + b2 = Basic(Basic()) + + expr = [b2, b1] + assert pretty(expr) == "[Basic(Basic()), Basic()]" + assert upretty(expr) == "[Basic(Basic()), Basic()]" + + expr = {b2, b1} + assert pretty(expr) == "{Basic(), Basic(Basic())}" + assert upretty(expr) == "{Basic(), Basic(Basic())}" + + expr = {b2: b1, b1: b2} + expr2 = Dict({b2: b1, b1: b2}) + assert pretty(expr) == "{Basic(): Basic(Basic()), Basic(Basic()): Basic()}" + assert pretty( + expr2) == "{Basic(): Basic(Basic()), Basic(Basic()): Basic()}" + assert upretty( + expr) == "{Basic(): Basic(Basic()), Basic(Basic()): Basic()}" + assert upretty( + expr2) == "{Basic(): Basic(Basic()), Basic(Basic()): Basic()}" + + +def test_print_builtin_set(): + assert pretty(set()) == 'set()' + assert upretty(set()) == 'set()' + + assert pretty(frozenset()) == 'frozenset()' + assert upretty(frozenset()) == 'frozenset()' + + s1 = {1/x, x} + s2 = frozenset(s1) + + assert pretty(s1) == \ +"""\ + 1 \n\ +{-, x} + x \ +""" + assert upretty(s1) == \ +"""\ +⎧1 ⎫ +⎨─, x⎬ +⎩x ⎭\ +""" + + assert pretty(s2) == \ +"""\ + 1 \n\ +frozenset({-, x}) + x \ +""" + assert upretty(s2) == \ +"""\ + ⎛⎧1 ⎫⎞ +frozenset⎜⎨─, x⎬⎟ + ⎝⎩x ⎭⎠\ +""" + + +def test_pretty_sets(): + s = FiniteSet + assert pretty(s(*[x*y, x**2])) == \ +"""\ + 2 \n\ +{x , x*y}\ +""" + assert pretty(s(*range(1, 6))) == "{1, 2, 3, 4, 5}" + assert pretty(s(*range(1, 13))) == "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" + + assert pretty({x*y, x**2}) == \ +"""\ + 2 \n\ +{x , x*y}\ +""" + assert pretty(set(range(1, 6))) == "{1, 2, 3, 4, 5}" + assert pretty(set(range(1, 13))) == \ + "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" + + assert pretty(frozenset([x*y, x**2])) == \ +"""\ + 2 \n\ +frozenset({x , x*y})\ +""" + assert pretty(frozenset(range(1, 6))) == "frozenset({1, 2, 3, 4, 5})" + assert pretty(frozenset(range(1, 13))) == \ + "frozenset({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})" + + assert pretty(Range(0, 3, 1)) == '{0, 1, 2}' + + ascii_str = '{0, 1, ..., 29}' + ucode_str = '{0, 1, …, 29}' + assert pretty(Range(0, 30, 1)) == ascii_str + assert upretty(Range(0, 30, 1)) == ucode_str + + ascii_str = '{30, 29, ..., 2}' + ucode_str = '{30, 29, …, 2}' + assert pretty(Range(30, 1, -1)) == ascii_str + assert upretty(Range(30, 1, -1)) == ucode_str + + ascii_str = '{0, 2, ...}' + ucode_str = '{0, 2, …}' + assert pretty(Range(0, oo, 2)) == ascii_str + assert upretty(Range(0, oo, 2)) == ucode_str + + ascii_str = '{..., 2, 0}' + ucode_str = '{…, 2, 0}' + assert pretty(Range(oo, -2, -2)) == ascii_str + assert upretty(Range(oo, -2, -2)) == ucode_str + + ascii_str = '{-2, -3, ...}' + ucode_str = '{-2, -3, …}' + assert pretty(Range(-2, -oo, -1)) == ascii_str + assert upretty(Range(-2, -oo, -1)) == ucode_str + + +def test_pretty_SetExpr(): + iv = Interval(1, 3) + se = SetExpr(iv) + ascii_str = "SetExpr([1, 3])" + ucode_str = "SetExpr([1, 3])" + assert pretty(se) == ascii_str + assert upretty(se) == ucode_str + + +def test_pretty_ImageSet(): + imgset = ImageSet(Lambda((x, y), x + y), {1, 2, 3}, {3, 4}) + ascii_str = '{x + y | x in {1, 2, 3}, y in {3, 4}}' + ucode_str = '{x + y │ x ∊ {1, 2, 3}, y ∊ {3, 4}}' + assert pretty(imgset) == ascii_str + assert upretty(imgset) == ucode_str + + imgset = ImageSet(Lambda(((x, y),), x + y), ProductSet({1, 2, 3}, {3, 4})) + ascii_str = '{x + y | (x, y) in {1, 2, 3} x {3, 4}}' + ucode_str = '{x + y │ (x, y) ∊ {1, 2, 3} × {3, 4}}' + assert pretty(imgset) == ascii_str + assert upretty(imgset) == ucode_str + + imgset = ImageSet(Lambda(x, x**2), S.Naturals) + ascii_str = '''\ + 2 \n\ +{x | x in Naturals}''' + ucode_str = '''\ +⎧ 2 │ ⎫\n\ +⎨x │ x ∊ ℕ⎬\n\ +⎩ │ ⎭''' + assert pretty(imgset) == ascii_str + assert upretty(imgset) == ucode_str + + # TODO: The "x in N" parts below should be centered independently of the + # 1/x**2 fraction + imgset = ImageSet(Lambda(x, 1/x**2), S.Naturals) + ascii_str = '''\ + 1 \n\ +{-- | x in Naturals} + 2 \n\ + x ''' + ucode_str = '''\ +⎧1 │ ⎫\n\ +⎪── │ x ∊ ℕ⎪\n\ +⎨ 2 │ ⎬\n\ +⎪x │ ⎪\n\ +⎩ │ ⎭''' + assert pretty(imgset) == ascii_str + assert upretty(imgset) == ucode_str + + imgset = ImageSet(Lambda((x, y), 1/(x + y)**2), S.Naturals, S.Naturals) + ascii_str = '''\ + 1 \n\ +{-------- | x in Naturals, y in Naturals} + 2 \n\ + (x + y) ''' + ucode_str = '''\ +⎧ 1 │ ⎫ +⎪──────── │ x ∊ ℕ, y ∊ ℕ⎪ +⎨ 2 │ ⎬ +⎪(x + y) │ ⎪ +⎩ │ ⎭''' + assert pretty(imgset) == ascii_str + assert upretty(imgset) == ucode_str + + # issue 23449 centering issue + assert upretty([Symbol("ihat") / (Symbol("i") + 1)]) == '''\ +⎡ î ⎤ +⎢─────⎥ +⎣i + 1⎦\ +''' + assert upretty(Matrix([Symbol("ihat"), Symbol("i") + 1])) == '''\ +⎡ î ⎤ +⎢ ⎥ +⎣i + 1⎦\ +''' + + +def test_pretty_ConditionSet(): + ascii_str = '{x | x in (-oo, oo) and sin(x) = 0}' + ucode_str = '{x │ x ∊ ℝ ∧ (sin(x) = 0)}' + assert pretty(ConditionSet(x, Eq(sin(x), 0), S.Reals)) == ascii_str + assert upretty(ConditionSet(x, Eq(sin(x), 0), S.Reals)) == ucode_str + + assert pretty(ConditionSet(x, Contains(x, S.Reals, evaluate=False), FiniteSet(1))) == '{1}' + assert upretty(ConditionSet(x, Contains(x, S.Reals, evaluate=False), FiniteSet(1))) == '{1}' + + assert pretty(ConditionSet(x, And(x > 1, x < -1), FiniteSet(1, 2, 3))) == "EmptySet" + assert upretty(ConditionSet(x, And(x > 1, x < -1), FiniteSet(1, 2, 3))) == "∅" + + assert pretty(ConditionSet(x, Or(x > 1, x < -1), FiniteSet(1, 2))) == '{2}' + assert upretty(ConditionSet(x, Or(x > 1, x < -1), FiniteSet(1, 2))) == '{2}' + + condset = ConditionSet(x, 1/x**2 > 0) + ascii_str = '''\ + 1 \n\ +{x | -- > 0} + 2 \n\ + x ''' + ucode_str = '''\ +⎧ │ ⎛1 ⎞⎫ +⎪x │ ⎜── > 0⎟⎪ +⎨ │ ⎜ 2 ⎟⎬ +⎪ │ ⎝x ⎠⎪ +⎩ │ ⎭''' + assert pretty(condset) == ascii_str + assert upretty(condset) == ucode_str + + condset = ConditionSet(x, 1/x**2 > 0, S.Reals) + ascii_str = '''\ + 1 \n\ +{x | x in (-oo, oo) and -- > 0} + 2 \n\ + x ''' + ucode_str = '''\ +⎧ │ ⎛1 ⎞⎫ +⎪x │ x ∊ ℝ ∧ ⎜── > 0⎟⎪ +⎨ │ ⎜ 2 ⎟⎬ +⎪ │ ⎝x ⎠⎪ +⎩ │ ⎭''' + assert pretty(condset) == ascii_str + assert upretty(condset) == ucode_str + + +def test_pretty_ComplexRegion(): + from sympy.sets.fancysets import ComplexRegion + cregion = ComplexRegion(Interval(3, 5)*Interval(4, 6)) + ascii_str = '{x + y*I | x, y in [3, 5] x [4, 6]}' + ucode_str = '{x + y⋅ⅈ │ x, y ∊ [3, 5] × [4, 6]}' + assert pretty(cregion) == ascii_str + assert upretty(cregion) == ucode_str + + cregion = ComplexRegion(Interval(0, 1)*Interval(0, 2*pi), polar=True) + ascii_str = '{r*(I*sin(theta) + cos(theta)) | r, theta in [0, 1] x [0, 2*pi)}' + ucode_str = '{r⋅(ⅈ⋅sin(θ) + cos(θ)) │ r, θ ∊ [0, 1] × [0, 2⋅π)}' + assert pretty(cregion) == ascii_str + assert upretty(cregion) == ucode_str + + cregion = ComplexRegion(Interval(3, 1/a**2)*Interval(4, 6)) + ascii_str = '''\ + 1 \n\ +{x + y*I | x, y in [3, --] x [4, 6]} + 2 \n\ + a ''' + ucode_str = '''\ +⎧ │ ⎡ 1 ⎤ ⎫ +⎪x + y⋅ⅈ │ x, y ∊ ⎢3, ──⎥ × [4, 6]⎪ +⎨ │ ⎢ 2⎥ ⎬ +⎪ │ ⎣ a ⎦ ⎪ +⎩ │ ⎭''' + assert pretty(cregion) == ascii_str + assert upretty(cregion) == ucode_str + + cregion = ComplexRegion(Interval(0, 1/a**2)*Interval(0, 2*pi), polar=True) + ascii_str = '''\ + 1 \n\ +{r*(I*sin(theta) + cos(theta)) | r, theta in [0, --] x [0, 2*pi)} + 2 \n\ + a ''' + ucode_str = '''\ +⎧ │ ⎡ 1 ⎤ ⎫ +⎪r⋅(ⅈ⋅sin(θ) + cos(θ)) │ r, θ ∊ ⎢0, ──⎥ × [0, 2⋅π)⎪ +⎨ │ ⎢ 2⎥ ⎬ +⎪ │ ⎣ a ⎦ ⎪ +⎩ │ ⎭''' + assert pretty(cregion) == ascii_str + assert upretty(cregion) == ucode_str + + +def test_pretty_Union_issue_10414(): + a, b = Interval(2, 3), Interval(4, 7) + ucode_str = '[2, 3] ∪ [4, 7]' + ascii_str = '[2, 3] U [4, 7]' + assert upretty(Union(a, b)) == ucode_str + assert pretty(Union(a, b)) == ascii_str + + +def test_pretty_Intersection_issue_10414(): + x, y, z, w = symbols('x, y, z, w') + a, b = Interval(x, y), Interval(z, w) + ucode_str = '[x, y] ∩ [z, w]' + ascii_str = '[x, y] n [z, w]' + assert upretty(Intersection(a, b)) == ucode_str + assert pretty(Intersection(a, b)) == ascii_str + + +def test_ProductSet_exponent(): + ucode_str = ' 1\n[0, 1] ' + assert upretty(Interval(0, 1)**1) == ucode_str + ucode_str = ' 2\n[0, 1] ' + assert upretty(Interval(0, 1)**2) == ucode_str + + +def test_ProductSet_parenthesis(): + ucode_str = '([4, 7] × {1, 2}) ∪ ([2, 3] × [4, 7])' + + a, b = Interval(2, 3), Interval(4, 7) + assert upretty(Union(a*b, b*FiniteSet(1, 2))) == ucode_str + + +def test_ProductSet_prod_char_issue_10413(): + ascii_str = '[2, 3] x [4, 7]' + ucode_str = '[2, 3] × [4, 7]' + + a, b = Interval(2, 3), Interval(4, 7) + assert pretty(a*b) == ascii_str + assert upretty(a*b) == ucode_str + + +def test_pretty_sequences(): + s1 = SeqFormula(a**2, (0, oo)) + s2 = SeqPer((1, 2)) + + ascii_str = '[0, 1, 4, 9, ...]' + ucode_str = '[0, 1, 4, 9, …]' + + assert pretty(s1) == ascii_str + assert upretty(s1) == ucode_str + + ascii_str = '[1, 2, 1, 2, ...]' + ucode_str = '[1, 2, 1, 2, …]' + assert pretty(s2) == ascii_str + assert upretty(s2) == ucode_str + + s3 = SeqFormula(a**2, (0, 2)) + s4 = SeqPer((1, 2), (0, 2)) + + ascii_str = '[0, 1, 4]' + ucode_str = '[0, 1, 4]' + + assert pretty(s3) == ascii_str + assert upretty(s3) == ucode_str + + ascii_str = '[1, 2, 1]' + ucode_str = '[1, 2, 1]' + assert pretty(s4) == ascii_str + assert upretty(s4) == ucode_str + + s5 = SeqFormula(a**2, (-oo, 0)) + s6 = SeqPer((1, 2), (-oo, 0)) + + ascii_str = '[..., 9, 4, 1, 0]' + ucode_str = '[…, 9, 4, 1, 0]' + + assert pretty(s5) == ascii_str + assert upretty(s5) == ucode_str + + ascii_str = '[..., 2, 1, 2, 1]' + ucode_str = '[…, 2, 1, 2, 1]' + assert pretty(s6) == ascii_str + assert upretty(s6) == ucode_str + + ascii_str = '[1, 3, 5, 11, ...]' + ucode_str = '[1, 3, 5, 11, …]' + + assert pretty(SeqAdd(s1, s2)) == ascii_str + assert upretty(SeqAdd(s1, s2)) == ucode_str + + ascii_str = '[1, 3, 5]' + ucode_str = '[1, 3, 5]' + + assert pretty(SeqAdd(s3, s4)) == ascii_str + assert upretty(SeqAdd(s3, s4)) == ucode_str + + ascii_str = '[..., 11, 5, 3, 1]' + ucode_str = '[…, 11, 5, 3, 1]' + + assert pretty(SeqAdd(s5, s6)) == ascii_str + assert upretty(SeqAdd(s5, s6)) == ucode_str + + ascii_str = '[0, 2, 4, 18, ...]' + ucode_str = '[0, 2, 4, 18, …]' + + assert pretty(SeqMul(s1, s2)) == ascii_str + assert upretty(SeqMul(s1, s2)) == ucode_str + + ascii_str = '[0, 2, 4]' + ucode_str = '[0, 2, 4]' + + assert pretty(SeqMul(s3, s4)) == ascii_str + assert upretty(SeqMul(s3, s4)) == ucode_str + + ascii_str = '[..., 18, 4, 2, 0]' + ucode_str = '[…, 18, 4, 2, 0]' + + assert pretty(SeqMul(s5, s6)) == ascii_str + assert upretty(SeqMul(s5, s6)) == ucode_str + + # Sequences with symbolic limits, issue 12629 + s7 = SeqFormula(a**2, (a, 0, x)) + raises(NotImplementedError, lambda: pretty(s7)) + raises(NotImplementedError, lambda: upretty(s7)) + + b = Symbol('b') + s8 = SeqFormula(b*a**2, (a, 0, 2)) + ascii_str = '[0, b, 4*b]' + ucode_str = '[0, b, 4⋅b]' + assert pretty(s8) == ascii_str + assert upretty(s8) == ucode_str + + +def test_pretty_FourierSeries(): + f = fourier_series(x, (x, -pi, pi)) + + ascii_str = \ +"""\ + 2*sin(3*x) \n\ +2*sin(x) - sin(2*x) + ---------- + ...\n\ + 3 \ +""" + + ucode_str = \ +"""\ + 2⋅sin(3⋅x) \n\ +2⋅sin(x) - sin(2⋅x) + ────────── + …\n\ + 3 \ +""" + + assert pretty(f) == ascii_str + assert upretty(f) == ucode_str + + +def test_pretty_FormalPowerSeries(): + f = fps(log(1 + x)) + + + ascii_str = \ +"""\ + oo \n\ +____ \n\ +\\ ` \n\ + \\ -k k \n\ + \\ -(-1) *x \n\ + / -----------\n\ + / k \n\ +/___, \n\ +k = 1 \ +""" + + ucode_str = \ +"""\ + ∞ \n\ +____ \n\ +╲ \n\ + ╲ -k k \n\ + ╲ -(-1) ⋅x \n\ + ╱ ───────────\n\ + ╱ k \n\ +╱ \n\ +‾‾‾‾ \n\ +k = 1 \ +""" + + assert pretty(f) == ascii_str + assert upretty(f) == ucode_str + + +def test_pretty_limits(): + expr = Limit(x, x, oo) + ascii_str = \ +"""\ + lim x\n\ +x->oo \ +""" + ucode_str = \ +"""\ +lim x\n\ +x─→∞ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Limit(x**2, x, 0) + ascii_str = \ +"""\ + 2\n\ + lim x \n\ +x->0+ \ +""" + ucode_str = \ +"""\ + 2\n\ + lim x \n\ +x─→0⁺ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Limit(1/x, x, 0) + ascii_str = \ +"""\ + 1\n\ + lim -\n\ +x->0+x\ +""" + ucode_str = \ +"""\ + 1\n\ + lim ─\n\ +x─→0⁺x\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Limit(sin(x)/x, x, 0) + ascii_str = \ +"""\ + /sin(x)\\\n\ + lim |------|\n\ +x->0+\\ x /\ +""" + ucode_str = \ +"""\ + ⎛sin(x)⎞\n\ + lim ⎜──────⎟\n\ +x─→0⁺⎝ x ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Limit(sin(x)/x, x, 0, "-") + ascii_str = \ +"""\ + /sin(x)\\\n\ + lim |------|\n\ +x->0-\\ x /\ +""" + ucode_str = \ +"""\ + ⎛sin(x)⎞\n\ + lim ⎜──────⎟\n\ +x─→0⁻⎝ x ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Limit(x + sin(x), x, 0) + ascii_str = \ +"""\ + lim (x + sin(x))\n\ +x->0+ \ +""" + ucode_str = \ +"""\ + lim (x + sin(x))\n\ +x─→0⁺ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Limit(x, x, 0)**2 + ascii_str = \ +"""\ + 2\n\ +/ lim x\\ \n\ +\\x->0+ / \ +""" + ucode_str = \ +"""\ + 2\n\ +⎛ lim x⎞ \n\ +⎝x─→0⁺ ⎠ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Limit(x*Limit(y/2,y,0), x, 0) + ascii_str = \ +"""\ + / /y\\\\\n\ + lim |x* lim |-||\n\ +x->0+\\ y->0+\\2//\ +""" + ucode_str = \ +"""\ + ⎛ ⎛y⎞⎞\n\ + lim ⎜x⋅ lim ⎜─⎟⎟\n\ +x─→0⁺⎝ y─→0⁺⎝2⎠⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = 2*Limit(x*Limit(y/2,y,0), x, 0) + ascii_str = \ +"""\ + / /y\\\\\n\ +2* lim |x* lim |-||\n\ + x->0+\\ y->0+\\2//\ +""" + ucode_str = \ +"""\ + ⎛ ⎛y⎞⎞\n\ +2⋅ lim ⎜x⋅ lim ⎜─⎟⎟\n\ + x─→0⁺⎝ y─→0⁺⎝2⎠⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Limit(sin(x), x, 0, dir='+-') + ascii_str = \ +"""\ +lim sin(x)\n\ +x->0 \ +""" + ucode_str = \ +"""\ +lim sin(x)\n\ +x─→0 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_ComplexRootOf(): + expr = rootof(x**5 + 11*x - 2, 0) + ascii_str = \ +"""\ + / 5 \\\n\ +CRootOf\\x + 11*x - 2, 0/\ +""" + ucode_str = \ +"""\ + ⎛ 5 ⎞\n\ +CRootOf⎝x + 11⋅x - 2, 0⎠\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_RootSum(): + expr = RootSum(x**5 + 11*x - 2, auto=False) + ascii_str = \ +"""\ + / 5 \\\n\ +RootSum\\x + 11*x - 2/\ +""" + ucode_str = \ +"""\ + ⎛ 5 ⎞\n\ +RootSum⎝x + 11⋅x - 2⎠\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = RootSum(x**5 + 11*x - 2, Lambda(z, exp(z))) + ascii_str = \ +"""\ + / 5 z\\\n\ +RootSum\\x + 11*x - 2, z -> e /\ +""" + ucode_str = \ +"""\ + ⎛ 5 z⎞\n\ +RootSum⎝x + 11⋅x - 2, z ↦ ℯ ⎠\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_GroebnerBasis(): + expr = groebner([], x, y) + + ascii_str = \ +"""\ +GroebnerBasis([], x, y, domain=ZZ, order=lex)\ +""" + ucode_str = \ +"""\ +GroebnerBasis([], x, y, domain=ℤ, order=lex)\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + F = [x**2 - 3*y - x + 1, y**2 - 2*x + y - 1] + expr = groebner(F, x, y, order='grlex') + + ascii_str = \ +"""\ + /[ 2 2 ] \\\n\ +GroebnerBasis\\[x - x - 3*y + 1, y - 2*x + y - 1], x, y, domain=ZZ, order=grlex/\ +""" + ucode_str = \ +"""\ + ⎛⎡ 2 2 ⎤ ⎞\n\ +GroebnerBasis⎝⎣x - x - 3⋅y + 1, y - 2⋅x + y - 1⎦, x, y, domain=ℤ, order=grlex⎠\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = expr.fglm('lex') + + ascii_str = \ +"""\ + /[ 2 4 3 2 ] \\\n\ +GroebnerBasis\\[2*x - y - y + 1, y + 2*y - 3*y - 16*y + 7], x, y, domain=ZZ, order=lex/\ +""" + ucode_str = \ +"""\ + ⎛⎡ 2 4 3 2 ⎤ ⎞\n\ +GroebnerBasis⎝⎣2⋅x - y - y + 1, y + 2⋅y - 3⋅y - 16⋅y + 7⎦, x, y, domain=ℤ, order=lex⎠\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_UniversalSet(): + assert pretty(S.UniversalSet) == "UniversalSet" + assert upretty(S.UniversalSet) == '𝕌' + + +def test_pretty_Boolean(): + expr = Not(x, evaluate=False) + + assert pretty(expr) == "Not(x)" + assert upretty(expr) == "¬x" + + expr = And(x, y) + + assert pretty(expr) == "And(x, y)" + assert upretty(expr) == "x ∧ y" + + expr = Or(x, y) + + assert pretty(expr) == "Or(x, y)" + assert upretty(expr) == "x ∨ y" + + syms = symbols('a:f') + expr = And(*syms) + + assert pretty(expr) == "And(a, b, c, d, e, f)" + assert upretty(expr) == "a ∧ b ∧ c ∧ d ∧ e ∧ f" + + expr = Or(*syms) + + assert pretty(expr) == "Or(a, b, c, d, e, f)" + assert upretty(expr) == "a ∨ b ∨ c ∨ d ∨ e ∨ f" + + expr = Xor(x, y, evaluate=False) + + assert pretty(expr) == "Xor(x, y)" + assert upretty(expr) == "x ⊻ y" + + expr = Nand(x, y, evaluate=False) + + assert pretty(expr) == "Nand(x, y)" + assert upretty(expr) == "x ⊼ y" + + expr = Nor(x, y, evaluate=False) + + assert pretty(expr) == "Nor(x, y)" + assert upretty(expr) == "x ⊽ y" + + expr = Implies(x, y, evaluate=False) + + assert pretty(expr) == "Implies(x, y)" + assert upretty(expr) == "x → y" + + # don't sort args + expr = Implies(y, x, evaluate=False) + + assert pretty(expr) == "Implies(y, x)" + assert upretty(expr) == "y → x" + + expr = Equivalent(x, y, evaluate=False) + + assert pretty(expr) == "Equivalent(x, y)" + assert upretty(expr) == "x ⇔ y" + + expr = Equivalent(y, x, evaluate=False) + + assert pretty(expr) == "Equivalent(x, y)" + assert upretty(expr) == "x ⇔ y" + + +def test_pretty_Domain(): + expr = FF(23) + + assert pretty(expr) == "GF(23)" + assert upretty(expr) == "ℤ₂₃" + + expr = ZZ + + assert pretty(expr) == "ZZ" + assert upretty(expr) == "ℤ" + + expr = QQ + + assert pretty(expr) == "QQ" + assert upretty(expr) == "ℚ" + + expr = RR + + assert pretty(expr) == "RR" + assert upretty(expr) == "ℝ" + + expr = QQ[x] + + assert pretty(expr) == "QQ[x]" + assert upretty(expr) == "ℚ[x]" + + expr = QQ[x, y] + + assert pretty(expr) == "QQ[x, y]" + assert upretty(expr) == "ℚ[x, y]" + + expr = ZZ.frac_field(x) + + assert pretty(expr) == "ZZ(x)" + assert upretty(expr) == "ℤ(x)" + + expr = ZZ.frac_field(x, y) + + assert pretty(expr) == "ZZ(x, y)" + assert upretty(expr) == "ℤ(x, y)" + + expr = QQ.poly_ring(x, y, order=grlex) + + assert pretty(expr) == "QQ[x, y, order=grlex]" + assert upretty(expr) == "ℚ[x, y, order=grlex]" + + expr = QQ.poly_ring(x, y, order=ilex) + + assert pretty(expr) == "QQ[x, y, order=ilex]" + assert upretty(expr) == "ℚ[x, y, order=ilex]" + + +def test_pretty_prec(): + assert xpretty(S("0.3"), full_prec=True, wrap_line=False) == "0.300000000000000" + assert xpretty(S("0.3"), full_prec="auto", wrap_line=False) == "0.300000000000000" + assert xpretty(S("0.3"), full_prec=False, wrap_line=False) == "0.3" + assert xpretty(S("0.3")*x, full_prec=True, use_unicode=False, wrap_line=False) in [ + "0.300000000000000*x", + "x*0.300000000000000" + ] + assert xpretty(S("0.3")*x, full_prec="auto", use_unicode=False, wrap_line=False) in [ + "0.3*x", + "x*0.3" + ] + assert xpretty(S("0.3")*x, full_prec=False, use_unicode=False, wrap_line=False) in [ + "0.3*x", + "x*0.3" + ] + + +def test_pprint(): + import sys + from io import StringIO + fd = StringIO() + sso = sys.stdout + sys.stdout = fd + try: + pprint(pi, use_unicode=False, wrap_line=False) + finally: + sys.stdout = sso + assert fd.getvalue() == 'pi\n' + + +def test_pretty_class(): + """Test that the printer dispatcher correctly handles classes.""" + class C: + pass # C has no .__class__ and this was causing problems + + class D: + pass + + assert pretty( C ) == str( C ) + assert pretty( D ) == str( D ) + + +def test_pretty_no_wrap_line(): + huge_expr = 0 + for i in range(20): + huge_expr += i*sin(i + x) + assert xpretty(huge_expr ).find('\n') != -1 + assert xpretty(huge_expr, wrap_line=False).find('\n') == -1 + + +def test_settings(): + raises(TypeError, lambda: pretty(S(4), method="garbage")) + + +def test_pretty_sum(): + from sympy.abc import x, a, b, k, m, n + + expr = Sum(k**k, (k, 0, n)) + ascii_str = \ +"""\ + n \n\ +___ \n\ +\\ ` \n\ + \\ k\n\ + / k \n\ +/__, \n\ +k = 0 \ +""" + ucode_str = \ +"""\ + n \n\ + ___ \n\ + ╲ \n\ + ╲ k\n\ + ╱ k \n\ + ╱ \n\ + ‾‾‾ \n\ +k = 0 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(k**k, (k, oo, n)) + ascii_str = \ +"""\ + n \n\ + ___ \n\ + \\ ` \n\ + \\ k\n\ + / k \n\ + /__, \n\ +k = oo \ +""" + ucode_str = \ +"""\ + n \n\ + ___ \n\ + ╲ \n\ + ╲ k\n\ + ╱ k \n\ + ╱ \n\ + ‾‾‾ \n\ +k = ∞ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(k**(Integral(x**n, (x, -oo, oo))), (k, 0, n**n)) + ascii_str = \ +"""\ + n \n\ + n \n\ +______ \n\ +\\ ` \n\ + \\ oo \n\ + \\ / \n\ + \\ | \n\ + \\ | n \n\ + ) | x dx\n\ + / | \n\ + / / \n\ + / -oo \n\ + / k \n\ +/_____, \n\ + k = 0 \ +""" + ucode_str = \ +"""\ + n \n\ + n \n\ +______ \n\ +╲ \n\ + ╲ \n\ + ╲ ∞ \n\ + ╲ ⌠ \n\ + ╲ ⎮ n \n\ + ╱ ⎮ x dx\n\ + ╱ ⌡ \n\ + ╱ -∞ \n\ + ╱ k \n\ +╱ \n\ +‾‾‾‾‾‾ \n\ +k = 0 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(k**( + Integral(x**n, (x, -oo, oo))), (k, 0, Integral(x**x, (x, -oo, oo)))) + ascii_str = \ +"""\ + oo \n\ + / \n\ + | \n\ + | x \n\ + | x dx \n\ + | \n\ +/ \n\ +-oo \n\ + ______ \n\ + \\ ` \n\ + \\ oo \n\ + \\ / \n\ + \\ | \n\ + \\ | n \n\ + ) | x dx\n\ + / | \n\ + / / \n\ + / -oo \n\ + / k \n\ + /_____, \n\ + k = 0 \ +""" + ucode_str = \ +"""\ +∞ \n\ +⌠ \n\ +⎮ x \n\ +⎮ x dx \n\ +⌡ \n\ +-∞ \n\ + ______ \n\ + ╲ \n\ + ╲ \n\ + ╲ ∞ \n\ + ╲ ⌠ \n\ + ╲ ⎮ n \n\ + ╱ ⎮ x dx\n\ + ╱ ⌡ \n\ + ╱ -∞ \n\ + ╱ k \n\ + ╱ \n\ + ‾‾‾‾‾‾ \n\ + k = 0 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(k**(Integral(x**n, (x, -oo, oo))), ( + k, x + n + x**2 + n**2 + (x/n) + (1/x), Integral(x**x, (x, -oo, oo)))) + ascii_str = \ +"""\ + oo \n\ + / \n\ + | \n\ + | x \n\ + | x dx \n\ + | \n\ + / \n\ + -oo \n\ + ______ \n\ + \\ ` \n\ + \\ oo \n\ + \\ / \n\ + \\ | \n\ + \\ | n \n\ + ) | x dx\n\ + / | \n\ + / / \n\ + / -oo \n\ + / k \n\ + /_____, \n\ + 2 2 1 x \n\ +k = n + n + x + x + - + - \n\ + x n \ +""" + ucode_str = \ +"""\ + ∞ \n\ + ⌠ \n\ + ⎮ x \n\ + ⎮ x dx \n\ + ⌡ \n\ + -∞ \n\ + ______ \n\ + ╲ \n\ + ╲ \n\ + ╲ ∞ \n\ + ╲ ⌠ \n\ + ╲ ⎮ n \n\ + ╱ ⎮ x dx\n\ + ╱ ⌡ \n\ + ╱ -∞ \n\ + ╱ k \n\ + ╱ \n\ + ‾‾‾‾‾‾ \n\ + 2 2 1 x \n\ +k = n + n + x + x + ─ + ─ \n\ + x n \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(k**( + Integral(x**n, (x, -oo, oo))), (k, 0, x + n + x**2 + n**2 + (x/n) + (1/x))) + ascii_str = \ +"""\ + 2 2 1 x \n\ +n + n + x + x + - + - \n\ + x n \n\ + ______ \n\ + \\ ` \n\ + \\ oo \n\ + \\ / \n\ + \\ | \n\ + \\ | n \n\ + ) | x dx\n\ + / | \n\ + / / \n\ + / -oo \n\ + / k \n\ + /_____, \n\ + k = 0 \ +""" + ucode_str = \ +"""\ + 2 2 1 x \n\ +n + n + x + x + ─ + ─ \n\ + x n \n\ + ______ \n\ + ╲ \n\ + ╲ \n\ + ╲ ∞ \n\ + ╲ ⌠ \n\ + ╲ ⎮ n \n\ + ╱ ⎮ x dx\n\ + ╱ ⌡ \n\ + ╱ -∞ \n\ + ╱ k \n\ + ╱ \n\ + ‾‾‾‾‾‾ \n\ + k = 0 \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(x, (x, 0, oo)) + ascii_str = \ +"""\ + oo \n\ + __ \n\ + \\ ` \n\ + ) x\n\ + /_, \n\ +x = 0 \ +""" + ucode_str = \ +"""\ + ∞ \n\ + ___ \n\ + ╲ \n\ + ╲ \n\ + ╱ x\n\ + ╱ \n\ + ‾‾‾ \n\ +x = 0 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(x**2, (x, 0, oo)) + ascii_str = \ +"""\ + oo \n\ +___ \n\ +\\ ` \n\ + \\ 2\n\ + / x \n\ +/__, \n\ +x = 0 \ +""" + ucode_str = \ +"""\ + ∞ \n\ + ___ \n\ + ╲ \n\ + ╲ 2\n\ + ╱ x \n\ + ╱ \n\ + ‾‾‾ \n\ +x = 0 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(x/2, (x, 0, oo)) + ascii_str = \ +"""\ + oo \n\ +___ \n\ +\\ ` \n\ + \\ x\n\ + ) -\n\ + / 2\n\ +/__, \n\ +x = 0 \ +""" + ucode_str = \ +"""\ + ∞ \n\ +____ \n\ +╲ \n\ + ╲ \n\ + ╲ x\n\ + ╱ ─\n\ + ╱ 2\n\ +╱ \n\ +‾‾‾‾ \n\ +x = 0 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(x**3/2, (x, 0, oo)) + ascii_str = \ +"""\ + oo \n\ +____ \n\ +\\ ` \n\ + \\ 3\n\ + \\ x \n\ + / --\n\ + / 2 \n\ +/___, \n\ +x = 0 \ +""" + ucode_str = \ +"""\ + ∞ \n\ +____ \n\ +╲ \n\ + ╲ 3\n\ + ╲ x \n\ + ╱ ──\n\ + ╱ 2 \n\ +╱ \n\ +‾‾‾‾ \n\ +x = 0 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum((x**3*y**(x/2))**n, (x, 0, oo)) + ascii_str = \ +"""\ + oo \n\ +____ \n\ +\\ ` \n\ + \\ n\n\ + \\ / x\\ \n\ + ) | -| \n\ + / | 3 2| \n\ + / \\x *y / \n\ +/___, \n\ +x = 0 \ +""" + ucode_str = \ +"""\ + ∞ \n\ +_____ \n\ +╲ \n\ + ╲ \n\ + ╲ n\n\ + ╲ ⎛ x⎞ \n\ + ╱ ⎜ ─⎟ \n\ + ╱ ⎜ 3 2⎟ \n\ + ╱ ⎝x ⋅y ⎠ \n\ +╱ \n\ +‾‾‾‾‾ \n\ +x = 0 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(1/x**2, (x, 0, oo)) + ascii_str = \ +"""\ + oo \n\ +____ \n\ +\\ ` \n\ + \\ 1 \n\ + \\ --\n\ + / 2\n\ + / x \n\ +/___, \n\ +x = 0 \ +""" + ucode_str = \ +"""\ + ∞ \n\ +____ \n\ +╲ \n\ + ╲ 1 \n\ + ╲ ──\n\ + ╱ 2\n\ + ╱ x \n\ +╱ \n\ +‾‾‾‾ \n\ +x = 0 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(1/y**(a/b), (x, 0, oo)) + ascii_str = \ +"""\ + oo \n\ +____ \n\ +\\ ` \n\ + \\ -a \n\ + \\ ---\n\ + / b \n\ + / y \n\ +/___, \n\ +x = 0 \ +""" + ucode_str = \ +"""\ + ∞ \n\ +____ \n\ +╲ \n\ + ╲ -a \n\ + ╲ ───\n\ + ╱ b \n\ + ╱ y \n\ +╱ \n\ +‾‾‾‾ \n\ +x = 0 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Sum(1/y**(a/b), (x, 0, oo), (y, 1, 2)) + ascii_str = \ +"""\ + 2 oo \n\ +____ ____ \n\ +\\ ` \\ ` \n\ + \\ \\ -a\n\ + \\ \\ --\n\ + / / b \n\ + / / y \n\ +/___, /___, \n\ +y = 1 x = 0 \ +""" + ucode_str = \ +"""\ + 2 ∞ \n\ +____ ____ \n\ +╲ ╲ \n\ + ╲ ╲ -a\n\ + ╲ ╲ ──\n\ + ╱ ╱ b \n\ + ╱ ╱ y \n\ +╱ ╱ \n\ +‾‾‾‾ ‾‾‾‾ \n\ +y = 1 x = 0 \ +""" + expr = Sum(1/(1 + 1/( + 1 + 1/k)) + 1, (k, 111, 1 + 1/n), (k, 1/(1 + m), oo)) + 1/(1 + 1/k) + ascii_str = \ +"""\ + 1 \n\ + 1 + - \n\ + oo n \n\ + _____ _____ \n\ + \\ ` \\ ` \n\ + \\ \\ / 1 \\ \n\ + \\ \\ |1 + ---------| \n\ + \\ \\ | 1 | 1 \n\ + ) ) | 1 + -----| + -----\n\ + / / | 1| 1\n\ + / / | 1 + -| 1 + -\n\ + / / \\ k/ k\n\ + /____, /____, \n\ + 1 k = 111 \n\ +k = ----- \n\ + m + 1 \ +""" + ucode_str = \ +"""\ + 1 \n\ + 1 + ─ \n\ + ∞ n \n\ + ______ ______ \n\ + ╲ ╲ \n\ + ╲ ╲ \n\ + ╲ ╲ ⎛ 1 ⎞ \n\ + ╲ ╲ ⎜1 + ─────────⎟ \n\ + ╲ ╲ ⎜ 1 ⎟ 1 \n\ + ╱ ╱ ⎜ 1 + ─────⎟ + ─────\n\ + ╱ ╱ ⎜ 1⎟ 1\n\ + ╱ ╱ ⎜ 1 + ─⎟ 1 + ─\n\ + ╱ ╱ ⎝ k⎠ k\n\ + ╱ ╱ \n\ + ‾‾‾‾‾‾ ‾‾‾‾‾‾ \n\ + 1 k = 111 \n\ +k = ───── \n\ + m + 1 \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_units(): + expr = joule + ascii_str1 = \ +"""\ + 2\n\ +kilogram*meter \n\ +---------------\n\ + 2 \n\ + second \ +""" + unicode_str1 = \ +"""\ + 2\n\ +kilogram⋅meter \n\ +───────────────\n\ + 2 \n\ + second \ +""" + + ascii_str2 = \ +"""\ + 2\n\ +3*x*y*kilogram*meter \n\ +---------------------\n\ + 2 \n\ + second \ +""" + unicode_str2 = \ +"""\ + 2\n\ +3⋅x⋅y⋅kilogram⋅meter \n\ +─────────────────────\n\ + 2 \n\ + second \ +""" + + from sympy.physics.units import kg, m, s + assert upretty(expr) == "joule" + assert pretty(expr) == "joule" + assert upretty(expr.convert_to(kg*m**2/s**2)) == unicode_str1 + assert pretty(expr.convert_to(kg*m**2/s**2)) == ascii_str1 + assert upretty(3*kg*x*m**2*y/s**2) == unicode_str2 + assert pretty(3*kg*x*m**2*y/s**2) == ascii_str2 + + +def test_pretty_Subs(): + f = Function('f') + expr = Subs(f(x), x, ph**2) + ascii_str = \ +"""\ +(f(x))| 2\n\ + |x=phi \ +""" + unicode_str = \ +"""\ +(f(x))│ 2\n\ + │x=φ \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == unicode_str + + expr = Subs(f(x).diff(x), x, 0) + ascii_str = \ +"""\ +/d \\| \n\ +|--(f(x))|| \n\ +\\dx /|x=0\ +""" + unicode_str = \ +"""\ +⎛d ⎞│ \n\ +⎜──(f(x))⎟│ \n\ +⎝dx ⎠│x=0\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == unicode_str + + expr = Subs(f(x).diff(x)/y, (x, y), (0, Rational(1, 2))) + ascii_str = \ +"""\ +/d \\| \n\ +|--(f(x))|| \n\ +|dx || \n\ +|--------|| \n\ +\\ y /|x=0, y=1/2\ +""" + unicode_str = \ +"""\ +⎛d ⎞│ \n\ +⎜──(f(x))⎟│ \n\ +⎜dx ⎟│ \n\ +⎜────────⎟│ \n\ +⎝ y ⎠│x=0, y=1/2\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == unicode_str + + +def test_gammas(): + assert upretty(lowergamma(x, y)) == "γ(x, y)" + assert upretty(uppergamma(x, y)) == "Γ(x, y)" + assert xpretty(gamma(x), use_unicode=True) == 'Γ(x)' + assert xpretty(gamma, use_unicode=True) == 'Γ' + assert xpretty(symbols('gamma', cls=Function)(x), use_unicode=True) == 'γ(x)' + assert xpretty(symbols('gamma', cls=Function), use_unicode=True) == 'γ' + + +def test_beta(): + assert xpretty(beta(x,y), use_unicode=True) == 'Β(x, y)' + assert xpretty(beta(x,y), use_unicode=False) == 'B(x, y)' + assert xpretty(beta, use_unicode=True) == 'Β' + assert xpretty(beta, use_unicode=False) == 'B' + mybeta = Function('beta') + assert xpretty(mybeta(x), use_unicode=True) == 'β(x)' + assert xpretty(mybeta(x, y, z), use_unicode=False) == 'beta(x, y, z)' + assert xpretty(mybeta, use_unicode=True) == 'β' + + +# test that notation passes to subclasses of the same name only +def test_function_subclass_different_name(): + class mygamma(gamma): + pass + assert xpretty(mygamma, use_unicode=True) == r"mygamma" + assert xpretty(mygamma(x), use_unicode=True) == r"mygamma(x)" + + +def test_SingularityFunction(): + assert xpretty(SingularityFunction(x, 0, n), use_unicode=True) == ( +"""\ + n\n\ + \ +""") + assert xpretty(SingularityFunction(x, 1, n), use_unicode=True) == ( +"""\ + n\n\ + \ +""") + assert xpretty(SingularityFunction(x, -1, n), use_unicode=True) == ( +"""\ + n\n\ + \ +""") + assert xpretty(SingularityFunction(x, a, n), use_unicode=True) == ( +"""\ + n\n\ +<-a + x> \ +""") + assert xpretty(SingularityFunction(x, y, n), use_unicode=True) == ( +"""\ + n\n\ + \ +""") + assert xpretty(SingularityFunction(x, 0, n), use_unicode=False) == ( +"""\ + n\n\ + \ +""") + assert xpretty(SingularityFunction(x, 1, n), use_unicode=False) == ( +"""\ + n\n\ + \ +""") + assert xpretty(SingularityFunction(x, -1, n), use_unicode=False) == ( +"""\ + n\n\ + \ +""") + assert xpretty(SingularityFunction(x, a, n), use_unicode=False) == ( +"""\ + n\n\ +<-a + x> \ +""") + assert xpretty(SingularityFunction(x, y, n), use_unicode=False) == ( +"""\ + n\n\ + \ +""") + + +def test_deltas(): + assert xpretty(DiracDelta(x), use_unicode=True) == 'δ(x)' + assert xpretty(DiracDelta(x, 1), use_unicode=True) == \ +"""\ + (1) \n\ +δ (x)\ +""" + assert xpretty(x*DiracDelta(x, 1), use_unicode=True) == \ +"""\ + (1) \n\ +x⋅δ (x)\ +""" + + +def test_hyper(): + expr = hyper((), (), z) + ucode_str = \ +"""\ + ┌─ ⎛ │ ⎞\n\ + ├─ ⎜ │ z⎟\n\ +0╵ 0 ⎝ │ ⎠\ +""" + ascii_str = \ +"""\ + _ \n\ + |_ / | \\\n\ + | | | z|\n\ +0 0 \\ | /\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = hyper((), (1,), x) + ucode_str = \ +"""\ + ┌─ ⎛ │ ⎞\n\ + ├─ ⎜ │ x⎟\n\ +0╵ 1 ⎝1 │ ⎠\ +""" + ascii_str = \ +"""\ + _ \n\ + |_ / | \\\n\ + | | | x|\n\ +0 1 \\1 | /\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = hyper([2], [1], x) + ucode_str = \ +"""\ + ┌─ ⎛2 │ ⎞\n\ + ├─ ⎜ │ x⎟\n\ +1╵ 1 ⎝1 │ ⎠\ +""" + ascii_str = \ +"""\ + _ \n\ + |_ /2 | \\\n\ + | | | x|\n\ +1 1 \\1 | /\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = hyper((pi/3, -2*k), (3, 4, 5, -3), x) + ucode_str = \ +"""\ + ⎛ π │ ⎞\n\ + ┌─ ⎜ ─, -2⋅k │ ⎟\n\ + ├─ ⎜ 3 │ x⎟\n\ +2╵ 4 ⎜ │ ⎟\n\ + ⎝-3, 3, 4, 5 │ ⎠\ +""" + ascii_str = \ +"""\ + \n\ + _ / pi | \\\n\ + |_ | --, -2*k | |\n\ + | | 3 | x|\n\ +2 4 | | |\n\ + \\-3, 3, 4, 5 | /\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = hyper((pi, S('2/3'), -2*k), (3, 4, 5, -3), x**2) + ucode_str = \ +"""\ + ┌─ ⎛2/3, π, -2⋅k │ 2⎞\n\ + ├─ ⎜ │ x ⎟\n\ +3╵ 4 ⎝-3, 3, 4, 5 │ ⎠\ +""" + ascii_str = \ +"""\ + _ \n\ + |_ /2/3, pi, -2*k | 2\\ + | | | x | +3 4 \\ -3, 3, 4, 5 | /""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = hyper([1, 2], [3, 4], 1/(1/(1/(1/x + 1) + 1) + 1)) + ucode_str = \ +"""\ + ⎛ │ 1 ⎞\n\ + ⎜ │ ─────────────⎟\n\ + ⎜ │ 1 ⎟\n\ + ┌─ ⎜1, 2 │ 1 + ─────────⎟\n\ + ├─ ⎜ │ 1 ⎟\n\ +2╵ 2 ⎜3, 4 │ 1 + ─────⎟\n\ + ⎜ │ 1⎟\n\ + ⎜ │ 1 + ─⎟\n\ + ⎝ │ x⎠\ +""" + + ascii_str = \ +"""\ + \n\ + / | 1 \\\n\ + | | -------------|\n\ + _ | | 1 |\n\ + |_ |1, 2 | 1 + ---------|\n\ + | | | 1 |\n\ +2 2 |3, 4 | 1 + -----|\n\ + | | 1|\n\ + | | 1 + -|\n\ + \\ | x/\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_meijerg(): + expr = meijerg([pi, pi, x], [1], [0, 1], [1, 2, 3], z) + ucode_str = \ +"""\ +╭─╮2, 3 ⎛π, π, x 1 │ ⎞\n\ +│╶┐ ⎜ │ z⎟\n\ +╰─╯4, 5 ⎝ 0, 1 1, 2, 3 │ ⎠\ +""" + ascii_str = \ +"""\ + __2, 3 /pi, pi, x 1 | \\\n\ +/__ | | z|\n\ +\\_|4, 5 \\ 0, 1 1, 2, 3 | /\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = meijerg([1, pi/7], [2, pi, 5], [], [], z**2) + ucode_str = \ +"""\ + ⎛ π │ ⎞\n\ +╭─╮0, 2 ⎜1, ─ 2, 5, π │ 2⎟\n\ +│╶┐ ⎜ 7 │ z ⎟\n\ +╰─╯5, 0 ⎜ │ ⎟\n\ + ⎝ │ ⎠\ +""" + ascii_str = \ +"""\ + / pi | \\\n\ + __0, 2 |1, -- 2, 5, pi | 2|\n\ +/__ | 7 | z |\n\ +\\_|5, 0 | | |\n\ + \\ | /\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + ucode_str = \ +"""\ +╭─╮ 1, 10 ⎛1, 1, 1, 1, 1, 1, 1, 1, 1, 1 1 │ ⎞\n\ +│╶┐ ⎜ │ z⎟\n\ +╰─╯11, 2 ⎝ 1 1 │ ⎠\ +""" + ascii_str = \ +"""\ + __ 1, 10 /1, 1, 1, 1, 1, 1, 1, 1, 1, 1 1 | \\\n\ +/__ | | z|\n\ +\\_|11, 2 \\ 1 1 | /\ +""" + + expr = meijerg([1]*10, [1], [1], [1], z) + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = meijerg([1, 2, ], [4, 3], [3], [4, 5], 1/(1/(1/(1/x + 1) + 1) + 1)) + + ucode_str = \ +"""\ + ⎛ │ 1 ⎞\n\ + ⎜ │ ─────────────⎟\n\ + ⎜ │ 1 ⎟\n\ +╭─╮1, 2 ⎜1, 2 3, 4 │ 1 + ─────────⎟\n\ +│╶┐ ⎜ │ 1 ⎟\n\ +╰─╯4, 3 ⎜ 3 4, 5 │ 1 + ─────⎟\n\ + ⎜ │ 1⎟\n\ + ⎜ │ 1 + ─⎟\n\ + ⎝ │ x⎠\ +""" + + ascii_str = \ +"""\ + / | 1 \\\n\ + | | -------------|\n\ + | | 1 |\n\ + __1, 2 |1, 2 3, 4 | 1 + ---------|\n\ +/__ | | 1 |\n\ +\\_|4, 3 | 3 4, 5 | 1 + -----|\n\ + | | 1|\n\ + | | 1 + -|\n\ + \\ | x/\ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = Integral(expr, x) + + ucode_str = \ +"""\ +⌠ \n\ +⎮ ⎛ │ 1 ⎞ \n\ +⎮ ⎜ │ ─────────────⎟ \n\ +⎮ ⎜ │ 1 ⎟ \n\ +⎮ ╭─╮1, 2 ⎜1, 2 3, 4 │ 1 + ─────────⎟ \n\ +⎮ │╶┐ ⎜ │ 1 ⎟ dx\n\ +⎮ ╰─╯4, 3 ⎜ 3 4, 5 │ 1 + ─────⎟ \n\ +⎮ ⎜ │ 1⎟ \n\ +⎮ ⎜ │ 1 + ─⎟ \n\ +⎮ ⎝ │ x⎠ \n\ +⌡ \ +""" + + ascii_str = \ +"""\ + / \n\ + | \n\ + | / | 1 \\ \n\ + | | | -------------| \n\ + | | | 1 | \n\ + | __1, 2 |1, 2 3, 4 | 1 + ---------| \n\ + | /__ | | 1 | dx\n\ + | \\_|4, 3 | 3 4, 5 | 1 + -----| \n\ + | | | 1| \n\ + | | | 1 + -| \n\ + | \\ | x/ \n\ + | \n\ +/ \ +""" + + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_noncommutative(): + A, B, C = symbols('A,B,C', commutative=False) + + expr = A*B*C**-1 + ascii_str = \ +"""\ + -1\n\ +A*B*C \ +""" + ucode_str = \ +"""\ + -1\n\ +A⋅B⋅C \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = C**-1*A*B + ascii_str = \ +"""\ + -1 \n\ +C *A*B\ +""" + ucode_str = \ +"""\ + -1 \n\ +C ⋅A⋅B\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = A*C**-1*B + ascii_str = \ +"""\ + -1 \n\ +A*C *B\ +""" + ucode_str = \ +"""\ + -1 \n\ +A⋅C ⋅B\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = A*C**-1*B/x + ascii_str = \ +"""\ + -1 \n\ +A*C *B\n\ +-------\n\ + x \ +""" + ucode_str = \ +"""\ + -1 \n\ +A⋅C ⋅B\n\ +───────\n\ + x \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_special_functions(): + x, y = symbols("x y") + + # atan2 + expr = atan2(y/sqrt(200), sqrt(x)) + ascii_str = \ +"""\ + / ___ \\\n\ + |\\/ 2 *y ___|\n\ +atan2|-------, \\/ x |\n\ + \\ 20 /\ +""" + ucode_str = \ +"""\ + ⎛√2⋅y ⎞\n\ +atan2⎜────, √x⎟\n\ + ⎝ 20 ⎠\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_geometry(): + e = Segment((0, 1), (0, 2)) + assert pretty(e) == 'Segment2D(Point2D(0, 1), Point2D(0, 2))' + e = Ray((1, 1), angle=4.02*pi) + assert pretty(e) == 'Ray2D(Point2D(1, 1), Point2D(2, tan(pi/50) + 1))' + + +def test_expint(): + expr = Ei(x) + string = 'Ei(x)' + assert pretty(expr) == string + assert upretty(expr) == string + + expr = expint(1, z) + ucode_str = "E₁(z)" + ascii_str = "expint(1, z)" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + assert pretty(Shi(x)) == 'Shi(x)' + assert pretty(Si(x)) == 'Si(x)' + assert pretty(Ci(x)) == 'Ci(x)' + assert pretty(Chi(x)) == 'Chi(x)' + assert upretty(Shi(x)) == 'Shi(x)' + assert upretty(Si(x)) == 'Si(x)' + assert upretty(Ci(x)) == 'Ci(x)' + assert upretty(Chi(x)) == 'Chi(x)' + + +def test_elliptic_functions(): + ascii_str = \ +"""\ + / 1 \\\n\ +K|-----|\n\ + \\z + 1/\ +""" + ucode_str = \ +"""\ + ⎛ 1 ⎞\n\ +K⎜─────⎟\n\ + ⎝z + 1⎠\ +""" + expr = elliptic_k(1/(z + 1)) + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + ascii_str = \ +"""\ + / | 1 \\\n\ +F|1|-----|\n\ + \\ |z + 1/\ +""" + ucode_str = \ +"""\ + ⎛ │ 1 ⎞\n\ +F⎜1│─────⎟\n\ + ⎝ │z + 1⎠\ +""" + expr = elliptic_f(1, 1/(1 + z)) + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + ascii_str = \ +"""\ + / 1 \\\n\ +E|-----|\n\ + \\z + 1/\ +""" + ucode_str = \ +"""\ + ⎛ 1 ⎞\n\ +E⎜─────⎟\n\ + ⎝z + 1⎠\ +""" + expr = elliptic_e(1/(z + 1)) + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + ascii_str = \ +"""\ + / | 1 \\\n\ +E|1|-----|\n\ + \\ |z + 1/\ +""" + ucode_str = \ +"""\ + ⎛ │ 1 ⎞\n\ +E⎜1│─────⎟\n\ + ⎝ │z + 1⎠\ +""" + expr = elliptic_e(1, 1/(1 + z)) + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + ascii_str = \ +"""\ + / |4\\\n\ +Pi|3|-|\n\ + \\ |x/\ +""" + ucode_str = \ +"""\ + ⎛ │4⎞\n\ +Π⎜3│─⎟\n\ + ⎝ │x⎠\ +""" + expr = elliptic_pi(3, 4/x) + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + ascii_str = \ +"""\ + / 4| \\\n\ +Pi|3; -|6|\n\ + \\ x| /\ +""" + ucode_str = \ +"""\ + ⎛ 4│ ⎞\n\ +Π⎜3; ─│6⎟\n\ + ⎝ x│ ⎠\ +""" + expr = elliptic_pi(3, 4/x, 6) + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_RandomDomain(): + from sympy.stats import Normal, Die, Exponential, pspace, where + X = Normal('x1', 0, 1) + assert upretty(where(X > 0)) == "Domain: 0 < x₁ ∧ x₁ < ∞" + + D = Die('d1', 6) + assert upretty(where(D > 4)) == 'Domain: d₁ = 5 ∨ d₁ = 6' + + A = Exponential('a', 1) + B = Exponential('b', 1) + assert upretty(pspace(Tuple(A, B)).domain) == \ + 'Domain: 0 ≤ a ∧ 0 ≤ b ∧ a < ∞ ∧ b < ∞' + + +def test_PrettyPoly(): + F = QQ.frac_field(x, y) + R = QQ.poly_ring(x, y) + + expr = F.convert(x/(x + y)) + assert pretty(expr) == "x/(x + y)" + assert upretty(expr) == "x/(x + y)" + + expr = R.convert(x + y) + assert pretty(expr) == "x + y" + assert upretty(expr) == "x + y" + + +def test_issue_6285(): + assert pretty(Pow(2, -5, evaluate=False)) == '1 \n--\n 5\n2 ' + assert pretty(Pow(x, (1/pi))) == \ + ' 1 \n'\ + ' --\n'\ + ' pi\n'\ + 'x ' + + +def test_issue_6359(): + assert pretty(Integral(x**2, x)**2) == \ +"""\ + 2 +/ / \\ \n\ +| | | \n\ +| | 2 | \n\ +| | x dx| \n\ +| | | \n\ +\\/ / \ +""" + assert upretty(Integral(x**2, x)**2) == \ +"""\ + 2 +⎛⌠ ⎞ \n\ +⎜⎮ 2 ⎟ \n\ +⎜⎮ x dx⎟ \n\ +⎝⌡ ⎠ \ +""" + + assert pretty(Sum(x**2, (x, 0, 1))**2) == \ +"""\ + 2\n\ +/ 1 \\ \n\ +|___ | \n\ +|\\ ` | \n\ +| \\ 2| \n\ +| / x | \n\ +|/__, | \n\ +\\x = 0 / \ +""" + assert upretty(Sum(x**2, (x, 0, 1))**2) == \ +"""\ + 2 +⎛ 1 ⎞ \n\ +⎜ ___ ⎟ \n\ +⎜ ╲ ⎟ \n\ +⎜ ╲ 2⎟ \n\ +⎜ ╱ x ⎟ \n\ +⎜ ╱ ⎟ \n\ +⎜ ‾‾‾ ⎟ \n\ +⎝x = 0 ⎠ \ +""" + + assert pretty(Product(x**2, (x, 1, 2))**2) == \ +"""\ + 2 +/ 2 \\ \n\ +|______ | \n\ +| | | 2| \n\ +| | | x | \n\ +| | | | \n\ +\\x = 1 / \ +""" + assert upretty(Product(x**2, (x, 1, 2))**2) == \ +"""\ + 2 +⎛ 2 ⎞ \n\ +⎜─┬──┬─ ⎟ \n\ +⎜ │ │ 2⎟ \n\ +⎜ │ │ x ⎟ \n\ +⎜ │ │ ⎟ \n\ +⎝x = 1 ⎠ \ +""" + + f = Function('f') + assert pretty(Derivative(f(x), x)**2) == \ +"""\ + 2 +/d \\ \n\ +|--(f(x))| \n\ +\\dx / \ +""" + assert upretty(Derivative(f(x), x)**2) == \ +"""\ + 2 +⎛d ⎞ \n\ +⎜──(f(x))⎟ \n\ +⎝dx ⎠ \ +""" + + +def test_issue_6739(): + ascii_str = \ +"""\ + 1 \n\ +-----\n\ + ___\n\ +\\/ x \ +""" + ucode_str = \ +"""\ +1 \n\ +──\n\ +√x\ +""" + assert pretty(1/sqrt(x)) == ascii_str + assert upretty(1/sqrt(x)) == ucode_str + + +def test_complicated_symbol_unchanged(): + for symb_name in ["dexpr2_d1tau", "dexpr2^d1tau"]: + assert pretty(Symbol(symb_name)) == symb_name + + +def test_categories(): + from sympy.categories import (Object, IdentityMorphism, + NamedMorphism, Category, Diagram, DiagramGrid) + + A1 = Object("A1") + A2 = Object("A2") + A3 = Object("A3") + + f1 = NamedMorphism(A1, A2, "f1") + f2 = NamedMorphism(A2, A3, "f2") + id_A1 = IdentityMorphism(A1) + + K1 = Category("K1") + + assert pretty(A1) == "A1" + assert upretty(A1) == "A₁" + + assert pretty(f1) == "f1:A1-->A2" + assert upretty(f1) == "f₁:A₁——▶A₂" + assert pretty(id_A1) == "id:A1-->A1" + assert upretty(id_A1) == "id:A₁——▶A₁" + + assert pretty(f2*f1) == "f2*f1:A1-->A3" + assert upretty(f2*f1) == "f₂∘f₁:A₁——▶A₃" + + assert pretty(K1) == "K1" + assert upretty(K1) == "K₁" + + # Test how diagrams are printed. + d = Diagram() + assert pretty(d) == "EmptySet" + assert upretty(d) == "∅" + + d = Diagram({f1: "unique", f2: S.EmptySet}) + assert pretty(d) == "{f2*f1:A1-->A3: EmptySet, id:A1-->A1: " \ + "EmptySet, id:A2-->A2: EmptySet, id:A3-->A3: " \ + "EmptySet, f1:A1-->A2: {unique}, f2:A2-->A3: EmptySet}" + + assert upretty(d) == "{f₂∘f₁:A₁——▶A₃: ∅, id:A₁——▶A₁: ∅, " \ + "id:A₂——▶A₂: ∅, id:A₃——▶A₃: ∅, f₁:A₁——▶A₂: {unique}, f₂:A₂——▶A₃: ∅}" + + d = Diagram({f1: "unique", f2: S.EmptySet}, {f2 * f1: "unique"}) + assert pretty(d) == "{f2*f1:A1-->A3: EmptySet, id:A1-->A1: " \ + "EmptySet, id:A2-->A2: EmptySet, id:A3-->A3: " \ + "EmptySet, f1:A1-->A2: {unique}, f2:A2-->A3: EmptySet}" \ + " ==> {f2*f1:A1-->A3: {unique}}" + assert upretty(d) == "{f₂∘f₁:A₁——▶A₃: ∅, id:A₁——▶A₁: ∅, id:A₂——▶A₂: " \ + "∅, id:A₃——▶A₃: ∅, f₁:A₁——▶A₂: {unique}, f₂:A₂——▶A₃: ∅}" \ + " ══▶ {f₂∘f₁:A₁——▶A₃: {unique}}" + + grid = DiagramGrid(d) + assert pretty(grid) == "A1 A2\n \nA3 " + assert upretty(grid) == "A₁ A₂\n \nA₃ " + + +def test_PrettyModules(): + R = QQ.old_poly_ring(x, y) + F = R.free_module(2) + M = F.submodule([x, y], [1, x**2]) + + ucode_str = \ +"""\ + 2\n\ +ℚ[x, y] \ +""" + ascii_str = \ +"""\ + 2\n\ +QQ[x, y] \ +""" + + assert upretty(F) == ucode_str + assert pretty(F) == ascii_str + + ucode_str = \ +"""\ +╱ ⎡ 2⎤╲\n\ +╲[x, y], ⎣1, x ⎦╱\ +""" + ascii_str = \ +"""\ + 2 \n\ +<[x, y], [1, x ]>\ +""" + + assert upretty(M) == ucode_str + assert pretty(M) == ascii_str + + I = R.ideal(x**2, y) + + ucode_str = \ +"""\ +╱ 2 ╲\n\ +╲x , y╱\ +""" + + ascii_str = \ +"""\ + 2 \n\ +\ +""" + + assert upretty(I) == ucode_str + assert pretty(I) == ascii_str + + Q = F / M + + ucode_str = \ +"""\ + 2 \n\ + ℚ[x, y] \n\ +─────────────────\n\ +╱ ⎡ 2⎤╲\n\ +╲[x, y], ⎣1, x ⎦╱\ +""" + + ascii_str = \ +"""\ + 2 \n\ + QQ[x, y] \n\ +-----------------\n\ + 2 \n\ +<[x, y], [1, x ]>\ +""" + + assert upretty(Q) == ucode_str + assert pretty(Q) == ascii_str + + ucode_str = \ +"""\ +╱⎡ 3⎤ ╲\n\ +│⎢ x ⎥ ╱ ⎡ 2⎤╲ ╱ ⎡ 2⎤╲│\n\ +│⎢1, ──⎥ + ╲[x, y], ⎣1, x ⎦╱, [2, y] + ╲[x, y], ⎣1, x ⎦╱│\n\ +╲⎣ 2 ⎦ ╱\ +""" + + ascii_str = \ +"""\ + 3 \n\ + x 2 2 \n\ +<[1, --] + <[x, y], [1, x ]>, [2, y] + <[x, y], [1, x ]>>\n\ + 2 \ +""" + + +def test_QuotientRing(): + R = QQ.old_poly_ring(x)/[x**2 + 1] + + ucode_str = \ +"""\ + ℚ[x] \n\ +────────\n\ +╱ 2 ╲\n\ +╲x + 1╱\ +""" + + ascii_str = \ +"""\ + QQ[x] \n\ +--------\n\ + 2 \n\ +\ +""" + + assert upretty(R) == ucode_str + assert pretty(R) == ascii_str + + ucode_str = \ +"""\ + ╱ 2 ╲\n\ +1 + ╲x + 1╱\ +""" + + ascii_str = \ +"""\ + 2 \n\ +1 + \ +""" + + assert upretty(R.one) == ucode_str + assert pretty(R.one) == ascii_str + + +def test_Homomorphism(): + from sympy.polys.agca import homomorphism + + R = QQ.old_poly_ring(x) + + expr = homomorphism(R.free_module(1), R.free_module(1), [0]) + + ucode_str = \ +"""\ + 1 1\n\ +[0] : ℚ[x] ──> ℚ[x] \ +""" + + ascii_str = \ +"""\ + 1 1\n\ +[0] : QQ[x] --> QQ[x] \ +""" + + assert upretty(expr) == ucode_str + assert pretty(expr) == ascii_str + + expr = homomorphism(R.free_module(2), R.free_module(2), [0, 0]) + + ucode_str = \ +"""\ +⎡0 0⎤ 2 2\n\ +⎢ ⎥ : ℚ[x] ──> ℚ[x] \n\ +⎣0 0⎦ \ +""" + + ascii_str = \ +"""\ +[0 0] 2 2\n\ +[ ] : QQ[x] --> QQ[x] \n\ +[0 0] \ +""" + + assert upretty(expr) == ucode_str + assert pretty(expr) == ascii_str + + expr = homomorphism(R.free_module(1), R.free_module(1) / [[x]], [0]) + + ucode_str = \ +"""\ + 1\n\ + 1 ℚ[x] \n\ +[0] : ℚ[x] ──> ─────\n\ + <[x]>\ +""" + + ascii_str = \ +"""\ + 1\n\ + 1 QQ[x] \n\ +[0] : QQ[x] --> ------\n\ + <[x]> \ +""" + + assert upretty(expr) == ucode_str + assert pretty(expr) == ascii_str + + +def test_Tr(): + A, B = symbols('A B', commutative=False) + t = Tr(A*B) + assert pretty(t) == r'Tr(A*B)' + assert upretty(t) == 'Tr(A⋅B)' + + +def test_pretty_Add(): + eq = Mul(-2, x - 2, evaluate=False) + 5 + assert pretty(eq) == '5 - 2*(x - 2)' + + +def test_issue_7179(): + assert upretty(Not(Equivalent(x, y))) == 'x ⇎ y' + assert upretty(Not(Implies(x, y))) == 'x ↛ y' + + +def test_issue_7180(): + assert upretty(Equivalent(x, y)) == 'x ⇔ y' + + +def test_pretty_Complement(): + assert pretty(S.Reals - S.Naturals) == '(-oo, oo) \\ Naturals' + assert upretty(S.Reals - S.Naturals) == 'ℝ \\ ℕ' + assert pretty(S.Reals - S.Naturals0) == '(-oo, oo) \\ Naturals0' + assert upretty(S.Reals - S.Naturals0) == 'ℝ \\ ℕ₀' + + +def test_pretty_SymmetricDifference(): + from sympy.sets.sets import SymmetricDifference + assert upretty(SymmetricDifference(Interval(2,3), Interval(3,5), \ + evaluate = False)) == '[2, 3] ∆ [3, 5]' + with raises(NotImplementedError): + pretty(SymmetricDifference(Interval(2,3), Interval(3,5), evaluate = False)) + + +def test_pretty_Contains(): + assert pretty(Contains(x, S.Integers)) == 'Contains(x, Integers)' + assert upretty(Contains(x, S.Integers)) == 'x ∈ ℤ' + + +def test_issue_8292(): + from sympy.core import sympify + e = sympify('((x+x**4)/(x-1))-(2*(x-1)**4/(x-1)**4)', evaluate=False) + ucode_str = \ +"""\ + 4 4 \n\ + 2⋅(x - 1) x + x\n\ +- ────────── + ──────\n\ + 4 x - 1 \n\ + (x - 1) \ +""" + ascii_str = \ +"""\ + 4 4 \n\ + 2*(x - 1) x + x\n\ +- ---------- + ------\n\ + 4 x - 1 \n\ + (x - 1) \ +""" + assert pretty(e) == ascii_str + assert upretty(e) == ucode_str + + +def test_issue_4335(): + y = Function('y') + expr = -y(x).diff(x) + ucode_str = \ +"""\ + d \n\ +-──(y(x))\n\ + dx \ +""" + ascii_str = \ +"""\ + d \n\ +- --(y(x))\n\ + dx \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_issue_8344(): + from sympy.core import sympify + e = sympify('2*x*y**2/1**2 + 1', evaluate=False) + ucode_str = \ +"""\ + 2 \n\ +2⋅x⋅y \n\ +────── + 1\n\ + 2 \n\ + 1 \ +""" + assert upretty(e) == ucode_str + + +def test_issue_6324(): + x = Pow(2, 3, evaluate=False) + y = Pow(10, -2, evaluate=False) + e = Mul(x, y, evaluate=False) + ucode_str = \ +"""\ + 3 \n\ +2 \n\ +───\n\ + 2\n\ +10 \ +""" + assert upretty(e) == ucode_str + + +def test_issue_7927(): + e = sin(x/2)**cos(x/2) + ucode_str = \ +"""\ + ⎛x⎞\n\ + cos⎜─⎟\n\ + ⎝2⎠\n\ +⎛ ⎛x⎞⎞ \n\ +⎜sin⎜─⎟⎟ \n\ +⎝ ⎝2⎠⎠ \ +""" + assert upretty(e) == ucode_str + e = sin(x)**(S(11)/13) + ucode_str = \ +"""\ + 11\n\ + ──\n\ + 13\n\ +(sin(x)) \ +""" + assert upretty(e) == ucode_str + + +def test_issue_6134(): + from sympy.abc import lamda, t + phi = Function('phi') + + e = lamda*x*Integral(phi(t)*pi*sin(pi*t), (t, 0, 1)) + lamda*x**2*Integral(phi(t)*2*pi*sin(2*pi*t), (t, 0, 1)) + ucode_str = \ +"""\ + 1 1 \n\ + 2 ⌠ ⌠ \n\ +λ⋅x ⋅⎮ 2⋅π⋅φ(t)⋅sin(2⋅π⋅t) dt + λ⋅x⋅⎮ π⋅φ(t)⋅sin(π⋅t) dt\n\ + ⌡ ⌡ \n\ + 0 0 \ +""" + assert upretty(e) == ucode_str + + +def test_issue_9877(): + ucode_str1 = '(2, 3) ∪ ([1, 2] \\ {x})' + a, b, c = Interval(2, 3, True, True), Interval(1, 2), FiniteSet(x) + assert upretty(Union(a, Complement(b, c))) == ucode_str1 + + ucode_str2 = '{x} ∩ {y} ∩ ({z} \\ [1, 2])' + d, e, f, g = FiniteSet(x), FiniteSet(y), FiniteSet(z), Interval(1, 2) + assert upretty(Intersection(d, e, Complement(f, g))) == ucode_str2 + + +def test_issue_13651(): + expr1 = c + Mul(-1, a + b, evaluate=False) + assert pretty(expr1) == 'c - (a + b)' + expr2 = c + Mul(-1, a - b + d, evaluate=False) + assert pretty(expr2) == 'c - (a - b + d)' + + +def test_pretty_primenu(): + from sympy.functions.combinatorial.numbers import primenu + + ascii_str1 = "nu(n)" + ucode_str1 = "ν(n)" + + n = symbols('n', integer=True) + assert pretty(primenu(n)) == ascii_str1 + assert upretty(primenu(n)) == ucode_str1 + + +def test_pretty_primeomega(): + from sympy.functions.combinatorial.numbers import primeomega + + ascii_str1 = "Omega(n)" + ucode_str1 = "Ω(n)" + + n = symbols('n', integer=True) + assert pretty(primeomega(n)) == ascii_str1 + assert upretty(primeomega(n)) == ucode_str1 + + +def test_pretty_Mod(): + from sympy.core import Mod + + ascii_str1 = "x mod 7" + ucode_str1 = "x mod 7" + + ascii_str2 = "(x + 1) mod 7" + ucode_str2 = "(x + 1) mod 7" + + ascii_str3 = "2*x mod 7" + ucode_str3 = "2⋅x mod 7" + + ascii_str4 = "(x mod 7) + 1" + ucode_str4 = "(x mod 7) + 1" + + ascii_str5 = "2*(x mod 7)" + ucode_str5 = "2⋅(x mod 7)" + + x = symbols('x', integer=True) + assert pretty(Mod(x, 7)) == ascii_str1 + assert upretty(Mod(x, 7)) == ucode_str1 + assert pretty(Mod(x + 1, 7)) == ascii_str2 + assert upretty(Mod(x + 1, 7)) == ucode_str2 + assert pretty(Mod(2 * x, 7)) == ascii_str3 + assert upretty(Mod(2 * x, 7)) == ucode_str3 + assert pretty(Mod(x, 7) + 1) == ascii_str4 + assert upretty(Mod(x, 7) + 1) == ucode_str4 + assert pretty(2 * Mod(x, 7)) == ascii_str5 + assert upretty(2 * Mod(x, 7)) == ucode_str5 + + +def test_issue_11801(): + assert pretty(Symbol("")) == "" + assert upretty(Symbol("")) == "" + + +def test_pretty_UnevaluatedExpr(): + x = symbols('x') + he = UnevaluatedExpr(1/x) + + ucode_str = \ +"""\ +1\n\ +─\n\ +x\ +""" + + assert upretty(he) == ucode_str + + ucode_str = \ +"""\ + 2\n\ +⎛1⎞ \n\ +⎜─⎟ \n\ +⎝x⎠ \ +""" + + assert upretty(he**2) == ucode_str + + ucode_str = \ +"""\ + 1\n\ +1 + ─\n\ + x\ +""" + + assert upretty(he + 1) == ucode_str + + ucode_str = \ +('''\ + 1\n\ +x⋅─\n\ + x\ +''') + assert upretty(x*he) == ucode_str + + +def test_issue_10472(): + M = (Matrix([[0, 0], [0, 0]]), Matrix([0, 0])) + + ucode_str = \ +"""\ +⎛⎡0 0⎤ ⎡0⎤⎞ +⎜⎢ ⎥, ⎢ ⎥⎟ +⎝⎣0 0⎦ ⎣0⎦⎠\ +""" + assert upretty(M) == ucode_str + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + ascii_str1 = "A_00" + ucode_str1 = "A₀₀" + assert pretty(A[0, 0]) == ascii_str1 + assert upretty(A[0, 0]) == ucode_str1 + + ascii_str1 = "3*A_00" + ucode_str1 = "3⋅A₀₀" + assert pretty(3*A[0, 0]) == ascii_str1 + assert upretty(3*A[0, 0]) == ucode_str1 + + ascii_str1 = "(-B + A)[0, 0]" + ucode_str1 = "(-B + A)[0, 0]" + F = C[0, 0].subs(C, A - B) + assert pretty(F) == ascii_str1 + assert upretty(F) == ucode_str1 + + +def test_issue_12675(): + x, y, t, j = symbols('x y t j') + e = CoordSys3D('e') + + ucode_str = \ +"""\ +⎛ t⎞ \n\ +⎜⎛x⎞ ⎟ j_e\n\ +⎜⎜─⎟ ⎟ \n\ +⎝⎝y⎠ ⎠ \ +""" + assert upretty((x/y)**t*e.j) == ucode_str + ucode_str = \ +"""\ +⎛1⎞ \n\ +⎜─⎟ j_e\n\ +⎝y⎠ \ +""" + assert upretty((1/y)*e.j) == ucode_str + + +def test_MatrixSymbol_printing(): + # test cases for issue #14237 + A = MatrixSymbol("A", 3, 3) + B = MatrixSymbol("B", 3, 3) + C = MatrixSymbol("C", 3, 3) + assert pretty(-A*B*C) == "-A*B*C" + assert pretty(A - B) == "-B + A" + assert pretty(A*B*C - A*B - B*C) == "-A*B -B*C + A*B*C" + + # issue #14814 + x = MatrixSymbol('x', n, n) + y = MatrixSymbol('y*', n, n) + assert pretty(x + y) == "x + y*" + ascii_str = \ +"""\ + 2 \n\ +-2*y* -a*x\ +""" + assert pretty(-a*x + -2*y*y) == ascii_str + + +def test_degree_printing(): + expr1 = 90*degree + assert pretty(expr1) == '90°' + expr2 = x*degree + assert pretty(expr2) == 'x°' + expr3 = cos(x*degree + 90*degree) + assert pretty(expr3) == 'cos(x° + 90°)' + + +def test_vector_expr_pretty_printing(): + A = CoordSys3D('A') + + assert upretty(Cross(A.i, A.x*A.i+3*A.y*A.j)) == "(i_A)×((x_A) i_A + (3⋅y_A) j_A)" + assert upretty(x*Cross(A.i, A.j)) == 'x⋅(i_A)×(j_A)' + + assert upretty(Curl(A.x*A.i + 3*A.y*A.j)) == "∇×((x_A) i_A + (3⋅y_A) j_A)" + + assert upretty(Divergence(A.x*A.i + 3*A.y*A.j)) == "∇⋅((x_A) i_A + (3⋅y_A) j_A)" + + assert upretty(Dot(A.i, A.x*A.i+3*A.y*A.j)) == "(i_A)⋅((x_A) i_A + (3⋅y_A) j_A)" + + assert upretty(Gradient(A.x+3*A.y)) == "∇(x_A + 3⋅y_A)" + assert upretty(Laplacian(A.x+3*A.y)) == "∆(x_A + 3⋅y_A)" + # TODO: add support for ASCII pretty. + + +def test_pretty_print_tensor_expr(): + L = TensorIndexType("L") + i, j, k = tensor_indices("i j k", L) + i0 = tensor_indices("i_0", L) + A, B, C, D = tensor_heads("A B C D", [L]) + H = TensorHead("H", [L, L]) + + expr = -i + ascii_str = \ +"""\ +-i\ +""" + ucode_str = \ +"""\ +-i\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = A(i) + ascii_str = \ +"""\ + i\n\ +A \n\ + \ +""" + ucode_str = \ +"""\ + i\n\ +A \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = A(i0) + ascii_str = \ +"""\ + i_0\n\ +A \n\ + \ +""" + ucode_str = \ +"""\ + i₀\n\ +A \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = A(-i) + ascii_str = \ +"""\ + \n\ +A \n\ + i\ +""" + ucode_str = \ +"""\ + \n\ +A \n\ + i\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = -3*A(-i) + ascii_str = \ +"""\ + \n\ +-3*A \n\ + i\ +""" + ucode_str = \ +"""\ + \n\ +-3⋅A \n\ + i\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = H(i, -j) + ascii_str = \ +"""\ + i \n\ +H \n\ + j\ +""" + ucode_str = \ +"""\ + i \n\ +H \n\ + j\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = H(i, -i) + ascii_str = \ +"""\ + L_0 \n\ +H \n\ + L_0\ +""" + ucode_str = \ +"""\ + L₀ \n\ +H \n\ + L₀\ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = H(i, -j)*A(j)*B(k) + ascii_str = \ +"""\ + i L_0 k\n\ +H *A *B \n\ + L_0 \ +""" + ucode_str = \ +"""\ + i L₀ k\n\ +H ⋅A ⋅B \n\ + L₀ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (1+x)*A(i) + ascii_str = \ +"""\ + i\n\ +(x + 1)*A \n\ + \ +""" + ucode_str = \ +"""\ + i\n\ +(x + 1)⋅A \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = A(i) + 3*B(i) + ascii_str = \ +"""\ + i i\n\ +3*B + A \n\ + \ +""" + ucode_str = \ +"""\ + i i\n\ +3⋅B + A \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_pretty_print_tensor_partial_deriv(): + from sympy.tensor.toperators import PartialDerivative + + L = TensorIndexType("L") + i, j, k = tensor_indices("i j k", L) + + A, B, C, D = tensor_heads("A B C D", [L]) + + H = TensorHead("H", [L, L]) + + expr = PartialDerivative(A(i), A(j)) + ascii_str = \ +"""\ + d / i\\\n\ +---|A |\n\ + j\\ /\n\ +dA \n\ + \ +""" + ucode_str = \ +"""\ + ∂ ⎛ i⎞\n\ +───⎜A ⎟\n\ + j⎝ ⎠\n\ +∂A \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = A(i)*PartialDerivative(H(k, -i), A(j)) + ascii_str = \ +"""\ + L_0 d / k \\\n\ +A *---|H |\n\ + j\\ L_0/\n\ + dA \n\ + \ +""" + ucode_str = \ +"""\ + L₀ ∂ ⎛ k ⎞\n\ +A ⋅───⎜H ⎟\n\ + j⎝ L₀⎠\n\ + ∂A \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = A(i)*PartialDerivative(B(k)*C(-i) + 3*H(k, -i), A(j)) + ascii_str = \ +"""\ + L_0 d / k k \\\n\ +A *---|3*H + B *C |\n\ + j\\ L_0 L_0/\n\ + dA \n\ + \ +""" + ucode_str = \ +"""\ + L₀ ∂ ⎛ k k ⎞\n\ +A ⋅───⎜3⋅H + B ⋅C ⎟\n\ + j⎝ L₀ L₀⎠\n\ + ∂A \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (A(i) + B(i))*PartialDerivative(C(j), D(j)) + ascii_str = \ +"""\ +/ i i\\ d / L_0\\\n\ +|A + B |*-----|C |\n\ +\\ / L_0\\ /\n\ + dD \n\ + \ +""" + ucode_str = \ +"""\ +⎛ i i⎞ ∂ ⎛ L₀⎞\n\ +⎜A + B ⎟⋅────⎜C ⎟\n\ +⎝ ⎠ L₀⎝ ⎠\n\ + ∂D \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = (A(i) + B(i))*PartialDerivative(C(-i), D(j)) + ascii_str = \ +"""\ +/ L_0 L_0\\ d / \\\n\ +|A + B |*---|C |\n\ +\\ / j\\ L_0/\n\ + dD \n\ + \ +""" + ucode_str = \ +"""\ +⎛ L₀ L₀⎞ ∂ ⎛ ⎞\n\ +⎜A + B ⎟⋅───⎜C ⎟\n\ +⎝ ⎠ j⎝ L₀⎠\n\ + ∂D \n\ + \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = PartialDerivative(B(-i) + A(-i), A(-j), A(-n)) + ucode_str = """\ + 2 \n\ + ∂ ⎛ ⎞\n\ +───────⎜A + B ⎟\n\ + ⎝ i i⎠\n\ +∂A ∂A \n\ + n j \ +""" + assert upretty(expr) == ucode_str + + expr = PartialDerivative(3*A(-i), A(-j), A(-n)) + ucode_str = """\ + 2 \n\ + ∂ ⎛ ⎞\n\ +───────⎜3⋅A ⎟\n\ + ⎝ i⎠\n\ +∂A ∂A \n\ + n j \ +""" + assert upretty(expr) == ucode_str + + expr = TensorElement(H(i, j), {i:1}) + ascii_str = \ +"""\ + i=1,j\n\ +H \n\ + \ +""" + ucode_str = ascii_str + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = TensorElement(H(i, j), {i: 1, j: 1}) + ascii_str = \ +"""\ + i=1,j=1\n\ +H \n\ + \ +""" + ucode_str = ascii_str + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = TensorElement(H(i, j), {j: 1}) + ascii_str = \ +"""\ + i,j=1\n\ +H \n\ + \ +""" + ucode_str = ascii_str + + expr = TensorElement(H(-i, j), {-i: 1}) + ascii_str = \ +"""\ + j\n\ +H \n\ + i=1 \ +""" + ucode_str = ascii_str + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_issue_15560(): + a = MatrixSymbol('a', 1, 1) + e = pretty(a*(KroneckerProduct(a, a))) + result = 'a*(a x a)' + assert e == result + + +def test_print_polylog(): + # Part of issue 6013 + uresult = 'Li₂(3)' + aresult = 'polylog(2, 3)' + assert pretty(polylog(2, 3)) == aresult + assert upretty(polylog(2, 3)) == uresult + + +# Issue #25312 +def test_print_expint_polylog_symbolic_order(): + s, z = symbols("s, z") + uresult = 'Liₛ(z)' + aresult = 'polylog(s, z)' + assert pretty(polylog(s, z)) == aresult + assert upretty(polylog(s, z)) == uresult + # TODO: TBD polylog(s - 1, z) + uresult = 'Eₛ(z)' + aresult = 'expint(s, z)' + assert pretty(expint(s, z)) == aresult + assert upretty(expint(s, z)) == uresult + + + +def test_print_polylog_long_order_issue_25309(): + s, z = symbols("s, z") + ucode_str = \ +"""\ + ⎛ 2 ⎞\n\ +polylog⎝s , z⎠\ +""" + assert upretty(polylog(s**2, z)) == ucode_str + + +def test_print_lerchphi(): + # Part of issue 6013 + a = Symbol('a') + pretty(lerchphi(a, 1, 2)) + uresult = 'Φ(a, 1, 2)' + aresult = 'lerchphi(a, 1, 2)' + assert pretty(lerchphi(a, 1, 2)) == aresult + assert upretty(lerchphi(a, 1, 2)) == uresult + + +def test_issue_15583(): + + N = mechanics.ReferenceFrame('N') + result = '(n_x, n_y, n_z)' + e = pretty((N.x, N.y, N.z)) + assert e == result + + +def test_matrixSymbolBold(): + # Issue 15871 + def boldpretty(expr): + return xpretty(expr, use_unicode=True, wrap_line=False, mat_symbol_style="bold") + + from sympy.matrices.expressions.trace import trace + A = MatrixSymbol("A", 2, 2) + assert boldpretty(trace(A)) == 'tr(𝐀)' + + A = MatrixSymbol("A", 3, 3) + B = MatrixSymbol("B", 3, 3) + C = MatrixSymbol("C", 3, 3) + + assert boldpretty(-A) == '-𝐀' + assert boldpretty(A - A*B - B) == '-𝐁 -𝐀⋅𝐁 + 𝐀' + assert boldpretty(-A*B - A*B*C - B) == '-𝐁 -𝐀⋅𝐁 -𝐀⋅𝐁⋅𝐂' + + A = MatrixSymbol("Addot", 3, 3) + assert boldpretty(A) == '𝐀̈' + omega = MatrixSymbol("omega", 3, 3) + assert boldpretty(omega) == 'ω' + omega = MatrixSymbol("omeganorm", 3, 3) + assert boldpretty(omega) == '‖ω‖' + + a = Symbol('alpha') + b = Symbol('b') + c = MatrixSymbol("c", 3, 1) + d = MatrixSymbol("d", 3, 1) + + assert boldpretty(a*B*c+b*d) == 'b⋅𝐝 + α⋅𝐁⋅𝐜' + + d = MatrixSymbol("delta", 3, 1) + B = MatrixSymbol("Beta", 3, 3) + + assert boldpretty(a*B*c+b*d) == 'b⋅δ + α⋅Β⋅𝐜' + + A = MatrixSymbol("A_2", 3, 3) + assert boldpretty(A) == '𝐀₂' + + +def test_center_accent(): + assert center_accent('a', '\N{COMBINING TILDE}') == 'ã' + assert center_accent('aa', '\N{COMBINING TILDE}') == 'aã' + assert center_accent('aaa', '\N{COMBINING TILDE}') == 'aãa' + assert center_accent('aaaa', '\N{COMBINING TILDE}') == 'aaãa' + assert center_accent('aaaaa', '\N{COMBINING TILDE}') == 'aaãaa' + assert center_accent('abcdefg', '\N{COMBINING FOUR DOTS ABOVE}') == 'abcd⃜efg' + + +def test_imaginary_unit(): + from sympy.printing.pretty import pretty # b/c it was redefined above + assert pretty(1 + I, use_unicode=False) == '1 + I' + assert pretty(1 + I, use_unicode=True) == '1 + ⅈ' + assert pretty(1 + I, use_unicode=False, imaginary_unit='j') == '1 + I' + assert pretty(1 + I, use_unicode=True, imaginary_unit='j') == '1 + ⅉ' + + raises(TypeError, lambda: pretty(I, imaginary_unit=I)) + raises(ValueError, lambda: pretty(I, imaginary_unit="kkk")) + + +def test_str_special_matrices(): + from sympy.matrices import Identity, ZeroMatrix, OneMatrix + assert pretty(Identity(4)) == 'I' + assert upretty(Identity(4)) == '𝕀' + assert pretty(ZeroMatrix(2, 2)) == '0' + assert upretty(ZeroMatrix(2, 2)) == '𝟘' + assert pretty(OneMatrix(2, 2)) == '1' + assert upretty(OneMatrix(2, 2)) == '𝟙' + + +def test_pretty_misc_functions(): + assert pretty(LambertW(x)) == 'W(x)' + assert upretty(LambertW(x)) == 'W(x)' + assert pretty(LambertW(x, y)) == 'W(x, y)' + assert upretty(LambertW(x, y)) == 'W(x, y)' + assert pretty(airyai(x)) == 'Ai(x)' + assert upretty(airyai(x)) == 'Ai(x)' + assert pretty(airybi(x)) == 'Bi(x)' + assert upretty(airybi(x)) == 'Bi(x)' + assert pretty(airyaiprime(x)) == "Ai'(x)" + assert upretty(airyaiprime(x)) == "Ai'(x)" + assert pretty(airybiprime(x)) == "Bi'(x)" + assert upretty(airybiprime(x)) == "Bi'(x)" + assert pretty(fresnelc(x)) == 'C(x)' + assert upretty(fresnelc(x)) == 'C(x)' + assert pretty(fresnels(x)) == 'S(x)' + assert upretty(fresnels(x)) == 'S(x)' + assert pretty(Heaviside(x)) == 'Heaviside(x)' + assert upretty(Heaviside(x)) == 'θ(x)' + assert pretty(Heaviside(x, y)) == 'Heaviside(x, y)' + assert upretty(Heaviside(x, y)) == 'θ(x, y)' + assert pretty(dirichlet_eta(x)) == 'dirichlet_eta(x)' + assert upretty(dirichlet_eta(x)) == 'η(x)' + + +def test_hadamard_power(): + m, n, p = symbols('m, n, p', integer=True) + A = MatrixSymbol('A', m, n) + B = MatrixSymbol('B', m, n) + + # Testing printer: + expr = hadamard_power(A, n) + ascii_str = \ +"""\ + .n\n\ +A \ +""" + ucode_str = \ +"""\ + ∘n\n\ +A \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = hadamard_power(A, 1+n) + ascii_str = \ +"""\ + .(n + 1)\n\ +A \ +""" + ucode_str = \ +"""\ + ∘(n + 1)\n\ +A \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + expr = hadamard_power(A*B.T, 1+n) + ascii_str = \ +"""\ + .(n + 1)\n\ +/ T\\ \n\ +\\A*B / \ +""" + ucode_str = \ +"""\ + ∘(n + 1)\n\ +⎛ T⎞ \n\ +⎝A⋅B ⎠ \ +""" + assert pretty(expr) == ascii_str + assert upretty(expr) == ucode_str + + +def test_issue_17258(): + n = Symbol('n', integer=True) + assert pretty(Sum(n, (n, -oo, 1))) == \ + ' 1 \n'\ + ' __ \n'\ + ' \\ ` \n'\ + ' ) n\n'\ + ' /_, \n'\ + 'n = -oo ' + + assert upretty(Sum(n, (n, -oo, 1))) == \ +"""\ + 1 \n\ + ___ \n\ + ╲ \n\ + ╲ \n\ + ╱ n\n\ + ╱ \n\ + ‾‾‾ \n\ +n = -∞ \ +""" + + +def test_is_combining(): + line = "v̇_m" + assert [is_combining(sym) for sym in line] == \ + [False, True, False, False] + + +def test_issue_17616(): + assert pretty(pi**(1/exp(1))) == \ + ' / -1\\\n'\ + ' \\e /\n'\ + 'pi ' + + assert upretty(pi**(1/exp(1))) == \ + ' ⎛ -1⎞\n'\ + ' ⎝ℯ ⎠\n'\ + 'π ' + + assert pretty(pi**(1/pi)) == \ + ' 1 \n'\ + ' --\n'\ + ' pi\n'\ + 'pi ' + + assert upretty(pi**(1/pi)) == \ + ' 1\n'\ + ' ─\n'\ + ' π\n'\ + 'π ' + + assert pretty(pi**(1/EulerGamma)) == \ + ' 1 \n'\ + ' ----------\n'\ + ' EulerGamma\n'\ + 'pi ' + + assert upretty(pi**(1/EulerGamma)) == \ + ' 1\n'\ + ' ─\n'\ + ' γ\n'\ + 'π ' + + z = Symbol("x_17") + assert upretty(7**(1/z)) == \ + 'x₁₇___\n'\ + ' ╲╱ 7 ' + + assert pretty(7**(1/z)) == \ + 'x_17___\n'\ + ' \\/ 7 ' + + +def test_issue_17857(): + assert pretty(Range(-oo, oo)) == '{..., -1, 0, 1, ...}' + assert pretty(Range(oo, -oo, -1)) == '{..., 1, 0, -1, ...}' + + +def test_issue_18272(): + x = Symbol('x') + n = Symbol('n') + + assert upretty(ConditionSet(x, Eq(-x + exp(x), 0), S.Complexes)) == \ + '⎧ │ ⎛ x ⎞⎫\n'\ + '⎨x │ x ∊ ℂ ∧ ⎝-x + ℯ = 0⎠⎬\n'\ + '⎩ │ ⎭' + assert upretty(ConditionSet(x, Contains(n/2, Interval(0, oo)), FiniteSet(-n/2, n/2))) == \ + '⎧ │ ⎧-n n⎫ ⎛n ⎞⎫\n'\ + '⎨x │ x ∊ ⎨───, ─⎬ ∧ ⎜─ ∈ [0, ∞)⎟⎬\n'\ + '⎩ │ ⎩ 2 2⎭ ⎝2 ⎠⎭' + assert upretty(ConditionSet(x, Eq(Piecewise((1, x >= 3), (x/2 - 1/2, x >= 2), (1/2, x >= 1), + (x/2, True)) - 1/2, 0), Interval(0, 3))) == \ + '⎧ │ ⎛⎛⎧ 1 for x ≥ 3⎞ ⎞⎫\n'\ + '⎪ │ ⎜⎜⎪ ⎟ ⎟⎪\n'\ + '⎪ │ ⎜⎜⎪x ⎟ ⎟⎪\n'\ + '⎪ │ ⎜⎜⎪─ - 0.5 for x ≥ 2⎟ ⎟⎪\n'\ + '⎪ │ ⎜⎜⎪2 ⎟ ⎟⎪\n'\ + '⎨x │ x ∊ [0, 3] ∧ ⎜⎜⎨ ⎟ - 0.5 = 0⎟⎬\n'\ + '⎪ │ ⎜⎜⎪ 0.5 for x ≥ 1⎟ ⎟⎪\n'\ + '⎪ │ ⎜⎜⎪ ⎟ ⎟⎪\n'\ + '⎪ │ ⎜⎜⎪ x ⎟ ⎟⎪\n'\ + '⎪ │ ⎜⎜⎪ ─ otherwise⎟ ⎟⎪\n'\ + '⎩ │ ⎝⎝⎩ 2 ⎠ ⎠⎭' + + +def test_Str(): + from sympy.core.symbol import Str + assert pretty(Str('x')) == 'x' + + +def test_symbolic_probability(): + mu = symbols("mu") + sigma = symbols("sigma", positive=True) + X = Normal("X", mu, sigma) + assert pretty(Expectation(X)) == r'E[X]' + assert pretty(Variance(X)) == r'Var(X)' + assert pretty(Probability(X > 0)) == r'P(X > 0)' + Y = Normal("Y", mu, sigma) + assert pretty(Covariance(X, Y)) == 'Cov(X, Y)' + + +def test_issue_21758(): + from sympy.functions.elementary.piecewise import piecewise_fold + from sympy.series.fourier import FourierSeries + x = Symbol('x') + k, n = symbols('k n') + fo = FourierSeries(x, (x, -pi, pi), (0, SeqFormula(0, (k, 1, oo)), SeqFormula( + Piecewise((-2*pi*cos(n*pi)/n + 2*sin(n*pi)/n**2, (n > -oo) & (n < oo) & Ne(n, 0)), + (0, True))*sin(n*x)/pi, (n, 1, oo)))) + assert upretty(piecewise_fold(fo)) == \ + '⎧ 2⋅sin(3⋅x) \n'\ + '⎪2⋅sin(x) - sin(2⋅x) + ────────── + … for n > -∞ ∧ n < ∞ ∧ n ≠ 0\n'\ + '⎨ 3 \n'\ + '⎪ \n'\ + '⎩ 0 otherwise ' + assert pretty(FourierSeries(x, (x, -pi, pi), (0, SeqFormula(0, (k, 1, oo)), + SeqFormula(0, (n, 1, oo))))) == '0' + + +def test_diffgeom(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseScalarField + x,y = symbols('x y', real=True) + m = Manifold('M', 2) + assert pretty(m) == 'M' + p = Patch('P', m) + assert pretty(p) == "P" + rect = CoordSystem('rect', p, [x, y]) + assert pretty(rect) == "rect" + b = BaseScalarField(rect, 0) + assert pretty(b) == "x" + + +def test_deprecated_prettyForm(): + with warns_deprecated_sympy(): + from sympy.printing.pretty.pretty_symbology import xstr + assert xstr(1) == '1' + + with warns_deprecated_sympy(): + from sympy.printing.pretty.stringpict import prettyForm + p = prettyForm('s', unicode='s') + + with warns_deprecated_sympy(): + assert p.unicode == p.s == 's' + + +def test_center(): + assert center('1', 2) == '1 ' + assert center('1', 3) == ' 1 ' + assert center('1', 3, '-') == '-1-' + assert center('1', 5, '-') == '--1--' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/preview.py b/venv/lib/python3.10/site-packages/sympy/printing/preview.py new file mode 100644 index 0000000000000000000000000000000000000000..b04a344b5b4acc086eb84ff068bc1c6a8b55d811 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/preview.py @@ -0,0 +1,390 @@ +import os +from os.path import join +import shutil +import tempfile +from pathlib import Path + +try: + from subprocess import STDOUT, CalledProcessError, check_output +except ImportError: + pass + +from sympy.utilities.decorator import doctest_depends_on +from sympy.utilities.misc import debug +from .latex import latex + +__doctest_requires__ = {('preview',): ['pyglet']} + + +def _check_output_no_window(*args, **kwargs): + # Avoid showing a cmd.exe window when running this + # on Windows + if os.name == 'nt': + creation_flag = 0x08000000 # CREATE_NO_WINDOW + else: + creation_flag = 0 # Default value + return check_output(*args, creationflags=creation_flag, **kwargs) + + +def system_default_viewer(fname, fmt): + """ Open fname with the default system viewer. + + In practice, it is impossible for python to know when the system viewer is + done. For this reason, we ensure the passed file will not be deleted under + it, and this function does not attempt to block. + """ + # copy to a new temporary file that will not be deleted + with tempfile.NamedTemporaryFile(prefix='sympy-preview-', + suffix=os.path.splitext(fname)[1], + delete=False) as temp_f: + with open(fname, 'rb') as f: + shutil.copyfileobj(f, temp_f) + + import platform + if platform.system() == 'Darwin': + import subprocess + subprocess.call(('open', temp_f.name)) + elif platform.system() == 'Windows': + os.startfile(temp_f.name) + else: + import subprocess + subprocess.call(('xdg-open', temp_f.name)) + + +def pyglet_viewer(fname, fmt): + try: + from pyglet import window, image, gl + from pyglet.window import key + from pyglet.image.codecs import ImageDecodeException + except ImportError: + raise ImportError("pyglet is required for preview.\n visit https://pyglet.org/") + + try: + img = image.load(fname) + except ImageDecodeException: + raise ValueError("pyglet preview does not work for '{}' files.".format(fmt)) + + offset = 25 + + config = gl.Config(double_buffer=False) + win = window.Window( + width=img.width + 2*offset, + height=img.height + 2*offset, + caption="SymPy", + resizable=False, + config=config + ) + + win.set_vsync(False) + + try: + def on_close(): + win.has_exit = True + + win.on_close = on_close + + def on_key_press(symbol, modifiers): + if symbol in [key.Q, key.ESCAPE]: + on_close() + + win.on_key_press = on_key_press + + def on_expose(): + gl.glClearColor(1.0, 1.0, 1.0, 1.0) + gl.glClear(gl.GL_COLOR_BUFFER_BIT) + + img.blit( + (win.width - img.width) / 2, + (win.height - img.height) / 2 + ) + + win.on_expose = on_expose + + while not win.has_exit: + win.dispatch_events() + win.flip() + except KeyboardInterrupt: + pass + + win.close() + + +def _get_latex_main(expr, *, preamble=None, packages=(), extra_preamble=None, + euler=True, fontsize=None, **latex_settings): + """ + Generate string of a LaTeX document rendering ``expr``. + """ + if preamble is None: + actual_packages = packages + ("amsmath", "amsfonts") + if euler: + actual_packages += ("euler",) + package_includes = "\n" + "\n".join(["\\usepackage{%s}" % p + for p in actual_packages]) + if extra_preamble: + package_includes += extra_preamble + + if not fontsize: + fontsize = "12pt" + elif isinstance(fontsize, int): + fontsize = "{}pt".format(fontsize) + preamble = r"""\documentclass[varwidth,%s]{standalone} +%s + +\begin{document} +""" % (fontsize, package_includes) + else: + if packages or extra_preamble: + raise ValueError("The \"packages\" or \"extra_preamble\" keywords" + "must not be set if a " + "custom LaTeX preamble was specified") + + if isinstance(expr, str): + latex_string = expr + else: + latex_string = ('$\\displaystyle ' + + latex(expr, mode='plain', **latex_settings) + + '$') + + return preamble + '\n' + latex_string + '\n\n' + r"\end{document}" + + +@doctest_depends_on(exe=('latex', 'dvipng'), modules=('pyglet',), + disable_viewers=('evince', 'gimp', 'superior-dvi-viewer')) +def preview(expr, output='png', viewer=None, euler=True, packages=(), + filename=None, outputbuffer=None, preamble=None, dvioptions=None, + outputTexFile=None, extra_preamble=None, fontsize=None, + **latex_settings): + r""" + View expression or LaTeX markup in PNG, DVI, PostScript or PDF form. + + If the expr argument is an expression, it will be exported to LaTeX and + then compiled using the available TeX distribution. The first argument, + 'expr', may also be a LaTeX string. The function will then run the + appropriate viewer for the given output format or use the user defined + one. By default png output is generated. + + By default pretty Euler fonts are used for typesetting (they were used to + typeset the well known "Concrete Mathematics" book). For that to work, you + need the 'eulervm.sty' LaTeX style (in Debian/Ubuntu, install the + texlive-fonts-extra package). If you prefer default AMS fonts or your + system lacks 'eulervm' LaTeX package then unset the 'euler' keyword + argument. + + To use viewer auto-detection, lets say for 'png' output, issue + + >>> from sympy import symbols, preview, Symbol + >>> x, y = symbols("x,y") + + >>> preview(x + y, output='png') + + This will choose 'pyglet' by default. To select a different one, do + + >>> preview(x + y, output='png', viewer='gimp') + + The 'png' format is considered special. For all other formats the rules + are slightly different. As an example we will take 'dvi' output format. If + you would run + + >>> preview(x + y, output='dvi') + + then 'view' will look for available 'dvi' viewers on your system + (predefined in the function, so it will try evince, first, then kdvi and + xdvi). If nothing is found, it will fall back to using a system file + association (via ``open`` and ``xdg-open``). To always use your system file + association without searching for the above readers, use + + >>> from sympy.printing.preview import system_default_viewer + >>> preview(x + y, output='dvi', viewer=system_default_viewer) + + If this still does not find the viewer you want, it can be set explicitly. + + >>> preview(x + y, output='dvi', viewer='superior-dvi-viewer') + + This will skip auto-detection and will run user specified + 'superior-dvi-viewer'. If ``view`` fails to find it on your system it will + gracefully raise an exception. + + You may also enter ``'file'`` for the viewer argument. Doing so will cause + this function to return a file object in read-only mode, if ``filename`` + is unset. However, if it was set, then 'preview' writes the generated + file to this filename instead. + + There is also support for writing to a ``io.BytesIO`` like object, which + needs to be passed to the ``outputbuffer`` argument. + + >>> from io import BytesIO + >>> obj = BytesIO() + >>> preview(x + y, output='png', viewer='BytesIO', + ... outputbuffer=obj) + + The LaTeX preamble can be customized by setting the 'preamble' keyword + argument. This can be used, e.g., to set a different font size, use a + custom documentclass or import certain set of LaTeX packages. + + >>> preamble = "\\documentclass[10pt]{article}\n" \ + ... "\\usepackage{amsmath,amsfonts}\\begin{document}" + >>> preview(x + y, output='png', preamble=preamble) + + It is also possible to use the standard preamble and provide additional + information to the preamble using the ``extra_preamble`` keyword argument. + + >>> from sympy import sin + >>> extra_preamble = "\\renewcommand{\\sin}{\\cos}" + >>> preview(sin(x), output='png', extra_preamble=extra_preamble) + + If the value of 'output' is different from 'dvi' then command line + options can be set ('dvioptions' argument) for the execution of the + 'dvi'+output conversion tool. These options have to be in the form of a + list of strings (see ``subprocess.Popen``). + + Additional keyword args will be passed to the :func:`~sympy.printing.latex.latex` call, + e.g., the ``symbol_names`` flag. + + >>> phidd = Symbol('phidd') + >>> preview(phidd, symbol_names={phidd: r'\ddot{\varphi}'}) + + For post-processing the generated TeX File can be written to a file by + passing the desired filename to the 'outputTexFile' keyword + argument. To write the TeX code to a file named + ``"sample.tex"`` and run the default png viewer to display the resulting + bitmap, do + + >>> preview(x + y, outputTexFile="sample.tex") + + + """ + # pyglet is the default for png + if viewer is None and output == "png": + try: + import pyglet # noqa: F401 + except ImportError: + pass + else: + viewer = pyglet_viewer + + # look up a known application + if viewer is None: + # sorted in order from most pretty to most ugly + # very discussable, but indeed 'gv' looks awful :) + candidates = { + "dvi": [ "evince", "okular", "kdvi", "xdvi" ], + "ps": [ "evince", "okular", "gsview", "gv" ], + "pdf": [ "evince", "okular", "kpdf", "acroread", "xpdf", "gv" ], + } + + for candidate in candidates.get(output, []): + path = shutil.which(candidate) + if path is not None: + viewer = path + break + + # otherwise, use the system default for file association + if viewer is None: + viewer = system_default_viewer + + if viewer == "file": + if filename is None: + raise ValueError("filename has to be specified if viewer=\"file\"") + elif viewer == "BytesIO": + if outputbuffer is None: + raise ValueError("outputbuffer has to be a BytesIO " + "compatible object if viewer=\"BytesIO\"") + elif not callable(viewer) and not shutil.which(viewer): + raise OSError("Unrecognized viewer: %s" % viewer) + + latex_main = _get_latex_main(expr, preamble=preamble, packages=packages, + euler=euler, extra_preamble=extra_preamble, + fontsize=fontsize, **latex_settings) + + debug("Latex code:") + debug(latex_main) + with tempfile.TemporaryDirectory() as workdir: + Path(join(workdir, 'texput.tex')).write_text(latex_main, encoding='utf-8') + + if outputTexFile is not None: + shutil.copyfile(join(workdir, 'texput.tex'), outputTexFile) + + if not shutil.which('latex'): + raise RuntimeError("latex program is not installed") + + try: + _check_output_no_window( + ['latex', '-halt-on-error', '-interaction=nonstopmode', + 'texput.tex'], + cwd=workdir, + stderr=STDOUT) + except CalledProcessError as e: + raise RuntimeError( + "'latex' exited abnormally with the following output:\n%s" % + e.output) + + src = "texput.%s" % (output) + + if output != "dvi": + # in order of preference + commandnames = { + "ps": ["dvips"], + "pdf": ["dvipdfmx", "dvipdfm", "dvipdf"], + "png": ["dvipng"], + "svg": ["dvisvgm"], + } + try: + cmd_variants = commandnames[output] + except KeyError: + raise ValueError("Invalid output format: %s" % output) from None + + # find an appropriate command + for cmd_variant in cmd_variants: + cmd_path = shutil.which(cmd_variant) + if cmd_path: + cmd = [cmd_path] + break + else: + if len(cmd_variants) > 1: + raise RuntimeError("None of %s are installed" % ", ".join(cmd_variants)) + else: + raise RuntimeError("%s is not installed" % cmd_variants[0]) + + defaultoptions = { + "dvipng": ["-T", "tight", "-z", "9", "--truecolor"], + "dvisvgm": ["--no-fonts"], + } + + commandend = { + "dvips": ["-o", src, "texput.dvi"], + "dvipdf": ["texput.dvi", src], + "dvipdfm": ["-o", src, "texput.dvi"], + "dvipdfmx": ["-o", src, "texput.dvi"], + "dvipng": ["-o", src, "texput.dvi"], + "dvisvgm": ["-o", src, "texput.dvi"], + } + + if dvioptions is not None: + cmd.extend(dvioptions) + else: + cmd.extend(defaultoptions.get(cmd_variant, [])) + cmd.extend(commandend[cmd_variant]) + + try: + _check_output_no_window(cmd, cwd=workdir, stderr=STDOUT) + except CalledProcessError as e: + raise RuntimeError( + "'%s' exited abnormally with the following output:\n%s" % + (' '.join(cmd), e.output)) + + + if viewer == "file": + shutil.move(join(workdir, src), filename) + elif viewer == "BytesIO": + s = Path(join(workdir, src)).read_bytes() + outputbuffer.write(s) + elif callable(viewer): + viewer(join(workdir, src), fmt=output) + else: + try: + _check_output_no_window( + [viewer, src], cwd=workdir, stderr=STDOUT) + except CalledProcessError as e: + raise RuntimeError( + "'%s %s' exited abnormally with the following output:\n%s" % + (viewer, src, e.output)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/printer.py b/venv/lib/python3.10/site-packages/sympy/printing/printer.py new file mode 100644 index 0000000000000000000000000000000000000000..0c0a6970920cf0928ad330ed9a3ea4291107a29d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/printer.py @@ -0,0 +1,432 @@ +"""Printing subsystem driver + +SymPy's printing system works the following way: Any expression can be +passed to a designated Printer who then is responsible to return an +adequate representation of that expression. + +**The basic concept is the following:** + +1. Let the object print itself if it knows how. +2. Take the best fitting method defined in the printer. +3. As fall-back use the emptyPrinter method for the printer. + +Which Method is Responsible for Printing? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The whole printing process is started by calling ``.doprint(expr)`` on the printer +which you want to use. This method looks for an appropriate method which can +print the given expression in the given style that the printer defines. +While looking for the method, it follows these steps: + +1. **Let the object print itself if it knows how.** + + The printer looks for a specific method in every object. The name of that method + depends on the specific printer and is defined under ``Printer.printmethod``. + For example, StrPrinter calls ``_sympystr`` and LatexPrinter calls ``_latex``. + Look at the documentation of the printer that you want to use. + The name of the method is specified there. + + This was the original way of doing printing in sympy. Every class had + its own latex, mathml, str and repr methods, but it turned out that it + is hard to produce a high quality printer, if all the methods are spread + out that far. Therefore all printing code was combined into the different + printers, which works great for built-in SymPy objects, but not that + good for user defined classes where it is inconvenient to patch the + printers. + +2. **Take the best fitting method defined in the printer.** + + The printer loops through expr classes (class + its bases), and tries + to dispatch the work to ``_print_`` + + e.g., suppose we have the following class hierarchy:: + + Basic + | + Atom + | + Number + | + Rational + + then, for ``expr=Rational(...)``, the Printer will try + to call printer methods in the order as shown in the figure below:: + + p._print(expr) + | + |-- p._print_Rational(expr) + | + |-- p._print_Number(expr) + | + |-- p._print_Atom(expr) + | + `-- p._print_Basic(expr) + + if ``._print_Rational`` method exists in the printer, then it is called, + and the result is returned back. Otherwise, the printer tries to call + ``._print_Number`` and so on. + +3. **As a fall-back use the emptyPrinter method for the printer.** + + As fall-back ``self.emptyPrinter`` will be called with the expression. If + not defined in the Printer subclass this will be the same as ``str(expr)``. + +.. _printer_example: + +Example of Custom Printer +^^^^^^^^^^^^^^^^^^^^^^^^^ + +In the example below, we have a printer which prints the derivative of a function +in a shorter form. + +.. code-block:: python + + from sympy.core.symbol import Symbol + from sympy.printing.latex import LatexPrinter, print_latex + from sympy.core.function import UndefinedFunction, Function + + + class MyLatexPrinter(LatexPrinter): + \"\"\"Print derivative of a function of symbols in a shorter form. + \"\"\" + def _print_Derivative(self, expr): + function, *vars = expr.args + if not isinstance(type(function), UndefinedFunction) or \\ + not all(isinstance(i, Symbol) for i in vars): + return super()._print_Derivative(expr) + + # If you want the printer to work correctly for nested + # expressions then use self._print() instead of str() or latex(). + # See the example of nested modulo below in the custom printing + # method section. + return "{}_{{{}}}".format( + self._print(Symbol(function.func.__name__)), + ''.join(self._print(i) for i in vars)) + + + def print_my_latex(expr): + \"\"\" Most of the printers define their own wrappers for print(). + These wrappers usually take printer settings. Our printer does not have + any settings. + \"\"\" + print(MyLatexPrinter().doprint(expr)) + + + y = Symbol("y") + x = Symbol("x") + f = Function("f") + expr = f(x, y).diff(x, y) + + # Print the expression using the normal latex printer and our custom + # printer. + print_latex(expr) + print_my_latex(expr) + +The output of the code above is:: + + \\frac{\\partial^{2}}{\\partial x\\partial y} f{\\left(x,y \\right)} + f_{xy} + +.. _printer_method_example: + +Example of Custom Printing Method +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In the example below, the latex printing of the modulo operator is modified. +This is done by overriding the method ``_latex`` of ``Mod``. + +>>> from sympy import Symbol, Mod, Integer, print_latex + +>>> # Always use printer._print() +>>> class ModOp(Mod): +... def _latex(self, printer): +... a, b = [printer._print(i) for i in self.args] +... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b) + +Comparing the output of our custom operator to the builtin one: + +>>> x = Symbol('x') +>>> m = Symbol('m') +>>> print_latex(Mod(x, m)) +x \\bmod m +>>> print_latex(ModOp(x, m)) +\\operatorname{Mod}{\\left(x, m\\right)} + +Common mistakes +~~~~~~~~~~~~~~~ +It's important to always use ``self._print(obj)`` to print subcomponents of +an expression when customizing a printer. Mistakes include: + +1. Using ``self.doprint(obj)`` instead: + + >>> # This example does not work properly, as only the outermost call may use + >>> # doprint. + >>> class ModOpModeWrong(Mod): + ... def _latex(self, printer): + ... a, b = [printer.doprint(i) for i in self.args] + ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b) + + This fails when the ``mode`` argument is passed to the printer: + + >>> print_latex(ModOp(x, m), mode='inline') # ok + $\\operatorname{Mod}{\\left(x, m\\right)}$ + >>> print_latex(ModOpModeWrong(x, m), mode='inline') # bad + $\\operatorname{Mod}{\\left($x$, $m$\\right)}$ + +2. Using ``str(obj)`` instead: + + >>> class ModOpNestedWrong(Mod): + ... def _latex(self, printer): + ... a, b = [str(i) for i in self.args] + ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b) + + This fails on nested objects: + + >>> # Nested modulo. + >>> print_latex(ModOp(ModOp(x, m), Integer(7))) # ok + \\operatorname{Mod}{\\left(\\operatorname{Mod}{\\left(x, m\\right)}, 7\\right)} + >>> print_latex(ModOpNestedWrong(ModOpNestedWrong(x, m), Integer(7))) # bad + \\operatorname{Mod}{\\left(ModOpNestedWrong(x, m), 7\\right)} + +3. Using ``LatexPrinter()._print(obj)`` instead. + + >>> from sympy.printing.latex import LatexPrinter + >>> class ModOpSettingsWrong(Mod): + ... def _latex(self, printer): + ... a, b = [LatexPrinter()._print(i) for i in self.args] + ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b) + + This causes all the settings to be discarded in the subobjects. As an + example, the ``full_prec`` setting which shows floats to full precision is + ignored: + + >>> from sympy import Float + >>> print_latex(ModOp(Float(1) * x, m), full_prec=True) # ok + \\operatorname{Mod}{\\left(1.00000000000000 x, m\\right)} + >>> print_latex(ModOpSettingsWrong(Float(1) * x, m), full_prec=True) # bad + \\operatorname{Mod}{\\left(1.0 x, m\\right)} + +""" + +from __future__ import annotations +import sys +from typing import Any, Type +import inspect +from contextlib import contextmanager +from functools import cmp_to_key, update_wrapper + +from sympy.core.add import Add +from sympy.core.basic import Basic + +from sympy.core.function import AppliedUndef, UndefinedFunction, Function + + + +@contextmanager +def printer_context(printer, **kwargs): + original = printer._context.copy() + try: + printer._context.update(kwargs) + yield + finally: + printer._context = original + + +class Printer: + """ Generic printer + + Its job is to provide infrastructure for implementing new printers easily. + + If you want to define your custom Printer or your custom printing method + for your custom class then see the example above: printer_example_ . + """ + + _global_settings: dict[str, Any] = {} + + _default_settings: dict[str, Any] = {} + + # must be initialized to pass tests and cannot be set to '| None' to pass mypy + printmethod = None # type: str + + @classmethod + def _get_initial_settings(cls): + settings = cls._default_settings.copy() + for key, val in cls._global_settings.items(): + if key in cls._default_settings: + settings[key] = val + return settings + + def __init__(self, settings=None): + self._str = str + + self._settings = self._get_initial_settings() + self._context = {} # mutable during printing + + if settings is not None: + self._settings.update(settings) + + if len(self._settings) > len(self._default_settings): + for key in self._settings: + if key not in self._default_settings: + raise TypeError("Unknown setting '%s'." % key) + + # _print_level is the number of times self._print() was recursively + # called. See StrPrinter._print_Float() for an example of usage + self._print_level = 0 + + @classmethod + def set_global_settings(cls, **settings): + """Set system-wide printing settings. """ + for key, val in settings.items(): + if val is not None: + cls._global_settings[key] = val + + @property + def order(self): + if 'order' in self._settings: + return self._settings['order'] + else: + raise AttributeError("No order defined.") + + def doprint(self, expr): + """Returns printer's representation for expr (as a string)""" + return self._str(self._print(expr)) + + def _print(self, expr, **kwargs) -> str: + """Internal dispatcher + + Tries the following concepts to print an expression: + 1. Let the object print itself if it knows how. + 2. Take the best fitting method defined in the printer. + 3. As fall-back use the emptyPrinter method for the printer. + """ + self._print_level += 1 + try: + # If the printer defines a name for a printing method + # (Printer.printmethod) and the object knows for itself how it + # should be printed, use that method. + if self.printmethod and hasattr(expr, self.printmethod): + if not (isinstance(expr, type) and issubclass(expr, Basic)): + return getattr(expr, self.printmethod)(self, **kwargs) + + # See if the class of expr is known, or if one of its super + # classes is known, and use that print function + # Exception: ignore the subclasses of Undefined, so that, e.g., + # Function('gamma') does not get dispatched to _print_gamma + classes = type(expr).__mro__ + if AppliedUndef in classes: + classes = classes[classes.index(AppliedUndef):] + if UndefinedFunction in classes: + classes = classes[classes.index(UndefinedFunction):] + # Another exception: if someone subclasses a known function, e.g., + # gamma, and changes the name, then ignore _print_gamma + if Function in classes: + i = classes.index(Function) + classes = tuple(c for c in classes[:i] if \ + c.__name__ == classes[0].__name__ or \ + c.__name__.endswith("Base")) + classes[i:] + for cls in classes: + printmethodname = '_print_' + cls.__name__ + printmethod = getattr(self, printmethodname, None) + if printmethod is not None: + return printmethod(expr, **kwargs) + # Unknown object, fall back to the emptyPrinter. + return self.emptyPrinter(expr) + finally: + self._print_level -= 1 + + def emptyPrinter(self, expr): + return str(expr) + + def _as_ordered_terms(self, expr, order=None): + """A compatibility function for ordering terms in Add. """ + order = order or self.order + + if order == 'old': + return sorted(Add.make_args(expr), key=cmp_to_key(self._compare_pretty)) + elif order == 'none': + return list(expr.args) + else: + return expr.as_ordered_terms(order=order) + + def _compare_pretty(self, a, b): + """return -1, 0, 1 if a is canonically less, equal or + greater than b. This is used when 'order=old' is selected + for printing. This puts Order last, orders Rationals + according to value, puts terms in order wrt the power of + the last power appearing in a term. Ties are broken using + Basic.compare. + """ + from sympy.core.numbers import Rational + from sympy.core.symbol import Wild + from sympy.series.order import Order + if isinstance(a, Order) and not isinstance(b, Order): + return 1 + if not isinstance(a, Order) and isinstance(b, Order): + return -1 + + if isinstance(a, Rational) and isinstance(b, Rational): + l = a.p * b.q + r = b.p * a.q + return (l > r) - (l < r) + else: + p1, p2, p3 = Wild("p1"), Wild("p2"), Wild("p3") + r_a = a.match(p1 * p2**p3) + if r_a and p3 in r_a: + a3 = r_a[p3] + r_b = b.match(p1 * p2**p3) + if r_b and p3 in r_b: + b3 = r_b[p3] + c = Basic.compare(a3, b3) + if c != 0: + return c + + # break ties + return Basic.compare(a, b) + + +class _PrintFunction: + """ + Function wrapper to replace ``**settings`` in the signature with printer defaults + """ + def __init__(self, f, print_cls: Type[Printer]): + # find all the non-setting arguments + params = list(inspect.signature(f).parameters.values()) + assert params.pop(-1).kind == inspect.Parameter.VAR_KEYWORD + self.__other_params = params + + self.__print_cls = print_cls + update_wrapper(self, f) + + def __reduce__(self): + # Since this is used as a decorator, it replaces the original function. + # The default pickling will try to pickle self.__wrapped__ and fail + # because the wrapped function can't be retrieved by name. + return self.__wrapped__.__qualname__ + + def __call__(self, *args, **kwargs): + return self.__wrapped__(*args, **kwargs) + + @property + def __signature__(self) -> inspect.Signature: + settings = self.__print_cls._get_initial_settings() + return inspect.Signature( + parameters=self.__other_params + [ + inspect.Parameter(k, inspect.Parameter.KEYWORD_ONLY, default=v) + for k, v in settings.items() + ], + return_annotation=self.__wrapped__.__annotations__.get('return', inspect.Signature.empty) # type:ignore + ) + + +def print_function(print_cls): + """ A decorator to replace kwargs with the printer settings in __signature__ """ + def decorator(f): + if sys.version_info < (3, 9): + # We have to create a subclass so that `help` actually shows the docstring in older Python versions. + # IPython and Sphinx do not need this, only a raw Python console. + cls = type(f'{f.__qualname__}_PrintFunction', (_PrintFunction,), {"__doc__": f.__doc__}) + else: + cls = _PrintFunction + return cls(f, print_cls) + return decorator diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pycode.py b/venv/lib/python3.10/site-packages/sympy/printing/pycode.py new file mode 100644 index 0000000000000000000000000000000000000000..09bdc6788775d409c06bdaae0a43c54544894602 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/pycode.py @@ -0,0 +1,852 @@ +""" +Python code printers + +This module contains Python code printers for plain Python as well as NumPy & SciPy enabled code. +""" +from collections import defaultdict +from itertools import chain +from sympy.core import S +from sympy.core.mod import Mod +from .precedence import precedence +from .codeprinter import CodePrinter + +_kw = { + 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', + 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', + 'is', 'lambda', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', + 'with', 'yield', 'None', 'False', 'nonlocal', 'True' +} + +_known_functions = { + 'Abs': 'abs', + 'Min': 'min', + 'Max': 'max', +} +_known_functions_math = { + 'acos': 'acos', + 'acosh': 'acosh', + 'asin': 'asin', + 'asinh': 'asinh', + 'atan': 'atan', + 'atan2': 'atan2', + 'atanh': 'atanh', + 'ceiling': 'ceil', + 'cos': 'cos', + 'cosh': 'cosh', + 'erf': 'erf', + 'erfc': 'erfc', + 'exp': 'exp', + 'expm1': 'expm1', + 'factorial': 'factorial', + 'floor': 'floor', + 'gamma': 'gamma', + 'hypot': 'hypot', + 'isinf': 'isinf', + 'isnan': 'isnan', + 'loggamma': 'lgamma', + 'log': 'log', + 'ln': 'log', + 'log10': 'log10', + 'log1p': 'log1p', + 'log2': 'log2', + 'sin': 'sin', + 'sinh': 'sinh', + 'Sqrt': 'sqrt', + 'tan': 'tan', + 'tanh': 'tanh' +} # Not used from ``math``: [copysign isclose isfinite isinf ldexp frexp pow modf +# radians trunc fmod fsum gcd degrees fabs] +_known_constants_math = { + 'Exp1': 'e', + 'Pi': 'pi', + 'E': 'e', + 'Infinity': 'inf', + 'NaN': 'nan', + 'ComplexInfinity': 'nan' +} + +def _print_known_func(self, expr): + known = self.known_functions[expr.__class__.__name__] + return '{name}({args})'.format(name=self._module_format(known), + args=', '.join((self._print(arg) for arg in expr.args))) + + +def _print_known_const(self, expr): + known = self.known_constants[expr.__class__.__name__] + return self._module_format(known) + + +class AbstractPythonCodePrinter(CodePrinter): + printmethod = "_pythoncode" + language = "Python" + reserved_words = _kw + modules = None # initialized to a set in __init__ + tab = ' ' + _kf = dict(chain( + _known_functions.items(), + [(k, 'math.' + v) for k, v in _known_functions_math.items()] + )) + _kc = {k: 'math.'+v for k, v in _known_constants_math.items()} + _operators = {'and': 'and', 'or': 'or', 'not': 'not'} + _default_settings = dict( + CodePrinter._default_settings, + user_functions={}, + precision=17, + inline=True, + fully_qualified_modules=True, + contract=False, + standard='python3', + ) + + def __init__(self, settings=None): + super().__init__(settings) + + # Python standard handler + std = self._settings['standard'] + if std is None: + import sys + std = 'python{}'.format(sys.version_info.major) + if std != 'python3': + raise ValueError('Only Python 3 is supported.') + self.standard = std + + self.module_imports = defaultdict(set) + + # Known functions and constants handler + self.known_functions = dict(self._kf, **(settings or {}).get( + 'user_functions', {})) + self.known_constants = dict(self._kc, **(settings or {}).get( + 'user_constants', {})) + + def _declare_number_const(self, name, value): + return "%s = %s" % (name, value) + + def _module_format(self, fqn, register=True): + parts = fqn.split('.') + if register and len(parts) > 1: + self.module_imports['.'.join(parts[:-1])].add(parts[-1]) + + if self._settings['fully_qualified_modules']: + return fqn + else: + return fqn.split('(')[0].split('[')[0].split('.')[-1] + + def _format_code(self, lines): + return lines + + def _get_statement(self, codestring): + return "{}".format(codestring) + + def _get_comment(self, text): + return " # {}".format(text) + + def _expand_fold_binary_op(self, op, args): + """ + This method expands a fold on binary operations. + + ``functools.reduce`` is an example of a folded operation. + + For example, the expression + + `A + B + C + D` + + is folded into + + `((A + B) + C) + D` + """ + if len(args) == 1: + return self._print(args[0]) + else: + return "%s(%s, %s)" % ( + self._module_format(op), + self._expand_fold_binary_op(op, args[:-1]), + self._print(args[-1]), + ) + + def _expand_reduce_binary_op(self, op, args): + """ + This method expands a reduction on binary operations. + + Notice: this is NOT the same as ``functools.reduce``. + + For example, the expression + + `A + B + C + D` + + is reduced into: + + `(A + B) + (C + D)` + """ + if len(args) == 1: + return self._print(args[0]) + else: + N = len(args) + Nhalf = N // 2 + return "%s(%s, %s)" % ( + self._module_format(op), + self._expand_reduce_binary_op(args[:Nhalf]), + self._expand_reduce_binary_op(args[Nhalf:]), + ) + + def _print_NaN(self, expr): + return "float('nan')" + + def _print_Infinity(self, expr): + return "float('inf')" + + def _print_NegativeInfinity(self, expr): + return "float('-inf')" + + def _print_ComplexInfinity(self, expr): + return self._print_NaN(expr) + + def _print_Mod(self, expr): + PREC = precedence(expr) + return ('{} % {}'.format(*(self.parenthesize(x, PREC) for x in expr.args))) + + def _print_Piecewise(self, expr): + result = [] + i = 0 + for arg in expr.args: + e = arg.expr + c = arg.cond + if i == 0: + result.append('(') + result.append('(') + result.append(self._print(e)) + result.append(')') + result.append(' if ') + result.append(self._print(c)) + result.append(' else ') + i += 1 + result = result[:-1] + if result[-1] == 'True': + result = result[:-2] + result.append(')') + else: + result.append(' else None)') + return ''.join(result) + + def _print_Relational(self, expr): + "Relational printer for Equality and Unequality" + op = { + '==' :'equal', + '!=' :'not_equal', + '<' :'less', + '<=' :'less_equal', + '>' :'greater', + '>=' :'greater_equal', + } + if expr.rel_op in op: + lhs = self._print(expr.lhs) + rhs = self._print(expr.rhs) + return '({lhs} {op} {rhs})'.format(op=expr.rel_op, lhs=lhs, rhs=rhs) + return super()._print_Relational(expr) + + def _print_ITE(self, expr): + from sympy.functions.elementary.piecewise import Piecewise + return self._print(expr.rewrite(Piecewise)) + + def _print_Sum(self, expr): + loops = ( + 'for {i} in range({a}, {b}+1)'.format( + i=self._print(i), + a=self._print(a), + b=self._print(b)) + for i, a, b in expr.limits[::-1]) + return '(builtins.sum({function} {loops}))'.format( + function=self._print(expr.function), + loops=' '.join(loops)) + + def _print_ImaginaryUnit(self, expr): + return '1j' + + def _print_KroneckerDelta(self, expr): + a, b = expr.args + + return '(1 if {a} == {b} else 0)'.format( + a = self._print(a), + b = self._print(b) + ) + + def _print_MatrixBase(self, expr): + name = expr.__class__.__name__ + func = self.known_functions.get(name, name) + return "%s(%s)" % (func, self._print(expr.tolist())) + + _print_SparseRepMatrix = \ + _print_MutableSparseMatrix = \ + _print_ImmutableSparseMatrix = \ + _print_Matrix = \ + _print_DenseMatrix = \ + _print_MutableDenseMatrix = \ + _print_ImmutableMatrix = \ + _print_ImmutableDenseMatrix = \ + lambda self, expr: self._print_MatrixBase(expr) + + def _indent_codestring(self, codestring): + return '\n'.join([self.tab + line for line in codestring.split('\n')]) + + def _print_FunctionDefinition(self, fd): + body = '\n'.join((self._print(arg) for arg in fd.body)) + return "def {name}({parameters}):\n{body}".format( + name=self._print(fd.name), + parameters=', '.join([self._print(var.symbol) for var in fd.parameters]), + body=self._indent_codestring(body) + ) + + def _print_While(self, whl): + body = '\n'.join((self._print(arg) for arg in whl.body)) + return "while {cond}:\n{body}".format( + cond=self._print(whl.condition), + body=self._indent_codestring(body) + ) + + def _print_Declaration(self, decl): + return '%s = %s' % ( + self._print(decl.variable.symbol), + self._print(decl.variable.value) + ) + + def _print_BreakToken(self, bt): + return 'break' + + def _print_Return(self, ret): + arg, = ret.args + return 'return %s' % self._print(arg) + + def _print_Raise(self, rs): + arg, = rs.args + return 'raise %s' % self._print(arg) + + def _print_RuntimeError_(self, re): + message, = re.args + return "RuntimeError(%s)" % self._print(message) + + def _print_Print(self, prnt): + print_args = ', '.join((self._print(arg) for arg in prnt.print_args)) + from sympy.codegen.ast import none + if prnt.format_string != none: + print_args = '{} % ({}), end=""'.format( + self._print(prnt.format_string), + print_args + ) + if prnt.file != None: # Must be '!= None', cannot be 'is not None' + print_args += ', file=%s' % self._print(prnt.file) + return 'print(%s)' % print_args + + def _print_Stream(self, strm): + if str(strm.name) == 'stdout': + return self._module_format('sys.stdout') + elif str(strm.name) == 'stderr': + return self._module_format('sys.stderr') + else: + return self._print(strm.name) + + def _print_NoneToken(self, arg): + return 'None' + + def _hprint_Pow(self, expr, rational=False, sqrt='math.sqrt'): + """Printing helper function for ``Pow`` + + Notes + ===== + + This preprocesses the ``sqrt`` as math formatter and prints division + + Examples + ======== + + >>> from sympy import sqrt + >>> from sympy.printing.pycode import PythonCodePrinter + >>> from sympy.abc import x + + Python code printer automatically looks up ``math.sqrt``. + + >>> printer = PythonCodePrinter() + >>> printer._hprint_Pow(sqrt(x), rational=True) + 'x**(1/2)' + >>> printer._hprint_Pow(sqrt(x), rational=False) + 'math.sqrt(x)' + >>> printer._hprint_Pow(1/sqrt(x), rational=True) + 'x**(-1/2)' + >>> printer._hprint_Pow(1/sqrt(x), rational=False) + '1/math.sqrt(x)' + >>> printer._hprint_Pow(1/x, rational=False) + '1/x' + >>> printer._hprint_Pow(1/x, rational=True) + 'x**(-1)' + + Using sqrt from numpy or mpmath + + >>> printer._hprint_Pow(sqrt(x), sqrt='numpy.sqrt') + 'numpy.sqrt(x)' + >>> printer._hprint_Pow(sqrt(x), sqrt='mpmath.sqrt') + 'mpmath.sqrt(x)' + + See Also + ======== + + sympy.printing.str.StrPrinter._print_Pow + """ + PREC = precedence(expr) + + if expr.exp == S.Half and not rational: + func = self._module_format(sqrt) + arg = self._print(expr.base) + return '{func}({arg})'.format(func=func, arg=arg) + + if expr.is_commutative and not rational: + if -expr.exp is S.Half: + func = self._module_format(sqrt) + num = self._print(S.One) + arg = self._print(expr.base) + return f"{num}/{func}({arg})" + if expr.exp is S.NegativeOne: + num = self._print(S.One) + arg = self.parenthesize(expr.base, PREC, strict=False) + return f"{num}/{arg}" + + + base_str = self.parenthesize(expr.base, PREC, strict=False) + exp_str = self.parenthesize(expr.exp, PREC, strict=False) + return "{}**{}".format(base_str, exp_str) + + +class ArrayPrinter: + + def _arrayify(self, indexed): + from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array + try: + return convert_indexed_to_array(indexed) + except Exception: + return indexed + + def _get_einsum_string(self, subranks, contraction_indices): + letters = self._get_letter_generator_for_einsum() + contraction_string = "" + counter = 0 + d = {j: min(i) for i in contraction_indices for j in i} + indices = [] + for rank_arg in subranks: + lindices = [] + for i in range(rank_arg): + if counter in d: + lindices.append(d[counter]) + else: + lindices.append(counter) + counter += 1 + indices.append(lindices) + mapping = {} + letters_free = [] + letters_dum = [] + for i in indices: + for j in i: + if j not in mapping: + l = next(letters) + mapping[j] = l + else: + l = mapping[j] + contraction_string += l + if j in d: + if l not in letters_dum: + letters_dum.append(l) + else: + letters_free.append(l) + contraction_string += "," + contraction_string = contraction_string[:-1] + return contraction_string, letters_free, letters_dum + + def _get_letter_generator_for_einsum(self): + for i in range(97, 123): + yield chr(i) + for i in range(65, 91): + yield chr(i) + raise ValueError("out of letters") + + def _print_ArrayTensorProduct(self, expr): + letters = self._get_letter_generator_for_einsum() + contraction_string = ",".join(["".join([next(letters) for j in range(i)]) for i in expr.subranks]) + return '%s("%s", %s)' % ( + self._module_format(self._module + "." + self._einsum), + contraction_string, + ", ".join([self._print(arg) for arg in expr.args]) + ) + + def _print_ArrayContraction(self, expr): + from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct + base = expr.expr + contraction_indices = expr.contraction_indices + + if isinstance(base, ArrayTensorProduct): + elems = ",".join(["%s" % (self._print(arg)) for arg in base.args]) + ranks = base.subranks + else: + elems = self._print(base) + ranks = [len(base.shape)] + + contraction_string, letters_free, letters_dum = self._get_einsum_string(ranks, contraction_indices) + + if not contraction_indices: + return self._print(base) + if isinstance(base, ArrayTensorProduct): + elems = ",".join(["%s" % (self._print(arg)) for arg in base.args]) + else: + elems = self._print(base) + return "%s(\"%s\", %s)" % ( + self._module_format(self._module + "." + self._einsum), + "{}->{}".format(contraction_string, "".join(sorted(letters_free))), + elems, + ) + + def _print_ArrayDiagonal(self, expr): + from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct + diagonal_indices = list(expr.diagonal_indices) + if isinstance(expr.expr, ArrayTensorProduct): + subranks = expr.expr.subranks + elems = expr.expr.args + else: + subranks = expr.subranks + elems = [expr.expr] + diagonal_string, letters_free, letters_dum = self._get_einsum_string(subranks, diagonal_indices) + elems = [self._print(i) for i in elems] + return '%s("%s", %s)' % ( + self._module_format(self._module + "." + self._einsum), + "{}->{}".format(diagonal_string, "".join(letters_free+letters_dum)), + ", ".join(elems) + ) + + def _print_PermuteDims(self, expr): + return "%s(%s, %s)" % ( + self._module_format(self._module + "." + self._transpose), + self._print(expr.expr), + self._print(expr.permutation.array_form), + ) + + def _print_ArrayAdd(self, expr): + return self._expand_fold_binary_op(self._module + "." + self._add, expr.args) + + def _print_OneArray(self, expr): + return "%s((%s,))" % ( + self._module_format(self._module+ "." + self._ones), + ','.join(map(self._print,expr.args)) + ) + + def _print_ZeroArray(self, expr): + return "%s((%s,))" % ( + self._module_format(self._module+ "." + self._zeros), + ','.join(map(self._print,expr.args)) + ) + + def _print_Assignment(self, expr): + #XXX: maybe this needs to happen at a higher level e.g. at _print or + #doprint? + lhs = self._print(self._arrayify(expr.lhs)) + rhs = self._print(self._arrayify(expr.rhs)) + return "%s = %s" % ( lhs, rhs ) + + def _print_IndexedBase(self, expr): + return self._print_ArraySymbol(expr) + + +class PythonCodePrinter(AbstractPythonCodePrinter): + + def _print_sign(self, e): + return '(0.0 if {e} == 0 else {f}(1, {e}))'.format( + f=self._module_format('math.copysign'), e=self._print(e.args[0])) + + def _print_Not(self, expr): + PREC = precedence(expr) + return self._operators['not'] + ' ' + self.parenthesize(expr.args[0], PREC) + + def _print_IndexedBase(self, expr): + return expr.name + + def _print_Indexed(self, expr): + base = expr.args[0] + index = expr.args[1:] + return "{}[{}]".format(str(base), ", ".join([self._print(ind) for ind in index])) + + def _print_Pow(self, expr, rational=False): + return self._hprint_Pow(expr, rational=rational) + + def _print_Rational(self, expr): + return '{}/{}'.format(expr.p, expr.q) + + def _print_Half(self, expr): + return self._print_Rational(expr) + + def _print_frac(self, expr): + return self._print_Mod(Mod(expr.args[0], 1)) + + def _print_Symbol(self, expr): + + name = super()._print_Symbol(expr) + + if name in self.reserved_words: + if self._settings['error_on_reserved']: + msg = ('This expression includes the symbol "{}" which is a ' + 'reserved keyword in this language.') + raise ValueError(msg.format(name)) + return name + self._settings['reserved_word_suffix'] + elif '{' in name: # Remove curly braces from subscripted variables + return name.replace('{', '').replace('}', '') + else: + return name + + _print_lowergamma = CodePrinter._print_not_supported + _print_uppergamma = CodePrinter._print_not_supported + _print_fresnelc = CodePrinter._print_not_supported + _print_fresnels = CodePrinter._print_not_supported + + +for k in PythonCodePrinter._kf: + setattr(PythonCodePrinter, '_print_%s' % k, _print_known_func) + +for k in _known_constants_math: + setattr(PythonCodePrinter, '_print_%s' % k, _print_known_const) + + +def pycode(expr, **settings): + """ Converts an expr to a string of Python code + + Parameters + ========== + + expr : Expr + A SymPy expression. + fully_qualified_modules : bool + Whether or not to write out full module names of functions + (``math.sin`` vs. ``sin``). default: ``True``. + standard : str or None, optional + Only 'python3' (default) is supported. + This parameter may be removed in the future. + + Examples + ======== + + >>> from sympy import pycode, tan, Symbol + >>> pycode(tan(Symbol('x')) + 1) + 'math.tan(x) + 1' + + """ + return PythonCodePrinter(settings).doprint(expr) + + +from itertools import chain +from sympy.printing.pycode import PythonCodePrinter + +_known_functions_cmath = { + 'exp': 'exp', + 'sqrt': 'sqrt', + 'log': 'log', + 'cos': 'cos', + 'sin': 'sin', + 'tan': 'tan', + 'acos': 'acos', + 'asin': 'asin', + 'atan': 'atan', + 'cosh': 'cosh', + 'sinh': 'sinh', + 'tanh': 'tanh', + 'acosh': 'acosh', + 'asinh': 'asinh', + 'atanh': 'atanh', +} + +_known_constants_cmath = { + 'Pi': 'pi', + 'E': 'e', + 'Infinity': 'inf', + 'NegativeInfinity': '-inf', +} + +class CmathPrinter(PythonCodePrinter): + """ Printer for Python's cmath module """ + printmethod = "_cmathcode" + language = "Python with cmath" + + _kf = dict(chain( + _known_functions_cmath.items() + )) + + _kc = {k: 'cmath.' + v for k, v in _known_constants_cmath.items()} + + def _print_Pow(self, expr, rational=False): + return self._hprint_Pow(expr, rational=rational, sqrt='cmath.sqrt') + + def _print_Float(self, e): + return '{func}({val})'.format(func=self._module_format('cmath.mpf'), val=self._print(e)) + + def _print_known_func(self, expr): + func_name = expr.func.__name__ + if func_name in self._kf: + return f"cmath.{self._kf[func_name]}({', '.join(map(self._print, expr.args))})" + return super()._print_Function(expr) + + def _print_known_const(self, expr): + return self._kc[expr.__class__.__name__] + + def _print_re(self, expr): + """Prints `re(z)` as `z.real`""" + return f"({self._print(expr.args[0])}).real" + + def _print_im(self, expr): + """Prints `im(z)` as `z.imag`""" + return f"({self._print(expr.args[0])}).imag" + + +for k in CmathPrinter._kf: + setattr(CmathPrinter, '_print_%s' % k, CmathPrinter._print_known_func) + +for k in _known_constants_cmath: + setattr(CmathPrinter, '_print_%s' % k, CmathPrinter._print_known_const) + + +_not_in_mpmath = 'log1p log2'.split() +_in_mpmath = [(k, v) for k, v in _known_functions_math.items() if k not in _not_in_mpmath] +_known_functions_mpmath = dict(_in_mpmath, **{ + 'beta': 'beta', + 'frac': 'frac', + 'fresnelc': 'fresnelc', + 'fresnels': 'fresnels', + 'sign': 'sign', + 'loggamma': 'loggamma', + 'hyper': 'hyper', + 'meijerg': 'meijerg', + 'besselj': 'besselj', + 'bessely': 'bessely', + 'besseli': 'besseli', + 'besselk': 'besselk', +}) +_known_constants_mpmath = { + 'Exp1': 'e', + 'Pi': 'pi', + 'GoldenRatio': 'phi', + 'EulerGamma': 'euler', + 'Catalan': 'catalan', + 'NaN': 'nan', + 'Infinity': 'inf', + 'NegativeInfinity': 'ninf' +} + + +def _unpack_integral_limits(integral_expr): + """ helper function for _print_Integral that + - accepts an Integral expression + - returns a tuple of + - a list variables of integration + - a list of tuples of the upper and lower limits of integration + """ + integration_vars = [] + limits = [] + for integration_range in integral_expr.limits: + if len(integration_range) == 3: + integration_var, lower_limit, upper_limit = integration_range + else: + raise NotImplementedError("Only definite integrals are supported") + integration_vars.append(integration_var) + limits.append((lower_limit, upper_limit)) + return integration_vars, limits + + +class MpmathPrinter(PythonCodePrinter): + """ + Lambda printer for mpmath which maintains precision for floats + """ + printmethod = "_mpmathcode" + + language = "Python with mpmath" + + _kf = dict(chain( + _known_functions.items(), + [(k, 'mpmath.' + v) for k, v in _known_functions_mpmath.items()] + )) + _kc = {k: 'mpmath.'+v for k, v in _known_constants_mpmath.items()} + + def _print_Float(self, e): + # XXX: This does not handle setting mpmath.mp.dps. It is assumed that + # the caller of the lambdified function will have set it to sufficient + # precision to match the Floats in the expression. + + # Remove 'mpz' if gmpy is installed. + args = str(tuple(map(int, e._mpf_))) + return '{func}({args})'.format(func=self._module_format('mpmath.mpf'), args=args) + + + def _print_Rational(self, e): + return "{func}({p})/{func}({q})".format( + func=self._module_format('mpmath.mpf'), + q=self._print(e.q), + p=self._print(e.p) + ) + + def _print_Half(self, e): + return self._print_Rational(e) + + def _print_uppergamma(self, e): + return "{}({}, {}, {})".format( + self._module_format('mpmath.gammainc'), + self._print(e.args[0]), + self._print(e.args[1]), + self._module_format('mpmath.inf')) + + def _print_lowergamma(self, e): + return "{}({}, 0, {})".format( + self._module_format('mpmath.gammainc'), + self._print(e.args[0]), + self._print(e.args[1])) + + def _print_log2(self, e): + return '{0}({1})/{0}(2)'.format( + self._module_format('mpmath.log'), self._print(e.args[0])) + + def _print_log1p(self, e): + return '{}({})'.format( + self._module_format('mpmath.log1p'), self._print(e.args[0])) + + def _print_Pow(self, expr, rational=False): + return self._hprint_Pow(expr, rational=rational, sqrt='mpmath.sqrt') + + def _print_Integral(self, e): + integration_vars, limits = _unpack_integral_limits(e) + + return "{}(lambda {}: {}, {})".format( + self._module_format("mpmath.quad"), + ", ".join(map(self._print, integration_vars)), + self._print(e.args[0]), + ", ".join("(%s, %s)" % tuple(map(self._print, l)) for l in limits)) + + + def _print_Derivative_zeta(self, args, seq_orders): + arg, = args + deriv_order, = seq_orders + return '{}({}, derivative={})'.format( + self._module_format('mpmath.zeta'), + self._print(arg), deriv_order + ) + + +for k in MpmathPrinter._kf: + setattr(MpmathPrinter, '_print_%s' % k, _print_known_func) + +for k in _known_constants_mpmath: + setattr(MpmathPrinter, '_print_%s' % k, _print_known_const) + + +class SymPyPrinter(AbstractPythonCodePrinter): + + language = "Python with SymPy" + + _default_settings = dict( + AbstractPythonCodePrinter._default_settings, + strict=False # any class name will per definition be what we target in SymPyPrinter. + ) + + def _print_Function(self, expr): + mod = expr.func.__module__ or '' + return '%s(%s)' % (self._module_format(mod + ('.' if mod else '') + expr.func.__name__), + ', '.join((self._print(arg) for arg in expr.args))) + + def _print_Pow(self, expr, rational=False): + return self._hprint_Pow(expr, rational=rational, sqrt='sympy.sqrt') diff --git a/venv/lib/python3.10/site-packages/sympy/printing/python.py b/venv/lib/python3.10/site-packages/sympy/printing/python.py new file mode 100644 index 0000000000000000000000000000000000000000..2f6862574d99db90f289de65144c7122ed2d731a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/python.py @@ -0,0 +1,92 @@ +import keyword as kw +import sympy +from .repr import ReprPrinter +from .str import StrPrinter + +# A list of classes that should be printed using StrPrinter +STRPRINT = ("Add", "Infinity", "Integer", "Mul", "NegativeInfinity", "Pow") + + +class PythonPrinter(ReprPrinter, StrPrinter): + """A printer which converts an expression into its Python interpretation.""" + + def __init__(self, settings=None): + super().__init__(settings) + self.symbols = [] + self.functions = [] + + # Create print methods for classes that should use StrPrinter instead + # of ReprPrinter. + for name in STRPRINT: + f_name = "_print_%s" % name + f = getattr(StrPrinter, f_name) + setattr(PythonPrinter, f_name, f) + + def _print_Function(self, expr): + func = expr.func.__name__ + if not hasattr(sympy, func) and func not in self.functions: + self.functions.append(func) + return StrPrinter._print_Function(self, expr) + + # procedure (!) for defining symbols which have be defined in print_python() + def _print_Symbol(self, expr): + symbol = self._str(expr) + if symbol not in self.symbols: + self.symbols.append(symbol) + return StrPrinter._print_Symbol(self, expr) + + def _print_module(self, expr): + raise ValueError('Modules in the expression are unacceptable') + + +def python(expr, **settings): + """Return Python interpretation of passed expression + (can be passed to the exec() function without any modifications)""" + + printer = PythonPrinter(settings) + exprp = printer.doprint(expr) + + result = '' + # Returning found symbols and functions + renamings = {} + for symbolname in printer.symbols: + # Remove curly braces from subscripted variables + if '{' in symbolname: + newsymbolname = symbolname.replace('{', '').replace('}', '') + renamings[sympy.Symbol(symbolname)] = newsymbolname + else: + newsymbolname = symbolname + + # Escape symbol names that are reserved Python keywords + if kw.iskeyword(newsymbolname): + while True: + newsymbolname += "_" + if (newsymbolname not in printer.symbols and + newsymbolname not in printer.functions): + renamings[sympy.Symbol( + symbolname)] = sympy.Symbol(newsymbolname) + break + result += newsymbolname + ' = Symbol(\'' + symbolname + '\')\n' + + for functionname in printer.functions: + newfunctionname = functionname + # Escape function names that are reserved Python keywords + if kw.iskeyword(newfunctionname): + while True: + newfunctionname += "_" + if (newfunctionname not in printer.symbols and + newfunctionname not in printer.functions): + renamings[sympy.Function( + functionname)] = sympy.Function(newfunctionname) + break + result += newfunctionname + ' = Function(\'' + functionname + '\')\n' + + if renamings: + exprp = expr.subs(renamings) + result += 'e = ' + printer._str(exprp) + return result + + +def print_python(expr, **settings): + """Print output of python() function""" + print(python(expr, **settings)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/pytorch.py b/venv/lib/python3.10/site-packages/sympy/printing/pytorch.py new file mode 100644 index 0000000000000000000000000000000000000000..0e8ff01856fa1dce7f8e786065a32bb74d987254 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/pytorch.py @@ -0,0 +1,297 @@ + +from sympy.printing.pycode import AbstractPythonCodePrinter, ArrayPrinter +from sympy.matrices.expressions import MatrixExpr +from sympy.core.mul import Mul +from sympy.printing.precedence import PRECEDENCE +from sympy.external import import_module +from sympy.codegen.cfunctions import Sqrt +from sympy import S +from sympy import Integer + +import sympy + +torch = import_module('torch') + + +class TorchPrinter(ArrayPrinter, AbstractPythonCodePrinter): + + printmethod = "_torchcode" + + mapping = { + sympy.Abs: "torch.abs", + sympy.sign: "torch.sign", + + # XXX May raise error for ints. + sympy.ceiling: "torch.ceil", + sympy.floor: "torch.floor", + sympy.log: "torch.log", + sympy.exp: "torch.exp", + Sqrt: "torch.sqrt", + sympy.cos: "torch.cos", + sympy.acos: "torch.acos", + sympy.sin: "torch.sin", + sympy.asin: "torch.asin", + sympy.tan: "torch.tan", + sympy.atan: "torch.atan", + sympy.atan2: "torch.atan2", + # XXX Also may give NaN for complex results. + sympy.cosh: "torch.cosh", + sympy.acosh: "torch.acosh", + sympy.sinh: "torch.sinh", + sympy.asinh: "torch.asinh", + sympy.tanh: "torch.tanh", + sympy.atanh: "torch.atanh", + sympy.Pow: "torch.pow", + + sympy.re: "torch.real", + sympy.im: "torch.imag", + sympy.arg: "torch.angle", + + # XXX May raise error for ints and complexes + sympy.erf: "torch.erf", + sympy.loggamma: "torch.lgamma", + + sympy.Eq: "torch.eq", + sympy.Ne: "torch.ne", + sympy.StrictGreaterThan: "torch.gt", + sympy.StrictLessThan: "torch.lt", + sympy.LessThan: "torch.le", + sympy.GreaterThan: "torch.ge", + + sympy.And: "torch.logical_and", + sympy.Or: "torch.logical_or", + sympy.Not: "torch.logical_not", + sympy.Max: "torch.max", + sympy.Min: "torch.min", + + # Matrices + sympy.MatAdd: "torch.add", + sympy.HadamardProduct: "torch.mul", + sympy.Trace: "torch.trace", + + # XXX May raise error for integer matrices. + sympy.Determinant: "torch.det", + } + + _default_settings = dict( + AbstractPythonCodePrinter._default_settings, + torch_version=None, + requires_grad=False, + dtype="torch.float64", + ) + + def __init__(self, settings=None): + super().__init__(settings) + + version = self._settings['torch_version'] + self.requires_grad = self._settings['requires_grad'] + self.dtype = self._settings['dtype'] + if version is None and torch: + version = torch.__version__ + self.torch_version = version + + def _print_Function(self, expr): + + op = self.mapping.get(type(expr), None) + if op is None: + return super()._print_Basic(expr) + children = [self._print(arg) for arg in expr.args] + if len(children) == 1: + return "%s(%s)" % ( + self._module_format(op), + children[0] + ) + else: + return self._expand_fold_binary_op(op, children) + + # mirrors the tensorflow version + _print_Expr = _print_Function + _print_Application = _print_Function + _print_MatrixExpr = _print_Function + _print_Relational = _print_Function + _print_Not = _print_Function + _print_And = _print_Function + _print_Or = _print_Function + _print_HadamardProduct = _print_Function + _print_Trace = _print_Function + _print_Determinant = _print_Function + + def _print_Inverse(self, expr): + return '{}({})'.format(self._module_format("torch.linalg.inv"), + self._print(expr.args[0])) + + def _print_Transpose(self, expr): + if expr.arg.is_Matrix and expr.arg.shape[0] == expr.arg.shape[1]: + # For square matrices, we can use the .t() method + return "{}({}).t()".format("torch.transpose", self._print(expr.arg)) + else: + # For non-square matrices or more general cases + # transpose first and second dimensions (typical matrix transpose) + return "{}.permute({})".format( + self._print(expr.arg), + ", ".join([str(i) for i in range(len(expr.arg.shape))])[::-1] + ) + + def _print_PermuteDims(self, expr): + return "%s.permute(%s)" % ( + self._print(expr.expr), + ", ".join(str(i) for i in expr.permutation.array_form) + ) + + def _print_Derivative(self, expr): + # this version handles multi-variable and mixed partial derivatives. The tensorflow version does not. + variables = expr.variables + expr_arg = expr.expr + + # Handle multi-variable or repeated derivatives + if len(variables) > 1 or ( + len(variables) == 1 and not isinstance(variables[0], tuple) and variables.count(variables[0]) > 1): + result = self._print(expr_arg) + var_groups = {} + + # Group variables by base symbol + for var in variables: + if isinstance(var, tuple): + base_var, order = var + var_groups[base_var] = var_groups.get(base_var, 0) + order + else: + var_groups[var] = var_groups.get(var, 0) + 1 + + # Apply gradients in sequence + for var, order in var_groups.items(): + for _ in range(order): + result = "torch.autograd.grad({}, {}, create_graph=True)[0]".format(result, self._print(var)) + return result + + # Handle single variable case + if len(variables) == 1: + variable = variables[0] + if isinstance(variable, tuple) and len(variable) == 2: + base_var, order = variable + if not isinstance(order, Integer): raise NotImplementedError("Only integer orders are supported") + result = self._print(expr_arg) + for _ in range(order): + result = "torch.autograd.grad({}, {}, create_graph=True)[0]".format(result, self._print(base_var)) + return result + return "torch.autograd.grad({}, {})[0]".format(self._print(expr_arg), self._print(variable)) + + return self._print(expr_arg) # Empty variables case + + def _print_Piecewise(self, expr): + from sympy import Piecewise + e, cond = expr.args[0].args + if len(expr.args) == 1: + return '{}({}, {}, {})'.format( + self._module_format("torch.where"), + self._print(cond), + self._print(e), + 0) + + return '{}({}, {}, {})'.format( + self._module_format("torch.where"), + self._print(cond), + self._print(e), + self._print(Piecewise(*expr.args[1:]))) + + def _print_Pow(self, expr): + # XXX May raise error for + # int**float or int**complex or float**complex + base, exp = expr.args + if expr.exp == S.Half: + return "{}({})".format( + self._module_format("torch.sqrt"), self._print(base)) + return "{}({}, {})".format( + self._module_format("torch.pow"), + self._print(base), self._print(exp)) + + def _print_MatMul(self, expr): + # Separate matrix and scalar arguments + mat_args = [arg for arg in expr.args if isinstance(arg, MatrixExpr)] + args = [arg for arg in expr.args if arg not in mat_args] + # Handle scalar multipliers if present + if args: + return "%s*%s" % ( + self.parenthesize(Mul.fromiter(args), PRECEDENCE["Mul"]), + self._expand_fold_binary_op("torch.matmul", mat_args) + ) + else: + return self._expand_fold_binary_op("torch.matmul", mat_args) + + def _print_MatPow(self, expr): + return self._expand_fold_binary_op("torch.mm", [expr.base]*expr.exp) + + def _print_MatrixBase(self, expr): + data = "[" + ", ".join(["[" + ", ".join([self._print(j) for j in i]) + "]" for i in expr.tolist()]) + "]" + params = [str(data)] + params.append(f"dtype={self.dtype}") + if self.requires_grad: + params.append("requires_grad=True") + + return "{}({})".format( + self._module_format("torch.tensor"), + ", ".join(params) + ) + + def _print_isnan(self, expr): + return f'torch.isnan({self._print(expr.args[0])})' + + def _print_isinf(self, expr): + return f'torch.isinf({self._print(expr.args[0])})' + + def _print_Identity(self, expr): + if all(dim.is_Integer for dim in expr.shape): + return "{}({})".format( + self._module_format("torch.eye"), + self._print(expr.shape[0]) + ) + else: + # For symbolic dimensions, fall back to a more general approach + return "{}({}, {})".format( + self._module_format("torch.eye"), + self._print(expr.shape[0]), + self._print(expr.shape[1]) + ) + + def _print_ZeroMatrix(self, expr): + return "{}({})".format( + self._module_format("torch.zeros"), + self._print(expr.shape) + ) + + def _print_OneMatrix(self, expr): + return "{}({})".format( + self._module_format("torch.ones"), + self._print(expr.shape) + ) + + def _print_conjugate(self, expr): + return f"{self._module_format('torch.conj')}({self._print(expr.args[0])})" + + def _print_ImaginaryUnit(self, expr): + return "1j" # uses the Python built-in 1j notation for the imaginary unit + + def _print_Heaviside(self, expr): + args = [self._print(expr.args[0]), "0.5"] + if len(expr.args) > 1: + args[1] = self._print(expr.args[1]) + return f"{self._module_format('torch.heaviside')}({args[0]}, {args[1]})" + + def _print_gamma(self, expr): + return f"{self._module_format('torch.special.gamma')}({self._print(expr.args[0])})" + + def _print_polygamma(self, expr): + if expr.args[0] == S.Zero: + return f"{self._module_format('torch.special.digamma')}({self._print(expr.args[1])})" + else: + raise NotImplementedError("PyTorch only supports digamma (0th order polygamma)") + + _module = "torch" + _einsum = "einsum" + _add = "add" + _transpose = "t" + _ones = "ones" + _zeros = "zeros" + +def torch_code(expr, requires_grad=False, dtype="torch.float64", **settings): + printer = TorchPrinter(settings={'requires_grad': requires_grad, 'dtype': dtype}) + return printer.doprint(expr, **settings) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/rcode.py b/venv/lib/python3.10/site-packages/sympy/printing/rcode.py new file mode 100644 index 0000000000000000000000000000000000000000..3107e6e94d5c5acf0b2dc063e4a83af6970f6576 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/rcode.py @@ -0,0 +1,402 @@ +""" +R code printer + +The RCodePrinter converts single SymPy expressions into single R expressions, +using the functions defined in math.h where possible. + + + +""" + +from __future__ import annotations +from typing import Any + +from sympy.core.numbers import equal_valued +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import precedence, PRECEDENCE +from sympy.sets.fancysets import Range + +# dictionary mapping SymPy function to (argument_conditions, C_function). +# Used in RCodePrinter._print_Function(self) +known_functions = { + #"Abs": [(lambda x: not x.is_integer, "fabs")], + "Abs": "abs", + "sin": "sin", + "cos": "cos", + "tan": "tan", + "asin": "asin", + "acos": "acos", + "atan": "atan", + "atan2": "atan2", + "exp": "exp", + "log": "log", + "erf": "erf", + "sinh": "sinh", + "cosh": "cosh", + "tanh": "tanh", + "asinh": "asinh", + "acosh": "acosh", + "atanh": "atanh", + "floor": "floor", + "ceiling": "ceiling", + "sign": "sign", + "Max": "max", + "Min": "min", + "factorial": "factorial", + "gamma": "gamma", + "digamma": "digamma", + "trigamma": "trigamma", + "beta": "beta", + "sqrt": "sqrt", # To enable automatic rewrite +} + +# These are the core reserved words in the R language. Taken from: +# https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Reserved-words + +reserved_words = ['if', + 'else', + 'repeat', + 'while', + 'function', + 'for', + 'in', + 'next', + 'break', + 'TRUE', + 'FALSE', + 'NULL', + 'Inf', + 'NaN', + 'NA', + 'NA_integer_', + 'NA_real_', + 'NA_complex_', + 'NA_character_', + 'volatile'] + + +class RCodePrinter(CodePrinter): + """A printer to convert SymPy expressions to strings of R code""" + printmethod = "_rcode" + language = "R" + + _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{ + 'precision': 15, + 'user_functions': {}, + 'contract': True, + 'dereference': set(), + }) + _operators = { + 'and': '&', + 'or': '|', + 'not': '!', + } + + _relationals: dict[str, str] = {} + + def __init__(self, settings={}): + CodePrinter.__init__(self, settings) + self.known_functions = dict(known_functions) + userfuncs = settings.get('user_functions', {}) + self.known_functions.update(userfuncs) + self._dereference = set(settings.get('dereference', [])) + self.reserved_words = set(reserved_words) + + def _rate_index_position(self, p): + return p*5 + + def _get_statement(self, codestring): + return "%s;" % codestring + + def _get_comment(self, text): + return "// {}".format(text) + + def _declare_number_const(self, name, value): + return "{} = {};".format(name, value) + + def _format_code(self, lines): + return self.indent_code(lines) + + def _traverse_matrix_indices(self, mat): + rows, cols = mat.shape + return ((i, j) for i in range(rows) for j in range(cols)) + + def _get_loop_opening_ending(self, indices): + """Returns a tuple (open_lines, close_lines) containing lists of codelines + """ + open_lines = [] + close_lines = [] + loopstart = "for (%(var)s in %(start)s:%(end)s){" + for i in indices: + # R arrays start at 1 and end at dimension + open_lines.append(loopstart % { + 'var': self._print(i.label), + 'start': self._print(i.lower+1), + 'end': self._print(i.upper + 1)}) + close_lines.append("}") + return open_lines, close_lines + + def _print_Pow(self, expr): + if "Pow" in self.known_functions: + return self._print_Function(expr) + PREC = precedence(expr) + if equal_valued(expr.exp, -1): + return '1.0/%s' % (self.parenthesize(expr.base, PREC)) + elif equal_valued(expr.exp, 0.5): + return 'sqrt(%s)' % self._print(expr.base) + else: + return '%s^%s' % (self.parenthesize(expr.base, PREC), + self.parenthesize(expr.exp, PREC)) + + + def _print_Rational(self, expr): + p, q = int(expr.p), int(expr.q) + return '%d.0/%d.0' % (p, q) + + def _print_Indexed(self, expr): + inds = [ self._print(i) for i in expr.indices ] + return "%s[%s]" % (self._print(expr.base.label), ", ".join(inds)) + + def _print_Exp1(self, expr): + return "exp(1)" + + def _print_Pi(self, expr): + return 'pi' + + def _print_Infinity(self, expr): + return 'Inf' + + def _print_NegativeInfinity(self, expr): + return '-Inf' + + def _print_Assignment(self, expr): + from sympy.codegen.ast import Assignment + + from sympy.matrices.expressions.matexpr import MatrixSymbol + from sympy.tensor.indexed import IndexedBase + lhs = expr.lhs + rhs = expr.rhs + # We special case assignments that take multiple lines + #if isinstance(expr.rhs, Piecewise): + # from sympy.functions.elementary.piecewise import Piecewise + # # Here we modify Piecewise so each expression is now + # # an Assignment, and then continue on the print. + # expressions = [] + # conditions = [] + # for (e, c) in rhs.args: + # expressions.append(Assignment(lhs, e)) + # conditions.append(c) + # temp = Piecewise(*zip(expressions, conditions)) + # return self._print(temp) + #elif isinstance(lhs, MatrixSymbol): + if isinstance(lhs, MatrixSymbol): + # Here we form an Assignment for each element in the array, + # printing each one. + lines = [] + for (i, j) in self._traverse_matrix_indices(lhs): + temp = Assignment(lhs[i, j], rhs[i, j]) + code0 = self._print(temp) + lines.append(code0) + return "\n".join(lines) + elif self._settings["contract"] and (lhs.has(IndexedBase) or + rhs.has(IndexedBase)): + # Here we check if there is looping to be done, and if so + # print the required loops. + return self._doprint_loops(rhs, lhs) + else: + lhs_code = self._print(lhs) + rhs_code = self._print(rhs) + return self._get_statement("%s = %s" % (lhs_code, rhs_code)) + + def _print_Piecewise(self, expr): + # This method is called only for inline if constructs + # Top level piecewise is handled in doprint() + if expr.args[-1].cond == True: + last_line = "%s" % self._print(expr.args[-1].expr) + else: + last_line = "ifelse(%s,%s,NA)" % (self._print(expr.args[-1].cond), self._print(expr.args[-1].expr)) + code=last_line + for e, c in reversed(expr.args[:-1]): + code= "ifelse(%s,%s," % (self._print(c), self._print(e))+code+")" + return(code) + + def _print_ITE(self, expr): + from sympy.functions import Piecewise + return self._print(expr.rewrite(Piecewise)) + + def _print_MatrixElement(self, expr): + return "{}[{}]".format(self.parenthesize(expr.parent, PRECEDENCE["Atom"], + strict=True), expr.j + expr.i*expr.parent.shape[1]) + + def _print_Symbol(self, expr): + name = super()._print_Symbol(expr) + if expr in self._dereference: + return '(*{})'.format(name) + else: + return name + + def _print_Relational(self, expr): + lhs_code = self._print(expr.lhs) + rhs_code = self._print(expr.rhs) + op = expr.rel_op + return "{} {} {}".format(lhs_code, op, rhs_code) + + def _print_AugmentedAssignment(self, expr): + lhs_code = self._print(expr.lhs) + op = expr.op + rhs_code = self._print(expr.rhs) + return "{} {} {};".format(lhs_code, op, rhs_code) + + def _print_For(self, expr): + target = self._print(expr.target) + if isinstance(expr.iterable, Range): + start, stop, step = expr.iterable.args + else: + raise NotImplementedError("Only iterable currently supported is Range") + body = self._print(expr.body) + return 'for({target} in seq(from={start}, to={stop}, by={step}){{\n{body}\n}}'.format(target=target, start=start, + stop=stop-1, step=step, body=body) + + + def indent_code(self, code): + """Accepts a string of code or a list of code lines""" + + if isinstance(code, str): + code_lines = self.indent_code(code.splitlines(True)) + return ''.join(code_lines) + + tab = " " + inc_token = ('{', '(', '{\n', '(\n') + dec_token = ('}', ')') + + code = [ line.lstrip(' \t') for line in code ] + + increase = [ int(any(map(line.endswith, inc_token))) for line in code ] + decrease = [ int(any(map(line.startswith, dec_token))) + for line in code ] + + pretty = [] + level = 0 + for n, line in enumerate(code): + if line in ('', '\n'): + pretty.append(line) + continue + level -= decrease[n] + pretty.append("%s%s" % (tab*level, line)) + level += increase[n] + return pretty + + +def rcode(expr, assign_to=None, **settings): + """Converts an expr to a string of r code + + Parameters + ========== + + expr : Expr + A SymPy expression to be converted. + assign_to : optional + When given, the argument is used as the name of the variable to which + the expression is assigned. Can be a string, ``Symbol``, + ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of + line-wrapping, or for expressions that generate multi-line statements. + precision : integer, optional + The precision for numbers such as pi [default=15]. + user_functions : dict, optional + A dictionary where the keys are string representations of either + ``FunctionClass`` or ``UndefinedFunction`` instances and the values + are their desired R string representations. Alternatively, the + dictionary value can be a list of tuples i.e. [(argument_test, + rfunction_string)] or [(argument_test, rfunction_formater)]. See below + for examples. + human : bool, optional + If True, the result is a single string that may contain some constant + declarations for the number symbols. If False, the same information is + returned in a tuple of (symbols_to_declare, not_supported_functions, + code_text). [default=True]. + contract: bool, optional + If True, ``Indexed`` instances are assumed to obey tensor contraction + rules and the corresponding nested loops over indices are generated. + Setting contract=False will not generate loops, instead the user is + responsible to provide values for the indices in the code. + [default=True]. + + Examples + ======== + + >>> from sympy import rcode, symbols, Rational, sin, ceiling, Abs, Function + >>> x, tau = symbols("x, tau") + >>> rcode((2*tau)**Rational(7, 2)) + '8*sqrt(2)*tau^(7.0/2.0)' + >>> rcode(sin(x), assign_to="s") + 's = sin(x);' + + Simple custom printing can be defined for certain types by passing a + dictionary of {"type" : "function"} to the ``user_functions`` kwarg. + Alternatively, the dictionary value can be a list of tuples i.e. + [(argument_test, cfunction_string)]. + + >>> custom_functions = { + ... "ceiling": "CEIL", + ... "Abs": [(lambda x: not x.is_integer, "fabs"), + ... (lambda x: x.is_integer, "ABS")], + ... "func": "f" + ... } + >>> func = Function('func') + >>> rcode(func(Abs(x) + ceiling(x)), user_functions=custom_functions) + 'f(fabs(x) + CEIL(x))' + + or if the R-function takes a subset of the original arguments: + + >>> rcode(2**x + 3**x, user_functions={'Pow': [ + ... (lambda b, e: b == 2, lambda b, e: 'exp2(%s)' % e), + ... (lambda b, e: b != 2, 'pow')]}) + 'exp2(x) + pow(3, x)' + + ``Piecewise`` expressions are converted into conditionals. If an + ``assign_to`` variable is provided an if statement is created, otherwise + the ternary operator is used. Note that if the ``Piecewise`` lacks a + default term, represented by ``(expr, True)`` then an error will be thrown. + This is to prevent generating an expression that may not evaluate to + anything. + + >>> from sympy import Piecewise + >>> expr = Piecewise((x + 1, x > 0), (x, True)) + >>> print(rcode(expr, assign_to=tau)) + tau = ifelse(x > 0,x + 1,x); + + Support for loops is provided through ``Indexed`` types. With + ``contract=True`` these expressions will be turned into loops, whereas + ``contract=False`` will just print the assignment expression that should be + looped over: + + >>> from sympy import Eq, IndexedBase, Idx + >>> len_y = 5 + >>> y = IndexedBase('y', shape=(len_y,)) + >>> t = IndexedBase('t', shape=(len_y,)) + >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) + >>> i = Idx('i', len_y-1) + >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) + >>> rcode(e.rhs, assign_to=e.lhs, contract=False) + 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);' + + Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions + must be provided to ``assign_to``. Note that any expression that can be + generated normally can also exist inside a Matrix: + + >>> from sympy import Matrix, MatrixSymbol + >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)]) + >>> A = MatrixSymbol('A', 3, 1) + >>> print(rcode(mat, A)) + A[0] = x^2; + A[1] = ifelse(x > 0,x + 1,x); + A[2] = sin(x); + + """ + + return RCodePrinter(settings).doprint(expr, assign_to) + + +def print_rcode(expr, **settings): + """Prints R representation of the given expression.""" + print(rcode(expr, **settings)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/repr.py b/venv/lib/python3.10/site-packages/sympy/printing/repr.py new file mode 100644 index 0000000000000000000000000000000000000000..0a4b756abbab77c3eb0fd77ee1f0bd97382c36fb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/repr.py @@ -0,0 +1,339 @@ +""" +A Printer for generating executable code. + +The most important function here is srepr that returns a string so that the +relation eval(srepr(expr))=expr holds in an appropriate environment. +""" + +from __future__ import annotations +from typing import Any + +from sympy.core.function import AppliedUndef +from sympy.core.mul import Mul +from mpmath.libmp import repr_dps, to_str as mlib_to_str + +from .printer import Printer, print_function + + +class ReprPrinter(Printer): + printmethod = "_sympyrepr" + + _default_settings: dict[str, Any] = { + "order": None, + "perm_cyclic" : True, + } + + def reprify(self, args, sep): + """ + Prints each item in `args` and joins them with `sep`. + """ + return sep.join([self.doprint(item) for item in args]) + + def emptyPrinter(self, expr): + """ + The fallback printer. + """ + if isinstance(expr, str): + return expr + elif hasattr(expr, "__srepr__"): + return expr.__srepr__() + elif hasattr(expr, "args") and hasattr(expr.args, "__iter__"): + l = [] + for o in expr.args: + l.append(self._print(o)) + return expr.__class__.__name__ + '(%s)' % ', '.join(l) + elif hasattr(expr, "__module__") and hasattr(expr, "__name__"): + return "<'%s.%s'>" % (expr.__module__, expr.__name__) + else: + return str(expr) + + def _print_Add(self, expr, order=None): + args = self._as_ordered_terms(expr, order=order) + args = map(self._print, args) + clsname = type(expr).__name__ + return clsname + "(%s)" % ", ".join(args) + + def _print_Cycle(self, expr): + return expr.__repr__() + + def _print_Permutation(self, expr): + from sympy.combinatorics.permutations import Permutation, Cycle + from sympy.utilities.exceptions import sympy_deprecation_warning + + perm_cyclic = Permutation.print_cyclic + if perm_cyclic is not None: + sympy_deprecation_warning( + f""" + Setting Permutation.print_cyclic is deprecated. Instead use + init_printing(perm_cyclic={perm_cyclic}). + """, + deprecated_since_version="1.6", + active_deprecations_target="deprecated-permutation-print_cyclic", + stacklevel=7, + ) + else: + perm_cyclic = self._settings.get("perm_cyclic", True) + + if perm_cyclic: + if not expr.size: + return 'Permutation()' + # before taking Cycle notation, see if the last element is + # a singleton and move it to the head of the string + s = Cycle(expr)(expr.size - 1).__repr__()[len('Cycle'):] + last = s.rfind('(') + if not last == 0 and ',' not in s[last:]: + s = s[last:] + s[:last] + return 'Permutation%s' %s + else: + s = expr.support() + if not s: + if expr.size < 5: + return 'Permutation(%s)' % str(expr.array_form) + return 'Permutation([], size=%s)' % expr.size + trim = str(expr.array_form[:s[-1] + 1]) + ', size=%s' % expr.size + use = full = str(expr.array_form) + if len(trim) < len(full): + use = trim + return 'Permutation(%s)' % use + + def _print_Function(self, expr): + r = self._print(expr.func) + r += '(%s)' % ', '.join([self._print(a) for a in expr.args]) + return r + + def _print_Heaviside(self, expr): + # Same as _print_Function but uses pargs to suppress default value for + # 2nd arg. + r = self._print(expr.func) + r += '(%s)' % ', '.join([self._print(a) for a in expr.pargs]) + return r + + def _print_FunctionClass(self, expr): + if issubclass(expr, AppliedUndef): + return 'Function(%r)' % (expr.__name__) + else: + return expr.__name__ + + def _print_Half(self, expr): + return 'Rational(1, 2)' + + def _print_RationalConstant(self, expr): + return str(expr) + + def _print_AtomicExpr(self, expr): + return str(expr) + + def _print_NumberSymbol(self, expr): + return str(expr) + + def _print_Integer(self, expr): + return 'Integer(%i)' % expr.p + + def _print_Complexes(self, expr): + return 'Complexes' + + def _print_Integers(self, expr): + return 'Integers' + + def _print_Naturals(self, expr): + return 'Naturals' + + def _print_Naturals0(self, expr): + return 'Naturals0' + + def _print_Rationals(self, expr): + return 'Rationals' + + def _print_Reals(self, expr): + return 'Reals' + + def _print_EmptySet(self, expr): + return 'EmptySet' + + def _print_UniversalSet(self, expr): + return 'UniversalSet' + + def _print_EmptySequence(self, expr): + return 'EmptySequence' + + def _print_list(self, expr): + return "[%s]" % self.reprify(expr, ", ") + + def _print_dict(self, expr): + sep = ", " + dict_kvs = ["%s: %s" % (self.doprint(key), self.doprint(value)) for key, value in expr.items()] + return "{%s}" % sep.join(dict_kvs) + + def _print_set(self, expr): + if not expr: + return "set()" + return "{%s}" % self.reprify(expr, ", ") + + def _print_MatrixBase(self, expr): + # special case for some empty matrices + if (expr.rows == 0) ^ (expr.cols == 0): + return '%s(%s, %s, %s)' % (expr.__class__.__name__, + self._print(expr.rows), + self._print(expr.cols), + self._print([])) + l = [] + for i in range(expr.rows): + l.append([]) + for j in range(expr.cols): + l[-1].append(expr[i, j]) + return '%s(%s)' % (expr.__class__.__name__, self._print(l)) + + def _print_BooleanTrue(self, expr): + return "true" + + def _print_BooleanFalse(self, expr): + return "false" + + def _print_NaN(self, expr): + return "nan" + + def _print_Mul(self, expr, order=None): + if self.order not in ('old', 'none'): + args = expr.as_ordered_factors() + else: + # use make_args in case expr was something like -x -> x + args = Mul.make_args(expr) + + args = map(self._print, args) + clsname = type(expr).__name__ + return clsname + "(%s)" % ", ".join(args) + + def _print_Rational(self, expr): + return 'Rational(%s, %s)' % (self._print(expr.p), self._print(expr.q)) + + def _print_PythonRational(self, expr): + return "%s(%d, %d)" % (expr.__class__.__name__, expr.p, expr.q) + + def _print_Fraction(self, expr): + return 'Fraction(%s, %s)' % (self._print(expr.numerator), self._print(expr.denominator)) + + def _print_Float(self, expr): + r = mlib_to_str(expr._mpf_, repr_dps(expr._prec)) + return "%s('%s', precision=%i)" % (expr.__class__.__name__, r, expr._prec) + + def _print_Sum2(self, expr): + return "Sum2(%s, (%s, %s, %s))" % (self._print(expr.f), self._print(expr.i), + self._print(expr.a), self._print(expr.b)) + + def _print_Str(self, s): + return "%s(%s)" % (s.__class__.__name__, self._print(s.name)) + + def _print_Symbol(self, expr): + d = expr._assumptions_orig + # print the dummy_index like it was an assumption + if expr.is_Dummy: + d = d.copy() + d['dummy_index'] = expr.dummy_index + + if d == {}: + return "%s(%s)" % (expr.__class__.__name__, self._print(expr.name)) + else: + attr = ['%s=%s' % (k, v) for k, v in d.items()] + return "%s(%s, %s)" % (expr.__class__.__name__, + self._print(expr.name), ', '.join(attr)) + + def _print_CoordinateSymbol(self, expr): + d = expr._assumptions.generator + + if d == {}: + return "%s(%s, %s)" % ( + expr.__class__.__name__, + self._print(expr.coord_sys), + self._print(expr.index) + ) + else: + attr = ['%s=%s' % (k, v) for k, v in d.items()] + return "%s(%s, %s, %s)" % ( + expr.__class__.__name__, + self._print(expr.coord_sys), + self._print(expr.index), + ', '.join(attr) + ) + + def _print_Predicate(self, expr): + return "Q.%s" % expr.name + + def _print_AppliedPredicate(self, expr): + # will be changed to just expr.args when args overriding is removed + args = expr._args + return "%s(%s)" % (expr.__class__.__name__, self.reprify(args, ", ")) + + def _print_str(self, expr): + return repr(expr) + + def _print_tuple(self, expr): + if len(expr) == 1: + return "(%s,)" % self._print(expr[0]) + else: + return "(%s)" % self.reprify(expr, ", ") + + def _print_WildFunction(self, expr): + return "%s('%s')" % (expr.__class__.__name__, expr.name) + + def _print_AlgebraicNumber(self, expr): + return "%s(%s, %s)" % (expr.__class__.__name__, + self._print(expr.root), self._print(expr.coeffs())) + + def _print_PolyRing(self, ring): + return "%s(%s, %s, %s)" % (ring.__class__.__name__, + self._print(ring.symbols), self._print(ring.domain), self._print(ring.order)) + + def _print_FracField(self, field): + return "%s(%s, %s, %s)" % (field.__class__.__name__, + self._print(field.symbols), self._print(field.domain), self._print(field.order)) + + def _print_PolyElement(self, poly): + terms = list(poly.terms()) + terms.sort(key=poly.ring.order, reverse=True) + return "%s(%s, %s)" % (poly.__class__.__name__, self._print(poly.ring), self._print(terms)) + + def _print_FracElement(self, frac): + numer_terms = list(frac.numer.terms()) + numer_terms.sort(key=frac.field.order, reverse=True) + denom_terms = list(frac.denom.terms()) + denom_terms.sort(key=frac.field.order, reverse=True) + numer = self._print(numer_terms) + denom = self._print(denom_terms) + return "%s(%s, %s, %s)" % (frac.__class__.__name__, self._print(frac.field), numer, denom) + + def _print_FractionField(self, domain): + cls = domain.__class__.__name__ + field = self._print(domain.field) + return "%s(%s)" % (cls, field) + + def _print_PolynomialRingBase(self, ring): + cls = ring.__class__.__name__ + dom = self._print(ring.domain) + gens = ', '.join(map(self._print, ring.gens)) + order = str(ring.order) + if order != ring.default_order: + orderstr = ", order=" + order + else: + orderstr = "" + return "%s(%s, %s%s)" % (cls, dom, gens, orderstr) + + def _print_DMP(self, p): + cls = p.__class__.__name__ + rep = self._print(p.to_list()) + dom = self._print(p.dom) + return "%s(%s, %s)" % (cls, rep, dom) + + def _print_MonogenicFiniteExtension(self, ext): + # The expanded tree shown by srepr(ext.modulus) + # is not practical. + return "FiniteExtension(%s)" % str(ext.modulus) + + def _print_ExtensionElement(self, f): + rep = self._print(f.rep) + ext = self._print(f.ext) + return "ExtElem(%s, %s)" % (rep, ext) + +@print_function(ReprPrinter) +def srepr(expr, **settings): + """return expr in repr form""" + return ReprPrinter(settings).doprint(expr) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/rust.py b/venv/lib/python3.10/site-packages/sympy/printing/rust.py new file mode 100644 index 0000000000000000000000000000000000000000..5bfd481bec6b7350df281accfb9b7a598cf05baa --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/rust.py @@ -0,0 +1,637 @@ +""" +Rust code printer + +The `RustCodePrinter` converts SymPy expressions into Rust expressions. + +A complete code generator, which uses `rust_code` extensively, can be found +in `sympy.utilities.codegen`. The `codegen` module can be used to generate +complete source code files. + +""" + +# Possible Improvement +# +# * make sure we follow Rust Style Guidelines_ +# * make use of pattern matching +# * better support for reference +# * generate generic code and use trait to make sure they have specific methods +# * use crates_ to get more math support +# - num_ +# + BigInt_, BigUint_ +# + Complex_ +# + Rational64_, Rational32_, BigRational_ +# +# .. _crates: https://crates.io/ +# .. _Guidelines: https://github.com/rust-lang/rust/tree/master/src/doc/style +# .. _num: http://rust-num.github.io/num/num/ +# .. _BigInt: http://rust-num.github.io/num/num/bigint/struct.BigInt.html +# .. _BigUint: http://rust-num.github.io/num/num/bigint/struct.BigUint.html +# .. _Complex: http://rust-num.github.io/num/num/complex/struct.Complex.html +# .. _Rational32: http://rust-num.github.io/num/num/rational/type.Rational32.html +# .. _Rational64: http://rust-num.github.io/num/num/rational/type.Rational64.html +# .. _BigRational: http://rust-num.github.io/num/num/rational/type.BigRational.html + +from __future__ import annotations +from functools import reduce +import operator +from typing import Any + +from sympy.codegen.ast import ( + float32, float64, int32, + real, integer, bool_ +) +from sympy.core import S, Rational, Float, Lambda +from sympy.core.expr import Expr +from sympy.core.numbers import equal_valued +from sympy.functions.elementary.integers import ceiling, floor +from sympy.printing.codeprinter import CodePrinter +from sympy.printing.precedence import PRECEDENCE + +# Rust's methods for integer and float can be found at here : +# +# * `Rust - Primitive Type f64 `_ +# * `Rust - Primitive Type i64 `_ +# +# Function Style : +# +# 1. args[0].func(args[1:]), method with arguments +# 2. args[0].func(), method without arguments +# 3. args[1].func(), method without arguments (e.g. (e, x) => x.exp()) +# 4. func(args), function with arguments + +# dictionary mapping SymPy function to (argument_conditions, Rust_function). +# Used in RustCodePrinter._print_Function(self) + +class float_floor(floor): + """ + Same as `sympy.floor`, but mimics the Rust behavior of returning a float rather than an integer + """ + def _eval_is_integer(self): + return False + +class float_ceiling(ceiling): + """ + Same as `sympy.ceiling`, but mimics the Rust behavior of returning a float rather than an integer + """ + def _eval_is_integer(self): + return False + + +function_overrides = { + "floor": (floor, float_floor), + "ceiling": (ceiling, float_ceiling), +} + +# f64 method in Rust +known_functions = { + # "": "is_nan", + # "": "is_infinite", + # "": "is_finite", + # "": "is_normal", + # "": "classify", + "float_floor": "floor", + "float_ceiling": "ceil", + # "": "round", + # "": "trunc", + # "": "fract", + "Abs": "abs", + # "": "signum", + # "": "is_sign_positive", + # "": "is_sign_negative", + # "": "mul_add", + "Pow": [(lambda base, exp: equal_valued(exp, -1), "recip", 2), # 1.0/x + (lambda base, exp: equal_valued(exp, 0.5), "sqrt", 2), # x ** 0.5 + (lambda base, exp: equal_valued(exp, -0.5), "sqrt().recip", 2), # 1/(x ** 0.5) + (lambda base, exp: exp == Rational(1, 3), "cbrt", 2), # x ** (1/3) + (lambda base, exp: equal_valued(base, 2), "exp2", 3), # 2 ** x + (lambda base, exp: exp.is_integer, "powi", 1), # x ** y, for i32 + (lambda base, exp: not exp.is_integer, "powf", 1)], # x ** y, for f64 + "exp": [(lambda exp: True, "exp", 2)], # e ** x + "log": "ln", + # "": "log", # number.log(base) + # "": "log2", + # "": "log10", + # "": "to_degrees", + # "": "to_radians", + "Max": "max", + "Min": "min", + # "": "hypot", # (x**2 + y**2) ** 0.5 + "sin": "sin", + "cos": "cos", + "tan": "tan", + "asin": "asin", + "acos": "acos", + "atan": "atan", + "atan2": "atan2", + # "": "sin_cos", + # "": "exp_m1", # e ** x - 1 + # "": "ln_1p", # ln(1 + x) + "sinh": "sinh", + "cosh": "cosh", + "tanh": "tanh", + "asinh": "asinh", + "acosh": "acosh", + "atanh": "atanh", + "sqrt": "sqrt", # To enable automatic rewrites +} + +# i64 method in Rust +# known_functions_i64 = { +# "": "min_value", +# "": "max_value", +# "": "from_str_radix", +# "": "count_ones", +# "": "count_zeros", +# "": "leading_zeros", +# "": "trainling_zeros", +# "": "rotate_left", +# "": "rotate_right", +# "": "swap_bytes", +# "": "from_be", +# "": "from_le", +# "": "to_be", # to big endian +# "": "to_le", # to little endian +# "": "checked_add", +# "": "checked_sub", +# "": "checked_mul", +# "": "checked_div", +# "": "checked_rem", +# "": "checked_neg", +# "": "checked_shl", +# "": "checked_shr", +# "": "checked_abs", +# "": "saturating_add", +# "": "saturating_sub", +# "": "saturating_mul", +# "": "wrapping_add", +# "": "wrapping_sub", +# "": "wrapping_mul", +# "": "wrapping_div", +# "": "wrapping_rem", +# "": "wrapping_neg", +# "": "wrapping_shl", +# "": "wrapping_shr", +# "": "wrapping_abs", +# "": "overflowing_add", +# "": "overflowing_sub", +# "": "overflowing_mul", +# "": "overflowing_div", +# "": "overflowing_rem", +# "": "overflowing_neg", +# "": "overflowing_shl", +# "": "overflowing_shr", +# "": "overflowing_abs", +# "Pow": "pow", +# "Abs": "abs", +# "sign": "signum", +# "": "is_positive", +# "": "is_negnative", +# } + +# These are the core reserved words in the Rust language. Taken from: +# https://doc.rust-lang.org/reference/keywords.html + +reserved_words = ['abstract', + 'as', + 'async', + 'await', + 'become', + 'box', + 'break', + 'const', + 'continue', + 'crate', + 'do', + 'dyn', + 'else', + 'enum', + 'extern', + 'false', + 'final', + 'fn', + 'for', + 'gen', + 'if', + 'impl', + 'in', + 'let', + 'loop', + 'macro', + 'match', + 'mod', + 'move', + 'mut', + 'override', + 'priv', + 'pub', + 'ref', + 'return', + 'Self', + 'self', + 'static', + 'struct', + 'super', + 'trait', + 'true', + 'try', + 'type', + 'typeof', + 'unsafe', + 'unsized', + 'use', + 'virtual', + 'where', + 'while', + 'yield'] + + +class TypeCast(Expr): + """ + The type casting operator of the Rust language. + """ + + def __init__(self, expr, type_) -> None: + super().__init__() + self.explicit = expr.is_integer and type_ is not integer + self._assumptions = expr._assumptions + if self.explicit: + setattr(self, 'precedence', PRECEDENCE["Func"] + 10) + + @property + def expr(self): + return self.args[0] + + @property + def type_(self): + return self.args[1] + + def sort_key(self, order=None): + return self.args[0].sort_key(order=order) + + +class RustCodePrinter(CodePrinter): + """A printer to convert SymPy expressions to strings of Rust code""" + printmethod = "_rust_code" + language = "Rust" + + type_aliases = { + integer: int32, + real: float64, + } + + type_mappings = { + int32: 'i32', + float32: 'f32', + float64: 'f64', + bool_: 'bool' + } + + _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{ + 'precision': 17, + 'user_functions': {}, + 'contract': True, + 'dereference': set(), + }) + + def __init__(self, settings={}): + CodePrinter.__init__(self, settings) + self.known_functions = dict(known_functions) + userfuncs = settings.get('user_functions', {}) + self.known_functions.update(userfuncs) + self._dereference = set(settings.get('dereference', [])) + self.reserved_words = set(reserved_words) + self.function_overrides = function_overrides + + def _rate_index_position(self, p): + return p*5 + + def _get_statement(self, codestring): + return "%s;" % codestring + + def _get_comment(self, text): + return "// %s" % text + + def _declare_number_const(self, name, value): + type_ = self.type_mappings[self.type_aliases[real]] + return "const %s: %s = %s;" % (name, type_, value) + + def _format_code(self, lines): + return self.indent_code(lines) + + def _traverse_matrix_indices(self, mat): + rows, cols = mat.shape + return ((i, j) for i in range(rows) for j in range(cols)) + + def _get_loop_opening_ending(self, indices): + open_lines = [] + close_lines = [] + loopstart = "for %(var)s in %(start)s..%(end)s {" + for i in indices: + # Rust arrays start at 0 and end at dimension-1 + open_lines.append(loopstart % { + 'var': self._print(i), + 'start': self._print(i.lower), + 'end': self._print(i.upper + 1)}) + close_lines.append("}") + return open_lines, close_lines + + def _print_caller_var(self, expr): + if len(expr.args) > 1: + # for something like `sin(x + y + z)`, + # make sure we can get '(x + y + z).sin()' + # instead of 'x + y + z.sin()' + return '(' + self._print(expr) + ')' + elif expr.is_number: + return self._print(expr, _type=True) + else: + return self._print(expr) + + def _print_Function(self, expr): + """ + basic function for printing `Function` + + Function Style : + + 1. args[0].func(args[1:]), method with arguments + 2. args[0].func(), method without arguments + 3. args[1].func(), method without arguments (e.g. (e, x) => x.exp()) + 4. func(args), function with arguments + """ + + if expr.func.__name__ in self.known_functions: + cond_func = self.known_functions[expr.func.__name__] + func = None + style = 1 + if isinstance(cond_func, str): + func = cond_func + else: + for cond, func, style in cond_func: + if cond(*expr.args): + break + if func is not None: + if style == 1: + ret = "%(var)s.%(method)s(%(args)s)" % { + 'var': self._print_caller_var(expr.args[0]), + 'method': func, + 'args': self.stringify(expr.args[1:], ", ") if len(expr.args) > 1 else '' + } + elif style == 2: + ret = "%(var)s.%(method)s()" % { + 'var': self._print_caller_var(expr.args[0]), + 'method': func, + } + elif style == 3: + ret = "%(var)s.%(method)s()" % { + 'var': self._print_caller_var(expr.args[1]), + 'method': func, + } + else: + ret = "%(func)s(%(args)s)" % { + 'func': func, + 'args': self.stringify(expr.args, ", "), + } + return ret + elif hasattr(expr, '_imp_') and isinstance(expr._imp_, Lambda): + # inlined function + return self._print(expr._imp_(*expr.args)) + else: + return self._print_not_supported(expr) + + def _print_Mul(self, expr): + contains_floats = any(arg.is_real and not arg.is_integer for arg in expr.args) + if contains_floats: + expr = reduce(operator.mul,(self._cast_to_float(arg) if arg != -1 else arg for arg in expr.args)) + + return super()._print_Mul(expr) + + def _print_Add(self, expr, order=None): + contains_floats = any(arg.is_real and not arg.is_integer for arg in expr.args) + if contains_floats: + expr = reduce(operator.add, (self._cast_to_float(arg) for arg in expr.args)) + + return super()._print_Add(expr, order) + + def _print_Pow(self, expr): + if expr.base.is_integer and not expr.exp.is_integer: + expr = type(expr)(Float(expr.base), expr.exp) + return self._print(expr) + return self._print_Function(expr) + + def _print_TypeCast(self, expr): + if not expr.explicit: + return self._print(expr.expr) + else: + return self._print(expr.expr) + ' as %s' % self.type_mappings[self.type_aliases[expr.type_]] + + def _print_Float(self, expr, _type=False): + ret = super()._print_Float(expr) + if _type: + return ret + '_%s' % self.type_mappings[self.type_aliases[real]] + else: + return ret + + def _print_Integer(self, expr, _type=False): + ret = super()._print_Integer(expr) + if _type: + return ret + '_%s' % self.type_mappings[self.type_aliases[integer]] + else: + return ret + + def _print_Rational(self, expr): + p, q = int(expr.p), int(expr.q) + float_suffix = self.type_mappings[self.type_aliases[real]] + return '%d_%s/%d.0' % (p, float_suffix, q) + + def _print_Relational(self, expr): + if (expr.lhs.is_integer and not expr.rhs.is_integer) or (expr.rhs.is_integer and not expr.lhs.is_integer): + lhs = self._cast_to_float(expr.lhs) + rhs = self._cast_to_float(expr.rhs) + else: + lhs = expr.lhs + rhs = expr.rhs + lhs_code = self._print(lhs) + rhs_code = self._print(rhs) + op = expr.rel_op + return "{} {} {}".format(lhs_code, op, rhs_code) + + def _print_Indexed(self, expr): + # calculate index for 1d array + dims = expr.shape + elem = S.Zero + offset = S.One + for i in reversed(range(expr.rank)): + elem += expr.indices[i]*offset + offset *= dims[i] + return "%s[%s]" % (self._print(expr.base.label), self._print(elem)) + + def _print_Idx(self, expr): + return expr.label.name + + def _print_Dummy(self, expr): + return expr.name + + def _print_Exp1(self, expr, _type=False): + return "E" + + def _print_Pi(self, expr, _type=False): + return 'PI' + + def _print_Infinity(self, expr, _type=False): + return 'INFINITY' + + def _print_NegativeInfinity(self, expr, _type=False): + return 'NEG_INFINITY' + + def _print_BooleanTrue(self, expr, _type=False): + return "true" + + def _print_BooleanFalse(self, expr, _type=False): + return "false" + + def _print_bool(self, expr, _type=False): + return str(expr).lower() + + def _print_NaN(self, expr, _type=False): + return "NAN" + + def _print_Piecewise(self, expr): + if expr.args[-1].cond != True: + # We need the last conditional to be a True, otherwise the resulting + # function may not return a result. + raise ValueError("All Piecewise expressions must contain an " + "(expr, True) statement to be used as a default " + "condition. Without one, the generated " + "expression may not evaluate to anything under " + "some condition.") + lines = [] + + for i, (e, c) in enumerate(expr.args): + if i == 0: + lines.append("if (%s) {" % self._print(c)) + elif i == len(expr.args) - 1 and c == True: + lines[-1] += " else {" + else: + lines[-1] += " else if (%s) {" % self._print(c) + code0 = self._print(e) + lines.append(code0) + lines.append("}") + + if self._settings['inline']: + return " ".join(lines) + else: + return "\n".join(lines) + + def _print_ITE(self, expr): + from sympy.functions import Piecewise + return self._print(expr.rewrite(Piecewise, deep=False)) + + def _print_MatrixBase(self, A): + if A.cols == 1: + return "[%s]" % ", ".join(self._print(a) for a in A) + else: + raise ValueError("Full Matrix Support in Rust need Crates (https://crates.io/keywords/matrix).") + + def _print_SparseRepMatrix(self, mat): + # do not allow sparse matrices to be made dense + return self._print_not_supported(mat) + + def _print_MatrixElement(self, expr): + return "%s[%s]" % (expr.parent, + expr.j + expr.i*expr.parent.shape[1]) + + def _print_Symbol(self, expr): + + name = super()._print_Symbol(expr) + + if expr in self._dereference: + return '(*%s)' % name + else: + return name + + def _print_Assignment(self, expr): + from sympy.tensor.indexed import IndexedBase + lhs = expr.lhs + rhs = expr.rhs + if self._settings["contract"] and (lhs.has(IndexedBase) or + rhs.has(IndexedBase)): + # Here we check if there is looping to be done, and if so + # print the required loops. + return self._doprint_loops(rhs, lhs) + else: + lhs_code = self._print(lhs) + rhs_code = self._print(rhs) + return self._get_statement("%s = %s" % (lhs_code, rhs_code)) + + def _print_sign(self, expr): + arg = self._print(expr.args[0]) + return "(if (%s == 0.0) { 0.0 } else { (%s).signum() })" % (arg, arg) + + def _cast_to_float(self, expr): + if not expr.is_number: + return TypeCast(expr, real) + elif expr.is_integer: + return Float(expr) + return expr + + def _can_print(self, name): + """ Check if function ``name`` is either a known function or has its own + printing method. Used to check if rewriting is possible.""" + + # since the whole point of function_overrides is to enable proper printing, + # we presume they all are printable + + return name in self.known_functions or name in function_overrides or getattr(self, '_print_{}'.format(name), False) + + def _collect_functions(self, expr): + functions = set() + if isinstance(expr, Expr): + if expr.is_Function: + functions.add(expr.func) + for arg in expr.args: + functions = functions.union(self._collect_functions(arg)) + return functions + + def _rewrite_known_functions(self, expr): + if not isinstance(expr, Expr): + return expr + + expression_functions = self._collect_functions(expr) + rewriteable_functions = { + name: (target_f, required_fs) + for name, (target_f, required_fs) in self._rewriteable_functions.items() + if self._can_print(target_f) + and all(self._can_print(f) for f in required_fs) + } + for func in expression_functions: + target_f, _ = rewriteable_functions.get(func.__name__, (None, None)) + if target_f: + expr = expr.rewrite(target_f) + return expr + + def indent_code(self, code): + """Accepts a string of code or a list of code lines""" + + if isinstance(code, str): + code_lines = self.indent_code(code.splitlines(True)) + return ''.join(code_lines) + + tab = " " + inc_token = ('{', '(', '{\n', '(\n') + dec_token = ('}', ')') + + code = [ line.lstrip(' \t') for line in code ] + + increase = [ int(any(map(line.endswith, inc_token))) for line in code ] + decrease = [ int(any(map(line.startswith, dec_token))) + for line in code ] + + pretty = [] + level = 0 + for n, line in enumerate(code): + if line in ('', '\n'): + pretty.append(line) + continue + level -= decrease[n] + pretty.append("%s%s" % (tab*level, line)) + level += increase[n] + return pretty diff --git a/venv/lib/python3.10/site-packages/sympy/printing/smtlib.py b/venv/lib/python3.10/site-packages/sympy/printing/smtlib.py new file mode 100644 index 0000000000000000000000000000000000000000..8fa015c6198cb32837eb3a0d7fe9d61352da25ad --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/smtlib.py @@ -0,0 +1,583 @@ +import typing + +import sympy +from sympy.core import Add, Mul +from sympy.core import Symbol, Expr, Float, Rational, Integer, Basic +from sympy.core.function import UndefinedFunction, Function +from sympy.core.relational import Relational, Unequality, Equality, LessThan, GreaterThan, StrictLessThan, StrictGreaterThan +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp, log, Pow +from sympy.functions.elementary.hyperbolic import sinh, cosh, tanh +from sympy.functions.elementary.miscellaneous import Min, Max +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import sin, cos, tan, asin, acos, atan, atan2 +from sympy.logic.boolalg import And, Or, Xor, Implies, Boolean +from sympy.logic.boolalg import BooleanTrue, BooleanFalse, BooleanFunction, Not, ITE +from sympy.printing.printer import Printer +from sympy.sets import Interval +from mpmath.libmp.libmpf import prec_to_dps, to_str as mlib_to_str +from sympy.assumptions.assume import AppliedPredicate +from sympy.assumptions.relation.binrel import AppliedBinaryRelation +from sympy.assumptions.ask import Q +from sympy.assumptions.relation.equality import StrictGreaterThanPredicate, StrictLessThanPredicate, GreaterThanPredicate, LessThanPredicate, EqualityPredicate + + +class SMTLibPrinter(Printer): + printmethod = "_smtlib" + + # based on dReal, an automated reasoning tool for solving problems that can be encoded as first-order logic formulas over the real numbers. + # dReal's special strength is in handling problems that involve a wide range of nonlinear real functions. + _default_settings: dict = { + 'precision': None, + 'known_types': { + bool: 'Bool', + int: 'Int', + float: 'Real' + }, + 'known_constants': { + # pi: 'MY_VARIABLE_PI_DECLARED_ELSEWHERE', + }, + 'known_functions': { + Add: '+', + Mul: '*', + + Equality: '=', + LessThan: '<=', + GreaterThan: '>=', + StrictLessThan: '<', + StrictGreaterThan: '>', + + EqualityPredicate(): '=', + LessThanPredicate(): '<=', + GreaterThanPredicate(): '>=', + StrictLessThanPredicate(): '<', + StrictGreaterThanPredicate(): '>', + + exp: 'exp', + log: 'log', + Abs: 'abs', + sin: 'sin', + cos: 'cos', + tan: 'tan', + asin: 'arcsin', + acos: 'arccos', + atan: 'arctan', + atan2: 'arctan2', + sinh: 'sinh', + cosh: 'cosh', + tanh: 'tanh', + Min: 'min', + Max: 'max', + Pow: 'pow', + + And: 'and', + Or: 'or', + Xor: 'xor', + Not: 'not', + ITE: 'ite', + Implies: '=>', + } + } + + symbol_table: dict + + def __init__(self, settings: typing.Optional[dict] = None, + symbol_table=None): + settings = settings or {} + self.symbol_table = symbol_table or {} + Printer.__init__(self, settings) + self._precision = self._settings['precision'] + self._known_types = dict(self._settings['known_types']) + self._known_constants = dict(self._settings['known_constants']) + self._known_functions = dict(self._settings['known_functions']) + + for _ in self._known_types.values(): assert self._is_legal_name(_) + for _ in self._known_constants.values(): assert self._is_legal_name(_) + # for _ in self._known_functions.values(): assert self._is_legal_name(_) # +, *, <, >, etc. + + def _is_legal_name(self, s: str): + if not s: return False + if s[0].isnumeric(): return False + return all(_.isalnum() or _ == '_' for _ in s) + + def _s_expr(self, op: str, args: typing.Union[list, tuple]) -> str: + args_str = ' '.join( + a if isinstance(a, str) + else self._print(a) + for a in args + ) + return f'({op} {args_str})' + + def _print_Function(self, e): + if e in self._known_functions: + op = self._known_functions[e] + elif type(e) in self._known_functions: + op = self._known_functions[type(e)] + elif type(type(e)) == UndefinedFunction: + op = e.name + elif isinstance(e, AppliedBinaryRelation) and e.function in self._known_functions: + op = self._known_functions[e.function] + return self._s_expr(op, e.arguments) + else: + op = self._known_functions[e] # throw KeyError + + return self._s_expr(op, e.args) + + def _print_Relational(self, e: Relational): + return self._print_Function(e) + + def _print_BooleanFunction(self, e: BooleanFunction): + return self._print_Function(e) + + def _print_Expr(self, e: Expr): + return self._print_Function(e) + + def _print_Unequality(self, e: Unequality): + if type(e) in self._known_functions: + return self._print_Relational(e) # default + else: + eq_op = self._known_functions[Equality] + not_op = self._known_functions[Not] + return self._s_expr(not_op, [self._s_expr(eq_op, e.args)]) + + def _print_Piecewise(self, e: Piecewise): + def _print_Piecewise_recursive(args: typing.Union[list, tuple]): + e, c = args[0] + if len(args) == 1: + assert (c is True) or isinstance(c, BooleanTrue) + return self._print(e) + else: + ite = self._known_functions[ITE] + return self._s_expr(ite, [ + c, e, _print_Piecewise_recursive(args[1:]) + ]) + + return _print_Piecewise_recursive(e.args) + + def _print_Interval(self, e: Interval): + if e.start.is_infinite and e.end.is_infinite: + return '' + elif e.start.is_infinite != e.end.is_infinite: + raise ValueError(f'One-sided intervals (`{e}`) are not supported in SMT.') + else: + return f'[{e.start}, {e.end}]' + + def _print_AppliedPredicate(self, e: AppliedPredicate): + if e.function == Q.positive: + rel = Q.gt(e.arguments[0],0) + elif e.function == Q.negative: + rel = Q.lt(e.arguments[0], 0) + elif e.function == Q.zero: + rel = Q.eq(e.arguments[0], 0) + elif e.function == Q.nonpositive: + rel = Q.le(e.arguments[0], 0) + elif e.function == Q.nonnegative: + rel = Q.ge(e.arguments[0], 0) + elif e.function == Q.nonzero: + rel = Q.ne(e.arguments[0], 0) + else: + raise ValueError(f"Predicate (`{e}`) is not handled.") + + return self._print_AppliedBinaryRelation(rel) + + def _print_AppliedBinaryRelation(self, e: AppliedPredicate): + if e.function == Q.ne: + return self._print_Unequality(Unequality(*e.arguments)) + else: + return self._print_Function(e) + + # todo: Sympy does not support quantifiers yet as of 2022, but quantifiers can be handy in SMT. + # For now, users can extend this class and build in their own quantifier support. + # See `test_quantifier_extensions()` in test_smtlib.py for an example of how this might look. + + # def _print_ForAll(self, e: ForAll): + # return self._s('forall', [ + # self._s('', [ + # self._s(sym.name, [self._type_name(sym), Interval(start, end)]) + # for sym, start, end in e.limits + # ]), + # e.function + # ]) + + def _print_BooleanTrue(self, x: BooleanTrue): + return 'true' + + def _print_BooleanFalse(self, x: BooleanFalse): + return 'false' + + def _print_Float(self, x: Float): + dps = prec_to_dps(x._prec) + str_real = mlib_to_str(x._mpf_, dps, strip_zeros=True, min_fixed=None, max_fixed=None) + + if 'e' in str_real: + (mant, exp) = str_real.split('e') + + if exp[0] == '+': + exp = exp[1:] + + mul = self._known_functions[Mul] + pow = self._known_functions[Pow] + + return r"(%s %s (%s 10 %s))" % (mul, mant, pow, exp) + elif str_real in ["+inf", "-inf"]: + raise ValueError("Infinite values are not supported in SMT.") + else: + return str_real + + def _print_float(self, x: float): + return self._print(Float(x)) + + def _print_Rational(self, x: Rational): + return self._s_expr('/', [x.p, x.q]) + + def _print_Integer(self, x: Integer): + assert x.q == 1 + return str(x.p) + + def _print_int(self, x: int): + return str(x) + + def _print_Symbol(self, x: Symbol): + assert self._is_legal_name(x.name) + return x.name + + def _print_NumberSymbol(self, x): + name = self._known_constants.get(x) + if name: + return name + else: + f = x.evalf(self._precision) if self._precision else x.evalf() + return self._print_Float(f) + + def _print_UndefinedFunction(self, x): + assert self._is_legal_name(x.name) + return x.name + + def _print_Exp1(self, x): + return ( + self._print_Function(exp(1, evaluate=False)) + if exp in self._known_functions else + self._print_NumberSymbol(x) + ) + + def emptyPrinter(self, expr): + raise NotImplementedError(f'Cannot convert `{repr(expr)}` of type `{type(expr)}` to SMT.') + + +def smtlib_code( + expr, + auto_assert=True, auto_declare=True, + precision=None, + symbol_table=None, + known_types=None, known_constants=None, known_functions=None, + prefix_expressions=None, suffix_expressions=None, + log_warn=None +): + r"""Converts ``expr`` to a string of smtlib code. + + Parameters + ========== + + expr : Expr | List[Expr] + A SymPy expression or system to be converted. + auto_assert : bool, optional + If false, do not modify expr and produce only the S-Expression equivalent of expr. + If true, assume expr is a system and assert each boolean element. + auto_declare : bool, optional + If false, do not produce declarations for the symbols used in expr. + If true, prepend all necessary declarations for variables used in expr based on symbol_table. + precision : integer, optional + The ``evalf(..)`` precision for numbers such as pi. + symbol_table : dict, optional + A dictionary where keys are ``Symbol`` or ``Function`` instances and values are their Python type i.e. ``bool``, ``int``, ``float``, or ``Callable[...]``. + If incomplete, an attempt will be made to infer types from ``expr``. + known_types: dict, optional + A dictionary where keys are ``bool``, ``int``, ``float`` etc. and values are their corresponding SMT type names. + If not given, a partial listing compatible with several solvers will be used. + known_functions : dict, optional + A dictionary where keys are ``Function``, ``Relational``, ``BooleanFunction``, or ``Expr`` instances and values are their SMT string representations. + If not given, a partial listing optimized for dReal solver (but compatible with others) will be used. + known_constants: dict, optional + A dictionary where keys are ``NumberSymbol`` instances and values are their SMT variable names. + When using this feature, extra caution must be taken to avoid naming collisions between user symbols and listed constants. + If not given, constants will be expanded inline i.e. ``3.14159`` instead of ``MY_SMT_VARIABLE_FOR_PI``. + prefix_expressions: list, optional + A list of lists of ``str`` and/or expressions to convert into SMTLib and prefix to the output. + suffix_expressions: list, optional + A list of lists of ``str`` and/or expressions to convert into SMTLib and postfix to the output. + log_warn: lambda function, optional + A function to record all warnings during potentially risky operations. + Soundness is a core value in SMT solving, so it is good to log all assumptions made. + + Examples + ======== + >>> from sympy import smtlib_code, symbols, sin, Eq + >>> x = symbols('x') + >>> smtlib_code(sin(x).series(x).removeO(), log_warn=print) + Could not infer type of `x`. Defaulting to float. + Non-Boolean expression `x**5/120 - x**3/6 + x` will not be asserted. Converting to SMTLib verbatim. + '(declare-const x Real)\n(+ x (* (/ -1 6) (pow x 3)) (* (/ 1 120) (pow x 5)))' + + >>> from sympy import Rational + >>> x, y, tau = symbols("x, y, tau") + >>> smtlib_code((2*tau)**Rational(7, 2), log_warn=print) + Could not infer type of `tau`. Defaulting to float. + Non-Boolean expression `8*sqrt(2)*tau**(7/2)` will not be asserted. Converting to SMTLib verbatim. + '(declare-const tau Real)\n(* 8 (pow 2 (/ 1 2)) (pow tau (/ 7 2)))' + + ``Piecewise`` expressions are implemented with ``ite`` expressions by default. + Note that if the ``Piecewise`` lacks a default term, represented by + ``(expr, True)`` then an error will be thrown. This is to prevent + generating an expression that may not evaluate to anything. + + >>> from sympy import Piecewise + >>> pw = Piecewise((x + 1, x > 0), (x, True)) + >>> smtlib_code(Eq(pw, 3), symbol_table={x: float}, log_warn=print) + '(declare-const x Real)\n(assert (= (ite (> x 0) (+ 1 x) x) 3))' + + Custom printing can be defined for certain types by passing a dictionary of + PythonType : "SMT Name" to the ``known_types``, ``known_constants``, and ``known_functions`` kwargs. + + >>> from typing import Callable + >>> from sympy import Function, Add + >>> f = Function('f') + >>> g = Function('g') + >>> smt_builtin_funcs = { # functions our SMT solver will understand + ... f: "existing_smtlib_fcn", + ... Add: "sum", + ... } + >>> user_def_funcs = { # functions defined by the user must have their types specified explicitly + ... g: Callable[[int], float], + ... } + >>> smtlib_code(f(x) + g(x), symbol_table=user_def_funcs, known_functions=smt_builtin_funcs, log_warn=print) + Non-Boolean expression `f(x) + g(x)` will not be asserted. Converting to SMTLib verbatim. + '(declare-const x Int)\n(declare-fun g (Int) Real)\n(sum (existing_smtlib_fcn x) (g x))' + """ + log_warn = log_warn or (lambda _: None) + + if not isinstance(expr, list): expr = [expr] + expr = [ + sympy.sympify(_, strict=True, evaluate=False, convert_xor=False) + for _ in expr + ] + + if not symbol_table: symbol_table = {} + symbol_table = _auto_infer_smtlib_types( + *expr, symbol_table=symbol_table + ) + # See [FALLBACK RULES] + # Need SMTLibPrinter to populate known_functions and known_constants first. + + settings = {} + if precision: settings['precision'] = precision + del precision + + if known_types: settings['known_types'] = known_types + del known_types + + if known_functions: settings['known_functions'] = known_functions + del known_functions + + if known_constants: settings['known_constants'] = known_constants + del known_constants + + if not prefix_expressions: prefix_expressions = [] + if not suffix_expressions: suffix_expressions = [] + + p = SMTLibPrinter(settings, symbol_table) + del symbol_table + + # [FALLBACK RULES] + for e in expr: + for sym in e.atoms(Symbol, Function): + if ( + sym.is_Symbol and + sym not in p._known_constants and + sym not in p.symbol_table + ): + log_warn(f"Could not infer type of `{sym}`. Defaulting to float.") + p.symbol_table[sym] = float + if ( + sym.is_Function and + type(sym) not in p._known_functions and + type(sym) not in p.symbol_table and + not sym.is_Piecewise + ): raise TypeError( + f"Unknown type of undefined function `{sym}`. " + f"Must be mapped to ``str`` in known_functions or mapped to ``Callable[..]`` in symbol_table." + ) + + declarations = [] + if auto_declare: + constants = {sym.name: sym for e in expr for sym in e.free_symbols + if sym not in p._known_constants} + functions = {fnc.name: fnc for e in expr for fnc in e.atoms(Function) + if type(fnc) not in p._known_functions and not fnc.is_Piecewise} + declarations = \ + [ + _auto_declare_smtlib(sym, p, log_warn) + for sym in constants.values() + ] + [ + _auto_declare_smtlib(fnc, p, log_warn) + for fnc in functions.values() + ] + declarations = [decl for decl in declarations if decl] + + if auto_assert: + expr = [_auto_assert_smtlib(e, p, log_warn) for e in expr] + + # return SMTLibPrinter().doprint(expr) + return '\n'.join([ + # ';; PREFIX EXPRESSIONS', + *[ + e if isinstance(e, str) else p.doprint(e) + for e in prefix_expressions + ], + + # ';; DECLARATIONS', + *sorted(e for e in declarations), + + # ';; EXPRESSIONS', + *[ + e if isinstance(e, str) else p.doprint(e) + for e in expr + ], + + # ';; SUFFIX EXPRESSIONS', + *[ + e if isinstance(e, str) else p.doprint(e) + for e in suffix_expressions + ], + ]) + + +def _auto_declare_smtlib(sym: typing.Union[Symbol, Function], p: SMTLibPrinter, log_warn: typing.Callable[[str], None]): + if sym.is_Symbol: + type_signature = p.symbol_table[sym] + assert isinstance(type_signature, type) + type_signature = p._known_types[type_signature] + return p._s_expr('declare-const', [sym, type_signature]) + + elif sym.is_Function: + type_signature = p.symbol_table[type(sym)] + assert callable(type_signature) + type_signature = [p._known_types[_] for _ in type_signature.__args__] + assert len(type_signature) > 0 + params_signature = f"({' '.join(type_signature[:-1])})" + return_signature = type_signature[-1] + return p._s_expr('declare-fun', [type(sym), params_signature, return_signature]) + + else: + log_warn(f"Non-Symbol/Function `{sym}` will not be declared.") + return None + + +def _auto_assert_smtlib(e: Expr, p: SMTLibPrinter, log_warn: typing.Callable[[str], None]): + if isinstance(e, Boolean) or ( + e in p.symbol_table and p.symbol_table[e] == bool + ) or ( + e.is_Function and + type(e) in p.symbol_table and + p.symbol_table[type(e)].__args__[-1] == bool + ): + return p._s_expr('assert', [e]) + else: + log_warn(f"Non-Boolean expression `{e}` will not be asserted. Converting to SMTLib verbatim.") + return e + + +def _auto_infer_smtlib_types( + *exprs: Basic, + symbol_table: typing.Optional[dict] = None +) -> dict: + # [TYPE INFERENCE RULES] + # X is alone in an expr => X is bool + # X in BooleanFunction.args => X is bool + # X matches to a bool param of a symbol_table function => X is bool + # X matches to an int param of a symbol_table function => X is int + # X.is_integer => X is int + # X == Y, where X is T => Y is T + + # [FALLBACK RULES] + # see _auto_declare_smtlib(..) + # X is not bool and X is not int and X is Symbol => X is float + # else (e.g. X is Function) => error. must be specified explicitly. + + _symbols = dict(symbol_table) if symbol_table else {} + + def safe_update(syms: set, inf): + for s in syms: + assert s.is_Symbol + if (old_type := _symbols.setdefault(s, inf)) != inf: + raise TypeError(f"Could not infer type of `{s}`. Apparently both `{old_type}` and `{inf}`?") + + # EXPLICIT TYPES + safe_update({ + e + for e in exprs + if e.is_Symbol + }, bool) + + safe_update({ + symbol + for e in exprs + for boolfunc in e.atoms(BooleanFunction) + for symbol in boolfunc.args + if symbol.is_Symbol + }, bool) + + safe_update({ + symbol + for e in exprs + for boolfunc in e.atoms(Function) + if type(boolfunc) in _symbols + for symbol, param in zip(boolfunc.args, _symbols[type(boolfunc)].__args__) + if symbol.is_Symbol and param == bool + }, bool) + + safe_update({ + symbol + for e in exprs + for intfunc in e.atoms(Function) + if type(intfunc) in _symbols + for symbol, param in zip(intfunc.args, _symbols[type(intfunc)].__args__) + if symbol.is_Symbol and param == int + }, int) + + safe_update({ + symbol + for e in exprs + for symbol in e.atoms(Symbol) + if symbol.is_integer + }, int) + + safe_update({ + symbol + for e in exprs + for symbol in e.atoms(Symbol) + if symbol.is_real and not symbol.is_integer + }, float) + + # EQUALITY RELATION RULE + rels_eq = [rel for expr in exprs for rel in expr.atoms(Equality)] + rels = [ + (rel.lhs, rel.rhs) for rel in rels_eq if rel.lhs.is_Symbol + ] + [ + (rel.rhs, rel.lhs) for rel in rels_eq if rel.rhs.is_Symbol + ] + for infer, reltd in rels: + inference = ( + _symbols[infer] if infer in _symbols else + _symbols[reltd] if reltd in _symbols else + + _symbols[type(reltd)].__args__[-1] + if reltd.is_Function and type(reltd) in _symbols else + + bool if reltd.is_Boolean else + int if reltd.is_integer or reltd.is_Integer else + float if reltd.is_real else + None + ) + if inference: safe_update({infer}, inference) + + return _symbols diff --git a/venv/lib/python3.10/site-packages/sympy/printing/str.py b/venv/lib/python3.10/site-packages/sympy/printing/str.py new file mode 100644 index 0000000000000000000000000000000000000000..9975776fbb73bec9c956fe359387fa8036995795 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/str.py @@ -0,0 +1,1021 @@ +""" +A Printer for generating readable representation of most SymPy classes. +""" + +from __future__ import annotations +from typing import Any + +from sympy.core import S, Rational, Pow, Basic, Mul, Number +from sympy.core.mul import _keep_coeff +from sympy.core.numbers import Integer +from sympy.core.relational import Relational +from sympy.core.sorting import default_sort_key +from sympy.utilities.iterables import sift +from .precedence import precedence, PRECEDENCE +from .printer import Printer, print_function + +from mpmath.libmp import prec_to_dps, to_str as mlib_to_str + + +class StrPrinter(Printer): + printmethod = "_sympystr" + _default_settings: dict[str, Any] = { + "order": None, + "full_prec": "auto", + "sympy_integers": False, + "abbrev": False, + "perm_cyclic": True, + "min": None, + "max": None, + "dps" : None + } + + _relationals: dict[str, str] = {} + + def parenthesize(self, item, level, strict=False): + if (precedence(item) < level) or ((not strict) and precedence(item) <= level): + return "(%s)" % self._print(item) + else: + return self._print(item) + + def stringify(self, args, sep, level=0): + return sep.join([self.parenthesize(item, level) for item in args]) + + def emptyPrinter(self, expr): + if isinstance(expr, str): + return expr + elif isinstance(expr, Basic): + return repr(expr) + else: + return str(expr) + + def _print_Add(self, expr, order=None): + terms = self._as_ordered_terms(expr, order=order) + + prec = precedence(expr) + l = [] + for term in terms: + t = self._print(term) + if t.startswith('-') and not term.is_Add: + sign = "-" + t = t[1:] + else: + sign = "+" + if precedence(term) < prec or term.is_Add: + l.extend([sign, "(%s)" % t]) + else: + l.extend([sign, t]) + sign = l.pop(0) + if sign == '+': + sign = "" + return sign + ' '.join(l) + + def _print_BooleanTrue(self, expr): + return "True" + + def _print_BooleanFalse(self, expr): + return "False" + + def _print_Not(self, expr): + return '~%s' %(self.parenthesize(expr.args[0],PRECEDENCE["Not"])) + + def _print_And(self, expr): + args = list(expr.args) + for j, i in enumerate(args): + if isinstance(i, Relational) and ( + i.canonical.rhs is S.NegativeInfinity): + args.insert(0, args.pop(j)) + return self.stringify(args, " & ", PRECEDENCE["BitwiseAnd"]) + + def _print_Or(self, expr): + return self.stringify(expr.args, " | ", PRECEDENCE["BitwiseOr"]) + + def _print_Xor(self, expr): + return self.stringify(expr.args, " ^ ", PRECEDENCE["BitwiseXor"]) + + def _print_AppliedPredicate(self, expr): + return '%s(%s)' % ( + self._print(expr.function), self.stringify(expr.arguments, ", ")) + + def _print_Basic(self, expr): + l = [self._print(o) for o in expr.args] + return expr.__class__.__name__ + "(%s)" % ", ".join(l) + + def _print_BlockMatrix(self, B): + if B.blocks.shape == (1, 1): + self._print(B.blocks[0, 0]) + return self._print(B.blocks) + + def _print_Catalan(self, expr): + return 'Catalan' + + def _print_ComplexInfinity(self, expr): + return 'zoo' + + def _print_ConditionSet(self, s): + args = tuple([self._print(i) for i in (s.sym, s.condition)]) + if s.base_set is S.UniversalSet: + return 'ConditionSet(%s, %s)' % args + args += (self._print(s.base_set),) + return 'ConditionSet(%s, %s, %s)' % args + + def _print_Derivative(self, expr): + dexpr = expr.expr + dvars = [i[0] if i[1] == 1 else i for i in expr.variable_count] + return 'Derivative(%s)' % ", ".join((self._print(arg) for arg in [dexpr] + dvars)) + + def _print_dict(self, d): + keys = sorted(d.keys(), key=default_sort_key) + items = [] + + for key in keys: + item = "%s: %s" % (self._print(key), self._print(d[key])) + items.append(item) + + return "{%s}" % ", ".join(items) + + def _print_Dict(self, expr): + return self._print_dict(expr) + + def _print_RandomDomain(self, d): + if hasattr(d, 'as_boolean'): + return 'Domain: ' + self._print(d.as_boolean()) + elif hasattr(d, 'set'): + return ('Domain: ' + self._print(d.symbols) + ' in ' + + self._print(d.set)) + else: + return 'Domain on ' + self._print(d.symbols) + + def _print_Dummy(self, expr): + return '_' + expr.name + + def _print_EulerGamma(self, expr): + return 'EulerGamma' + + def _print_Exp1(self, expr): + return 'E' + + def _print_ExprCondPair(self, expr): + return '(%s, %s)' % (self._print(expr.expr), self._print(expr.cond)) + + def _print_Function(self, expr): + return expr.func.__name__ + "(%s)" % self.stringify(expr.args, ", ") + + def _print_GoldenRatio(self, expr): + return 'GoldenRatio' + + def _print_Heaviside(self, expr): + # Same as _print_Function but uses pargs to suppress default 1/2 for + # 2nd args + return expr.func.__name__ + "(%s)" % self.stringify(expr.pargs, ", ") + + def _print_TribonacciConstant(self, expr): + return 'TribonacciConstant' + + def _print_ImaginaryUnit(self, expr): + return 'I' + + def _print_Infinity(self, expr): + return 'oo' + + def _print_Integral(self, expr): + def _xab_tostr(xab): + if len(xab) == 1: + return self._print(xab[0]) + else: + return self._print((xab[0],) + tuple(xab[1:])) + L = ', '.join([_xab_tostr(l) for l in expr.limits]) + return 'Integral(%s, %s)' % (self._print(expr.function), L) + + def _print_Interval(self, i): + fin = 'Interval{m}({a}, {b})' + a, b, l, r = i.args + if a.is_infinite and b.is_infinite: + m = '' + elif a.is_infinite and not r: + m = '' + elif b.is_infinite and not l: + m = '' + elif not l and not r: + m = '' + elif l and r: + m = '.open' + elif l: + m = '.Lopen' + else: + m = '.Ropen' + return fin.format(**{'a': a, 'b': b, 'm': m}) + + def _print_AccumulationBounds(self, i): + return "AccumBounds(%s, %s)" % (self._print(i.min), + self._print(i.max)) + + def _print_Inverse(self, I): + return "%s**(-1)" % self.parenthesize(I.arg, PRECEDENCE["Pow"]) + + def _print_Lambda(self, obj): + expr = obj.expr + sig = obj.signature + if len(sig) == 1 and sig[0].is_symbol: + sig = sig[0] + return "Lambda(%s, %s)" % (self._print(sig), self._print(expr)) + + def _print_LatticeOp(self, expr): + args = sorted(expr.args, key=default_sort_key) + return expr.func.__name__ + "(%s)" % ", ".join(self._print(arg) for arg in args) + + def _print_Limit(self, expr): + e, z, z0, dir = expr.args + return "Limit(%s, %s, %s, dir='%s')" % tuple(map(self._print, (e, z, z0, dir))) + + + def _print_list(self, expr): + return "[%s]" % self.stringify(expr, ", ") + + def _print_List(self, expr): + return self._print_list(expr) + + def _print_MatrixBase(self, expr): + return expr._format_str(self) + + def _print_MatrixElement(self, expr): + return self.parenthesize(expr.parent, PRECEDENCE["Atom"], strict=True) \ + + '[%s, %s]' % (self._print(expr.i), self._print(expr.j)) + + def _print_MatrixSlice(self, expr): + def strslice(x, dim): + x = list(x) + if x[2] == 1: + del x[2] + if x[0] == 0: + x[0] = '' + if x[1] == dim: + x[1] = '' + return ':'.join((self._print(arg) for arg in x)) + return (self.parenthesize(expr.parent, PRECEDENCE["Atom"], strict=True) + '[' + + strslice(expr.rowslice, expr.parent.rows) + ', ' + + strslice(expr.colslice, expr.parent.cols) + ']') + + def _print_DeferredVector(self, expr): + return expr.name + + def _print_Mul(self, expr): + + prec = precedence(expr) + + # Check for unevaluated Mul. In this case we need to make sure the + # identities are visible, multiple Rational factors are not combined + # etc so we display in a straight-forward form that fully preserves all + # args and their order. + args = expr.args + if args[0] is S.One or any( + isinstance(a, Number) or + a.is_Pow and all(ai.is_Integer for ai in a.args) + for a in args[1:]): + d, n = sift(args, lambda x: + isinstance(x, Pow) and bool(x.exp.as_coeff_Mul()[0] < 0), + binary=True) + for i, di in enumerate(d): + if di.exp.is_Number: + e = -di.exp + else: + dargs = list(di.exp.args) + dargs[0] = -dargs[0] + e = Mul._from_args(dargs) + d[i] = Pow(di.base, e, evaluate=False) if e - 1 else di.base + + pre = [] + # don't parenthesize first factor if negative + if n and not n[0].is_Add and n[0].could_extract_minus_sign(): + pre = [self._print(n.pop(0))] + + nfactors = pre + [self.parenthesize(a, prec, strict=False) + for a in n] + if not nfactors: + nfactors = ['1'] + + # don't parenthesize first of denominator unless singleton + if len(d) > 1 and d[0].could_extract_minus_sign(): + pre = [self._print(d.pop(0))] + else: + pre = [] + dfactors = pre + [self.parenthesize(a, prec, strict=False) + for a in d] + + n = '*'.join(nfactors) + d = '*'.join(dfactors) + if len(dfactors) > 1: + return '%s/(%s)' % (n, d) + elif dfactors: + return '%s/%s' % (n, d) + return n + + c, e = expr.as_coeff_Mul() + if c < 0: + expr = _keep_coeff(-c, e) + sign = "-" + else: + sign = "" + + a = [] # items in the numerator + b = [] # items that are in the denominator (if any) + + pow_paren = [] # Will collect all pow with more than one base element and exp = -1 + + if self.order not in ('old', 'none'): + args = expr.as_ordered_factors() + else: + # use make_args in case expr was something like -x -> x + args = Mul.make_args(expr) + + # Gather args for numerator/denominator + def apow(i): + b, e = i.as_base_exp() + eargs = list(Mul.make_args(e)) + if eargs[0] is S.NegativeOne: + eargs = eargs[1:] + else: + eargs[0] = -eargs[0] + e = Mul._from_args(eargs) + if isinstance(i, Pow): + return i.func(b, e, evaluate=False) + return i.func(e, evaluate=False) + for item in args: + if (item.is_commutative and + isinstance(item, Pow) and + bool(item.exp.as_coeff_Mul()[0] < 0)): + if item.exp is not S.NegativeOne: + b.append(apow(item)) + else: + if (len(item.args[0].args) != 1 and + isinstance(item.base, (Mul, Pow))): + # To avoid situations like #14160 + pow_paren.append(item) + b.append(item.base) + elif item.is_Rational and item is not S.Infinity: + if item.p != 1: + a.append(Rational(item.p)) + if item.q != 1: + b.append(Rational(item.q)) + else: + a.append(item) + + a = a or [S.One] + + a_str = [self.parenthesize(x, prec, strict=False) for x in a] + b_str = [self.parenthesize(x, prec, strict=False) for x in b] + + # To parenthesize Pow with exp = -1 and having more than one Symbol + for item in pow_paren: + if item.base in b: + b_str[b.index(item.base)] = "(%s)" % b_str[b.index(item.base)] + + if not b: + return sign + '*'.join(a_str) + elif len(b) == 1: + return sign + '*'.join(a_str) + "/" + b_str[0] + else: + return sign + '*'.join(a_str) + "/(%s)" % '*'.join(b_str) + + def _print_MatMul(self, expr): + c, m = expr.as_coeff_mmul() + + sign = "" + if c.is_number: + re, im = c.as_real_imag() + if im.is_zero and re.is_negative: + expr = _keep_coeff(-c, m) + sign = "-" + elif re.is_zero and im.is_negative: + expr = _keep_coeff(-c, m) + sign = "-" + + return sign + '*'.join( + [self.parenthesize(arg, precedence(expr)) for arg in expr.args] + ) + + def _print_ElementwiseApplyFunction(self, expr): + return "{}.({})".format( + expr.function, + self._print(expr.expr), + ) + + def _print_NaN(self, expr): + return 'nan' + + def _print_NegativeInfinity(self, expr): + return '-oo' + + def _print_Order(self, expr): + if not expr.variables or all(p is S.Zero for p in expr.point): + if len(expr.variables) <= 1: + return 'O(%s)' % self._print(expr.expr) + else: + return 'O(%s)' % self.stringify((expr.expr,) + expr.variables, ', ', 0) + else: + return 'O(%s)' % self.stringify(expr.args, ', ', 0) + + def _print_Ordinal(self, expr): + return expr.__str__() + + def _print_Cycle(self, expr): + return expr.__str__() + + def _print_Permutation(self, expr): + from sympy.combinatorics.permutations import Permutation, Cycle + from sympy.utilities.exceptions import sympy_deprecation_warning + + perm_cyclic = Permutation.print_cyclic + if perm_cyclic is not None: + sympy_deprecation_warning( + f""" + Setting Permutation.print_cyclic is deprecated. Instead use + init_printing(perm_cyclic={perm_cyclic}). + """, + deprecated_since_version="1.6", + active_deprecations_target="deprecated-permutation-print_cyclic", + stacklevel=7, + ) + else: + perm_cyclic = self._settings.get("perm_cyclic", True) + + if perm_cyclic: + if not expr.size: + return '()' + # before taking Cycle notation, see if the last element is + # a singleton and move it to the head of the string + s = Cycle(expr)(expr.size - 1).__repr__()[len('Cycle'):] + last = s.rfind('(') + if not last == 0 and ',' not in s[last:]: + s = s[last:] + s[:last] + s = s.replace(',', '') + return s + else: + s = expr.support() + if not s: + if expr.size < 5: + return 'Permutation(%s)' % self._print(expr.array_form) + return 'Permutation([], size=%s)' % self._print(expr.size) + trim = self._print(expr.array_form[:s[-1] + 1]) + ', size=%s' % self._print(expr.size) + use = full = self._print(expr.array_form) + if len(trim) < len(full): + use = trim + return 'Permutation(%s)' % use + + def _print_Subs(self, obj): + expr, old, new = obj.args + if len(obj.point) == 1: + old = old[0] + new = new[0] + return "Subs(%s, %s, %s)" % ( + self._print(expr), self._print(old), self._print(new)) + + def _print_TensorIndex(self, expr): + return expr._print() + + def _print_TensorHead(self, expr): + return expr._print() + + def _print_Tensor(self, expr): + return expr._print() + + def _print_TensMul(self, expr): + # prints expressions like "A(a)", "3*A(a)", "(1+x)*A(a)" + sign, args = expr._get_args_for_traditional_printer() + return sign + "*".join( + [self.parenthesize(arg, precedence(expr)) for arg in args] + ) + + def _print_TensAdd(self, expr): + return expr._print() + + def _print_ArraySymbol(self, expr): + return self._print(expr.name) + + def _print_ArrayElement(self, expr): + return "%s[%s]" % ( + self.parenthesize(expr.name, PRECEDENCE["Func"], True), ", ".join([self._print(i) for i in expr.indices])) + + def _print_PermutationGroup(self, expr): + p = [' %s' % self._print(a) for a in expr.args] + return 'PermutationGroup([\n%s])' % ',\n'.join(p) + + def _print_Pi(self, expr): + return 'pi' + + def _print_PolyRing(self, ring): + return "Polynomial ring in %s over %s with %s order" % \ + (", ".join((self._print(rs) for rs in ring.symbols)), + self._print(ring.domain), self._print(ring.order)) + + def _print_FracField(self, field): + return "Rational function field in %s over %s with %s order" % \ + (", ".join((self._print(fs) for fs in field.symbols)), + self._print(field.domain), self._print(field.order)) + + def _print_FreeGroupElement(self, elm): + return elm.__str__() + + def _print_GaussianElement(self, poly): + return "(%s + %s*I)" % (poly.x, poly.y) + + def _print_PolyElement(self, poly): + return poly.str(self, PRECEDENCE, "%s**%s", "*") + + def _print_FracElement(self, frac): + if frac.denom == 1: + return self._print(frac.numer) + else: + numer = self.parenthesize(frac.numer, PRECEDENCE["Mul"], strict=True) + denom = self.parenthesize(frac.denom, PRECEDENCE["Atom"], strict=True) + return numer + "/" + denom + + def _print_Poly(self, expr): + ATOM_PREC = PRECEDENCE["Atom"] - 1 + terms, gens = [], [ self.parenthesize(s, ATOM_PREC) for s in expr.gens ] + + for monom, coeff in expr.terms(): + s_monom = [] + + for i, e in enumerate(monom): + if e > 0: + if e == 1: + s_monom.append(gens[i]) + else: + s_monom.append(gens[i] + "**%d" % e) + + s_monom = "*".join(s_monom) + + if coeff.is_Add: + if s_monom: + s_coeff = "(" + self._print(coeff) + ")" + else: + s_coeff = self._print(coeff) + else: + if s_monom: + if coeff is S.One: + terms.extend(['+', s_monom]) + continue + + if coeff is S.NegativeOne: + terms.extend(['-', s_monom]) + continue + + s_coeff = self._print(coeff) + + if not s_monom: + s_term = s_coeff + else: + s_term = s_coeff + "*" + s_monom + + if s_term.startswith('-'): + terms.extend(['-', s_term[1:]]) + else: + terms.extend(['+', s_term]) + + if terms[0] in ('-', '+'): + modifier = terms.pop(0) + + if modifier == '-': + terms[0] = '-' + terms[0] + + format = expr.__class__.__name__ + "(%s, %s" + + from sympy.polys.polyerrors import PolynomialError + + try: + format += ", modulus=%s" % expr.get_modulus() + except PolynomialError: + format += ", domain='%s'" % expr.get_domain() + + format += ")" + + for index, item in enumerate(gens): + if len(item) > 2 and (item[:1] == "(" and item[len(item) - 1:] == ")"): + gens[index] = item[1:len(item) - 1] + + return format % (' '.join(terms), ', '.join(gens)) + + def _print_UniversalSet(self, p): + return 'UniversalSet' + + def _print_AlgebraicNumber(self, expr): + if expr.is_aliased: + return self._print(expr.as_poly().as_expr()) + else: + return self._print(expr.as_expr()) + + def _print_Pow(self, expr, rational=False): + """Printing helper function for ``Pow`` + + Parameters + ========== + + rational : bool, optional + If ``True``, it will not attempt printing ``sqrt(x)`` or + ``x**S.Half`` as ``sqrt``, and will use ``x**(1/2)`` + instead. + + See examples for additional details + + Examples + ======== + + >>> from sympy import sqrt, StrPrinter + >>> from sympy.abc import x + + How ``rational`` keyword works with ``sqrt``: + + >>> printer = StrPrinter() + >>> printer._print_Pow(sqrt(x), rational=True) + 'x**(1/2)' + >>> printer._print_Pow(sqrt(x), rational=False) + 'sqrt(x)' + >>> printer._print_Pow(1/sqrt(x), rational=True) + 'x**(-1/2)' + >>> printer._print_Pow(1/sqrt(x), rational=False) + '1/sqrt(x)' + + Notes + ===== + + ``sqrt(x)`` is canonicalized as ``Pow(x, S.Half)`` in SymPy, + so there is no need of defining a separate printer for ``sqrt``. + Instead, it should be handled here as well. + """ + PREC = precedence(expr) + + if expr.exp is S.Half and not rational: + return "sqrt(%s)" % self._print(expr.base) + + if expr.is_commutative: + if -expr.exp is S.Half and not rational: + # Note: Don't test "expr.exp == -S.Half" here, because that will + # match -0.5, which we don't want. + return "%s/sqrt(%s)" % tuple((self._print(arg) for arg in (S.One, expr.base))) + if expr.exp is -S.One: + # Similarly to the S.Half case, don't test with "==" here. + return '%s/%s' % (self._print(S.One), + self.parenthesize(expr.base, PREC, strict=False)) + + e = self.parenthesize(expr.exp, PREC, strict=False) + if self.printmethod == '_sympyrepr' and expr.exp.is_Rational and expr.exp.q != 1: + # the parenthesized exp should be '(Rational(a, b))' so strip parens, + # but just check to be sure. + if e.startswith('(Rational'): + return '%s**%s' % (self.parenthesize(expr.base, PREC, strict=False), e[1:-1]) + return '%s**%s' % (self.parenthesize(expr.base, PREC, strict=False), e) + + def _print_UnevaluatedExpr(self, expr): + return self._print(expr.args[0]) + + def _print_MatPow(self, expr): + PREC = precedence(expr) + return '%s**%s' % (self.parenthesize(expr.base, PREC, strict=False), + self.parenthesize(expr.exp, PREC, strict=False)) + + def _print_Integer(self, expr): + if self._settings.get("sympy_integers", False): + return "S(%s)" % (expr) + return str(expr.p) + + def _print_Integers(self, expr): + return 'Integers' + + def _print_Naturals(self, expr): + return 'Naturals' + + def _print_Naturals0(self, expr): + return 'Naturals0' + + def _print_Rationals(self, expr): + return 'Rationals' + + def _print_Reals(self, expr): + return 'Reals' + + def _print_Complexes(self, expr): + return 'Complexes' + + def _print_EmptySet(self, expr): + return 'EmptySet' + + def _print_EmptySequence(self, expr): + return 'EmptySequence' + + def _print_int(self, expr): + return str(expr) + + def _print_mpz(self, expr): + return str(expr) + + def _print_Rational(self, expr): + if expr.q == 1: + return str(expr.p) + else: + if self._settings.get("sympy_integers", False): + return "S(%s)/%s" % (expr.p, expr.q) + return "%s/%s" % (expr.p, expr.q) + + def _print_PythonRational(self, expr): + if expr.q == 1: + return str(expr.p) + else: + return "%d/%d" % (expr.p, expr.q) + + def _print_Fraction(self, expr): + if expr.denominator == 1: + return str(expr.numerator) + else: + return "%s/%s" % (expr.numerator, expr.denominator) + + def _print_mpq(self, expr): + if expr.denominator == 1: + return str(expr.numerator) + else: + return "%s/%s" % (expr.numerator, expr.denominator) + + def _print_Float(self, expr): + prec = expr._prec + dps = self._settings.get('dps', None) + if dps is None: + dps = 0 if prec < 5 else prec_to_dps(expr._prec) + if self._settings["full_prec"] is True: + strip = False + elif self._settings["full_prec"] is False: + strip = True + elif self._settings["full_prec"] == "auto": + strip = self._print_level > 1 + low = self._settings["min"] if "min" in self._settings else None + high = self._settings["max"] if "max" in self._settings else None + rv = mlib_to_str(expr._mpf_, dps, strip_zeros=strip, min_fixed=low, max_fixed=high) + if rv.startswith('-.0'): + rv = '-0.' + rv[3:] + elif rv.startswith('.0'): + rv = '0.' + rv[2:] + rv = rv.removeprefix('+') # e.g., +inf -> inf + return rv + + def _print_Relational(self, expr): + + charmap = { + "==": "Eq", + "!=": "Ne", + ":=": "Assignment", + '+=': "AddAugmentedAssignment", + "-=": "SubAugmentedAssignment", + "*=": "MulAugmentedAssignment", + "/=": "DivAugmentedAssignment", + "%=": "ModAugmentedAssignment", + } + + if expr.rel_op in charmap: + return '%s(%s, %s)' % (charmap[expr.rel_op], self._print(expr.lhs), + self._print(expr.rhs)) + + return '%s %s %s' % (self.parenthesize(expr.lhs, precedence(expr)), + self._relationals.get(expr.rel_op) or expr.rel_op, + self.parenthesize(expr.rhs, precedence(expr))) + + def _print_ComplexRootOf(self, expr): + return "CRootOf(%s, %d)" % (self._print_Add(expr.expr, order='lex'), + expr.index) + + def _print_RootSum(self, expr): + args = [self._print_Add(expr.expr, order='lex')] + + if expr.fun is not S.IdentityFunction: + args.append(self._print(expr.fun)) + + return "RootSum(%s)" % ", ".join(args) + + def _print_GroebnerBasis(self, basis): + cls = basis.__class__.__name__ + + exprs = [self._print_Add(arg, order=basis.order) for arg in basis.exprs] + exprs = "[%s]" % ", ".join(exprs) + + gens = [ self._print(gen) for gen in basis.gens ] + domain = "domain='%s'" % self._print(basis.domain) + order = "order='%s'" % self._print(basis.order) + + args = [exprs] + gens + [domain, order] + + return "%s(%s)" % (cls, ", ".join(args)) + + def _print_set(self, s): + items = sorted(s, key=default_sort_key) + + args = ', '.join(self._print(item) for item in items) + if not args: + return "set()" + return '{%s}' % args + + def _print_FiniteSet(self, s): + from sympy.sets.sets import FiniteSet + items = sorted(s, key=default_sort_key) + + args = ', '.join(self._print(item) for item in items) + if any(item.has(FiniteSet) for item in items): + return 'FiniteSet({})'.format(args) + return '{{{}}}'.format(args) + + def _print_Partition(self, s): + items = sorted(s, key=default_sort_key) + + args = ', '.join(self._print(arg) for arg in items) + return 'Partition({})'.format(args) + + def _print_frozenset(self, s): + if not s: + return "frozenset()" + return "frozenset(%s)" % self._print_set(s) + + def _print_Sum(self, expr): + def _xab_tostr(xab): + if len(xab) == 1: + return self._print(xab[0]) + else: + return self._print((xab[0],) + tuple(xab[1:])) + L = ', '.join([_xab_tostr(l) for l in expr.limits]) + return 'Sum(%s, %s)' % (self._print(expr.function), L) + + def _print_Symbol(self, expr): + return expr.name + _print_MatrixSymbol = _print_Symbol + _print_RandomSymbol = _print_Symbol + + def _print_Identity(self, expr): + return "I" + + def _print_ZeroMatrix(self, expr): + return "0" + + def _print_OneMatrix(self, expr): + return "1" + + def _print_Predicate(self, expr): + return "Q.%s" % expr.name + + def _print_str(self, expr): + return str(expr) + + def _print_tuple(self, expr): + if len(expr) == 1: + return "(%s,)" % self._print(expr[0]) + else: + return "(%s)" % self.stringify(expr, ", ") + + def _print_Tuple(self, expr): + return self._print_tuple(expr) + + def _print_Transpose(self, T): + return "%s.T" % self.parenthesize(T.arg, PRECEDENCE["Pow"]) + + def _print_Uniform(self, expr): + return "Uniform(%s, %s)" % (self._print(expr.a), self._print(expr.b)) + + def _print_Quantity(self, expr): + if self._settings.get("abbrev", False): + return "%s" % expr.abbrev + return "%s" % expr.name + + def _print_Quaternion(self, expr): + s = [self.parenthesize(i, PRECEDENCE["Mul"], strict=True) for i in expr.args] + a = [s[0]] + [i+"*"+j for i, j in zip(s[1:], "ijk")] + return " + ".join(a) + + def _print_Dimension(self, expr): + return str(expr) + + def _print_Wild(self, expr): + return expr.name + '_' + + def _print_WildFunction(self, expr): + return expr.name + '_' + + def _print_WildDot(self, expr): + return expr.name + + def _print_WildPlus(self, expr): + return expr.name + + def _print_WildStar(self, expr): + return expr.name + + def _print_Zero(self, expr): + if self._settings.get("sympy_integers", False): + return "S(0)" + return self._print_Integer(Integer(0)) + + def _print_DMP(self, p): + cls = p.__class__.__name__ + rep = self._print(p.to_list()) + dom = self._print(p.dom) + + return "%s(%s, %s)" % (cls, rep, dom) + + def _print_DMF(self, expr): + cls = expr.__class__.__name__ + num = self._print(expr.num) + den = self._print(expr.den) + dom = self._print(expr.dom) + + return "%s(%s, %s, %s)" % (cls, num, den, dom) + + def _print_Object(self, obj): + return 'Object("%s")' % obj.name + + def _print_IdentityMorphism(self, morphism): + return 'IdentityMorphism(%s)' % morphism.domain + + def _print_NamedMorphism(self, morphism): + return 'NamedMorphism(%s, %s, "%s")' % \ + (morphism.domain, morphism.codomain, morphism.name) + + def _print_Category(self, category): + return 'Category("%s")' % category.name + + def _print_Manifold(self, manifold): + return manifold.name.name + + def _print_Patch(self, patch): + return patch.name.name + + def _print_CoordSystem(self, coords): + return coords.name.name + + def _print_BaseScalarField(self, field): + return field._coord_sys.symbols[field._index].name + + def _print_BaseVectorField(self, field): + return 'e_%s' % field._coord_sys.symbols[field._index].name + + def _print_Differential(self, diff): + field = diff._form_field + if hasattr(field, '_coord_sys'): + return 'd%s' % field._coord_sys.symbols[field._index].name + else: + return 'd(%s)' % self._print(field) + + def _print_Tr(self, expr): + #TODO : Handle indices + return "%s(%s)" % ("Tr", self._print(expr.args[0])) + + def _print_Str(self, s): + return self._print(s.name) + + def _print_AppliedBinaryRelation(self, expr): + rel = expr.function + return '%s(%s, %s)' % (self._print(rel), + self._print(expr.lhs), + self._print(expr.rhs)) + + +@print_function(StrPrinter) +def sstr(expr, **settings): + """Returns the expression as a string. + + For large expressions where speed is a concern, use the setting + order='none'. If abbrev=True setting is used then units are printed in + abbreviated form. + + Examples + ======== + + >>> from sympy import symbols, Eq, sstr + >>> a, b = symbols('a b') + >>> sstr(Eq(a + b, 0)) + 'Eq(a + b, 0)' + """ + + p = StrPrinter(settings) + s = p.doprint(expr) + + return s + + +class StrReprPrinter(StrPrinter): + """(internal) -- see sstrrepr""" + + def _print_str(self, s): + return repr(s) + + def _print_Str(self, s): + # Str does not to be printed same as str here + return "%s(%s)" % (s.__class__.__name__, self._print(s.name)) + +@print_function(StrReprPrinter) +def sstrrepr(expr, **settings): + """return expr in mixed str/repr form + + i.e. strings are returned in repr form with quotes, and everything else + is returned in str form. + + This function could be useful for hooking into sys.displayhook + """ + + p = StrReprPrinter(settings) + s = p.doprint(expr) + + return s diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tableform.py b/venv/lib/python3.10/site-packages/sympy/printing/tableform.py new file mode 100644 index 0000000000000000000000000000000000000000..4a84ef96ae92517a6ec01ca9db1a13e9afa67093 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tableform.py @@ -0,0 +1,366 @@ +from sympy.core.containers import Tuple +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.sympify import SympifyError + +from types import FunctionType + + +class TableForm: + r""" + Create a nice table representation of data. + + Examples + ======== + + >>> from sympy import TableForm + >>> t = TableForm([[5, 7], [4, 2], [10, 3]]) + >>> print(t) + 5 7 + 4 2 + 10 3 + + You can use the SymPy's printing system to produce tables in any + format (ascii, latex, html, ...). + + >>> print(t.as_latex()) + \begin{tabular}{l l} + $5$ & $7$ \\ + $4$ & $2$ \\ + $10$ & $3$ \\ + \end{tabular} + + """ + + def __init__(self, data, **kwarg): + """ + Creates a TableForm. + + Parameters: + + data ... + 2D data to be put into the table; data can be + given as a Matrix + + headings ... + gives the labels for rows and columns: + + Can be a single argument that applies to both + dimensions: + + - None ... no labels + - "automatic" ... labels are 1, 2, 3, ... + + Can be a list of labels for rows and columns: + The labels for each dimension can be given + as None, "automatic", or [l1, l2, ...] e.g. + ["automatic", None] will number the rows + + [default: None] + + alignments ... + alignment of the columns with: + + - "left" or "<" + - "center" or "^" + - "right" or ">" + + When given as a single value, the value is used for + all columns. The row headings (if given) will be + right justified unless an explicit alignment is + given for it and all other columns. + + [default: "left"] + + formats ... + a list of format strings or functions that accept + 3 arguments (entry, row number, col number) and + return a string for the table entry. (If a function + returns None then the _print method will be used.) + + wipe_zeros ... + Do not show zeros in the table. + + [default: True] + + pad ... + the string to use to indicate a missing value (e.g. + elements that are None or those that are missing + from the end of a row (i.e. any row that is shorter + than the rest is assumed to have missing values). + When None, nothing will be shown for values that + are missing from the end of a row; values that are + None, however, will be shown. + + [default: None] + + Examples + ======== + + >>> from sympy import TableForm, Symbol + >>> TableForm([[5, 7], [4, 2], [10, 3]]) + 5 7 + 4 2 + 10 3 + >>> TableForm([list('.'*i) for i in range(1, 4)], headings='automatic') + | 1 2 3 + --------- + 1 | . + 2 | . . + 3 | . . . + >>> TableForm([[Symbol('.'*(j if not i%2 else 1)) for i in range(3)] + ... for j in range(4)], alignments='rcl') + . + . . . + .. . .. + ... . ... + """ + from sympy.matrices.dense import Matrix + + # We only support 2D data. Check the consistency: + if isinstance(data, Matrix): + data = data.tolist() + _h = len(data) + + # fill out any short lines + pad = kwarg.get('pad', None) + ok_None = False + if pad is None: + pad = " " + ok_None = True + pad = Symbol(pad) + _w = max(len(line) for line in data) + for i, line in enumerate(data): + if len(line) != _w: + line.extend([pad]*(_w - len(line))) + for j, lj in enumerate(line): + if lj is None: + if not ok_None: + lj = pad + else: + try: + lj = S(lj) + except SympifyError: + lj = Symbol(str(lj)) + line[j] = lj + data[i] = line + _lines = Tuple(*[Tuple(*d) for d in data]) + + headings = kwarg.get("headings", [None, None]) + if headings == "automatic": + _headings = [range(1, _h + 1), range(1, _w + 1)] + else: + h1, h2 = headings + if h1 == "automatic": + h1 = range(1, _h + 1) + if h2 == "automatic": + h2 = range(1, _w + 1) + _headings = [h1, h2] + + allow = ('l', 'r', 'c') + alignments = kwarg.get("alignments", "l") + + def _std_align(a): + a = a.strip().lower() + if len(a) > 1: + return {'left': 'l', 'right': 'r', 'center': 'c'}.get(a, a) + else: + return {'<': 'l', '>': 'r', '^': 'c'}.get(a, a) + std_align = _std_align(alignments) + if std_align in allow: + _alignments = [std_align]*_w + else: + _alignments = [] + for a in alignments: + std_align = _std_align(a) + _alignments.append(std_align) + if std_align not in ('l', 'r', 'c'): + raise ValueError('alignment "%s" unrecognized' % + alignments) + if _headings[0] and len(_alignments) == _w + 1: + _head_align = _alignments[0] + _alignments = _alignments[1:] + else: + _head_align = 'r' + if len(_alignments) != _w: + raise ValueError( + 'wrong number of alignments: expected %s but got %s' % + (_w, len(_alignments))) + + _column_formats = kwarg.get("formats", [None]*_w) + + _wipe_zeros = kwarg.get("wipe_zeros", True) + + self._w = _w + self._h = _h + self._lines = _lines + self._headings = _headings + self._head_align = _head_align + self._alignments = _alignments + self._column_formats = _column_formats + self._wipe_zeros = _wipe_zeros + + def __repr__(self): + from .str import sstr + return sstr(self, order=None) + + def __str__(self): + from .str import sstr + return sstr(self, order=None) + + def as_matrix(self): + """Returns the data of the table in Matrix form. + + Examples + ======== + + >>> from sympy import TableForm + >>> t = TableForm([[5, 7], [4, 2], [10, 3]], headings='automatic') + >>> t + | 1 2 + -------- + 1 | 5 7 + 2 | 4 2 + 3 | 10 3 + >>> t.as_matrix() + Matrix([ + [ 5, 7], + [ 4, 2], + [10, 3]]) + """ + from sympy.matrices.dense import Matrix + return Matrix(self._lines) + + def as_str(self): + # XXX obsolete ? + return str(self) + + def as_latex(self): + from .latex import latex + return latex(self) + + def _sympystr(self, p): + """ + Returns the string representation of 'self'. + + Examples + ======== + + >>> from sympy import TableForm + >>> t = TableForm([[5, 7], [4, 2], [10, 3]]) + >>> s = t.as_str() + + """ + column_widths = [0] * self._w + lines = [] + for line in self._lines: + new_line = [] + for i in range(self._w): + # Format the item somehow if needed: + s = str(line[i]) + if self._wipe_zeros and (s == "0"): + s = " " + w = len(s) + if w > column_widths[i]: + column_widths[i] = w + new_line.append(s) + lines.append(new_line) + + # Check heading: + if self._headings[0]: + self._headings[0] = [str(x) for x in self._headings[0]] + _head_width = max(len(x) for x in self._headings[0]) + + if self._headings[1]: + new_line = [] + for i in range(self._w): + # Format the item somehow if needed: + s = str(self._headings[1][i]) + w = len(s) + if w > column_widths[i]: + column_widths[i] = w + new_line.append(s) + self._headings[1] = new_line + + format_str = [] + + def _align(align, w): + return '%%%s%ss' % ( + ("-" if align == "l" else ""), + str(w)) + format_str = [_align(align, w) for align, w in + zip(self._alignments, column_widths)] + if self._headings[0]: + format_str.insert(0, _align(self._head_align, _head_width)) + format_str.insert(1, '|') + format_str = ' '.join(format_str) + '\n' + + s = [] + if self._headings[1]: + d = self._headings[1] + if self._headings[0]: + d = [""] + d + first_line = format_str % tuple(d) + s.append(first_line) + s.append("-" * (len(first_line) - 1) + "\n") + for i, line in enumerate(lines): + d = [l if self._alignments[j] != 'c' else + l.center(column_widths[j]) for j, l in enumerate(line)] + if self._headings[0]: + l = self._headings[0][i] + l = (l if self._head_align != 'c' else + l.center(_head_width)) + d = [l] + d + s.append(format_str % tuple(d)) + return ''.join(s)[:-1] # don't include trailing newline + + def _latex(self, printer): + """ + Returns the string representation of 'self'. + """ + # Check heading: + if self._headings[1]: + new_line = [] + for i in range(self._w): + # Format the item somehow if needed: + new_line.append(str(self._headings[1][i])) + self._headings[1] = new_line + + alignments = [] + if self._headings[0]: + self._headings[0] = [str(x) for x in self._headings[0]] + alignments = [self._head_align] + alignments.extend(self._alignments) + + s = r"\begin{tabular}{" + " ".join(alignments) + "}\n" + + if self._headings[1]: + d = self._headings[1] + if self._headings[0]: + d = [""] + d + first_line = " & ".join(d) + r" \\" + "\n" + s += first_line + s += r"\hline" + "\n" + for i, line in enumerate(self._lines): + d = [] + for j, x in enumerate(line): + if self._wipe_zeros and (x in (0, "0")): + d.append(" ") + continue + f = self._column_formats[j] + if f: + if isinstance(f, FunctionType): + v = f(x, i, j) + if v is None: + v = printer._print(x) + else: + v = f % x + d.append(v) + else: + v = printer._print(x) + d.append("$%s$" % v) + if self._headings[0]: + d = [self._headings[0][i]] + d + s += " & ".join(d) + r" \\" + "\n" + s += r"\end{tabular}" + return s diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tensorflow.py b/venv/lib/python3.10/site-packages/sympy/printing/tensorflow.py new file mode 100644 index 0000000000000000000000000000000000000000..78b0df62b611f336468769e4cee1695bc068eee9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tensorflow.py @@ -0,0 +1,224 @@ +import sympy.codegen +import sympy.codegen.cfunctions +from sympy.external.importtools import version_tuple +from collections.abc import Iterable + +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.codegen.cfunctions import Sqrt +from sympy.external import import_module +from sympy.printing.precedence import PRECEDENCE +from sympy.printing.pycode import AbstractPythonCodePrinter, ArrayPrinter +import sympy + +tensorflow = import_module('tensorflow') + +class TensorflowPrinter(ArrayPrinter, AbstractPythonCodePrinter): + """ + Tensorflow printer which handles vectorized piecewise functions, + logical operators, max/min, and relational operators. + """ + printmethod = "_tensorflowcode" + + mapping = { + sympy.Abs: "tensorflow.math.abs", + sympy.sign: "tensorflow.math.sign", + + # XXX May raise error for ints. + sympy.ceiling: "tensorflow.math.ceil", + sympy.floor: "tensorflow.math.floor", + sympy.log: "tensorflow.math.log", + sympy.exp: "tensorflow.math.exp", + Sqrt: "tensorflow.math.sqrt", + sympy.cos: "tensorflow.math.cos", + sympy.acos: "tensorflow.math.acos", + sympy.sin: "tensorflow.math.sin", + sympy.asin: "tensorflow.math.asin", + sympy.tan: "tensorflow.math.tan", + sympy.atan: "tensorflow.math.atan", + sympy.atan2: "tensorflow.math.atan2", + # XXX Also may give NaN for complex results. + sympy.cosh: "tensorflow.math.cosh", + sympy.acosh: "tensorflow.math.acosh", + sympy.sinh: "tensorflow.math.sinh", + sympy.asinh: "tensorflow.math.asinh", + sympy.tanh: "tensorflow.math.tanh", + sympy.atanh: "tensorflow.math.atanh", + + sympy.re: "tensorflow.math.real", + sympy.im: "tensorflow.math.imag", + sympy.arg: "tensorflow.math.angle", + + # XXX May raise error for ints and complexes + sympy.erf: "tensorflow.math.erf", + sympy.loggamma: "tensorflow.math.lgamma", + + sympy.Eq: "tensorflow.math.equal", + sympy.Ne: "tensorflow.math.not_equal", + sympy.StrictGreaterThan: "tensorflow.math.greater", + sympy.StrictLessThan: "tensorflow.math.less", + sympy.LessThan: "tensorflow.math.less_equal", + sympy.GreaterThan: "tensorflow.math.greater_equal", + + sympy.And: "tensorflow.math.logical_and", + sympy.Or: "tensorflow.math.logical_or", + sympy.Not: "tensorflow.math.logical_not", + sympy.Max: "tensorflow.math.maximum", + sympy.Min: "tensorflow.math.minimum", + + # Matrices + sympy.MatAdd: "tensorflow.math.add", + sympy.HadamardProduct: "tensorflow.math.multiply", + sympy.Trace: "tensorflow.linalg.trace", + + # XXX May raise error for integer matrices. + sympy.Determinant : "tensorflow.linalg.det", + } + + _default_settings = dict( + AbstractPythonCodePrinter._default_settings, + tensorflow_version=None + ) + + def __init__(self, settings=None): + super().__init__(settings) + + version = self._settings['tensorflow_version'] + if version is None and tensorflow: + version = tensorflow.__version__ + self.tensorflow_version = version + + def _print_Function(self, expr): + op = self.mapping.get(type(expr), None) + if op is None: + return super()._print_Basic(expr) + children = [self._print(arg) for arg in expr.args] + if len(children) == 1: + return "%s(%s)" % ( + self._module_format(op), + children[0] + ) + else: + return self._expand_fold_binary_op(op, children) + + _print_Expr = _print_Function + _print_Application = _print_Function + _print_MatrixExpr = _print_Function + # TODO: a better class structure would avoid this mess: + _print_Relational = _print_Function + _print_Not = _print_Function + _print_And = _print_Function + _print_Or = _print_Function + _print_HadamardProduct = _print_Function + _print_Trace = _print_Function + _print_Determinant = _print_Function + + def _print_Inverse(self, expr): + op = self._module_format('tensorflow.linalg.inv') + return "{}({})".format(op, self._print(expr.arg)) + + def _print_Transpose(self, expr): + version = self.tensorflow_version + if version and version_tuple(version) < version_tuple('1.14'): + op = self._module_format('tensorflow.matrix_transpose') + else: + op = self._module_format('tensorflow.linalg.matrix_transpose') + return "{}({})".format(op, self._print(expr.arg)) + + def _print_Derivative(self, expr): + variables = expr.variables + if any(isinstance(i, Iterable) for i in variables): + raise NotImplementedError("derivation by multiple variables is not supported") + def unfold(expr, args): + if not args: + return self._print(expr) + return "%s(%s, %s)[0]" % ( + self._module_format("tensorflow.gradients"), + unfold(expr, args[:-1]), + self._print(args[-1]), + ) + return unfold(expr.expr, variables) + + def _print_Piecewise(self, expr): + version = self.tensorflow_version + if version and version_tuple(version) < version_tuple('1.0'): + tensorflow_piecewise = "tensorflow.select" + else: + tensorflow_piecewise = "tensorflow.where" + + from sympy.functions.elementary.piecewise import Piecewise + e, cond = expr.args[0].args + if len(expr.args) == 1: + return '{}({}, {}, {})'.format( + self._module_format(tensorflow_piecewise), + self._print(cond), + self._print(e), + 0) + + return '{}({}, {}, {})'.format( + self._module_format(tensorflow_piecewise), + self._print(cond), + self._print(e), + self._print(Piecewise(*expr.args[1:]))) + + def _print_Pow(self, expr): + # XXX May raise error for + # int**float or int**complex or float**complex + base, exp = expr.args + if expr.exp == S.Half: + return "{}({})".format( + self._module_format("tensorflow.math.sqrt"), self._print(base)) + return "{}({}, {})".format( + self._module_format("tensorflow.math.pow"), + self._print(base), self._print(exp)) + + def _print_MatrixBase(self, expr): + tensorflow_f = "tensorflow.Variable" if expr.free_symbols else "tensorflow.constant" + data = "["+", ".join(["["+", ".join([self._print(j) for j in i])+"]" for i in expr.tolist()])+"]" + return "%s(%s)" % ( + self._module_format(tensorflow_f), + data, + ) + + def _print_MatMul(self, expr): + from sympy.matrices.expressions import MatrixExpr + mat_args = [arg for arg in expr.args if isinstance(arg, MatrixExpr)] + args = [arg for arg in expr.args if arg not in mat_args] + if args: + return "%s*%s" % ( + self.parenthesize(Mul.fromiter(args), PRECEDENCE["Mul"]), + self._expand_fold_binary_op( + "tensorflow.linalg.matmul", mat_args) + ) + else: + return self._expand_fold_binary_op( + "tensorflow.linalg.matmul", mat_args) + + def _print_MatPow(self, expr): + return self._expand_fold_binary_op( + "tensorflow.linalg.matmul", [expr.base]*expr.exp) + + def _print_CodeBlock(self, expr): + # TODO: is this necessary? + ret = [] + for subexpr in expr.args: + ret.append(self._print(subexpr)) + return "\n".join(ret) + + def _print_isnan(self, exp): + return f'tensorflow.math.is_nan({self._print(*exp.args)})' + + def _print_isinf(self, exp): + return f'tensorflow.math.is_inf({self._print(*exp.args)})' + + _module = "tensorflow" + _einsum = "linalg.einsum" + _add = "math.add" + _transpose = "transpose" + _ones = "ones" + _zeros = "zeros" + + +def tensorflow_code(expr, **settings): + printer = TensorflowPrinter(settings) + return printer.doprint(expr) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca25f01d81877eff420cc28643ea57f40d1b6438 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_aesaracode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_aesaracode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74f7d5c46c68f202369c0048e8e003766a99bcd5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_aesaracode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_c.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_c.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d49375ed67e25e8ba5b68bfa5e189d11b5d6dbc Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_c.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_codeprinter.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_codeprinter.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c19239ffcc125bd20adebe5bbb0e47922e51a8b9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_codeprinter.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_conventions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_conventions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce28d041949cad85d9e8e1885cf271056299d3bc Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_conventions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_cupy.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_cupy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d70c6040c9f4509126e6eedfd1a6f897ddc34d08 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_cupy.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_cxx.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_cxx.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49c5de0a8a64f1147ec1d555374131a02b576f04 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_cxx.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_dot.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_dot.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebcd81dae6fae13f032ea1eb395cd5afba913e7a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_dot.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_fortran.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_fortran.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0e153ba89ba80a48c7bf245e1be909e24f5a08d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_fortran.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_glsl.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_glsl.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4528650c3bf6ea5e636b5a8df6ba12bdf9987d5b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_glsl.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_gtk.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_gtk.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af1038e3c4f78b8bde5b2d753f333f2d67909ed8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_gtk.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_jax.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_jax.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41292abf5dfd8084f0479d4f7ca94a5694d3a1a2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_jax.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_jscode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_jscode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52155fba06ef9a767241e824a287af15a2f5fffa Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_jscode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_julia.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_julia.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8115b5062e70ed3c9da88553bffe6d4677857c1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_julia.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_lambdarepr.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_lambdarepr.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08b9d623e233f746c649f9db235206ebdc91af46 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_lambdarepr.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_llvmjit.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_llvmjit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84a68482964d1b4c953ae43ffe38cc273efea69a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_llvmjit.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_maple.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_maple.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ae7e2a6f976505382a4cede767268c4e4d41772 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_maple.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_mathematica.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_mathematica.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c92873abd21cd71b93f11022dbba00236ace6e8e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_mathematica.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_mathml.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_mathml.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5852ad377f889f5d48b33d43fdf8fadc4f660560 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_mathml.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_numpy.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_numpy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8cb8e02e53b9fbb589cecbf0f6b36b00fef5376 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_numpy.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_octave.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_octave.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3020d4129bedf455fc2e38eb3e5e250b52c9a3ee Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_octave.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_precedence.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_precedence.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e3ce2c3313bd4bbc9f37768fdb185b4247a6b8b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_precedence.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_preview.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_preview.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b618647d19eef9f55507f7d05f1cac836402324a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_preview.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_pycode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_pycode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa2427786784072c193f23ad3426e3dc509a70a3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_pycode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_python.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_python.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a08adcd3c47c297b6459bb34c94436fa1a27803 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_python.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_rcode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_rcode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98793d3fad6a1d44cc48acbb54b265274fc1499a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_rcode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_repr.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_repr.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6dbcb2594b98c310c1b2b21f540bc972d56968a4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_repr.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_rust.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_rust.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e606cf77cc5284c9ca0ce1343293ed06567d1981 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_rust.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_smtlib.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_smtlib.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9186c43d4d2a05383f4e7409b3ac0a2aa0fcb6d0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_smtlib.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_str.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_str.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0e421fc8b2ca32c203e0329781071f27711baf5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_str.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tableform.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tableform.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9972c37640e8d4b5e787675ebaae4fb895a26d9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tableform.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tensorflow.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tensorflow.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d62ef910c15707728a7a1a0917592216345f1b07 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tensorflow.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_theanocode.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_theanocode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37ca849fae9151d6e8f4cb07502de374a5952acf Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_theanocode.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_torch.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_torch.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1020014c99a39356af2bdbc84193d62fb3290cc8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_torch.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tree.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tree.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43548ddc5083e10b9e96496a9fe565b5770e51f2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/printing/tests/__pycache__/test_tree.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_aesaracode.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_aesaracode.py new file mode 100644 index 0000000000000000000000000000000000000000..13308af65b382e77de33302bcd75344d2b00adbf --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_aesaracode.py @@ -0,0 +1,633 @@ +""" +Important note on tests in this module - the Aesara printing functions use a +global cache by default, which means that tests using it will modify global +state and thus not be independent from each other. Instead of using the "cache" +keyword argument each time, this module uses the aesara_code_ and +aesara_function_ functions defined below which default to using a new, empty +cache instead. +""" + +import logging + +from sympy.external import import_module +from sympy.testing.pytest import raises, SKIP, warns_deprecated_sympy + +from sympy.utilities.exceptions import ignore_warnings + + +aesaralogger = logging.getLogger('aesara.configdefaults') +aesaralogger.setLevel(logging.CRITICAL) +aesara = import_module('aesara') +aesaralogger.setLevel(logging.WARNING) + + +if aesara: + import numpy as np + aet = aesara.tensor + from aesara.scalar.basic import ScalarType + from aesara.graph.basic import Variable + from aesara.tensor.var import TensorVariable + from aesara.tensor.elemwise import Elemwise, DimShuffle + from aesara.tensor.math import Dot + + from sympy.printing.aesaracode import true_divide + + xt, yt, zt = [aet.scalar(name, 'floatX') for name in 'xyz'] + Xt, Yt, Zt = [aet.tensor('floatX', (False, False), name=n) for n in 'XYZ'] +else: + #bin/test will not execute any tests now + disabled = True + +import sympy as sy +from sympy.core.singleton import S +from sympy.abc import x, y, z, t +from sympy.printing.aesaracode import (aesara_code, dim_handling, + aesara_function) + + +# Default set of matrix symbols for testing - make square so we can both +# multiply and perform elementwise operations between them. +X, Y, Z = [sy.MatrixSymbol(n, 4, 4) for n in 'XYZ'] + +# For testing AppliedUndef +f_t = sy.Function('f')(t) + + +def aesara_code_(expr, **kwargs): + """ Wrapper for aesara_code that uses a new, empty cache by default. """ + kwargs.setdefault('cache', {}) + with warns_deprecated_sympy(): + return aesara_code(expr, **kwargs) + +def aesara_function_(inputs, outputs, **kwargs): + """ Wrapper for aesara_function that uses a new, empty cache by default. """ + kwargs.setdefault('cache', {}) + with warns_deprecated_sympy(): + return aesara_function(inputs, outputs, **kwargs) + + +def fgraph_of(*exprs): + """ Transform SymPy expressions into Aesara Computation. + + Parameters + ========== + exprs + SymPy expressions + + Returns + ======= + aesara.graph.fg.FunctionGraph + """ + outs = list(map(aesara_code_, exprs)) + ins = list(aesara.graph.basic.graph_inputs(outs)) + ins, outs = aesara.graph.basic.clone(ins, outs) + return aesara.graph.fg.FunctionGraph(ins, outs) + + +def aesara_simplify(fgraph): + """ Simplify a Aesara Computation. + + Parameters + ========== + fgraph : aesara.graph.fg.FunctionGraph + + Returns + ======= + aesara.graph.fg.FunctionGraph + """ + mode = aesara.compile.get_default_mode().excluding("fusion") + fgraph = fgraph.clone() + mode.optimizer.rewrite(fgraph) + return fgraph + + +def theq(a, b): + """ Test two Aesara objects for equality. + + Also accepts numeric types and lists/tuples of supported types. + + Note - debugprint() has a bug where it will accept numeric types but does + not respect the "file" argument and in this case and instead prints the number + to stdout and returns an empty string. This can lead to tests passing where + they should fail because any two numbers will always compare as equal. To + prevent this we treat numbers as a separate case. + """ + numeric_types = (int, float, np.number) + a_is_num = isinstance(a, numeric_types) + b_is_num = isinstance(b, numeric_types) + + # Compare numeric types using regular equality + if a_is_num or b_is_num: + if not (a_is_num and b_is_num): + return False + + return a == b + + # Compare sequences element-wise + a_is_seq = isinstance(a, (tuple, list)) + b_is_seq = isinstance(b, (tuple, list)) + + if a_is_seq or b_is_seq: + if not (a_is_seq and b_is_seq) or type(a) != type(b): + return False + + return list(map(theq, a)) == list(map(theq, b)) + + # Otherwise, assume debugprint() can handle it + astr = aesara.printing.debugprint(a, file='str') + bstr = aesara.printing.debugprint(b, file='str') + + # Check for bug mentioned above + for argname, argval, argstr in [('a', a, astr), ('b', b, bstr)]: + if argstr == '': + raise TypeError( + 'aesara.printing.debugprint(%s) returned empty string ' + '(%s is instance of %r)' + % (argname, argname, type(argval)) + ) + + return astr == bstr + + +def test_example_symbols(): + """ + Check that the example symbols in this module print to their Aesara + equivalents, as many of the other tests depend on this. + """ + assert theq(xt, aesara_code_(x)) + assert theq(yt, aesara_code_(y)) + assert theq(zt, aesara_code_(z)) + assert theq(Xt, aesara_code_(X)) + assert theq(Yt, aesara_code_(Y)) + assert theq(Zt, aesara_code_(Z)) + + +def test_Symbol(): + """ Test printing a Symbol to a aesara variable. """ + xx = aesara_code_(x) + assert isinstance(xx, Variable) + assert xx.broadcastable == () + assert xx.name == x.name + + xx2 = aesara_code_(x, broadcastables={x: (False,)}) + assert xx2.broadcastable == (False,) + assert xx2.name == x.name + +def test_MatrixSymbol(): + """ Test printing a MatrixSymbol to a aesara variable. """ + XX = aesara_code_(X) + assert isinstance(XX, TensorVariable) + assert XX.broadcastable == (False, False) + +@SKIP # TODO - this is currently not checked but should be implemented +def test_MatrixSymbol_wrong_dims(): + """ Test MatrixSymbol with invalid broadcastable. """ + bcs = [(), (False,), (True,), (True, False), (False, True,), (True, True)] + for bc in bcs: + with raises(ValueError): + aesara_code_(X, broadcastables={X: bc}) + +def test_AppliedUndef(): + """ Test printing AppliedUndef instance, which works similarly to Symbol. """ + ftt = aesara_code_(f_t) + assert isinstance(ftt, TensorVariable) + assert ftt.broadcastable == () + assert ftt.name == 'f_t' + + +def test_add(): + expr = x + y + comp = aesara_code_(expr) + assert comp.owner.op == aesara.tensor.add + +def test_trig(): + assert theq(aesara_code_(sy.sin(x)), aet.sin(xt)) + assert theq(aesara_code_(sy.tan(x)), aet.tan(xt)) + +def test_many(): + """ Test printing a complex expression with multiple symbols. """ + expr = sy.exp(x**2 + sy.cos(y)) * sy.log(2*z) + comp = aesara_code_(expr) + expected = aet.exp(xt**2 + aet.cos(yt)) * aet.log(2*zt) + assert theq(comp, expected) + + +def test_dtype(): + """ Test specifying specific data types through the dtype argument. """ + for dtype in ['float32', 'float64', 'int8', 'int16', 'int32', 'int64']: + assert aesara_code_(x, dtypes={x: dtype}).type.dtype == dtype + + # "floatX" type + assert aesara_code_(x, dtypes={x: 'floatX'}).type.dtype in ('float32', 'float64') + + # Type promotion + assert aesara_code_(x + 1, dtypes={x: 'float32'}).type.dtype == 'float32' + assert aesara_code_(x + y, dtypes={x: 'float64', y: 'float32'}).type.dtype == 'float64' + + +def test_broadcastables(): + """ Test the "broadcastables" argument when printing symbol-like objects. """ + + # No restrictions on shape + for s in [x, f_t]: + for bc in [(), (False,), (True,), (False, False), (True, False)]: + assert aesara_code_(s, broadcastables={s: bc}).broadcastable == bc + + # TODO - matrix broadcasting? + +def test_broadcasting(): + """ Test "broadcastable" attribute after applying element-wise binary op. """ + + expr = x + y + + cases = [ + [(), (), ()], + [(False,), (False,), (False,)], + [(True,), (False,), (False,)], + [(False, True), (False, False), (False, False)], + [(True, False), (False, False), (False, False)], + ] + + for bc1, bc2, bc3 in cases: + comp = aesara_code_(expr, broadcastables={x: bc1, y: bc2}) + assert comp.broadcastable == bc3 + + +def test_MatMul(): + expr = X*Y*Z + expr_t = aesara_code_(expr) + assert isinstance(expr_t.owner.op, Dot) + assert theq(expr_t, Xt.dot(Yt).dot(Zt)) + +def test_Transpose(): + assert isinstance(aesara_code_(X.T).owner.op, DimShuffle) + +def test_MatAdd(): + expr = X+Y+Z + assert isinstance(aesara_code_(expr).owner.op, Elemwise) + + +def test_Rationals(): + assert theq(aesara_code_(sy.Integer(2) / 3), true_divide(2, 3)) + assert theq(aesara_code_(S.Half), true_divide(1, 2)) + +def test_Integers(): + assert aesara_code_(sy.Integer(3)) == 3 + +def test_factorial(): + n = sy.Symbol('n') + assert aesara_code_(sy.factorial(n)) + +def test_Derivative(): + with ignore_warnings(UserWarning): + simp = lambda expr: aesara_simplify(fgraph_of(expr)) + assert theq(simp(aesara_code_(sy.Derivative(sy.sin(x), x, evaluate=False))), + simp(aesara.grad(aet.sin(xt), xt))) + + +def test_aesara_function_simple(): + """ Test aesara_function() with single output. """ + f = aesara_function_([x, y], [x+y]) + assert f(2, 3) == 5 + +def test_aesara_function_multi(): + """ Test aesara_function() with multiple outputs. """ + f = aesara_function_([x, y], [x+y, x-y]) + o1, o2 = f(2, 3) + assert o1 == 5 + assert o2 == -1 + +def test_aesara_function_numpy(): + """ Test aesara_function() vs Numpy implementation. """ + f = aesara_function_([x, y], [x+y], dim=1, + dtypes={x: 'float64', y: 'float64'}) + assert np.linalg.norm(f([1, 2], [3, 4]) - np.asarray([4, 6])) < 1e-9 + + f = aesara_function_([x, y], [x+y], dtypes={x: 'float64', y: 'float64'}, + dim=1) + xx = np.arange(3).astype('float64') + yy = 2*np.arange(3).astype('float64') + assert np.linalg.norm(f(xx, yy) - 3*np.arange(3)) < 1e-9 + + +def test_aesara_function_matrix(): + m = sy.Matrix([[x, y], [z, x + y + z]]) + expected = np.array([[1.0, 2.0], [3.0, 1.0 + 2.0 + 3.0]]) + f = aesara_function_([x, y, z], [m]) + np.testing.assert_allclose(f(1.0, 2.0, 3.0), expected) + f = aesara_function_([x, y, z], [m], scalar=True) + np.testing.assert_allclose(f(1.0, 2.0, 3.0), expected) + f = aesara_function_([x, y, z], [m, m]) + assert isinstance(f(1.0, 2.0, 3.0), type([])) + np.testing.assert_allclose(f(1.0, 2.0, 3.0)[0], expected) + np.testing.assert_allclose(f(1.0, 2.0, 3.0)[1], expected) + +def test_dim_handling(): + assert dim_handling([x], dim=2) == {x: (False, False)} + assert dim_handling([x, y], dims={x: 1, y: 2}) == {x: (False, True), + y: (False, False)} + assert dim_handling([x], broadcastables={x: (False,)}) == {x: (False,)} + +def test_aesara_function_kwargs(): + """ + Test passing additional kwargs from aesara_function() to aesara.function(). + """ + import numpy as np + f = aesara_function_([x, y, z], [x+y], dim=1, on_unused_input='ignore', + dtypes={x: 'float64', y: 'float64', z: 'float64'}) + assert np.linalg.norm(f([1, 2], [3, 4], [0, 0]) - np.asarray([4, 6])) < 1e-9 + + f = aesara_function_([x, y, z], [x+y], + dtypes={x: 'float64', y: 'float64', z: 'float64'}, + dim=1, on_unused_input='ignore') + xx = np.arange(3).astype('float64') + yy = 2*np.arange(3).astype('float64') + zz = 2*np.arange(3).astype('float64') + assert np.linalg.norm(f(xx, yy, zz) - 3*np.arange(3)) < 1e-9 + +def test_aesara_function_scalar(): + """ Test the "scalar" argument to aesara_function(). """ + from aesara.compile.function.types import Function + + args = [ + ([x, y], [x + y], None, [0]), # Single 0d output + ([X, Y], [X + Y], None, [2]), # Single 2d output + ([x, y], [x + y], {x: 0, y: 1}, [1]), # Single 1d output + ([x, y], [x + y, x - y], None, [0, 0]), # Two 0d outputs + ([x, y, X, Y], [x + y, X + Y], None, [0, 2]), # One 0d output, one 2d + ] + + # Create and test functions with and without the scalar setting + for inputs, outputs, in_dims, out_dims in args: + for scalar in [False, True]: + + f = aesara_function_(inputs, outputs, dims=in_dims, scalar=scalar) + + # Check the aesara_function attribute is set whether wrapped or not + assert isinstance(f.aesara_function, Function) + + # Feed in inputs of the appropriate size and get outputs + in_values = [ + np.ones([1 if bc else 5 for bc in i.type.broadcastable]) + for i in f.aesara_function.input_storage + ] + out_values = f(*in_values) + if not isinstance(out_values, list): + out_values = [out_values] + + # Check output types and shapes + assert len(out_dims) == len(out_values) + for d, value in zip(out_dims, out_values): + + if scalar and d == 0: + # Should have been converted to a scalar value + assert isinstance(value, np.number) + + else: + # Otherwise should be an array + assert isinstance(value, np.ndarray) + assert value.ndim == d + +def test_aesara_function_bad_kwarg(): + """ + Passing an unknown keyword argument to aesara_function() should raise an + exception. + """ + raises(Exception, lambda : aesara_function_([x], [x+1], foobar=3)) + + +def test_slice(): + assert aesara_code_(slice(1, 2, 3)) == slice(1, 2, 3) + + def theq_slice(s1, s2): + for attr in ['start', 'stop', 'step']: + a1 = getattr(s1, attr) + a2 = getattr(s2, attr) + if a1 is None or a2 is None: + if not (a1 is None or a2 is None): + return False + elif not theq(a1, a2): + return False + return True + + dtypes = {x: 'int32', y: 'int32'} + assert theq_slice(aesara_code_(slice(x, y), dtypes=dtypes), slice(xt, yt)) + assert theq_slice(aesara_code_(slice(1, x, 3), dtypes=dtypes), slice(1, xt, 3)) + +def test_MatrixSlice(): + cache = {} + + n = sy.Symbol('n', integer=True) + X = sy.MatrixSymbol('X', n, n) + + Y = X[1:2:3, 4:5:6] + Yt = aesara_code_(Y, cache=cache) + + s = ScalarType('int64') + assert tuple(Yt.owner.op.idx_list) == (slice(s, s, s), slice(s, s, s)) + assert Yt.owner.inputs[0] == aesara_code_(X, cache=cache) + # == doesn't work in Aesara like it does in SymPy. You have to use + # equals. + assert all(Yt.owner.inputs[i].data == i for i in range(1, 7)) + + k = sy.Symbol('k') + aesara_code_(k, dtypes={k: 'int32'}) + start, stop, step = 4, k, 2 + Y = X[start:stop:step] + Yt = aesara_code_(Y, dtypes={n: 'int32', k: 'int32'}) + # assert Yt.owner.op.idx_list[0].stop == kt + +def test_BlockMatrix(): + n = sy.Symbol('n', integer=True) + A, B, C, D = [sy.MatrixSymbol(name, n, n) for name in 'ABCD'] + At, Bt, Ct, Dt = map(aesara_code_, (A, B, C, D)) + Block = sy.BlockMatrix([[A, B], [C, D]]) + Blockt = aesara_code_(Block) + solutions = [aet.join(0, aet.join(1, At, Bt), aet.join(1, Ct, Dt)), + aet.join(1, aet.join(0, At, Ct), aet.join(0, Bt, Dt))] + assert any(theq(Blockt, solution) for solution in solutions) + +@SKIP +def test_BlockMatrix_Inverse_execution(): + k, n = 2, 4 + dtype = 'float32' + A = sy.MatrixSymbol('A', n, k) + B = sy.MatrixSymbol('B', n, n) + inputs = A, B + output = B.I*A + + cutsizes = {A: [(n//2, n//2), (k//2, k//2)], + B: [(n//2, n//2), (n//2, n//2)]} + cutinputs = [sy.blockcut(i, *cutsizes[i]) for i in inputs] + cutoutput = output.subs(dict(zip(inputs, cutinputs))) + + dtypes = dict(zip(inputs, [dtype]*len(inputs))) + f = aesara_function_(inputs, [output], dtypes=dtypes, cache={}) + fblocked = aesara_function_(inputs, [sy.block_collapse(cutoutput)], + dtypes=dtypes, cache={}) + + ninputs = [np.random.rand(*x.shape).astype(dtype) for x in inputs] + ninputs = [np.arange(n*k).reshape(A.shape).astype(dtype), + np.eye(n).astype(dtype)] + ninputs[1] += np.ones(B.shape)*1e-5 + + assert np.allclose(f(*ninputs), fblocked(*ninputs), rtol=1e-5) + +def test_DenseMatrix(): + from aesara.tensor.basic import Join + + t = sy.Symbol('theta') + for MatrixType in [sy.Matrix, sy.ImmutableMatrix]: + X = MatrixType([[sy.cos(t), -sy.sin(t)], [sy.sin(t), sy.cos(t)]]) + tX = aesara_code_(X) + assert isinstance(tX, TensorVariable) + assert isinstance(tX.owner.op, Join) + + +def test_cache_basic(): + """ Test single symbol-like objects are cached when printed by themselves. """ + + # Pairs of objects which should be considered equivalent with respect to caching + pairs = [ + (x, sy.Symbol('x')), + (X, sy.MatrixSymbol('X', *X.shape)), + (f_t, sy.Function('f')(sy.Symbol('t'))), + ] + + for s1, s2 in pairs: + cache = {} + st = aesara_code_(s1, cache=cache) + + # Test hit with same instance + assert aesara_code_(s1, cache=cache) is st + + # Test miss with same instance but new cache + assert aesara_code_(s1, cache={}) is not st + + # Test hit with different but equivalent instance + assert aesara_code_(s2, cache=cache) is st + +def test_global_cache(): + """ Test use of the global cache. """ + from sympy.printing.aesaracode import global_cache + + backup = dict(global_cache) + try: + # Temporarily empty global cache + global_cache.clear() + + for s in [x, X, f_t]: + with warns_deprecated_sympy(): + st = aesara_code(s) + assert aesara_code(s) is st + + finally: + # Restore global cache + global_cache.update(backup) + +def test_cache_types_distinct(): + """ + Test that symbol-like objects of different types (Symbol, MatrixSymbol, + AppliedUndef) are distinguished by the cache even if they have the same + name. + """ + symbols = [sy.Symbol('f_t'), sy.MatrixSymbol('f_t', 4, 4), f_t] + + cache = {} # Single shared cache + printed = {} + + for s in symbols: + st = aesara_code_(s, cache=cache) + assert st not in printed.values() + printed[s] = st + + # Check all printed objects are distinct + assert len(set(map(id, printed.values()))) == len(symbols) + + # Check retrieving + for s, st in printed.items(): + with warns_deprecated_sympy(): + assert aesara_code(s, cache=cache) is st + +def test_symbols_are_created_once(): + """ + Test that a symbol is cached and reused when it appears in an expression + more than once. + """ + expr = sy.Add(x, x, evaluate=False) + comp = aesara_code_(expr) + + assert theq(comp, xt + xt) + assert not theq(comp, xt + aesara_code_(x)) + +def test_cache_complex(): + """ + Test caching on a complicated expression with multiple symbols appearing + multiple times. + """ + expr = x ** 2 + (y - sy.exp(x)) * sy.sin(z - x * y) + symbol_names = {s.name for s in expr.free_symbols} + expr_t = aesara_code_(expr) + + # Iterate through variables in the Aesara computational graph that the + # printed expression depends on + seen = set() + for v in aesara.graph.basic.ancestors([expr_t]): + # Owner-less, non-constant variables should be our symbols + if v.owner is None and not isinstance(v, aesara.graph.basic.Constant): + # Check it corresponds to a symbol and appears only once + assert v.name in symbol_names + assert v.name not in seen + seen.add(v.name) + + # Check all were present + assert seen == symbol_names + + +def test_Piecewise(): + # A piecewise linear + expr = sy.Piecewise((0, x<0), (x, x<2), (1, True)) # ___/III + result = aesara_code_(expr) + assert result.owner.op == aet.switch + + expected = aet.switch(xt<0, 0, aet.switch(xt<2, xt, 1)) + assert theq(result, expected) + + expr = sy.Piecewise((x, x < 0)) + result = aesara_code_(expr) + expected = aet.switch(xt < 0, xt, np.nan) + assert theq(result, expected) + + expr = sy.Piecewise((0, sy.And(x>0, x<2)), \ + (x, sy.Or(x>2, x<0))) + result = aesara_code_(expr) + expected = aet.switch(aet.and_(xt>0,xt<2), 0, \ + aet.switch(aet.or_(xt>2, xt<0), xt, np.nan)) + assert theq(result, expected) + + +def test_Relationals(): + assert theq(aesara_code_(sy.Eq(x, y)), aet.eq(xt, yt)) + # assert theq(aesara_code_(sy.Ne(x, y)), aet.neq(xt, yt)) # TODO - implement + assert theq(aesara_code_(x > y), xt > yt) + assert theq(aesara_code_(x < y), xt < yt) + assert theq(aesara_code_(x >= y), xt >= yt) + assert theq(aesara_code_(x <= y), xt <= yt) + + +def test_complexfunctions(): + dtypes = {x:'complex128', y:'complex128'} + with warns_deprecated_sympy(): + xt, yt = aesara_code(x, dtypes=dtypes), aesara_code(y, dtypes=dtypes) + from sympy.functions.elementary.complexes import conjugate + from aesara.tensor import as_tensor_variable as atv + from aesara.tensor import complex as cplx + with warns_deprecated_sympy(): + assert theq(aesara_code(y*conjugate(x), dtypes=dtypes), yt*(xt.conj())) + assert theq(aesara_code((1+2j)*x), xt*(atv(1.0)+atv(2.0)*cplx(0,1))) + + +def test_constantfunctions(): + with warns_deprecated_sympy(): + tf = aesara_function([],[1+1j]) + assert(tf()==1+1j) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_c.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_c.py new file mode 100644 index 0000000000000000000000000000000000000000..626e7b6f244ea3227b886cd897d327f5d7bf66ec --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_c.py @@ -0,0 +1,888 @@ +from sympy.core import ( + S, pi, oo, Symbol, symbols, Rational, Integer, Float, Function, Mod, GoldenRatio, EulerGamma, Catalan, + Lambda, Dummy, nan, Mul, Pow, UnevaluatedExpr +) +from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne) +from sympy.functions import ( + Abs, acos, acosh, asin, asinh, atan, atanh, atan2, ceiling, cos, cosh, erf, + erfc, exp, floor, gamma, log, loggamma, Max, Min, Piecewise, sign, sin, sinh, + sqrt, tan, tanh, fibonacci, lucas +) +from sympy.sets import Range +from sympy.logic import ITE, Implies, Equivalent +from sympy.codegen import For, aug_assign, Assignment +from sympy.testing.pytest import raises, XFAIL +from sympy.printing.codeprinter import PrintMethodNotImplementedError +from sympy.printing.c import C89CodePrinter, C99CodePrinter, get_math_macros +from sympy.codegen.ast import ( + AddAugmentedAssignment, Element, Type, FloatType, Declaration, Pointer, Variable, value_const, pointer_const, + While, Scope, Print, FunctionPrototype, FunctionDefinition, FunctionCall, Return, + real, float32, float64, float80, float128, intc, Comment, CodeBlock, stderr, QuotedString +) +from sympy.codegen.cfunctions import expm1, log1p, exp2, log2, fma, log10, Cbrt, hypot, Sqrt, isnan, isinf +from sympy.codegen.cnodes import restrict +from sympy.utilities.lambdify import implemented_function +from sympy.tensor import IndexedBase, Idx +from sympy.matrices import Matrix, MatrixSymbol, SparseMatrix + +from sympy.printing.codeprinter import ccode + +x, y, z = symbols('x,y,z') + + +def test_printmethod(): + class fabs(Abs): + def _ccode(self, printer): + return "fabs(%s)" % printer._print(self.args[0]) + + assert ccode(fabs(x)) == "fabs(x)" + + +def test_ccode_sqrt(): + assert ccode(sqrt(x)) == "sqrt(x)" + assert ccode(x**0.5) == "sqrt(x)" + assert ccode(sqrt(x)) == "sqrt(x)" + + +def test_ccode_Pow(): + assert ccode(x**3) == "pow(x, 3)" + assert ccode(x**(y**3)) == "pow(x, pow(y, 3))" + g = implemented_function('g', Lambda(x, 2*x)) + assert ccode(1/(g(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + "pow(3.5*2*x, -x + pow(y, x))/(pow(x, 2) + y)" + assert ccode(x**-1.0) == '1.0/x' + assert ccode(x**Rational(2, 3)) == 'pow(x, 2.0/3.0)' + assert ccode(x**Rational(2, 3), type_aliases={real: float80}) == 'powl(x, 2.0L/3.0L)' + _cond_cfunc = [(lambda base, exp: exp.is_integer, "dpowi"), + (lambda base, exp: not exp.is_integer, "pow")] + assert ccode(x**3, user_functions={'Pow': _cond_cfunc}) == 'dpowi(x, 3)' + assert ccode(x**0.5, user_functions={'Pow': _cond_cfunc}) == 'pow(x, 0.5)' + assert ccode(x**Rational(16, 5), user_functions={'Pow': _cond_cfunc}) == 'pow(x, 16.0/5.0)' + _cond_cfunc2 = [(lambda base, exp: base == 2, lambda base, exp: 'exp2(%s)' % exp), + (lambda base, exp: base != 2, 'pow')] + # Related to gh-11353 + assert ccode(2**x, user_functions={'Pow': _cond_cfunc2}) == 'exp2(x)' + assert ccode(x**2, user_functions={'Pow': _cond_cfunc2}) == 'pow(x, 2)' + # For issue 14160 + assert ccode(Mul(-2, x, Pow(Mul(y,y,evaluate=False), -1, evaluate=False), + evaluate=False)) == '-2*x/(y*y)' + + +def test_ccode_Max(): + # Test for gh-11926 + assert ccode(Max(x,x*x),user_functions={"Max":"my_max", "Pow":"my_pow"}) == 'my_max(x, my_pow(x, 2))' + + +def test_ccode_Min_performance(): + #Shouldn't take more than a few seconds + big_min = Min(*symbols('a[0:50]')) + for curr_standard in ('c89', 'c99', 'c11'): + output = ccode(big_min, standard=curr_standard) + assert output.count('(') == output.count(')') + + +def test_ccode_constants_mathh(): + assert ccode(exp(1)) == "M_E" + assert ccode(pi) == "M_PI" + assert ccode(oo, standard='c89') == "HUGE_VAL" + assert ccode(-oo, standard='c89') == "-HUGE_VAL" + assert ccode(oo) == "INFINITY" + assert ccode(-oo, standard='c99') == "-INFINITY" + assert ccode(pi, type_aliases={real: float80}) == "M_PIl" + + +def test_ccode_constants_other(): + assert ccode(2*GoldenRatio) == "const double GoldenRatio = %s;\n2*GoldenRatio" % GoldenRatio.evalf(17) + assert ccode( + 2*Catalan) == "const double Catalan = %s;\n2*Catalan" % Catalan.evalf(17) + assert ccode(2*EulerGamma) == "const double EulerGamma = %s;\n2*EulerGamma" % EulerGamma.evalf(17) + + +def test_ccode_Rational(): + assert ccode(Rational(3, 7)) == "3.0/7.0" + assert ccode(Rational(3, 7), type_aliases={real: float80}) == "3.0L/7.0L" + assert ccode(Rational(18, 9)) == "2" + assert ccode(Rational(3, -7)) == "-3.0/7.0" + assert ccode(Rational(3, -7), type_aliases={real: float80}) == "-3.0L/7.0L" + assert ccode(Rational(-3, -7)) == "3.0/7.0" + assert ccode(Rational(-3, -7), type_aliases={real: float80}) == "3.0L/7.0L" + assert ccode(x + Rational(3, 7)) == "x + 3.0/7.0" + assert ccode(x + Rational(3, 7), type_aliases={real: float80}) == "x + 3.0L/7.0L" + assert ccode(Rational(3, 7)*x) == "(3.0/7.0)*x" + assert ccode(Rational(3, 7)*x, type_aliases={real: float80}) == "(3.0L/7.0L)*x" + + +def test_ccode_Integer(): + assert ccode(Integer(67)) == "67" + assert ccode(Integer(-1)) == "-1" + + +def test_ccode_functions(): + assert ccode(sin(x) ** cos(x)) == "pow(sin(x), cos(x))" + + +def test_ccode_inline_function(): + x = symbols('x') + g = implemented_function('g', Lambda(x, 2*x)) + assert ccode(g(x)) == "2*x" + g = implemented_function('g', Lambda(x, 2*x/Catalan)) + assert ccode( + g(x)) == "const double Catalan = %s;\n2*x/Catalan" % Catalan.evalf(17) + A = IndexedBase('A') + i = Idx('i', symbols('n', integer=True)) + g = implemented_function('g', Lambda(x, x*(1 + x)*(2 + x))) + assert ccode(g(A[i]), assign_to=A[i]) == ( + "for (int i=0; i y" + assert ccode(Ge(x, y)) == "x >= y" + + +def test_ccode_Piecewise(): + expr = Piecewise((x, x < 1), (x**2, True)) + assert ccode(expr) == ( + "((x < 1) ? (\n" + " x\n" + ")\n" + ": (\n" + " pow(x, 2)\n" + "))") + assert ccode(expr, assign_to="c") == ( + "if (x < 1) {\n" + " c = x;\n" + "}\n" + "else {\n" + " c = pow(x, 2);\n" + "}") + expr = Piecewise((x, x < 1), (x + 1, x < 2), (x**2, True)) + assert ccode(expr) == ( + "((x < 1) ? (\n" + " x\n" + ")\n" + ": ((x < 2) ? (\n" + " x + 1\n" + ")\n" + ": (\n" + " pow(x, 2)\n" + ")))") + assert ccode(expr, assign_to='c') == ( + "if (x < 1) {\n" + " c = x;\n" + "}\n" + "else if (x < 2) {\n" + " c = x + 1;\n" + "}\n" + "else {\n" + " c = pow(x, 2);\n" + "}") + # Check that Piecewise without a True (default) condition error + expr = Piecewise((x, x < 1), (x**2, x > 1), (sin(x), x > 0)) + raises(ValueError, lambda: ccode(expr)) + + +def test_ccode_sinc(): + from sympy.functions.elementary.trigonometric import sinc + expr = sinc(x) + assert ccode(expr) == ( + "(((x != 0) ? (\n" + " sin(x)/x\n" + ")\n" + ": (\n" + " 1\n" + ")))") + + +def test_ccode_Piecewise_deep(): + p = ccode(2*Piecewise((x, x < 1), (x + 1, x < 2), (x**2, True))) + assert p == ( + "2*((x < 1) ? (\n" + " x\n" + ")\n" + ": ((x < 2) ? (\n" + " x + 1\n" + ")\n" + ": (\n" + " pow(x, 2)\n" + ")))") + expr = x*y*z + x**2 + y**2 + Piecewise((0, x < 0.5), (1, True)) + cos(z) - 1 + assert ccode(expr) == ( + "pow(x, 2) + x*y*z + pow(y, 2) + ((x < 0.5) ? (\n" + " 0\n" + ")\n" + ": (\n" + " 1\n" + ")) + cos(z) - 1") + assert ccode(expr, assign_to='c') == ( + "c = pow(x, 2) + x*y*z + pow(y, 2) + ((x < 0.5) ? (\n" + " 0\n" + ")\n" + ": (\n" + " 1\n" + ")) + cos(z) - 1;") + + +def test_ccode_ITE(): + expr = ITE(x < 1, y, z) + assert ccode(expr) == ( + "((x < 1) ? (\n" + " y\n" + ")\n" + ": (\n" + " z\n" + "))") + + +def test_ccode_settings(): + raises(TypeError, lambda: ccode(sin(x), method="garbage")) + + +def test_ccode_Indexed(): + s, n, m, o = symbols('s n m o', integer=True) + i, j, k = Idx('i', n), Idx('j', m), Idx('k', o) + + x = IndexedBase('x')[j] + A = IndexedBase('A')[i, j] + B = IndexedBase('B')[i, j, k] + + p = C99CodePrinter() + + assert p._print_Indexed(x) == 'x[j]' + assert p._print_Indexed(A) == 'A[%s]' % (m*i+j) + assert p._print_Indexed(B) == 'B[%s]' % (i*o*m+j*o+k) + + A = IndexedBase('A', shape=(5,3))[i, j] + assert p._print_Indexed(A) == 'A[%s]' % (3*i + j) + + A = IndexedBase('A', shape=(5,3), strides='F')[i, j] + assert ccode(A) == 'A[%s]' % (i + 5*j) + + A = IndexedBase('A', shape=(29,29), strides=(1, s), offset=o)[i, j] + assert ccode(A) == 'A[o + s*j + i]' + + Abase = IndexedBase('A', strides=(s, m, n), offset=o) + assert ccode(Abase[i, j, k]) == 'A[m*j + n*k + o + s*i]' + assert ccode(Abase[2, 3, k]) == 'A[3*m + n*k + o + 2*s]' + + +def test_Element(): + assert ccode(Element('x', 'ij')) == 'x[i][j]' + assert ccode(Element('x', 'ij', strides='kl', offset='o')) == 'x[i*k + j*l + o]' + assert ccode(Element('x', (3,))) == 'x[3]' + assert ccode(Element('x', (3,4,5))) == 'x[3][4][5]' + + +def test_ccode_Indexed_without_looking_for_contraction(): + len_y = 5 + y = IndexedBase('y', shape=(len_y,)) + x = IndexedBase('x', shape=(len_y,)) + Dy = IndexedBase('Dy', shape=(len_y-1,)) + i = Idx('i', len_y-1) + e = Eq(Dy[i], (y[i+1]-y[i])/(x[i+1]-x[i])) + code0 = ccode(e.rhs, assign_to=e.lhs, contract=False) + assert code0 == 'Dy[i] = (y[%s] - y[i])/(x[%s] - x[i]);' % (i + 1, i + 1) + + +def test_ccode_loops_matrix_vector(): + n, m = symbols('n m', integer=True) + A = IndexedBase('A') + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + + s = ( + 'for (int i=0; i0), (y, True)), sin(z)]) + A = MatrixSymbol('A', 3, 1) + assert ccode(mat, A) == ( + "A[0] = x*y;\n" + "if (y > 0) {\n" + " A[1] = x + 2;\n" + "}\n" + "else {\n" + " A[1] = y;\n" + "}\n" + "A[2] = sin(z);") + # Test using MatrixElements in expressions + expr = Piecewise((2*A[2, 0], x > 0), (A[2, 0], True)) + sin(A[1, 0]) + A[0, 0] + assert ccode(expr) == ( + "((x > 0) ? (\n" + " 2*A[2]\n" + ")\n" + ": (\n" + " A[2]\n" + ")) + sin(A[1]) + A[0]") + # Test using MatrixElements in a Matrix + q = MatrixSymbol('q', 5, 1) + M = MatrixSymbol('M', 3, 3) + m = Matrix([[sin(q[1,0]), 0, cos(q[2,0])], + [q[1,0] + q[2,0], q[3, 0], 5], + [2*q[4, 0]/q[1,0], sqrt(q[0,0]) + 4, 0]]) + assert ccode(m, M) == ( + "M[0] = sin(q[1]);\n" + "M[1] = 0;\n" + "M[2] = cos(q[2]);\n" + "M[3] = q[1] + q[2];\n" + "M[4] = q[3];\n" + "M[5] = 5;\n" + "M[6] = 2*q[4]/q[1];\n" + "M[7] = sqrt(q[0]) + 4;\n" + "M[8] = 0;") + + +def test_sparse_matrix(): + # gh-15791 + with raises(PrintMethodNotImplementedError): + ccode(SparseMatrix([[1, 2, 3]])) + + assert 'Not supported in C' in C89CodePrinter({'strict': False}).doprint(SparseMatrix([[1, 2, 3]])) + + + +def test_ccode_reserved_words(): + x, y = symbols('x, if') + with raises(ValueError): + ccode(y**2, error_on_reserved=True, standard='C99') + assert ccode(y**2) == 'pow(if_, 2)' + assert ccode(x * y**2, dereference=[y]) == 'pow((*if_), 2)*x' + assert ccode(y**2, reserved_word_suffix='_unreserved') == 'pow(if_unreserved, 2)' + + +def test_ccode_sign(): + expr1, ref1 = sign(x) * y, 'y*(((x) > 0) - ((x) < 0))' + expr2, ref2 = sign(cos(x)), '(((cos(x)) > 0) - ((cos(x)) < 0))' + expr3, ref3 = sign(2 * x + x**2) * x + x**2, 'pow(x, 2) + x*(((pow(x, 2) + 2*x) > 0) - ((pow(x, 2) + 2*x) < 0))' + assert ccode(expr1) == ref1 + assert ccode(expr1, 'z') == 'z = %s;' % ref1 + assert ccode(expr2) == ref2 + assert ccode(expr3) == ref3 + +def test_ccode_Assignment(): + assert ccode(Assignment(x, y + z)) == 'x = y + z;' + assert ccode(aug_assign(x, '+', y + z)) == 'x += y + z;' + + +def test_ccode_For(): + f = For(x, Range(0, 10, 2), [aug_assign(y, '*', x)]) + assert ccode(f) == ("for (x = 0; x < 10; x += 2) {\n" + " y *= x;\n" + "}") + +def test_ccode_Max_Min(): + assert ccode(Max(x, 0), standard='C89') == '((0 > x) ? 0 : x)' + assert ccode(Max(x, 0), standard='C99') == 'fmax(0, x)' + assert ccode(Min(x, 0, sqrt(x)), standard='c89') == ( + '((0 < ((x < sqrt(x)) ? x : sqrt(x))) ? 0 : ((x < sqrt(x)) ? x : sqrt(x)))' + ) + +def test_ccode_standard(): + assert ccode(expm1(x), standard='c99') == 'expm1(x)' + assert ccode(nan, standard='c99') == 'NAN' + assert ccode(float('nan'), standard='c99') == 'NAN' + + +def test_C89CodePrinter(): + c89printer = C89CodePrinter() + assert c89printer.language == 'C' + assert c89printer.standard == 'C89' + assert 'void' in c89printer.reserved_words + assert 'template' not in c89printer.reserved_words + assert c89printer.doprint(log10(x)) == 'log10(x)' + + +def test_C99CodePrinter(): + assert C99CodePrinter().doprint(expm1(x)) == 'expm1(x)' + assert C99CodePrinter().doprint(log1p(x)) == 'log1p(x)' + assert C99CodePrinter().doprint(exp2(x)) == 'exp2(x)' + assert C99CodePrinter().doprint(log2(x)) == 'log2(x)' + assert C99CodePrinter().doprint(fma(x, y, -z)) == 'fma(x, y, -z)' + assert C99CodePrinter().doprint(log10(x)) == 'log10(x)' + assert C99CodePrinter().doprint(Cbrt(x)) == 'cbrt(x)' # note Cbrt due to cbrt already taken. + assert C99CodePrinter().doprint(hypot(x, y)) == 'hypot(x, y)' + assert C99CodePrinter().doprint(loggamma(x)) == 'lgamma(x)' + assert C99CodePrinter().doprint(Max(x, 3, x**2)) == 'fmax(3, fmax(x, pow(x, 2)))' + assert C99CodePrinter().doprint(Min(x, 3)) == 'fmin(3, x)' + c99printer = C99CodePrinter() + assert c99printer.language == 'C' + assert c99printer.standard == 'C99' + assert 'restrict' in c99printer.reserved_words + assert 'using' not in c99printer.reserved_words + + +@XFAIL +def test_C99CodePrinter__precision_f80(): + f80_printer = C99CodePrinter({"type_aliases": {real: float80}}) + assert f80_printer.doprint(sin(x + Float('2.1'))) == 'sinl(x + 2.1L)' + + +def test_C99CodePrinter__precision(): + n = symbols('n', integer=True) + p = symbols('p', integer=True, positive=True) + f32_printer = C99CodePrinter({"type_aliases": {real: float32}}) + f64_printer = C99CodePrinter({"type_aliases": {real: float64}}) + f80_printer = C99CodePrinter({"type_aliases": {real: float80}}) + assert f32_printer.doprint(sin(x+2.1)) == 'sinf(x + 2.1F)' + assert f64_printer.doprint(sin(x+2.1)) == 'sin(x + 2.1000000000000001)' + assert f80_printer.doprint(sin(x+Float('2.0'))) == 'sinl(x + 2.0L)' + + for printer, suffix in zip([f32_printer, f64_printer, f80_printer], ['f', '', 'l']): + def check(expr, ref): + assert printer.doprint(expr) == ref.format(s=suffix, S=suffix.upper()) + check(Abs(n), 'abs(n)') + check(Abs(x + 2.0), 'fabs{s}(x + 2.0{S})') + check(sin(x + 4.0)**cos(x - 2.0), 'pow{s}(sin{s}(x + 4.0{S}), cos{s}(x - 2.0{S}))') + check(exp(x*8.0), 'exp{s}(8.0{S}*x)') + check(exp2(x), 'exp2{s}(x)') + check(expm1(x*4.0), 'expm1{s}(4.0{S}*x)') + check(Mod(p, 2), 'p % 2') + check(Mod(2*p + 3, 3*p + 5, evaluate=False), '(2*p + 3) % (3*p + 5)') + check(Mod(x + 2.0, 3.0), 'fmod{s}(1.0{S}*x + 2.0{S}, 3.0{S})') + check(Mod(x, 2.0*x + 3.0), 'fmod{s}(1.0{S}*x, 2.0{S}*x + 3.0{S})') + check(log(x/2), 'log{s}((1.0{S}/2.0{S})*x)') + check(log10(3*x/2), 'log10{s}((3.0{S}/2.0{S})*x)') + check(log2(x*8.0), 'log2{s}(8.0{S}*x)') + check(log1p(x), 'log1p{s}(x)') + check(2**x, 'pow{s}(2, x)') + check(2.0**x, 'pow{s}(2.0{S}, x)') + check(x**3, 'pow{s}(x, 3)') + check(x**4.0, 'pow{s}(x, 4.0{S})') + check(sqrt(3+x), 'sqrt{s}(x + 3)') + check(Cbrt(x-2.0), 'cbrt{s}(x - 2.0{S})') + check(hypot(x, y), 'hypot{s}(x, y)') + check(sin(3.*x + 2.), 'sin{s}(3.0{S}*x + 2.0{S})') + check(cos(3.*x - 1.), 'cos{s}(3.0{S}*x - 1.0{S})') + check(tan(4.*y + 2.), 'tan{s}(4.0{S}*y + 2.0{S})') + check(asin(3.*x + 2.), 'asin{s}(3.0{S}*x + 2.0{S})') + check(acos(3.*x + 2.), 'acos{s}(3.0{S}*x + 2.0{S})') + check(atan(3.*x + 2.), 'atan{s}(3.0{S}*x + 2.0{S})') + check(atan2(3.*x, 2.*y), 'atan2{s}(3.0{S}*x, 2.0{S}*y)') + + check(sinh(3.*x + 2.), 'sinh{s}(3.0{S}*x + 2.0{S})') + check(cosh(3.*x - 1.), 'cosh{s}(3.0{S}*x - 1.0{S})') + check(tanh(4.0*y + 2.), 'tanh{s}(4.0{S}*y + 2.0{S})') + check(asinh(3.*x + 2.), 'asinh{s}(3.0{S}*x + 2.0{S})') + check(acosh(3.*x + 2.), 'acosh{s}(3.0{S}*x + 2.0{S})') + check(atanh(3.*x + 2.), 'atanh{s}(3.0{S}*x + 2.0{S})') + check(erf(42.*x), 'erf{s}(42.0{S}*x)') + check(erfc(42.*x), 'erfc{s}(42.0{S}*x)') + check(gamma(x), 'tgamma{s}(x)') + check(loggamma(x), 'lgamma{s}(x)') + + check(ceiling(x + 2.), "ceil{s}(x) + 2") + check(floor(x + 2.), "floor{s}(x) + 2") + check(fma(x, y, -z), 'fma{s}(x, y, -z)') + check(Max(x, 8.0, x**4.0), 'fmax{s}(8.0{S}, fmax{s}(x, pow{s}(x, 4.0{S})))') + check(Min(x, 2.0), 'fmin{s}(2.0{S}, x)') + + +def test_get_math_macros(): + macros = get_math_macros() + assert macros[exp(1)] == 'M_E' + assert macros[1/Sqrt(2)] == 'M_SQRT1_2' + + +def test_ccode_Declaration(): + i = symbols('i', integer=True) + var1 = Variable(i, type=Type.from_expr(i)) + dcl1 = Declaration(var1) + assert ccode(dcl1) == 'int i' + + var2 = Variable(x, type=float32, attrs={value_const}) + dcl2a = Declaration(var2) + assert ccode(dcl2a) == 'const float x' + dcl2b = var2.as_Declaration(value=pi) + assert ccode(dcl2b) == 'const float x = M_PI' + + var3 = Variable(y, type=Type('bool')) + dcl3 = Declaration(var3) + printer = C89CodePrinter() + assert 'stdbool.h' not in printer.headers + assert printer.doprint(dcl3) == 'bool y' + assert 'stdbool.h' in printer.headers + + u = symbols('u', real=True) + ptr4 = Pointer.deduced(u, attrs={pointer_const, restrict}) + dcl4 = Declaration(ptr4) + assert ccode(dcl4) == 'double * const restrict u' + + var5 = Variable(x, Type('__float128'), attrs={value_const}) + dcl5a = Declaration(var5) + assert ccode(dcl5a) == 'const __float128 x' + var5b = Variable(var5.symbol, var5.type, pi, attrs=var5.attrs) + dcl5b = Declaration(var5b) + assert ccode(dcl5b) == 'const __float128 x = M_PI' + + +def test_C99CodePrinter_custom_type(): + # We will look at __float128 (new in glibc 2.26) + f128 = FloatType('_Float128', float128.nbits, float128.nmant, float128.nexp) + p128 = C99CodePrinter({ + "type_aliases": {real: f128}, + "type_literal_suffixes": {f128: 'Q'}, + "type_func_suffixes": {f128: 'f128'}, + "type_math_macro_suffixes": { + real: 'f128', + f128: 'f128' + }, + "type_macros": { + f128: ('__STDC_WANT_IEC_60559_TYPES_EXT__',) + } + }) + assert p128.doprint(x) == 'x' + assert not p128.headers + assert not p128.libraries + assert not p128.macros + assert p128.doprint(2.0) == '2.0Q' + assert not p128.headers + assert not p128.libraries + assert p128.macros == {'__STDC_WANT_IEC_60559_TYPES_EXT__'} + + assert p128.doprint(Rational(1, 2)) == '1.0Q/2.0Q' + assert p128.doprint(sin(x)) == 'sinf128(x)' + assert p128.doprint(cos(2., evaluate=False)) == 'cosf128(2.0Q)' + assert p128.doprint(x**-1.0) == '1.0Q/x' + + var5 = Variable(x, f128, attrs={value_const}) + + dcl5a = Declaration(var5) + assert ccode(dcl5a) == 'const _Float128 x' + var5b = Variable(x, f128, pi, attrs={value_const}) + dcl5b = Declaration(var5b) + assert p128.doprint(dcl5b) == 'const _Float128 x = M_PIf128' + var5b = Variable(x, f128, value=Catalan.evalf(38), attrs={value_const}) + dcl5c = Declaration(var5b) + assert p128.doprint(dcl5c) == 'const _Float128 x = %sQ' % Catalan.evalf(f128.decimal_dig) + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert(ccode(A[0, 0]) == "A[0]") + assert(ccode(3 * A[0, 0]) == "3*A[0]") + + F = C[0, 0].subs(C, A - B) + assert(ccode(F) == "(A - B)[0]") + +def test_ccode_math_macros(): + assert ccode(z + exp(1)) == 'z + M_E' + assert ccode(z + log2(exp(1))) == 'z + M_LOG2E' + assert ccode(z + 1/log(2)) == 'z + M_LOG2E' + assert ccode(z + log(2)) == 'z + M_LN2' + assert ccode(z + log(10)) == 'z + M_LN10' + assert ccode(z + pi) == 'z + M_PI' + assert ccode(z + pi/2) == 'z + M_PI_2' + assert ccode(z + pi/4) == 'z + M_PI_4' + assert ccode(z + 1/pi) == 'z + M_1_PI' + assert ccode(z + 2/pi) == 'z + M_2_PI' + assert ccode(z + 2/sqrt(pi)) == 'z + M_2_SQRTPI' + assert ccode(z + 2/Sqrt(pi)) == 'z + M_2_SQRTPI' + assert ccode(z + sqrt(2)) == 'z + M_SQRT2' + assert ccode(z + Sqrt(2)) == 'z + M_SQRT2' + assert ccode(z + 1/sqrt(2)) == 'z + M_SQRT1_2' + assert ccode(z + 1/Sqrt(2)) == 'z + M_SQRT1_2' + + +def test_ccode_Type(): + assert ccode(Type('float')) == 'float' + assert ccode(intc) == 'int' + + +def test_ccode_codegen_ast(): + # Note that C only allows comments of the form /* ... */, double forward + # slash is not standard C, and some C compilers will grind to a halt upon + # encountering them. + assert ccode(Comment("this is a comment")) == "/* this is a comment */" # not // + assert ccode(While(abs(x) > 1, [aug_assign(x, '-', 1)])) == ( + 'while (fabs(x) > 1) {\n' + ' x -= 1;\n' + '}' + ) + assert ccode(Scope([AddAugmentedAssignment(x, 1)])) == ( + '{\n' + ' x += 1;\n' + '}' + ) + inp_x = Declaration(Variable(x, type=real)) + assert ccode(FunctionPrototype(real, 'pwer', [inp_x])) == 'double pwer(double x)' + assert ccode(FunctionDefinition(real, 'pwer', [inp_x], [Assignment(x, x**2)])) == ( + 'double pwer(double x){\n' + ' x = pow(x, 2);\n' + '}' + ) + + # Elements of CodeBlock are formatted as statements: + block = CodeBlock( + x, + Print([x, y], "%d %d"), + Print([QuotedString('hello'), y], "%s %d", file=stderr), + FunctionCall('pwer', [x]), + Return(x), + ) + assert ccode(block) == '\n'.join([ + 'x;', + 'printf("%d %d", x, y);', + 'fprintf(stderr, "%s %d", "hello", y);', + 'pwer(x);', + 'return x;', + ]) + +def test_ccode_UnevaluatedExpr(): + assert ccode(UnevaluatedExpr(y * x) + z) == "z + x*y" + assert ccode(UnevaluatedExpr(y + x) + z) == "z + (x + y)" # gh-21955 + w = symbols('w') + assert ccode(UnevaluatedExpr(y + x) + UnevaluatedExpr(z + w)) == "(w + z) + (x + y)" + + p, q, r = symbols("p q r", real=True) + q_r = UnevaluatedExpr(q + r) + expr = abs(exp(p+q_r)) + assert ccode(expr) == "exp(p + (q + r))" + + +def test_ccode_array_like_containers(): + assert ccode([2,3,4]) == "{2, 3, 4}" + assert ccode((2,3,4)) == "{2, 3, 4}" + +def test_ccode__isinf_isnan(): + assert ccode(isinf(x)) == 'isinf(x)' + assert ccode(isnan(x)) == 'isnan(x)' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_codeprinter.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_codeprinter.py new file mode 100644 index 0000000000000000000000000000000000000000..4b077037eb84e218fcfd4a05fc03e40b211e45b9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_codeprinter.py @@ -0,0 +1,77 @@ +from sympy.printing.codeprinter import CodePrinter, PrintMethodNotImplementedError +from sympy.core import symbols +from sympy.core.symbol import Dummy +from sympy.testing.pytest import raises +from sympy import cos +from sympy.utilities.lambdify import lambdify +from math import cos as math_cos +from sympy.printing.lambdarepr import LambdaPrinter + + +def setup_test_printer(**kwargs): + p = CodePrinter(settings=kwargs) + p._not_supported = set() + p._number_symbols = set() + return p + + +def test_print_Dummy(): + d = Dummy('d') + p = setup_test_printer() + assert p._print_Dummy(d) == "d_%i" % d.dummy_index + +def test_print_Symbol(): + + x, y = symbols('x, if') + + p = setup_test_printer() + assert p._print(x) == 'x' + assert p._print(y) == 'if' + + p.reserved_words.update(['if']) + assert p._print(y) == 'if_' + + p = setup_test_printer(error_on_reserved=True) + p.reserved_words.update(['if']) + with raises(ValueError): + p._print(y) + + p = setup_test_printer(reserved_word_suffix='_He_Man') + p.reserved_words.update(['if']) + assert p._print(y) == 'if_He_Man' + + +def test_lambdify_LaTeX_symbols_issue_23374(): + # Create symbols with Latex style names + x1, x2 = symbols("x_{1} x_2") + + # Lambdify the function + f1 = lambdify([x1, x2], cos(x1 ** 2 + x2 ** 2)) + + # Test that the function works correctly (numerically) + assert f1(1, 2) == math_cos(1 ** 2 + 2 ** 2) + + # Explicitly generate a custom printer to verify the naming convention + p = LambdaPrinter() + expr_str = p.doprint(cos(x1 ** 2 + x2 ** 2)) + assert 'x_1' in expr_str + assert 'x_2' in expr_str + + +def test_issue_15791(): + class CrashingCodePrinter(CodePrinter): + def emptyPrinter(self, obj): + raise NotImplementedError + + from sympy.matrices import ( + MutableSparseMatrix, + ImmutableSparseMatrix, + ) + + c = CrashingCodePrinter() + + # these should not silently succeed + with raises(PrintMethodNotImplementedError): + c.doprint(ImmutableSparseMatrix(2, 2, {})) + with raises(PrintMethodNotImplementedError): + c.doprint(MutableSparseMatrix(2, 2, {})) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_conventions.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_conventions.py new file mode 100644 index 0000000000000000000000000000000000000000..e8f1fa8532f96130828b89d1ba5ba11fd5bed7a4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_conventions.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +from sympy.core.function import (Derivative, Function) +from sympy.core.numbers import oo +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.trigonometric import cos +from sympy.integrals.integrals import Integral +from sympy.functions.special.bessel import besselj +from sympy.functions.special.polynomials import legendre +from sympy.functions.combinatorial.numbers import bell +from sympy.printing.conventions import split_super_sub, requires_partial +from sympy.testing.pytest import XFAIL + +def test_super_sub(): + assert split_super_sub("beta_13_2") == ("beta", [], ["13", "2"]) + assert split_super_sub("beta_132_20") == ("beta", [], ["132", "20"]) + assert split_super_sub("beta_13") == ("beta", [], ["13"]) + assert split_super_sub("x_a_b") == ("x", [], ["a", "b"]) + assert split_super_sub("x_1_2_3") == ("x", [], ["1", "2", "3"]) + assert split_super_sub("x_a_b1") == ("x", [], ["a", "b1"]) + assert split_super_sub("x_a_1") == ("x", [], ["a", "1"]) + assert split_super_sub("x_1_a") == ("x", [], ["1", "a"]) + assert split_super_sub("x_1^aa") == ("x", ["aa"], ["1"]) + assert split_super_sub("x_1__aa") == ("x", ["aa"], ["1"]) + assert split_super_sub("x_11^a") == ("x", ["a"], ["11"]) + assert split_super_sub("x_11__a") == ("x", ["a"], ["11"]) + assert split_super_sub("x_a_b_c_d") == ("x", [], ["a", "b", "c", "d"]) + assert split_super_sub("x_a_b^c^d") == ("x", ["c", "d"], ["a", "b"]) + assert split_super_sub("x_a_b__c__d") == ("x", ["c", "d"], ["a", "b"]) + assert split_super_sub("x_a^b_c^d") == ("x", ["b", "d"], ["a", "c"]) + assert split_super_sub("x_a__b_c__d") == ("x", ["b", "d"], ["a", "c"]) + assert split_super_sub("x^a^b_c_d") == ("x", ["a", "b"], ["c", "d"]) + assert split_super_sub("x__a__b_c_d") == ("x", ["a", "b"], ["c", "d"]) + assert split_super_sub("x^a^b^c^d") == ("x", ["a", "b", "c", "d"], []) + assert split_super_sub("x__a__b__c__d") == ("x", ["a", "b", "c", "d"], []) + assert split_super_sub("alpha_11") == ("alpha", [], ["11"]) + assert split_super_sub("alpha_11_11") == ("alpha", [], ["11", "11"]) + assert split_super_sub("w1") == ("w", [], ["1"]) + assert split_super_sub("w𝟙") == ("w", [], ["𝟙"]) + assert split_super_sub("w11") == ("w", [], ["11"]) + assert split_super_sub("w𝟙𝟙") == ("w", [], ["𝟙𝟙"]) + assert split_super_sub("w𝟙2𝟙") == ("w", [], ["𝟙2𝟙"]) + assert split_super_sub("w1^a") == ("w", ["a"], ["1"]) + assert split_super_sub("ω1") == ("ω", [], ["1"]) + assert split_super_sub("ω11") == ("ω", [], ["11"]) + assert split_super_sub("ω1^a") == ("ω", ["a"], ["1"]) + assert split_super_sub("ω𝟙^α") == ("ω", ["α"], ["𝟙"]) + assert split_super_sub("ω𝟙2^3α") == ("ω", ["3α"], ["𝟙2"]) + assert split_super_sub("") == ("", [], []) + + +def test_requires_partial(): + x, y, z, t, nu = symbols('x y z t nu') + n = symbols('n', integer=True) + + f = x * y + assert requires_partial(Derivative(f, x)) is True + assert requires_partial(Derivative(f, y)) is True + + ## integrating out one of the variables + assert requires_partial(Derivative(Integral(exp(-x * y), (x, 0, oo)), y, evaluate=False)) is False + + ## bessel function with smooth parameter + f = besselj(nu, x) + assert requires_partial(Derivative(f, x)) is True + assert requires_partial(Derivative(f, nu)) is True + + ## bessel function with integer parameter + f = besselj(n, x) + assert requires_partial(Derivative(f, x)) is False + # this is not really valid (differentiating with respect to an integer) + # but there's no reason to use the partial derivative symbol there. make + # sure we don't throw an exception here, though + assert requires_partial(Derivative(f, n)) is False + + ## bell polynomial + f = bell(n, x) + assert requires_partial(Derivative(f, x)) is False + # again, invalid + assert requires_partial(Derivative(f, n)) is False + + ## legendre polynomial + f = legendre(0, x) + assert requires_partial(Derivative(f, x)) is False + + f = legendre(n, x) + assert requires_partial(Derivative(f, x)) is False + # again, invalid + assert requires_partial(Derivative(f, n)) is False + + f = x ** n + assert requires_partial(Derivative(f, x)) is False + + assert requires_partial(Derivative(Integral((x*y) ** n * exp(-x * y), (x, 0, oo)), y, evaluate=False)) is False + + # parametric equation + f = (exp(t), cos(t)) + g = sum(f) + assert requires_partial(Derivative(g, t)) is False + + f = symbols('f', cls=Function) + assert requires_partial(Derivative(f(x), x)) is False + assert requires_partial(Derivative(f(x), y)) is False + assert requires_partial(Derivative(f(x, y), x)) is True + assert requires_partial(Derivative(f(x, y), y)) is True + assert requires_partial(Derivative(f(x, y), z)) is True + assert requires_partial(Derivative(f(x, y), x, y)) is True + +@XFAIL +def test_requires_partial_unspecified_variables(): + x, y = symbols('x y') + # function of unspecified variables + f = symbols('f', cls=Function) + assert requires_partial(Derivative(f, x)) is False + assert requires_partial(Derivative(f, x, y)) is True diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_cupy.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_cupy.py new file mode 100644 index 0000000000000000000000000000000000000000..cf111ec1623390a3dbbf489235d2ed387624a36c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_cupy.py @@ -0,0 +1,56 @@ +from sympy.concrete.summations import Sum +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.utilities.lambdify import lambdify +from sympy.abc import x, i, a, b +from sympy.codegen.numpy_nodes import logaddexp +from sympy.printing.numpy import CuPyPrinter, _cupy_known_constants, _cupy_known_functions + +from sympy.testing.pytest import skip, raises +from sympy.external import import_module + +cp = import_module('cupy') + +def test_cupy_print(): + prntr = CuPyPrinter() + assert prntr.doprint(logaddexp(a, b)) == 'cupy.logaddexp(a, b)' + assert prntr.doprint(sqrt(x)) == 'cupy.sqrt(x)' + assert prntr.doprint(log(x)) == 'cupy.log(x)' + assert prntr.doprint("acos(x)") == 'cupy.arccos(x)' + assert prntr.doprint("exp(x)") == 'cupy.exp(x)' + assert prntr.doprint("Abs(x)") == 'abs(x)' + +def test_not_cupy_print(): + prntr = CuPyPrinter() + with raises(NotImplementedError): + prntr.doprint("abcd(x)") + +def test_cupy_sum(): + if not cp: + skip("CuPy not installed") + + s = Sum(x ** i, (i, a, b)) + f = lambdify((a, b, x), s, 'cupy') + + a_, b_ = 0, 10 + x_ = cp.linspace(-1, +1, 10) + assert cp.allclose(f(a_, b_, x_), sum(x_ ** i_ for i_ in range(a_, b_ + 1))) + + s = Sum(i * x, (i, a, b)) + f = lambdify((a, b, x), s, 'numpy') + + a_, b_ = 0, 10 + x_ = cp.linspace(-1, +1, 10) + assert cp.allclose(f(a_, b_, x_), sum(i_ * x_ for i_ in range(a_, b_ + 1))) + +def test_cupy_known_funcs_consts(): + assert _cupy_known_constants['NaN'] == 'cupy.nan' + assert _cupy_known_constants['EulerGamma'] == 'cupy.euler_gamma' + + assert _cupy_known_functions['acos'] == 'cupy.arccos' + assert _cupy_known_functions['log'] == 'cupy.log' + +def test_cupy_print_methods(): + prntr = CuPyPrinter() + assert hasattr(prntr, '_print_acos') + assert hasattr(prntr, '_print_log') diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_cxx.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_cxx.py new file mode 100644 index 0000000000000000000000000000000000000000..d84ec75cbf0eeb60a1176b9cb3b401a3384454e7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_cxx.py @@ -0,0 +1,86 @@ +from sympy.core.numbers import Float, Integer, Rational +from sympy.core.symbol import symbols +from sympy.functions import beta, Ei, zeta, Max, Min, sqrt, riemann_xi, frac +from sympy.printing.cxx import CXX98CodePrinter, CXX11CodePrinter, CXX17CodePrinter, cxxcode +from sympy.codegen.cfunctions import log1p + + +x, y, u, v = symbols('x y u v') + + +def test_CXX98CodePrinter(): + assert CXX98CodePrinter().doprint(Max(x, 3)) in ('std::max(x, 3)', 'std::max(3, x)') + assert CXX98CodePrinter().doprint(Min(x, 3, sqrt(x))) == 'std::min(3, std::min(x, std::sqrt(x)))' + cxx98printer = CXX98CodePrinter() + assert cxx98printer.language == 'C++' + assert cxx98printer.standard == 'C++98' + assert 'template' in cxx98printer.reserved_words + assert 'alignas' not in cxx98printer.reserved_words + + +def test_CXX11CodePrinter(): + assert CXX11CodePrinter().doprint(log1p(x)) == 'std::log1p(x)' + + cxx11printer = CXX11CodePrinter() + assert cxx11printer.language == 'C++' + assert cxx11printer.standard == 'C++11' + assert 'operator' in cxx11printer.reserved_words + assert 'noexcept' in cxx11printer.reserved_words + assert 'concept' not in cxx11printer.reserved_words + + +def test_subclass_print_method(): + class MyPrinter(CXX11CodePrinter): + def _print_log1p(self, expr): + return 'my_library::log1p(%s)' % ', '.join(map(self._print, expr.args)) + + assert MyPrinter().doprint(log1p(x)) == 'my_library::log1p(x)' + + +def test_subclass_print_method__ns(): + class MyPrinter(CXX11CodePrinter): + _ns = 'my_library::' + + p = CXX11CodePrinter() + myp = MyPrinter() + + assert p.doprint(log1p(x)) == 'std::log1p(x)' + assert myp.doprint(log1p(x)) == 'my_library::log1p(x)' + + +def test_CXX17CodePrinter(): + assert CXX17CodePrinter().doprint(beta(x, y)) == 'std::beta(x, y)' + assert CXX17CodePrinter().doprint(Ei(x)) == 'std::expint(x)' + assert CXX17CodePrinter().doprint(zeta(x)) == 'std::riemann_zeta(x)' + + # Automatic rewrite + assert CXX17CodePrinter().doprint(frac(x)) == '(x - std::floor(x))' + assert CXX17CodePrinter().doprint(riemann_xi(x)) == '((1.0/2.0)*std::pow(M_PI, -1.0/2.0*x)*x*(x - 1)*std::tgamma((1.0/2.0)*x)*std::riemann_zeta(x))' + + +def test_cxxcode(): + assert sorted(cxxcode(sqrt(x)*.5).split('*')) == sorted(['0.5', 'std::sqrt(x)']) + +def test_cxxcode_nested_minmax(): + assert cxxcode(Max(Min(x, y), Min(u, v))) \ + == 'std::max(std::min(u, v), std::min(x, y))' + assert cxxcode(Min(Max(x, y), Max(u, v))) \ + == 'std::min(std::max(u, v), std::max(x, y))' + +def test_subclass_Integer_Float(): + class MyPrinter(CXX17CodePrinter): + def _print_Integer(self, arg): + return 'bigInt("%s")' % super()._print_Integer(arg) + + def _print_Float(self, arg): + rat = Rational(arg) + return 'bigFloat(%s, %s)' % ( + self._print(Integer(rat.p)), + self._print(Integer(rat.q)) + ) + + p = MyPrinter() + for i in range(13): + assert p.doprint(i) == 'bigInt("%d")' % i + assert p.doprint(Float(0.5)) == 'bigFloat(bigInt("1"), bigInt("2"))' + assert p.doprint(x**-1.0) == 'bigFloat(bigInt("1"), bigInt("1"))/x' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_dot.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_dot.py new file mode 100644 index 0000000000000000000000000000000000000000..6213e237fb7aac6460a956b4c9fc1f7c8710fec6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_dot.py @@ -0,0 +1,134 @@ +from sympy.printing.dot import (purestr, styleof, attrprint, dotnode, + dotedges, dotprint) +from sympy.core.basic import Basic +from sympy.core.expr import Expr +from sympy.core.numbers import (Float, Integer) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.printing.repr import srepr +from sympy.abc import x + + +def test_purestr(): + assert purestr(Symbol('x')) == "Symbol('x')" + assert purestr(Basic(S(1), S(2))) == "Basic(Integer(1), Integer(2))" + assert purestr(Float(2)) == "Float('2.0', precision=53)" + + assert purestr(Symbol('x'), with_args=True) == ("Symbol('x')", ()) + assert purestr(Basic(S(1), S(2)), with_args=True) == \ + ('Basic(Integer(1), Integer(2))', ('Integer(1)', 'Integer(2)')) + assert purestr(Float(2), with_args=True) == \ + ("Float('2.0', precision=53)", ()) + + +def test_styleof(): + styles = [(Basic, {'color': 'blue', 'shape': 'ellipse'}), + (Expr, {'color': 'black'})] + assert styleof(Basic(S(1)), styles) == {'color': 'blue', 'shape': 'ellipse'} + + assert styleof(x + 1, styles) == {'color': 'black', 'shape': 'ellipse'} + + +def test_attrprint(): + assert attrprint({'color': 'blue', 'shape': 'ellipse'}) == \ + '"color"="blue", "shape"="ellipse"' + +def test_dotnode(): + + assert dotnode(x, repeat=False) == \ + '"Symbol(\'x\')" ["color"="black", "label"="x", "shape"="ellipse"];' + assert dotnode(x+2, repeat=False) == \ + '"Add(Integer(2), Symbol(\'x\'))" ' \ + '["color"="black", "label"="Add", "shape"="ellipse"];', \ + dotnode(x+2,repeat=0) + + assert dotnode(x + x**2, repeat=False) == \ + '"Add(Symbol(\'x\'), Pow(Symbol(\'x\'), Integer(2)))" ' \ + '["color"="black", "label"="Add", "shape"="ellipse"];' + assert dotnode(x + x**2, repeat=True) == \ + '"Add(Symbol(\'x\'), Pow(Symbol(\'x\'), Integer(2)))_()" ' \ + '["color"="black", "label"="Add", "shape"="ellipse"];' + +def test_dotedges(): + assert sorted(dotedges(x+2, repeat=False)) == [ + '"Add(Integer(2), Symbol(\'x\'))" -> "Integer(2)";', + '"Add(Integer(2), Symbol(\'x\'))" -> "Symbol(\'x\')";' + ] + assert sorted(dotedges(x + 2, repeat=True)) == [ + '"Add(Integer(2), Symbol(\'x\'))_()" -> "Integer(2)_(0,)";', + '"Add(Integer(2), Symbol(\'x\'))_()" -> "Symbol(\'x\')_(1,)";' + ] + +def test_dotprint(): + text = dotprint(x+2, repeat=False) + assert all(e in text for e in dotedges(x+2, repeat=False)) + assert all( + n in text for n in [dotnode(expr, repeat=False) + for expr in (x, Integer(2), x+2)]) + assert 'digraph' in text + + text = dotprint(x+x**2, repeat=False) + assert all(e in text for e in dotedges(x+x**2, repeat=False)) + assert all( + n in text for n in [dotnode(expr, repeat=False) + for expr in (x, Integer(2), x**2)]) + assert 'digraph' in text + + text = dotprint(x+x**2, repeat=True) + assert all(e in text for e in dotedges(x+x**2, repeat=True)) + assert all( + n in text for n in [dotnode(expr, pos=()) + for expr in [x + x**2]]) + + text = dotprint(x**x, repeat=True) + assert all(e in text for e in dotedges(x**x, repeat=True)) + assert all( + n in text for n in [dotnode(x, pos=(0,)), dotnode(x, pos=(1,))]) + assert 'digraph' in text + +def test_dotprint_depth(): + text = dotprint(3*x+2, depth=1) + assert dotnode(3*x+2) in text + assert dotnode(x) not in text + text = dotprint(3*x+2) + assert "depth" not in text + +def test_Matrix_and_non_basics(): + from sympy.matrices.expressions.matexpr import MatrixSymbol + n = Symbol('n') + assert dotprint(MatrixSymbol('X', n, n)) == \ +"""digraph{ + +# Graph style +"ordering"="out" +"rankdir"="TD" + +######### +# Nodes # +######### + +"MatrixSymbol(Str('X'), Symbol('n'), Symbol('n'))_()" ["color"="black", "label"="MatrixSymbol", "shape"="ellipse"]; +"Str('X')_(0,)" ["color"="blue", "label"="X", "shape"="ellipse"]; +"Symbol('n')_(1,)" ["color"="black", "label"="n", "shape"="ellipse"]; +"Symbol('n')_(2,)" ["color"="black", "label"="n", "shape"="ellipse"]; + +######### +# Edges # +######### + +"MatrixSymbol(Str('X'), Symbol('n'), Symbol('n'))_()" -> "Str('X')_(0,)"; +"MatrixSymbol(Str('X'), Symbol('n'), Symbol('n'))_()" -> "Symbol('n')_(1,)"; +"MatrixSymbol(Str('X'), Symbol('n'), Symbol('n'))_()" -> "Symbol('n')_(2,)"; +}""" + + +def test_labelfunc(): + text = dotprint(x + 2, labelfunc=srepr) + assert "Symbol('x')" in text + assert "Integer(2)" in text + + +def test_commutative(): + x, y = symbols('x y', commutative=False) + assert dotprint(x + y) == dotprint(y + x) + assert dotprint(x*y) != dotprint(y*x) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_fortran.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_fortran.py new file mode 100644 index 0000000000000000000000000000000000000000..c28a1ea16dcf2157b58d763286428dccc1944b71 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_fortran.py @@ -0,0 +1,854 @@ +from sympy.core.add import Add +from sympy.core.expr import Expr +from sympy.core.function import (Function, Lambda, diff) +from sympy.core.mod import Mod +from sympy.core import (Catalan, EulerGamma, GoldenRatio) +from sympy.core.numbers import (E, Float, I, Integer, Rational, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (conjugate, sign) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (atan2, cos, sin) +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import Integral +from sympy.sets.fancysets import Range + +from sympy.codegen import For, Assignment, aug_assign +from sympy.codegen.ast import Declaration, Variable, float32, float64, \ + value_const, real, bool_, While, FunctionPrototype, FunctionDefinition, \ + integer, Return, Element +from sympy.core.expr import UnevaluatedExpr +from sympy.core.relational import Relational +from sympy.logic.boolalg import And, Or, Not, Equivalent, Xor +from sympy.matrices import Matrix, MatrixSymbol +from sympy.printing.fortran import fcode, FCodePrinter +from sympy.tensor import IndexedBase, Idx +from sympy.tensor.array.expressions import ArraySymbol, ArrayElement +from sympy.utilities.lambdify import implemented_function +from sympy.testing.pytest import raises + + +def test_UnevaluatedExpr(): + p, q, r = symbols("p q r", real=True) + q_r = UnevaluatedExpr(q + r) + expr = abs(exp(p+q_r)) + assert fcode(expr, source_format="free") == "exp(p + (q + r))" + x, y, z = symbols("x y z") + y_z = UnevaluatedExpr(y + z) + expr2 = abs(exp(x+y_z)) + assert fcode(expr2, human=False)[2].lstrip() == "exp(re(x) + re(y + z))" + assert fcode(expr2, user_functions={"re": "realpart"}).lstrip() == "exp(realpart(x) + realpart(y + z))" + + +def test_printmethod(): + x = symbols('x') + + class nint(Function): + def _fcode(self, printer): + return "nint(%s)" % printer._print(self.args[0]) + assert fcode(nint(x)) == " nint(x)" + + +def test_fcode_sign(): #issue 12267 + x=symbols('x') + y=symbols('y', integer=True) + z=symbols('z', complex=True) + assert fcode(sign(x), standard=95, source_format='free') == "merge(0d0, dsign(1d0, x), x == 0d0)" + assert fcode(sign(y), standard=95, source_format='free') == "merge(0, isign(1, y), y == 0)" + assert fcode(sign(z), standard=95, source_format='free') == "merge(cmplx(0d0, 0d0), z/abs(z), abs(z) == 0d0)" + raises(NotImplementedError, lambda: fcode(sign(x))) + + +def test_fcode_Pow(): + x, y = symbols('x,y') + n = symbols('n', integer=True) + + assert fcode(x**3) == " x**3" + assert fcode(x**(y**3)) == " x**(y**3)" + assert fcode(1/(sin(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + " (3.5d0*sin(x))**(-x + y**x)/(x**2 + y)" + assert fcode(sqrt(x)) == ' sqrt(x)' + assert fcode(sqrt(n)) == ' sqrt(dble(n))' + assert fcode(x**0.5) == ' sqrt(x)' + assert fcode(sqrt(x)) == ' sqrt(x)' + assert fcode(sqrt(10)) == ' sqrt(10.0d0)' + assert fcode(x**-1.0) == ' 1d0/x' + assert fcode(x**-2.0, 'y', source_format='free') == 'y = x**(-2.0d0)' # 2823 + assert fcode(x**Rational(3, 7)) == ' x**(3.0d0/7.0d0)' + + +def test_fcode_Rational(): + x = symbols('x') + assert fcode(Rational(3, 7)) == " 3.0d0/7.0d0" + assert fcode(Rational(18, 9)) == " 2" + assert fcode(Rational(3, -7)) == " -3.0d0/7.0d0" + assert fcode(Rational(-3, -7)) == " 3.0d0/7.0d0" + assert fcode(x + Rational(3, 7)) == " x + 3.0d0/7.0d0" + assert fcode(Rational(3, 7)*x) == " (3.0d0/7.0d0)*x" + + +def test_fcode_Integer(): + assert fcode(Integer(67)) == " 67" + assert fcode(Integer(-1)) == " -1" + + +def test_fcode_Float(): + assert fcode(Float(42.0)) == " 42.0000000000000d0" + assert fcode(Float(-1e20)) == " -1.00000000000000d+20" + + +def test_fcode_functions(): + x, y = symbols('x,y') + assert fcode(sin(x) ** cos(y)) == " sin(x)**cos(y)" + raises(NotImplementedError, lambda: fcode(Mod(x, y), standard=66)) + raises(NotImplementedError, lambda: fcode(x % y, standard=66)) + raises(NotImplementedError, lambda: fcode(Mod(x, y), standard=77)) + raises(NotImplementedError, lambda: fcode(x % y, standard=77)) + for standard in [90, 95, 2003, 2008]: + assert fcode(Mod(x, y), standard=standard) == " modulo(x, y)" + assert fcode(x % y, standard=standard) == " modulo(x, y)" + + +def test_case(): + ob = FCodePrinter() + x,x_,x__,y,X,X_,Y = symbols('x,x_,x__,y,X,X_,Y') + assert fcode(exp(x_) + sin(x*y) + cos(X*Y)) == \ + ' exp(x_) + sin(x*y) + cos(X__*Y_)' + assert fcode(exp(x__) + 2*x*Y*X_**Rational(7, 2)) == \ + ' 2*X_**(7.0d0/2.0d0)*Y*x + exp(x__)' + assert fcode(exp(x_) + sin(x*y) + cos(X*Y), name_mangling=False) == \ + ' exp(x_) + sin(x*y) + cos(X*Y)' + assert fcode(x - cos(X), name_mangling=False) == ' x - cos(X)' + assert ob.doprint(X*sin(x) + x_, assign_to='me') == ' me = X*sin(x_) + x__' + assert ob.doprint(X*sin(x), assign_to='mu') == ' mu = X*sin(x_)' + assert ob.doprint(x_, assign_to='ad') == ' ad = x__' + n, m = symbols('n,m', integer=True) + A = IndexedBase('A') + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx('i', m) + I = Idx('I', n) + assert fcode(A[i, I]*x[I], assign_to=y[i], source_format='free') == ( + "do i = 1, m\n" + " y(i) = 0\n" + "end do\n" + "do i = 1, m\n" + " do I_ = 1, n\n" + " y(i) = A(i, I_)*x(I_) + y(i)\n" + " end do\n" + "end do" ) + + +#issue 6814 +def test_fcode_functions_with_integers(): + x= symbols('x') + log10_17 = log(10).evalf(17) + loglog10_17 = '0.8340324452479558d0' + assert fcode(x * log(10)) == " x*%sd0" % log10_17 + assert fcode(x * log(10)) == " x*%sd0" % log10_17 + assert fcode(x * log(S(10))) == " x*%sd0" % log10_17 + assert fcode(log(S(10))) == " %sd0" % log10_17 + assert fcode(exp(10)) == " %sd0" % exp(10).evalf(17) + assert fcode(x * log(log(10))) == " x*%s" % loglog10_17 + assert fcode(x * log(log(S(10)))) == " x*%s" % loglog10_17 + + +def test_fcode_NumberSymbol(): + prec = 17 + p = FCodePrinter() + assert fcode(Catalan) == ' parameter (Catalan = %sd0)\n Catalan' % Catalan.evalf(prec) + assert fcode(EulerGamma) == ' parameter (EulerGamma = %sd0)\n EulerGamma' % EulerGamma.evalf(prec) + assert fcode(E) == ' parameter (E = %sd0)\n E' % E.evalf(prec) + assert fcode(GoldenRatio) == ' parameter (GoldenRatio = %sd0)\n GoldenRatio' % GoldenRatio.evalf(prec) + assert fcode(pi) == ' parameter (pi = %sd0)\n pi' % pi.evalf(prec) + assert fcode( + pi, precision=5) == ' parameter (pi = %sd0)\n pi' % pi.evalf(5) + assert fcode(Catalan, human=False) == ({ + (Catalan, p._print(Catalan.evalf(prec)))}, set(), ' Catalan') + assert fcode(EulerGamma, human=False) == ({(EulerGamma, p._print( + EulerGamma.evalf(prec)))}, set(), ' EulerGamma') + assert fcode(E, human=False) == ( + {(E, p._print(E.evalf(prec)))}, set(), ' E') + assert fcode(GoldenRatio, human=False) == ({(GoldenRatio, p._print( + GoldenRatio.evalf(prec)))}, set(), ' GoldenRatio') + assert fcode(pi, human=False) == ( + {(pi, p._print(pi.evalf(prec)))}, set(), ' pi') + assert fcode(pi, precision=5, human=False) == ( + {(pi, p._print(pi.evalf(5)))}, set(), ' pi') + + +def test_fcode_complex(): + assert fcode(I) == " cmplx(0,1)" + x = symbols('x') + assert fcode(4*I) == " cmplx(0,4)" + assert fcode(3 + 4*I) == " cmplx(3,4)" + assert fcode(3 + 4*I + x) == " cmplx(3,4) + x" + assert fcode(I*x) == " cmplx(0,1)*x" + assert fcode(3 + 4*I - x) == " cmplx(3,4) - x" + x = symbols('x', imaginary=True) + assert fcode(5*x) == " 5*x" + assert fcode(I*x) == " cmplx(0,1)*x" + assert fcode(3 + x) == " x + 3" + + +def test_implicit(): + x, y = symbols('x,y') + assert fcode(sin(x)) == " sin(x)" + assert fcode(atan2(x, y)) == " atan2(x, y)" + assert fcode(conjugate(x)) == " conjg(x)" + + +def test_not_fortran(): + x = symbols('x') + g = Function('g') + with raises(NotImplementedError): + fcode(gamma(x)) + assert fcode(Integral(sin(x)), strict=False) == "C Not supported in Fortran:\nC Integral\n Integral(sin(x), x)" + with raises(NotImplementedError): + fcode(g(x)) + + +def test_user_functions(): + x = symbols('x') + assert fcode(sin(x), user_functions={"sin": "zsin"}) == " zsin(x)" + x = symbols('x') + assert fcode( + gamma(x), user_functions={"gamma": "mygamma"}) == " mygamma(x)" + g = Function('g') + assert fcode(g(x), user_functions={"g": "great"}) == " great(x)" + n = symbols('n', integer=True) + assert fcode( + factorial(n), user_functions={"factorial": "fct"}) == " fct(n)" + + +def test_inline_function(): + x = symbols('x') + g = implemented_function('g', Lambda(x, 2*x)) + assert fcode(g(x)) == " 2*x" + g = implemented_function('g', Lambda(x, 2*pi/x)) + assert fcode(g(x)) == ( + " parameter (pi = %sd0)\n" + " 2*pi/x" + ) % pi.evalf(17) + A = IndexedBase('A') + i = Idx('i', symbols('n', integer=True)) + g = implemented_function('g', Lambda(x, x*(1 + x)*(2 + x))) + assert fcode(g(A[i]), assign_to=A[i]) == ( + " do i = 1, n\n" + " A(i) = (A(i) + 1)*(A(i) + 2)*A(i)\n" + " end do" + ) + + +def test_assign_to(): + x = symbols('x') + assert fcode(sin(x), assign_to="s") == " s = sin(x)" + + +def test_line_wrapping(): + x, y = symbols('x,y') + assert fcode(((x + y)**10).expand(), assign_to="var") == ( + " var = x**10 + 10*x**9*y + 45*x**8*y**2 + 120*x**7*y**3 + 210*x**6*\n" + " @ y**4 + 252*x**5*y**5 + 210*x**4*y**6 + 120*x**3*y**7 + 45*x**2*y\n" + " @ **8 + 10*x*y**9 + y**10" + ) + e = [x**i for i in range(11)] + assert fcode(Add(*e)) == ( + " x**10 + x**9 + x**8 + x**7 + x**6 + x**5 + x**4 + x**3 + x**2 + x\n" + " @ + 1" + ) + + +def test_fcode_precedence(): + x, y = symbols("x y") + assert fcode(And(x < y, y < x + 1), source_format="free") == \ + "x < y .and. y < x + 1" + assert fcode(Or(x < y, y < x + 1), source_format="free") == \ + "x < y .or. y < x + 1" + assert fcode(Xor(x < y, y < x + 1, evaluate=False), + source_format="free") == "x < y .neqv. y < x + 1" + assert fcode(Equivalent(x < y, y < x + 1), source_format="free") == \ + "x < y .eqv. y < x + 1" + + +def test_fcode_Logical(): + x, y, z = symbols("x y z") + # unary Not + assert fcode(Not(x), source_format="free") == ".not. x" + # binary And + assert fcode(And(x, y), source_format="free") == "x .and. y" + assert fcode(And(x, Not(y)), source_format="free") == "x .and. .not. y" + assert fcode(And(Not(x), y), source_format="free") == "y .and. .not. x" + assert fcode(And(Not(x), Not(y)), source_format="free") == \ + ".not. x .and. .not. y" + assert fcode(Not(And(x, y), evaluate=False), source_format="free") == \ + ".not. (x .and. y)" + # binary Or + assert fcode(Or(x, y), source_format="free") == "x .or. y" + assert fcode(Or(x, Not(y)), source_format="free") == "x .or. .not. y" + assert fcode(Or(Not(x), y), source_format="free") == "y .or. .not. x" + assert fcode(Or(Not(x), Not(y)), source_format="free") == \ + ".not. x .or. .not. y" + assert fcode(Not(Or(x, y), evaluate=False), source_format="free") == \ + ".not. (x .or. y)" + # mixed And/Or + assert fcode(And(Or(y, z), x), source_format="free") == "x .and. (y .or. z)" + assert fcode(And(Or(z, x), y), source_format="free") == "y .and. (x .or. z)" + assert fcode(And(Or(x, y), z), source_format="free") == "z .and. (x .or. y)" + assert fcode(Or(And(y, z), x), source_format="free") == "x .or. y .and. z" + assert fcode(Or(And(z, x), y), source_format="free") == "y .or. x .and. z" + assert fcode(Or(And(x, y), z), source_format="free") == "z .or. x .and. y" + # trinary And + assert fcode(And(x, y, z), source_format="free") == "x .and. y .and. z" + assert fcode(And(x, y, Not(z)), source_format="free") == \ + "x .and. y .and. .not. z" + assert fcode(And(x, Not(y), z), source_format="free") == \ + "x .and. z .and. .not. y" + assert fcode(And(Not(x), y, z), source_format="free") == \ + "y .and. z .and. .not. x" + assert fcode(Not(And(x, y, z), evaluate=False), source_format="free") == \ + ".not. (x .and. y .and. z)" + # trinary Or + assert fcode(Or(x, y, z), source_format="free") == "x .or. y .or. z" + assert fcode(Or(x, y, Not(z)), source_format="free") == \ + "x .or. y .or. .not. z" + assert fcode(Or(x, Not(y), z), source_format="free") == \ + "x .or. z .or. .not. y" + assert fcode(Or(Not(x), y, z), source_format="free") == \ + "y .or. z .or. .not. x" + assert fcode(Not(Or(x, y, z), evaluate=False), source_format="free") == \ + ".not. (x .or. y .or. z)" + + +def test_fcode_Xlogical(): + x, y, z = symbols("x y z") + # binary Xor + assert fcode(Xor(x, y, evaluate=False), source_format="free") == \ + "x .neqv. y" + assert fcode(Xor(x, Not(y), evaluate=False), source_format="free") == \ + "x .neqv. .not. y" + assert fcode(Xor(Not(x), y, evaluate=False), source_format="free") == \ + "y .neqv. .not. x" + assert fcode(Xor(Not(x), Not(y), evaluate=False), + source_format="free") == ".not. x .neqv. .not. y" + assert fcode(Not(Xor(x, y, evaluate=False), evaluate=False), + source_format="free") == ".not. (x .neqv. y)" + # binary Equivalent + assert fcode(Equivalent(x, y), source_format="free") == "x .eqv. y" + assert fcode(Equivalent(x, Not(y)), source_format="free") == \ + "x .eqv. .not. y" + assert fcode(Equivalent(Not(x), y), source_format="free") == \ + "y .eqv. .not. x" + assert fcode(Equivalent(Not(x), Not(y)), source_format="free") == \ + ".not. x .eqv. .not. y" + assert fcode(Not(Equivalent(x, y), evaluate=False), + source_format="free") == ".not. (x .eqv. y)" + # mixed And/Equivalent + assert fcode(Equivalent(And(y, z), x), source_format="free") == \ + "x .eqv. y .and. z" + assert fcode(Equivalent(And(z, x), y), source_format="free") == \ + "y .eqv. x .and. z" + assert fcode(Equivalent(And(x, y), z), source_format="free") == \ + "z .eqv. x .and. y" + assert fcode(And(Equivalent(y, z), x), source_format="free") == \ + "x .and. (y .eqv. z)" + assert fcode(And(Equivalent(z, x), y), source_format="free") == \ + "y .and. (x .eqv. z)" + assert fcode(And(Equivalent(x, y), z), source_format="free") == \ + "z .and. (x .eqv. y)" + # mixed Or/Equivalent + assert fcode(Equivalent(Or(y, z), x), source_format="free") == \ + "x .eqv. y .or. z" + assert fcode(Equivalent(Or(z, x), y), source_format="free") == \ + "y .eqv. x .or. z" + assert fcode(Equivalent(Or(x, y), z), source_format="free") == \ + "z .eqv. x .or. y" + assert fcode(Or(Equivalent(y, z), x), source_format="free") == \ + "x .or. (y .eqv. z)" + assert fcode(Or(Equivalent(z, x), y), source_format="free") == \ + "y .or. (x .eqv. z)" + assert fcode(Or(Equivalent(x, y), z), source_format="free") == \ + "z .or. (x .eqv. y)" + # mixed Xor/Equivalent + assert fcode(Equivalent(Xor(y, z, evaluate=False), x), + source_format="free") == "x .eqv. (y .neqv. z)" + assert fcode(Equivalent(Xor(z, x, evaluate=False), y), + source_format="free") == "y .eqv. (x .neqv. z)" + assert fcode(Equivalent(Xor(x, y, evaluate=False), z), + source_format="free") == "z .eqv. (x .neqv. y)" + assert fcode(Xor(Equivalent(y, z), x, evaluate=False), + source_format="free") == "x .neqv. (y .eqv. z)" + assert fcode(Xor(Equivalent(z, x), y, evaluate=False), + source_format="free") == "y .neqv. (x .eqv. z)" + assert fcode(Xor(Equivalent(x, y), z, evaluate=False), + source_format="free") == "z .neqv. (x .eqv. y)" + # mixed And/Xor + assert fcode(Xor(And(y, z), x, evaluate=False), source_format="free") == \ + "x .neqv. y .and. z" + assert fcode(Xor(And(z, x), y, evaluate=False), source_format="free") == \ + "y .neqv. x .and. z" + assert fcode(Xor(And(x, y), z, evaluate=False), source_format="free") == \ + "z .neqv. x .and. y" + assert fcode(And(Xor(y, z, evaluate=False), x), source_format="free") == \ + "x .and. (y .neqv. z)" + assert fcode(And(Xor(z, x, evaluate=False), y), source_format="free") == \ + "y .and. (x .neqv. z)" + assert fcode(And(Xor(x, y, evaluate=False), z), source_format="free") == \ + "z .and. (x .neqv. y)" + # mixed Or/Xor + assert fcode(Xor(Or(y, z), x, evaluate=False), source_format="free") == \ + "x .neqv. y .or. z" + assert fcode(Xor(Or(z, x), y, evaluate=False), source_format="free") == \ + "y .neqv. x .or. z" + assert fcode(Xor(Or(x, y), z, evaluate=False), source_format="free") == \ + "z .neqv. x .or. y" + assert fcode(Or(Xor(y, z, evaluate=False), x), source_format="free") == \ + "x .or. (y .neqv. z)" + assert fcode(Or(Xor(z, x, evaluate=False), y), source_format="free") == \ + "y .or. (x .neqv. z)" + assert fcode(Or(Xor(x, y, evaluate=False), z), source_format="free") == \ + "z .or. (x .neqv. y)" + # trinary Xor + assert fcode(Xor(x, y, z, evaluate=False), source_format="free") == \ + "x .neqv. y .neqv. z" + assert fcode(Xor(x, y, Not(z), evaluate=False), source_format="free") == \ + "x .neqv. y .neqv. .not. z" + assert fcode(Xor(x, Not(y), z, evaluate=False), source_format="free") == \ + "x .neqv. z .neqv. .not. y" + assert fcode(Xor(Not(x), y, z, evaluate=False), source_format="free") == \ + "y .neqv. z .neqv. .not. x" + + +def test_fcode_Relational(): + x, y = symbols("x y") + assert fcode(Relational(x, y, "=="), source_format="free") == "x == y" + assert fcode(Relational(x, y, "!="), source_format="free") == "x /= y" + assert fcode(Relational(x, y, ">="), source_format="free") == "x >= y" + assert fcode(Relational(x, y, "<="), source_format="free") == "x <= y" + assert fcode(Relational(x, y, ">"), source_format="free") == "x > y" + assert fcode(Relational(x, y, "<"), source_format="free") == "x < y" + + +def test_fcode_Piecewise(): + x = symbols('x') + expr = Piecewise((x, x < 1), (x**2, True)) + # Check that inline conditional (merge) fails if standard isn't 95+ + raises(NotImplementedError, lambda: fcode(expr)) + code = fcode(expr, standard=95) + expected = " merge(x, x**2, x < 1)" + assert code == expected + assert fcode(Piecewise((x, x < 1), (x**2, True)), assign_to="var") == ( + " if (x < 1) then\n" + " var = x\n" + " else\n" + " var = x**2\n" + " end if" + ) + a = cos(x)/x + b = sin(x)/x + for i in range(10): + a = diff(a, x) + b = diff(b, x) + expected = ( + " if (x < 0) then\n" + " weird_name = -cos(x)/x + 10*sin(x)/x**2 + 90*cos(x)/x**3 - 720*\n" + " @ sin(x)/x**4 - 5040*cos(x)/x**5 + 30240*sin(x)/x**6 + 151200*cos(x\n" + " @ )/x**7 - 604800*sin(x)/x**8 - 1814400*cos(x)/x**9 + 3628800*sin(x\n" + " @ )/x**10 + 3628800*cos(x)/x**11\n" + " else\n" + " weird_name = -sin(x)/x - 10*cos(x)/x**2 + 90*sin(x)/x**3 + 720*\n" + " @ cos(x)/x**4 - 5040*sin(x)/x**5 - 30240*cos(x)/x**6 + 151200*sin(x\n" + " @ )/x**7 + 604800*cos(x)/x**8 - 1814400*sin(x)/x**9 - 3628800*cos(x\n" + " @ )/x**10 + 3628800*sin(x)/x**11\n" + " end if" + ) + code = fcode(Piecewise((a, x < 0), (b, True)), assign_to="weird_name") + assert code == expected + code = fcode(Piecewise((x, x < 1), (x**2, x > 1), (sin(x), True)), standard=95) + expected = " merge(x, merge(x**2, sin(x), x > 1), x < 1)" + assert code == expected + # Check that Piecewise without a True (default) condition error + expr = Piecewise((x, x < 1), (x**2, x > 1), (sin(x), x > 0)) + raises(ValueError, lambda: fcode(expr)) + + +def test_wrap_fortran(): + # "########################################################################" + printer = FCodePrinter() + lines = [ + "C This is a long comment on a single line that must be wrapped properly to produce nice output", + " this = is + a + long + and + nasty + fortran + statement + that * must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that * must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that * must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement(that)/must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement(that)/must + be + wrapped + properly", + ] + wrapped_lines = printer._wrap_fortran(lines) + expected_lines = [ + "C This is a long comment on a single line that must be wrapped", + "C properly to produce nice output", + " this = is + a + long + and + nasty + fortran + statement + that *", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that *", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that", + " @ * must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that", + " @ *must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement +", + " @ that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that", + " @ **must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that", + " @ **must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement +", + " @ that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement(that)/", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement(that)", + " @ /must + be + wrapped + properly", + ] + for line in wrapped_lines: + assert len(line) <= 72 + for w, e in zip(wrapped_lines, expected_lines): + assert w == e + assert len(wrapped_lines) == len(expected_lines) + + +def test_wrap_fortran_keep_d0(): + printer = FCodePrinter() + lines = [ + ' this_variable_is_very_long_because_we_try_to_test_line_break=1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break =1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break = 1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break = 1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break = 1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break = 10.0d0' + ] + expected = [ + ' this_variable_is_very_long_because_we_try_to_test_line_break=1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break =', + ' @ 1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break =', + ' @ 1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break =', + ' @ 1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break =', + ' @ 1.0d0', + ' this_variable_is_very_long_because_we_try_to_test_line_break =', + ' @ 10.0d0' + ] + assert printer._wrap_fortran(lines) == expected + + +def test_settings(): + raises(TypeError, lambda: fcode(S(4), method="garbage")) + + +def test_free_form_code_line(): + x, y = symbols('x,y') + assert fcode(cos(x) + sin(y), source_format='free') == "sin(y) + cos(x)" + + +def test_free_form_continuation_line(): + x, y = symbols('x,y') + result = fcode(((cos(x) + sin(y))**(7)).expand(), source_format='free') + expected = ( + 'sin(y)**7 + 7*sin(y)**6*cos(x) + 21*sin(y)**5*cos(x)**2 + 35*sin(y)**4* &\n' + ' cos(x)**3 + 35*sin(y)**3*cos(x)**4 + 21*sin(y)**2*cos(x)**5 + 7* &\n' + ' sin(y)*cos(x)**6 + cos(x)**7' + ) + assert result == expected + + +def test_free_form_comment_line(): + printer = FCodePrinter({'source_format': 'free'}) + lines = [ "! This is a long comment on a single line that must be wrapped properly to produce nice output"] + expected = [ + '! This is a long comment on a single line that must be wrapped properly', + '! to produce nice output'] + assert printer._wrap_fortran(lines) == expected + + +def test_loops(): + n, m = symbols('n,m', integer=True) + A = IndexedBase('A') + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + + expected = ( + 'do i = 1, m\n' + ' y(i) = 0\n' + 'end do\n' + 'do i = 1, m\n' + ' do j = 1, n\n' + ' y(i) = %(rhs)s\n' + ' end do\n' + 'end do' + ) + + code = fcode(A[i, j]*x[j], assign_to=y[i], source_format='free') + assert (code == expected % {'rhs': 'y(i) + A(i, j)*x(j)'} or + code == expected % {'rhs': 'y(i) + x(j)*A(i, j)'} or + code == expected % {'rhs': 'x(j)*A(i, j) + y(i)'} or + code == expected % {'rhs': 'A(i, j)*x(j) + y(i)'}) + + +def test_dummy_loops(): + i, m = symbols('i m', integer=True, cls=Dummy) + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx(i, m) + + expected = ( + 'do i_%(icount)i = 1, m_%(mcount)i\n' + ' y(i_%(icount)i) = x(i_%(icount)i)\n' + 'end do' + ) % {'icount': i.label.dummy_index, 'mcount': m.dummy_index} + code = fcode(x[i], assign_to=y[i], source_format='free') + assert code == expected + + +def test_fcode_Indexed_without_looking_for_contraction(): + len_y = 5 + y = IndexedBase('y', shape=(len_y,)) + x = IndexedBase('x', shape=(len_y,)) + Dy = IndexedBase('Dy', shape=(len_y-1,)) + i = Idx('i', len_y-1) + e=Eq(Dy[i], (y[i+1]-y[i])/(x[i+1]-x[i])) + code0 = fcode(e.rhs, assign_to=e.lhs, contract=False) + assert code0.endswith('Dy(i) = (y(i + 1) - y(i))/(x(i + 1) - x(i))') + + +def test_element_like_objects(): + len_y = 5 + y = ArraySymbol('y', shape=(len_y,)) + x = ArraySymbol('x', shape=(len_y,)) + Dy = ArraySymbol('Dy', shape=(len_y-1,)) + i = Idx('i', len_y-1) + e=Eq(Dy[i], (y[i+1]-y[i])/(x[i+1]-x[i])) + code0 = fcode(Assignment(e.lhs, e.rhs)) + assert code0.endswith('Dy(i) = (y(i + 1) - y(i))/(x(i + 1) - x(i))') + + class ElementExpr(Element, Expr): + pass + + e = e.subs((a, ElementExpr(a.name, a.indices)) for a in e.atoms(ArrayElement) ) + e=Eq(Dy[i], (y[i+1]-y[i])/(x[i+1]-x[i])) + code0 = fcode(Assignment(e.lhs, e.rhs)) + assert code0.endswith('Dy(i) = (y(i + 1) - y(i))/(x(i + 1) - x(i))') + + +def test_derived_classes(): + class MyFancyFCodePrinter(FCodePrinter): + _default_settings = FCodePrinter._default_settings.copy() + + printer = MyFancyFCodePrinter() + x = symbols('x') + assert printer.doprint(sin(x), "bork") == " bork = sin(x)" + + +def test_indent(): + codelines = ( + 'subroutine test(a)\n' + 'integer :: a, i, j\n' + '\n' + 'do\n' + 'do \n' + 'do j = 1, 5\n' + 'if (a>b) then\n' + 'if(b>0) then\n' + 'a = 3\n' + 'donot_indent_me = 2\n' + 'do_not_indent_me_either = 2\n' + 'ifIam_indented_something_went_wrong = 2\n' + 'if_I_am_indented_something_went_wrong = 2\n' + 'end should not be unindented here\n' + 'end if\n' + 'endif\n' + 'end do\n' + 'end do\n' + 'enddo\n' + 'end subroutine\n' + '\n' + 'subroutine test2(a)\n' + 'integer :: a\n' + 'do\n' + 'a = a + 1\n' + 'end do \n' + 'end subroutine\n' + ) + expected = ( + 'subroutine test(a)\n' + 'integer :: a, i, j\n' + '\n' + 'do\n' + ' do \n' + ' do j = 1, 5\n' + ' if (a>b) then\n' + ' if(b>0) then\n' + ' a = 3\n' + ' donot_indent_me = 2\n' + ' do_not_indent_me_either = 2\n' + ' ifIam_indented_something_went_wrong = 2\n' + ' if_I_am_indented_something_went_wrong = 2\n' + ' end should not be unindented here\n' + ' end if\n' + ' endif\n' + ' end do\n' + ' end do\n' + 'enddo\n' + 'end subroutine\n' + '\n' + 'subroutine test2(a)\n' + 'integer :: a\n' + 'do\n' + ' a = a + 1\n' + 'end do \n' + 'end subroutine\n' + ) + p = FCodePrinter({'source_format': 'free'}) + result = p.indent_code(codelines) + assert result == expected + +def test_Matrix_printing(): + x, y, z = symbols('x,y,z') + # Test returning a Matrix + mat = Matrix([x*y, Piecewise((2 + x, y>0), (y, True)), sin(z)]) + A = MatrixSymbol('A', 3, 1) + assert fcode(mat, A) == ( + " A(1, 1) = x*y\n" + " if (y > 0) then\n" + " A(2, 1) = x + 2\n" + " else\n" + " A(2, 1) = y\n" + " end if\n" + " A(3, 1) = sin(z)") + # Test using MatrixElements in expressions + expr = Piecewise((2*A[2, 0], x > 0), (A[2, 0], True)) + sin(A[1, 0]) + A[0, 0] + assert fcode(expr, standard=95) == ( + " merge(2*A(3, 1), A(3, 1), x > 0) + sin(A(2, 1)) + A(1, 1)") + # Test using MatrixElements in a Matrix + q = MatrixSymbol('q', 5, 1) + M = MatrixSymbol('M', 3, 3) + m = Matrix([[sin(q[1,0]), 0, cos(q[2,0])], + [q[1,0] + q[2,0], q[3, 0], 5], + [2*q[4, 0]/q[1,0], sqrt(q[0,0]) + 4, 0]]) + assert fcode(m, M) == ( + " M(1, 1) = sin(q(2, 1))\n" + " M(2, 1) = q(2, 1) + q(3, 1)\n" + " M(3, 1) = 2*q(5, 1)/q(2, 1)\n" + " M(1, 2) = 0\n" + " M(2, 2) = q(4, 1)\n" + " M(3, 2) = sqrt(q(1, 1)) + 4\n" + " M(1, 3) = cos(q(3, 1))\n" + " M(2, 3) = 5\n" + " M(3, 3) = 0") + + +def test_fcode_For(): + x, y = symbols('x y') + + f = For(x, Range(0, 10, 2), [Assignment(y, x * y)]) + sol = fcode(f) + assert sol == (" do x = 0, 9, 2\n" + " y = x*y\n" + " end do") + + +def test_fcode_Declaration(): + def check(expr, ref, **kwargs): + assert fcode(expr, standard=95, source_format='free', **kwargs) == ref + + i = symbols('i', integer=True) + var1 = Variable.deduced(i) + dcl1 = Declaration(var1) + check(dcl1, "integer*4 :: i") + + + x, y = symbols('x y') + var2 = Variable(x, float32, value=42, attrs={value_const}) + dcl2b = Declaration(var2) + check(dcl2b, 'real*4, parameter :: x = 42') + + var3 = Variable(y, type=bool_) + dcl3 = Declaration(var3) + check(dcl3, 'logical :: y') + + check(float32, "real*4") + check(float64, "real*8") + check(real, "real*4", type_aliases={real: float32}) + check(real, "real*8", type_aliases={real: float64}) + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert(fcode(A[0, 0]) == " A(1, 1)") + assert(fcode(3 * A[0, 0]) == " 3*A(1, 1)") + + F = C[0, 0].subs(C, A - B) + assert(fcode(F) == " (A - B)(1, 1)") + + +def test_aug_assign(): + x = symbols('x') + assert fcode(aug_assign(x, '+', 1), source_format='free') == 'x = x + 1' + + +def test_While(): + x = symbols('x') + assert fcode(While(abs(x) > 1, [aug_assign(x, '-', 1)]), source_format='free') == ( + 'do while (abs(x) > 1)\n' + ' x = x - 1\n' + 'end do' + ) + + +def test_FunctionPrototype_print(): + x = symbols('x') + n = symbols('n', integer=True) + vx = Variable(x, type=real) + vn = Variable(n, type=integer) + fp1 = FunctionPrototype(real, 'power', [vx, vn]) + # Should be changed to proper test once multi-line generation is working + # see https://github.com/sympy/sympy/issues/15824 + raises(NotImplementedError, lambda: fcode(fp1)) + + +def test_FunctionDefinition_print(): + x = symbols('x') + n = symbols('n', integer=True) + vx = Variable(x, type=real) + vn = Variable(n, type=integer) + body = [Assignment(x, x**n), Return(x)] + fd1 = FunctionDefinition(real, 'power', [vx, vn], body) + # Should be changed to proper test once multi-line generation is working + # see https://github.com/sympy/sympy/issues/15824 + raises(NotImplementedError, lambda: fcode(fd1)) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_glsl.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_glsl.py new file mode 100644 index 0000000000000000000000000000000000000000..86ec1dfe4a37d141e8435c369cb692d3a9a3b7bc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_glsl.py @@ -0,0 +1,998 @@ +from sympy.core import (pi, symbols, Rational, Integer, GoldenRatio, EulerGamma, + Catalan, Lambda, Dummy, Eq, Ne, Le, Lt, Gt, Ge) +from sympy.functions import Piecewise, sin, cos, Abs, exp, ceiling, sqrt +from sympy.testing.pytest import raises, warns_deprecated_sympy +from sympy.printing.glsl import GLSLPrinter +from sympy.printing.str import StrPrinter +from sympy.utilities.lambdify import implemented_function +from sympy.tensor import IndexedBase, Idx +from sympy.matrices import Matrix, MatrixSymbol +from sympy.core import Tuple +from sympy.printing.glsl import glsl_code +import textwrap + +x, y, z = symbols('x,y,z') + + +def test_printmethod(): + assert glsl_code(Abs(x)) == "abs(x)" + +def test_print_without_operators(): + assert glsl_code(x*y,use_operators = False) == 'mul(x, y)' + assert glsl_code(x**y+z,use_operators = False) == 'add(pow(x, y), z)' + assert glsl_code(x*(y+z),use_operators = False) == 'mul(x, add(y, z))' + assert glsl_code(x*(y+z),use_operators = False) == 'mul(x, add(y, z))' + assert glsl_code(x*(y+z**y**0.5),use_operators = False) == 'mul(x, add(y, pow(z, sqrt(y))))' + assert glsl_code(-x-y, use_operators=False, zero='zero()') == 'sub(zero(), add(x, y))' + assert glsl_code(-x-y, use_operators=False) == 'sub(0.0, add(x, y))' + +def test_glsl_code_sqrt(): + assert glsl_code(sqrt(x)) == "sqrt(x)" + assert glsl_code(x**0.5) == "sqrt(x)" + assert glsl_code(sqrt(x)) == "sqrt(x)" + + +def test_glsl_code_Pow(): + g = implemented_function('g', Lambda(x, 2*x)) + assert glsl_code(x**3) == "pow(x, 3.0)" + assert glsl_code(x**(y**3)) == "pow(x, pow(y, 3.0))" + assert glsl_code(1/(g(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + "pow(3.5*2*x, -x + pow(y, x))/(pow(x, 2.0) + y)" + assert glsl_code(x**-1.0) == '1.0/x' + + +def test_glsl_code_Relational(): + assert glsl_code(Eq(x, y)) == "x == y" + assert glsl_code(Ne(x, y)) == "x != y" + assert glsl_code(Le(x, y)) == "x <= y" + assert glsl_code(Lt(x, y)) == "x < y" + assert glsl_code(Gt(x, y)) == "x > y" + assert glsl_code(Ge(x, y)) == "x >= y" + + +def test_glsl_code_constants_mathh(): + assert glsl_code(exp(1)) == "float E = 2.71828183;\nE" + assert glsl_code(pi) == "float pi = 3.14159265;\npi" + # assert glsl_code(oo) == "Number.POSITIVE_INFINITY" + # assert glsl_code(-oo) == "Number.NEGATIVE_INFINITY" + + +def test_glsl_code_constants_other(): + assert glsl_code(2*GoldenRatio) == "float GoldenRatio = 1.61803399;\n2*GoldenRatio" + assert glsl_code(2*Catalan) == "float Catalan = 0.915965594;\n2*Catalan" + assert glsl_code(2*EulerGamma) == "float EulerGamma = 0.577215665;\n2*EulerGamma" + + +def test_glsl_code_Rational(): + assert glsl_code(Rational(3, 7)) == "3.0/7.0" + assert glsl_code(Rational(18, 9)) == "2" + assert glsl_code(Rational(3, -7)) == "-3.0/7.0" + assert glsl_code(Rational(-3, -7)) == "3.0/7.0" + + +def test_glsl_code_Integer(): + assert glsl_code(Integer(67)) == "67" + assert glsl_code(Integer(-1)) == "-1" + + +def test_glsl_code_functions(): + assert glsl_code(sin(x) ** cos(x)) == "pow(sin(x), cos(x))" + + +def test_glsl_code_inline_function(): + x = symbols('x') + g = implemented_function('g', Lambda(x, 2*x)) + assert glsl_code(g(x)) == "2*x" + g = implemented_function('g', Lambda(x, 2*x/Catalan)) + assert glsl_code(g(x)) == "float Catalan = 0.915965594;\n2*x/Catalan" + A = IndexedBase('A') + i = Idx('i', symbols('n', integer=True)) + g = implemented_function('g', Lambda(x, x*(1 + x)*(2 + x))) + assert glsl_code(g(A[i]), assign_to=A[i]) == ( + "for (int i=0; i 1), (sin(x), x > 0)) + raises(ValueError, lambda: glsl_code(expr)) + + +def test_glsl_code_Piecewise_deep(): + p = glsl_code(2*Piecewise((x, x < 1), (x**2, True))) + s = \ +"""\ +2*((x < 1) ? ( + x +) +: ( + pow(x, 2.0) +))\ +""" + assert p == s + + +def test_glsl_code_settings(): + raises(TypeError, lambda: glsl_code(sin(x), method="garbage")) + + +def test_glsl_code_Indexed(): + n, m, o = symbols('n m o', integer=True) + i, j, k = Idx('i', n), Idx('j', m), Idx('k', o) + p = GLSLPrinter() + p._not_c = set() + + x = IndexedBase('x')[j] + assert p._print_Indexed(x) == 'x[j]' + A = IndexedBase('A')[i, j] + assert p._print_Indexed(A) == 'A[%s]' % (m*i+j) + B = IndexedBase('B')[i, j, k] + assert p._print_Indexed(B) == 'B[%s]' % (i*o*m+j*o+k) + + assert p._not_c == set() + +def test_glsl_code_list_tuple_Tuple(): + assert glsl_code([1,2,3,4]) == 'vec4(1, 2, 3, 4)' + assert glsl_code([1,2,3],glsl_types=False) == 'float[3](1, 2, 3)' + assert glsl_code([1,2,3]) == glsl_code((1,2,3)) + assert glsl_code([1,2,3]) == glsl_code(Tuple(1,2,3)) + + m = MatrixSymbol('A',3,4) + assert glsl_code([m[0],m[1]]) + +def test_glsl_code_loops_matrix_vector(): + n, m = symbols('n m', integer=True) + A = IndexedBase('A') + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + + s = ( + 'for (int i=0; i0), (y, True)), sin(z)]) + A = MatrixSymbol('A', 3, 1) + assert glsl_code(mat, assign_to=A) == ( +'''A[0][0] = x*y; +if (y > 0) { + A[1][0] = x + 2; +} +else { + A[1][0] = y; +} +A[2][0] = sin(z);''' ) + assert glsl_code(Matrix([A[0],A[1]])) + # Test using MatrixElements in expressions + expr = Piecewise((2*A[2, 0], x > 0), (A[2, 0], True)) + sin(A[1, 0]) + A[0, 0] + assert glsl_code(expr) == ( +'''((x > 0) ? ( + 2*A[2][0] +) +: ( + A[2][0] +)) + sin(A[1][0]) + A[0][0]''' ) + + # Test using MatrixElements in a Matrix + q = MatrixSymbol('q', 5, 1) + M = MatrixSymbol('M', 3, 3) + m = Matrix([[sin(q[1,0]), 0, cos(q[2,0])], + [q[1,0] + q[2,0], q[3, 0], 5], + [2*q[4, 0]/q[1,0], sqrt(q[0,0]) + 4, 0]]) + assert glsl_code(m,M) == ( +'''M[0][0] = sin(q[1]); +M[0][1] = 0; +M[0][2] = cos(q[2]); +M[1][0] = q[1] + q[2]; +M[1][1] = q[3]; +M[1][2] = 5; +M[2][0] = 2*q[4]/q[1]; +M[2][1] = sqrt(q[0]) + 4; +M[2][2] = 0;''' + ) + +def test_Matrices_1x7(): + gl = glsl_code + A = Matrix([1,2,3,4,5,6,7]) + assert gl(A) == 'float[7](1, 2, 3, 4, 5, 6, 7)' + assert gl(A.transpose()) == 'float[7](1, 2, 3, 4, 5, 6, 7)' + +def test_Matrices_1x7_array_type_int(): + gl = glsl_code + A = Matrix([1,2,3,4,5,6,7]) + assert gl(A, array_type='int') == 'int[7](1, 2, 3, 4, 5, 6, 7)' + +def test_Tuple_array_type_custom(): + gl = glsl_code + A = symbols('a b c') + assert gl(A, array_type='AbcType', glsl_types=False) == 'AbcType[3](a, b, c)' + +def test_Matrices_1x7_spread_assign_to_symbols(): + gl = glsl_code + A = Matrix([1,2,3,4,5,6,7]) + assign_to = symbols('x.a x.b x.c x.d x.e x.f x.g') + assert gl(A, assign_to=assign_to) == textwrap.dedent('''\ + x.a = 1; + x.b = 2; + x.c = 3; + x.d = 4; + x.e = 5; + x.f = 6; + x.g = 7;''' + ) + +def test_spread_assign_to_nested_symbols(): + gl = glsl_code + expr = ((1,2,3), (1,2,3)) + assign_to = (symbols('a b c'), symbols('x y z')) + assert gl(expr, assign_to=assign_to) == textwrap.dedent('''\ + a = 1; + b = 2; + c = 3; + x = 1; + y = 2; + z = 3;''' + ) + +def test_spread_assign_to_deeply_nested_symbols(): + gl = glsl_code + a, b, c, x, y, z = symbols('a b c x y z') + expr = (((1,2),3), ((1,2),3)) + assign_to = (((a, b), c), ((x, y), z)) + assert gl(expr, assign_to=assign_to) == textwrap.dedent('''\ + a = 1; + b = 2; + c = 3; + x = 1; + y = 2; + z = 3;''' + ) + +def test_matrix_of_tuples_spread_assign_to_symbols(): + gl = glsl_code + with warns_deprecated_sympy(): + expr = Matrix([[(1,2),(3,4)],[(5,6),(7,8)]]) + assign_to = (symbols('a b'), symbols('c d'), symbols('e f'), symbols('g h')) + assert gl(expr, assign_to) == textwrap.dedent('''\ + a = 1; + b = 2; + c = 3; + d = 4; + e = 5; + f = 6; + g = 7; + h = 8;''' + ) + +def test_cannot_assign_to_cause_mismatched_length(): + expr = (1, 2) + assign_to = symbols('x y z') + raises(ValueError, lambda: glsl_code(expr, assign_to)) + +def test_matrix_4x4_assign(): + gl = glsl_code + expr = MatrixSymbol('A',4,4) * MatrixSymbol('B',4,4) + MatrixSymbol('C',4,4) + assign_to = MatrixSymbol('X',4,4) + assert gl(expr, assign_to=assign_to) == textwrap.dedent('''\ + X[0][0] = A[0][0]*B[0][0] + A[0][1]*B[1][0] + A[0][2]*B[2][0] + A[0][3]*B[3][0] + C[0][0]; + X[0][1] = A[0][0]*B[0][1] + A[0][1]*B[1][1] + A[0][2]*B[2][1] + A[0][3]*B[3][1] + C[0][1]; + X[0][2] = A[0][0]*B[0][2] + A[0][1]*B[1][2] + A[0][2]*B[2][2] + A[0][3]*B[3][2] + C[0][2]; + X[0][3] = A[0][0]*B[0][3] + A[0][1]*B[1][3] + A[0][2]*B[2][3] + A[0][3]*B[3][3] + C[0][3]; + X[1][0] = A[1][0]*B[0][0] + A[1][1]*B[1][0] + A[1][2]*B[2][0] + A[1][3]*B[3][0] + C[1][0]; + X[1][1] = A[1][0]*B[0][1] + A[1][1]*B[1][1] + A[1][2]*B[2][1] + A[1][3]*B[3][1] + C[1][1]; + X[1][2] = A[1][0]*B[0][2] + A[1][1]*B[1][2] + A[1][2]*B[2][2] + A[1][3]*B[3][2] + C[1][2]; + X[1][3] = A[1][0]*B[0][3] + A[1][1]*B[1][3] + A[1][2]*B[2][3] + A[1][3]*B[3][3] + C[1][3]; + X[2][0] = A[2][0]*B[0][0] + A[2][1]*B[1][0] + A[2][2]*B[2][0] + A[2][3]*B[3][0] + C[2][0]; + X[2][1] = A[2][0]*B[0][1] + A[2][1]*B[1][1] + A[2][2]*B[2][1] + A[2][3]*B[3][1] + C[2][1]; + X[2][2] = A[2][0]*B[0][2] + A[2][1]*B[1][2] + A[2][2]*B[2][2] + A[2][3]*B[3][2] + C[2][2]; + X[2][3] = A[2][0]*B[0][3] + A[2][1]*B[1][3] + A[2][2]*B[2][3] + A[2][3]*B[3][3] + C[2][3]; + X[3][0] = A[3][0]*B[0][0] + A[3][1]*B[1][0] + A[3][2]*B[2][0] + A[3][3]*B[3][0] + C[3][0]; + X[3][1] = A[3][0]*B[0][1] + A[3][1]*B[1][1] + A[3][2]*B[2][1] + A[3][3]*B[3][1] + C[3][1]; + X[3][2] = A[3][0]*B[0][2] + A[3][1]*B[1][2] + A[3][2]*B[2][2] + A[3][3]*B[3][2] + C[3][2]; + X[3][3] = A[3][0]*B[0][3] + A[3][1]*B[1][3] + A[3][2]*B[2][3] + A[3][3]*B[3][3] + C[3][3];''' + ) + +def test_1xN_vecs(): + gl = glsl_code + for i in range(1,10): + A = Matrix(range(i)) + assert gl(A.transpose()) == gl(A) + assert gl(A,mat_transpose=True) == gl(A) + if i > 1: + if i <= 4: + assert gl(A) == 'vec%s(%s)' % (i,', '.join(str(s) for s in range(i))) + else: + assert gl(A) == 'float[%s](%s)' % (i,', '.join(str(s) for s in range(i))) + +def test_MxN_mats(): + generatedAssertions='def test_misc_mats():\n' + for i in range(1,6): + for j in range(1,6): + A = Matrix([[x + y*j for x in range(j)] for y in range(i)]) + gl = glsl_code(A) + glTransposed = glsl_code(A,mat_transpose=True) + generatedAssertions+=' mat = '+StrPrinter()._print(A)+'\n\n' + generatedAssertions+=' gl = \'\'\''+gl+'\'\'\'\n' + generatedAssertions+=' glTransposed = \'\'\''+glTransposed+'\'\'\'\n\n' + generatedAssertions+=' assert glsl_code(mat) == gl\n' + generatedAssertions+=' assert glsl_code(mat,mat_transpose=True) == glTransposed\n' + if i == 1 and j == 1: + assert gl == '0' + elif i <= 4 and j <= 4 and i>1 and j>1: + assert gl.startswith('mat%s' % j) + assert glTransposed.startswith('mat%s' % i) + elif i == 1 and j <= 4: + assert gl.startswith('vec') + elif j == 1 and i <= 4: + assert gl.startswith('vec') + elif i == 1: + assert gl.startswith('float[%s]('% j*i) + assert glTransposed.startswith('float[%s]('% j*i) + elif j == 1: + assert gl.startswith('float[%s]('% i*j) + assert glTransposed.startswith('float[%s]('% i*j) + else: + assert gl.startswith('float[%s](' % (i*j)) + assert glTransposed.startswith('float[%s](' % (i*j)) + glNested = glsl_code(A,mat_nested=True) + glNestedTransposed = glsl_code(A,mat_transpose=True,mat_nested=True) + assert glNested.startswith('float[%s][%s]' % (i,j)) + assert glNestedTransposed.startswith('float[%s][%s]' % (j,i)) + generatedAssertions+=' glNested = \'\'\''+glNested+'\'\'\'\n' + generatedAssertions+=' glNestedTransposed = \'\'\''+glNestedTransposed+'\'\'\'\n\n' + generatedAssertions+=' assert glsl_code(mat,mat_nested=True) == glNested\n' + generatedAssertions+=' assert glsl_code(mat,mat_nested=True,mat_transpose=True) == glNestedTransposed\n\n' + generateAssertions = False # set this to true to write bake these generated tests to a file + if generateAssertions: + gen = open('test_glsl_generated_matrices.py','w') + gen.write(generatedAssertions) + gen.close() + + +# these assertions were generated from the previous function +# glsl has complicated rules and this makes it easier to look over all the cases +def test_misc_mats(): + + mat = Matrix([[0]]) + + gl = '''0''' + glTransposed = '''0''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([[0, 1]]) + + gl = '''vec2(0, 1)''' + glTransposed = '''vec2(0, 1)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([[0, 1, 2]]) + + gl = '''vec3(0, 1, 2)''' + glTransposed = '''vec3(0, 1, 2)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([[0, 1, 2, 3]]) + + gl = '''vec4(0, 1, 2, 3)''' + glTransposed = '''vec4(0, 1, 2, 3)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([[0, 1, 2, 3, 4]]) + + gl = '''float[5](0, 1, 2, 3, 4)''' + glTransposed = '''float[5](0, 1, 2, 3, 4)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0], +[1]]) + + gl = '''vec2(0, 1)''' + glTransposed = '''vec2(0, 1)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1], +[2, 3]]) + + gl = '''mat2(0, 1, 2, 3)''' + glTransposed = '''mat2(0, 2, 1, 3)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1, 2], +[3, 4, 5]]) + + gl = '''mat3x2(0, 1, 2, 3, 4, 5)''' + glTransposed = '''mat2x3(0, 3, 1, 4, 2, 5)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1, 2, 3], +[4, 5, 6, 7]]) + + gl = '''mat4x2(0, 1, 2, 3, 4, 5, 6, 7)''' + glTransposed = '''mat2x4(0, 4, 1, 5, 2, 6, 3, 7)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1, 2, 3, 4], +[5, 6, 7, 8, 9]]) + + gl = '''float[10]( + 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9 +) /* a 2x5 matrix */''' + glTransposed = '''float[10]( + 0, 5, + 1, 6, + 2, 7, + 3, 8, + 4, 9 +) /* a 5x2 matrix */''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + glNested = '''float[2][5]( + float[](0, 1, 2, 3, 4), + float[](5, 6, 7, 8, 9) +)''' + glNestedTransposed = '''float[5][2]( + float[](0, 5), + float[](1, 6), + float[](2, 7), + float[](3, 8), + float[](4, 9) +)''' + + assert glsl_code(mat,mat_nested=True) == glNested + assert glsl_code(mat,mat_nested=True,mat_transpose=True) == glNestedTransposed + + mat = Matrix([ +[0], +[1], +[2]]) + + gl = '''vec3(0, 1, 2)''' + glTransposed = '''vec3(0, 1, 2)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1], +[2, 3], +[4, 5]]) + + gl = '''mat2x3(0, 1, 2, 3, 4, 5)''' + glTransposed = '''mat3x2(0, 2, 4, 1, 3, 5)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1, 2], +[3, 4, 5], +[6, 7, 8]]) + + gl = '''mat3(0, 1, 2, 3, 4, 5, 6, 7, 8)''' + glTransposed = '''mat3(0, 3, 6, 1, 4, 7, 2, 5, 8)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1, 2, 3], +[4, 5, 6, 7], +[8, 9, 10, 11]]) + + gl = '''mat4x3(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)''' + glTransposed = '''mat3x4(0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[ 0, 1, 2, 3, 4], +[ 5, 6, 7, 8, 9], +[10, 11, 12, 13, 14]]) + + gl = '''float[15]( + 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14 +) /* a 3x5 matrix */''' + glTransposed = '''float[15]( + 0, 5, 10, + 1, 6, 11, + 2, 7, 12, + 3, 8, 13, + 4, 9, 14 +) /* a 5x3 matrix */''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + glNested = '''float[3][5]( + float[]( 0, 1, 2, 3, 4), + float[]( 5, 6, 7, 8, 9), + float[](10, 11, 12, 13, 14) +)''' + glNestedTransposed = '''float[5][3]( + float[](0, 5, 10), + float[](1, 6, 11), + float[](2, 7, 12), + float[](3, 8, 13), + float[](4, 9, 14) +)''' + + assert glsl_code(mat,mat_nested=True) == glNested + assert glsl_code(mat,mat_nested=True,mat_transpose=True) == glNestedTransposed + + mat = Matrix([ +[0], +[1], +[2], +[3]]) + + gl = '''vec4(0, 1, 2, 3)''' + glTransposed = '''vec4(0, 1, 2, 3)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1], +[2, 3], +[4, 5], +[6, 7]]) + + gl = '''mat2x4(0, 1, 2, 3, 4, 5, 6, 7)''' + glTransposed = '''mat4x2(0, 2, 4, 6, 1, 3, 5, 7)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1, 2], +[3, 4, 5], +[6, 7, 8], +[9, 10, 11]]) + + gl = '''mat3x4(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)''' + glTransposed = '''mat4x3(0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[ 0, 1, 2, 3], +[ 4, 5, 6, 7], +[ 8, 9, 10, 11], +[12, 13, 14, 15]]) + + gl = '''mat4( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)''' + glTransposed = '''mat4(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[ 0, 1, 2, 3, 4], +[ 5, 6, 7, 8, 9], +[10, 11, 12, 13, 14], +[15, 16, 17, 18, 19]]) + + gl = '''float[20]( + 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19 +) /* a 4x5 matrix */''' + glTransposed = '''float[20]( + 0, 5, 10, 15, + 1, 6, 11, 16, + 2, 7, 12, 17, + 3, 8, 13, 18, + 4, 9, 14, 19 +) /* a 5x4 matrix */''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + glNested = '''float[4][5]( + float[]( 0, 1, 2, 3, 4), + float[]( 5, 6, 7, 8, 9), + float[](10, 11, 12, 13, 14), + float[](15, 16, 17, 18, 19) +)''' + glNestedTransposed = '''float[5][4]( + float[](0, 5, 10, 15), + float[](1, 6, 11, 16), + float[](2, 7, 12, 17), + float[](3, 8, 13, 18), + float[](4, 9, 14, 19) +)''' + + assert glsl_code(mat,mat_nested=True) == glNested + assert glsl_code(mat,mat_nested=True,mat_transpose=True) == glNestedTransposed + + mat = Matrix([ +[0], +[1], +[2], +[3], +[4]]) + + gl = '''float[5](0, 1, 2, 3, 4)''' + glTransposed = '''float[5](0, 1, 2, 3, 4)''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + + mat = Matrix([ +[0, 1], +[2, 3], +[4, 5], +[6, 7], +[8, 9]]) + + gl = '''float[10]( + 0, 1, + 2, 3, + 4, 5, + 6, 7, + 8, 9 +) /* a 5x2 matrix */''' + glTransposed = '''float[10]( + 0, 2, 4, 6, 8, + 1, 3, 5, 7, 9 +) /* a 2x5 matrix */''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + glNested = '''float[5][2]( + float[](0, 1), + float[](2, 3), + float[](4, 5), + float[](6, 7), + float[](8, 9) +)''' + glNestedTransposed = '''float[2][5]( + float[](0, 2, 4, 6, 8), + float[](1, 3, 5, 7, 9) +)''' + + assert glsl_code(mat,mat_nested=True) == glNested + assert glsl_code(mat,mat_nested=True,mat_transpose=True) == glNestedTransposed + + mat = Matrix([ +[ 0, 1, 2], +[ 3, 4, 5], +[ 6, 7, 8], +[ 9, 10, 11], +[12, 13, 14]]) + + gl = '''float[15]( + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + 9, 10, 11, + 12, 13, 14 +) /* a 5x3 matrix */''' + glTransposed = '''float[15]( + 0, 3, 6, 9, 12, + 1, 4, 7, 10, 13, + 2, 5, 8, 11, 14 +) /* a 3x5 matrix */''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + glNested = '''float[5][3]( + float[]( 0, 1, 2), + float[]( 3, 4, 5), + float[]( 6, 7, 8), + float[]( 9, 10, 11), + float[](12, 13, 14) +)''' + glNestedTransposed = '''float[3][5]( + float[](0, 3, 6, 9, 12), + float[](1, 4, 7, 10, 13), + float[](2, 5, 8, 11, 14) +)''' + + assert glsl_code(mat,mat_nested=True) == glNested + assert glsl_code(mat,mat_nested=True,mat_transpose=True) == glNestedTransposed + + mat = Matrix([ +[ 0, 1, 2, 3], +[ 4, 5, 6, 7], +[ 8, 9, 10, 11], +[12, 13, 14, 15], +[16, 17, 18, 19]]) + + gl = '''float[20]( + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15, + 16, 17, 18, 19 +) /* a 5x4 matrix */''' + glTransposed = '''float[20]( + 0, 4, 8, 12, 16, + 1, 5, 9, 13, 17, + 2, 6, 10, 14, 18, + 3, 7, 11, 15, 19 +) /* a 4x5 matrix */''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + glNested = '''float[5][4]( + float[]( 0, 1, 2, 3), + float[]( 4, 5, 6, 7), + float[]( 8, 9, 10, 11), + float[](12, 13, 14, 15), + float[](16, 17, 18, 19) +)''' + glNestedTransposed = '''float[4][5]( + float[](0, 4, 8, 12, 16), + float[](1, 5, 9, 13, 17), + float[](2, 6, 10, 14, 18), + float[](3, 7, 11, 15, 19) +)''' + + assert glsl_code(mat,mat_nested=True) == glNested + assert glsl_code(mat,mat_nested=True,mat_transpose=True) == glNestedTransposed + + mat = Matrix([ +[ 0, 1, 2, 3, 4], +[ 5, 6, 7, 8, 9], +[10, 11, 12, 13, 14], +[15, 16, 17, 18, 19], +[20, 21, 22, 23, 24]]) + + gl = '''float[25]( + 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24 +) /* a 5x5 matrix */''' + glTransposed = '''float[25]( + 0, 5, 10, 15, 20, + 1, 6, 11, 16, 21, + 2, 7, 12, 17, 22, + 3, 8, 13, 18, 23, + 4, 9, 14, 19, 24 +) /* a 5x5 matrix */''' + + assert glsl_code(mat) == gl + assert glsl_code(mat,mat_transpose=True) == glTransposed + glNested = '''float[5][5]( + float[]( 0, 1, 2, 3, 4), + float[]( 5, 6, 7, 8, 9), + float[](10, 11, 12, 13, 14), + float[](15, 16, 17, 18, 19), + float[](20, 21, 22, 23, 24) +)''' + glNestedTransposed = '''float[5][5]( + float[](0, 5, 10, 15, 20), + float[](1, 6, 11, 16, 21), + float[](2, 7, 12, 17, 22), + float[](3, 8, 13, 18, 23), + float[](4, 9, 14, 19, 24) +)''' + + assert glsl_code(mat,mat_nested=True) == glNested + assert glsl_code(mat,mat_nested=True,mat_transpose=True) == glNestedTransposed diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_gtk.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_gtk.py new file mode 100644 index 0000000000000000000000000000000000000000..5a595ab04d3a29d23e06ec12207bf917392aebce --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_gtk.py @@ -0,0 +1,18 @@ +from sympy.functions.elementary.trigonometric import sin +from sympy.printing.gtk import print_gtk +from sympy.testing.pytest import XFAIL, raises + +# this test fails if python-lxml isn't installed. We don't want to depend on +# anything with SymPy + + +@XFAIL +def test_1(): + from sympy.abc import x + print_gtk(x**2, start_viewer=False) + print_gtk(x**2 + sin(x)/4, start_viewer=False) + + +def test_settings(): + from sympy.abc import x + raises(TypeError, lambda: print_gtk(x, method="garbage")) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_jax.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_jax.py new file mode 100644 index 0000000000000000000000000000000000000000..365d87c5b91fdd49a8e46cfde9c2b5792c23a03c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_jax.py @@ -0,0 +1,370 @@ +from sympy.concrete.summations import Sum +from sympy.core.mod import Mod +from sympy.core.relational import (Equality, Unequality) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.matrices.expressions.blockmatrix import BlockMatrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import Identity +from sympy.utilities.lambdify import lambdify + +from sympy.abc import x, i, j, a, b, c, d +from sympy.core import Function, Pow, Symbol +from sympy.codegen.matrix_nodes import MatrixSolve +from sympy.codegen.numpy_nodes import logaddexp, logaddexp2 +from sympy.codegen.cfunctions import log1p, expm1, hypot, log10, exp2, log2, Sqrt +from sympy.tensor.array import Array +from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayAdd, \ + PermuteDims, ArrayDiagonal +from sympy.printing.numpy import JaxPrinter, _jax_known_constants, _jax_known_functions +from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array + +from sympy.testing.pytest import skip, raises +from sympy.external import import_module + +# Unlike NumPy which will aggressively promote operands to double precision, +# jax always uses single precision. Double precision in jax can be +# configured before the call to `import jax`, however this must be explicitly +# configured and is not fully supported. Thus, the tests here have been modified +# from the tests in test_numpy.py, only in the fact that they assert lambdify +# function accuracy to only single precision accuracy. +# https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#double-64bit-precision + +jax = import_module('jax') + +if jax: + deafult_float_info = jax.numpy.finfo(jax.numpy.array([]).dtype) + JAX_DEFAULT_EPSILON = deafult_float_info.eps + + +def test_jax_piecewise_regression(): + """ + NumPyPrinter needs to print Piecewise()'s choicelist as a list to avoid + breaking compatibility with numpy 1.8. This is not necessary in numpy 1.9+. + See gh-9747 and gh-9749 for details. + """ + printer = JaxPrinter() + p = Piecewise((1, x < 0), (0, True)) + assert printer.doprint(p) == \ + 'jax.numpy.select([jax.numpy.less(x, 0),True], [1,0], default=jax.numpy.nan)' + assert printer.module_imports == {'jax.numpy': {'select', 'less', 'nan'}} + + +def test_jax_logaddexp(): + lae = logaddexp(a, b) + assert JaxPrinter().doprint(lae) == 'jax.numpy.logaddexp(a, b)' + lae2 = logaddexp2(a, b) + assert JaxPrinter().doprint(lae2) == 'jax.numpy.logaddexp2(a, b)' + + +def test_jax_sum(): + if not jax: + skip("JAX not installed") + + s = Sum(x ** i, (i, a, b)) + f = lambdify((a, b, x), s, 'jax') + + a_, b_ = 0, 10 + x_ = jax.numpy.linspace(-1, +1, 10) + assert jax.numpy.allclose(f(a_, b_, x_), sum(x_ ** i_ for i_ in range(a_, b_ + 1))) + + s = Sum(i * x, (i, a, b)) + f = lambdify((a, b, x), s, 'jax') + + a_, b_ = 0, 10 + x_ = jax.numpy.linspace(-1, +1, 10) + assert jax.numpy.allclose(f(a_, b_, x_), sum(i_ * x_ for i_ in range(a_, b_ + 1))) + + +def test_jax_multiple_sums(): + if not jax: + skip("JAX not installed") + + s = Sum((x + j) * i, (i, a, b), (j, c, d)) + f = lambdify((a, b, c, d, x), s, 'jax') + + a_, b_ = 0, 10 + c_, d_ = 11, 21 + x_ = jax.numpy.linspace(-1, +1, 10) + assert jax.numpy.allclose(f(a_, b_, c_, d_, x_), + sum((x_ + j_) * i_ for i_ in range(a_, b_ + 1) for j_ in range(c_, d_ + 1))) + + +def test_jax_codegen_einsum(): + if not jax: + skip("JAX not installed") + + M = MatrixSymbol("M", 2, 2) + N = MatrixSymbol("N", 2, 2) + + cg = convert_matrix_to_array(M * N) + f = lambdify((M, N), cg, 'jax') + + ma = jax.numpy.array([[1, 2], [3, 4]]) + mb = jax.numpy.array([[1,-2], [-1, 3]]) + assert (f(ma, mb) == jax.numpy.matmul(ma, mb)).all() + + +def test_jax_codegen_extra(): + if not jax: + skip("JAX not installed") + + M = MatrixSymbol("M", 2, 2) + N = MatrixSymbol("N", 2, 2) + P = MatrixSymbol("P", 2, 2) + Q = MatrixSymbol("Q", 2, 2) + ma = jax.numpy.array([[1, 2], [3, 4]]) + mb = jax.numpy.array([[1,-2], [-1, 3]]) + mc = jax.numpy.array([[2, 0], [1, 2]]) + md = jax.numpy.array([[1,-1], [4, 7]]) + + cg = ArrayTensorProduct(M, N) + f = lambdify((M, N), cg, 'jax') + assert (f(ma, mb) == jax.numpy.einsum(ma, [0, 1], mb, [2, 3])).all() + + cg = ArrayAdd(M, N) + f = lambdify((M, N), cg, 'jax') + assert (f(ma, mb) == ma+mb).all() + + cg = ArrayAdd(M, N, P) + f = lambdify((M, N, P), cg, 'jax') + assert (f(ma, mb, mc) == ma+mb+mc).all() + + cg = ArrayAdd(M, N, P, Q) + f = lambdify((M, N, P, Q), cg, 'jax') + assert (f(ma, mb, mc, md) == ma+mb+mc+md).all() + + cg = PermuteDims(M, [1, 0]) + f = lambdify((M,), cg, 'jax') + assert (f(ma) == ma.T).all() + + cg = PermuteDims(ArrayTensorProduct(M, N), [1, 2, 3, 0]) + f = lambdify((M, N), cg, 'jax') + assert (f(ma, mb) == jax.numpy.transpose(jax.numpy.einsum(ma, [0, 1], mb, [2, 3]), (1, 2, 3, 0))).all() + + cg = ArrayDiagonal(ArrayTensorProduct(M, N), (1, 2)) + f = lambdify((M, N), cg, 'jax') + assert (f(ma, mb) == jax.numpy.diagonal(jax.numpy.einsum(ma, [0, 1], mb, [2, 3]), axis1=1, axis2=2)).all() + + +def test_jax_relational(): + if not jax: + skip("JAX not installed") + + e = Equality(x, 1) + + f = lambdify((x,), e, 'jax') + x_ = jax.numpy.array([0, 1, 2]) + assert jax.numpy.array_equal(f(x_), [False, True, False]) + + e = Unequality(x, 1) + + f = lambdify((x,), e, 'jax') + x_ = jax.numpy.array([0, 1, 2]) + assert jax.numpy.array_equal(f(x_), [True, False, True]) + + e = (x < 1) + + f = lambdify((x,), e, 'jax') + x_ = jax.numpy.array([0, 1, 2]) + assert jax.numpy.array_equal(f(x_), [True, False, False]) + + e = (x <= 1) + + f = lambdify((x,), e, 'jax') + x_ = jax.numpy.array([0, 1, 2]) + assert jax.numpy.array_equal(f(x_), [True, True, False]) + + e = (x > 1) + + f = lambdify((x,), e, 'jax') + x_ = jax.numpy.array([0, 1, 2]) + assert jax.numpy.array_equal(f(x_), [False, False, True]) + + e = (x >= 1) + + f = lambdify((x,), e, 'jax') + x_ = jax.numpy.array([0, 1, 2]) + assert jax.numpy.array_equal(f(x_), [False, True, True]) + + # Multi-condition expressions + e = (x >= 1) & (x < 2) + f = lambdify((x,), e, 'jax') + x_ = jax.numpy.array([0, 1, 2]) + assert jax.numpy.array_equal(f(x_), [False, True, False]) + + e = (x >= 1) | (x < 2) + f = lambdify((x,), e, 'jax') + x_ = jax.numpy.array([0, 1, 2]) + assert jax.numpy.array_equal(f(x_), [True, True, True]) + +def test_jax_mod(): + if not jax: + skip("JAX not installed") + + e = Mod(a, b) + f = lambdify((a, b), e, 'jax') + + a_ = jax.numpy.array([0, 1, 2, 3]) + b_ = 2 + assert jax.numpy.array_equal(f(a_, b_), [0, 1, 0, 1]) + + a_ = jax.numpy.array([0, 1, 2, 3]) + b_ = jax.numpy.array([2, 2, 2, 2]) + assert jax.numpy.array_equal(f(a_, b_), [0, 1, 0, 1]) + + a_ = jax.numpy.array([2, 3, 4, 5]) + b_ = jax.numpy.array([2, 3, 4, 5]) + assert jax.numpy.array_equal(f(a_, b_), [0, 0, 0, 0]) + + +def test_jax_pow(): + if not jax: + skip('JAX not installed') + + expr = Pow(2, -1, evaluate=False) + f = lambdify([], expr, 'jax') + assert f() == 0.5 + + +def test_jax_expm1(): + if not jax: + skip("JAX not installed") + + f = lambdify((a,), expm1(a), 'jax') + assert abs(f(1e-10) - 1e-10 - 5e-21) <= 1e-10 * JAX_DEFAULT_EPSILON + + +def test_jax_log1p(): + if not jax: + skip("JAX not installed") + + f = lambdify((a,), log1p(a), 'jax') + assert abs(f(1e-99) - 1e-99) <= 1e-99 * JAX_DEFAULT_EPSILON + +def test_jax_hypot(): + if not jax: + skip("JAX not installed") + assert abs(lambdify((a, b), hypot(a, b), 'jax')(3, 4) - 5) <= JAX_DEFAULT_EPSILON + +def test_jax_log10(): + if not jax: + skip("JAX not installed") + + assert abs(lambdify((a,), log10(a), 'jax')(100) - 2) <= JAX_DEFAULT_EPSILON + + +def test_jax_exp2(): + if not jax: + skip("JAX not installed") + assert abs(lambdify((a,), exp2(a), 'jax')(5) - 32) <= JAX_DEFAULT_EPSILON + + +def test_jax_log2(): + if not jax: + skip("JAX not installed") + assert abs(lambdify((a,), log2(a), 'jax')(256) - 8) <= JAX_DEFAULT_EPSILON + + +def test_jax_Sqrt(): + if not jax: + skip("JAX not installed") + assert abs(lambdify((a,), Sqrt(a), 'jax')(4) - 2) <= JAX_DEFAULT_EPSILON + + +def test_jax_sqrt(): + if not jax: + skip("JAX not installed") + assert abs(lambdify((a,), sqrt(a), 'jax')(4) - 2) <= JAX_DEFAULT_EPSILON + + +def test_jax_matsolve(): + if not jax: + skip("JAX not installed") + + M = MatrixSymbol("M", 3, 3) + x = MatrixSymbol("x", 3, 1) + + expr = M**(-1) * x + x + matsolve_expr = MatrixSolve(M, x) + x + + f = lambdify((M, x), expr, 'jax') + f_matsolve = lambdify((M, x), matsolve_expr, 'jax') + + m0 = jax.numpy.array([[1, 2, 3], [3, 2, 5], [5, 6, 7]]) + assert jax.numpy.linalg.matrix_rank(m0) == 3 + + x0 = jax.numpy.array([3, 4, 5]) + + assert jax.numpy.allclose(f_matsolve(m0, x0), f(m0, x0)) + + +def test_16857(): + if not jax: + skip("JAX not installed") + + a_1 = MatrixSymbol('a_1', 10, 3) + a_2 = MatrixSymbol('a_2', 10, 3) + a_3 = MatrixSymbol('a_3', 10, 3) + a_4 = MatrixSymbol('a_4', 10, 3) + A = BlockMatrix([[a_1, a_2], [a_3, a_4]]) + assert A.shape == (20, 6) + + printer = JaxPrinter() + assert printer.doprint(A) == 'jax.numpy.block([[a_1, a_2], [a_3, a_4]])' + + +def test_issue_17006(): + if not jax: + skip("JAX not installed") + + M = MatrixSymbol("M", 2, 2) + + f = lambdify(M, M + Identity(2), 'jax') + ma = jax.numpy.array([[1, 2], [3, 4]]) + mr = jax.numpy.array([[2, 2], [3, 5]]) + + assert (f(ma) == mr).all() + + from sympy.core.symbol import symbols + n = symbols('n', integer=True) + N = MatrixSymbol("M", n, n) + raises(NotImplementedError, lambda: lambdify(N, N + Identity(n), 'jax')) + + +def test_jax_array(): + assert JaxPrinter().doprint(Array(((1, 2), (3, 5)))) == 'jax.numpy.array([[1, 2], [3, 5]])' + assert JaxPrinter().doprint(Array((1, 2))) == 'jax.numpy.array([1, 2])' + + +def test_jax_known_funcs_consts(): + assert _jax_known_constants['NaN'] == 'jax.numpy.nan' + assert _jax_known_constants['EulerGamma'] == 'jax.numpy.euler_gamma' + + assert _jax_known_functions['acos'] == 'jax.numpy.arccos' + assert _jax_known_functions['log'] == 'jax.numpy.log' + + +def test_jax_print_methods(): + prntr = JaxPrinter() + assert hasattr(prntr, '_print_acos') + assert hasattr(prntr, '_print_log') + + +def test_jax_printmethod(): + printer = JaxPrinter() + assert hasattr(printer, 'printmethod') + assert printer.printmethod == '_jaxcode' + + +def test_jax_custom_print_method(): + + class expm1(Function): + + def _jaxcode(self, printer): + x, = self.args + function = f'expm1({printer._print(x)})' + return printer._module_format(printer._module + '.' + function) + + printer = JaxPrinter() + assert printer.doprint(expm1(Symbol('x'))) == 'jax.numpy.expm1(x)' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_jscode.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_jscode.py new file mode 100644 index 0000000000000000000000000000000000000000..9199a8e0d62e87f2e964cb1712726a21c894fd20 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_jscode.py @@ -0,0 +1,396 @@ +from sympy.core import (pi, oo, symbols, Rational, Integer, GoldenRatio, + EulerGamma, Catalan, Lambda, Dummy, S, Eq, Ne, Le, + Lt, Gt, Ge, Mod) +from sympy.functions import (Piecewise, sin, cos, Abs, exp, ceiling, sqrt, + sinh, cosh, tanh, asin, acos, acosh, Max, Min) +from sympy.testing.pytest import raises +from sympy.printing.jscode import JavascriptCodePrinter +from sympy.utilities.lambdify import implemented_function +from sympy.tensor import IndexedBase, Idx +from sympy.matrices import Matrix, MatrixSymbol + +from sympy.printing.jscode import jscode + +x, y, z = symbols('x,y,z') + + +def test_printmethod(): + assert jscode(Abs(x)) == "Math.abs(x)" + + +def test_jscode_sqrt(): + assert jscode(sqrt(x)) == "Math.sqrt(x)" + assert jscode(x**0.5) == "Math.sqrt(x)" + assert jscode(x**(S.One/3)) == "Math.cbrt(x)" + + +def test_jscode_Pow(): + g = implemented_function('g', Lambda(x, 2*x)) + assert jscode(x**3) == "Math.pow(x, 3)" + assert jscode(x**(y**3)) == "Math.pow(x, Math.pow(y, 3))" + assert jscode(1/(g(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + "Math.pow(3.5*2*x, -x + Math.pow(y, x))/(Math.pow(x, 2) + y)" + assert jscode(x**-1.0) == '1/x' + + +def test_jscode_constants_mathh(): + assert jscode(exp(1)) == "Math.E" + assert jscode(pi) == "Math.PI" + assert jscode(oo) == "Number.POSITIVE_INFINITY" + assert jscode(-oo) == "Number.NEGATIVE_INFINITY" + + +def test_jscode_constants_other(): + assert jscode( + 2*GoldenRatio) == "var GoldenRatio = %s;\n2*GoldenRatio" % GoldenRatio.evalf(17) + assert jscode(2*Catalan) == "var Catalan = %s;\n2*Catalan" % Catalan.evalf(17) + assert jscode( + 2*EulerGamma) == "var EulerGamma = %s;\n2*EulerGamma" % EulerGamma.evalf(17) + + +def test_jscode_Rational(): + assert jscode(Rational(3, 7)) == "3/7" + assert jscode(Rational(18, 9)) == "2" + assert jscode(Rational(3, -7)) == "-3/7" + assert jscode(Rational(-3, -7)) == "3/7" + + +def test_Relational(): + assert jscode(Eq(x, y)) == "x == y" + assert jscode(Ne(x, y)) == "x != y" + assert jscode(Le(x, y)) == "x <= y" + assert jscode(Lt(x, y)) == "x < y" + assert jscode(Gt(x, y)) == "x > y" + assert jscode(Ge(x, y)) == "x >= y" + + +def test_Mod(): + assert jscode(Mod(x, y)) == '((x % y) + y) % y' + assert jscode(Mod(x, x + y)) == '((x % (x + y)) + (x + y)) % (x + y)' + p1, p2 = symbols('p1 p2', positive=True) + assert jscode(Mod(p1, p2)) == 'p1 % p2' + assert jscode(Mod(p1, p2 + 3)) == 'p1 % (p2 + 3)' + assert jscode(Mod(-3, -7, evaluate=False)) == '(-3) % (-7)' + assert jscode(-Mod(p1, p2)) == '-(p1 % p2)' + assert jscode(x*Mod(p1, p2)) == 'x*(p1 % p2)' + + +def test_jscode_Integer(): + assert jscode(Integer(67)) == "67" + assert jscode(Integer(-1)) == "-1" + + +def test_jscode_functions(): + assert jscode(sin(x) ** cos(x)) == "Math.pow(Math.sin(x), Math.cos(x))" + assert jscode(sinh(x) * cosh(x)) == "Math.sinh(x)*Math.cosh(x)" + assert jscode(Max(x, y) + Min(x, y)) == "Math.max(x, y) + Math.min(x, y)" + assert jscode(tanh(x)*acosh(y)) == "Math.tanh(x)*Math.acosh(y)" + assert jscode(asin(x)-acos(y)) == "-Math.acos(y) + Math.asin(x)" + + +def test_jscode_inline_function(): + x = symbols('x') + g = implemented_function('g', Lambda(x, 2*x)) + assert jscode(g(x)) == "2*x" + g = implemented_function('g', Lambda(x, 2*x/Catalan)) + assert jscode(g(x)) == "var Catalan = %s;\n2*x/Catalan" % Catalan.evalf(17) + A = IndexedBase('A') + i = Idx('i', symbols('n', integer=True)) + g = implemented_function('g', Lambda(x, x*(1 + x)*(2 + x))) + assert jscode(g(A[i]), assign_to=A[i]) == ( + "for (var i=0; i 1), (sin(x), x > 0)) + raises(ValueError, lambda: jscode(expr)) + + +def test_jscode_Piecewise_deep(): + p = jscode(2*Piecewise((x, x < 1), (x**2, True))) + s = \ +"""\ +2*((x < 1) ? ( + x +) +: ( + Math.pow(x, 2) +))\ +""" + assert p == s + + +def test_jscode_settings(): + raises(TypeError, lambda: jscode(sin(x), method="garbage")) + + +def test_jscode_Indexed(): + n, m, o = symbols('n m o', integer=True) + i, j, k = Idx('i', n), Idx('j', m), Idx('k', o) + p = JavascriptCodePrinter() + p._not_c = set() + + x = IndexedBase('x')[j] + assert p._print_Indexed(x) == 'x[j]' + A = IndexedBase('A')[i, j] + assert p._print_Indexed(A) == 'A[%s]' % (m*i+j) + B = IndexedBase('B')[i, j, k] + assert p._print_Indexed(B) == 'B[%s]' % (i*o*m+j*o+k) + + assert p._not_c == set() + + +def test_jscode_loops_matrix_vector(): + n, m = symbols('n m', integer=True) + A = IndexedBase('A') + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + + s = ( + 'for (var i=0; i0), (y, True)), sin(z)]) + A = MatrixSymbol('A', 3, 1) + assert jscode(mat, A) == ( + "A[0] = x*y;\n" + "if (y > 0) {\n" + " A[1] = x + 2;\n" + "}\n" + "else {\n" + " A[1] = y;\n" + "}\n" + "A[2] = Math.sin(z);") + # Test using MatrixElements in expressions + expr = Piecewise((2*A[2, 0], x > 0), (A[2, 0], True)) + sin(A[1, 0]) + A[0, 0] + assert jscode(expr) == ( + "((x > 0) ? (\n" + " 2*A[2]\n" + ")\n" + ": (\n" + " A[2]\n" + ")) + Math.sin(A[1]) + A[0]") + # Test using MatrixElements in a Matrix + q = MatrixSymbol('q', 5, 1) + M = MatrixSymbol('M', 3, 3) + m = Matrix([[sin(q[1,0]), 0, cos(q[2,0])], + [q[1,0] + q[2,0], q[3, 0], 5], + [2*q[4, 0]/q[1,0], sqrt(q[0,0]) + 4, 0]]) + assert jscode(m, M) == ( + "M[0] = Math.sin(q[1]);\n" + "M[1] = 0;\n" + "M[2] = Math.cos(q[2]);\n" + "M[3] = q[1] + q[2];\n" + "M[4] = q[3];\n" + "M[5] = 5;\n" + "M[6] = 2*q[4]/q[1];\n" + "M[7] = Math.sqrt(q[0]) + 4;\n" + "M[8] = 0;") + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert(jscode(A[0, 0]) == "A[0]") + assert(jscode(3 * A[0, 0]) == "3*A[0]") + + F = C[0, 0].subs(C, A - B) + assert(jscode(F) == "(A - B)[0]") diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_julia.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_julia.py new file mode 100644 index 0000000000000000000000000000000000000000..b19c7b4fd4f21d8402ca2f577605322b3ec10f5b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_julia.py @@ -0,0 +1,390 @@ +from sympy.core import (S, pi, oo, symbols, Function, Rational, Integer, + Tuple, Symbol, Eq, Ne, Le, Lt, Gt, Ge) +from sympy.core import EulerGamma, GoldenRatio, Catalan, Lambda, Mul, Pow +from sympy.functions import Piecewise, sqrt, ceiling, exp, sin, cos, sinc +from sympy.testing.pytest import raises +from sympy.utilities.lambdify import implemented_function +from sympy.matrices import (eye, Matrix, MatrixSymbol, Identity, + HadamardProduct, SparseMatrix) +from sympy.functions.special.bessel import (jn, yn, besselj, bessely, besseli, + besselk, hankel1, hankel2, airyai, + airybi, airyaiprime, airybiprime) +from sympy.testing.pytest import XFAIL + +from sympy.printing.julia import julia_code + +x, y, z = symbols('x,y,z') + + +def test_Integer(): + assert julia_code(Integer(67)) == "67" + assert julia_code(Integer(-1)) == "-1" + + +def test_Rational(): + assert julia_code(Rational(3, 7)) == "3 // 7" + assert julia_code(Rational(18, 9)) == "2" + assert julia_code(Rational(3, -7)) == "-3 // 7" + assert julia_code(Rational(-3, -7)) == "3 // 7" + assert julia_code(x + Rational(3, 7)) == "x + 3 // 7" + assert julia_code(Rational(3, 7)*x) == "(3 // 7) * x" + + +def test_Relational(): + assert julia_code(Eq(x, y)) == "x == y" + assert julia_code(Ne(x, y)) == "x != y" + assert julia_code(Le(x, y)) == "x <= y" + assert julia_code(Lt(x, y)) == "x < y" + assert julia_code(Gt(x, y)) == "x > y" + assert julia_code(Ge(x, y)) == "x >= y" + + +def test_Function(): + assert julia_code(sin(x) ** cos(x)) == "sin(x) .^ cos(x)" + assert julia_code(abs(x)) == "abs(x)" + assert julia_code(ceiling(x)) == "ceil(x)" + + +def test_Pow(): + assert julia_code(x**3) == "x .^ 3" + assert julia_code(x**(y**3)) == "x .^ (y .^ 3)" + assert julia_code(x**Rational(2, 3)) == 'x .^ (2 // 3)' + g = implemented_function('g', Lambda(x, 2*x)) + assert julia_code(1/(g(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + "(3.5 * 2 * x) .^ (-x + y .^ x) ./ (x .^ 2 + y)" + # For issue 14160 + assert julia_code(Mul(-2, x, Pow(Mul(y,y,evaluate=False), -1, evaluate=False), + evaluate=False)) == '-2 * x ./ (y .* y)' + + +def test_basic_ops(): + assert julia_code(x*y) == "x .* y" + assert julia_code(x + y) == "x + y" + assert julia_code(x - y) == "x - y" + assert julia_code(-x) == "-x" + + +def test_1_over_x_and_sqrt(): + # 1.0 and 0.5 would do something different in regular StrPrinter, + # but these are exact in IEEE floating point so no different here. + assert julia_code(1/x) == '1 ./ x' + assert julia_code(x**-1) == julia_code(x**-1.0) == '1 ./ x' + assert julia_code(1/sqrt(x)) == '1 ./ sqrt(x)' + assert julia_code(x**-S.Half) == julia_code(x**-0.5) == '1 ./ sqrt(x)' + assert julia_code(sqrt(x)) == 'sqrt(x)' + assert julia_code(x**S.Half) == julia_code(x**0.5) == 'sqrt(x)' + assert julia_code(1/pi) == '1 / pi' + assert julia_code(pi**-1) == julia_code(pi**-1.0) == '1 / pi' + assert julia_code(pi**-0.5) == '1 / sqrt(pi)' + + +def test_mix_number_mult_symbols(): + assert julia_code(3*x) == "3 * x" + assert julia_code(pi*x) == "pi * x" + assert julia_code(3/x) == "3 ./ x" + assert julia_code(pi/x) == "pi ./ x" + assert julia_code(x/3) == "x / 3" + assert julia_code(x/pi) == "x / pi" + assert julia_code(x*y) == "x .* y" + assert julia_code(3*x*y) == "3 * x .* y" + assert julia_code(3*pi*x*y) == "3 * pi * x .* y" + assert julia_code(x/y) == "x ./ y" + assert julia_code(3*x/y) == "3 * x ./ y" + assert julia_code(x*y/z) == "x .* y ./ z" + assert julia_code(x/y*z) == "x .* z ./ y" + assert julia_code(1/x/y) == "1 ./ (x .* y)" + assert julia_code(2*pi*x/y/z) == "2 * pi * x ./ (y .* z)" + assert julia_code(3*pi/x) == "3 * pi ./ x" + assert julia_code(S(3)/5) == "3 // 5" + assert julia_code(S(3)/5*x) == "(3 // 5) * x" + assert julia_code(x/y/z) == "x ./ (y .* z)" + assert julia_code((x+y)/z) == "(x + y) ./ z" + assert julia_code((x+y)/(z+x)) == "(x + y) ./ (x + z)" + assert julia_code((x+y)/EulerGamma) == "(x + y) / eulergamma" + assert julia_code(x/3/pi) == "x / (3 * pi)" + assert julia_code(S(3)/5*x*y/pi) == "(3 // 5) * x .* y / pi" + + +def test_mix_number_pow_symbols(): + assert julia_code(pi**3) == 'pi ^ 3' + assert julia_code(x**2) == 'x .^ 2' + assert julia_code(x**(pi**3)) == 'x .^ (pi ^ 3)' + assert julia_code(x**y) == 'x .^ y' + assert julia_code(x**(y**z)) == 'x .^ (y .^ z)' + assert julia_code((x**y)**z) == '(x .^ y) .^ z' + + +def test_imag(): + I = S('I') + assert julia_code(I) == "im" + assert julia_code(5*I) == "5im" + assert julia_code((S(3)/2)*I) == "(3 // 2) * im" + assert julia_code(3+4*I) == "3 + 4im" + + +def test_constants(): + assert julia_code(pi) == "pi" + assert julia_code(oo) == "Inf" + assert julia_code(-oo) == "-Inf" + assert julia_code(S.NegativeInfinity) == "-Inf" + assert julia_code(S.NaN) == "NaN" + assert julia_code(S.Exp1) == "e" + assert julia_code(exp(1)) == "e" + + +def test_constants_other(): + assert julia_code(2*GoldenRatio) == "2 * golden" + assert julia_code(2*Catalan) == "2 * catalan" + assert julia_code(2*EulerGamma) == "2 * eulergamma" + + +def test_boolean(): + assert julia_code(x & y) == "x && y" + assert julia_code(x | y) == "x || y" + assert julia_code(~x) == "!x" + assert julia_code(x & y & z) == "x && y && z" + assert julia_code(x | y | z) == "x || y || z" + assert julia_code((x & y) | z) == "z || x && y" + assert julia_code((x | y) & z) == "z && (x || y)" + +def test_sinc(): + assert julia_code(sinc(x)) == 'sinc(x / pi)' + assert julia_code(sinc(x + 3)) == 'sinc((x + 3) / pi)' + assert julia_code(sinc(pi * (x + 3))) == 'sinc(x + 3)' + +def test_Matrices(): + assert julia_code(Matrix(1, 1, [10])) == "[10]" + A = Matrix([[1, sin(x/2), abs(x)], + [0, 1, pi], + [0, exp(1), ceiling(x)]]) + expected = ("[1 sin(x / 2) abs(x);\n" + "0 1 pi;\n" + "0 e ceil(x)]") + assert julia_code(A) == expected + # row and columns + assert julia_code(A[:,0]) == "[1, 0, 0]" + assert julia_code(A[0,:]) == "[1 sin(x / 2) abs(x)]" + # empty matrices + assert julia_code(Matrix(0, 0, [])) == 'zeros(0, 0)' + assert julia_code(Matrix(0, 3, [])) == 'zeros(0, 3)' + # annoying to read but correct + assert julia_code(Matrix([[x, x - y, -y]])) == "[x x - y -y]" + + +def test_vector_entries_hadamard(): + # For a row or column, user might to use the other dimension + A = Matrix([[1, sin(2/x), 3*pi/x/5]]) + assert julia_code(A) == "[1 sin(2 ./ x) (3 // 5) * pi ./ x]" + assert julia_code(A.T) == "[1, sin(2 ./ x), (3 // 5) * pi ./ x]" + + +@XFAIL +def test_Matrices_entries_not_hadamard(): + # For Matrix with col >= 2, row >= 2, they need to be scalars + # FIXME: is it worth worrying about this? Its not wrong, just + # leave it user's responsibility to put scalar data for x. + A = Matrix([[1, sin(2/x), 3*pi/x/5], [1, 2, x*y]]) + expected = ("[1 sin(2/x) 3*pi/(5*x);\n" + "1 2 x*y]") # <- we give x.*y + assert julia_code(A) == expected + + +def test_MatrixSymbol(): + n = Symbol('n', integer=True) + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', n, n) + assert julia_code(A*B) == "A * B" + assert julia_code(B*A) == "B * A" + assert julia_code(2*A*B) == "2 * A * B" + assert julia_code(B*2*A) == "2 * B * A" + assert julia_code(A*(B + 3*Identity(n))) == "A * (3 * eye(n) + B)" + assert julia_code(A**(x**2)) == "A ^ (x .^ 2)" + assert julia_code(A**3) == "A ^ 3" + assert julia_code(A**S.Half) == "A ^ (1 // 2)" + + +def test_special_matrices(): + assert julia_code(6*Identity(3)) == "6 * eye(3)" + + +def test_containers(): + assert julia_code([1, 2, 3, [4, 5, [6, 7]], 8, [9, 10], 11]) == \ + "Any[1, 2, 3, Any[4, 5, Any[6, 7]], 8, Any[9, 10], 11]" + assert julia_code((1, 2, (3, 4))) == "(1, 2, (3, 4))" + assert julia_code([1]) == "Any[1]" + assert julia_code((1,)) == "(1,)" + assert julia_code(Tuple(*[1, 2, 3])) == "(1, 2, 3)" + assert julia_code((1, x*y, (3, x**2))) == "(1, x .* y, (3, x .^ 2))" + # scalar, matrix, empty matrix and empty list + assert julia_code((1, eye(3), Matrix(0, 0, []), [])) == "(1, [1 0 0;\n0 1 0;\n0 0 1], zeros(0, 0), Any[])" + + +def test_julia_noninline(): + source = julia_code((x+y)/Catalan, assign_to='me', inline=False) + expected = ( + "const Catalan = %s\n" + "me = (x + y) / Catalan" + ) % Catalan.evalf(17) + assert source == expected + + +def test_julia_piecewise(): + expr = Piecewise((x, x < 1), (x**2, True)) + assert julia_code(expr) == "((x < 1) ? (x) : (x .^ 2))" + assert julia_code(expr, assign_to="r") == ( + "r = ((x < 1) ? (x) : (x .^ 2))") + assert julia_code(expr, assign_to="r", inline=False) == ( + "if (x < 1)\n" + " r = x\n" + "else\n" + " r = x .^ 2\n" + "end") + expr = Piecewise((x**2, x < 1), (x**3, x < 2), (x**4, x < 3), (x**5, True)) + expected = ("((x < 1) ? (x .^ 2) :\n" + "(x < 2) ? (x .^ 3) :\n" + "(x < 3) ? (x .^ 4) : (x .^ 5))") + assert julia_code(expr) == expected + assert julia_code(expr, assign_to="r") == "r = " + expected + assert julia_code(expr, assign_to="r", inline=False) == ( + "if (x < 1)\n" + " r = x .^ 2\n" + "elseif (x < 2)\n" + " r = x .^ 3\n" + "elseif (x < 3)\n" + " r = x .^ 4\n" + "else\n" + " r = x .^ 5\n" + "end") + # Check that Piecewise without a True (default) condition error + expr = Piecewise((x, x < 1), (x**2, x > 1), (sin(x), x > 0)) + raises(ValueError, lambda: julia_code(expr)) + + +def test_julia_piecewise_times_const(): + pw = Piecewise((x, x < 1), (x**2, True)) + assert julia_code(2*pw) == "2 * ((x < 1) ? (x) : (x .^ 2))" + assert julia_code(pw/x) == "((x < 1) ? (x) : (x .^ 2)) ./ x" + assert julia_code(pw/(x*y)) == "((x < 1) ? (x) : (x .^ 2)) ./ (x .* y)" + assert julia_code(pw/3) == "((x < 1) ? (x) : (x .^ 2)) / 3" + + +def test_julia_matrix_assign_to(): + A = Matrix([[1, 2, 3]]) + assert julia_code(A, assign_to='a') == "a = [1 2 3]" + A = Matrix([[1, 2], [3, 4]]) + assert julia_code(A, assign_to='A') == "A = [1 2;\n3 4]" + + +def test_julia_matrix_assign_to_more(): + # assigning to Symbol or MatrixSymbol requires lhs/rhs match + A = Matrix([[1, 2, 3]]) + B = MatrixSymbol('B', 1, 3) + C = MatrixSymbol('C', 2, 3) + assert julia_code(A, assign_to=B) == "B = [1 2 3]" + raises(ValueError, lambda: julia_code(A, assign_to=x)) + raises(ValueError, lambda: julia_code(A, assign_to=C)) + + +def test_julia_matrix_1x1(): + A = Matrix([[3]]) + B = MatrixSymbol('B', 1, 1) + C = MatrixSymbol('C', 1, 2) + assert julia_code(A, assign_to=B) == "B = [3]" + # FIXME? + #assert julia_code(A, assign_to=x) == "x = [3]" + raises(ValueError, lambda: julia_code(A, assign_to=C)) + + +def test_julia_matrix_elements(): + A = Matrix([[x, 2, x*y]]) + assert julia_code(A[0, 0]**2 + A[0, 1] + A[0, 2]) == "x .^ 2 + x .* y + 2" + A = MatrixSymbol('AA', 1, 3) + assert julia_code(A) == "AA" + assert julia_code(A[0, 0]**2 + sin(A[0,1]) + A[0,2]) == \ + "sin(AA[1,2]) + AA[1,1] .^ 2 + AA[1,3]" + assert julia_code(sum(A)) == "AA[1,1] + AA[1,2] + AA[1,3]" + + +def test_julia_boolean(): + assert julia_code(True) == "true" + assert julia_code(S.true) == "true" + assert julia_code(False) == "false" + assert julia_code(S.false) == "false" + + +def test_julia_not_supported(): + with raises(NotImplementedError): + julia_code(S.ComplexInfinity) + + f = Function('f') + assert julia_code(f(x).diff(x), strict=False) == ( + "# Not supported in Julia:\n" + "# Derivative\n" + "Derivative(f(x), x)" + ) + + +def test_trick_indent_with_end_else_words(): + # words starting with "end" or "else" do not confuse the indenter + t1 = S('endless') + t2 = S('elsewhere') + pw = Piecewise((t1, x < 0), (t2, x <= 1), (1, True)) + assert julia_code(pw, inline=False) == ( + "if (x < 0)\n" + " endless\n" + "elseif (x <= 1)\n" + " elsewhere\n" + "else\n" + " 1\n" + "end") + + +def test_haramard(): + A = MatrixSymbol('A', 3, 3) + B = MatrixSymbol('B', 3, 3) + v = MatrixSymbol('v', 3, 1) + h = MatrixSymbol('h', 1, 3) + C = HadamardProduct(A, B) + assert julia_code(C) == "A .* B" + assert julia_code(C*v) == "(A .* B) * v" + assert julia_code(h*C*v) == "h * (A .* B) * v" + assert julia_code(C*A) == "(A .* B) * A" + # mixing Hadamard and scalar strange b/c we vectorize scalars + assert julia_code(C*x*y) == "(x .* y) * (A .* B)" + + +def test_sparse(): + M = SparseMatrix(5, 6, {}) + M[2, 2] = 10 + M[1, 2] = 20 + M[1, 3] = 22 + M[0, 3] = 30 + M[3, 0] = x*y + assert julia_code(M) == ( + "sparse([4, 2, 3, 1, 2], [1, 3, 3, 4, 4], [x .* y, 20, 10, 30, 22], 5, 6)" + ) + + +def test_specfun(): + n = Symbol('n') + for f in [besselj, bessely, besseli, besselk]: + assert julia_code(f(n, x)) == f.__name__ + '(n, x)' + for f in [airyai, airyaiprime, airybi, airybiprime]: + assert julia_code(f(x)) == f.__name__ + '(x)' + assert julia_code(hankel1(n, x)) == 'hankelh1(n, x)' + assert julia_code(hankel2(n, x)) == 'hankelh2(n, x)' + assert julia_code(jn(n, x)) == 'sqrt(2) * sqrt(pi) * sqrt(1 ./ x) .* besselj(n + 1 // 2, x) / 2' + assert julia_code(yn(n, x)) == 'sqrt(2) * sqrt(pi) * sqrt(1 ./ x) .* bessely(n + 1 // 2, x) / 2' + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert(julia_code(A[0, 0]) == "A[1,1]") + assert(julia_code(3 * A[0, 0]) == "3 * A[1,1]") + + F = C[0, 0].subs(C, A - B) + assert(julia_code(F) == "(A - B)[1,1]") diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_lambdarepr.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_lambdarepr.py new file mode 100644 index 0000000000000000000000000000000000000000..94e09ada7a9ce7d01667edd8fc6ec35ebfbb9639 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_lambdarepr.py @@ -0,0 +1,246 @@ +from sympy.concrete.summations import Sum +from sympy.core.expr import Expr +from sympy.core.symbol import symbols +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import sin +from sympy.matrices.dense import MutableDenseMatrix as Matrix +from sympy.sets.sets import Interval +from sympy.utilities.lambdify import lambdify +from sympy.testing.pytest import raises + +from sympy.printing.tensorflow import TensorflowPrinter +from sympy.printing.lambdarepr import lambdarepr, LambdaPrinter, NumExprPrinter + + +x, y, z = symbols("x,y,z") +i, a, b = symbols("i,a,b") +j, c, d = symbols("j,c,d") + + +def test_basic(): + assert lambdarepr(x*y) == "x*y" + assert lambdarepr(x + y) in ["y + x", "x + y"] + assert lambdarepr(x**y) == "x**y" + + +def test_matrix(): + # Test printing a Matrix that has an element that is printed differently + # with the LambdaPrinter than with the StrPrinter. + e = x % 2 + assert lambdarepr(e) != str(e) + assert lambdarepr(Matrix([e])) == 'ImmutableDenseMatrix([[x % 2]])' + + +def test_piecewise(): + # In each case, test eval() the lambdarepr() to make sure there are a + # correct number of parentheses. It will give a SyntaxError if there aren't. + + h = "lambda x: " + + p = Piecewise((x, x < 0)) + l = lambdarepr(p) + eval(h + l) + assert l == "((x) if (x < 0) else None)" + + p = Piecewise( + (1, x < 1), + (2, x < 2), + (0, True) + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((1) if (x < 1) else (2) if (x < 2) else (0))" + + p = Piecewise( + (1, x < 1), + (2, x < 2), + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((1) if (x < 1) else (2) if (x < 2) else None)" + + p = Piecewise( + (x, x < 1), + (x**2, Interval(3, 4, True, False).contains(x)), + (0, True), + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((x) if (x < 1) else (x**2) if (((x <= 4)) and ((x > 3))) else (0))" + + p = Piecewise( + (x**2, x < 0), + (x, x < 1), + (2 - x, x >= 1), + (0, True), evaluate=False + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((x**2) if (x < 0) else (x) if (x < 1)"\ + " else (2 - x) if (x >= 1) else (0))" + + p = Piecewise( + (x**2, x < 0), + (x, x < 1), + (2 - x, x >= 1), evaluate=False + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((x**2) if (x < 0) else (x) if (x < 1)"\ + " else (2 - x) if (x >= 1) else None)" + + p = Piecewise( + (1, x >= 1), + (2, x >= 2), + (3, x >= 3), + (4, x >= 4), + (5, x >= 5), + (6, True) + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((1) if (x >= 1) else (2) if (x >= 2) else (3) if (x >= 3)"\ + " else (4) if (x >= 4) else (5) if (x >= 5) else (6))" + + p = Piecewise( + (1, x <= 1), + (2, x <= 2), + (3, x <= 3), + (4, x <= 4), + (5, x <= 5), + (6, True) + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((1) if (x <= 1) else (2) if (x <= 2) else (3) if (x <= 3)"\ + " else (4) if (x <= 4) else (5) if (x <= 5) else (6))" + + p = Piecewise( + (1, x > 1), + (2, x > 2), + (3, x > 3), + (4, x > 4), + (5, x > 5), + (6, True) + ) + l = lambdarepr(p) + eval(h + l) + assert l =="((1) if (x > 1) else (2) if (x > 2) else (3) if (x > 3)"\ + " else (4) if (x > 4) else (5) if (x > 5) else (6))" + + p = Piecewise( + (1, x < 1), + (2, x < 2), + (3, x < 3), + (4, x < 4), + (5, x < 5), + (6, True) + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((1) if (x < 1) else (2) if (x < 2) else (3) if (x < 3)"\ + " else (4) if (x < 4) else (5) if (x < 5) else (6))" + + p = Piecewise( + (Piecewise( + (1, x > 0), + (2, True) + ), y > 0), + (3, True) + ) + l = lambdarepr(p) + eval(h + l) + assert l == "((((1) if (x > 0) else (2))) if (y > 0) else (3))" + + +def test_sum__1(): + # In each case, test eval() the lambdarepr() to make sure that + # it evaluates to the same results as the symbolic expression + s = Sum(x ** i, (i, a, b)) + l = lambdarepr(s) + assert l == "(builtins.sum(x**i for i in range(a, b+1)))" + + args = x, a, b + f = lambdify(args, s) + v = 2, 3, 8 + assert f(*v) == s.subs(zip(args, v)).doit() + +def test_sum__2(): + s = Sum(i * x, (i, a, b)) + l = lambdarepr(s) + assert l == "(builtins.sum(i*x for i in range(a, b+1)))" + + args = x, a, b + f = lambdify(args, s) + v = 2, 3, 8 + assert f(*v) == s.subs(zip(args, v)).doit() + + +def test_multiple_sums(): + s = Sum(i * x + j, (i, a, b), (j, c, d)) + + l = lambdarepr(s) + assert l == "(builtins.sum(i*x + j for j in range(c, d+1) for i in range(a, b+1)))" + + args = x, a, b, c, d + f = lambdify(args, s) + vals = 2, 3, 4, 5, 6 + f_ref = s.subs(zip(args, vals)).doit() + f_res = f(*vals) + assert f_res == f_ref + + +def test_sqrt(): + prntr = LambdaPrinter({'standard' : 'python3'}) + assert prntr._print_Pow(sqrt(x), rational=False) == 'sqrt(x)' + assert prntr._print_Pow(sqrt(x), rational=True) == 'x**(1/2)' + + +def test_settings(): + raises(TypeError, lambda: lambdarepr(sin(x), method="garbage")) + + +def test_numexpr(): + # test ITE rewrite as Piecewise + from sympy.logic.boolalg import ITE + expr = ITE(x > 0, True, False, evaluate=False) + assert NumExprPrinter().doprint(expr) == \ + "numexpr.evaluate('where((x > 0), True, False)', truediv=True)" + + from sympy.codegen.ast import Return, FunctionDefinition, Variable, Assignment + func_def = FunctionDefinition(None, 'foo', [Variable(x)], [Assignment(y,x), Return(y**2)]) + expected = "def foo(x):\n"\ + " y = numexpr.evaluate('x', truediv=True)\n"\ + " return numexpr.evaluate('y**2', truediv=True)" + assert NumExprPrinter().doprint(func_def) == expected + + +class CustomPrintedObject(Expr): + def _lambdacode(self, printer): + return 'lambda' + + def _tensorflowcode(self, printer): + return 'tensorflow' + + def _numpycode(self, printer): + return 'numpy' + + def _numexprcode(self, printer): + return 'numexpr' + + def _mpmathcode(self, printer): + return 'mpmath' + + +def test_printmethod(): + # In each case, printmethod is called to test + # its working + + obj = CustomPrintedObject() + assert LambdaPrinter().doprint(obj) == 'lambda' + assert TensorflowPrinter().doprint(obj) == 'tensorflow' + assert NumExprPrinter().doprint(obj) == "numexpr.evaluate('numexpr', truediv=True)" + + assert NumExprPrinter().doprint(Piecewise((y, x >= 0), (z, x < 0))) == \ + "numexpr.evaluate('where((x >= 0), y, z)', truediv=True)" diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_latex.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_latex.py new file mode 100644 index 0000000000000000000000000000000000000000..063611d09a923881cd94bd693f3f3f721535fd0c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_latex.py @@ -0,0 +1,3164 @@ +from sympy import MatAdd, MatMul, Array +from sympy.algebras.quaternion import Quaternion +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.combinatorics.permutations import Cycle, Permutation, AppliedPermutation +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum +from sympy.core.containers import Tuple, Dict +from sympy.core.expr import UnevaluatedExpr +from sympy.core.function import (Derivative, Function, Lambda, Subs, diff) +from sympy.core.mod import Mod +from sympy.core.mul import Mul +from sympy.core.numbers import (AlgebraicNumber, Float, I, Integer, Rational, oo, pi) +from sympy.core.parameters import evaluate +from sympy.core.power import Pow +from sympy.core.relational import Eq, Ne +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, Wild, symbols) +from sympy.functions.combinatorial.factorials import (FallingFactorial, RisingFactorial, binomial, factorial, factorial2, subfactorial) +from sympy.functions.combinatorial.numbers import (bernoulli, bell, catalan, euler, genocchi, + lucas, fibonacci, tribonacci, divisor_sigma, udivisor_sigma, + mobius, primenu, primeomega, + totient, reduced_totient) +from sympy.functions.elementary.complexes import (Abs, arg, conjugate, im, polar_lift, re) +from sympy.functions.elementary.exponential import (LambertW, exp, log) +from sympy.functions.elementary.hyperbolic import (asinh, coth) +from sympy.functions.elementary.integers import (ceiling, floor, frac) +from sympy.functions.elementary.miscellaneous import (Max, Min, root, sqrt) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acsc, asin, cos, cot, sin, tan) +from sympy.functions.special.beta_functions import beta +from sympy.functions.special.delta_functions import (DiracDelta, Heaviside) +from sympy.functions.special.elliptic_integrals import (elliptic_e, elliptic_f, elliptic_k, elliptic_pi) +from sympy.functions.special.error_functions import (Chi, Ci, Ei, Shi, Si, expint) +from sympy.functions.special.gamma_functions import (gamma, uppergamma) +from sympy.functions.special.hyper import (hyper, meijerg) +from sympy.functions.special.mathieu_functions import (mathieuc, mathieucprime, mathieus, mathieusprime) +from sympy.functions.special.polynomials import (assoc_laguerre, assoc_legendre, chebyshevt, chebyshevu, gegenbauer, hermite, jacobi, laguerre, legendre) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.functions.special.spherical_harmonics import (Ynm, Znm) +from sympy.functions.special.tensor_functions import (KroneckerDelta, LeviCivita) +from sympy.functions.special.zeta_functions import (dirichlet_eta, lerchphi, polylog, stieltjes, zeta) +from sympy.integrals.integrals import Integral +from sympy.integrals.transforms import (CosineTransform, FourierTransform, InverseCosineTransform, InverseFourierTransform, InverseLaplaceTransform, InverseMellinTransform, InverseSineTransform, LaplaceTransform, MellinTransform, SineTransform) +from sympy.logic import Implies +from sympy.logic.boolalg import (And, Or, Xor, Equivalent, false, Not, true) +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.kronecker import KroneckerProduct +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.permutation import PermutationMatrix +from sympy.matrices.expressions.slice import MatrixSlice +from sympy.matrices.expressions.dotproduct import DotProduct +from sympy.physics.control.lti import TransferFunction, Series, Parallel, Feedback, TransferFunctionMatrix, MIMOSeries, MIMOParallel, MIMOFeedback +from sympy.physics.quantum import Commutator, Operator +from sympy.physics.quantum.trace import Tr +from sympy.physics.units import meter, gibibyte, gram, microgram, second, milli, micro +from sympy.polys.domains.integerring import ZZ +from sympy.polys.fields import field +from sympy.polys.polytools import Poly +from sympy.polys.rings import ring +from sympy.polys.rootoftools import (RootSum, rootof) +from sympy.series.formal import fps +from sympy.series.fourier import fourier_series +from sympy.series.limits import Limit +from sympy.series.order import Order +from sympy.series.sequences import (SeqAdd, SeqFormula, SeqMul, SeqPer) +from sympy.sets.conditionset import ConditionSet +from sympy.sets.contains import Contains +from sympy.sets.fancysets import (ComplexRegion, ImageSet, Range) +from sympy.sets.ordinals import Ordinal, OrdinalOmega, OmegaPower +from sympy.sets.powerset import PowerSet +from sympy.sets.sets import (FiniteSet, Interval, Union, Intersection, Complement, SymmetricDifference, ProductSet) +from sympy.sets.setexpr import SetExpr +from sympy.stats.crv_types import Normal +from sympy.stats.symbolic_probability import (Covariance, Expectation, + Probability, Variance) +from sympy.tensor.array import (ImmutableDenseNDimArray, + ImmutableSparseNDimArray, + MutableSparseNDimArray, + MutableDenseNDimArray, + tensorproduct) +from sympy.tensor.array.expressions.array_expressions import ArraySymbol, ArrayElement +from sympy.tensor.indexed import (Idx, Indexed, IndexedBase) +from sympy.tensor.toperators import PartialDerivative +from sympy.vector import CoordSys3D, Cross, Curl, Dot, Divergence, Gradient, Laplacian + + +from sympy.testing.pytest import (XFAIL, raises, _both_exp_pow, + warns_deprecated_sympy) +from sympy.printing.latex import (latex, translate, greek_letters_set, + tex_greek_dictionary, multiline_latex, + latex_escape, LatexPrinter) + +import sympy as sym + +from sympy.abc import mu, tau + + +class lowergamma(sym.lowergamma): + pass # testing notation inheritance by a subclass with same name + + +x, y, z, t, w, a, b, c, s, p = symbols('x y z t w a b c s p') +k, m, n = symbols('k m n', integer=True) + + +def test_printmethod(): + class R(Abs): + def _latex(self, printer): + return "foo(%s)" % printer._print(self.args[0]) + assert latex(R(x)) == r"foo(x)" + + class R(Abs): + def _latex(self, printer): + return "foo" + assert latex(R(x)) == r"foo" + + +def test_latex_basic(): + assert latex(1 + x) == r"x + 1" + assert latex(x**2) == r"x^{2}" + assert latex(x**(1 + x)) == r"x^{x + 1}" + assert latex(x**3 + x + 1 + x**2) == r"x^{3} + x^{2} + x + 1" + + assert latex(2*x*y) == r"2 x y" + assert latex(2*x*y, mul_symbol='dot') == r"2 \cdot x \cdot y" + assert latex(3*x**2*y, mul_symbol='\\,') == r"3\,x^{2}\,y" + assert latex(1.5*3**x, mul_symbol='\\,') == r"1.5 \cdot 3^{x}" + + assert latex(x**S.Half**5) == r"\sqrt[32]{x}" + assert latex(Mul(S.Half, x**2, -5, evaluate=False)) == r"\frac{1}{2} x^{2} \left(-5\right)" + assert latex(Mul(S.Half, x**2, 5, evaluate=False)) == r"\frac{1}{2} x^{2} \cdot 5" + assert latex(Mul(-5, -5, evaluate=False)) == r"\left(-5\right) \left(-5\right)" + assert latex(Mul(5, -5, evaluate=False)) == r"5 \left(-5\right)" + assert latex(Mul(S.Half, -5, S.Half, evaluate=False)) == r"\frac{1}{2} \left(-5\right) \frac{1}{2}" + assert latex(Mul(5, I, 5, evaluate=False)) == r"5 i 5" + assert latex(Mul(5, I, -5, evaluate=False)) == r"5 i \left(-5\right)" + assert latex(Mul(Pow(x, 2), S.Half*x + 1)) == r"x^{2} \left(\frac{x}{2} + 1\right)" + assert latex(Mul(Pow(x, 3), Rational(2, 3)*x + 1)) == r"x^{3} \left(\frac{2 x}{3} + 1\right)" + assert latex(Mul(Pow(x, 11), 2*x + 1)) == r"x^{11} \left(2 x + 1\right)" + + assert latex(Mul(0, 1, evaluate=False)) == r'0 \cdot 1' + assert latex(Mul(1, 0, evaluate=False)) == r'1 \cdot 0' + assert latex(Mul(1, 1, evaluate=False)) == r'1 \cdot 1' + assert latex(Mul(-1, 1, evaluate=False)) == r'\left(-1\right) 1' + assert latex(Mul(1, 1, 1, evaluate=False)) == r'1 \cdot 1 \cdot 1' + assert latex(Mul(1, 2, evaluate=False)) == r'1 \cdot 2' + assert latex(Mul(1, S.Half, evaluate=False)) == r'1 \cdot \frac{1}{2}' + assert latex(Mul(1, 1, S.Half, evaluate=False)) == \ + r'1 \cdot 1 \cdot \frac{1}{2}' + assert latex(Mul(1, 1, 2, 3, x, evaluate=False)) == \ + r'1 \cdot 1 \cdot 2 \cdot 3 x' + assert latex(Mul(1, -1, evaluate=False)) == r'1 \left(-1\right)' + assert latex(Mul(4, 3, 2, 1, 0, y, x, evaluate=False)) == \ + r'4 \cdot 3 \cdot 2 \cdot 1 \cdot 0 y x' + assert latex(Mul(4, 3, 2, 1+z, 0, y, x, evaluate=False)) == \ + r'4 \cdot 3 \cdot 2 \left(z + 1\right) 0 y x' + assert latex(Mul(Rational(2, 3), Rational(5, 7), evaluate=False)) == \ + r'\frac{2}{3} \cdot \frac{5}{7}' + + assert latex(1/x) == r"\frac{1}{x}" + assert latex(1/x, fold_short_frac=True) == r"1 / x" + assert latex(-S(3)/2) == r"- \frac{3}{2}" + assert latex(-S(3)/2, fold_short_frac=True) == r"- 3 / 2" + assert latex(1/x**2) == r"\frac{1}{x^{2}}" + assert latex(1/(x + y)/2) == r"\frac{1}{2 \left(x + y\right)}" + assert latex(x/2) == r"\frac{x}{2}" + assert latex(x/2, fold_short_frac=True) == r"x / 2" + assert latex((x + y)/(2*x)) == r"\frac{x + y}{2 x}" + assert latex((x + y)/(2*x), fold_short_frac=True) == \ + r"\left(x + y\right) / 2 x" + assert latex((x + y)/(2*x), long_frac_ratio=0) == \ + r"\frac{1}{2 x} \left(x + y\right)" + assert latex((x + y)/x) == r"\frac{x + y}{x}" + assert latex((x + y)/x, long_frac_ratio=3) == r"\frac{x + y}{x}" + assert latex((2*sqrt(2)*x)/3) == r"\frac{2 \sqrt{2} x}{3}" + assert latex((2*sqrt(2)*x)/3, long_frac_ratio=2) == \ + r"\frac{2 x}{3} \sqrt{2}" + assert latex(binomial(x, y)) == r"{\binom{x}{y}}" + + x_star = Symbol('x^*') + f = Function('f') + assert latex(x_star**2) == r"\left(x^{*}\right)^{2}" + assert latex(x_star**2, parenthesize_super=False) == r"{x^{*}}^{2}" + assert latex(Derivative(f(x_star), x_star,2)) == r"\frac{d^{2}}{d \left(x^{*}\right)^{2}} f{\left(x^{*} \right)}" + assert latex(Derivative(f(x_star), x_star,2), parenthesize_super=False) == r"\frac{d^{2}}{d {x^{*}}^{2}} f{\left(x^{*} \right)}" + + assert latex(2*Integral(x, x)/3) == r"\frac{2 \int x\, dx}{3}" + assert latex(2*Integral(x, x)/3, fold_short_frac=True) == \ + r"\left(2 \int x\, dx\right) / 3" + + assert latex(sqrt(x)) == r"\sqrt{x}" + assert latex(x**Rational(1, 3)) == r"\sqrt[3]{x}" + assert latex(x**Rational(1, 3), root_notation=False) == r"x^{\frac{1}{3}}" + assert latex(sqrt(x)**3) == r"x^{\frac{3}{2}}" + assert latex(sqrt(x), itex=True) == r"\sqrt{x}" + assert latex(x**Rational(1, 3), itex=True) == r"\root{3}{x}" + assert latex(sqrt(x)**3, itex=True) == r"x^{\frac{3}{2}}" + assert latex(x**Rational(3, 4)) == r"x^{\frac{3}{4}}" + assert latex(x**Rational(3, 4), fold_frac_powers=True) == r"x^{3/4}" + assert latex((x + 1)**Rational(3, 4)) == \ + r"\left(x + 1\right)^{\frac{3}{4}}" + assert latex((x + 1)**Rational(3, 4), fold_frac_powers=True) == \ + r"\left(x + 1\right)^{3/4}" + assert latex(AlgebraicNumber(sqrt(2))) == r"\sqrt{2}" + assert latex(AlgebraicNumber(sqrt(2), [3, -7])) == r"-7 + 3 \sqrt{2}" + assert latex(AlgebraicNumber(sqrt(2), alias='alpha')) == r"\alpha" + assert latex(AlgebraicNumber(sqrt(2), [3, -7], alias='alpha')) == \ + r"3 \alpha - 7" + assert latex(AlgebraicNumber(2**(S(1)/3), [1, 3, -7], alias='beta')) == \ + r"\beta^{2} + 3 \beta - 7" + + k = ZZ.cyclotomic_field(5) + assert latex(k.ext.field_element([1, 2, 3, 4])) == \ + r"\zeta^{3} + 2 \zeta^{2} + 3 \zeta + 4" + assert latex(k.ext.field_element([1, 2, 3, 4]), order='old') == \ + r"4 + 3 \zeta + 2 \zeta^{2} + \zeta^{3}" + assert latex(k.primes_above(19)[0]) == \ + r"\left(19, \zeta^{2} + 5 \zeta + 1\right)" + assert latex(k.primes_above(19)[0], order='old') == \ + r"\left(19, 1 + 5 \zeta + \zeta^{2}\right)" + assert latex(k.primes_above(7)[0]) == r"\left(7\right)" + + assert latex(1.5e20*x) == r"1.5 \cdot 10^{20} x" + assert latex(1.5e20*x, mul_symbol='dot') == r"1.5 \cdot 10^{20} \cdot x" + assert latex(1.5e20*x, mul_symbol='times') == \ + r"1.5 \times 10^{20} \times x" + + assert latex(1/sin(x)) == r"\frac{1}{\sin{\left(x \right)}}" + assert latex(sin(x)**-1) == r"\frac{1}{\sin{\left(x \right)}}" + assert latex(sin(x)**Rational(3, 2)) == \ + r"\sin^{\frac{3}{2}}{\left(x \right)}" + assert latex(sin(x)**Rational(3, 2), fold_frac_powers=True) == \ + r"\sin^{3/2}{\left(x \right)}" + + assert latex(~x) == r"\neg x" + assert latex(x & y) == r"x \wedge y" + assert latex(x & y & z) == r"x \wedge y \wedge z" + assert latex(x | y) == r"x \vee y" + assert latex(x | y | z) == r"x \vee y \vee z" + assert latex((x & y) | z) == r"z \vee \left(x \wedge y\right)" + assert latex(Implies(x, y)) == r"x \Rightarrow y" + assert latex(~(x >> ~y)) == r"x \not\Rightarrow \neg y" + assert latex(Implies(Or(x,y), z)) == r"\left(x \vee y\right) \Rightarrow z" + assert latex(Implies(z, Or(x,y))) == r"z \Rightarrow \left(x \vee y\right)" + assert latex(~(x & y)) == r"\neg \left(x \wedge y\right)" + + assert latex(~x, symbol_names={x: "x_i"}) == r"\neg x_i" + assert latex(x & y, symbol_names={x: "x_i", y: "y_i"}) == \ + r"x_i \wedge y_i" + assert latex(x & y & z, symbol_names={x: "x_i", y: "y_i", z: "z_i"}) == \ + r"x_i \wedge y_i \wedge z_i" + assert latex(x | y, symbol_names={x: "x_i", y: "y_i"}) == r"x_i \vee y_i" + assert latex(x | y | z, symbol_names={x: "x_i", y: "y_i", z: "z_i"}) == \ + r"x_i \vee y_i \vee z_i" + assert latex((x & y) | z, symbol_names={x: "x_i", y: "y_i", z: "z_i"}) == \ + r"z_i \vee \left(x_i \wedge y_i\right)" + assert latex(Implies(x, y), symbol_names={x: "x_i", y: "y_i"}) == \ + r"x_i \Rightarrow y_i" + assert latex(Pow(Rational(1, 3), -1, evaluate=False)) == r"\frac{1}{\frac{1}{3}}" + assert latex(Pow(Rational(1, 3), -2, evaluate=False)) == r"\frac{1}{(\frac{1}{3})^{2}}" + assert latex(Pow(Integer(1)/100, -1, evaluate=False)) == r"\frac{1}{\frac{1}{100}}" + + p = Symbol('p', positive=True) + assert latex(exp(-p)*log(p)) == r"e^{- p} \log{\left(p \right)}" + + assert latex(Pow(Rational(2, 3), -1, evaluate=False)) == r'\frac{1}{\frac{2}{3}}' + assert latex(Pow(Rational(4, 3), -1, evaluate=False)) == r'\frac{1}{\frac{4}{3}}' + assert latex(Pow(Rational(-3, 4), -1, evaluate=False)) == r'\frac{1}{- \frac{3}{4}}' + assert latex(Pow(Rational(-4, 4), -1, evaluate=False)) == r'\frac{1}{-1}' + assert latex(Pow(Rational(1, 3), -1, evaluate=False)) == r'\frac{1}{\frac{1}{3}}' + assert latex(Pow(Rational(-1, 3), -1, evaluate=False)) == r'\frac{1}{- \frac{1}{3}}' + + +def test_latex_builtins(): + assert latex(True) == r"\text{True}" + assert latex(False) == r"\text{False}" + assert latex(None) == r"\text{None}" + assert latex(true) == r"\text{True}" + assert latex(false) == r'\text{False}' + + +def test_latex_SingularityFunction(): + assert latex(SingularityFunction(x, 4, 5)) == \ + r"{\left\langle x - 4 \right\rangle}^{5}" + assert latex(SingularityFunction(x, -3, 4)) == \ + r"{\left\langle x + 3 \right\rangle}^{4}" + assert latex(SingularityFunction(x, 0, 4)) == \ + r"{\left\langle x \right\rangle}^{4}" + assert latex(SingularityFunction(x, a, n)) == \ + r"{\left\langle - a + x \right\rangle}^{n}" + assert latex(SingularityFunction(x, 4, -2)) == \ + r"{\left\langle x - 4 \right\rangle}^{-2}" + assert latex(SingularityFunction(x, 4, -1)) == \ + r"{\left\langle x - 4 \right\rangle}^{-1}" + + assert latex(SingularityFunction(x, 4, 5)**3) == \ + r"{\left({\langle x - 4 \rangle}^{5}\right)}^{3}" + assert latex(SingularityFunction(x, -3, 4)**3) == \ + r"{\left({\langle x + 3 \rangle}^{4}\right)}^{3}" + assert latex(SingularityFunction(x, 0, 4)**3) == \ + r"{\left({\langle x \rangle}^{4}\right)}^{3}" + assert latex(SingularityFunction(x, a, n)**3) == \ + r"{\left({\langle - a + x \rangle}^{n}\right)}^{3}" + assert latex(SingularityFunction(x, 4, -2)**3) == \ + r"{\left({\langle x - 4 \rangle}^{-2}\right)}^{3}" + assert latex((SingularityFunction(x, 4, -1)**3)**3) == \ + r"{\left({\langle x - 4 \rangle}^{-1}\right)}^{9}" + + +def test_latex_cycle(): + assert latex(Cycle(1, 2, 4)) == r"\left( 1\; 2\; 4\right)" + assert latex(Cycle(1, 2)(4, 5, 6)) == \ + r"\left( 1\; 2\right)\left( 4\; 5\; 6\right)" + assert latex(Cycle()) == r"\left( \right)" + + +def test_latex_permutation(): + assert latex(Permutation(1, 2, 4)) == r"\left( 1\; 2\; 4\right)" + assert latex(Permutation(1, 2)(4, 5, 6)) == \ + r"\left( 1\; 2\right)\left( 4\; 5\; 6\right)" + assert latex(Permutation()) == r"\left( \right)" + assert latex(Permutation(2, 4)*Permutation(5)) == \ + r"\left( 2\; 4\right)\left( 5\right)" + assert latex(Permutation(5)) == r"\left( 5\right)" + + assert latex(Permutation(0, 1), perm_cyclic=False) == \ + r"\begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}" + assert latex(Permutation(0, 1)(2, 3), perm_cyclic=False) == \ + r"\begin{pmatrix} 0 & 1 & 2 & 3 \\ 1 & 0 & 3 & 2 \end{pmatrix}" + assert latex(Permutation(), perm_cyclic=False) == \ + r"\left( \right)" + + with warns_deprecated_sympy(): + old_print_cyclic = Permutation.print_cyclic + Permutation.print_cyclic = False + assert latex(Permutation(0, 1)(2, 3)) == \ + r"\begin{pmatrix} 0 & 1 & 2 & 3 \\ 1 & 0 & 3 & 2 \end{pmatrix}" + Permutation.print_cyclic = old_print_cyclic + +def test_latex_Float(): + assert latex(Float(1.0e100)) == r"1.0 \cdot 10^{100}" + assert latex(Float(1.0e-100)) == r"1.0 \cdot 10^{-100}" + assert latex(Float(1.0e-100), mul_symbol="times") == \ + r"1.0 \times 10^{-100}" + assert latex(Float('10000.0'), full_prec=False, min=-2, max=2) == \ + r"1.0 \cdot 10^{4}" + assert latex(Float('10000.0'), full_prec=False, min=-2, max=4) == \ + r"1.0 \cdot 10^{4}" + assert latex(Float('10000.0'), full_prec=False, min=-2, max=5) == \ + r"10000.0" + assert latex(Float('0.099999'), full_prec=True, min=-2, max=5) == \ + r"9.99990000000000 \cdot 10^{-2}" + + +def test_latex_vector_expressions(): + A = CoordSys3D('A') + + assert latex(Cross(A.i, A.j*A.x*3+A.k)) == \ + r"\mathbf{\hat{i}_{A}} \times \left(\left(3 \mathbf{{x}_{A}}\right)\mathbf{\hat{j}_{A}} + \mathbf{\hat{k}_{A}}\right)" + assert latex(Cross(A.i, A.j)) == \ + r"\mathbf{\hat{i}_{A}} \times \mathbf{\hat{j}_{A}}" + assert latex(x*Cross(A.i, A.j)) == \ + r"x \left(\mathbf{\hat{i}_{A}} \times \mathbf{\hat{j}_{A}}\right)" + assert latex(Cross(x*A.i, A.j)) == \ + r'- \mathbf{\hat{j}_{A}} \times \left(\left(x\right)\mathbf{\hat{i}_{A}}\right)' + + assert latex(Curl(3*A.x*A.j)) == \ + r"\nabla\times \left(\left(3 \mathbf{{x}_{A}}\right)\mathbf{\hat{j}_{A}}\right)" + assert latex(Curl(3*A.x*A.j+A.i)) == \ + r"\nabla\times \left(\mathbf{\hat{i}_{A}} + \left(3 \mathbf{{x}_{A}}\right)\mathbf{\hat{j}_{A}}\right)" + assert latex(Curl(3*x*A.x*A.j)) == \ + r"\nabla\times \left(\left(3 \mathbf{{x}_{A}} x\right)\mathbf{\hat{j}_{A}}\right)" + assert latex(x*Curl(3*A.x*A.j)) == \ + r"x \left(\nabla\times \left(\left(3 \mathbf{{x}_{A}}\right)\mathbf{\hat{j}_{A}}\right)\right)" + + assert latex(Divergence(3*A.x*A.j+A.i)) == \ + r"\nabla\cdot \left(\mathbf{\hat{i}_{A}} + \left(3 \mathbf{{x}_{A}}\right)\mathbf{\hat{j}_{A}}\right)" + assert latex(Divergence(3*A.x*A.j)) == \ + r"\nabla\cdot \left(\left(3 \mathbf{{x}_{A}}\right)\mathbf{\hat{j}_{A}}\right)" + assert latex(x*Divergence(3*A.x*A.j)) == \ + r"x \left(\nabla\cdot \left(\left(3 \mathbf{{x}_{A}}\right)\mathbf{\hat{j}_{A}}\right)\right)" + + assert latex(Dot(A.i, A.j*A.x*3+A.k)) == \ + r"\mathbf{\hat{i}_{A}} \cdot \left(\left(3 \mathbf{{x}_{A}}\right)\mathbf{\hat{j}_{A}} + \mathbf{\hat{k}_{A}}\right)" + assert latex(Dot(A.i, A.j)) == \ + r"\mathbf{\hat{i}_{A}} \cdot \mathbf{\hat{j}_{A}}" + assert latex(Dot(x*A.i, A.j)) == \ + r"\mathbf{\hat{j}_{A}} \cdot \left(\left(x\right)\mathbf{\hat{i}_{A}}\right)" + assert latex(x*Dot(A.i, A.j)) == \ + r"x \left(\mathbf{\hat{i}_{A}} \cdot \mathbf{\hat{j}_{A}}\right)" + + assert latex(Gradient(A.x)) == r"\nabla \mathbf{{x}_{A}}" + assert latex(Gradient(A.x + 3*A.y)) == \ + r"\nabla \left(\mathbf{{x}_{A}} + 3 \mathbf{{y}_{A}}\right)" + assert latex(x*Gradient(A.x)) == r"x \left(\nabla \mathbf{{x}_{A}}\right)" + assert latex(Gradient(x*A.x)) == r"\nabla \left(\mathbf{{x}_{A}} x\right)" + + assert latex(Laplacian(A.x)) == r"\Delta \mathbf{{x}_{A}}" + assert latex(Laplacian(A.x + 3*A.y)) == \ + r"\Delta \left(\mathbf{{x}_{A}} + 3 \mathbf{{y}_{A}}\right)" + assert latex(x*Laplacian(A.x)) == r"x \left(\Delta \mathbf{{x}_{A}}\right)" + assert latex(Laplacian(x*A.x)) == r"\Delta \left(\mathbf{{x}_{A}} x\right)" + +def test_latex_symbols(): + Gamma, lmbda, rho = symbols('Gamma, lambda, rho') + tau, Tau, TAU, taU = symbols('tau, Tau, TAU, taU') + assert latex(tau) == r"\tau" + assert latex(Tau) == r"\mathrm{T}" + assert latex(TAU) == r"\tau" + assert latex(taU) == r"\tau" + # Check that all capitalized greek letters are handled explicitly + capitalized_letters = {l.capitalize() for l in greek_letters_set} + assert len(capitalized_letters - set(tex_greek_dictionary.keys())) == 0 + assert latex(Gamma + lmbda) == r"\Gamma + \lambda" + assert latex(Gamma * lmbda) == r"\Gamma \lambda" + assert latex(Symbol('q1')) == r"q_{1}" + assert latex(Symbol('q21')) == r"q_{21}" + assert latex(Symbol('epsilon0')) == r"\epsilon_{0}" + assert latex(Symbol('omega1')) == r"\omega_{1}" + assert latex(Symbol('91')) == r"91" + assert latex(Symbol('alpha_new')) == r"\alpha_{new}" + assert latex(Symbol('C^orig')) == r"C^{orig}" + assert latex(Symbol('x^alpha')) == r"x^{\alpha}" + assert latex(Symbol('beta^alpha')) == r"\beta^{\alpha}" + assert latex(Symbol('e^Alpha')) == r"e^{\mathrm{A}}" + assert latex(Symbol('omega_alpha^beta')) == r"\omega^{\beta}_{\alpha}" + assert latex(Symbol('omega') ** Symbol('beta')) == r"\omega^{\beta}" + + +@XFAIL +def test_latex_symbols_failing(): + rho, mass, volume = symbols('rho, mass, volume') + assert latex( + volume * rho == mass) == r"\rho \mathrm{volume} = \mathrm{mass}" + assert latex(volume / mass * rho == 1) == \ + r"\rho \mathrm{volume} {\mathrm{mass}}^{(-1)} = 1" + assert latex(mass**3 * volume**3) == \ + r"{\mathrm{mass}}^{3} \cdot {\mathrm{volume}}^{3}" + + +@_both_exp_pow +def test_latex_functions(): + assert latex(exp(x)) == r"e^{x}" + assert latex(exp(1) + exp(2)) == r"e + e^{2}" + + f = Function('f') + assert latex(f(x)) == r'f{\left(x \right)}' + assert latex(f) == r'f' + + g = Function('g') + assert latex(g(x, y)) == r'g{\left(x,y \right)}' + assert latex(g) == r'g' + + h = Function('h') + assert latex(h(x, y, z)) == r'h{\left(x,y,z \right)}' + assert latex(h) == r'h' + + Li = Function('Li') + assert latex(Li) == r'\operatorname{Li}' + assert latex(Li(x)) == r'\operatorname{Li}{\left(x \right)}' + + mybeta = Function('beta') + # not to be confused with the beta function + assert latex(mybeta(x, y, z)) == r"\beta{\left(x,y,z \right)}" + assert latex(beta(x, y)) == r'\operatorname{B}\left(x, y\right)' + assert latex(beta(x, evaluate=False)) == r'\operatorname{B}\left(x, x\right)' + assert latex(beta(x, y)**2) == r'\operatorname{B}^{2}\left(x, y\right)' + assert latex(mybeta(x)) == r"\beta{\left(x \right)}" + assert latex(mybeta) == r"\beta" + + g = Function('gamma') + # not to be confused with the gamma function + assert latex(g(x, y, z)) == r"\gamma{\left(x,y,z \right)}" + assert latex(g(x)) == r"\gamma{\left(x \right)}" + assert latex(g) == r"\gamma" + + a_1 = Function('a_1') + assert latex(a_1) == r"a_{1}" + assert latex(a_1(x)) == r"a_{1}{\left(x \right)}" + assert latex(Function('a_1')) == r"a_{1}" + + # Issue #16925 + # multi letter function names + # > simple + assert latex(Function('ab')) == r"\operatorname{ab}" + assert latex(Function('ab1')) == r"\operatorname{ab}_{1}" + assert latex(Function('ab12')) == r"\operatorname{ab}_{12}" + assert latex(Function('ab_1')) == r"\operatorname{ab}_{1}" + assert latex(Function('ab_12')) == r"\operatorname{ab}_{12}" + assert latex(Function('ab_c')) == r"\operatorname{ab}_{c}" + assert latex(Function('ab_cd')) == r"\operatorname{ab}_{cd}" + # > with argument + assert latex(Function('ab')(Symbol('x'))) == r"\operatorname{ab}{\left(x \right)}" + assert latex(Function('ab1')(Symbol('x'))) == r"\operatorname{ab}_{1}{\left(x \right)}" + assert latex(Function('ab12')(Symbol('x'))) == r"\operatorname{ab}_{12}{\left(x \right)}" + assert latex(Function('ab_1')(Symbol('x'))) == r"\operatorname{ab}_{1}{\left(x \right)}" + assert latex(Function('ab_c')(Symbol('x'))) == r"\operatorname{ab}_{c}{\left(x \right)}" + assert latex(Function('ab_cd')(Symbol('x'))) == r"\operatorname{ab}_{cd}{\left(x \right)}" + + # > with power + # does not work on functions without brackets + + # > with argument and power combined + assert latex(Function('ab')()**2) == r"\operatorname{ab}^{2}{\left( \right)}" + assert latex(Function('ab1')()**2) == r"\operatorname{ab}_{1}^{2}{\left( \right)}" + assert latex(Function('ab12')()**2) == r"\operatorname{ab}_{12}^{2}{\left( \right)}" + assert latex(Function('ab_1')()**2) == r"\operatorname{ab}_{1}^{2}{\left( \right)}" + assert latex(Function('ab_12')()**2) == r"\operatorname{ab}_{12}^{2}{\left( \right)}" + assert latex(Function('ab')(Symbol('x'))**2) == r"\operatorname{ab}^{2}{\left(x \right)}" + assert latex(Function('ab1')(Symbol('x'))**2) == r"\operatorname{ab}_{1}^{2}{\left(x \right)}" + assert latex(Function('ab12')(Symbol('x'))**2) == r"\operatorname{ab}_{12}^{2}{\left(x \right)}" + assert latex(Function('ab_1')(Symbol('x'))**2) == r"\operatorname{ab}_{1}^{2}{\left(x \right)}" + assert latex(Function('ab_12')(Symbol('x'))**2) == \ + r"\operatorname{ab}_{12}^{2}{\left(x \right)}" + + # single letter function names + # > simple + assert latex(Function('a')) == r"a" + assert latex(Function('a1')) == r"a_{1}" + assert latex(Function('a12')) == r"a_{12}" + assert latex(Function('a_1')) == r"a_{1}" + assert latex(Function('a_12')) == r"a_{12}" + + # > with argument + assert latex(Function('a')()) == r"a{\left( \right)}" + assert latex(Function('a1')()) == r"a_{1}{\left( \right)}" + assert latex(Function('a12')()) == r"a_{12}{\left( \right)}" + assert latex(Function('a_1')()) == r"a_{1}{\left( \right)}" + assert latex(Function('a_12')()) == r"a_{12}{\left( \right)}" + + # > with power + # does not work on functions without brackets + + # > with argument and power combined + assert latex(Function('a')()**2) == r"a^{2}{\left( \right)}" + assert latex(Function('a1')()**2) == r"a_{1}^{2}{\left( \right)}" + assert latex(Function('a12')()**2) == r"a_{12}^{2}{\left( \right)}" + assert latex(Function('a_1')()**2) == r"a_{1}^{2}{\left( \right)}" + assert latex(Function('a_12')()**2) == r"a_{12}^{2}{\left( \right)}" + assert latex(Function('a')(Symbol('x'))**2) == r"a^{2}{\left(x \right)}" + assert latex(Function('a1')(Symbol('x'))**2) == r"a_{1}^{2}{\left(x \right)}" + assert latex(Function('a12')(Symbol('x'))**2) == r"a_{12}^{2}{\left(x \right)}" + assert latex(Function('a_1')(Symbol('x'))**2) == r"a_{1}^{2}{\left(x \right)}" + assert latex(Function('a_12')(Symbol('x'))**2) == r"a_{12}^{2}{\left(x \right)}" + + assert latex(Function('a')()**32) == r"a^{32}{\left( \right)}" + assert latex(Function('a1')()**32) == r"a_{1}^{32}{\left( \right)}" + assert latex(Function('a12')()**32) == r"a_{12}^{32}{\left( \right)}" + assert latex(Function('a_1')()**32) == r"a_{1}^{32}{\left( \right)}" + assert latex(Function('a_12')()**32) == r"a_{12}^{32}{\left( \right)}" + assert latex(Function('a')(Symbol('x'))**32) == r"a^{32}{\left(x \right)}" + assert latex(Function('a1')(Symbol('x'))**32) == r"a_{1}^{32}{\left(x \right)}" + assert latex(Function('a12')(Symbol('x'))**32) == r"a_{12}^{32}{\left(x \right)}" + assert latex(Function('a_1')(Symbol('x'))**32) == r"a_{1}^{32}{\left(x \right)}" + assert latex(Function('a_12')(Symbol('x'))**32) == r"a_{12}^{32}{\left(x \right)}" + + assert latex(Function('a')()**a) == r"a^{a}{\left( \right)}" + assert latex(Function('a1')()**a) == r"a_{1}^{a}{\left( \right)}" + assert latex(Function('a12')()**a) == r"a_{12}^{a}{\left( \right)}" + assert latex(Function('a_1')()**a) == r"a_{1}^{a}{\left( \right)}" + assert latex(Function('a_12')()**a) == r"a_{12}^{a}{\left( \right)}" + assert latex(Function('a')(Symbol('x'))**a) == r"a^{a}{\left(x \right)}" + assert latex(Function('a1')(Symbol('x'))**a) == r"a_{1}^{a}{\left(x \right)}" + assert latex(Function('a12')(Symbol('x'))**a) == r"a_{12}^{a}{\left(x \right)}" + assert latex(Function('a_1')(Symbol('x'))**a) == r"a_{1}^{a}{\left(x \right)}" + assert latex(Function('a_12')(Symbol('x'))**a) == r"a_{12}^{a}{\left(x \right)}" + + ab = Symbol('ab') + assert latex(Function('a')()**ab) == r"a^{ab}{\left( \right)}" + assert latex(Function('a1')()**ab) == r"a_{1}^{ab}{\left( \right)}" + assert latex(Function('a12')()**ab) == r"a_{12}^{ab}{\left( \right)}" + assert latex(Function('a_1')()**ab) == r"a_{1}^{ab}{\left( \right)}" + assert latex(Function('a_12')()**ab) == r"a_{12}^{ab}{\left( \right)}" + assert latex(Function('a')(Symbol('x'))**ab) == r"a^{ab}{\left(x \right)}" + assert latex(Function('a1')(Symbol('x'))**ab) == r"a_{1}^{ab}{\left(x \right)}" + assert latex(Function('a12')(Symbol('x'))**ab) == r"a_{12}^{ab}{\left(x \right)}" + assert latex(Function('a_1')(Symbol('x'))**ab) == r"a_{1}^{ab}{\left(x \right)}" + assert latex(Function('a_12')(Symbol('x'))**ab) == r"a_{12}^{ab}{\left(x \right)}" + + assert latex(Function('a^12')(x)) == R"a^{12}{\left(x \right)}" + assert latex(Function('a^12')(x) ** ab) == R"\left(a^{12}\right)^{ab}{\left(x \right)}" + assert latex(Function('a__12')(x)) == R"a^{12}{\left(x \right)}" + assert latex(Function('a__12')(x) ** ab) == R"\left(a^{12}\right)^{ab}{\left(x \right)}" + assert latex(Function('a_1__1_2')(x)) == R"a^{1}_{1 2}{\left(x \right)}" + + # issue 5868 + omega1 = Function('omega1') + assert latex(omega1) == r"\omega_{1}" + assert latex(omega1(x)) == r"\omega_{1}{\left(x \right)}" + + assert latex(sin(x)) == r"\sin{\left(x \right)}" + assert latex(sin(x), fold_func_brackets=True) == r"\sin {x}" + assert latex(sin(2*x**2), fold_func_brackets=True) == \ + r"\sin {2 x^{2}}" + assert latex(sin(x**2), fold_func_brackets=True) == \ + r"\sin {x^{2}}" + + assert latex(asin(x)**2) == r"\operatorname{asin}^{2}{\left(x \right)}" + assert latex(asin(x)**2, inv_trig_style="full") == \ + r"\arcsin^{2}{\left(x \right)}" + assert latex(asin(x)**2, inv_trig_style="power") == \ + r"\sin^{-1}{\left(x \right)}^{2}" + assert latex(asin(x**2), inv_trig_style="power", + fold_func_brackets=True) == \ + r"\sin^{-1} {x^{2}}" + assert latex(acsc(x), inv_trig_style="full") == \ + r"\operatorname{arccsc}{\left(x \right)}" + assert latex(asinh(x), inv_trig_style="full") == \ + r"\operatorname{arsinh}{\left(x \right)}" + + assert latex(factorial(k)) == r"k!" + assert latex(factorial(-k)) == r"\left(- k\right)!" + assert latex(factorial(k)**2) == r"k!^{2}" + + assert latex(subfactorial(k)) == r"!k" + assert latex(subfactorial(-k)) == r"!\left(- k\right)" + assert latex(subfactorial(k)**2) == r"\left(!k\right)^{2}" + + assert latex(factorial2(k)) == r"k!!" + assert latex(factorial2(-k)) == r"\left(- k\right)!!" + assert latex(factorial2(k)**2) == r"k!!^{2}" + + assert latex(binomial(2, k)) == r"{\binom{2}{k}}" + assert latex(binomial(2, k)**2) == r"{\binom{2}{k}}^{2}" + + assert latex(FallingFactorial(3, k)) == r"{\left(3\right)}_{k}" + assert latex(RisingFactorial(3, k)) == r"{3}^{\left(k\right)}" + + assert latex(floor(x)) == r"\left\lfloor{x}\right\rfloor" + assert latex(ceiling(x)) == r"\left\lceil{x}\right\rceil" + assert latex(frac(x)) == r"\operatorname{frac}{\left(x\right)}" + assert latex(floor(x)**2) == r"\left\lfloor{x}\right\rfloor^{2}" + assert latex(ceiling(x)**2) == r"\left\lceil{x}\right\rceil^{2}" + assert latex(frac(x)**2) == r"\operatorname{frac}{\left(x\right)}^{2}" + + assert latex(Min(x, 2, x**3)) == r"\min\left(2, x, x^{3}\right)" + assert latex(Min(x, y)**2) == r"\min\left(x, y\right)^{2}" + assert latex(Max(x, 2, x**3)) == r"\max\left(2, x, x^{3}\right)" + assert latex(Max(x, y)**2) == r"\max\left(x, y\right)^{2}" + assert latex(Abs(x)) == r"\left|{x}\right|" + assert latex(Abs(x)**2) == r"\left|{x}\right|^{2}" + assert latex(re(x)) == r"\operatorname{re}{\left(x\right)}" + assert latex(re(x + y)) == \ + r"\operatorname{re}{\left(x\right)} + \operatorname{re}{\left(y\right)}" + assert latex(im(x)) == r"\operatorname{im}{\left(x\right)}" + assert latex(conjugate(x)) == r"\overline{x}" + assert latex(conjugate(x)**2) == r"\overline{x}^{2}" + assert latex(conjugate(x**2)) == r"\overline{x}^{2}" + assert latex(gamma(x)) == r"\Gamma\left(x\right)" + w = Wild('w') + assert latex(gamma(w)) == r"\Gamma\left(w\right)" + assert latex(Order(x)) == r"O\left(x\right)" + assert latex(Order(x, x)) == r"O\left(x\right)" + assert latex(Order(x, (x, 0))) == r"O\left(x\right)" + assert latex(Order(x, (x, oo))) == r"O\left(x; x\rightarrow \infty\right)" + assert latex(Order(x - y, (x, y))) == \ + r"O\left(x - y; x\rightarrow y\right)" + assert latex(Order(x, x, y)) == \ + r"O\left(x; \left( x, \ y\right)\rightarrow \left( 0, \ 0\right)\right)" + assert latex(Order(x, x, y)) == \ + r"O\left(x; \left( x, \ y\right)\rightarrow \left( 0, \ 0\right)\right)" + assert latex(Order(x, (x, oo), (y, oo))) == \ + r"O\left(x; \left( x, \ y\right)\rightarrow \left( \infty, \ \infty\right)\right)" + assert latex(lowergamma(x, y)) == r'\gamma\left(x, y\right)' + assert latex(lowergamma(x, y)**2) == r'\gamma^{2}\left(x, y\right)' + assert latex(uppergamma(x, y)) == r'\Gamma\left(x, y\right)' + assert latex(uppergamma(x, y)**2) == r'\Gamma^{2}\left(x, y\right)' + + assert latex(cot(x)) == r'\cot{\left(x \right)}' + assert latex(coth(x)) == r'\coth{\left(x \right)}' + assert latex(re(x)) == r'\operatorname{re}{\left(x\right)}' + assert latex(im(x)) == r'\operatorname{im}{\left(x\right)}' + assert latex(root(x, y)) == r'x^{\frac{1}{y}}' + assert latex(arg(x)) == r'\arg{\left(x \right)}' + + assert latex(zeta(x)) == r"\zeta\left(x\right)" + assert latex(zeta(x)**2) == r"\zeta^{2}\left(x\right)" + assert latex(zeta(x, y)) == r"\zeta\left(x, y\right)" + assert latex(zeta(x, y)**2) == r"\zeta^{2}\left(x, y\right)" + assert latex(dirichlet_eta(x)) == r"\eta\left(x\right)" + assert latex(dirichlet_eta(x)**2) == r"\eta^{2}\left(x\right)" + assert latex(polylog(x, y)) == r"\operatorname{Li}_{x}\left(y\right)" + assert latex( + polylog(x, y)**2) == r"\operatorname{Li}_{x}^{2}\left(y\right)" + assert latex(lerchphi(x, y, n)) == r"\Phi\left(x, y, n\right)" + assert latex(lerchphi(x, y, n)**2) == r"\Phi^{2}\left(x, y, n\right)" + assert latex(stieltjes(x)) == r"\gamma_{x}" + assert latex(stieltjes(x)**2) == r"\gamma_{x}^{2}" + assert latex(stieltjes(x, y)) == r"\gamma_{x}\left(y\right)" + assert latex(stieltjes(x, y)**2) == r"\gamma_{x}\left(y\right)^{2}" + + assert latex(elliptic_k(z)) == r"K\left(z\right)" + assert latex(elliptic_k(z)**2) == r"K^{2}\left(z\right)" + assert latex(elliptic_f(x, y)) == r"F\left(x\middle| y\right)" + assert latex(elliptic_f(x, y)**2) == r"F^{2}\left(x\middle| y\right)" + assert latex(elliptic_e(x, y)) == r"E\left(x\middle| y\right)" + assert latex(elliptic_e(x, y)**2) == r"E^{2}\left(x\middle| y\right)" + assert latex(elliptic_e(z)) == r"E\left(z\right)" + assert latex(elliptic_e(z)**2) == r"E^{2}\left(z\right)" + assert latex(elliptic_pi(x, y, z)) == r"\Pi\left(x; y\middle| z\right)" + assert latex(elliptic_pi(x, y, z)**2) == \ + r"\Pi^{2}\left(x; y\middle| z\right)" + assert latex(elliptic_pi(x, y)) == r"\Pi\left(x\middle| y\right)" + assert latex(elliptic_pi(x, y)**2) == r"\Pi^{2}\left(x\middle| y\right)" + + assert latex(Ei(x)) == r'\operatorname{Ei}{\left(x \right)}' + assert latex(Ei(x)**2) == r'\operatorname{Ei}^{2}{\left(x \right)}' + assert latex(expint(x, y)) == r'\operatorname{E}_{x}\left(y\right)' + assert latex(expint(x, y)**2) == r'\operatorname{E}_{x}^{2}\left(y\right)' + assert latex(Shi(x)**2) == r'\operatorname{Shi}^{2}{\left(x \right)}' + assert latex(Si(x)**2) == r'\operatorname{Si}^{2}{\left(x \right)}' + assert latex(Ci(x)**2) == r'\operatorname{Ci}^{2}{\left(x \right)}' + assert latex(Chi(x)**2) == r'\operatorname{Chi}^{2}\left(x\right)' + assert latex(Chi(x)) == r'\operatorname{Chi}\left(x\right)' + assert latex(jacobi(n, a, b, x)) == \ + r'P_{n}^{\left(a,b\right)}\left(x\right)' + assert latex(jacobi(n, a, b, x)**2) == \ + r'\left(P_{n}^{\left(a,b\right)}\left(x\right)\right)^{2}' + assert latex(gegenbauer(n, a, x)) == \ + r'C_{n}^{\left(a\right)}\left(x\right)' + assert latex(gegenbauer(n, a, x)**2) == \ + r'\left(C_{n}^{\left(a\right)}\left(x\right)\right)^{2}' + assert latex(chebyshevt(n, x)) == r'T_{n}\left(x\right)' + assert latex(chebyshevt(n, x)**2) == \ + r'\left(T_{n}\left(x\right)\right)^{2}' + assert latex(chebyshevu(n, x)) == r'U_{n}\left(x\right)' + assert latex(chebyshevu(n, x)**2) == \ + r'\left(U_{n}\left(x\right)\right)^{2}' + assert latex(legendre(n, x)) == r'P_{n}\left(x\right)' + assert latex(legendre(n, x)**2) == r'\left(P_{n}\left(x\right)\right)^{2}' + assert latex(assoc_legendre(n, a, x)) == \ + r'P_{n}^{\left(a\right)}\left(x\right)' + assert latex(assoc_legendre(n, a, x)**2) == \ + r'\left(P_{n}^{\left(a\right)}\left(x\right)\right)^{2}' + assert latex(laguerre(n, x)) == r'L_{n}\left(x\right)' + assert latex(laguerre(n, x)**2) == r'\left(L_{n}\left(x\right)\right)^{2}' + assert latex(assoc_laguerre(n, a, x)) == \ + r'L_{n}^{\left(a\right)}\left(x\right)' + assert latex(assoc_laguerre(n, a, x)**2) == \ + r'\left(L_{n}^{\left(a\right)}\left(x\right)\right)^{2}' + assert latex(hermite(n, x)) == r'H_{n}\left(x\right)' + assert latex(hermite(n, x)**2) == r'\left(H_{n}\left(x\right)\right)^{2}' + + theta = Symbol("theta", real=True) + phi = Symbol("phi", real=True) + assert latex(Ynm(n, m, theta, phi)) == r'Y_{n}^{m}\left(\theta,\phi\right)' + assert latex(Ynm(n, m, theta, phi)**3) == \ + r'\left(Y_{n}^{m}\left(\theta,\phi\right)\right)^{3}' + assert latex(Znm(n, m, theta, phi)) == r'Z_{n}^{m}\left(\theta,\phi\right)' + assert latex(Znm(n, m, theta, phi)**3) == \ + r'\left(Z_{n}^{m}\left(\theta,\phi\right)\right)^{3}' + + # Test latex printing of function names with "_" + assert latex(polar_lift(0)) == \ + r"\operatorname{polar\_lift}{\left(0 \right)}" + assert latex(polar_lift(0)**3) == \ + r"\operatorname{polar\_lift}^{3}{\left(0 \right)}" + + assert latex(totient(n)) == r'\phi\left(n\right)' + assert latex(totient(n) ** 2) == r'\left(\phi\left(n\right)\right)^{2}' + + assert latex(reduced_totient(n)) == r'\lambda\left(n\right)' + assert latex(reduced_totient(n) ** 2) == \ + r'\left(\lambda\left(n\right)\right)^{2}' + + assert latex(divisor_sigma(x)) == r"\sigma\left(x\right)" + assert latex(divisor_sigma(x)**2) == r"\sigma^{2}\left(x\right)" + assert latex(divisor_sigma(x, y)) == r"\sigma_y\left(x\right)" + assert latex(divisor_sigma(x, y)**2) == r"\sigma^{2}_y\left(x\right)" + + assert latex(udivisor_sigma(x)) == r"\sigma^*\left(x\right)" + assert latex(udivisor_sigma(x)**2) == r"\sigma^*^{2}\left(x\right)" + assert latex(udivisor_sigma(x, y)) == r"\sigma^*_y\left(x\right)" + assert latex(udivisor_sigma(x, y)**2) == r"\sigma^*^{2}_y\left(x\right)" + + assert latex(primenu(n)) == r'\nu\left(n\right)' + assert latex(primenu(n) ** 2) == r'\left(\nu\left(n\right)\right)^{2}' + + assert latex(primeomega(n)) == r'\Omega\left(n\right)' + assert latex(primeomega(n) ** 2) == \ + r'\left(\Omega\left(n\right)\right)^{2}' + + assert latex(LambertW(n)) == r'W\left(n\right)' + assert latex(LambertW(n, -1)) == r'W_{-1}\left(n\right)' + assert latex(LambertW(n, k)) == r'W_{k}\left(n\right)' + assert latex(LambertW(n) * LambertW(n)) == r"W^{2}\left(n\right)" + assert latex(Pow(LambertW(n), 2)) == r"W^{2}\left(n\right)" + assert latex(LambertW(n)**k) == r"W^{k}\left(n\right)" + assert latex(LambertW(n, k)**p) == r"W^{p}_{k}\left(n\right)" + + assert latex(Mod(x, 7)) == r'x \bmod 7' + assert latex(Mod(x + 1, 7)) == r'\left(x + 1\right) \bmod 7' + assert latex(Mod(7, x + 1)) == r'7 \bmod \left(x + 1\right)' + assert latex(Mod(2 * x, 7)) == r'2 x \bmod 7' + assert latex(Mod(7, 2 * x)) == r'7 \bmod 2 x' + assert latex(Mod(x, 7) + 1) == r'\left(x \bmod 7\right) + 1' + assert latex(2 * Mod(x, 7)) == r'2 \left(x \bmod 7\right)' + assert latex(Mod(7, 2 * x)**n) == r'\left(7 \bmod 2 x\right)^{n}' + + # some unknown function name should get rendered with \operatorname + fjlkd = Function('fjlkd') + assert latex(fjlkd(x)) == r'\operatorname{fjlkd}{\left(x \right)}' + # even when it is referred to without an argument + assert latex(fjlkd) == r'\operatorname{fjlkd}' + + +# test that notation passes to subclasses of the same name only +def test_function_subclass_different_name(): + class mygamma(gamma): + pass + assert latex(mygamma) == r"\operatorname{mygamma}" + assert latex(mygamma(x)) == r"\operatorname{mygamma}{\left(x \right)}" + + +def test_hyper_printing(): + from sympy.abc import x, z + + assert latex(meijerg(Tuple(pi, pi, x), Tuple(1), + (0, 1), Tuple(1, 2, 3/pi), z)) == \ + r'{G_{4, 5}^{2, 3}\left(\begin{matrix} \pi, \pi, x & 1 \\0, 1 & 1, 2, '\ + r'\frac{3}{\pi} \end{matrix} \middle| {z} \right)}' + assert latex(meijerg(Tuple(), Tuple(1), (0,), Tuple(), z)) == \ + r'{G_{1, 1}^{1, 0}\left(\begin{matrix} & 1 \\0 & \end{matrix} \middle| {z} \right)}' + assert latex(hyper((x, 2), (3,), z)) == \ + r'{{}_{2}F_{1}\left(\begin{matrix} 2, x ' \ + r'\\ 3 \end{matrix}\middle| {z} \right)}' + assert latex(hyper(Tuple(), Tuple(1), z)) == \ + r'{{}_{0}F_{1}\left(\begin{matrix} ' \ + r'\\ 1 \end{matrix}\middle| {z} \right)}' + + +def test_latex_bessel(): + from sympy.functions.special.bessel import (besselj, bessely, besseli, + besselk, hankel1, hankel2, + jn, yn, hn1, hn2) + from sympy.abc import z + assert latex(besselj(n, z**2)**k) == r'J^{k}_{n}\left(z^{2}\right)' + assert latex(bessely(n, z)) == r'Y_{n}\left(z\right)' + assert latex(besseli(n, z)) == r'I_{n}\left(z\right)' + assert latex(besselk(n, z)) == r'K_{n}\left(z\right)' + assert latex(hankel1(n, z**2)**2) == \ + r'\left(H^{(1)}_{n}\left(z^{2}\right)\right)^{2}' + assert latex(hankel2(n, z)) == r'H^{(2)}_{n}\left(z\right)' + assert latex(jn(n, z)) == r'j_{n}\left(z\right)' + assert latex(yn(n, z)) == r'y_{n}\left(z\right)' + assert latex(hn1(n, z)) == r'h^{(1)}_{n}\left(z\right)' + assert latex(hn2(n, z)) == r'h^{(2)}_{n}\left(z\right)' + + +def test_latex_fresnel(): + from sympy.functions.special.error_functions import (fresnels, fresnelc) + from sympy.abc import z + assert latex(fresnels(z)) == r'S\left(z\right)' + assert latex(fresnelc(z)) == r'C\left(z\right)' + assert latex(fresnels(z)**2) == r'S^{2}\left(z\right)' + assert latex(fresnelc(z)**2) == r'C^{2}\left(z\right)' + + +def test_latex_brackets(): + assert latex((-1)**x) == r"\left(-1\right)^{x}" + + +def test_latex_indexed(): + Psi_symbol = Symbol('Psi_0', complex=True, real=False) + Psi_indexed = IndexedBase(Symbol('Psi', complex=True, real=False)) + symbol_latex = latex(Psi_symbol * conjugate(Psi_symbol)) + indexed_latex = latex(Psi_indexed[0] * conjugate(Psi_indexed[0])) + # \\overline{{\\Psi}_{0}} {\\Psi}_{0} vs. \\Psi_{0} \\overline{\\Psi_{0}} + assert symbol_latex == r'\Psi_{0} \overline{\Psi_{0}}' + assert indexed_latex == r'\overline{{\Psi}_{0}} {\Psi}_{0}' + + # Symbol('gamma') gives r'\gamma' + interval = '\\mathrel{..}\\nobreak ' + assert latex(Indexed('x1', Symbol('i'))) == r'{x_{1}}_{i}' + assert latex(Indexed('x2', Idx('i'))) == r'{x_{2}}_{i}' + assert latex(Indexed('x3', Idx('i', Symbol('N')))) == r'{x_{3}}_{{i}_{0'+interval+'N - 1}}' + assert latex(Indexed('x3', Idx('i', Symbol('N')+1))) == r'{x_{3}}_{{i}_{0'+interval+'N}}' + assert latex(Indexed('x4', Idx('i', (Symbol('a'),Symbol('b'))))) == r'{x_{4}}_{{i}_{a'+interval+'b}}' + assert latex(IndexedBase('gamma')) == r'\gamma' + assert latex(IndexedBase('a b')) == r'a b' + assert latex(IndexedBase('a_b')) == r'a_{b}' + + +def test_latex_derivatives(): + # regular "d" for ordinary derivatives + assert latex(diff(x**3, x, evaluate=False)) == \ + r"\frac{d}{d x} x^{3}" + assert latex(diff(sin(x) + x**2, x, evaluate=False)) == \ + r"\frac{d}{d x} \left(x^{2} + \sin{\left(x \right)}\right)" + assert latex(diff(diff(sin(x) + x**2, x, evaluate=False), evaluate=False))\ + == \ + r"\frac{d^{2}}{d x^{2}} \left(x^{2} + \sin{\left(x \right)}\right)" + assert latex(diff(diff(diff(sin(x) + x**2, x, evaluate=False), evaluate=False), evaluate=False)) == \ + r"\frac{d^{3}}{d x^{3}} \left(x^{2} + \sin{\left(x \right)}\right)" + + # \partial for partial derivatives + assert latex(diff(sin(x * y), x, evaluate=False)) == \ + r"\frac{\partial}{\partial x} \sin{\left(x y \right)}" + assert latex(diff(sin(x * y) + x**2, x, evaluate=False)) == \ + r"\frac{\partial}{\partial x} \left(x^{2} + \sin{\left(x y \right)}\right)" + assert latex(diff(diff(sin(x*y) + x**2, x, evaluate=False), x, evaluate=False)) == \ + r"\frac{\partial^{2}}{\partial x^{2}} \left(x^{2} + \sin{\left(x y \right)}\right)" + assert latex(diff(diff(diff(sin(x*y) + x**2, x, evaluate=False), x, evaluate=False), x, evaluate=False)) == \ + r"\frac{\partial^{3}}{\partial x^{3}} \left(x^{2} + \sin{\left(x y \right)}\right)" + + # mixed partial derivatives + f = Function("f") + assert latex(diff(diff(f(x, y), x, evaluate=False), y, evaluate=False)) == \ + r"\frac{\partial^{2}}{\partial y\partial x} " + latex(f(x, y)) + + assert latex(diff(diff(diff(f(x, y), x, evaluate=False), x, evaluate=False), y, evaluate=False)) == \ + r"\frac{\partial^{3}}{\partial y\partial x^{2}} " + latex(f(x, y)) + + # for negative nested Derivative + assert latex(diff(-diff(y**2,x,evaluate=False),x,evaluate=False)) == r'\frac{d}{d x} \left(- \frac{d}{d x} y^{2}\right)' + assert latex(diff(diff(-diff(diff(y,x,evaluate=False),x,evaluate=False),x,evaluate=False),x,evaluate=False)) == \ + r'\frac{d^{2}}{d x^{2}} \left(- \frac{d^{2}}{d x^{2}} y\right)' + + # use ordinary d when one of the variables has been integrated out + assert latex(diff(Integral(exp(-x*y), (x, 0, oo)), y, evaluate=False)) == \ + r"\frac{d}{d y} \int\limits_{0}^{\infty} e^{- x y}\, dx" + + # Derivative wrapped in power: + assert latex(diff(x, x, evaluate=False)**2) == \ + r"\left(\frac{d}{d x} x\right)^{2}" + + assert latex(diff(f(x), x)**2) == \ + r"\left(\frac{d}{d x} f{\left(x \right)}\right)^{2}" + + assert latex(diff(f(x), (x, n))) == \ + r"\frac{d^{n}}{d x^{n}} f{\left(x \right)}" + + x1 = Symbol('x1') + x2 = Symbol('x2') + assert latex(diff(f(x1, x2), x1)) == r'\frac{\partial}{\partial x_{1}} f{\left(x_{1},x_{2} \right)}' + + n1 = Symbol('n1') + assert latex(diff(f(x), (x, n1))) == r'\frac{d^{n_{1}}}{d x^{n_{1}}} f{\left(x \right)}' + + n2 = Symbol('n2') + assert latex(diff(f(x), (x, Max(n1, n2)))) == \ + r'\frac{d^{\max\left(n_{1}, n_{2}\right)}}{d x^{\max\left(n_{1}, n_{2}\right)}} f{\left(x \right)}' + + # set diff operator + assert latex(diff(f(x), x), diff_operator="rd") == r'\frac{\mathrm{d}}{\mathrm{d} x} f{\left(x \right)}' + + +def test_latex_subs(): + assert latex(Subs(x*y, (x, y), (1, 2))) == r'\left. x y \right|_{\substack{ x=1\\ y=2 }}' + + +def test_latex_integrals(): + assert latex(Integral(log(x), x)) == r"\int \log{\left(x \right)}\, dx" + assert latex(Integral(x**2, (x, 0, 1))) == \ + r"\int\limits_{0}^{1} x^{2}\, dx" + assert latex(Integral(x**2, (x, 10, 20))) == \ + r"\int\limits_{10}^{20} x^{2}\, dx" + assert latex(Integral(y*x**2, (x, 0, 1), y)) == \ + r"\int\int\limits_{0}^{1} x^{2} y\, dx\, dy" + assert latex(Integral(y*x**2, (x, 0, 1), y), mode='equation*') == \ + r"\begin{equation*}\int\int\limits_{0}^{1} x^{2} y\, dx\, dy\end{equation*}" + assert latex(Integral(y*x**2, (x, 0, 1), y), mode='equation*', itex=True) \ + == r"$$\int\int_{0}^{1} x^{2} y\, dx\, dy$$" + assert latex(Integral(x, (x, 0))) == r"\int\limits^{0} x\, dx" + assert latex(Integral(x*y, x, y)) == r"\iint x y\, dx\, dy" + assert latex(Integral(x*y*z, x, y, z)) == r"\iiint x y z\, dx\, dy\, dz" + assert latex(Integral(x*y*z*t, x, y, z, t)) == \ + r"\iiiint t x y z\, dx\, dy\, dz\, dt" + assert latex(Integral(x, x, x, x, x, x, x)) == \ + r"\int\int\int\int\int\int x\, dx\, dx\, dx\, dx\, dx\, dx" + assert latex(Integral(x, x, y, (z, 0, 1))) == \ + r"\int\limits_{0}^{1}\int\int x\, dx\, dy\, dz" + + # for negative nested Integral + assert latex(Integral(-Integral(y**2,x),x)) == \ + r'\int \left(- \int y^{2}\, dx\right)\, dx' + assert latex(Integral(-Integral(-Integral(y,x),x),x)) == \ + r'\int \left(- \int \left(- \int y\, dx\right)\, dx\right)\, dx' + + # fix issue #10806 + assert latex(Integral(z, z)**2) == r"\left(\int z\, dz\right)^{2}" + assert latex(Integral(x + z, z)) == r"\int \left(x + z\right)\, dz" + assert latex(Integral(x+z/2, z)) == \ + r"\int \left(x + \frac{z}{2}\right)\, dz" + assert latex(Integral(x**y, z)) == r"\int x^{y}\, dz" + + # set diff operator + assert latex(Integral(x, x), diff_operator="rd") == r'\int x\, \mathrm{d}x' + assert latex(Integral(x, (x, 0, 1)), diff_operator="rd") == r'\int\limits_{0}^{1} x\, \mathrm{d}x' + + +def test_latex_sets(): + for s in (frozenset, set): + assert latex(s([x*y, x**2])) == r"\left\{x^{2}, x y\right\}" + assert latex(s(range(1, 6))) == r"\left\{1, 2, 3, 4, 5\right\}" + assert latex(s(range(1, 13))) == \ + r"\left\{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12\right\}" + + s = FiniteSet + assert latex(s(*[x*y, x**2])) == r"\left\{x^{2}, x y\right\}" + assert latex(s(*range(1, 6))) == r"\left\{1, 2, 3, 4, 5\right\}" + assert latex(s(*range(1, 13))) == \ + r"\left\{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12\right\}" + + +def test_latex_SetExpr(): + iv = Interval(1, 3) + se = SetExpr(iv) + assert latex(se) == r"SetExpr\left(\left[1, 3\right]\right)" + + +def test_latex_Range(): + assert latex(Range(1, 51)) == r'\left\{1, 2, \ldots, 50\right\}' + assert latex(Range(1, 4)) == r'\left\{1, 2, 3\right\}' + assert latex(Range(0, 3, 1)) == r'\left\{0, 1, 2\right\}' + assert latex(Range(0, 30, 1)) == r'\left\{0, 1, \ldots, 29\right\}' + assert latex(Range(30, 1, -1)) == r'\left\{30, 29, \ldots, 2\right\}' + assert latex(Range(0, oo, 2)) == r'\left\{0, 2, \ldots\right\}' + assert latex(Range(oo, -2, -2)) == r'\left\{\ldots, 2, 0\right\}' + assert latex(Range(-2, -oo, -1)) == r'\left\{-2, -3, \ldots\right\}' + assert latex(Range(-oo, oo)) == r'\left\{\ldots, -1, 0, 1, \ldots\right\}' + assert latex(Range(oo, -oo, -1)) == r'\left\{\ldots, 1, 0, -1, \ldots\right\}' + + a, b, c = symbols('a:c') + assert latex(Range(a, b, c)) == r'\text{Range}\left(a, b, c\right)' + assert latex(Range(a, 10, 1)) == r'\text{Range}\left(a, 10\right)' + assert latex(Range(0, b, 1)) == r'\text{Range}\left(b\right)' + assert latex(Range(0, 10, c)) == r'\text{Range}\left(0, 10, c\right)' + + i = Symbol('i', integer=True) + n = Symbol('n', negative=True, integer=True) + p = Symbol('p', positive=True, integer=True) + + assert latex(Range(i, i + 3)) == r'\left\{i, i + 1, i + 2\right\}' + assert latex(Range(-oo, n, 2)) == r'\left\{\ldots, n - 4, n - 2\right\}' + assert latex(Range(p, oo)) == r'\left\{p, p + 1, \ldots\right\}' + # The following will work if __iter__ is improved + # assert latex(Range(-3, p + 7)) == r'\left\{-3, -2, \ldots, p + 6\right\}' + # Must have integer assumptions + assert latex(Range(a, a + 3)) == r'\text{Range}\left(a, a + 3\right)' + + +def test_latex_sequences(): + s1 = SeqFormula(a**2, (0, oo)) + s2 = SeqPer((1, 2)) + + latex_str = r'\left[0, 1, 4, 9, \ldots\right]' + assert latex(s1) == latex_str + + latex_str = r'\left[1, 2, 1, 2, \ldots\right]' + assert latex(s2) == latex_str + + s3 = SeqFormula(a**2, (0, 2)) + s4 = SeqPer((1, 2), (0, 2)) + + latex_str = r'\left[0, 1, 4\right]' + assert latex(s3) == latex_str + + latex_str = r'\left[1, 2, 1\right]' + assert latex(s4) == latex_str + + s5 = SeqFormula(a**2, (-oo, 0)) + s6 = SeqPer((1, 2), (-oo, 0)) + + latex_str = r'\left[\ldots, 9, 4, 1, 0\right]' + assert latex(s5) == latex_str + + latex_str = r'\left[\ldots, 2, 1, 2, 1\right]' + assert latex(s6) == latex_str + + latex_str = r'\left[1, 3, 5, 11, \ldots\right]' + assert latex(SeqAdd(s1, s2)) == latex_str + + latex_str = r'\left[1, 3, 5\right]' + assert latex(SeqAdd(s3, s4)) == latex_str + + latex_str = r'\left[\ldots, 11, 5, 3, 1\right]' + assert latex(SeqAdd(s5, s6)) == latex_str + + latex_str = r'\left[0, 2, 4, 18, \ldots\right]' + assert latex(SeqMul(s1, s2)) == latex_str + + latex_str = r'\left[0, 2, 4\right]' + assert latex(SeqMul(s3, s4)) == latex_str + + latex_str = r'\left[\ldots, 18, 4, 2, 0\right]' + assert latex(SeqMul(s5, s6)) == latex_str + + # Sequences with symbolic limits, issue 12629 + s7 = SeqFormula(a**2, (a, 0, x)) + latex_str = r'\left\{a^{2}\right\}_{a=0}^{x}' + assert latex(s7) == latex_str + + b = Symbol('b') + s8 = SeqFormula(b*a**2, (a, 0, 2)) + latex_str = r'\left[0, b, 4 b\right]' + assert latex(s8) == latex_str + + +def test_latex_FourierSeries(): + latex_str = \ + r'2 \sin{\left(x \right)} - \sin{\left(2 x \right)} + \frac{2 \sin{\left(3 x \right)}}{3} + \ldots' + assert latex(fourier_series(x, (x, -pi, pi))) == latex_str + + +def test_latex_FormalPowerSeries(): + latex_str = r'\sum_{k=1}^{\infty} - \frac{\left(-1\right)^{- k} x^{k}}{k}' + assert latex(fps(log(1 + x))) == latex_str + + +def test_latex_intervals(): + a = Symbol('a', real=True) + assert latex(Interval(0, 0)) == r"\left\{0\right\}" + assert latex(Interval(0, a)) == r"\left[0, a\right]" + assert latex(Interval(0, a, False, False)) == r"\left[0, a\right]" + assert latex(Interval(0, a, True, False)) == r"\left(0, a\right]" + assert latex(Interval(0, a, False, True)) == r"\left[0, a\right)" + assert latex(Interval(0, a, True, True)) == r"\left(0, a\right)" + + +def test_latex_AccumuBounds(): + a = Symbol('a', real=True) + assert latex(AccumBounds(0, 1)) == r"\left\langle 0, 1\right\rangle" + assert latex(AccumBounds(0, a)) == r"\left\langle 0, a\right\rangle" + assert latex(AccumBounds(a + 1, a + 2)) == \ + r"\left\langle a + 1, a + 2\right\rangle" + + +def test_latex_emptyset(): + assert latex(S.EmptySet) == r"\emptyset" + + +def test_latex_universalset(): + assert latex(S.UniversalSet) == r"\mathbb{U}" + + +def test_latex_commutator(): + A = Operator('A') + B = Operator('B') + comm = Commutator(B, A) + assert latex(comm.doit()) == r"- (A B - B A)" + + +def test_latex_union(): + assert latex(Union(Interval(0, 1), Interval(2, 3))) == \ + r"\left[0, 1\right] \cup \left[2, 3\right]" + assert latex(Union(Interval(1, 1), Interval(2, 2), Interval(3, 4))) == \ + r"\left\{1, 2\right\} \cup \left[3, 4\right]" + + +def test_latex_intersection(): + assert latex(Intersection(Interval(0, 1), Interval(x, y))) == \ + r"\left[0, 1\right] \cap \left[x, y\right]" + + +def test_latex_symmetric_difference(): + assert latex(SymmetricDifference(Interval(2, 5), Interval(4, 7), + evaluate=False)) == \ + r'\left[2, 5\right] \triangle \left[4, 7\right]' + + +def test_latex_Complement(): + assert latex(Complement(S.Reals, S.Naturals)) == \ + r"\mathbb{R} \setminus \mathbb{N}" + + +def test_latex_productset(): + line = Interval(0, 1) + bigline = Interval(0, 10) + fset = FiniteSet(1, 2, 3) + assert latex(line**2) == r"%s^{2}" % latex(line) + assert latex(line**10) == r"%s^{10}" % latex(line) + assert latex((line * bigline * fset).flatten()) == r"%s \times %s \times %s" % ( + latex(line), latex(bigline), latex(fset)) + + +def test_latex_powerset(): + fset = FiniteSet(1, 2, 3) + assert latex(PowerSet(fset)) == r'\mathcal{P}\left(\left\{1, 2, 3\right\}\right)' + + +def test_latex_ordinals(): + w = OrdinalOmega() + assert latex(w) == r"\omega" + wp = OmegaPower(2, 3) + assert latex(wp) == r'3 \omega^{2}' + assert latex(Ordinal(wp, OmegaPower(1, 1))) == r'3 \omega^{2} + \omega' + assert latex(Ordinal(OmegaPower(2, 1), OmegaPower(1, 2))) == r'\omega^{2} + 2 \omega' + + +def test_set_operators_parenthesis(): + a, b, c, d = symbols('a:d') + A = FiniteSet(a) + B = FiniteSet(b) + C = FiniteSet(c) + D = FiniteSet(d) + + U1 = Union(A, B, evaluate=False) + U2 = Union(C, D, evaluate=False) + I1 = Intersection(A, B, evaluate=False) + I2 = Intersection(C, D, evaluate=False) + C1 = Complement(A, B, evaluate=False) + C2 = Complement(C, D, evaluate=False) + D1 = SymmetricDifference(A, B, evaluate=False) + D2 = SymmetricDifference(C, D, evaluate=False) + # XXX ProductSet does not support evaluate keyword + P1 = ProductSet(A, B) + P2 = ProductSet(C, D) + + assert latex(Intersection(A, U2, evaluate=False)) == \ + r'\left\{a\right\} \cap ' \ + r'\left(\left\{c\right\} \cup \left\{d\right\}\right)' + assert latex(Intersection(U1, U2, evaluate=False)) == \ + r'\left(\left\{a\right\} \cup \left\{b\right\}\right) ' \ + r'\cap \left(\left\{c\right\} \cup \left\{d\right\}\right)' + assert latex(Intersection(C1, C2, evaluate=False)) == \ + r'\left(\left\{a\right\} \setminus ' \ + r'\left\{b\right\}\right) \cap \left(\left\{c\right\} ' \ + r'\setminus \left\{d\right\}\right)' + assert latex(Intersection(D1, D2, evaluate=False)) == \ + r'\left(\left\{a\right\} \triangle ' \ + r'\left\{b\right\}\right) \cap \left(\left\{c\right\} ' \ + r'\triangle \left\{d\right\}\right)' + assert latex(Intersection(P1, P2, evaluate=False)) == \ + r'\left(\left\{a\right\} \times \left\{b\right\}\right) ' \ + r'\cap \left(\left\{c\right\} \times ' \ + r'\left\{d\right\}\right)' + + assert latex(Union(A, I2, evaluate=False)) == \ + r'\left\{a\right\} \cup ' \ + r'\left(\left\{c\right\} \cap \left\{d\right\}\right)' + assert latex(Union(I1, I2, evaluate=False)) == \ + r'\left(\left\{a\right\} \cap \left\{b\right\}\right) ' \ + r'\cup \left(\left\{c\right\} \cap \left\{d\right\}\right)' + assert latex(Union(C1, C2, evaluate=False)) == \ + r'\left(\left\{a\right\} \setminus ' \ + r'\left\{b\right\}\right) \cup \left(\left\{c\right\} ' \ + r'\setminus \left\{d\right\}\right)' + assert latex(Union(D1, D2, evaluate=False)) == \ + r'\left(\left\{a\right\} \triangle ' \ + r'\left\{b\right\}\right) \cup \left(\left\{c\right\} ' \ + r'\triangle \left\{d\right\}\right)' + assert latex(Union(P1, P2, evaluate=False)) == \ + r'\left(\left\{a\right\} \times \left\{b\right\}\right) ' \ + r'\cup \left(\left\{c\right\} \times ' \ + r'\left\{d\right\}\right)' + + assert latex(Complement(A, C2, evaluate=False)) == \ + r'\left\{a\right\} \setminus \left(\left\{c\right\} ' \ + r'\setminus \left\{d\right\}\right)' + assert latex(Complement(U1, U2, evaluate=False)) == \ + r'\left(\left\{a\right\} \cup \left\{b\right\}\right) ' \ + r'\setminus \left(\left\{c\right\} \cup ' \ + r'\left\{d\right\}\right)' + assert latex(Complement(I1, I2, evaluate=False)) == \ + r'\left(\left\{a\right\} \cap \left\{b\right\}\right) ' \ + r'\setminus \left(\left\{c\right\} \cap ' \ + r'\left\{d\right\}\right)' + assert latex(Complement(D1, D2, evaluate=False)) == \ + r'\left(\left\{a\right\} \triangle ' \ + r'\left\{b\right\}\right) \setminus ' \ + r'\left(\left\{c\right\} \triangle \left\{d\right\}\right)' + assert latex(Complement(P1, P2, evaluate=False)) == \ + r'\left(\left\{a\right\} \times \left\{b\right\}\right) '\ + r'\setminus \left(\left\{c\right\} \times '\ + r'\left\{d\right\}\right)' + + assert latex(SymmetricDifference(A, D2, evaluate=False)) == \ + r'\left\{a\right\} \triangle \left(\left\{c\right\} ' \ + r'\triangle \left\{d\right\}\right)' + assert latex(SymmetricDifference(U1, U2, evaluate=False)) == \ + r'\left(\left\{a\right\} \cup \left\{b\right\}\right) ' \ + r'\triangle \left(\left\{c\right\} \cup ' \ + r'\left\{d\right\}\right)' + assert latex(SymmetricDifference(I1, I2, evaluate=False)) == \ + r'\left(\left\{a\right\} \cap \left\{b\right\}\right) ' \ + r'\triangle \left(\left\{c\right\} \cap ' \ + r'\left\{d\right\}\right)' + assert latex(SymmetricDifference(C1, C2, evaluate=False)) == \ + r'\left(\left\{a\right\} \setminus ' \ + r'\left\{b\right\}\right) \triangle ' \ + r'\left(\left\{c\right\} \setminus \left\{d\right\}\right)' + assert latex(SymmetricDifference(P1, P2, evaluate=False)) == \ + r'\left(\left\{a\right\} \times \left\{b\right\}\right) ' \ + r'\triangle \left(\left\{c\right\} \times ' \ + r'\left\{d\right\}\right)' + + # XXX This can be incorrect since cartesian product is not associative + assert latex(ProductSet(A, P2).flatten()) == \ + r'\left\{a\right\} \times \left\{c\right\} \times ' \ + r'\left\{d\right\}' + assert latex(ProductSet(U1, U2)) == \ + r'\left(\left\{a\right\} \cup \left\{b\right\}\right) ' \ + r'\times \left(\left\{c\right\} \cup ' \ + r'\left\{d\right\}\right)' + assert latex(ProductSet(I1, I2)) == \ + r'\left(\left\{a\right\} \cap \left\{b\right\}\right) ' \ + r'\times \left(\left\{c\right\} \cap ' \ + r'\left\{d\right\}\right)' + assert latex(ProductSet(C1, C2)) == \ + r'\left(\left\{a\right\} \setminus ' \ + r'\left\{b\right\}\right) \times \left(\left\{c\right\} ' \ + r'\setminus \left\{d\right\}\right)' + assert latex(ProductSet(D1, D2)) == \ + r'\left(\left\{a\right\} \triangle ' \ + r'\left\{b\right\}\right) \times \left(\left\{c\right\} ' \ + r'\triangle \left\{d\right\}\right)' + + +def test_latex_Complexes(): + assert latex(S.Complexes) == r"\mathbb{C}" + + +def test_latex_Naturals(): + assert latex(S.Naturals) == r"\mathbb{N}" + + +def test_latex_Naturals0(): + assert latex(S.Naturals0) == r"\mathbb{N}_0" + + +def test_latex_Integers(): + assert latex(S.Integers) == r"\mathbb{Z}" + + +def test_latex_ImageSet(): + x = Symbol('x') + assert latex(ImageSet(Lambda(x, x**2), S.Naturals)) == \ + r"\left\{x^{2}\; \middle|\; x \in \mathbb{N}\right\}" + + y = Symbol('y') + imgset = ImageSet(Lambda((x, y), x + y), {1, 2, 3}, {3, 4}) + assert latex(imgset) == \ + r"\left\{x + y\; \middle|\; x \in \left\{1, 2, 3\right\}, y \in \left\{3, 4\right\}\right\}" + + imgset = ImageSet(Lambda(((x, y),), x + y), ProductSet({1, 2, 3}, {3, 4})) + assert latex(imgset) == \ + r"\left\{x + y\; \middle|\; \left( x, \ y\right) \in \left\{1, 2, 3\right\} \times \left\{3, 4\right\}\right\}" + + +def test_latex_ConditionSet(): + x = Symbol('x') + assert latex(ConditionSet(x, Eq(x**2, 1), S.Reals)) == \ + r"\left\{x\; \middle|\; x \in \mathbb{R} \wedge x^{2} = 1 \right\}" + assert latex(ConditionSet(x, Eq(x**2, 1), S.UniversalSet)) == \ + r"\left\{x\; \middle|\; x^{2} = 1 \right\}" + + +def test_latex_ComplexRegion(): + assert latex(ComplexRegion(Interval(3, 5)*Interval(4, 6))) == \ + r"\left\{x + y i\; \middle|\; x, y \in \left[3, 5\right] \times \left[4, 6\right] \right\}" + assert latex(ComplexRegion(Interval(0, 1)*Interval(0, 2*pi), polar=True)) == \ + r"\left\{r \left(i \sin{\left(\theta \right)} + \cos{\left(\theta "\ + r"\right)}\right)\; \middle|\; r, \theta \in \left[0, 1\right] \times \left[0, 2 \pi\right) \right\}" + + +def test_latex_Contains(): + x = Symbol('x') + assert latex(Contains(x, S.Naturals)) == r"x \in \mathbb{N}" + + +def test_latex_sum(): + assert latex(Sum(x*y**2, (x, -2, 2), (y, -5, 5))) == \ + r"\sum_{\substack{-2 \leq x \leq 2\\-5 \leq y \leq 5}} x y^{2}" + assert latex(Sum(x**2, (x, -2, 2))) == \ + r"\sum_{x=-2}^{2} x^{2}" + assert latex(Sum(x**2 + y, (x, -2, 2))) == \ + r"\sum_{x=-2}^{2} \left(x^{2} + y\right)" + assert latex(Sum(x**2 + y, (x, -2, 2))**2) == \ + r"\left(\sum_{x=-2}^{2} \left(x^{2} + y\right)\right)^{2}" + + +def test_latex_product(): + assert latex(Product(x*y**2, (x, -2, 2), (y, -5, 5))) == \ + r"\prod_{\substack{-2 \leq x \leq 2\\-5 \leq y \leq 5}} x y^{2}" + assert latex(Product(x**2, (x, -2, 2))) == \ + r"\prod_{x=-2}^{2} x^{2}" + assert latex(Product(x**2 + y, (x, -2, 2))) == \ + r"\prod_{x=-2}^{2} \left(x^{2} + y\right)" + + assert latex(Product(x, (x, -2, 2))**2) == \ + r"\left(\prod_{x=-2}^{2} x\right)^{2}" + + +def test_latex_limits(): + assert latex(Limit(x, x, oo)) == r"\lim_{x \to \infty} x" + + # issue 8175 + f = Function('f') + assert latex(Limit(f(x), x, 0)) == r"\lim_{x \to 0^+} f{\left(x \right)}" + assert latex(Limit(f(x), x, 0, "-")) == \ + r"\lim_{x \to 0^-} f{\left(x \right)}" + + # issue #10806 + assert latex(Limit(f(x), x, 0)**2) == \ + r"\left(\lim_{x \to 0^+} f{\left(x \right)}\right)^{2}" + # bi-directional limit + assert latex(Limit(f(x), x, 0, dir='+-')) == \ + r"\lim_{x \to 0} f{\left(x \right)}" + + +def test_latex_log(): + assert latex(log(x)) == r"\log{\left(x \right)}" + assert latex(log(x), ln_notation=True) == r"\ln{\left(x \right)}" + assert latex(log(x) + log(y)) == \ + r"\log{\left(x \right)} + \log{\left(y \right)}" + assert latex(log(x) + log(y), ln_notation=True) == \ + r"\ln{\left(x \right)} + \ln{\left(y \right)}" + assert latex(pow(log(x), x)) == r"\log{\left(x \right)}^{x}" + assert latex(pow(log(x), x), ln_notation=True) == \ + r"\ln{\left(x \right)}^{x}" + + +def test_issue_3568(): + beta = Symbol(r'\beta') + y = beta + x + assert latex(y) in [r'\beta + x', r'x + \beta'] + + beta = Symbol(r'beta') + y = beta + x + assert latex(y) in [r'\beta + x', r'x + \beta'] + + +def test_latex(): + assert latex((2*tau)**Rational(7, 2)) == r"8 \sqrt{2} \tau^{\frac{7}{2}}" + assert latex((2*mu)**Rational(7, 2), mode='equation*') == \ + r"\begin{equation*}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation*}" + assert latex((2*mu)**Rational(7, 2), mode='equation', itex=True) == \ + r"$$8 \sqrt{2} \mu^{\frac{7}{2}}$$" + assert latex([2/x, y]) == r"\left[ \frac{2}{x}, \ y\right]" + + +def test_latex_dict(): + d = {Rational(1): 1, x**2: 2, x: 3, x**3: 4} + assert latex(d) == \ + r'\left\{ 1 : 1, \ x : 3, \ x^{2} : 2, \ x^{3} : 4\right\}' + D = Dict(d) + assert latex(D) == \ + r'\left\{ 1 : 1, \ x : 3, \ x^{2} : 2, \ x^{3} : 4\right\}' + + +def test_latex_list(): + ll = [Symbol('omega1'), Symbol('a'), Symbol('alpha')] + assert latex(ll) == r'\left[ \omega_{1}, \ a, \ \alpha\right]' + + +def test_latex_NumberSymbols(): + assert latex(S.Catalan) == "G" + assert latex(S.EulerGamma) == r"\gamma" + assert latex(S.Exp1) == "e" + assert latex(S.GoldenRatio) == r"\phi" + assert latex(S.Pi) == r"\pi" + assert latex(S.TribonacciConstant) == r"\text{TribonacciConstant}" + + +def test_latex_rational(): + # tests issue 3973 + assert latex(-Rational(1, 2)) == r"- \frac{1}{2}" + assert latex(Rational(-1, 2)) == r"- \frac{1}{2}" + assert latex(Rational(1, -2)) == r"- \frac{1}{2}" + assert latex(-Rational(-1, 2)) == r"\frac{1}{2}" + assert latex(-Rational(1, 2)*x) == r"- \frac{x}{2}" + assert latex(-Rational(1, 2)*x + Rational(-2, 3)*y) == \ + r"- \frac{x}{2} - \frac{2 y}{3}" + + +def test_latex_inverse(): + # tests issue 4129 + assert latex(1/x) == r"\frac{1}{x}" + assert latex(1/(x + y)) == r"\frac{1}{x + y}" + + +def test_latex_DiracDelta(): + assert latex(DiracDelta(x)) == r"\delta\left(x\right)" + assert latex(DiracDelta(x)**2) == r"\left(\delta\left(x\right)\right)^{2}" + assert latex(DiracDelta(x, 0)) == r"\delta\left(x\right)" + assert latex(DiracDelta(x, 5)) == \ + r"\delta^{\left( 5 \right)}\left( x \right)" + assert latex(DiracDelta(x, 5)**2) == \ + r"\left(\delta^{\left( 5 \right)}\left( x \right)\right)^{2}" + + +def test_latex_Heaviside(): + assert latex(Heaviside(x)) == r"\theta\left(x\right)" + assert latex(Heaviside(x)**2) == r"\left(\theta\left(x\right)\right)^{2}" + + +def test_latex_KroneckerDelta(): + assert latex(KroneckerDelta(x, y)) == r"\delta_{x y}" + assert latex(KroneckerDelta(x, y + 1)) == r"\delta_{x, y + 1}" + # issue 6578 + assert latex(KroneckerDelta(x + 1, y)) == r"\delta_{y, x + 1}" + assert latex(Pow(KroneckerDelta(x, y), 2, evaluate=False)) == \ + r"\left(\delta_{x y}\right)^{2}" + + +def test_latex_LeviCivita(): + assert latex(LeviCivita(x, y, z)) == r"\varepsilon_{x y z}" + assert latex(LeviCivita(x, y, z)**2) == \ + r"\left(\varepsilon_{x y z}\right)^{2}" + assert latex(LeviCivita(x, y, z + 1)) == r"\varepsilon_{x, y, z + 1}" + assert latex(LeviCivita(x, y + 1, z)) == r"\varepsilon_{x, y + 1, z}" + assert latex(LeviCivita(x + 1, y, z)) == r"\varepsilon_{x + 1, y, z}" + + +def test_mode(): + expr = x + y + assert latex(expr) == r'x + y' + assert latex(expr, mode='plain') == r'x + y' + assert latex(expr, mode='inline') == r'$x + y$' + assert latex( + expr, mode='equation*') == r'\begin{equation*}x + y\end{equation*}' + assert latex( + expr, mode='equation') == r'\begin{equation}x + y\end{equation}' + raises(ValueError, lambda: latex(expr, mode='foo')) + + +def test_latex_mathieu(): + assert latex(mathieuc(x, y, z)) == r"C\left(x, y, z\right)" + assert latex(mathieus(x, y, z)) == r"S\left(x, y, z\right)" + assert latex(mathieuc(x, y, z)**2) == r"C\left(x, y, z\right)^{2}" + assert latex(mathieus(x, y, z)**2) == r"S\left(x, y, z\right)^{2}" + assert latex(mathieucprime(x, y, z)) == r"C^{\prime}\left(x, y, z\right)" + assert latex(mathieusprime(x, y, z)) == r"S^{\prime}\left(x, y, z\right)" + assert latex(mathieucprime(x, y, z)**2) == r"C^{\prime}\left(x, y, z\right)^{2}" + assert latex(mathieusprime(x, y, z)**2) == r"S^{\prime}\left(x, y, z\right)^{2}" + +def test_latex_Piecewise(): + p = Piecewise((x, x < 1), (x**2, True)) + assert latex(p) == r"\begin{cases} x & \text{for}\: x < 1 \\x^{2} &" \ + r" \text{otherwise} \end{cases}" + assert latex(p, itex=True) == \ + r"\begin{cases} x & \text{for}\: x \lt 1 \\x^{2} &" \ + r" \text{otherwise} \end{cases}" + p = Piecewise((x, x < 0), (0, x >= 0)) + assert latex(p) == r'\begin{cases} x & \text{for}\: x < 0 \\0 &' \ + r' \text{otherwise} \end{cases}' + A, B = symbols("A B", commutative=False) + p = Piecewise((A**2, Eq(A, B)), (A*B, True)) + s = r"\begin{cases} A^{2} & \text{for}\: A = B \\A B & \text{otherwise} \end{cases}" + assert latex(p) == s + assert latex(A*p) == r"A \left(%s\right)" % s + assert latex(p*A) == r"\left(%s\right) A" % s + assert latex(Piecewise((x, x < 1), (x**2, x < 2))) == \ + r'\begin{cases} x & ' \ + r'\text{for}\: x < 1 \\x^{2} & \text{for}\: x < 2 \end{cases}' + + +def test_latex_Matrix(): + M = Matrix([[1 + x, y], [y, x - 1]]) + assert latex(M) == \ + r'\left[\begin{matrix}x + 1 & y\\y & x - 1\end{matrix}\right]' + assert latex(M, mode='inline') == \ + r'$\left[\begin{smallmatrix}x + 1 & y\\' \ + r'y & x - 1\end{smallmatrix}\right]$' + assert latex(M, mat_str='array') == \ + r'\left[\begin{array}{cc}x + 1 & y\\y & x - 1\end{array}\right]' + assert latex(M, mat_str='bmatrix') == \ + r'\left[\begin{bmatrix}x + 1 & y\\y & x - 1\end{bmatrix}\right]' + assert latex(M, mat_delim=None, mat_str='bmatrix') == \ + r'\begin{bmatrix}x + 1 & y\\y & x - 1\end{bmatrix}' + + M2 = Matrix(1, 11, range(11)) + assert latex(M2) == \ + r'\left[\begin{array}{ccccccccccc}' \ + r'0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10\end{array}\right]' + + +def test_latex_matrix_with_functions(): + t = symbols('t') + theta1 = symbols('theta1', cls=Function) + + M = Matrix([[sin(theta1(t)), cos(theta1(t))], + [cos(theta1(t).diff(t)), sin(theta1(t).diff(t))]]) + + expected = (r'\left[\begin{matrix}\sin{\left(' + r'\theta_{1}{\left(t \right)} \right)} & ' + r'\cos{\left(\theta_{1}{\left(t \right)} \right)' + r'}\\\cos{\left(\frac{d}{d t} \theta_{1}{\left(t ' + r'\right)} \right)} & \sin{\left(\frac{d}{d t} ' + r'\theta_{1}{\left(t \right)} \right' + r')}\end{matrix}\right]') + + assert latex(M) == expected + + +def test_latex_NDimArray(): + x, y, z, w = symbols("x y z w") + + for ArrayType in (ImmutableDenseNDimArray, ImmutableSparseNDimArray, + MutableDenseNDimArray, MutableSparseNDimArray): + # Basic: scalar array + M = ArrayType(x) + + assert latex(M) == r"x" + + M = ArrayType([[1 / x, y], [z, w]]) + M1 = ArrayType([1 / x, y, z]) + + M2 = tensorproduct(M1, M) + M3 = tensorproduct(M, M) + + assert latex(M) == \ + r'\left[\begin{matrix}\frac{1}{x} & y\\z & w\end{matrix}\right]' + assert latex(M1) == \ + r"\left[\begin{matrix}\frac{1}{x} & y & z\end{matrix}\right]" + assert latex(M2) == \ + r"\left[\begin{matrix}" \ + r"\left[\begin{matrix}\frac{1}{x^{2}} & \frac{y}{x}\\\frac{z}{x} & \frac{w}{x}\end{matrix}\right] & " \ + r"\left[\begin{matrix}\frac{y}{x} & y^{2}\\y z & w y\end{matrix}\right] & " \ + r"\left[\begin{matrix}\frac{z}{x} & y z\\z^{2} & w z\end{matrix}\right]" \ + r"\end{matrix}\right]" + assert latex(M3) == \ + r"""\left[\begin{matrix}"""\ + r"""\left[\begin{matrix}\frac{1}{x^{2}} & \frac{y}{x}\\\frac{z}{x} & \frac{w}{x}\end{matrix}\right] & """\ + r"""\left[\begin{matrix}\frac{y}{x} & y^{2}\\y z & w y\end{matrix}\right]\\"""\ + r"""\left[\begin{matrix}\frac{z}{x} & y z\\z^{2} & w z\end{matrix}\right] & """\ + r"""\left[\begin{matrix}\frac{w}{x} & w y\\w z & w^{2}\end{matrix}\right]"""\ + r"""\end{matrix}\right]""" + + Mrow = ArrayType([[x, y, 1/z]]) + Mcolumn = ArrayType([[x], [y], [1/z]]) + Mcol2 = ArrayType([Mcolumn.tolist()]) + + assert latex(Mrow) == \ + r"\left[\left[\begin{matrix}x & y & \frac{1}{z}\end{matrix}\right]\right]" + assert latex(Mcolumn) == \ + r"\left[\begin{matrix}x\\y\\\frac{1}{z}\end{matrix}\right]" + assert latex(Mcol2) == \ + r'\left[\begin{matrix}\left[\begin{matrix}x\\y\\\frac{1}{z}\end{matrix}\right]\end{matrix}\right]' + + +def test_latex_mul_symbol(): + assert latex(4*4**x, mul_symbol='times') == r"4 \times 4^{x}" + assert latex(4*4**x, mul_symbol='dot') == r"4 \cdot 4^{x}" + assert latex(4*4**x, mul_symbol='ldot') == r"4 \,.\, 4^{x}" + + assert latex(4*x, mul_symbol='times') == r"4 \times x" + assert latex(4*x, mul_symbol='dot') == r"4 \cdot x" + assert latex(4*x, mul_symbol='ldot') == r"4 \,.\, x" + + +def test_latex_issue_4381(): + y = 4*4**log(2) + assert latex(y) == r'4 \cdot 4^{\log{\left(2 \right)}}' + assert latex(1/y) == r'\frac{1}{4 \cdot 4^{\log{\left(2 \right)}}}' + + +def test_latex_issue_4576(): + assert latex(Symbol("beta_13_2")) == r"\beta_{13 2}" + assert latex(Symbol("beta_132_20")) == r"\beta_{132 20}" + assert latex(Symbol("beta_13")) == r"\beta_{13}" + assert latex(Symbol("x_a_b")) == r"x_{a b}" + assert latex(Symbol("x_1_2_3")) == r"x_{1 2 3}" + assert latex(Symbol("x_a_b1")) == r"x_{a b1}" + assert latex(Symbol("x_a_1")) == r"x_{a 1}" + assert latex(Symbol("x_1_a")) == r"x_{1 a}" + assert latex(Symbol("x_1^aa")) == r"x^{aa}_{1}" + assert latex(Symbol("x_1__aa")) == r"x^{aa}_{1}" + assert latex(Symbol("x_11^a")) == r"x^{a}_{11}" + assert latex(Symbol("x_11__a")) == r"x^{a}_{11}" + assert latex(Symbol("x_a_a_a_a")) == r"x_{a a a a}" + assert latex(Symbol("x_a_a^a^a")) == r"x^{a a}_{a a}" + assert latex(Symbol("x_a_a__a__a")) == r"x^{a a}_{a a}" + assert latex(Symbol("alpha_11")) == r"\alpha_{11}" + assert latex(Symbol("alpha_11_11")) == r"\alpha_{11 11}" + assert latex(Symbol("alpha_alpha")) == r"\alpha_{\alpha}" + assert latex(Symbol("alpha^aleph")) == r"\alpha^{\aleph}" + assert latex(Symbol("alpha__aleph")) == r"\alpha^{\aleph}" + + +def test_latex_pow_fraction(): + x = Symbol('x') + # Testing exp + assert r'e^{-x}' in latex(exp(-x)/2).replace(' ', '') # Remove Whitespace + + # Testing e^{-x} in case future changes alter behavior of muls or fracs + # In particular current output is \frac{1}{2}e^{- x} but perhaps this will + # change to \frac{e^{-x}}{2} + + # Testing general, non-exp, power + assert r'3^{-x}' in latex(3**-x/2).replace(' ', '') + + +def test_noncommutative(): + A, B, C = symbols('A,B,C', commutative=False) + + assert latex(A*B*C**-1) == r"A B C^{-1}" + assert latex(C**-1*A*B) == r"C^{-1} A B" + assert latex(A*C**-1*B) == r"A C^{-1} B" + + +def test_latex_order(): + expr = x**3 + x**2*y + y**4 + 3*x*y**3 + + assert latex(expr, order='lex') == r"x^{3} + x^{2} y + 3 x y^{3} + y^{4}" + assert latex( + expr, order='rev-lex') == r"y^{4} + 3 x y^{3} + x^{2} y + x^{3}" + assert latex(expr, order='none') == r"x^{3} + y^{4} + y x^{2} + 3 x y^{3}" + + +def test_latex_Lambda(): + assert latex(Lambda(x, x + 1)) == r"\left( x \mapsto x + 1 \right)" + assert latex(Lambda((x, y), x + 1)) == r"\left( \left( x, \ y\right) \mapsto x + 1 \right)" + assert latex(Lambda(x, x)) == r"\left( x \mapsto x \right)" + +def test_latex_PolyElement(): + Ruv, u, v = ring("u,v", ZZ) + Rxyz, x, y, z = ring("x,y,z", Ruv) + + assert latex(x - x) == r"0" + assert latex(x - 1) == r"x - 1" + assert latex(x + 1) == r"x + 1" + + assert latex((u**2 + 3*u*v + 1)*x**2*y + u + 1) == \ + r"\left({u}^{2} + 3 u v + 1\right) {x}^{2} y + u + 1" + assert latex((u**2 + 3*u*v + 1)*x**2*y + (u + 1)*x) == \ + r"\left({u}^{2} + 3 u v + 1\right) {x}^{2} y + \left(u + 1\right) x" + assert latex((u**2 + 3*u*v + 1)*x**2*y + (u + 1)*x + 1) == \ + r"\left({u}^{2} + 3 u v + 1\right) {x}^{2} y + \left(u + 1\right) x + 1" + assert latex((-u**2 + 3*u*v - 1)*x**2*y - (u + 1)*x - 1) == \ + r"-\left({u}^{2} - 3 u v + 1\right) {x}^{2} y - \left(u + 1\right) x - 1" + + assert latex(-(v**2 + v + 1)*x + 3*u*v + 1) == \ + r"-\left({v}^{2} + v + 1\right) x + 3 u v + 1" + assert latex(-(v**2 + v + 1)*x - 3*u*v + 1) == \ + r"-\left({v}^{2} + v + 1\right) x - 3 u v + 1" + + +def test_latex_FracElement(): + Fuv, u, v = field("u,v", ZZ) + Fxyzt, x, y, z, t = field("x,y,z,t", Fuv) + + assert latex(x - x) == r"0" + assert latex(x - 1) == r"x - 1" + assert latex(x + 1) == r"x + 1" + + assert latex(x/3) == r"\frac{x}{3}" + assert latex(x/z) == r"\frac{x}{z}" + assert latex(x*y/z) == r"\frac{x y}{z}" + assert latex(x/(z*t)) == r"\frac{x}{z t}" + assert latex(x*y/(z*t)) == r"\frac{x y}{z t}" + + assert latex((x - 1)/y) == r"\frac{x - 1}{y}" + assert latex((x + 1)/y) == r"\frac{x + 1}{y}" + assert latex((-x - 1)/y) == r"\frac{-x - 1}{y}" + assert latex((x + 1)/(y*z)) == r"\frac{x + 1}{y z}" + assert latex(-y/(x + 1)) == r"\frac{-y}{x + 1}" + assert latex(y*z/(x + 1)) == r"\frac{y z}{x + 1}" + + assert latex(((u + 1)*x*y + 1)/((v - 1)*z - 1)) == \ + r"\frac{\left(u + 1\right) x y + 1}{\left(v - 1\right) z - 1}" + assert latex(((u + 1)*x*y + 1)/((v - 1)*z - t*u*v - 1)) == \ + r"\frac{\left(u + 1\right) x y + 1}{\left(v - 1\right) z - u v t - 1}" + + +def test_latex_Poly(): + assert latex(Poly(x**2 + 2 * x, x)) == \ + r"\operatorname{Poly}{\left( x^{2} + 2 x, x, domain=\mathbb{Z} \right)}" + assert latex(Poly(x/y, x)) == \ + r"\operatorname{Poly}{\left( \frac{1}{y} x, x, domain=\mathbb{Z}\left(y\right) \right)}" + assert latex(Poly(2.0*x + y)) == \ + r"\operatorname{Poly}{\left( 2.0 x + 1.0 y, x, y, domain=\mathbb{R} \right)}" + + +def test_latex_Poly_order(): + assert latex(Poly([a, 1, b, 2, c, 3], x)) == \ + r'\operatorname{Poly}{\left( a x^{5} + x^{4} + b x^{3} + 2 x^{2} + c'\ + r' x + 3, x, domain=\mathbb{Z}\left[a, b, c\right] \right)}' + assert latex(Poly([a, 1, b+c, 2, 3], x)) == \ + r'\operatorname{Poly}{\left( a x^{4} + x^{3} + \left(b + c\right) '\ + r'x^{2} + 2 x + 3, x, domain=\mathbb{Z}\left[a, b, c\right] \right)}' + assert latex(Poly(a*x**3 + x**2*y - x*y - c*y**3 - b*x*y**2 + y - a*x + b, + (x, y))) == \ + r'\operatorname{Poly}{\left( a x^{3} + x^{2}y - b xy^{2} - xy - '\ + r'a x - c y^{3} + y + b, x, y, domain=\mathbb{Z}\left[a, b, c\right] \right)}' + + +def test_latex_ComplexRootOf(): + assert latex(rootof(x**5 + x + 3, 0)) == \ + r"\operatorname{CRootOf} {\left(x^{5} + x + 3, 0\right)}" + + +def test_latex_RootSum(): + assert latex(RootSum(x**5 + x + 3, sin)) == \ + r"\operatorname{RootSum} {\left(x^{5} + x + 3, \left( x \mapsto \sin{\left(x \right)} \right)\right)}" + + +def test_settings(): + raises(TypeError, lambda: latex(x*y, method="garbage")) + + +def test_latex_numbers(): + assert latex(catalan(n)) == r"C_{n}" + assert latex(catalan(n)**2) == r"C_{n}^{2}" + assert latex(bernoulli(n)) == r"B_{n}" + assert latex(bernoulli(n, x)) == r"B_{n}\left(x\right)" + assert latex(bernoulli(n)**2) == r"B_{n}^{2}" + assert latex(bernoulli(n, x)**2) == r"B_{n}^{2}\left(x\right)" + assert latex(genocchi(n)) == r"G_{n}" + assert latex(genocchi(n, x)) == r"G_{n}\left(x\right)" + assert latex(genocchi(n)**2) == r"G_{n}^{2}" + assert latex(genocchi(n, x)**2) == r"G_{n}^{2}\left(x\right)" + assert latex(bell(n)) == r"B_{n}" + assert latex(bell(n, x)) == r"B_{n}\left(x\right)" + assert latex(bell(n, m, (x, y))) == r"B_{n, m}\left(x, y\right)" + assert latex(bell(n)**2) == r"B_{n}^{2}" + assert latex(bell(n, x)**2) == r"B_{n}^{2}\left(x\right)" + assert latex(bell(n, m, (x, y))**2) == r"B_{n, m}^{2}\left(x, y\right)" + assert latex(fibonacci(n)) == r"F_{n}" + assert latex(fibonacci(n, x)) == r"F_{n}\left(x\right)" + assert latex(fibonacci(n)**2) == r"F_{n}^{2}" + assert latex(fibonacci(n, x)**2) == r"F_{n}^{2}\left(x\right)" + assert latex(lucas(n)) == r"L_{n}" + assert latex(lucas(n)**2) == r"L_{n}^{2}" + assert latex(tribonacci(n)) == r"T_{n}" + assert latex(tribonacci(n, x)) == r"T_{n}\left(x\right)" + assert latex(tribonacci(n)**2) == r"T_{n}^{2}" + assert latex(tribonacci(n, x)**2) == r"T_{n}^{2}\left(x\right)" + assert latex(mobius(n)) == r"\mu\left(n\right)" + assert latex(mobius(n)**2) == r"\mu^{2}\left(n\right)" + + +def test_latex_euler(): + assert latex(euler(n)) == r"E_{n}" + assert latex(euler(n, x)) == r"E_{n}\left(x\right)" + assert latex(euler(n, x)**2) == r"E_{n}^{2}\left(x\right)" + + +def test_lamda(): + assert latex(Symbol('lamda')) == r"\lambda" + assert latex(Symbol('Lamda')) == r"\Lambda" + + +def test_custom_symbol_names(): + x = Symbol('x') + y = Symbol('y') + assert latex(x) == r"x" + assert latex(x, symbol_names={x: "x_i"}) == r"x_i" + assert latex(x + y, symbol_names={x: "x_i"}) == r"x_i + y" + assert latex(x**2, symbol_names={x: "x_i"}) == r"x_i^{2}" + assert latex(x + y, symbol_names={x: "x_i", y: "y_j"}) == r"x_i + y_j" + + +def test_matAdd(): + C = MatrixSymbol('C', 5, 5) + B = MatrixSymbol('B', 5, 5) + + n = symbols("n") + h = MatrixSymbol("h", 1, 1) + + assert latex(C - 2*B) in [r'- 2 B + C', r'C -2 B'] + assert latex(C + 2*B) in [r'2 B + C', r'C + 2 B'] + assert latex(B - 2*C) in [r'B - 2 C', r'- 2 C + B'] + assert latex(B + 2*C) in [r'B + 2 C', r'2 C + B'] + + assert latex(n * h - (-h + h.T) * (h + h.T)) == 'n h - \\left(- h + h^{T}\\right) \\left(h + h^{T}\\right)' + assert latex(MatAdd(MatAdd(h, h), MatAdd(h, h))) == '\\left(h + h\\right) + \\left(h + h\\right)' + assert latex(MatMul(MatMul(h, h), MatMul(h, h))) == '\\left(h h\\right) \\left(h h\\right)' + + +def test_matMul(): + A = MatrixSymbol('A', 5, 5) + B = MatrixSymbol('B', 5, 5) + x = Symbol('x') + assert latex(2*A) == r'2 A' + assert latex(2*x*A) == r'2 x A' + assert latex(-2*A) == r'- 2 A' + assert latex(1.5*A) == r'1.5 A' + assert latex(sqrt(2)*A) == r'\sqrt{2} A' + assert latex(-sqrt(2)*A) == r'- \sqrt{2} A' + assert latex(2*sqrt(2)*x*A) == r'2 \sqrt{2} x A' + assert latex(-2*A*(A + 2*B)) in [r'- 2 A \left(A + 2 B\right)', + r'- 2 A \left(2 B + A\right)'] + + +def test_latex_MatrixSlice(): + n = Symbol('n', integer=True) + x, y, z, w, t, = symbols('x y z w t') + X = MatrixSymbol('X', n, n) + Y = MatrixSymbol('Y', 10, 10) + Z = MatrixSymbol('Z', 10, 10) + + assert latex(MatrixSlice(X, (None, None, None), (None, None, None))) == r'X\left[:, :\right]' + assert latex(X[x:x + 1, y:y + 1]) == r'X\left[x:x + 1, y:y + 1\right]' + assert latex(X[x:x + 1:2, y:y + 1:2]) == r'X\left[x:x + 1:2, y:y + 1:2\right]' + assert latex(X[:x, y:]) == r'X\left[:x, y:\right]' + assert latex(X[:x, y:]) == r'X\left[:x, y:\right]' + assert latex(X[x:, :y]) == r'X\left[x:, :y\right]' + assert latex(X[x:y, z:w]) == r'X\left[x:y, z:w\right]' + assert latex(X[x:y:t, w:t:x]) == r'X\left[x:y:t, w:t:x\right]' + assert latex(X[x::y, t::w]) == r'X\left[x::y, t::w\right]' + assert latex(X[:x:y, :t:w]) == r'X\left[:x:y, :t:w\right]' + assert latex(X[::x, ::y]) == r'X\left[::x, ::y\right]' + assert latex(MatrixSlice(X, (0, None, None), (0, None, None))) == r'X\left[:, :\right]' + assert latex(MatrixSlice(X, (None, n, None), (None, n, None))) == r'X\left[:, :\right]' + assert latex(MatrixSlice(X, (0, n, None), (0, n, None))) == r'X\left[:, :\right]' + assert latex(MatrixSlice(X, (0, n, 2), (0, n, 2))) == r'X\left[::2, ::2\right]' + assert latex(X[1:2:3, 4:5:6]) == r'X\left[1:2:3, 4:5:6\right]' + assert latex(X[1:3:5, 4:6:8]) == r'X\left[1:3:5, 4:6:8\right]' + assert latex(X[1:10:2]) == r'X\left[1:10:2, :\right]' + assert latex(Y[:5, 1:9:2]) == r'Y\left[:5, 1:9:2\right]' + assert latex(Y[:5, 1:10:2]) == r'Y\left[:5, 1::2\right]' + assert latex(Y[5, :5:2]) == r'Y\left[5:6, :5:2\right]' + assert latex(X[0:1, 0:1]) == r'X\left[:1, :1\right]' + assert latex(X[0:1:2, 0:1:2]) == r'X\left[:1:2, :1:2\right]' + assert latex((Y + Z)[2:, 2:]) == r'\left(Y + Z\right)\left[2:, 2:\right]' + + +def test_latex_RandomDomain(): + from sympy.stats import Normal, Die, Exponential, pspace, where + from sympy.stats.rv import RandomDomain + + X = Normal('x1', 0, 1) + assert latex(where(X > 0)) == r"\text{Domain: }0 < x_{1} \wedge x_{1} < \infty" + + D = Die('d1', 6) + assert latex(where(D > 4)) == r"\text{Domain: }d_{1} = 5 \vee d_{1} = 6" + + A = Exponential('a', 1) + B = Exponential('b', 1) + assert latex( + pspace(Tuple(A, B)).domain) == \ + r"\text{Domain: }0 \leq a \wedge 0 \leq b \wedge a < \infty \wedge b < \infty" + + assert latex(RandomDomain(FiniteSet(x), FiniteSet(1, 2))) == \ + r'\text{Domain: }\left\{x\right\} \in \left\{1, 2\right\}' + +def test_PrettyPoly(): + from sympy.polys.domains import QQ + F = QQ.frac_field(x, y) + R = QQ[x, y] + + assert latex(F.convert(x/(x + y))) == latex(x/(x + y)) + assert latex(R.convert(x + y)) == latex(x + y) + + +def test_integral_transforms(): + x = Symbol("x") + k = Symbol("k") + f = Function("f") + a = Symbol("a") + b = Symbol("b") + + assert latex(MellinTransform(f(x), x, k)) == \ + r"\mathcal{M}_{x}\left[f{\left(x \right)}\right]\left(k\right)" + assert latex(InverseMellinTransform(f(k), k, x, a, b)) == \ + r"\mathcal{M}^{-1}_{k}\left[f{\left(k \right)}\right]\left(x\right)" + + assert latex(LaplaceTransform(f(x), x, k)) == \ + r"\mathcal{L}_{x}\left[f{\left(x \right)}\right]\left(k\right)" + assert latex(InverseLaplaceTransform(f(k), k, x, (a, b))) == \ + r"\mathcal{L}^{-1}_{k}\left[f{\left(k \right)}\right]\left(x\right)" + + assert latex(FourierTransform(f(x), x, k)) == \ + r"\mathcal{F}_{x}\left[f{\left(x \right)}\right]\left(k\right)" + assert latex(InverseFourierTransform(f(k), k, x)) == \ + r"\mathcal{F}^{-1}_{k}\left[f{\left(k \right)}\right]\left(x\right)" + + assert latex(CosineTransform(f(x), x, k)) == \ + r"\mathcal{COS}_{x}\left[f{\left(x \right)}\right]\left(k\right)" + assert latex(InverseCosineTransform(f(k), k, x)) == \ + r"\mathcal{COS}^{-1}_{k}\left[f{\left(k \right)}\right]\left(x\right)" + + assert latex(SineTransform(f(x), x, k)) == \ + r"\mathcal{SIN}_{x}\left[f{\left(x \right)}\right]\left(k\right)" + assert latex(InverseSineTransform(f(k), k, x)) == \ + r"\mathcal{SIN}^{-1}_{k}\left[f{\left(k \right)}\right]\left(x\right)" + + +def test_PolynomialRingBase(): + from sympy.polys.domains import QQ + assert latex(QQ.old_poly_ring(x, y)) == r"\mathbb{Q}\left[x, y\right]" + assert latex(QQ.old_poly_ring(x, y, order="ilex")) == \ + r"S_<^{-1}\mathbb{Q}\left[x, y\right]" + + +def test_categories(): + from sympy.categories import (Object, IdentityMorphism, + NamedMorphism, Category, Diagram, + DiagramGrid) + + A1 = Object("A1") + A2 = Object("A2") + A3 = Object("A3") + + f1 = NamedMorphism(A1, A2, "f1") + f2 = NamedMorphism(A2, A3, "f2") + id_A1 = IdentityMorphism(A1) + + K1 = Category("K1") + + assert latex(A1) == r"A_{1}" + assert latex(f1) == r"f_{1}:A_{1}\rightarrow A_{2}" + assert latex(id_A1) == r"id:A_{1}\rightarrow A_{1}" + assert latex(f2*f1) == r"f_{2}\circ f_{1}:A_{1}\rightarrow A_{3}" + + assert latex(K1) == r"\mathbf{K_{1}}" + + d = Diagram() + assert latex(d) == r"\emptyset" + + d = Diagram({f1: "unique", f2: S.EmptySet}) + assert latex(d) == r"\left\{ f_{2}\circ f_{1}:A_{1}" \ + r"\rightarrow A_{3} : \emptyset, \ id:A_{1}\rightarrow " \ + r"A_{1} : \emptyset, \ id:A_{2}\rightarrow A_{2} : " \ + r"\emptyset, \ id:A_{3}\rightarrow A_{3} : \emptyset, " \ + r"\ f_{1}:A_{1}\rightarrow A_{2} : \left\{unique\right\}, " \ + r"\ f_{2}:A_{2}\rightarrow A_{3} : \emptyset\right\}" + + d = Diagram({f1: "unique", f2: S.EmptySet}, {f2 * f1: "unique"}) + assert latex(d) == r"\left\{ f_{2}\circ f_{1}:A_{1}" \ + r"\rightarrow A_{3} : \emptyset, \ id:A_{1}\rightarrow " \ + r"A_{1} : \emptyset, \ id:A_{2}\rightarrow A_{2} : " \ + r"\emptyset, \ id:A_{3}\rightarrow A_{3} : \emptyset, " \ + r"\ f_{1}:A_{1}\rightarrow A_{2} : \left\{unique\right\}," \ + r" \ f_{2}:A_{2}\rightarrow A_{3} : \emptyset\right\}" \ + r"\Longrightarrow \left\{ f_{2}\circ f_{1}:A_{1}" \ + r"\rightarrow A_{3} : \left\{unique\right\}\right\}" + + # A linear diagram. + A = Object("A") + B = Object("B") + C = Object("C") + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + d = Diagram([f, g]) + grid = DiagramGrid(d) + + assert latex(grid) == r"\begin{array}{cc}" + "\n" \ + r"A & B \\" + "\n" \ + r" & C " + "\n" \ + r"\end{array}" + "\n" + + +def test_Modules(): + from sympy.polys.domains import QQ + from sympy.polys.agca import homomorphism + + R = QQ.old_poly_ring(x, y) + F = R.free_module(2) + M = F.submodule([x, y], [1, x**2]) + + assert latex(F) == r"{\mathbb{Q}\left[x, y\right]}^{2}" + assert latex(M) == \ + r"\left\langle {\left[ {x},{y} \right]},{\left[ {1},{x^{2}} \right]} \right\rangle" + + I = R.ideal(x**2, y) + assert latex(I) == r"\left\langle {x^{2}},{y} \right\rangle" + + Q = F / M + assert latex(Q) == \ + r"\frac{{\mathbb{Q}\left[x, y\right]}^{2}}{\left\langle {\left[ {x},"\ + r"{y} \right]},{\left[ {1},{x^{2}} \right]} \right\rangle}" + assert latex(Q.submodule([1, x**3/2], [2, y])) == \ + r"\left\langle {{\left[ {1},{\frac{x^{3}}{2}} \right]} + {\left"\ + r"\langle {\left[ {x},{y} \right]},{\left[ {1},{x^{2}} \right]} "\ + r"\right\rangle}},{{\left[ {2},{y} \right]} + {\left\langle {\left[ "\ + r"{x},{y} \right]},{\left[ {1},{x^{2}} \right]} \right\rangle}} \right\rangle" + + h = homomorphism(QQ.old_poly_ring(x).free_module(2), + QQ.old_poly_ring(x).free_module(2), [0, 0]) + + assert latex(h) == \ + r"{\left[\begin{matrix}0 & 0\\0 & 0\end{matrix}\right]} : "\ + r"{{\mathbb{Q}\left[x\right]}^{2}} \to {{\mathbb{Q}\left[x\right]}^{2}}" + + +def test_QuotientRing(): + from sympy.polys.domains import QQ + R = QQ.old_poly_ring(x)/[x**2 + 1] + + assert latex(R) == \ + r"\frac{\mathbb{Q}\left[x\right]}{\left\langle {x^{2} + 1} \right\rangle}" + assert latex(R.one) == r"{1} + {\left\langle {x^{2} + 1} \right\rangle}" + + +def test_Tr(): + #TODO: Handle indices + A, B = symbols('A B', commutative=False) + t = Tr(A*B) + assert latex(t) == r'\operatorname{tr}\left(A B\right)' + + +def test_Determinant(): + from sympy.matrices import Determinant, Inverse, BlockMatrix, OneMatrix, ZeroMatrix + m = Matrix(((1, 2), (3, 4))) + assert latex(Determinant(m)) == '\\left|{\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}}\\right|' + assert latex(Determinant(Inverse(m))) == \ + '\\left|{\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right]^{-1}}\\right|' + X = MatrixSymbol('X', 2, 2) + assert latex(Determinant(X)) == '\\left|{X}\\right|' + assert latex(Determinant(X + m)) == \ + '\\left|{\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right] + X}\\right|' + assert latex(Determinant(BlockMatrix(((OneMatrix(2, 2), X), + (m, ZeroMatrix(2, 2)))))) == \ + '\\left|{\\begin{matrix}1 & X\\\\\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right] & 0\\end{matrix}}\\right|' + + +def test_Adjoint(): + from sympy.matrices import Adjoint, Inverse, Transpose + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + assert latex(Adjoint(X)) == r'X^{\dagger}' + assert latex(Adjoint(X + Y)) == r'\left(X + Y\right)^{\dagger}' + assert latex(Adjoint(X) + Adjoint(Y)) == r'X^{\dagger} + Y^{\dagger}' + assert latex(Adjoint(X*Y)) == r'\left(X Y\right)^{\dagger}' + assert latex(Adjoint(Y)*Adjoint(X)) == r'Y^{\dagger} X^{\dagger}' + assert latex(Adjoint(X**2)) == r'\left(X^{2}\right)^{\dagger}' + assert latex(Adjoint(X)**2) == r'\left(X^{\dagger}\right)^{2}' + assert latex(Adjoint(Inverse(X))) == r'\left(X^{-1}\right)^{\dagger}' + assert latex(Inverse(Adjoint(X))) == r'\left(X^{\dagger}\right)^{-1}' + assert latex(Adjoint(Transpose(X))) == r'\left(X^{T}\right)^{\dagger}' + assert latex(Transpose(Adjoint(X))) == r'\left(X^{\dagger}\right)^{T}' + assert latex(Transpose(Adjoint(X) + Y)) == r'\left(X^{\dagger} + Y\right)^{T}' + m = Matrix(((1, 2), (3, 4))) + assert latex(Adjoint(m)) == '\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right]^{\\dagger}' + assert latex(Adjoint(m+X)) == \ + '\\left(\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right] + X\\right)^{\\dagger}' + from sympy.matrices import BlockMatrix, OneMatrix, ZeroMatrix + assert latex(Adjoint(BlockMatrix(((OneMatrix(2, 2), X), + (m, ZeroMatrix(2, 2)))))) == \ + '\\left[\\begin{matrix}1 & X\\\\\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right] & 0\\end{matrix}\\right]^{\\dagger}' + # Issue 20959 + Mx = MatrixSymbol('M^x', 2, 2) + assert latex(Adjoint(Mx)) == r'\left(M^{x}\right)^{\dagger}' + + # adjoint style + assert latex(Adjoint(X), adjoint_style="star") == r'X^{\ast}' + assert latex(Adjoint(X + Y), adjoint_style="hermitian") == r'\left(X + Y\right)^{\mathsf{H}}' + assert latex(Adjoint(X) + Adjoint(Y), adjoint_style="dagger") == r'X^{\dagger} + Y^{\dagger}' + assert latex(Adjoint(Y)*Adjoint(X)) == r'Y^{\dagger} X^{\dagger}' + assert latex(Adjoint(X**2), adjoint_style="star") == r'\left(X^{2}\right)^{\ast}' + assert latex(Adjoint(X)**2, adjoint_style="hermitian") == r'\left(X^{\mathsf{H}}\right)^{2}' + +def test_Transpose(): + from sympy.matrices import Transpose, MatPow, HadamardPower + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + assert latex(Transpose(X)) == r'X^{T}' + assert latex(Transpose(X + Y)) == r'\left(X + Y\right)^{T}' + + assert latex(Transpose(HadamardPower(X, 2))) == r'\left(X^{\circ {2}}\right)^{T}' + assert latex(HadamardPower(Transpose(X), 2)) == r'\left(X^{T}\right)^{\circ {2}}' + assert latex(Transpose(MatPow(X, 2))) == r'\left(X^{2}\right)^{T}' + assert latex(MatPow(Transpose(X), 2)) == r'\left(X^{T}\right)^{2}' + m = Matrix(((1, 2), (3, 4))) + assert latex(Transpose(m)) == '\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right]^{T}' + assert latex(Transpose(m+X)) == \ + '\\left(\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right] + X\\right)^{T}' + from sympy.matrices import BlockMatrix, OneMatrix, ZeroMatrix + assert latex(Transpose(BlockMatrix(((OneMatrix(2, 2), X), + (m, ZeroMatrix(2, 2)))))) == \ + '\\left[\\begin{matrix}1 & X\\\\\\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right] & 0\\end{matrix}\\right]^{T}' + # Issue 20959 + Mx = MatrixSymbol('M^x', 2, 2) + assert latex(Transpose(Mx)) == r'\left(M^{x}\right)^{T}' + + +def test_Hadamard(): + from sympy.matrices import HadamardProduct, HadamardPower + from sympy.matrices.expressions import MatAdd, MatMul, MatPow + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + assert latex(HadamardProduct(X, Y*Y)) == r'X \circ Y^{2}' + assert latex(HadamardProduct(X, Y)*Y) == r'\left(X \circ Y\right) Y' + + assert latex(HadamardPower(X, 2)) == r'X^{\circ {2}}' + assert latex(HadamardPower(X, -1)) == r'X^{\circ \left({-1}\right)}' + assert latex(HadamardPower(MatAdd(X, Y), 2)) == \ + r'\left(X + Y\right)^{\circ {2}}' + assert latex(HadamardPower(MatMul(X, Y), 2)) == \ + r'\left(X Y\right)^{\circ {2}}' + + assert latex(HadamardPower(MatPow(X, -1), -1)) == \ + r'\left(X^{-1}\right)^{\circ \left({-1}\right)}' + assert latex(MatPow(HadamardPower(X, -1), -1)) == \ + r'\left(X^{\circ \left({-1}\right)}\right)^{-1}' + + assert latex(HadamardPower(X, n+1)) == \ + r'X^{\circ \left({n + 1}\right)}' + + +def test_MatPow(): + from sympy.matrices.expressions import MatPow + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + assert latex(MatPow(X, 2)) == 'X^{2}' + assert latex(MatPow(X*X, 2)) == '\\left(X^{2}\\right)^{2}' + assert latex(MatPow(X*Y, 2)) == '\\left(X Y\\right)^{2}' + assert latex(MatPow(X + Y, 2)) == '\\left(X + Y\\right)^{2}' + assert latex(MatPow(X + X, 2)) == '\\left(2 X\\right)^{2}' + # Issue 20959 + Mx = MatrixSymbol('M^x', 2, 2) + assert latex(MatPow(Mx, 2)) == r'\left(M^{x}\right)^{2}' + + +def test_ElementwiseApplyFunction(): + X = MatrixSymbol('X', 2, 2) + expr = (X.T*X).applyfunc(sin) + assert latex(expr) == r"{\left( d \mapsto \sin{\left(d \right)} \right)}_{\circ}\left({X^{T} X}\right)" + expr = X.applyfunc(Lambda(x, 1/x)) + assert latex(expr) == r'{\left( x \mapsto \frac{1}{x} \right)}_{\circ}\left({X}\right)' + + +def test_ZeroMatrix(): + from sympy.matrices.expressions.special import ZeroMatrix + assert latex(ZeroMatrix(1, 1), mat_symbol_style='plain') == r"0" + assert latex(ZeroMatrix(1, 1), mat_symbol_style='bold') == r"\mathbf{0}" + + +def test_OneMatrix(): + from sympy.matrices.expressions.special import OneMatrix + assert latex(OneMatrix(3, 4), mat_symbol_style='plain') == r"1" + assert latex(OneMatrix(3, 4), mat_symbol_style='bold') == r"\mathbf{1}" + + +def test_Identity(): + from sympy.matrices.expressions.special import Identity + assert latex(Identity(1), mat_symbol_style='plain') == r"\mathbb{I}" + assert latex(Identity(1), mat_symbol_style='bold') == r"\mathbf{I}" + + +def test_latex_DFT_IDFT(): + from sympy.matrices.expressions.fourier import DFT, IDFT + assert latex(DFT(13)) == r"\text{DFT}_{13}" + assert latex(IDFT(x)) == r"\text{IDFT}_{x}" + + +def test_boolean_args_order(): + syms = symbols('a:f') + + expr = And(*syms) + assert latex(expr) == r'a \wedge b \wedge c \wedge d \wedge e \wedge f' + + expr = Or(*syms) + assert latex(expr) == r'a \vee b \vee c \vee d \vee e \vee f' + + expr = Equivalent(*syms) + assert latex(expr) == \ + r'a \Leftrightarrow b \Leftrightarrow c \Leftrightarrow d \Leftrightarrow e \Leftrightarrow f' + + expr = Xor(*syms) + assert latex(expr) == \ + r'a \veebar b \veebar c \veebar d \veebar e \veebar f' + + +def test_imaginary(): + i = sqrt(-1) + assert latex(i) == r'i' + + +def test_builtins_without_args(): + assert latex(sin) == r'\sin' + assert latex(cos) == r'\cos' + assert latex(tan) == r'\tan' + assert latex(log) == r'\log' + assert latex(Ei) == r'\operatorname{Ei}' + assert latex(zeta) == r'\zeta' + + +def test_latex_greek_functions(): + # bug because capital greeks that have roman equivalents should not use + # \Alpha, \Beta, \Eta, etc. + s = Function('Alpha') + assert latex(s) == r'\mathrm{A}' + assert latex(s(x)) == r'\mathrm{A}{\left(x \right)}' + s = Function('Beta') + assert latex(s) == r'\mathrm{B}' + s = Function('Eta') + assert latex(s) == r'\mathrm{H}' + assert latex(s(x)) == r'\mathrm{H}{\left(x \right)}' + + # bug because sympy.core.numbers.Pi is special + p = Function('Pi') + # assert latex(p(x)) == r'\Pi{\left(x \right)}' + assert latex(p) == r'\Pi' + + # bug because not all greeks are included + c = Function('chi') + assert latex(c(x)) == r'\chi{\left(x \right)}' + assert latex(c) == r'\chi' + + +def test_translate(): + s = 'Alpha' + assert translate(s) == r'\mathrm{A}' + s = 'Beta' + assert translate(s) == r'\mathrm{B}' + s = 'Eta' + assert translate(s) == r'\mathrm{H}' + s = 'omicron' + assert translate(s) == r'o' + s = 'Pi' + assert translate(s) == r'\Pi' + s = 'pi' + assert translate(s) == r'\pi' + s = 'LamdaHatDOT' + assert translate(s) == r'\dot{\hat{\Lambda}}' + + +def test_other_symbols(): + from sympy.printing.latex import other_symbols + for s in other_symbols: + assert latex(symbols(s)) == r"" "\\" + s + + +def test_modifiers(): + # Test each modifier individually in the simplest case + # (with funny capitalizations) + assert latex(symbols("xMathring")) == r"\mathring{x}" + assert latex(symbols("xCheck")) == r"\check{x}" + assert latex(symbols("xBreve")) == r"\breve{x}" + assert latex(symbols("xAcute")) == r"\acute{x}" + assert latex(symbols("xGrave")) == r"\grave{x}" + assert latex(symbols("xTilde")) == r"\tilde{x}" + assert latex(symbols("xPrime")) == r"{x}'" + assert latex(symbols("xddDDot")) == r"\ddddot{x}" + assert latex(symbols("xDdDot")) == r"\dddot{x}" + assert latex(symbols("xDDot")) == r"\ddot{x}" + assert latex(symbols("xBold")) == r"\boldsymbol{x}" + assert latex(symbols("xnOrM")) == r"\left\|{x}\right\|" + assert latex(symbols("xAVG")) == r"\left\langle{x}\right\rangle" + assert latex(symbols("xHat")) == r"\hat{x}" + assert latex(symbols("xDot")) == r"\dot{x}" + assert latex(symbols("xBar")) == r"\bar{x}" + assert latex(symbols("xVec")) == r"\vec{x}" + assert latex(symbols("xAbs")) == r"\left|{x}\right|" + assert latex(symbols("xMag")) == r"\left|{x}\right|" + assert latex(symbols("xPrM")) == r"{x}'" + assert latex(symbols("xBM")) == r"\boldsymbol{x}" + # Test strings that are *only* the names of modifiers + assert latex(symbols("Mathring")) == r"Mathring" + assert latex(symbols("Check")) == r"Check" + assert latex(symbols("Breve")) == r"Breve" + assert latex(symbols("Acute")) == r"Acute" + assert latex(symbols("Grave")) == r"Grave" + assert latex(symbols("Tilde")) == r"Tilde" + assert latex(symbols("Prime")) == r"Prime" + assert latex(symbols("DDot")) == r"\dot{D}" + assert latex(symbols("Bold")) == r"Bold" + assert latex(symbols("NORm")) == r"NORm" + assert latex(symbols("AVG")) == r"AVG" + assert latex(symbols("Hat")) == r"Hat" + assert latex(symbols("Dot")) == r"Dot" + assert latex(symbols("Bar")) == r"Bar" + assert latex(symbols("Vec")) == r"Vec" + assert latex(symbols("Abs")) == r"Abs" + assert latex(symbols("Mag")) == r"Mag" + assert latex(symbols("PrM")) == r"PrM" + assert latex(symbols("BM")) == r"BM" + assert latex(symbols("hbar")) == r"\hbar" + # Check a few combinations + assert latex(symbols("xvecdot")) == r"\dot{\vec{x}}" + assert latex(symbols("xDotVec")) == r"\vec{\dot{x}}" + assert latex(symbols("xHATNorm")) == r"\left\|{\hat{x}}\right\|" + # Check a couple big, ugly combinations + assert latex(symbols('xMathringBm_yCheckPRM__zbreveAbs')) == \ + r"\boldsymbol{\mathring{x}}^{\left|{\breve{z}}\right|}_{{\check{y}}'}" + assert latex(symbols('alphadothat_nVECDOT__tTildePrime')) == \ + r"\hat{\dot{\alpha}}^{{\tilde{t}}'}_{\dot{\vec{n}}}" + + +def test_greek_symbols(): + assert latex(Symbol('alpha')) == r'\alpha' + assert latex(Symbol('beta')) == r'\beta' + assert latex(Symbol('gamma')) == r'\gamma' + assert latex(Symbol('delta')) == r'\delta' + assert latex(Symbol('epsilon')) == r'\epsilon' + assert latex(Symbol('zeta')) == r'\zeta' + assert latex(Symbol('eta')) == r'\eta' + assert latex(Symbol('theta')) == r'\theta' + assert latex(Symbol('iota')) == r'\iota' + assert latex(Symbol('kappa')) == r'\kappa' + assert latex(Symbol('lambda')) == r'\lambda' + assert latex(Symbol('mu')) == r'\mu' + assert latex(Symbol('nu')) == r'\nu' + assert latex(Symbol('xi')) == r'\xi' + assert latex(Symbol('omicron')) == r'o' + assert latex(Symbol('pi')) == r'\pi' + assert latex(Symbol('rho')) == r'\rho' + assert latex(Symbol('sigma')) == r'\sigma' + assert latex(Symbol('tau')) == r'\tau' + assert latex(Symbol('upsilon')) == r'\upsilon' + assert latex(Symbol('phi')) == r'\phi' + assert latex(Symbol('chi')) == r'\chi' + assert latex(Symbol('psi')) == r'\psi' + assert latex(Symbol('omega')) == r'\omega' + + assert latex(Symbol('Alpha')) == r'\mathrm{A}' + assert latex(Symbol('Beta')) == r'\mathrm{B}' + assert latex(Symbol('Gamma')) == r'\Gamma' + assert latex(Symbol('Delta')) == r'\Delta' + assert latex(Symbol('Epsilon')) == r'\mathrm{E}' + assert latex(Symbol('Zeta')) == r'\mathrm{Z}' + assert latex(Symbol('Eta')) == r'\mathrm{H}' + assert latex(Symbol('Theta')) == r'\Theta' + assert latex(Symbol('Iota')) == r'\mathrm{I}' + assert latex(Symbol('Kappa')) == r'\mathrm{K}' + assert latex(Symbol('Lambda')) == r'\Lambda' + assert latex(Symbol('Mu')) == r'\mathrm{M}' + assert latex(Symbol('Nu')) == r'\mathrm{N}' + assert latex(Symbol('Xi')) == r'\Xi' + assert latex(Symbol('Omicron')) == r'\mathrm{O}' + assert latex(Symbol('Pi')) == r'\Pi' + assert latex(Symbol('Rho')) == r'\mathrm{P}' + assert latex(Symbol('Sigma')) == r'\Sigma' + assert latex(Symbol('Tau')) == r'\mathrm{T}' + assert latex(Symbol('Upsilon')) == r'\Upsilon' + assert latex(Symbol('Phi')) == r'\Phi' + assert latex(Symbol('Chi')) == r'\mathrm{X}' + assert latex(Symbol('Psi')) == r'\Psi' + assert latex(Symbol('Omega')) == r'\Omega' + + assert latex(Symbol('varepsilon')) == r'\varepsilon' + assert latex(Symbol('varkappa')) == r'\varkappa' + assert latex(Symbol('varphi')) == r'\varphi' + assert latex(Symbol('varpi')) == r'\varpi' + assert latex(Symbol('varrho')) == r'\varrho' + assert latex(Symbol('varsigma')) == r'\varsigma' + assert latex(Symbol('vartheta')) == r'\vartheta' + + +def test_fancyset_symbols(): + assert latex(S.Rationals) == r'\mathbb{Q}' + assert latex(S.Naturals) == r'\mathbb{N}' + assert latex(S.Naturals0) == r'\mathbb{N}_0' + assert latex(S.Integers) == r'\mathbb{Z}' + assert latex(S.Reals) == r'\mathbb{R}' + assert latex(S.Complexes) == r'\mathbb{C}' + + +@XFAIL +def test_builtin_without_args_mismatched_names(): + assert latex(CosineTransform) == r'\mathcal{COS}' + + +def test_builtin_no_args(): + assert latex(Chi) == r'\operatorname{Chi}' + assert latex(beta) == r'\operatorname{B}' + assert latex(gamma) == r'\Gamma' + assert latex(KroneckerDelta) == r'\delta' + assert latex(DiracDelta) == r'\delta' + assert latex(lowergamma) == r'\gamma' + + +def test_issue_6853(): + p = Function('Pi') + assert latex(p(x)) == r"\Pi{\left(x \right)}" + + +def test_Mul(): + e = Mul(-2, x + 1, evaluate=False) + assert latex(e) == r'- 2 \left(x + 1\right)' + e = Mul(2, x + 1, evaluate=False) + assert latex(e) == r'2 \left(x + 1\right)' + e = Mul(S.Half, x + 1, evaluate=False) + assert latex(e) == r'\frac{x + 1}{2}' + e = Mul(y, x + 1, evaluate=False) + assert latex(e) == r'y \left(x + 1\right)' + e = Mul(-y, x + 1, evaluate=False) + assert latex(e) == r'- y \left(x + 1\right)' + e = Mul(-2, x + 1) + assert latex(e) == r'- 2 x - 2' + e = Mul(2, x + 1) + assert latex(e) == r'2 x + 2' + + +def test_Pow(): + e = Pow(2, 2, evaluate=False) + assert latex(e) == r'2^{2}' + assert latex(x**(Rational(-1, 3))) == r'\frac{1}{\sqrt[3]{x}}' + x2 = Symbol(r'x^2') + assert latex(x2**2) == r'\left(x^{2}\right)^{2}' + # Issue 11011 + assert latex(S('1.453e4500')**x) == r'{1.453 \cdot 10^{4500}}^{x}' + + +def test_issue_7180(): + assert latex(Equivalent(x, y)) == r"x \Leftrightarrow y" + assert latex(Not(Equivalent(x, y))) == r"x \not\Leftrightarrow y" + + +def test_issue_8409(): + assert latex(S.Half**n) == r"\left(\frac{1}{2}\right)^{n}" + + +def test_issue_8470(): + from sympy.parsing.sympy_parser import parse_expr + e = parse_expr("-B*A", evaluate=False) + assert latex(e) == r"A \left(- B\right)" + + +def test_issue_15439(): + x = MatrixSymbol('x', 2, 2) + y = MatrixSymbol('y', 2, 2) + assert latex((x * y).subs(y, -y)) == r"x \left(- y\right)" + assert latex((x * y).subs(y, -2*y)) == r"x \left(- 2 y\right)" + assert latex((x * y).subs(x, -x)) == r"\left(- x\right) y" + + +def test_issue_2934(): + assert latex(Symbol(r'\frac{a_1}{b_1}')) == r'\frac{a_1}{b_1}' + + +def test_issue_10489(): + latexSymbolWithBrace = r'C_{x_{0}}' + s = Symbol(latexSymbolWithBrace) + assert latex(s) == latexSymbolWithBrace + assert latex(cos(s)) == r'\cos{\left(C_{x_{0}} \right)}' + + +def test_issue_12886(): + m__1, l__1 = symbols('m__1, l__1') + assert latex(m__1**2 + l__1**2) == \ + r'\left(l^{1}\right)^{2} + \left(m^{1}\right)^{2}' + + +def test_issue_13559(): + from sympy.parsing.sympy_parser import parse_expr + expr = parse_expr('5/1', evaluate=False) + assert latex(expr) == r"\frac{5}{1}" + + +def test_issue_13651(): + expr = c + Mul(-1, a + b, evaluate=False) + assert latex(expr) == r"c - \left(a + b\right)" + + +def test_latex_UnevaluatedExpr(): + x = symbols("x") + he = UnevaluatedExpr(1/x) + assert latex(he) == latex(1/x) == r"\frac{1}{x}" + assert latex(he**2) == r"\left(\frac{1}{x}\right)^{2}" + assert latex(he + 1) == r"1 + \frac{1}{x}" + assert latex(x*he) == r"x \frac{1}{x}" + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert latex(A[0, 0]) == r"{A}_{0,0}" + assert latex(3 * A[0, 0]) == r"3 {A}_{0,0}" + + F = C[0, 0].subs(C, A - B) + assert latex(F) == r"{\left(A - B\right)}_{0,0}" + + i, j, k = symbols("i j k") + M = MatrixSymbol("M", k, k) + N = MatrixSymbol("N", k, k) + assert latex((M*N)[i, j]) == \ + r'\sum_{i_{1}=0}^{k - 1} {M}_{i,i_{1}} {N}_{i_{1},j}' + + X_a = MatrixSymbol('X_a', 3, 3) + assert latex(X_a[0, 0]) == r"{X_{a}}_{0,0}" + + +def test_MatrixSymbol_printing(): + # test cases for issue #14237 + A = MatrixSymbol("A", 3, 3) + B = MatrixSymbol("B", 3, 3) + C = MatrixSymbol("C", 3, 3) + + assert latex(-A) == r"- A" + assert latex(A - A*B - B) == r"A - A B - B" + assert latex(-A*B - A*B*C - B) == r"- A B - A B C - B" + + +def test_DotProduct_printing(): + X = MatrixSymbol('X', 3, 1) + Y = MatrixSymbol('Y', 3, 1) + a = Symbol('a') + assert latex(DotProduct(X, Y)) == r"X \cdot Y" + assert latex(DotProduct(a * X, Y)) == r"a X \cdot Y" + assert latex(a * DotProduct(X, Y)) == r"a \left(X \cdot Y\right)" + + +def test_KroneckerProduct_printing(): + A = MatrixSymbol('A', 3, 3) + B = MatrixSymbol('B', 2, 2) + assert latex(KroneckerProduct(A, B)) == r'A \otimes B' + + +def test_Series_printing(): + tf1 = TransferFunction(x*y**2 - z, y**3 - t**3, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(t*x**2 - t**w*x + w, t - y, y) + assert latex(Series(tf1, tf2)) == \ + r'\left(\frac{x y^{2} - z}{- t^{3} + y^{3}}\right) \left(\frac{x - y}{x + y}\right)' + assert latex(Series(tf1, tf2, tf3)) == \ + r'\left(\frac{x y^{2} - z}{- t^{3} + y^{3}}\right) \left(\frac{x - y}{x + y}\right) \left(\frac{t x^{2} - t^{w} x + w}{t - y}\right)' + assert latex(Series(-tf2, tf1)) == \ + r'\left(\frac{- x + y}{x + y}\right) \left(\frac{x y^{2} - z}{- t^{3} + y^{3}}\right)' + + M_1 = Matrix([[5/s], [5/(2*s)]]) + T_1 = TransferFunctionMatrix.from_Matrix(M_1, s) + M_2 = Matrix([[5, 6*s**3]]) + T_2 = TransferFunctionMatrix.from_Matrix(M_2, s) + # Brackets + assert latex(T_1*(T_2 + T_2)) == \ + r'\left[\begin{matrix}\frac{5}{s}\\\frac{5}{2 s}\end{matrix}\right]_\tau\cdot\left(\left[\begin{matrix}\frac{5}{1} &' \ + r' \frac{6 s^{3}}{1}\end{matrix}\right]_\tau + \left[\begin{matrix}\frac{5}{1} & \frac{6 s^{3}}{1}\end{matrix}\right]_\tau\right)' \ + == latex(MIMOSeries(MIMOParallel(T_2, T_2), T_1)) + # No Brackets + M_3 = Matrix([[5, 6], [6, 5/s]]) + T_3 = TransferFunctionMatrix.from_Matrix(M_3, s) + assert latex(T_1*T_2 + T_3) == r'\left[\begin{matrix}\frac{5}{s}\\\frac{5}{2 s}\end{matrix}\right]_\tau\cdot\left[\begin{matrix}' \ + r'\frac{5}{1} & \frac{6 s^{3}}{1}\end{matrix}\right]_\tau + \left[\begin{matrix}\frac{5}{1} & \frac{6}{1}\\\frac{6}{1} & ' \ + r'\frac{5}{s}\end{matrix}\right]_\tau' == latex(MIMOParallel(MIMOSeries(T_2, T_1), T_3)) + + +def test_TransferFunction_printing(): + tf1 = TransferFunction(x - 1, x + 1, x) + assert latex(tf1) == r"\frac{x - 1}{x + 1}" + tf2 = TransferFunction(x + 1, 2 - y, x) + assert latex(tf2) == r"\frac{x + 1}{2 - y}" + tf3 = TransferFunction(y, y**2 + 2*y + 3, y) + assert latex(tf3) == r"\frac{y}{y^{2} + 2 y + 3}" + + +def test_Parallel_printing(): + tf1 = TransferFunction(x*y**2 - z, y**3 - t**3, y) + tf2 = TransferFunction(x - y, x + y, y) + assert latex(Parallel(tf1, tf2)) == \ + r'\frac{x y^{2} - z}{- t^{3} + y^{3}} + \frac{x - y}{x + y}' + assert latex(Parallel(-tf2, tf1)) == \ + r'\frac{- x + y}{x + y} + \frac{x y^{2} - z}{- t^{3} + y^{3}}' + + M_1 = Matrix([[5, 6], [6, 5/s]]) + T_1 = TransferFunctionMatrix.from_Matrix(M_1, s) + M_2 = Matrix([[5/s, 6], [6, 5/(s - 1)]]) + T_2 = TransferFunctionMatrix.from_Matrix(M_2, s) + M_3 = Matrix([[6, 5/(s*(s - 1))], [5, 6]]) + T_3 = TransferFunctionMatrix.from_Matrix(M_3, s) + assert latex(T_1 + T_2 + T_3) == r'\left[\begin{matrix}\frac{5}{1} & \frac{6}{1}\\\frac{6}{1} & \frac{5}{s}\end{matrix}\right]' \ + r'_\tau + \left[\begin{matrix}\frac{5}{s} & \frac{6}{1}\\\frac{6}{1} & \frac{5}{s - 1}\end{matrix}\right]_\tau + \left[\begin{matrix}' \ + r'\frac{6}{1} & \frac{5}{s \left(s - 1\right)}\\\frac{5}{1} & \frac{6}{1}\end{matrix}\right]_\tau' \ + == latex(MIMOParallel(T_1, T_2, T_3)) == latex(MIMOParallel(T_1, MIMOParallel(T_2, T_3))) == latex(MIMOParallel(MIMOParallel(T_1, T_2), T_3)) + + +def test_TransferFunctionMatrix_printing(): + tf1 = TransferFunction(p, p + x, p) + tf2 = TransferFunction(-s + p, p + s, p) + tf3 = TransferFunction(p, y**2 + 2*y + 3, p) + assert latex(TransferFunctionMatrix([[tf1], [tf2]])) == \ + r'\left[\begin{matrix}\frac{p}{p + x}\\\frac{p - s}{p + s}\end{matrix}\right]_\tau' + assert latex(TransferFunctionMatrix([[tf1, tf2], [tf3, -tf1]])) == \ + r'\left[\begin{matrix}\frac{p}{p + x} & \frac{p - s}{p + s}\\\frac{p}{y^{2} + 2 y + 3} & \frac{\left(-1\right) p}{p + x}\end{matrix}\right]_\tau' + + +def test_Feedback_printing(): + tf1 = TransferFunction(p, p + x, p) + tf2 = TransferFunction(-s + p, p + s, p) + # Negative Feedback (Default) + assert latex(Feedback(tf1, tf2)) == \ + r'\frac{\frac{p}{p + x}}{\frac{1}{1} + \left(\frac{p}{p + x}\right) \left(\frac{p - s}{p + s}\right)}' + assert latex(Feedback(tf1*tf2, TransferFunction(1, 1, p))) == \ + r'\frac{\left(\frac{p}{p + x}\right) \left(\frac{p - s}{p + s}\right)}{\frac{1}{1} + \left(\frac{p}{p + x}\right) \left(\frac{p - s}{p + s}\right)}' + # Positive Feedback + assert latex(Feedback(tf1, tf2, 1)) == \ + r'\frac{\frac{p}{p + x}}{\frac{1}{1} - \left(\frac{p}{p + x}\right) \left(\frac{p - s}{p + s}\right)}' + assert latex(Feedback(tf1*tf2, sign=1)) == \ + r'\frac{\left(\frac{p}{p + x}\right) \left(\frac{p - s}{p + s}\right)}{\frac{1}{1} - \left(\frac{p}{p + x}\right) \left(\frac{p - s}{p + s}\right)}' + + +def test_MIMOFeedback_printing(): + tf1 = TransferFunction(1, s, s) + tf2 = TransferFunction(s, s**2 - 1, s) + tf3 = TransferFunction(s, s - 1, s) + tf4 = TransferFunction(s**2, s**2 - 1, s) + + tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]]) + tfm_2 = TransferFunctionMatrix([[tf4, tf3], [tf2, tf1]]) + + # Negative Feedback (Default) + assert latex(MIMOFeedback(tfm_1, tfm_2)) == \ + r'\left(I_{\tau} + \left[\begin{matrix}\frac{1}{s} & \frac{s}{s^{2} - 1}\\\frac{s}{s - 1} & \frac{s^{2}}{s^{2} - 1}\end{matrix}\right]_\tau\cdot\left[' \ + r'\begin{matrix}\frac{s^{2}}{s^{2} - 1} & \frac{s}{s - 1}\\\frac{s}{s^{2} - 1} & \frac{1}{s}\end{matrix}\right]_\tau\right)^{-1} \cdot \left[\begin{matrix}' \ + r'\frac{1}{s} & \frac{s}{s^{2} - 1}\\\frac{s}{s - 1} & \frac{s^{2}}{s^{2} - 1}\end{matrix}\right]_\tau' + + # Positive Feedback + assert latex(MIMOFeedback(tfm_1*tfm_2, tfm_1, 1)) == \ + r'\left(I_{\tau} - \left[\begin{matrix}\frac{1}{s} & \frac{s}{s^{2} - 1}\\\frac{s}{s - 1} & \frac{s^{2}}{s^{2} - 1}\end{matrix}\right]_\tau\cdot\left' \ + r'[\begin{matrix}\frac{s^{2}}{s^{2} - 1} & \frac{s}{s - 1}\\\frac{s}{s^{2} - 1} & \frac{1}{s}\end{matrix}\right]_\tau\cdot\left[\begin{matrix}\frac{1}{s} & \frac{s}{s^{2} - 1}' \ + r'\\\frac{s}{s - 1} & \frac{s^{2}}{s^{2} - 1}\end{matrix}\right]_\tau\right)^{-1} \cdot \left[\begin{matrix}\frac{1}{s} & \frac{s}{s^{2} - 1}' \ + r'\\\frac{s}{s - 1} & \frac{s^{2}}{s^{2} - 1}\end{matrix}\right]_\tau\cdot\left[\begin{matrix}\frac{s^{2}}{s^{2} - 1} & \frac{s}{s - 1}\\\frac{s}{s^{2} - 1}' \ + r' & \frac{1}{s}\end{matrix}\right]_\tau' + + +def test_Quaternion_latex_printing(): + q = Quaternion(x, y, z, t) + assert latex(q) == r"x + y i + z j + t k" + q = Quaternion(x, y, z, x*t) + assert latex(q) == r"x + y i + z j + t x k" + q = Quaternion(x, y, z, x + t) + assert latex(q) == r"x + y i + z j + \left(t + x\right) k" + + +def test_TensorProduct_printing(): + from sympy.tensor.functions import TensorProduct + A = MatrixSymbol("A", 3, 3) + B = MatrixSymbol("B", 3, 3) + assert latex(TensorProduct(A, B)) == r"A \otimes B" + + +def test_WedgeProduct_printing(): + from sympy.diffgeom.rn import R2 + from sympy.diffgeom import WedgeProduct + wp = WedgeProduct(R2.dx, R2.dy) + assert latex(wp) == r"\operatorname{d}x \wedge \operatorname{d}y" + + +def test_issue_9216(): + expr_1 = Pow(1, -1, evaluate=False) + assert latex(expr_1) == r"1^{-1}" + + expr_2 = Pow(1, Pow(1, -1, evaluate=False), evaluate=False) + assert latex(expr_2) == r"1^{1^{-1}}" + + expr_3 = Pow(3, -2, evaluate=False) + assert latex(expr_3) == r"\frac{1}{9}" + + expr_4 = Pow(1, -2, evaluate=False) + assert latex(expr_4) == r"1^{-2}" + + +def test_latex_printer_tensor(): + from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead, tensor_heads + L = TensorIndexType("L") + i, j, k, l = tensor_indices("i j k l", L) + i0 = tensor_indices("i_0", L) + A, B, C, D = tensor_heads("A B C D", [L]) + H = TensorHead("H", [L, L]) + K = TensorHead("K", [L, L, L, L]) + + assert latex(i) == r"{}^{i}" + assert latex(-i) == r"{}_{i}" + + expr = A(i) + assert latex(expr) == r"A{}^{i}" + + expr = A(i0) + assert latex(expr) == r"A{}^{i_{0}}" + + expr = A(-i) + assert latex(expr) == r"A{}_{i}" + + expr = -3*A(i) + assert latex(expr) == r"-3A{}^{i}" + + expr = K(i, j, -k, -i0) + assert latex(expr) == r"K{}^{ij}{}_{ki_{0}}" + + expr = K(i, -j, -k, i0) + assert latex(expr) == r"K{}^{i}{}_{jk}{}^{i_{0}}" + + expr = K(i, -j, k, -i0) + assert latex(expr) == r"K{}^{i}{}_{j}{}^{k}{}_{i_{0}}" + + expr = H(i, -j) + assert latex(expr) == r"H{}^{i}{}_{j}" + + expr = H(i, j) + assert latex(expr) == r"H{}^{ij}" + + expr = H(-i, -j) + assert latex(expr) == r"H{}_{ij}" + + expr = (1+x)*A(i) + assert latex(expr) == r"\left(x + 1\right)A{}^{i}" + + expr = H(i, -i) + assert latex(expr) == r"H{}^{L_{0}}{}_{L_{0}}" + + expr = H(i, -j)*A(j)*B(k) + assert latex(expr) == r"H{}^{i}{}_{L_{0}}A{}^{L_{0}}B{}^{k}" + + expr = A(i) + 3*B(i) + assert latex(expr) == r"3B{}^{i} + A{}^{i}" + + # Test ``TensorElement``: + from sympy.tensor.tensor import TensorElement + + expr = TensorElement(K(i, j, k, l), {i: 3, k: 2}) + assert latex(expr) == r'K{}^{i=3,j,k=2,l}' + + expr = TensorElement(K(i, j, k, l), {i: 3}) + assert latex(expr) == r'K{}^{i=3,jkl}' + + expr = TensorElement(K(i, -j, k, l), {i: 3, k: 2}) + assert latex(expr) == r'K{}^{i=3}{}_{j}{}^{k=2,l}' + + expr = TensorElement(K(i, -j, k, -l), {i: 3, k: 2}) + assert latex(expr) == r'K{}^{i=3}{}_{j}{}^{k=2}{}_{l}' + + expr = TensorElement(K(i, j, -k, -l), {i: 3, -k: 2}) + assert latex(expr) == r'K{}^{i=3,j}{}_{k=2,l}' + + expr = TensorElement(K(i, j, -k, -l), {i: 3}) + assert latex(expr) == r'K{}^{i=3,j}{}_{kl}' + + expr = PartialDerivative(A(i), A(i)) + assert latex(expr) == r"\frac{\partial}{\partial {A{}^{L_{0}}}}{A{}^{L_{0}}}" + + expr = PartialDerivative(A(-i), A(-j)) + assert latex(expr) == r"\frac{\partial}{\partial {A{}_{j}}}{A{}_{i}}" + + expr = PartialDerivative(K(i, j, -k, -l), A(m), A(-n)) + assert latex(expr) == r"\frac{\partial^{2}}{\partial {A{}^{m}} \partial {A{}_{n}}}{K{}^{ij}{}_{kl}}" + + expr = PartialDerivative(B(-i) + A(-i), A(-j), A(-n)) + assert latex(expr) == r"\frac{\partial^{2}}{\partial {A{}_{j}} \partial {A{}_{n}}}{\left(A{}_{i} + B{}_{i}\right)}" + + expr = PartialDerivative(3*A(-i), A(-j), A(-n)) + assert latex(expr) == r"\frac{\partial^{2}}{\partial {A{}_{j}} \partial {A{}_{n}}}{\left(3A{}_{i}\right)}" + + +def test_multiline_latex(): + a, b, c, d, e, f = symbols('a b c d e f') + expr = -a + 2*b -3*c +4*d -5*e + expected = r"\begin{eqnarray}" + "\n"\ + r"f & = &- a \nonumber\\" + "\n"\ + r"& & + 2 b \nonumber\\" + "\n"\ + r"& & - 3 c \nonumber\\" + "\n"\ + r"& & + 4 d \nonumber\\" + "\n"\ + r"& & - 5 e " + "\n"\ + r"\end{eqnarray}" + assert multiline_latex(f, expr, environment="eqnarray") == expected + + expected2 = r'\begin{eqnarray}' + '\n'\ + r'f & = &- a + 2 b \nonumber\\' + '\n'\ + r'& & - 3 c + 4 d \nonumber\\' + '\n'\ + r'& & - 5 e ' + '\n'\ + r'\end{eqnarray}' + + assert multiline_latex(f, expr, 2, environment="eqnarray") == expected2 + + expected3 = r'\begin{eqnarray}' + '\n'\ + r'f & = &- a + 2 b - 3 c \nonumber\\'+ '\n'\ + r'& & + 4 d - 5 e ' + '\n'\ + r'\end{eqnarray}' + + assert multiline_latex(f, expr, 3, environment="eqnarray") == expected3 + + expected3dots = r'\begin{eqnarray}' + '\n'\ + r'f & = &- a + 2 b - 3 c \dots\nonumber\\'+ '\n'\ + r'& & + 4 d - 5 e ' + '\n'\ + r'\end{eqnarray}' + + assert multiline_latex(f, expr, 3, environment="eqnarray", use_dots=True) == expected3dots + + expected3align = r'\begin{align*}' + '\n'\ + r'f = &- a + 2 b - 3 c \\'+ '\n'\ + r'& + 4 d - 5 e ' + '\n'\ + r'\end{align*}' + + assert multiline_latex(f, expr, 3) == expected3align + assert multiline_latex(f, expr, 3, environment='align*') == expected3align + + expected2ieee = r'\begin{IEEEeqnarray}{rCl}' + '\n'\ + r'f & = &- a + 2 b \nonumber\\' + '\n'\ + r'& & - 3 c + 4 d \nonumber\\' + '\n'\ + r'& & - 5 e ' + '\n'\ + r'\end{IEEEeqnarray}' + + assert multiline_latex(f, expr, 2, environment="IEEEeqnarray") == expected2ieee + + raises(ValueError, lambda: multiline_latex(f, expr, environment="foo")) + +def test_issue_15353(): + a, x = symbols('a x') + # Obtained from nonlinsolve([(sin(a*x)),cos(a*x)],[x,a]) + sol = ConditionSet( + Tuple(x, a), Eq(sin(a*x), 0) & Eq(cos(a*x), 0), S.Complexes**2) + assert latex(sol) == \ + r'\left\{\left( x, \ a\right)\; \middle|\; \left( x, \ a\right) \in ' \ + r'\mathbb{C}^{2} \wedge \sin{\left(a x \right)} = 0 \wedge ' \ + r'\cos{\left(a x \right)} = 0 \right\}' + + +def test_latex_symbolic_probability(): + mu = symbols("mu") + sigma = symbols("sigma", positive=True) + X = Normal("X", mu, sigma) + assert latex(Expectation(X)) == r'\operatorname{E}\left[X\right]' + assert latex(Variance(X)) == r'\operatorname{Var}\left(X\right)' + assert latex(Probability(X > 0)) == r'\operatorname{P}\left(X > 0\right)' + Y = Normal("Y", mu, sigma) + assert latex(Covariance(X, Y)) == r'\operatorname{Cov}\left(X, Y\right)' + + +def test_trace(): + # Issue 15303 + from sympy.matrices.expressions.trace import trace + A = MatrixSymbol("A", 2, 2) + assert latex(trace(A)) == r"\operatorname{tr}\left(A \right)" + assert latex(trace(A**2)) == r"\operatorname{tr}\left(A^{2} \right)" + + +def test_print_basic(): + # Issue 15303 + from sympy.core.basic import Basic + from sympy.core.expr import Expr + + # dummy class for testing printing where the function is not + # implemented in latex.py + class UnimplementedExpr(Expr): + def __new__(cls, e): + return Basic.__new__(cls, e) + + # dummy function for testing + def unimplemented_expr(expr): + return UnimplementedExpr(expr).doit() + + # override class name to use superscript / subscript + def unimplemented_expr_sup_sub(expr): + result = UnimplementedExpr(expr) + result.__class__.__name__ = 'UnimplementedExpr_x^1' + return result + + assert latex(unimplemented_expr(x)) == r'\operatorname{UnimplementedExpr}\left(x\right)' + assert latex(unimplemented_expr(x**2)) == \ + r'\operatorname{UnimplementedExpr}\left(x^{2}\right)' + assert latex(unimplemented_expr_sup_sub(x)) == \ + r'\operatorname{UnimplementedExpr^{1}_{x}}\left(x\right)' + + +def test_MatrixSymbol_bold(): + # Issue #15871 + from sympy.matrices.expressions.trace import trace + A = MatrixSymbol("A", 2, 2) + assert latex(trace(A), mat_symbol_style='bold') == \ + r"\operatorname{tr}\left(\mathbf{A} \right)" + assert latex(trace(A), mat_symbol_style='plain') == \ + r"\operatorname{tr}\left(A \right)" + + A = MatrixSymbol("A", 3, 3) + B = MatrixSymbol("B", 3, 3) + C = MatrixSymbol("C", 3, 3) + + assert latex(-A, mat_symbol_style='bold') == r"- \mathbf{A}" + assert latex(A - A*B - B, mat_symbol_style='bold') == \ + r"\mathbf{A} - \mathbf{A} \mathbf{B} - \mathbf{B}" + assert latex(-A*B - A*B*C - B, mat_symbol_style='bold') == \ + r"- \mathbf{A} \mathbf{B} - \mathbf{A} \mathbf{B} \mathbf{C} - \mathbf{B}" + + A_k = MatrixSymbol("A_k", 3, 3) + assert latex(A_k, mat_symbol_style='bold') == r"\mathbf{A}_{k}" + + A = MatrixSymbol(r"\nabla_k", 3, 3) + assert latex(A, mat_symbol_style='bold') == r"\mathbf{\nabla}_{k}" + +def test_AppliedPermutation(): + p = Permutation(0, 1, 2) + x = Symbol('x') + assert latex(AppliedPermutation(p, x)) == \ + r'\sigma_{\left( 0\; 1\; 2\right)}(x)' + + +def test_PermutationMatrix(): + p = Permutation(0, 1, 2) + assert latex(PermutationMatrix(p)) == r'P_{\left( 0\; 1\; 2\right)}' + p = Permutation(0, 3)(1, 2) + assert latex(PermutationMatrix(p)) == \ + r'P_{\left( 0\; 3\right)\left( 1\; 2\right)}' + + +def test_issue_21758(): + from sympy.functions.elementary.piecewise import piecewise_fold + from sympy.series.fourier import FourierSeries + x = Symbol('x') + k, n = symbols('k n') + fo = FourierSeries(x, (x, -pi, pi), (0, SeqFormula(0, (k, 1, oo)), SeqFormula( + Piecewise((-2*pi*cos(n*pi)/n + 2*sin(n*pi)/n**2, (n > -oo) & (n < oo) & Ne(n, 0)), + (0, True))*sin(n*x)/pi, (n, 1, oo)))) + assert latex(piecewise_fold(fo)) == '\\begin{cases} 2 \\sin{\\left(x \\right)}' \ + ' - \\sin{\\left(2 x \\right)} + \\frac{2 \\sin{\\left(3 x \\right)}}{3} +' \ + ' \\ldots & \\text{for}\\: n > -\\infty \\wedge n < \\infty \\wedge ' \ + 'n \\neq 0 \\\\0 & \\text{otherwise} \\end{cases}' + assert latex(FourierSeries(x, (x, -pi, pi), (0, SeqFormula(0, (k, 1, oo)), + SeqFormula(0, (n, 1, oo))))) == '0' + + +def test_imaginary_unit(): + assert latex(1 + I) == r'1 + i' + assert latex(1 + I, imaginary_unit='i') == r'1 + i' + assert latex(1 + I, imaginary_unit='j') == r'1 + j' + assert latex(1 + I, imaginary_unit='foo') == r'1 + foo' + assert latex(I, imaginary_unit="ti") == r'\text{i}' + assert latex(I, imaginary_unit="tj") == r'\text{j}' + + +def test_text_re_im(): + assert latex(im(x), gothic_re_im=True) == r'\Im{\left(x\right)}' + assert latex(im(x), gothic_re_im=False) == r'\operatorname{im}{\left(x\right)}' + assert latex(re(x), gothic_re_im=True) == r'\Re{\left(x\right)}' + assert latex(re(x), gothic_re_im=False) == r'\operatorname{re}{\left(x\right)}' + + +def test_latex_diffgeom(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseScalarField, Differential + from sympy.diffgeom.rn import R2 + x,y = symbols('x y', real=True) + m = Manifold('M', 2) + assert latex(m) == r'\text{M}' + p = Patch('P', m) + assert latex(p) == r'\text{P}_{\text{M}}' + rect = CoordSystem('rect', p, [x, y]) + assert latex(rect) == r'\text{rect}^{\text{P}}_{\text{M}}' + b = BaseScalarField(rect, 0) + assert latex(b) == r'\mathbf{x}' + + g = Function('g') + s_field = g(R2.x, R2.y) + assert latex(Differential(s_field)) == \ + r'\operatorname{d}\left(g{\left(\mathbf{x},\mathbf{y} \right)}\right)' + + +def test_unit_printing(): + assert latex(5*meter) == r'5 \text{m}' + assert latex(3*gibibyte) == r'3 \text{gibibyte}' + assert latex(4*microgram/second) == r'\frac{4 \mu\text{g}}{\text{s}}' + assert latex(4*micro*gram/second) == r'\frac{4 \mu \text{g}}{\text{s}}' + assert latex(5*milli*meter) == r'5 \text{m} \text{m}' + assert latex(milli) == r'\text{m}' + + +def test_issue_17092(): + x_star = Symbol('x^*') + assert latex(Derivative(x_star, x_star,2)) == r'\frac{d^{2}}{d \left(x^{*}\right)^{2}} x^{*}' + + +def test_latex_decimal_separator(): + + x, y, z, t = symbols('x y z t') + k, m, n = symbols('k m n', integer=True) + f, g, h = symbols('f g h', cls=Function) + + # comma decimal_separator + assert(latex([1, 2.3, 4.5], decimal_separator='comma') == r'\left[ 1; \ 2{,}3; \ 4{,}5\right]') + assert(latex(FiniteSet(1, 2.3, 4.5), decimal_separator='comma') == r'\left\{1; 2{,}3; 4{,}5\right\}') + assert(latex((1, 2.3, 4.6), decimal_separator = 'comma') == r'\left( 1; \ 2{,}3; \ 4{,}6\right)') + assert(latex((1,), decimal_separator='comma') == r'\left( 1;\right)') + + # period decimal_separator + assert(latex([1, 2.3, 4.5], decimal_separator='period') == r'\left[ 1, \ 2.3, \ 4.5\right]' ) + assert(latex(FiniteSet(1, 2.3, 4.5), decimal_separator='period') == r'\left\{1, 2.3, 4.5\right\}') + assert(latex((1, 2.3, 4.6), decimal_separator = 'period') == r'\left( 1, \ 2.3, \ 4.6\right)') + assert(latex((1,), decimal_separator='period') == r'\left( 1,\right)') + + # default decimal_separator + assert(latex([1, 2.3, 4.5]) == r'\left[ 1, \ 2.3, \ 4.5\right]') + assert(latex(FiniteSet(1, 2.3, 4.5)) == r'\left\{1, 2.3, 4.5\right\}') + assert(latex((1, 2.3, 4.6)) == r'\left( 1, \ 2.3, \ 4.6\right)') + assert(latex((1,)) == r'\left( 1,\right)') + + assert(latex(Mul(3.4,5.3), decimal_separator = 'comma') == r'18{,}02') + assert(latex(3.4*5.3, decimal_separator = 'comma') == r'18{,}02') + x = symbols('x') + y = symbols('y') + z = symbols('z') + assert(latex(x*5.3 + 2**y**3.4 + 4.5 + z, decimal_separator = 'comma') == r'2^{y^{3{,}4}} + 5{,}3 x + z + 4{,}5') + + assert(latex(0.987, decimal_separator='comma') == r'0{,}987') + assert(latex(S(0.987), decimal_separator='comma') == r'0{,}987') + assert(latex(.3, decimal_separator='comma') == r'0{,}3') + assert(latex(S(.3), decimal_separator='comma') == r'0{,}3') + + + assert(latex(5.8*10**(-7), decimal_separator='comma') == r'5{,}8 \cdot 10^{-7}') + assert(latex(S(5.7)*10**(-7), decimal_separator='comma') == r'5{,}7 \cdot 10^{-7}') + assert(latex(S(5.7*10**(-7)), decimal_separator='comma') == r'5{,}7 \cdot 10^{-7}') + + x = symbols('x') + assert(latex(1.2*x+3.4, decimal_separator='comma') == r'1{,}2 x + 3{,}4') + assert(latex(FiniteSet(1, 2.3, 4.5), decimal_separator='period') == r'\left\{1, 2.3, 4.5\right\}') + + # Error Handling tests + raises(ValueError, lambda: latex([1,2.3,4.5], decimal_separator='non_existing_decimal_separator_in_list')) + raises(ValueError, lambda: latex(FiniteSet(1,2.3,4.5), decimal_separator='non_existing_decimal_separator_in_set')) + raises(ValueError, lambda: latex((1,2.3,4.5), decimal_separator='non_existing_decimal_separator_in_tuple')) + +def test_Str(): + from sympy.core.symbol import Str + assert str(Str('x')) == r'x' + +def test_latex_escape(): + assert latex_escape(r"~^\&%$#_{}") == "".join([ + r'\textasciitilde', + r'\textasciicircum', + r'\textbackslash', + r'\&', + r'\%', + r'\$', + r'\#', + r'\_', + r'\{', + r'\}', + ]) + +def test_emptyPrinter(): + class MyObject: + def __repr__(self): + return "" + + # unknown objects are monospaced + assert latex(MyObject()) == r"\mathtt{\text{}}" + + # even if they are nested within other objects + assert latex((MyObject(),)) == r"\left( \mathtt{\text{}},\right)" + +def test_global_settings(): + import inspect + + # settings should be visible in the signature of `latex` + assert inspect.signature(latex).parameters['imaginary_unit'].default == r'i' + assert latex(I) == r'i' + try: + # but changing the defaults... + LatexPrinter.set_global_settings(imaginary_unit='j') + # ... should change the signature + assert inspect.signature(latex).parameters['imaginary_unit'].default == r'j' + assert latex(I) == r'j' + finally: + # there's no public API to undo this, but we need to make sure we do + # so as not to impact other tests + del LatexPrinter._global_settings['imaginary_unit'] + + # check we really did undo it + assert inspect.signature(latex).parameters['imaginary_unit'].default == r'i' + assert latex(I) == r'i' + +def test_pickleable(): + # this tests that the _PrintFunction instance is pickleable + import pickle + assert pickle.loads(pickle.dumps(latex)) is latex + +def test_printing_latex_array_expressions(): + assert latex(ArraySymbol("A", (2, 3, 4))) == "A" + assert latex(ArrayElement("A", (2, 1/(1-x), 0))) == "{{A}_{2, \\frac{1}{1 - x}, 0}}" + M = MatrixSymbol("M", 3, 3) + N = MatrixSymbol("N", 3, 3) + assert latex(ArrayElement(M*N, [x, 0])) == "{{\\left(M N\\right)}_{x, 0}}" + +def test_Array(): + arr = Array(range(10)) + assert latex(arr) == r'\left[\begin{matrix}0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9\end{matrix}\right]' + + arr = Array(range(11)) + # fill the empty argument with a bunch of 'c' to avoid latex errors + assert latex(arr) == r'\left[\begin{array}{ccccccccccc}0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10\end{array}\right]' + +def test_latex_with_unevaluated(): + with evaluate(False): + assert latex(a * a) == r"a a" + + +def test_latex_disable_split_super_sub(): + assert latex(Symbol('u^a_b')) == 'u^{a}_{b}' + assert latex(Symbol('u^a_b'), disable_split_super_sub=False) == 'u^{a}_{b}' + assert latex(Symbol('u^a_b'), disable_split_super_sub=True) == 'u\\^a\\_b' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_llvmjit.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_llvmjit.py new file mode 100644 index 0000000000000000000000000000000000000000..709476f1d7517dc629210341594a70dc6f41808f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_llvmjit.py @@ -0,0 +1,224 @@ +from sympy.external import import_module +from sympy.testing.pytest import raises +import ctypes + + +if import_module('llvmlite'): + import sympy.printing.llvmjitcode as g +else: + disabled = True + +import sympy +from sympy.abc import a, b, n + + +# copied from numpy.isclose documentation +def isclose(a, b): + rtol = 1e-5 + atol = 1e-8 + return abs(a-b) <= atol + rtol*abs(b) + + +def test_simple_expr(): + e = a + 1.0 + f = g.llvm_callable([a], e) + res = float(e.subs({a: 4.0}).evalf()) + jit_res = f(4.0) + + assert isclose(jit_res, res) + + +def test_two_arg(): + e = 4.0*a + b + 3.0 + f = g.llvm_callable([a, b], e) + res = float(e.subs({a: 4.0, b: 3.0}).evalf()) + jit_res = f(4.0, 3.0) + + assert isclose(jit_res, res) + + +def test_func(): + e = 4.0*sympy.exp(-a) + f = g.llvm_callable([a], e) + res = float(e.subs({a: 1.5}).evalf()) + jit_res = f(1.5) + + assert isclose(jit_res, res) + + +def test_two_func(): + e = 4.0*sympy.exp(-a) + sympy.exp(b) + f = g.llvm_callable([a, b], e) + res = float(e.subs({a: 1.5, b: 2.0}).evalf()) + jit_res = f(1.5, 2.0) + + assert isclose(jit_res, res) + + +def test_two_sqrt(): + e = 4.0*sympy.sqrt(a) + sympy.sqrt(b) + f = g.llvm_callable([a, b], e) + res = float(e.subs({a: 1.5, b: 2.0}).evalf()) + jit_res = f(1.5, 2.0) + + assert isclose(jit_res, res) + + +def test_two_pow(): + e = a**1.5 + b**7 + f = g.llvm_callable([a, b], e) + res = float(e.subs({a: 1.5, b: 2.0}).evalf()) + jit_res = f(1.5, 2.0) + + assert isclose(jit_res, res) + + +def test_callback(): + e = a + 1.2 + f = g.llvm_callable([a], e, callback_type='scipy.integrate.test') + m = ctypes.c_int(1) + array_type = ctypes.c_double * 1 + inp = {a: 2.2} + array = array_type(inp[a]) + jit_res = f(m, array) + + res = float(e.subs(inp).evalf()) + + assert isclose(jit_res, res) + + +def test_callback_cubature(): + e = a + 1.2 + f = g.llvm_callable([a], e, callback_type='cubature') + m = ctypes.c_int(1) + array_type = ctypes.c_double * 1 + inp = {a: 2.2} + array = array_type(inp[a]) + out_array = array_type(0.0) + jit_ret = f(m, array, None, m, out_array) + + assert jit_ret == 0 + + res = float(e.subs(inp).evalf()) + + assert isclose(out_array[0], res) + + +def test_callback_two(): + e = 3*a*b + f = g.llvm_callable([a, b], e, callback_type='scipy.integrate.test') + m = ctypes.c_int(2) + array_type = ctypes.c_double * 2 + inp = {a: 0.2, b: 1.7} + array = array_type(inp[a], inp[b]) + jit_res = f(m, array) + + res = float(e.subs(inp).evalf()) + + assert isclose(jit_res, res) + + +def test_callback_alt_two(): + d = sympy.IndexedBase('d') + e = 3*d[0]*d[1] + f = g.llvm_callable([n, d], e, callback_type='scipy.integrate.test') + m = ctypes.c_int(2) + array_type = ctypes.c_double * 2 + inp = {d[0]: 0.2, d[1]: 1.7} + array = array_type(inp[d[0]], inp[d[1]]) + jit_res = f(m, array) + + res = float(e.subs(inp).evalf()) + + assert isclose(jit_res, res) + + +def test_multiple_statements(): + # Match return from CSE + e = [[(b, 4.0*a)], [b + 5]] + f = g.llvm_callable([a], e) + b_val = e[0][0][1].subs({a: 1.5}) + res = float(e[1][0].subs({b: b_val}).evalf()) + jit_res = f(1.5) + assert isclose(jit_res, res) + + f_callback = g.llvm_callable([a], e, callback_type='scipy.integrate.test') + m = ctypes.c_int(1) + array_type = ctypes.c_double * 1 + array = array_type(1.5) + jit_callback_res = f_callback(m, array) + assert isclose(jit_callback_res, res) + + +def test_cse(): + e = a*a + b*b + sympy.exp(-a*a - b*b) + e2 = sympy.cse(e) + f = g.llvm_callable([a, b], e2) + res = float(e.subs({a: 2.3, b: 0.1}).evalf()) + jit_res = f(2.3, 0.1) + + assert isclose(jit_res, res) + + +def eval_cse(e, sub_dict): + tmp_dict = {} + for tmp_name, tmp_expr in e[0]: + e2 = tmp_expr.subs(sub_dict) + e3 = e2.subs(tmp_dict) + tmp_dict[tmp_name] = e3 + return [e.subs(sub_dict).subs(tmp_dict) for e in e[1]] + + +def test_cse_multiple(): + e1 = a*a + e2 = a*a + b*b + e3 = sympy.cse([e1, e2]) + + raises(NotImplementedError, + lambda: g.llvm_callable([a, b], e3, callback_type='scipy.integrate')) + + f = g.llvm_callable([a, b], e3) + jit_res = f(0.1, 1.5) + assert len(jit_res) == 2 + res = eval_cse(e3, {a: 0.1, b: 1.5}) + assert isclose(res[0], jit_res[0]) + assert isclose(res[1], jit_res[1]) + + +def test_callback_cubature_multiple(): + e1 = a*a + e2 = a*a + b*b + e3 = sympy.cse([e1, e2, 4*e2]) + f = g.llvm_callable([a, b], e3, callback_type='cubature') + + # Number of input variables + ndim = 2 + # Number of output expression values + outdim = 3 + + m = ctypes.c_int(ndim) + fdim = ctypes.c_int(outdim) + array_type = ctypes.c_double * ndim + out_array_type = ctypes.c_double * outdim + inp = {a: 0.2, b: 1.5} + array = array_type(inp[a], inp[b]) + out_array = out_array_type() + jit_ret = f(m, array, None, fdim, out_array) + + assert jit_ret == 0 + + res = eval_cse(e3, inp) + + assert isclose(out_array[0], res[0]) + assert isclose(out_array[1], res[1]) + assert isclose(out_array[2], res[2]) + + +def test_symbol_not_found(): + e = a*a + b + raises(LookupError, lambda: g.llvm_callable([a], e)) + + +def test_bad_callback(): + e = a + raises(ValueError, lambda: g.llvm_callable([a], e, callback_type='bad_callback')) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_maple.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_maple.py new file mode 100644 index 0000000000000000000000000000000000000000..9bb4c512ad3203bd64ae56b350e15734b3a6afb0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_maple.py @@ -0,0 +1,381 @@ +from sympy.core import (S, pi, oo, symbols, Function, Rational, Integer, + Tuple, Symbol, Eq, Ne, Le, Lt, Gt, Ge) +from sympy.core import EulerGamma, GoldenRatio, Catalan, Lambda, Mul, Pow +from sympy.functions import Piecewise, sqrt, ceiling, exp, sin, cos, sinc, lucas +from sympy.testing.pytest import raises +from sympy.utilities.lambdify import implemented_function +from sympy.matrices import (eye, Matrix, MatrixSymbol, Identity, + HadamardProduct, SparseMatrix) +from sympy.functions.special.bessel import besseli + +from sympy.printing.maple import maple_code + +x, y, z = symbols('x,y,z') + + +def test_Integer(): + assert maple_code(Integer(67)) == "67" + assert maple_code(Integer(-1)) == "-1" + + +def test_Rational(): + assert maple_code(Rational(3, 7)) == "3/7" + assert maple_code(Rational(18, 9)) == "2" + assert maple_code(Rational(3, -7)) == "-3/7" + assert maple_code(Rational(-3, -7)) == "3/7" + assert maple_code(x + Rational(3, 7)) == "x + 3/7" + assert maple_code(Rational(3, 7) * x) == '(3/7)*x' + + +def test_Relational(): + assert maple_code(Eq(x, y)) == "x = y" + assert maple_code(Ne(x, y)) == "x <> y" + assert maple_code(Le(x, y)) == "x <= y" + assert maple_code(Lt(x, y)) == "x < y" + assert maple_code(Gt(x, y)) == "x > y" + assert maple_code(Ge(x, y)) == "x >= y" + + +def test_Function(): + assert maple_code(sin(x) ** cos(x)) == "sin(x)^cos(x)" + assert maple_code(abs(x)) == "abs(x)" + assert maple_code(ceiling(x)) == "ceil(x)" + + +def test_Pow(): + assert maple_code(x ** 3) == "x^3" + assert maple_code(x ** (y ** 3)) == "x^(y^3)" + + assert maple_code((x ** 3) ** y) == "(x^3)^y" + assert maple_code(x ** Rational(2, 3)) == 'x^(2/3)' + + g = implemented_function('g', Lambda(x, 2 * x)) + assert maple_code(1 / (g(x) * 3.5) ** (x - y ** x) / (x ** 2 + y)) == \ + "(3.5*2*x)^(-x + y^x)/(x^2 + y)" + # For issue 14160 + assert maple_code(Mul(-2, x, Pow(Mul(y, y, evaluate=False), -1, evaluate=False), + evaluate=False)) == '-2*x/(y*y)' + + +def test_basic_ops(): + assert maple_code(x * y) == "x*y" + assert maple_code(x + y) == "x + y" + assert maple_code(x - y) == "x - y" + assert maple_code(-x) == "-x" + + +def test_1_over_x_and_sqrt(): + # 1.0 and 0.5 would do something different in regular StrPrinter, + # but these are exact in IEEE floating point so no different here. + assert maple_code(1 / x) == '1/x' + assert maple_code(x ** -1) == maple_code(x ** -1.0) == '1/x' + assert maple_code(1 / sqrt(x)) == '1/sqrt(x)' + assert maple_code(x ** -S.Half) == maple_code(x ** -0.5) == '1/sqrt(x)' + assert maple_code(sqrt(x)) == 'sqrt(x)' + assert maple_code(x ** S.Half) == maple_code(x ** 0.5) == 'sqrt(x)' + assert maple_code(1 / pi) == '1/Pi' + assert maple_code(pi ** -1) == maple_code(pi ** -1.0) == '1/Pi' + assert maple_code(pi ** -0.5) == '1/sqrt(Pi)' + + +def test_mix_number_mult_symbols(): + assert maple_code(3 * x) == "3*x" + assert maple_code(pi * x) == "Pi*x" + assert maple_code(3 / x) == "3/x" + assert maple_code(pi / x) == "Pi/x" + assert maple_code(x / 3) == '(1/3)*x' + assert maple_code(x / pi) == "x/Pi" + assert maple_code(x * y) == "x*y" + assert maple_code(3 * x * y) == "3*x*y" + assert maple_code(3 * pi * x * y) == "3*Pi*x*y" + assert maple_code(x / y) == "x/y" + assert maple_code(3 * x / y) == "3*x/y" + assert maple_code(x * y / z) == "x*y/z" + assert maple_code(x / y * z) == "x*z/y" + assert maple_code(1 / x / y) == "1/(x*y)" + assert maple_code(2 * pi * x / y / z) == "2*Pi*x/(y*z)" + assert maple_code(3 * pi / x) == "3*Pi/x" + assert maple_code(S(3) / 5) == "3/5" + assert maple_code(S(3) / 5 * x) == '(3/5)*x' + assert maple_code(x / y / z) == "x/(y*z)" + assert maple_code((x + y) / z) == "(x + y)/z" + assert maple_code((x + y) / (z + x)) == "(x + y)/(x + z)" + assert maple_code((x + y) / EulerGamma) == '(x + y)/gamma' + assert maple_code(x / 3 / pi) == '(1/3)*x/Pi' + assert maple_code(S(3) / 5 * x * y / pi) == '(3/5)*x*y/Pi' + + +def test_mix_number_pow_symbols(): + assert maple_code(pi ** 3) == 'Pi^3' + assert maple_code(x ** 2) == 'x^2' + + assert maple_code(x ** (pi ** 3)) == 'x^(Pi^3)' + assert maple_code(x ** y) == 'x^y' + + assert maple_code(x ** (y ** z)) == 'x^(y^z)' + assert maple_code((x ** y) ** z) == '(x^y)^z' + + +def test_imag(): + I = S('I') + assert maple_code(I) == "I" + assert maple_code(5 * I) == "5*I" + + assert maple_code((S(3) / 2) * I) == "(3/2)*I" + assert maple_code(3 + 4 * I) == "3 + 4*I" + + +def test_constants(): + assert maple_code(pi) == "Pi" + assert maple_code(oo) == "infinity" + assert maple_code(-oo) == "-infinity" + assert maple_code(S.NegativeInfinity) == "-infinity" + assert maple_code(S.NaN) == "undefined" + assert maple_code(S.Exp1) == "exp(1)" + assert maple_code(exp(1)) == "exp(1)" + + +def test_constants_other(): + assert maple_code(2 * GoldenRatio) == '2*(1/2 + (1/2)*sqrt(5))' + assert maple_code(2 * Catalan) == '2*Catalan' + assert maple_code(2 * EulerGamma) == "2*gamma" + + +def test_boolean(): + assert maple_code(x & y) == "x and y" + assert maple_code(x | y) == "x or y" + assert maple_code(~x) == "not x" + assert maple_code(x & y & z) == "x and y and z" + assert maple_code(x | y | z) == "x or y or z" + assert maple_code((x & y) | z) == "z or x and y" + assert maple_code((x | y) & z) == "z and (x or y)" + + +def test_Matrices(): + assert maple_code(Matrix(1, 1, [10])) == \ + 'Matrix([[10]], storage = rectangular)' + + A = Matrix([[1, sin(x / 2), abs(x)], + [0, 1, pi], + [0, exp(1), ceiling(x)]]) + expected = \ + 'Matrix(' \ + '[[1, sin((1/2)*x), abs(x)],' \ + ' [0, 1, Pi],' \ + ' [0, exp(1), ceil(x)]], ' \ + 'storage = rectangular)' + assert maple_code(A) == expected + + # row and columns + assert maple_code(A[:, 0]) == \ + 'Matrix([[1], [0], [0]], storage = rectangular)' + assert maple_code(A[0, :]) == \ + 'Matrix([[1, sin((1/2)*x), abs(x)]], storage = rectangular)' + assert maple_code(Matrix([[x, x - y, -y]])) == \ + 'Matrix([[x, x - y, -y]], storage = rectangular)' + + # empty matrices + assert maple_code(Matrix(0, 0, [])) == \ + 'Matrix([], storage = rectangular)' + assert maple_code(Matrix(0, 3, [])) == \ + 'Matrix([], storage = rectangular)' + +def test_SparseMatrices(): + assert maple_code(SparseMatrix(Identity(2))) == 'Matrix([[1, 0], [0, 1]], storage = sparse)' + + +def test_vector_entries_hadamard(): + # For a row or column, user might to use the other dimension + A = Matrix([[1, sin(2 / x), 3 * pi / x / 5]]) + assert maple_code(A) == \ + 'Matrix([[1, sin(2/x), (3/5)*Pi/x]], storage = rectangular)' + assert maple_code(A.T) == \ + 'Matrix([[1], [sin(2/x)], [(3/5)*Pi/x]], storage = rectangular)' + + +def test_Matrices_entries_not_hadamard(): + A = Matrix([[1, sin(2 / x), 3 * pi / x / 5], [1, 2, x * y]]) + expected = \ + 'Matrix([[1, sin(2/x), (3/5)*Pi/x], [1, 2, x*y]], ' \ + 'storage = rectangular)' + assert maple_code(A) == expected + + +def test_MatrixSymbol(): + n = Symbol('n', integer=True) + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', n, n) + assert maple_code(A * B) == "A.B" + assert maple_code(B * A) == "B.A" + assert maple_code(2 * A * B) == "2*A.B" + assert maple_code(B * 2 * A) == "2*B.A" + + assert maple_code( + A * (B + 3 * Identity(n))) == "A.(3*Matrix(n, shape = identity) + B)" + + assert maple_code(A ** (x ** 2)) == "MatrixPower(A, x^2)" + assert maple_code(A ** 3) == "MatrixPower(A, 3)" + assert maple_code(A ** (S.Half)) == "MatrixPower(A, 1/2)" + + +def test_special_matrices(): + assert maple_code(6 * Identity(3)) == "6*Matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], storage = sparse)" + assert maple_code(Identity(x)) == 'Matrix(x, shape = identity)' + + +def test_containers(): + assert maple_code([1, 2, 3, [4, 5, [6, 7]], 8, [9, 10], 11]) == \ + "[1, 2, 3, [4, 5, [6, 7]], 8, [9, 10], 11]" + + assert maple_code((1, 2, (3, 4))) == "[1, 2, [3, 4]]" + assert maple_code([1]) == "[1]" + assert maple_code((1,)) == "[1]" + assert maple_code(Tuple(*[1, 2, 3])) == "[1, 2, 3]" + assert maple_code((1, x * y, (3, x ** 2))) == "[1, x*y, [3, x^2]]" + # scalar, matrix, empty matrix and empty list + + assert maple_code((1, eye(3), Matrix(0, 0, []), [])) == \ + "[1, Matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], storage = rectangular), Matrix([], storage = rectangular), []]" + + +def test_maple_noninline(): + source = maple_code((x + y)/Catalan, assign_to='me', inline=False) + expected = "me := (x + y)/Catalan" + + assert source == expected + + +def test_maple_matrix_assign_to(): + A = Matrix([[1, 2, 3]]) + assert maple_code(A, assign_to='a') == "a := Matrix([[1, 2, 3]], storage = rectangular)" + A = Matrix([[1, 2], [3, 4]]) + assert maple_code(A, assign_to='A') == "A := Matrix([[1, 2], [3, 4]], storage = rectangular)" + + +def test_maple_matrix_assign_to_more(): + # assigning to Symbol or MatrixSymbol requires lhs/rhs match + A = Matrix([[1, 2, 3]]) + B = MatrixSymbol('B', 1, 3) + C = MatrixSymbol('C', 2, 3) + assert maple_code(A, assign_to=B) == "B := Matrix([[1, 2, 3]], storage = rectangular)" + raises(ValueError, lambda: maple_code(A, assign_to=x)) + raises(ValueError, lambda: maple_code(A, assign_to=C)) + + +def test_maple_matrix_1x1(): + A = Matrix([[3]]) + assert maple_code(A, assign_to='B') == "B := Matrix([[3]], storage = rectangular)" + + +def test_maple_matrix_elements(): + A = Matrix([[x, 2, x * y]]) + + assert maple_code(A[0, 0] ** 2 + A[0, 1] + A[0, 2]) == "x^2 + x*y + 2" + AA = MatrixSymbol('AA', 1, 3) + assert maple_code(AA) == "AA" + + assert maple_code(AA[0, 0] ** 2 + sin(AA[0, 1]) + AA[0, 2]) == \ + "sin(AA[1, 2]) + AA[1, 1]^2 + AA[1, 3]" + assert maple_code(sum(AA)) == "AA[1, 1] + AA[1, 2] + AA[1, 3]" + + +def test_maple_boolean(): + assert maple_code(True) == "true" + assert maple_code(S.true) == "true" + assert maple_code(False) == "false" + assert maple_code(S.false) == "false" + + +def test_sparse(): + M = SparseMatrix(5, 6, {}) + M[2, 2] = 10 + M[1, 2] = 20 + M[1, 3] = 22 + M[0, 3] = 30 + M[3, 0] = x * y + assert maple_code(M) == \ + 'Matrix([[0, 0, 0, 30, 0, 0],' \ + ' [0, 0, 20, 22, 0, 0],' \ + ' [0, 0, 10, 0, 0, 0],' \ + ' [x*y, 0, 0, 0, 0, 0],' \ + ' [0, 0, 0, 0, 0, 0]], ' \ + 'storage = sparse)' + +# Not an important point. +def test_maple_not_supported(): + with raises(NotImplementedError): + maple_code(S.ComplexInfinity) + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + + assert (maple_code(A[0, 0]) == "A[1, 1]") + assert (maple_code(3 * A[0, 0]) == "3*A[1, 1]") + + F = A-B + + assert (maple_code(F[0,0]) == "A[1, 1] - B[1, 1]") + + +def test_hadamard(): + A = MatrixSymbol('A', 3, 3) + B = MatrixSymbol('B', 3, 3) + v = MatrixSymbol('v', 3, 1) + h = MatrixSymbol('h', 1, 3) + C = HadamardProduct(A, B) + assert maple_code(C) == "A*B" + + assert maple_code(C * v) == "(A*B).v" + # HadamardProduct is higher than dot product. + + assert maple_code(h * C * v) == "h.(A*B).v" + + assert maple_code(C * A) == "(A*B).A" + # mixing Hadamard and scalar strange b/c we vectorize scalars + + assert maple_code(C * x * y) == "x*y*(A*B)" + + +def test_maple_piecewise(): + expr = Piecewise((x, x < 1), (x ** 2, True)) + + assert maple_code(expr) == "piecewise(x < 1, x, x^2)" + assert maple_code(expr, assign_to="r") == ( + "r := piecewise(x < 1, x, x^2)") + + expr = Piecewise((x ** 2, x < 1), (x ** 3, x < 2), (x ** 4, x < 3), (x ** 5, True)) + expected = "piecewise(x < 1, x^2, x < 2, x^3, x < 3, x^4, x^5)" + assert maple_code(expr) == expected + assert maple_code(expr, assign_to="r") == "r := " + expected + + # Check that Piecewise without a True (default) condition error + expr = Piecewise((x, x < 1), (x ** 2, x > 1), (sin(x), x > 0)) + raises(ValueError, lambda: maple_code(expr)) + + +def test_maple_piecewise_times_const(): + pw = Piecewise((x, x < 1), (x ** 2, True)) + + assert maple_code(2 * pw) == "2*piecewise(x < 1, x, x^2)" + assert maple_code(pw / x) == "piecewise(x < 1, x, x^2)/x" + assert maple_code(pw / (x * y)) == "piecewise(x < 1, x, x^2)/(x*y)" + assert maple_code(pw / 3) == "(1/3)*piecewise(x < 1, x, x^2)" + + +def test_maple_derivatives(): + f = Function('f') + assert maple_code(f(x).diff(x)) == 'diff(f(x), x)' + assert maple_code(f(x).diff(x, 2)) == 'diff(f(x), x$2)' + + +def test_automatic_rewrites(): + assert maple_code(lucas(x)) == '(2^(-x)*((1 - sqrt(5))^x + (1 + sqrt(5))^x))' + assert maple_code(sinc(x)) == '(piecewise(x <> 0, sin(x)/x, 1))' + + +def test_specfun(): + assert maple_code('asin(x)') == 'arcsin(x)' + assert maple_code(besseli(x, y)) == 'BesselI(x, y)' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_mathematica.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_mathematica.py new file mode 100644 index 0000000000000000000000000000000000000000..aaf6b537677442ae59a4f1bbd2b5774d6646f4e2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_mathematica.py @@ -0,0 +1,287 @@ +from sympy.core import (S, pi, oo, symbols, Function, Rational, Integer, Tuple, + Derivative, Eq, Ne, Le, Lt, Gt, Ge) +from sympy.integrals import Integral +from sympy.concrete import Sum +from sympy.functions import (exp, sin, cos, fresnelc, fresnels, conjugate, Max, + Min, gamma, polygamma, loggamma, erf, erfi, erfc, + erf2, expint, erfinv, erfcinv, Ei, Si, Ci, li, + Shi, Chi, uppergamma, beta, subfactorial, erf2inv, + factorial, factorial2, catalan, RisingFactorial, + FallingFactorial, harmonic, atan2, sec, acsc, + hermite, laguerre, assoc_laguerre, jacobi, + gegenbauer, chebyshevt, chebyshevu, legendre, + assoc_legendre, Li, LambertW) + +from sympy.printing.mathematica import mathematica_code as mcode + +x, y, z, w = symbols('x,y,z,w') +f = Function('f') + + +def test_Integer(): + assert mcode(Integer(67)) == "67" + assert mcode(Integer(-1)) == "-1" + + +def test_Rational(): + assert mcode(Rational(3, 7)) == "3/7" + assert mcode(Rational(18, 9)) == "2" + assert mcode(Rational(3, -7)) == "-3/7" + assert mcode(Rational(-3, -7)) == "3/7" + assert mcode(x + Rational(3, 7)) == "x + 3/7" + assert mcode(Rational(3, 7)*x) == "(3/7)*x" + + +def test_Relational(): + assert mcode(Eq(x, y)) == "x == y" + assert mcode(Ne(x, y)) == "x != y" + assert mcode(Le(x, y)) == "x <= y" + assert mcode(Lt(x, y)) == "x < y" + assert mcode(Gt(x, y)) == "x > y" + assert mcode(Ge(x, y)) == "x >= y" + + +def test_Function(): + assert mcode(f(x, y, z)) == "f[x, y, z]" + assert mcode(sin(x) ** cos(x)) == "Sin[x]^Cos[x]" + assert mcode(sec(x) * acsc(x)) == "ArcCsc[x]*Sec[x]" + assert mcode(atan2(y, x)) == "ArcTan[x, y]" + assert mcode(conjugate(x)) == "Conjugate[x]" + assert mcode(Max(x, y, z)*Min(y, z)) == "Max[x, y, z]*Min[y, z]" + assert mcode(fresnelc(x)) == "FresnelC[x]" + assert mcode(fresnels(x)) == "FresnelS[x]" + assert mcode(gamma(x)) == "Gamma[x]" + assert mcode(uppergamma(x, y)) == "Gamma[x, y]" + assert mcode(polygamma(x, y)) == "PolyGamma[x, y]" + assert mcode(loggamma(x)) == "LogGamma[x]" + assert mcode(erf(x)) == "Erf[x]" + assert mcode(erfc(x)) == "Erfc[x]" + assert mcode(erfi(x)) == "Erfi[x]" + assert mcode(erf2(x, y)) == "Erf[x, y]" + assert mcode(expint(x, y)) == "ExpIntegralE[x, y]" + assert mcode(erfcinv(x)) == "InverseErfc[x]" + assert mcode(erfinv(x)) == "InverseErf[x]" + assert mcode(erf2inv(x, y)) == "InverseErf[x, y]" + assert mcode(Ei(x)) == "ExpIntegralEi[x]" + assert mcode(Ci(x)) == "CosIntegral[x]" + assert mcode(li(x)) == "LogIntegral[x]" + assert mcode(Si(x)) == "SinIntegral[x]" + assert mcode(Shi(x)) == "SinhIntegral[x]" + assert mcode(Chi(x)) == "CoshIntegral[x]" + assert mcode(beta(x, y)) == "Beta[x, y]" + assert mcode(factorial(x)) == "Factorial[x]" + assert mcode(factorial2(x)) == "Factorial2[x]" + assert mcode(subfactorial(x)) == "Subfactorial[x]" + assert mcode(FallingFactorial(x, y)) == "FactorialPower[x, y]" + assert mcode(RisingFactorial(x, y)) == "Pochhammer[x, y]" + assert mcode(catalan(x)) == "CatalanNumber[x]" + assert mcode(harmonic(x)) == "HarmonicNumber[x]" + assert mcode(harmonic(x, y)) == "HarmonicNumber[x, y]" + assert mcode(Li(x)) == "LogIntegral[x] - LogIntegral[2]" + assert mcode(LambertW(x)) == "ProductLog[x]" + assert mcode(LambertW(x, -1)) == "ProductLog[-1, x]" + assert mcode(LambertW(x, y)) == "ProductLog[y, x]" + + +def test_special_polynomials(): + assert mcode(hermite(x, y)) == "HermiteH[x, y]" + assert mcode(laguerre(x, y)) == "LaguerreL[x, y]" + assert mcode(assoc_laguerre(x, y, z)) == "LaguerreL[x, y, z]" + assert mcode(jacobi(x, y, z, w)) == "JacobiP[x, y, z, w]" + assert mcode(gegenbauer(x, y, z)) == "GegenbauerC[x, y, z]" + assert mcode(chebyshevt(x, y)) == "ChebyshevT[x, y]" + assert mcode(chebyshevu(x, y)) == "ChebyshevU[x, y]" + assert mcode(legendre(x, y)) == "LegendreP[x, y]" + assert mcode(assoc_legendre(x, y, z)) == "LegendreP[x, y, z]" + + +def test_Pow(): + assert mcode(x**3) == "x^3" + assert mcode(x**(y**3)) == "x^(y^3)" + assert mcode(1/(f(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + "(3.5*f[x])^(-x + y^x)/(x^2 + y)" + assert mcode(x**-1.0) == 'x^(-1.0)' + assert mcode(x**Rational(2, 3)) == 'x^(2/3)' + + +def test_Mul(): + A, B, C, D = symbols('A B C D', commutative=False) + assert mcode(x*y*z) == "x*y*z" + assert mcode(x*y*A) == "x*y*A" + assert mcode(x*y*A*B) == "x*y*A**B" + assert mcode(x*y*A*B*C) == "x*y*A**B**C" + assert mcode(x*A*B*(C + D)*A*y) == "x*y*A**B**(C + D)**A" + + +def test_constants(): + assert mcode(S.Zero) == "0" + assert mcode(S.One) == "1" + assert mcode(S.NegativeOne) == "-1" + assert mcode(S.Half) == "1/2" + assert mcode(S.ImaginaryUnit) == "I" + + assert mcode(oo) == "Infinity" + assert mcode(S.NegativeInfinity) == "-Infinity" + assert mcode(S.ComplexInfinity) == "ComplexInfinity" + assert mcode(S.NaN) == "Indeterminate" + + assert mcode(S.Exp1) == "E" + assert mcode(pi) == "Pi" + assert mcode(S.GoldenRatio) == "GoldenRatio" + assert mcode(S.TribonacciConstant) == \ + "(1/3 + (1/3)*(19 - 3*33^(1/2))^(1/3) + " \ + "(1/3)*(3*33^(1/2) + 19)^(1/3))" + assert mcode(2*S.TribonacciConstant) == \ + "2*(1/3 + (1/3)*(19 - 3*33^(1/2))^(1/3) + " \ + "(1/3)*(3*33^(1/2) + 19)^(1/3))" + assert mcode(S.EulerGamma) == "EulerGamma" + assert mcode(S.Catalan) == "Catalan" + + +def test_containers(): + assert mcode([1, 2, 3, [4, 5, [6, 7]], 8, [9, 10], 11]) == \ + "{1, 2, 3, {4, 5, {6, 7}}, 8, {9, 10}, 11}" + assert mcode((1, 2, (3, 4))) == "{1, 2, {3, 4}}" + assert mcode([1]) == "{1}" + assert mcode((1,)) == "{1}" + assert mcode(Tuple(*[1, 2, 3])) == "{1, 2, 3}" + + +def test_matrices(): + from sympy.matrices import MutableDenseMatrix, MutableSparseMatrix, \ + ImmutableDenseMatrix, ImmutableSparseMatrix + A = MutableDenseMatrix( + [[1, -1, 0, 0], + [0, 1, -1, 0], + [0, 0, 1, -1], + [0, 0, 0, 1]] + ) + B = MutableSparseMatrix(A) + C = ImmutableDenseMatrix(A) + D = ImmutableSparseMatrix(A) + + assert mcode(C) == mcode(A) == \ + "{{1, -1, 0, 0}, " \ + "{0, 1, -1, 0}, " \ + "{0, 0, 1, -1}, " \ + "{0, 0, 0, 1}}" + + assert mcode(D) == mcode(B) == \ + "SparseArray[{" \ + "{1, 1} -> 1, {1, 2} -> -1, {2, 2} -> 1, {2, 3} -> -1, " \ + "{3, 3} -> 1, {3, 4} -> -1, {4, 4} -> 1" \ + "}, {4, 4}]" + + # Trivial cases of matrices + assert mcode(MutableDenseMatrix(0, 0, [])) == '{}' + assert mcode(MutableSparseMatrix(0, 0, [])) == 'SparseArray[{}, {0, 0}]' + assert mcode(MutableDenseMatrix(0, 3, [])) == '{}' + assert mcode(MutableSparseMatrix(0, 3, [])) == 'SparseArray[{}, {0, 3}]' + assert mcode(MutableDenseMatrix(3, 0, [])) == '{{}, {}, {}}' + assert mcode(MutableSparseMatrix(3, 0, [])) == 'SparseArray[{}, {3, 0}]' + +def test_NDArray(): + from sympy.tensor.array import ( + MutableDenseNDimArray, ImmutableDenseNDimArray, + MutableSparseNDimArray, ImmutableSparseNDimArray) + + example = MutableDenseNDimArray( + [[[1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12]], + [[13, 14, 15, 16], + [17, 18, 19, 20], + [21, 22, 23, 24]]] + ) + + assert mcode(example) == \ + "{{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, " \ + "{{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}" + + example = ImmutableDenseNDimArray(example) + + assert mcode(example) == \ + "{{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, " \ + "{{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}" + + example = MutableSparseNDimArray(example) + + assert mcode(example) == \ + "SparseArray[{" \ + "{1, 1, 1} -> 1, {1, 1, 2} -> 2, {1, 1, 3} -> 3, " \ + "{1, 1, 4} -> 4, {1, 2, 1} -> 5, {1, 2, 2} -> 6, " \ + "{1, 2, 3} -> 7, {1, 2, 4} -> 8, {1, 3, 1} -> 9, " \ + "{1, 3, 2} -> 10, {1, 3, 3} -> 11, {1, 3, 4} -> 12, " \ + "{2, 1, 1} -> 13, {2, 1, 2} -> 14, {2, 1, 3} -> 15, " \ + "{2, 1, 4} -> 16, {2, 2, 1} -> 17, {2, 2, 2} -> 18, " \ + "{2, 2, 3} -> 19, {2, 2, 4} -> 20, {2, 3, 1} -> 21, " \ + "{2, 3, 2} -> 22, {2, 3, 3} -> 23, {2, 3, 4} -> 24" \ + "}, {2, 3, 4}]" + + example = ImmutableSparseNDimArray(example) + + assert mcode(example) == \ + "SparseArray[{" \ + "{1, 1, 1} -> 1, {1, 1, 2} -> 2, {1, 1, 3} -> 3, " \ + "{1, 1, 4} -> 4, {1, 2, 1} -> 5, {1, 2, 2} -> 6, " \ + "{1, 2, 3} -> 7, {1, 2, 4} -> 8, {1, 3, 1} -> 9, " \ + "{1, 3, 2} -> 10, {1, 3, 3} -> 11, {1, 3, 4} -> 12, " \ + "{2, 1, 1} -> 13, {2, 1, 2} -> 14, {2, 1, 3} -> 15, " \ + "{2, 1, 4} -> 16, {2, 2, 1} -> 17, {2, 2, 2} -> 18, " \ + "{2, 2, 3} -> 19, {2, 2, 4} -> 20, {2, 3, 1} -> 21, " \ + "{2, 3, 2} -> 22, {2, 3, 3} -> 23, {2, 3, 4} -> 24" \ + "}, {2, 3, 4}]" + + +def test_Integral(): + assert mcode(Integral(sin(sin(x)), x)) == "Hold[Integrate[Sin[Sin[x]], x]]" + assert mcode(Integral(exp(-x**2 - y**2), + (x, -oo, oo), + (y, -oo, oo))) == \ + "Hold[Integrate[Exp[-x^2 - y^2], {x, -Infinity, Infinity}, " \ + "{y, -Infinity, Infinity}]]" + + +def test_Derivative(): + assert mcode(Derivative(sin(x), x)) == "Hold[D[Sin[x], x]]" + assert mcode(Derivative(x, x)) == "Hold[D[x, x]]" + assert mcode(Derivative(sin(x)*y**4, x, 2)) == "Hold[D[y^4*Sin[x], {x, 2}]]" + assert mcode(Derivative(sin(x)*y**4, x, y, x)) == "Hold[D[y^4*Sin[x], x, y, x]]" + assert mcode(Derivative(sin(x)*y**4, x, y, 3, x)) == "Hold[D[y^4*Sin[x], x, {y, 3}, x]]" + + +def test_Sum(): + assert mcode(Sum(sin(x), (x, 0, 10))) == "Hold[Sum[Sin[x], {x, 0, 10}]]" + assert mcode(Sum(exp(-x**2 - y**2), + (x, -oo, oo), + (y, -oo, oo))) == \ + "Hold[Sum[Exp[-x^2 - y^2], {x, -Infinity, Infinity}, " \ + "{y, -Infinity, Infinity}]]" + + +def test_comment(): + from sympy.printing.mathematica import MCodePrinter + assert MCodePrinter()._get_comment("Hello World") == \ + "(* Hello World *)" + + +def test_userfuncs(): + # Dictionary mutation test + some_function = symbols("some_function", cls=Function) + my_user_functions = {"some_function": "SomeFunction"} + assert mcode( + some_function(z), + user_functions=my_user_functions) == \ + 'SomeFunction[z]' + assert mcode( + some_function(z), + user_functions=my_user_functions) == \ + 'SomeFunction[z]' + + # List argument test + my_user_functions = \ + {"some_function": [(lambda x: True, "SomeOtherFunction")]} + assert mcode( + some_function(z), + user_functions=my_user_functions) == \ + 'SomeOtherFunction[z]' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_mathml.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_mathml.py new file mode 100644 index 0000000000000000000000000000000000000000..4e7c2253c98fb1a4e99375774ad158df9b80b439 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_mathml.py @@ -0,0 +1,2048 @@ +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.function import Derivative, Lambda, diff, Function +from sympy.core.numbers import (zoo, Float, Integer, I, oo, pi, E, + Rational) +from sympy.core.relational import Lt, Ge, Ne, Eq +from sympy.core.singleton import S +from sympy.core.symbol import symbols, Symbol +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import (factorial2, + binomial, factorial) +from sympy.functions.combinatorial.numbers import (lucas, bell, + catalan, euler, tribonacci, fibonacci, bernoulli, primenu, primeomega, + totient, reduced_totient) +from sympy.functions.elementary.complexes import re, im, conjugate, Abs +from sympy.functions.elementary.exponential import exp, LambertW, log +from sympy.functions.elementary.hyperbolic import (tanh, acoth, atanh, + coth, asinh, acsch, asech, acosh, csch, sinh, cosh, sech) +from sympy.functions.elementary.integers import ceiling, floor +from sympy.functions.elementary.miscellaneous import Max, Min +from sympy.functions.elementary.trigonometric import (csc, sec, tan, + atan, sin, asec, cot, cos, acot, acsc, asin, acos) +from sympy.functions.special.delta_functions import Heaviside +from sympy.functions.special.elliptic_integrals import (elliptic_pi, + elliptic_f, elliptic_k, elliptic_e) +from sympy.functions.special.error_functions import (fresnelc, + fresnels, Ei, expint) +from sympy.functions.special.gamma_functions import (gamma, uppergamma, + lowergamma) +from sympy.functions.special.mathieu_functions import (mathieusprime, + mathieus, mathieucprime, mathieuc) +from sympy.functions.special.polynomials import (jacobi, chebyshevu, + chebyshevt, hermite, assoc_legendre, gegenbauer, assoc_laguerre, + legendre, laguerre) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.functions.special.zeta_functions import (polylog, stieltjes, + lerchphi, dirichlet_eta, zeta) +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import (Xor, Or, false, true, And, Equivalent, + Implies, Not) +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.determinant import Determinant +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.physics.quantum import (ComplexSpace, FockSpace, hbar, + HilbertSpace, Dagger) +from sympy.printing.mathml import (MathMLPresentationPrinter, + MathMLPrinter, MathMLContentPrinter, mathml) +from sympy.series.limits import Limit +from sympy.sets.contains import Contains +from sympy.sets.fancysets import Range +from sympy.sets.sets import (Interval, Union, SymmetricDifference, + Complement, FiniteSet, Intersection, ProductSet) +from sympy.stats.rv import RandomSymbol +from sympy.tensor.indexed import IndexedBase +from sympy.vector import (Divergence, CoordSys3D, Cross, Curl, Dot, + Laplacian, Gradient) +from sympy.testing.pytest import raises, XFAIL + +x, y, z, a, b, c, d, e, n = symbols('x:z a:e n') +mp = MathMLContentPrinter() +mpp = MathMLPresentationPrinter() + + +def test_mathml_printer(): + m = MathMLPrinter() + assert m.doprint(1+x) == mp.doprint(1+x) + + +def test_content_printmethod(): + assert mp.doprint(1 + x) == 'x1' + + +def test_content_mathml_core(): + mml_1 = mp._print(1 + x) + assert mml_1.nodeName == 'apply' + nodes = mml_1.childNodes + assert len(nodes) == 3 + assert nodes[0].nodeName == 'plus' + assert nodes[0].hasChildNodes() is False + assert nodes[0].nodeValue is None + assert nodes[1].nodeName in ['cn', 'ci'] + if nodes[1].nodeName == 'cn': + assert nodes[1].childNodes[0].nodeValue == '1' + assert nodes[2].childNodes[0].nodeValue == 'x' + else: + assert nodes[1].childNodes[0].nodeValue == 'x' + assert nodes[2].childNodes[0].nodeValue == '1' + + mml_2 = mp._print(x**2) + assert mml_2.nodeName == 'apply' + nodes = mml_2.childNodes + assert nodes[1].childNodes[0].nodeValue == 'x' + assert nodes[2].childNodes[0].nodeValue == '2' + + mml_3 = mp._print(2*x) + assert mml_3.nodeName == 'apply' + nodes = mml_3.childNodes + assert nodes[0].nodeName == 'times' + assert nodes[1].childNodes[0].nodeValue == '2' + assert nodes[2].childNodes[0].nodeValue == 'x' + + mml = mp._print(Float(1.0, 2)*x) + assert mml.nodeName == 'apply' + nodes = mml.childNodes + assert nodes[0].nodeName == 'times' + assert nodes[1].childNodes[0].nodeValue == '1.0' + assert nodes[2].childNodes[0].nodeValue == 'x' + + +def test_content_mathml_functions(): + mml_1 = mp._print(sin(x)) + assert mml_1.nodeName == 'apply' + assert mml_1.childNodes[0].nodeName == 'sin' + assert mml_1.childNodes[1].nodeName == 'ci' + + mml_2 = mp._print(diff(sin(x), x, evaluate=False)) + assert mml_2.nodeName == 'apply' + assert mml_2.childNodes[0].nodeName == 'diff' + assert mml_2.childNodes[1].nodeName == 'bvar' + assert mml_2.childNodes[1].childNodes[ + 0].nodeName == 'ci' # below bvar there's x/ci> + + mml_3 = mp._print(diff(cos(x*y), x, evaluate=False)) + assert mml_3.nodeName == 'apply' + assert mml_3.childNodes[0].nodeName == 'partialdiff' + assert mml_3.childNodes[1].nodeName == 'bvar' + assert mml_3.childNodes[1].childNodes[ + 0].nodeName == 'ci' # below bvar there's x/ci> + + mml_4 = mp._print(Lambda((x, y), x * y)) + assert mml_4.nodeName == 'lambda' + assert mml_4.childNodes[0].nodeName == 'bvar' + assert mml_4.childNodes[0].childNodes[ + 0].nodeName == 'ci' # below bvar there's x/ci> + assert mml_4.childNodes[1].nodeName == 'bvar' + assert mml_4.childNodes[1].childNodes[ + 0].nodeName == 'ci' # below bvar there's y/ci> + assert mml_4.childNodes[2].nodeName == 'apply' + + +def test_content_mathml_limits(): + # XXX No unevaluated limits + lim_fun = sin(x)/x + mml_1 = mp._print(Limit(lim_fun, x, 0)) + assert mml_1.childNodes[0].nodeName == 'limit' + assert mml_1.childNodes[1].nodeName == 'bvar' + assert mml_1.childNodes[2].nodeName == 'lowlimit' + assert mml_1.childNodes[3].toxml() == mp._print(lim_fun).toxml() + + +def test_content_mathml_integrals(): + integrand = x + mml_1 = mp._print(Integral(integrand, (x, 0, 1))) + assert mml_1.childNodes[0].nodeName == 'int' + assert mml_1.childNodes[1].nodeName == 'bvar' + assert mml_1.childNodes[2].nodeName == 'lowlimit' + assert mml_1.childNodes[3].nodeName == 'uplimit' + assert mml_1.childNodes[4].toxml() == mp._print(integrand).toxml() + + +def test_content_mathml_matrices(): + A = Matrix([1, 2, 3]) + B = Matrix([[0, 5, 4], [2, 3, 1], [9, 7, 9]]) + mll_1 = mp._print(A) + assert mll_1.childNodes[0].nodeName == 'matrixrow' + assert mll_1.childNodes[0].childNodes[0].nodeName == 'cn' + assert mll_1.childNodes[0].childNodes[0].childNodes[0].nodeValue == '1' + assert mll_1.childNodes[1].nodeName == 'matrixrow' + assert mll_1.childNodes[1].childNodes[0].nodeName == 'cn' + assert mll_1.childNodes[1].childNodes[0].childNodes[0].nodeValue == '2' + assert mll_1.childNodes[2].nodeName == 'matrixrow' + assert mll_1.childNodes[2].childNodes[0].nodeName == 'cn' + assert mll_1.childNodes[2].childNodes[0].childNodes[0].nodeValue == '3' + mll_2 = mp._print(B) + assert mll_2.childNodes[0].nodeName == 'matrixrow' + assert mll_2.childNodes[0].childNodes[0].nodeName == 'cn' + assert mll_2.childNodes[0].childNodes[0].childNodes[0].nodeValue == '0' + assert mll_2.childNodes[0].childNodes[1].nodeName == 'cn' + assert mll_2.childNodes[0].childNodes[1].childNodes[0].nodeValue == '5' + assert mll_2.childNodes[0].childNodes[2].nodeName == 'cn' + assert mll_2.childNodes[0].childNodes[2].childNodes[0].nodeValue == '4' + assert mll_2.childNodes[1].nodeName == 'matrixrow' + assert mll_2.childNodes[1].childNodes[0].nodeName == 'cn' + assert mll_2.childNodes[1].childNodes[0].childNodes[0].nodeValue == '2' + assert mll_2.childNodes[1].childNodes[1].nodeName == 'cn' + assert mll_2.childNodes[1].childNodes[1].childNodes[0].nodeValue == '3' + assert mll_2.childNodes[1].childNodes[2].nodeName == 'cn' + assert mll_2.childNodes[1].childNodes[2].childNodes[0].nodeValue == '1' + assert mll_2.childNodes[2].nodeName == 'matrixrow' + assert mll_2.childNodes[2].childNodes[0].nodeName == 'cn' + assert mll_2.childNodes[2].childNodes[0].childNodes[0].nodeValue == '9' + assert mll_2.childNodes[2].childNodes[1].nodeName == 'cn' + assert mll_2.childNodes[2].childNodes[1].childNodes[0].nodeValue == '7' + assert mll_2.childNodes[2].childNodes[2].nodeName == 'cn' + assert mll_2.childNodes[2].childNodes[2].childNodes[0].nodeValue == '9' + + +def test_content_mathml_sums(): + summand = x + mml_1 = mp._print(Sum(summand, (x, 1, 10))) + assert mml_1.childNodes[0].nodeName == 'sum' + assert mml_1.childNodes[1].nodeName == 'bvar' + assert mml_1.childNodes[2].nodeName == 'lowlimit' + assert mml_1.childNodes[3].nodeName == 'uplimit' + assert mml_1.childNodes[4].toxml() == mp._print(summand).toxml() + + +def test_content_mathml_tuples(): + mml_1 = mp._print([2]) + assert mml_1.nodeName == 'list' + assert mml_1.childNodes[0].nodeName == 'cn' + assert len(mml_1.childNodes) == 1 + + mml_2 = mp._print([2, Integer(1)]) + assert mml_2.nodeName == 'list' + assert mml_2.childNodes[0].nodeName == 'cn' + assert mml_2.childNodes[1].nodeName == 'cn' + assert len(mml_2.childNodes) == 2 + + +def test_content_mathml_add(): + mml = mp._print(x**5 - x**4 + x) + assert mml.childNodes[0].nodeName == 'plus' + assert mml.childNodes[1].childNodes[0].nodeName == 'minus' + assert mml.childNodes[1].childNodes[1].nodeName == 'apply' + + +def test_content_mathml_Rational(): + mml_1 = mp._print(Rational(1, 1)) + """should just return a number""" + assert mml_1.nodeName == 'cn' + + mml_2 = mp._print(Rational(2, 5)) + assert mml_2.childNodes[0].nodeName == 'divide' + + +def test_content_mathml_constants(): + mml = mp._print(I) + assert mml.nodeName == 'imaginaryi' + + mml = mp._print(E) + assert mml.nodeName == 'exponentiale' + + mml = mp._print(oo) + assert mml.nodeName == 'infinity' + + mml = mp._print(pi) + assert mml.nodeName == 'pi' + + assert mathml(hbar) == '' + assert mathml(S.TribonacciConstant) == '' + assert mathml(S.GoldenRatio) == 'φ' + mml = mathml(S.EulerGamma) + assert mml == '' + + mml = mathml(S.EmptySet) + assert mml == '' + + mml = mathml(S.true) + assert mml == '' + + mml = mathml(S.false) + assert mml == '' + + mml = mathml(S.NaN) + assert mml == '' + + +def test_content_mathml_trig(): + mml = mp._print(sin(x)) + assert mml.childNodes[0].nodeName == 'sin' + + mml = mp._print(cos(x)) + assert mml.childNodes[0].nodeName == 'cos' + + mml = mp._print(tan(x)) + assert mml.childNodes[0].nodeName == 'tan' + + mml = mp._print(cot(x)) + assert mml.childNodes[0].nodeName == 'cot' + + mml = mp._print(csc(x)) + assert mml.childNodes[0].nodeName == 'csc' + + mml = mp._print(sec(x)) + assert mml.childNodes[0].nodeName == 'sec' + + mml = mp._print(asin(x)) + assert mml.childNodes[0].nodeName == 'arcsin' + + mml = mp._print(acos(x)) + assert mml.childNodes[0].nodeName == 'arccos' + + mml = mp._print(atan(x)) + assert mml.childNodes[0].nodeName == 'arctan' + + mml = mp._print(acot(x)) + assert mml.childNodes[0].nodeName == 'arccot' + + mml = mp._print(acsc(x)) + assert mml.childNodes[0].nodeName == 'arccsc' + + mml = mp._print(asec(x)) + assert mml.childNodes[0].nodeName == 'arcsec' + + mml = mp._print(sinh(x)) + assert mml.childNodes[0].nodeName == 'sinh' + + mml = mp._print(cosh(x)) + assert mml.childNodes[0].nodeName == 'cosh' + + mml = mp._print(tanh(x)) + assert mml.childNodes[0].nodeName == 'tanh' + + mml = mp._print(coth(x)) + assert mml.childNodes[0].nodeName == 'coth' + + mml = mp._print(csch(x)) + assert mml.childNodes[0].nodeName == 'csch' + + mml = mp._print(sech(x)) + assert mml.childNodes[0].nodeName == 'sech' + + mml = mp._print(asinh(x)) + assert mml.childNodes[0].nodeName == 'arcsinh' + + mml = mp._print(atanh(x)) + assert mml.childNodes[0].nodeName == 'arctanh' + + mml = mp._print(acosh(x)) + assert mml.childNodes[0].nodeName == 'arccosh' + + mml = mp._print(acoth(x)) + assert mml.childNodes[0].nodeName == 'arccoth' + + mml = mp._print(acsch(x)) + assert mml.childNodes[0].nodeName == 'arccsch' + + mml = mp._print(asech(x)) + assert mml.childNodes[0].nodeName == 'arcsech' + + +def test_content_mathml_relational(): + mml_1 = mp._print(Eq(x, 1)) + assert mml_1.nodeName == 'apply' + assert mml_1.childNodes[0].nodeName == 'eq' + assert mml_1.childNodes[1].nodeName == 'ci' + assert mml_1.childNodes[1].childNodes[0].nodeValue == 'x' + assert mml_1.childNodes[2].nodeName == 'cn' + assert mml_1.childNodes[2].childNodes[0].nodeValue == '1' + + mml_2 = mp._print(Ne(1, x)) + assert mml_2.nodeName == 'apply' + assert mml_2.childNodes[0].nodeName == 'neq' + assert mml_2.childNodes[1].nodeName == 'cn' + assert mml_2.childNodes[1].childNodes[0].nodeValue == '1' + assert mml_2.childNodes[2].nodeName == 'ci' + assert mml_2.childNodes[2].childNodes[0].nodeValue == 'x' + + mml_3 = mp._print(Ge(1, x)) + assert mml_3.nodeName == 'apply' + assert mml_3.childNodes[0].nodeName == 'geq' + assert mml_3.childNodes[1].nodeName == 'cn' + assert mml_3.childNodes[1].childNodes[0].nodeValue == '1' + assert mml_3.childNodes[2].nodeName == 'ci' + assert mml_3.childNodes[2].childNodes[0].nodeValue == 'x' + + mml_4 = mp._print(Lt(1, x)) + assert mml_4.nodeName == 'apply' + assert mml_4.childNodes[0].nodeName == 'lt' + assert mml_4.childNodes[1].nodeName == 'cn' + assert mml_4.childNodes[1].childNodes[0].nodeValue == '1' + assert mml_4.childNodes[2].nodeName == 'ci' + assert mml_4.childNodes[2].childNodes[0].nodeValue == 'x' + + +def test_content_symbol(): + mml = mp._print(x) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeValue == 'x' + del mml + + mml = mp._print(Symbol("x^2")) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeName == 'mml:msup' + assert mml.childNodes[0].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeValue == '2' + del mml + + mml = mp._print(Symbol("x__2")) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeName == 'mml:msup' + assert mml.childNodes[0].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeValue == '2' + del mml + + mml = mp._print(Symbol("x_2")) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeName == 'mml:msub' + assert mml.childNodes[0].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeValue == '2' + del mml + + mml = mp._print(Symbol("x^3_2")) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeName == 'mml:msubsup' + assert mml.childNodes[0].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeValue == '2' + assert mml.childNodes[0].childNodes[2].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[2].childNodes[0].nodeValue == '3' + del mml + + mml = mp._print(Symbol("x__3_2")) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeName == 'mml:msubsup' + assert mml.childNodes[0].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeValue == '2' + assert mml.childNodes[0].childNodes[2].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[2].childNodes[0].nodeValue == '3' + del mml + + mml = mp._print(Symbol("x_2_a")) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeName == 'mml:msub' + assert mml.childNodes[0].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].nodeName == 'mml:mrow' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[0].childNodes[ + 0].nodeValue == '2' + assert mml.childNodes[0].childNodes[1].childNodes[1].nodeName == 'mml:mo' + assert mml.childNodes[0].childNodes[1].childNodes[1].childNodes[ + 0].nodeValue == ' ' + assert mml.childNodes[0].childNodes[1].childNodes[2].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[2].childNodes[ + 0].nodeValue == 'a' + del mml + + mml = mp._print(Symbol("x^2^a")) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeName == 'mml:msup' + assert mml.childNodes[0].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].nodeName == 'mml:mrow' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[0].childNodes[ + 0].nodeValue == '2' + assert mml.childNodes[0].childNodes[1].childNodes[1].nodeName == 'mml:mo' + assert mml.childNodes[0].childNodes[1].childNodes[1].childNodes[ + 0].nodeValue == ' ' + assert mml.childNodes[0].childNodes[1].childNodes[2].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[2].childNodes[ + 0].nodeValue == 'a' + del mml + + mml = mp._print(Symbol("x__2__a")) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeName == 'mml:msup' + assert mml.childNodes[0].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].nodeName == 'mml:mrow' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[0].childNodes[ + 0].nodeValue == '2' + assert mml.childNodes[0].childNodes[1].childNodes[1].nodeName == 'mml:mo' + assert mml.childNodes[0].childNodes[1].childNodes[1].childNodes[ + 0].nodeValue == ' ' + assert mml.childNodes[0].childNodes[1].childNodes[2].nodeName == 'mml:mi' + assert mml.childNodes[0].childNodes[1].childNodes[2].childNodes[ + 0].nodeValue == 'a' + del mml + + +def test_content_mathml_greek(): + mml = mp._print(Symbol('alpha')) + assert mml.nodeName == 'ci' + assert mml.childNodes[0].nodeValue == '\N{GREEK SMALL LETTER ALPHA}' + + assert mp.doprint(Symbol('alpha')) == 'α' + assert mp.doprint(Symbol('beta')) == 'β' + assert mp.doprint(Symbol('gamma')) == 'γ' + assert mp.doprint(Symbol('delta')) == 'δ' + assert mp.doprint(Symbol('epsilon')) == 'ε' + assert mp.doprint(Symbol('zeta')) == 'ζ' + assert mp.doprint(Symbol('eta')) == 'η' + assert mp.doprint(Symbol('theta')) == 'θ' + assert mp.doprint(Symbol('iota')) == 'ι' + assert mp.doprint(Symbol('kappa')) == 'κ' + assert mp.doprint(Symbol('lambda')) == 'λ' + assert mp.doprint(Symbol('mu')) == 'μ' + assert mp.doprint(Symbol('nu')) == 'ν' + assert mp.doprint(Symbol('xi')) == 'ξ' + assert mp.doprint(Symbol('omicron')) == 'ο' + assert mp.doprint(Symbol('pi')) == 'π' + assert mp.doprint(Symbol('rho')) == 'ρ' + assert mp.doprint(Symbol('varsigma')) == 'ς' + assert mp.doprint(Symbol('sigma')) == 'σ' + assert mp.doprint(Symbol('tau')) == 'τ' + assert mp.doprint(Symbol('upsilon')) == 'υ' + assert mp.doprint(Symbol('phi')) == 'φ' + assert mp.doprint(Symbol('chi')) == 'χ' + assert mp.doprint(Symbol('psi')) == 'ψ' + assert mp.doprint(Symbol('omega')) == 'ω' + + assert mp.doprint(Symbol('Alpha')) == 'Α' + assert mp.doprint(Symbol('Beta')) == 'Β' + assert mp.doprint(Symbol('Gamma')) == 'Γ' + assert mp.doprint(Symbol('Delta')) == 'Δ' + assert mp.doprint(Symbol('Epsilon')) == 'Ε' + assert mp.doprint(Symbol('Zeta')) == 'Ζ' + assert mp.doprint(Symbol('Eta')) == 'Η' + assert mp.doprint(Symbol('Theta')) == 'Θ' + assert mp.doprint(Symbol('Iota')) == 'Ι' + assert mp.doprint(Symbol('Kappa')) == 'Κ' + assert mp.doprint(Symbol('Lambda')) == 'Λ' + assert mp.doprint(Symbol('Mu')) == 'Μ' + assert mp.doprint(Symbol('Nu')) == 'Ν' + assert mp.doprint(Symbol('Xi')) == 'Ξ' + assert mp.doprint(Symbol('Omicron')) == 'Ο' + assert mp.doprint(Symbol('Pi')) == 'Π' + assert mp.doprint(Symbol('Rho')) == 'Ρ' + assert mp.doprint(Symbol('Sigma')) == 'Σ' + assert mp.doprint(Symbol('Tau')) == 'Τ' + assert mp.doprint(Symbol('Upsilon')) == 'Υ' + assert mp.doprint(Symbol('Phi')) == 'Φ' + assert mp.doprint(Symbol('Chi')) == 'Χ' + assert mp.doprint(Symbol('Psi')) == 'Ψ' + assert mp.doprint(Symbol('Omega')) == 'Ω' + + +def test_content_mathml_order(): + expr = x**3 + x**2*y + 3*x*y**3 + y**4 + + mp = MathMLContentPrinter({'order': 'lex'}) + mml = mp._print(expr) + + assert mml.childNodes[1].childNodes[0].nodeName == 'power' + assert mml.childNodes[1].childNodes[1].childNodes[0].data == 'x' + assert mml.childNodes[1].childNodes[2].childNodes[0].data == '3' + + assert mml.childNodes[4].childNodes[0].nodeName == 'power' + assert mml.childNodes[4].childNodes[1].childNodes[0].data == 'y' + assert mml.childNodes[4].childNodes[2].childNodes[0].data == '4' + + mp = MathMLContentPrinter({'order': 'rev-lex'}) + mml = mp._print(expr) + + assert mml.childNodes[1].childNodes[0].nodeName == 'power' + assert mml.childNodes[1].childNodes[1].childNodes[0].data == 'y' + assert mml.childNodes[1].childNodes[2].childNodes[0].data == '4' + + assert mml.childNodes[4].childNodes[0].nodeName == 'power' + assert mml.childNodes[4].childNodes[1].childNodes[0].data == 'x' + assert mml.childNodes[4].childNodes[2].childNodes[0].data == '3' + + +def test_content_settings(): + raises(TypeError, lambda: mathml(x, method="garbage")) + + +def test_content_mathml_logic(): + assert mathml(And(x, y)) == 'xy' + assert mathml(Or(x, y)) == 'xy' + assert mathml(Xor(x, y)) == 'xy' + assert mathml(Implies(x, y)) == 'xy' + assert mathml(Not(x)) == 'x' + + +def test_content_finite_sets(): + assert mathml(FiniteSet(a)) == 'a' + assert mathml(FiniteSet(a, b)) == 'ab' + assert mathml(FiniteSet(FiniteSet(a, b), c)) == \ + 'cab' + + A = FiniteSet(a) + B = FiniteSet(b) + C = FiniteSet(c) + D = FiniteSet(d) + + U1 = Union(A, B, evaluate=False) + U2 = Union(C, D, evaluate=False) + I1 = Intersection(A, B, evaluate=False) + I2 = Intersection(C, D, evaluate=False) + C1 = Complement(A, B, evaluate=False) + C2 = Complement(C, D, evaluate=False) + # XXX ProductSet does not support evaluate keyword + P1 = ProductSet(A, B) + P2 = ProductSet(C, D) + + assert mathml(U1) == \ + 'ab' + assert mathml(I1) == \ + 'ab' \ + '' + assert mathml(C1) == \ + 'ab' + assert mathml(P1) == \ + 'ab' \ + '' + + assert mathml(Intersection(A, U2, evaluate=False)) == \ + 'a' \ + 'cd' + assert mathml(Intersection(U1, U2, evaluate=False)) == \ + 'a' \ + 'bc' \ + 'd' + + # XXX Does the parenthesis appear correctly for these examples in mathjax? + assert mathml(Intersection(C1, C2, evaluate=False)) == \ + 'a' \ + 'bc' \ + 'd' + assert mathml(Intersection(P1, P2, evaluate=False)) == \ + 'a' \ + 'b' \ + 'cd' + + assert mathml(Union(A, I2, evaluate=False)) == \ + 'a' \ + 'cd' + assert mathml(Union(I1, I2, evaluate=False)) == \ + 'a' \ + 'bc' \ + 'd' + assert mathml(Union(C1, C2, evaluate=False)) == \ + 'a' \ + 'bc' \ + 'd' + assert mathml(Union(P1, P2, evaluate=False)) == \ + 'a' \ + 'b' \ + 'cd' + + assert mathml(Complement(A, C2, evaluate=False)) == \ + 'a' \ + 'cd' + assert mathml(Complement(U1, U2, evaluate=False)) == \ + 'a' \ + 'bc' \ + 'd' + assert mathml(Complement(I1, I2, evaluate=False)) == \ + 'a' \ + 'bc' \ + 'd' + assert mathml(Complement(P1, P2, evaluate=False)) == \ + 'a' \ + 'b' \ + 'cd' + + assert mathml(ProductSet(A, P2)) == \ + 'a' \ + 'c' \ + 'd' + assert mathml(ProductSet(U1, U2)) == \ + 'a' \ + 'bc' \ + 'd' + assert mathml(ProductSet(I1, I2)) == \ + 'a' \ + 'b' \ + 'cd' + assert mathml(ProductSet(C1, C2)) == \ + 'a' \ + 'b' \ + 'cd' + + +def test_presentation_printmethod(): + assert mpp.doprint(1 + x) == 'x+1' + assert mpp.doprint(x**2) == 'x2' + assert mpp.doprint(x**-1) == '1x' + assert mpp.doprint(x**-2) == \ + '1x2' + assert mpp.doprint(2*x) == \ + '2⁢x' + + +def test_presentation_mathml_core(): + mml_1 = mpp._print(1 + x) + assert mml_1.nodeName == 'mrow' + nodes = mml_1.childNodes + assert len(nodes) == 3 + assert nodes[0].nodeName in ['mi', 'mn'] + assert nodes[1].nodeName == 'mo' + if nodes[0].nodeName == 'mn': + assert nodes[0].childNodes[0].nodeValue == '1' + assert nodes[2].childNodes[0].nodeValue == 'x' + else: + assert nodes[0].childNodes[0].nodeValue == 'x' + assert nodes[2].childNodes[0].nodeValue == '1' + + mml_2 = mpp._print(x**2) + assert mml_2.nodeName == 'msup' + nodes = mml_2.childNodes + assert nodes[0].childNodes[0].nodeValue == 'x' + assert nodes[1].childNodes[0].nodeValue == '2' + + mml_3 = mpp._print(2*x) + assert mml_3.nodeName == 'mrow' + nodes = mml_3.childNodes + assert nodes[0].childNodes[0].nodeValue == '2' + assert nodes[1].childNodes[0].nodeValue == '⁢' + assert nodes[2].childNodes[0].nodeValue == 'x' + + mml = mpp._print(Float(1.0, 2)*x) + assert mml.nodeName == 'mrow' + nodes = mml.childNodes + assert nodes[0].childNodes[0].nodeValue == '1.0' + assert nodes[1].childNodes[0].nodeValue == '⁢' + assert nodes[2].childNodes[0].nodeValue == 'x' + + +def test_presentation_mathml_functions(): + mml_1 = mpp._print(sin(x)) + assert mml_1.childNodes[0].childNodes[0 + ].nodeValue == 'sin' + assert mml_1.childNodes[1].childNodes[1 + ].childNodes[0].nodeValue == 'x' + + mml_2 = mpp._print(diff(sin(x), x, evaluate=False)) + assert mml_2.nodeName == 'mrow' + assert mml_2.childNodes[0].childNodes[0 + ].childNodes[0].childNodes[0].nodeValue == 'ⅆ' + assert mml_2.childNodes[1].childNodes[1 + ].nodeName == 'mrow' + assert mml_2.childNodes[0].childNodes[1 + ].childNodes[0].childNodes[0].nodeValue == 'ⅆ' + + mml_3 = mpp._print(diff(cos(x*y), x, evaluate=False)) + assert mml_3.childNodes[0].nodeName == 'mfrac' + assert mml_3.childNodes[0].childNodes[0 + ].childNodes[0].childNodes[0].nodeValue == '∂' + assert mml_3.childNodes[1].childNodes[0 + ].childNodes[0].nodeValue == 'cos' + + +def test_print_derivative(): + f = Function('f') + d = Derivative(f(x, y, z), x, z, x, z, z, y) + assert mathml(d) == \ + 'yz2xzxxyz' + assert mathml(d, printer='presentation') == \ + '∂6∂y∂2z∂x∂z∂xf(x,y,z)' + + +def test_presentation_mathml_limits(): + lim_fun = sin(x)/x + mml_1 = mpp._print(Limit(lim_fun, x, 0)) + assert mml_1.childNodes[0].nodeName == 'munder' + assert mml_1.childNodes[0].childNodes[0 + ].childNodes[0].nodeValue == 'lim' + assert mml_1.childNodes[0].childNodes[1 + ].childNodes[0].childNodes[0 + ].nodeValue == 'x' + assert mml_1.childNodes[0].childNodes[1 + ].childNodes[1].childNodes[0 + ].nodeValue == '→' + assert mml_1.childNodes[0].childNodes[1 + ].childNodes[2].childNodes[0 + ].nodeValue == '0' + + +def test_presentation_mathml_integrals(): + assert mpp.doprint(Integral(x, (x, 0, 1))) == \ + '∫01'\ + 'xⅆx' + assert mpp.doprint(Integral(log(x), x)) == \ + '∫log(x' \ + ')ⅆx' + assert mpp.doprint(Integral(x*y, x, y)) == \ + '∬x⁢'\ + 'yⅆyⅆx' + z, w = symbols('z w') + assert mpp.doprint(Integral(x*y*z, x, y, z)) == \ + '∭x⁢'\ + 'y⁢zⅆ'\ + 'zⅆyⅆx' + assert mpp.doprint(Integral(x*y*z*w, x, y, z, w)) == \ + '∫∫∫'\ + '∫w⁢'\ + 'x⁢y'\ + '⁢zⅆw'\ + 'ⅆzⅆyⅆx' + assert mpp.doprint(Integral(x, x, y, (z, 0, 1))) == \ + '∫01'\ + '∫∫xⅆz'\ + 'ⅆyⅆx' + assert mpp.doprint(Integral(x, (x, 0))) == \ + '∫0xⅆ'\ + 'x' + + +def test_presentation_mathml_matrices(): + A = Matrix([1, 2, 3]) + B = Matrix([[0, 5, 4], [2, 3, 1], [9, 7, 9]]) + mll_1 = mpp._print(A) + assert mll_1.childNodes[1].nodeName == 'mtable' + assert mll_1.childNodes[1].childNodes[0].nodeName == 'mtr' + assert len(mll_1.childNodes[1].childNodes) == 3 + assert mll_1.childNodes[1].childNodes[0].childNodes[0].nodeName == 'mtd' + assert len(mll_1.childNodes[1].childNodes[0].childNodes) == 1 + assert mll_1.childNodes[1].childNodes[0].childNodes[0 + ].childNodes[0].childNodes[0].nodeValue == '1' + assert mll_1.childNodes[1].childNodes[1].childNodes[0 + ].childNodes[0].childNodes[0].nodeValue == '2' + assert mll_1.childNodes[1].childNodes[2].childNodes[0 + ].childNodes[0].childNodes[0].nodeValue == '3' + mll_2 = mpp._print(B) + assert mll_2.childNodes[1].nodeName == 'mtable' + assert mll_2.childNodes[1].childNodes[0].nodeName == 'mtr' + assert len(mll_2.childNodes[1].childNodes) == 3 + assert mll_2.childNodes[1].childNodes[0].childNodes[0].nodeName == 'mtd' + assert len(mll_2.childNodes[1].childNodes[0].childNodes) == 3 + assert mll_2.childNodes[1].childNodes[0].childNodes[0 + ].childNodes[0].childNodes[0].nodeValue == '0' + assert mll_2.childNodes[1].childNodes[0].childNodes[1 + ].childNodes[0].childNodes[0].nodeValue == '5' + assert mll_2.childNodes[1].childNodes[0].childNodes[2 + ].childNodes[0].childNodes[0].nodeValue == '4' + assert mll_2.childNodes[1].childNodes[1].childNodes[0 + ].childNodes[0].childNodes[0].nodeValue == '2' + assert mll_2.childNodes[1].childNodes[1].childNodes[1 + ].childNodes[0].childNodes[0].nodeValue == '3' + assert mll_2.childNodes[1].childNodes[1].childNodes[2 + ].childNodes[0].childNodes[0].nodeValue == '1' + assert mll_2.childNodes[1].childNodes[2].childNodes[0 + ].childNodes[0].childNodes[0].nodeValue == '9' + assert mll_2.childNodes[1].childNodes[2].childNodes[1 + ].childNodes[0].childNodes[0].nodeValue == '7' + assert mll_2.childNodes[1].childNodes[2].childNodes[2 + ].childNodes[0].childNodes[0].nodeValue == '9' + + +def test_presentation_mathml_sums(): + mml_1 = mpp._print(Sum(x, (x, 1, 10))) + assert mml_1.childNodes[0].nodeName == 'munderover' + assert len(mml_1.childNodes[0].childNodes) == 3 + assert mml_1.childNodes[0].childNodes[0].childNodes[0 + ].nodeValue == '∑' + assert len(mml_1.childNodes[0].childNodes[1].childNodes) == 3 + assert mml_1.childNodes[0].childNodes[2].childNodes[0 + ].nodeValue == '10' + assert mml_1.childNodes[1].childNodes[0].nodeValue == 'x' + + assert mpp.doprint(Sum(x, (x, 1, 10))) == \ + '∑x=110x' + assert mpp.doprint(Sum(x + y, (x, 1, 10))) == \ + '∑x=110(x+y)' + + +def test_presentation_mathml_add(): + mml = mpp._print(x**5 - x**4 + x) + assert len(mml.childNodes) == 5 + assert mml.childNodes[0].childNodes[0].childNodes[0 + ].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].childNodes[0 + ].nodeValue == '5' + assert mml.childNodes[1].childNodes[0].nodeValue == '-' + assert mml.childNodes[2].childNodes[0].childNodes[0 + ].nodeValue == 'x' + assert mml.childNodes[2].childNodes[1].childNodes[0 + ].nodeValue == '4' + assert mml.childNodes[3].childNodes[0].nodeValue == '+' + assert mml.childNodes[4].childNodes[0].nodeValue == 'x' + + +def test_presentation_mathml_Rational(): + mml_1 = mpp._print(Rational(1, 1)) + assert mml_1.nodeName == 'mn' + + mml_2 = mpp._print(Rational(2, 5)) + assert mml_2.nodeName == 'mfrac' + assert mml_2.childNodes[0].childNodes[0].nodeValue == '2' + assert mml_2.childNodes[1].childNodes[0].nodeValue == '5' + + +def test_presentation_mathml_constants(): + mml = mpp._print(I) + assert mml.childNodes[0].nodeValue == 'ⅈ' + + mml = mpp._print(E) + assert mml.childNodes[0].nodeValue == 'ⅇ' + + mml = mpp._print(oo) + assert mml.childNodes[0].nodeValue == '∞' + + mml = mpp._print(pi) + assert mml.childNodes[0].nodeValue == 'π' + + assert mathml(hbar, printer='presentation') == 'ℏ' + assert mathml(S.TribonacciConstant, printer='presentation' + ) == 'TribonacciConstant' + assert mathml(S.EulerGamma, printer='presentation' + ) == 'γ' + assert mathml(S.GoldenRatio, printer='presentation' + ) == 'Φ' + + assert mathml(zoo, printer='presentation') == \ + '∞~' + + assert mathml(S.NaN, printer='presentation') == 'NaN' + +def test_presentation_mathml_trig(): + mml = mpp._print(sin(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'sin' + + mml = mpp._print(cos(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'cos' + + mml = mpp._print(tan(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'tan' + + mml = mpp._print(asin(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'arcsin' + + mml = mpp._print(acos(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'arccos' + + mml = mpp._print(atan(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'arctan' + + mml = mpp._print(sinh(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'sinh' + + mml = mpp._print(cosh(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'cosh' + + mml = mpp._print(tanh(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'tanh' + + mml = mpp._print(asinh(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'arcsinh' + + mml = mpp._print(atanh(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'arctanh' + + mml = mpp._print(acosh(x)) + assert mml.childNodes[0].childNodes[0].nodeValue == 'arccosh' + + +def test_presentation_mathml_relational(): + mml_1 = mpp._print(Eq(x, 1)) + assert len(mml_1.childNodes) == 3 + assert mml_1.childNodes[0].nodeName == 'mi' + assert mml_1.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml_1.childNodes[1].nodeName == 'mo' + assert mml_1.childNodes[1].childNodes[0].nodeValue == '=' + assert mml_1.childNodes[2].nodeName == 'mn' + assert mml_1.childNodes[2].childNodes[0].nodeValue == '1' + + mml_2 = mpp._print(Ne(1, x)) + assert len(mml_2.childNodes) == 3 + assert mml_2.childNodes[0].nodeName == 'mn' + assert mml_2.childNodes[0].childNodes[0].nodeValue == '1' + assert mml_2.childNodes[1].nodeName == 'mo' + assert mml_2.childNodes[1].childNodes[0].nodeValue == '≠' + assert mml_2.childNodes[2].nodeName == 'mi' + assert mml_2.childNodes[2].childNodes[0].nodeValue == 'x' + + mml_3 = mpp._print(Ge(1, x)) + assert len(mml_3.childNodes) == 3 + assert mml_3.childNodes[0].nodeName == 'mn' + assert mml_3.childNodes[0].childNodes[0].nodeValue == '1' + assert mml_3.childNodes[1].nodeName == 'mo' + assert mml_3.childNodes[1].childNodes[0].nodeValue == '≥' + assert mml_3.childNodes[2].nodeName == 'mi' + assert mml_3.childNodes[2].childNodes[0].nodeValue == 'x' + + mml_4 = mpp._print(Lt(1, x)) + assert len(mml_4.childNodes) == 3 + assert mml_4.childNodes[0].nodeName == 'mn' + assert mml_4.childNodes[0].childNodes[0].nodeValue == '1' + assert mml_4.childNodes[1].nodeName == 'mo' + assert mml_4.childNodes[1].childNodes[0].nodeValue == '<' + assert mml_4.childNodes[2].nodeName == 'mi' + assert mml_4.childNodes[2].childNodes[0].nodeValue == 'x' + + +def test_presentation_symbol(): + mml = mpp._print(x) + assert mml.nodeName == 'mi' + assert mml.childNodes[0].nodeValue == 'x' + del mml + + mml = mpp._print(Symbol("x^2")) + assert mml.nodeName == 'msup' + assert mml.childNodes[0].nodeName == 'mi' + assert mml.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[1].nodeName == 'mi' + assert mml.childNodes[1].childNodes[0].nodeValue == '2' + del mml + + mml = mpp._print(Symbol("x__2")) + assert mml.nodeName == 'msup' + assert mml.childNodes[0].nodeName == 'mi' + assert mml.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[1].nodeName == 'mi' + assert mml.childNodes[1].childNodes[0].nodeValue == '2' + del mml + + mml = mpp._print(Symbol("x_2")) + assert mml.nodeName == 'msub' + assert mml.childNodes[0].nodeName == 'mi' + assert mml.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[1].nodeName == 'mi' + assert mml.childNodes[1].childNodes[0].nodeValue == '2' + del mml + + mml = mpp._print(Symbol("x^3_2")) + assert mml.nodeName == 'msubsup' + assert mml.childNodes[0].nodeName == 'mi' + assert mml.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[1].nodeName == 'mi' + assert mml.childNodes[1].childNodes[0].nodeValue == '2' + assert mml.childNodes[2].nodeName == 'mi' + assert mml.childNodes[2].childNodes[0].nodeValue == '3' + del mml + + mml = mpp._print(Symbol("x__3_2")) + assert mml.nodeName == 'msubsup' + assert mml.childNodes[0].nodeName == 'mi' + assert mml.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[1].nodeName == 'mi' + assert mml.childNodes[1].childNodes[0].nodeValue == '2' + assert mml.childNodes[2].nodeName == 'mi' + assert mml.childNodes[2].childNodes[0].nodeValue == '3' + del mml + + mml = mpp._print(Symbol("x_2_a")) + assert mml.nodeName == 'msub' + assert mml.childNodes[0].nodeName == 'mi' + assert mml.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[1].nodeName == 'mrow' + assert mml.childNodes[1].childNodes[0].nodeName == 'mi' + assert mml.childNodes[1].childNodes[0].childNodes[0].nodeValue == '2' + assert mml.childNodes[1].childNodes[1].nodeName == 'mo' + assert mml.childNodes[1].childNodes[1].childNodes[0].nodeValue == ' ' + assert mml.childNodes[1].childNodes[2].nodeName == 'mi' + assert mml.childNodes[1].childNodes[2].childNodes[0].nodeValue == 'a' + del mml + + mml = mpp._print(Symbol("x^2^a")) + assert mml.nodeName == 'msup' + assert mml.childNodes[0].nodeName == 'mi' + assert mml.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[1].nodeName == 'mrow' + assert mml.childNodes[1].childNodes[0].nodeName == 'mi' + assert mml.childNodes[1].childNodes[0].childNodes[0].nodeValue == '2' + assert mml.childNodes[1].childNodes[1].nodeName == 'mo' + assert mml.childNodes[1].childNodes[1].childNodes[0].nodeValue == ' ' + assert mml.childNodes[1].childNodes[2].nodeName == 'mi' + assert mml.childNodes[1].childNodes[2].childNodes[0].nodeValue == 'a' + del mml + + mml = mpp._print(Symbol("x__2__a")) + assert mml.nodeName == 'msup' + assert mml.childNodes[0].nodeName == 'mi' + assert mml.childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[1].nodeName == 'mrow' + assert mml.childNodes[1].childNodes[0].nodeName == 'mi' + assert mml.childNodes[1].childNodes[0].childNodes[0].nodeValue == '2' + assert mml.childNodes[1].childNodes[1].nodeName == 'mo' + assert mml.childNodes[1].childNodes[1].childNodes[0].nodeValue == ' ' + assert mml.childNodes[1].childNodes[2].nodeName == 'mi' + assert mml.childNodes[1].childNodes[2].childNodes[0].nodeValue == 'a' + del mml + + +def test_presentation_mathml_greek(): + mml = mpp._print(Symbol('alpha')) + assert mml.nodeName == 'mi' + assert mml.childNodes[0].nodeValue == '\N{GREEK SMALL LETTER ALPHA}' + + assert mpp.doprint(Symbol('alpha')) == 'α' + assert mpp.doprint(Symbol('beta')) == 'β' + assert mpp.doprint(Symbol('gamma')) == 'γ' + assert mpp.doprint(Symbol('delta')) == 'δ' + assert mpp.doprint(Symbol('epsilon')) == 'ε' + assert mpp.doprint(Symbol('zeta')) == 'ζ' + assert mpp.doprint(Symbol('eta')) == 'η' + assert mpp.doprint(Symbol('theta')) == 'θ' + assert mpp.doprint(Symbol('iota')) == 'ι' + assert mpp.doprint(Symbol('kappa')) == 'κ' + assert mpp.doprint(Symbol('lambda')) == 'λ' + assert mpp.doprint(Symbol('mu')) == 'μ' + assert mpp.doprint(Symbol('nu')) == 'ν' + assert mpp.doprint(Symbol('xi')) == 'ξ' + assert mpp.doprint(Symbol('omicron')) == 'ο' + assert mpp.doprint(Symbol('pi')) == 'π' + assert mpp.doprint(Symbol('rho')) == 'ρ' + assert mpp.doprint(Symbol('varsigma')) == 'ς' + assert mpp.doprint(Symbol('sigma')) == 'σ' + assert mpp.doprint(Symbol('tau')) == 'τ' + assert mpp.doprint(Symbol('upsilon')) == 'υ' + assert mpp.doprint(Symbol('phi')) == 'φ' + assert mpp.doprint(Symbol('chi')) == 'χ' + assert mpp.doprint(Symbol('psi')) == 'ψ' + assert mpp.doprint(Symbol('omega')) == 'ω' + + assert mpp.doprint(Symbol('Alpha')) == 'Α' + assert mpp.doprint(Symbol('Beta')) == 'Β' + assert mpp.doprint(Symbol('Gamma')) == 'Γ' + assert mpp.doprint(Symbol('Delta')) == 'Δ' + assert mpp.doprint(Symbol('Epsilon')) == 'Ε' + assert mpp.doprint(Symbol('Zeta')) == 'Ζ' + assert mpp.doprint(Symbol('Eta')) == 'Η' + assert mpp.doprint(Symbol('Theta')) == 'Θ' + assert mpp.doprint(Symbol('Iota')) == 'Ι' + assert mpp.doprint(Symbol('Kappa')) == 'Κ' + assert mpp.doprint(Symbol('Lambda')) == 'Λ' + assert mpp.doprint(Symbol('Mu')) == 'Μ' + assert mpp.doprint(Symbol('Nu')) == 'Ν' + assert mpp.doprint(Symbol('Xi')) == 'Ξ' + assert mpp.doprint(Symbol('Omicron')) == 'Ο' + assert mpp.doprint(Symbol('Pi')) == 'Π' + assert mpp.doprint(Symbol('Rho')) == 'Ρ' + assert mpp.doprint(Symbol('Sigma')) == 'Σ' + assert mpp.doprint(Symbol('Tau')) == 'Τ' + assert mpp.doprint(Symbol('Upsilon')) == 'Υ' + assert mpp.doprint(Symbol('Phi')) == 'Φ' + assert mpp.doprint(Symbol('Chi')) == 'Χ' + assert mpp.doprint(Symbol('Psi')) == 'Ψ' + assert mpp.doprint(Symbol('Omega')) == 'Ω' + + +def test_presentation_mathml_order(): + expr = x**3 + x**2*y + 3*x*y**3 + y**4 + + mp = MathMLPresentationPrinter({'order': 'lex'}) + mml = mp._print(expr) + assert mml.childNodes[0].nodeName == 'msup' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeValue == '3' + + assert mml.childNodes[6].nodeName == 'msup' + assert mml.childNodes[6].childNodes[0].childNodes[0].nodeValue == 'y' + assert mml.childNodes[6].childNodes[1].childNodes[0].nodeValue == '4' + + mp = MathMLPresentationPrinter({'order': 'rev-lex'}) + mml = mp._print(expr) + + assert mml.childNodes[0].nodeName == 'msup' + assert mml.childNodes[0].childNodes[0].childNodes[0].nodeValue == 'y' + assert mml.childNodes[0].childNodes[1].childNodes[0].nodeValue == '4' + + assert mml.childNodes[6].nodeName == 'msup' + assert mml.childNodes[6].childNodes[0].childNodes[0].nodeValue == 'x' + assert mml.childNodes[6].childNodes[1].childNodes[0].nodeValue == '3' + + +def test_print_intervals(): + a = Symbol('a', real=True) + assert mpp.doprint(Interval(0, a)) == \ + '[0,a]' + assert mpp.doprint(Interval(0, a, False, False)) == \ + '[0,a]' + assert mpp.doprint(Interval(0, a, True, False)) == \ + '(0,a]' + assert mpp.doprint(Interval(0, a, False, True)) == \ + '[0,a)' + assert mpp.doprint(Interval(0, a, True, True)) == \ + '(0,a)' + + +def test_print_tuples(): + assert mpp.doprint(Tuple(0,)) == \ + '(0)' + assert mpp.doprint(Tuple(0, a)) == \ + '(0,a)' + assert mpp.doprint(Tuple(0, a, a)) == \ + '(0,a,a)' + assert mpp.doprint(Tuple(0, 1, 2, 3, 4)) == \ + '(0,1,2,3,4)' + assert mpp.doprint(Tuple(0, 1, Tuple(2, 3, 4))) == \ + '(0,1,(2,3'\ + ',4))' + + +def test_print_re_im(): + assert mpp.doprint(re(x)) == \ + 'ℜ(x)' + assert mpp.doprint(im(x)) == \ + 'ℑ(x)' + assert mpp.doprint(re(x + 1, evaluate=False)) == \ + 'ℜ(x+1)' + assert mpp.doprint(im(x + 1, evaluate=False)) == \ + 'ℑ(x+1)' + + +def test_print_Abs(): + assert mpp.doprint(Abs(x)) == \ + '|x|' + assert mpp.doprint(Abs(x + 1)) == \ + '|x+1|' + + +def test_print_Determinant(): + assert mpp.doprint(Determinant(Matrix([[1, 2], [3, 4]]))) == \ + '|[1234]|' + + +def test_presentation_settings(): + raises(TypeError, lambda: mathml(x, printer='presentation', + method="garbage")) + + +def test_print_domains(): + from sympy.sets import Integers, Naturals, Naturals0, Reals, Complexes + + assert mpp.doprint(Complexes) == 'ℂ' + assert mpp.doprint(Integers) == 'ℤ' + assert mpp.doprint(Naturals) == 'ℕ' + assert mpp.doprint(Naturals0) == \ + 'ℕ0' + assert mpp.doprint(Reals) == 'ℝ' + + +def test_print_expression_with_minus(): + assert mpp.doprint(-x) == '-x' + assert mpp.doprint(-x/y) == \ + '-xy' + assert mpp.doprint(-Rational(1, 2)) == \ + '-12' + + +def test_print_AssocOp(): + from sympy.core.operations import AssocOp + + class TestAssocOp(AssocOp): + identity = 0 + + expr = TestAssocOp(1, 2) + assert mpp.doprint(expr) == \ + 'testassocop12' + + +def test_print_basic(): + expr = Basic(S(1), S(2)) + assert mpp.doprint(expr) == \ + 'basic(1,2)' + assert mp.doprint(expr) == '12' + + +def test_mat_delim_print(): + expr = Matrix([[1, 2], [3, 4]]) + assert mathml(expr, printer='presentation', mat_delim='[') == \ + '[1'\ + '234'\ + ']' + assert mathml(expr, printer='presentation', mat_delim='(') == \ + '(12'\ + '34)' + assert mathml(expr, printer='presentation', mat_delim='') == \ + '12'\ + '34' + + +def test_ln_notation_print(): + expr = log(x) + assert mathml(expr, printer='presentation') == \ + 'log(x)' + assert mathml(expr, printer='presentation', ln_notation=False) == \ + 'log(x)' + assert mathml(expr, printer='presentation', ln_notation=True) == \ + 'ln(x)' + + +def test_mul_symbol_print(): + expr = x * y + assert mathml(expr, printer='presentation') == \ + 'x⁢y' + assert mathml(expr, printer='presentation', mul_symbol=None) == \ + 'x⁢y' + assert mathml(expr, printer='presentation', mul_symbol='dot') == \ + 'x·y' + assert mathml(expr, printer='presentation', mul_symbol='ldot') == \ + 'x․y' + assert mathml(expr, printer='presentation', mul_symbol='times') == \ + 'x×y' + + +def test_print_lerchphi(): + assert mpp.doprint(lerchphi(1, 2, 3)) == \ + 'Φ(1,2,3)' + + +def test_print_polylog(): + assert mp.doprint(polylog(x, y)) == \ + 'xy' + assert mpp.doprint(polylog(x, y)) == \ + 'Lix(y)' + + +def test_print_set_frozenset(): + f = frozenset({1, 5, 3}) + assert mpp.doprint(f) == \ + '{1,3,5}' + s = set({1, 2, 3}) + assert mpp.doprint(s) == \ + '{1,2,3}' + + +def test_print_FiniteSet(): + f1 = FiniteSet(x, 1, 3) + assert mpp.doprint(f1) == \ + '{1,3,x}' + + +def test_print_LambertW(): + assert mpp.doprint(LambertW(x)) == 'W(x)' + assert mpp.doprint(LambertW(x, y)) == 'W(x,y)' + + +def test_print_EmptySet(): + assert mpp.doprint(S.EmptySet) == '∅' + + +def test_print_UniversalSet(): + assert mpp.doprint(S.UniversalSet) == '𝕌' + + +def test_print_spaces(): + assert mpp.doprint(HilbertSpace()) == 'ℋ' + assert mpp.doprint(ComplexSpace(2)) == '𝒞2' + assert mpp.doprint(FockSpace()) == 'ℱ' + + +def test_print_constants(): + assert mpp.doprint(hbar) == 'ℏ' + assert mpp.doprint(S.TribonacciConstant) == 'TribonacciConstant' + assert mpp.doprint(S.GoldenRatio) == 'Φ' + assert mpp.doprint(S.EulerGamma) == 'γ' + + +def test_print_Contains(): + assert mpp.doprint(Contains(x, S.Naturals)) == \ + 'x∈ℕ' + + +def test_print_Dagger(): + x = symbols('x', commutative=False) + assert mpp.doprint(Dagger(x)) == 'x†' + + +def test_print_SetOp(): + f1 = FiniteSet(x, 1, 3) + f2 = FiniteSet(y, 2, 4) + + prntr = lambda x: mathml(x, printer='presentation') + + assert prntr(Union(f1, f2, evaluate=False)) == \ + '{1,3,x'\ + '}∪{2,'\ + '4,y}' + assert prntr(Intersection(f1, f2, evaluate=False)) == \ + '{1,3,x'\ + '}∩{2'\ + ',4,y}' + assert prntr(Complement(f1, f2, evaluate=False)) == \ + '{1,3,x'\ + '}∖{2'\ + ',4,y}' + assert prntr(SymmetricDifference(f1, f2, evaluate=False)) == \ + '{1,3,x'\ + '}∆{2'\ + ',4,y}' + + A = FiniteSet(a) + C = FiniteSet(c) + D = FiniteSet(d) + + U1 = Union(C, D, evaluate=False) + I1 = Intersection(C, D, evaluate=False) + C1 = Complement(C, D, evaluate=False) + D1 = SymmetricDifference(C, D, evaluate=False) + # XXX ProductSet does not support evaluate keyword + P1 = ProductSet(C, D) + + assert prntr(Union(A, I1, evaluate=False)) == \ + '{a}' \ + '∪({' \ + 'c}∩{' \ + 'd})' + assert prntr(Intersection(A, C1, evaluate=False)) == \ + '{a}' \ + '∩({' \ + 'c}∖{' \ + 'd})' + assert prntr(Complement(A, D1, evaluate=False)) == \ + '{a}' \ + '∖({' \ + 'c}∆{' \ + 'd})' + assert prntr(SymmetricDifference(A, P1, evaluate=False)) == \ + '{a}' \ + '∆({' \ + 'c}×{' \ + 'd})' + assert prntr(ProductSet(A, U1)) == \ + '{a}' \ + '×({' \ + 'c}∪{' \ + 'd})' + + +def test_print_logic(): + assert mpp.doprint(And(x, y)) == \ + 'x∧y' + assert mpp.doprint(Or(x, y)) == \ + 'x∨y' + assert mpp.doprint(Xor(x, y)) == \ + 'x⊻y' + assert mpp.doprint(Implies(x, y)) == \ + 'x⇒y' + assert mpp.doprint(Equivalent(x, y)) == \ + 'x⇔y' + + assert mpp.doprint(And(Eq(x, y), x > 4)) == \ + 'x=y∧'\ + 'x>4' + assert mpp.doprint(And(Eq(x, 3), y < 3, x > y + 1)) == \ + 'x=3∧'\ + 'x>y+1'\ + '∧y<3' + assert mpp.doprint(Or(Eq(x, y), x > 4)) == \ + 'x=y∨'\ + 'x>4' + assert mpp.doprint(And(Eq(x, 3), Or(y < 3, x > y + 1))) == \ + 'x=3'\ + '∧(x>'\ + 'y+1∨'\ + 'y<3)' + + assert mpp.doprint(Not(x)) == '¬x' + assert mpp.doprint(Not(And(x, y))) == \ + '¬(x∧y)' + + +def test_root_notation_print(): + assert mathml(x**(S.One/3), printer='presentation') == \ + 'x3' + assert mathml(x**(S.One/3), printer='presentation', root_notation=False) ==\ + 'x13' + assert mathml(x**(S.One/3), printer='content') == \ + '3x' + assert mathml(x**(S.One/3), printer='content', root_notation=False) == \ + 'x13' + assert mathml(x**(Rational(-1, 3)), printer='presentation') == \ + '1x3' + assert mathml(x**(Rational(-1, 3)), printer='presentation', root_notation=False) \ + == '1x13' + + +def test_fold_frac_powers_print(): + expr = x ** Rational(5, 2) + assert mathml(expr, printer='presentation') == \ + 'x52' + assert mathml(expr, printer='presentation', fold_frac_powers=True) == \ + 'x52' + assert mathml(expr, printer='presentation', fold_frac_powers=False) == \ + 'x52' + + +def test_fold_short_frac_print(): + expr = Rational(2, 5) + assert mathml(expr, printer='presentation') == \ + '25' + assert mathml(expr, printer='presentation', fold_short_frac=True) == \ + '25' + assert mathml(expr, printer='presentation', fold_short_frac=False) == \ + '25' + + +def test_print_factorials(): + assert mpp.doprint(factorial(x)) == 'x!' + assert mpp.doprint(factorial(x + 1)) == \ + '(x+1)!' + assert mpp.doprint(factorial2(x)) == 'x!!' + assert mpp.doprint(factorial2(x + 1)) == \ + '(x+1)!!' + assert mpp.doprint(binomial(x, y)) == \ + '(xy)' + assert mpp.doprint(binomial(4, x + y)) == \ + '(4x'\ + '+y)' + + +def test_print_floor(): + expr = floor(x) + assert mathml(expr, printer='presentation') == \ + '⌊x⌋' + + +def test_print_ceiling(): + expr = ceiling(x) + assert mathml(expr, printer='presentation') == \ + '⌈x⌉' + + +def test_print_Lambda(): + expr = Lambda(x, x+1) + assert mathml(expr, printer='presentation') == \ + '(x↦x+1)' + expr = Lambda((x, y), x + y) + assert mathml(expr, printer='presentation') == \ + '((x,y)↦x+y)' + + +def test_print_conjugate(): + assert mpp.doprint(conjugate(x)) == \ + 'x' + assert mpp.doprint(conjugate(x + 1)) == \ + 'x+1' + + +def test_print_AccumBounds(): + a = Symbol('a', real=True) + assert mpp.doprint(AccumBounds(0, 1)) == '⟨0,1⟩' + assert mpp.doprint(AccumBounds(0, a)) == '⟨0,a⟩' + assert mpp.doprint(AccumBounds(a + 1, a + 2)) == '⟨a+1,a+2⟩' + + +def test_print_Float(): + assert mpp.doprint(Float(1e100)) == '1.0·10100' + assert mpp.doprint(Float(1e-100)) == '1.0·10-100' + assert mpp.doprint(Float(-1e100)) == '-1.0·10100' + assert mpp.doprint(Float(1.0*oo)) == '∞' + assert mpp.doprint(Float(-1.0*oo)) == '-∞' + + +def test_print_different_functions(): + assert mpp.doprint(gamma(x)) == 'Γ(x)' + assert mpp.doprint(lowergamma(x, y)) == 'γ(x,y)' + assert mpp.doprint(uppergamma(x, y)) == 'Γ(x,y)' + assert mpp.doprint(zeta(x)) == 'ζ(x)' + assert mpp.doprint(zeta(x, y)) == 'ζ(x,y)' + assert mpp.doprint(dirichlet_eta(x)) == 'η(x)' + assert mpp.doprint(elliptic_k(x)) == 'Κ(x)' + assert mpp.doprint(totient(x)) == 'ϕ(x)' + assert mpp.doprint(reduced_totient(x)) == 'λ(x)' + assert mpp.doprint(primenu(x)) == 'ν(x)' + assert mpp.doprint(primeomega(x)) == 'Ω(x)' + assert mpp.doprint(fresnels(x)) == 'S(x)' + assert mpp.doprint(fresnelc(x)) == 'C(x)' + assert mpp.doprint(Heaviside(x)) == 'Θ(x,12)' + + +def test_mathml_builtins(): + assert mpp.doprint(None) == 'None' + assert mpp.doprint(true) == 'True' + assert mpp.doprint(false) == 'False' + + +def test_mathml_Range(): + assert mpp.doprint(Range(1, 51)) == \ + '{1,2,…,50}' + assert mpp.doprint(Range(1, 4)) == \ + '{1,2,3}' + assert mpp.doprint(Range(0, 3, 1)) == \ + '{0,1,2}' + assert mpp.doprint(Range(0, 30, 1)) == \ + '{0,1,…,29}' + assert mpp.doprint(Range(30, 1, -1)) == \ + '{30,29,…,2}' + assert mpp.doprint(Range(0, oo, 2)) == \ + '{0,2,…}' + assert mpp.doprint(Range(oo, -2, -2)) == \ + '{…,2,0}' + assert mpp.doprint(Range(-2, -oo, -1)) == \ + '{-2,-3,…}' + + +def test_print_exp(): + assert mpp.doprint(exp(x)) == \ + 'ⅇx' + assert mpp.doprint(exp(1) + exp(2)) == \ + 'ⅇ+ⅇ2' + + +def test_print_MinMax(): + assert mpp.doprint(Min(x, y)) == \ + 'min(x,y)' + assert mpp.doprint(Min(x, 2, x**3)) == \ + 'min(2,x,x3)' + assert mpp.doprint(Max(x, y)) == \ + 'max(x,y)' + assert mpp.doprint(Max(x, 2, x**3)) == \ + 'max(2,x,x3)' + + +def test_mathml_presentation_numbers(): + n = Symbol('n') + assert mathml(catalan(n), printer='presentation') == \ + 'Cn' + assert mathml(bernoulli(n), printer='presentation') == \ + 'Bn' + assert mathml(bell(n), printer='presentation') == \ + 'Bn' + assert mathml(euler(n), printer='presentation') == \ + 'En' + assert mathml(fibonacci(n), printer='presentation') == \ + 'Fn' + assert mathml(lucas(n), printer='presentation') == \ + 'Ln' + assert mathml(tribonacci(n), printer='presentation') == \ + 'Tn' + assert mathml(bernoulli(n, x), printer='presentation') == \ + mathml(bell(n, x), printer='presentation') == \ + 'Bn(x)' + assert mathml(euler(n, x), printer='presentation') == \ + 'En(x)' + assert mathml(fibonacci(n, x), printer='presentation') == \ + 'Fn(x)' + assert mathml(tribonacci(n, x), printer='presentation') == \ + 'Tn(x)' + + +def test_mathml_presentation_mathieu(): + assert mathml(mathieuc(x, y, z), printer='presentation') == \ + 'C(x,y,z)' + assert mathml(mathieus(x, y, z), printer='presentation') == \ + 'S(x,y,z)' + assert mathml(mathieucprime(x, y, z), printer='presentation') == \ + 'C′(x,y,z)' + assert mathml(mathieusprime(x, y, z), printer='presentation') == \ + 'S′(x,y,z)' + + +def test_mathml_presentation_stieltjes(): + assert mathml(stieltjes(n), printer='presentation') == \ + 'γn' + assert mathml(stieltjes(n, x), printer='presentation') == \ + 'γn(x)' + + +def test_print_matrix_symbol(): + A = MatrixSymbol('A', 1, 2) + assert mpp.doprint(A) == 'A' + assert mp.doprint(A) == 'A' + assert mathml(A, printer='presentation', mat_symbol_style="bold") == \ + 'A' + # No effect in content printer + assert mathml(A, mat_symbol_style="bold") == 'A' + + +def test_print_hadamard(): + from sympy.matrices.expressions import HadamardProduct + from sympy.matrices.expressions import Transpose + + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + + assert mathml(HadamardProduct(X, Y*Y), printer="presentation") == \ + '' \ + 'X' \ + '∘' \ + 'Y2' \ + '' + + assert mathml(HadamardProduct(X, Y)*Y, printer="presentation") == \ + '' \ + '(' \ + 'X∘Y' \ + ')' \ + '⁢Y' \ + '' + + assert mathml(HadamardProduct(X, Y, Y), printer="presentation") == \ + '' \ + 'X∘' \ + 'Y∘' \ + 'Y' \ + '' + + assert mathml( + Transpose(HadamardProduct(X, Y)), printer="presentation") == \ + '' \ + '(' \ + 'X∘Y' \ + ')' \ + 'T' \ + '' + + +def test_print_random_symbol(): + R = RandomSymbol(Symbol('R')) + assert mpp.doprint(R) == 'R' + assert mp.doprint(R) == 'R' + + +def test_print_IndexedBase(): + assert mathml(IndexedBase(a)[b], printer='presentation') == \ + 'ab' + assert mathml(IndexedBase(a)[b, c, d], printer='presentation') == \ + 'a(b,c,d)' + assert mathml(IndexedBase(a)[b]*IndexedBase(c)[d]*IndexedBase(e), + printer='presentation') == \ + 'ab⁢'\ + 'cd⁢e' + + +def test_print_Indexed(): + assert mathml(IndexedBase(a), printer='presentation') == 'a' + assert mathml(IndexedBase(a/b), printer='presentation') == \ + 'ab' + assert mathml(IndexedBase((a, b)), printer='presentation') == \ + '(a,b)' + +def test_print_MatrixElement(): + i, j = symbols('i j') + A = MatrixSymbol('A', i, j) + assert mathml(A[0,0],printer = 'presentation') == \ + 'A0,0' + assert mathml(A[i,j], printer = 'presentation') == \ + 'Ai,j' + assert mathml(A[i*j,0], printer = 'presentation') == \ + 'Ai⁢j,0' + + +def test_print_Vector(): + ACS = CoordSys3D('A') + assert mathml(Cross(ACS.i, ACS.j*ACS.x*3 + ACS.k), printer='presentation') == \ + 'i^'\ + 'A×('\ + '(3⁢'\ + 'xA'\ + ')⁢'\ + 'j^'\ + 'A+'\ + 'k^'\ + 'A)' + assert mathml(Cross(ACS.i, ACS.j), printer='presentation') == \ + 'i^'\ + 'A×'\ + 'j^'\ + 'A' + assert mathml(x*Cross(ACS.i, ACS.j), printer='presentation') == \ + 'x⁢('\ + 'i^'\ + 'A×'\ + 'j^'\ + 'A)' + assert mathml(Cross(x*ACS.i, ACS.j), printer='presentation') == \ + '-j'\ + '^A'\ + '×((x)'\ + '⁢i'\ + '^A'\ + ')' + assert mathml(Curl(3*ACS.x*ACS.j), printer='presentation') == \ + '∇×(('\ + '3⁢x'\ + 'A)⁢'\ + 'j^'\ + 'A)' + assert mathml(Curl(3*x*ACS.x*ACS.j), printer='presentation') == \ + '∇×(('\ + '3⁢x'\ + 'A⁢'\ + 'x)⁢'\ + 'j^'\ + 'A)' + assert mathml(x*Curl(3*ACS.x*ACS.j), printer='presentation') == \ + 'x⁢(∇'\ + '×((3'\ + '⁢x'\ + 'A)'\ + '⁢j'\ + '^A)'\ + ')' + assert mathml(Curl(3*x*ACS.x*ACS.j + ACS.i), printer='presentation') == \ + '∇×('\ + 'i^'\ + 'A+('\ + '3⁢x'\ + 'A⁢'\ + 'x)⁢'\ + 'j^'\ + 'A)' + assert mathml(Divergence(3*ACS.x*ACS.j), printer='presentation') == \ + '∇·(('\ + '3⁢x'\ + 'A)'\ + '⁢j'\ + '^A)' + assert mathml(x*Divergence(3*ACS.x*ACS.j), printer='presentation') == \ + 'x⁢(∇'\ + '·((3'\ + '⁢x'\ + 'A)'\ + '⁢j'\ + '^A'\ + '))' + assert mathml(Divergence(3*x*ACS.x*ACS.j + ACS.i), printer='presentation') == \ + '∇·('\ + 'i^'\ + 'A+('\ + '3⁢'\ + 'xA'\ + '⁢x)'\ + '⁢j'\ + '^A'\ + ')' + assert mathml(Dot(ACS.i, ACS.j*ACS.x*3+ACS.k), printer='presentation') == \ + 'i^'\ + 'A·('\ + '(3⁢'\ + 'xA'\ + ')⁢'\ + 'j^'\ + 'A+'\ + 'k^'\ + 'A)' + assert mathml(Dot(ACS.i, ACS.j), printer='presentation') == \ + 'i^'\ + 'A·'\ + 'j^'\ + 'A' + assert mathml(Dot(x*ACS.i, ACS.j), printer='presentation') == \ + 'j^'\ + 'A·('\ + '(x)⁢'\ + 'i^'\ + 'A)' + assert mathml(x*Dot(ACS.i, ACS.j), printer='presentation') == \ + 'x⁢('\ + 'i^'\ + 'A·'\ + 'j^'\ + 'A)' + assert mathml(Gradient(ACS.x), printer='presentation') == \ + '∇x'\ + 'A' + assert mathml(Gradient(ACS.x + 3*ACS.y), printer='presentation') == \ + '∇('\ + 'xA+3'\ + '⁢y'\ + 'A)' + assert mathml(x*Gradient(ACS.x), printer='presentation') == \ + 'x⁢(∇'\ + 'xA'\ + ')' + assert mathml(Gradient(x*ACS.x), printer='presentation') == \ + '∇('\ + 'xA⁢'\ + 'x)' + assert mathml(Cross(ACS.z, ACS.x), printer='presentation') == \ + '-x'\ + 'A×'\ + 'zA' + assert mathml(Laplacian(ACS.x), printer='presentation') == \ + '∆x'\ + 'A' + assert mathml(Laplacian(ACS.x + 3*ACS.y), printer='presentation') == \ + '∆('\ + 'xA+3'\ + '⁢y'\ + 'A)' + assert mathml(x*Laplacian(ACS.x), printer='presentation') == \ + 'x⁢(∆'\ + 'xA'\ + ')' + assert mathml(Laplacian(x*ACS.x), printer='presentation') == \ + '∆('\ + 'xA⁢'\ + 'x)' + +@XFAIL +def test_vector_cross_xfail(): + ACS = CoordSys3D('A') + assert mathml(Cross(ACS.x, ACS.z) + Cross(ACS.z, ACS.x), printer='presentation') == \ + '0^' + +def test_print_elliptic_f(): + assert mathml(elliptic_f(x, y), printer = 'presentation') == \ + '𝖥(x|y)' + assert mathml(elliptic_f(x/y, y), printer = 'presentation') == \ + '𝖥(xy|y)' + +def test_print_elliptic_e(): + assert mathml(elliptic_e(x), printer = 'presentation') == \ + '𝖤(x)' + assert mathml(elliptic_e(x, y), printer = 'presentation') == \ + '𝖤(x|y)' + +def test_print_elliptic_pi(): + assert mathml(elliptic_pi(x, y), printer = 'presentation') == \ + '𝛱(x|y)' + assert mathml(elliptic_pi(x, y, z), printer = 'presentation') == \ + '𝛱(x;y|z)' + +def test_print_Ei(): + assert mathml(Ei(x), printer = 'presentation') == \ + 'Ei(x)' + assert mathml(Ei(x**y), printer = 'presentation') == \ + 'Ei(xy)' + +def test_print_expint(): + assert mathml(expint(x, y), printer = 'presentation') == \ + 'Ex(y)' + assert mathml(expint(IndexedBase(x)[1], IndexedBase(x)[2]), printer = 'presentation') == \ + 'Ex1(x2)' + +def test_print_jacobi(): + assert mathml(jacobi(n, a, b, x), printer = 'presentation') == \ + 'Pn(a,b)(x)' + +def test_print_gegenbauer(): + assert mathml(gegenbauer(n, a, x), printer = 'presentation') == \ + 'Cn(a)(x)' + +def test_print_chebyshevt(): + assert mathml(chebyshevt(n, x), printer = 'presentation') == \ + 'Tn(x)' + +def test_print_chebyshevu(): + assert mathml(chebyshevu(n, x), printer = 'presentation') == \ + 'Un(x)' + +def test_print_legendre(): + assert mathml(legendre(n, x), printer = 'presentation') == \ + 'Pn(x)' + +def test_print_assoc_legendre(): + assert mathml(assoc_legendre(n, a, x), printer = 'presentation') == \ + 'Pn(a)(x)' + +def test_print_laguerre(): + assert mathml(laguerre(n, x), printer = 'presentation') == \ + 'Ln(x)' + +def test_print_assoc_laguerre(): + assert mathml(assoc_laguerre(n, a, x), printer = 'presentation') == \ + 'Ln(a)(x)' + +def test_print_hermite(): + assert mathml(hermite(n, x), printer = 'presentation') == \ + 'Hn(x)' + +def test_mathml_SingularityFunction(): + assert mathml(SingularityFunction(x, 4, 5), printer='presentation') == \ + '⟨x-4⟩5' + assert mathml(SingularityFunction(x, -3, 4), printer='presentation') == \ + '⟨x+3⟩4' + assert mathml(SingularityFunction(x, 0, 4), printer='presentation') == \ + '⟨x⟩4' + assert mathml(SingularityFunction(x, a, n), printer='presentation') == \ + '⟨-a+x⟩n' + assert mathml(SingularityFunction(x, 4, -2), printer='presentation') == \ + '⟨x-4⟩-2' + assert mathml(SingularityFunction(x, 4, -1), printer='presentation') == \ + '⟨x-4⟩-1' + + +def test_mathml_matrix_functions(): + from sympy.matrices import Adjoint, Inverse, Transpose + X = MatrixSymbol('X', 2, 2) + Y = MatrixSymbol('Y', 2, 2) + assert mathml(Adjoint(X), printer='presentation') == \ + 'X†' + assert mathml(Adjoint(X + Y), printer='presentation') == \ + '(X+Y)†' + assert mathml(Adjoint(X) + Adjoint(Y), printer='presentation') == \ + 'X†+' \ + 'Y†' + assert mathml(Adjoint(X*Y), printer='presentation') == \ + '(X⁢' \ + 'Y)†' + assert mathml(Adjoint(Y)*Adjoint(X), printer='presentation') == \ + 'Y†⁢' \ + 'X†' + assert mathml(Adjoint(X**2), printer='presentation') == \ + '(X2)†' + assert mathml(Adjoint(X)**2, printer='presentation') == \ + '(X†)2' + assert mathml(Adjoint(Inverse(X)), printer='presentation') == \ + '(X-1)†' + assert mathml(Inverse(Adjoint(X)), printer='presentation') == \ + '(X†)-1' + assert mathml(Adjoint(Transpose(X)), printer='presentation') == \ + '(XT)†' + assert mathml(Transpose(Adjoint(X)), printer='presentation') == \ + '(X†)T' + assert mathml(Transpose(Adjoint(X) + Y), printer='presentation') == \ + '(X†' \ + '+Y)T' + assert mathml(Transpose(X), printer='presentation') == \ + 'XT' + assert mathml(Transpose(X + Y), printer='presentation') == \ + '(X+Y)T' + + +def test_mathml_special_matrices(): + from sympy.matrices import Identity, ZeroMatrix, OneMatrix + assert mathml(Identity(4), printer='presentation') == '𝕀' + assert mathml(ZeroMatrix(2, 2), printer='presentation') == '𝟘' + assert mathml(OneMatrix(2, 2), printer='presentation') == '𝟙' + +def test_mathml_piecewise(): + from sympy.functions.elementary.piecewise import Piecewise + # Content MathML + assert mathml(Piecewise((x, x <= 1), (x**2, True))) == \ + 'xx1x2' + + raises(ValueError, lambda: mathml(Piecewise((x, x <= 1)))) + + +def test_issue_17857(): + assert mathml(Range(-oo, oo), printer='presentation') == \ + '{…,-1,0,1,…}' + assert mathml(Range(oo, -oo, -1), printer='presentation') == \ + '{…,1,0,-1,…}' + + +def test_float_roundtrip(): + x = sympify(0.8975979010256552) + y = float(mp.doprint(x).strip('')) + assert x == y + + +def test_content_mathml_disable_split_super_sub(): + mp = MathMLContentPrinter() + assert mp.doprint(Symbol('u_b')) == 'ub' + mp = MathMLContentPrinter({'disable_split_super_sub': False}) + assert mp.doprint(Symbol('u_b')) == 'ub' + mp = MathMLContentPrinter({'disable_split_super_sub': True}) + assert mp.doprint(Symbol('u_b')) == 'u_b' + +def test_presentation_mathml_disable_split_super_sub(): + mpp = MathMLPresentationPrinter() + assert mpp.doprint(Symbol('u_b')) == 'ub' + mpp = MathMLPresentationPrinter({'disable_split_super_sub': False}) + assert mpp.doprint(Symbol('u_b')) == 'ub' + mpp = MathMLPresentationPrinter({'disable_split_super_sub': True}) + assert mpp.doprint(Symbol('u_b')) == 'u_b' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_numpy.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_numpy.py new file mode 100644 index 0000000000000000000000000000000000000000..fee1c6bd95e54790a048220f37b8e5de79017d2f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_numpy.py @@ -0,0 +1,381 @@ +from sympy.concrete.summations import Sum +from sympy.core.mod import Mod +from sympy.core.relational import (Equality, Unequality) +from sympy.core.symbol import Symbol +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.gamma_functions import polygamma +from sympy.functions.special.error_functions import (Si, Ci) +from sympy.matrices import Matrix +from sympy.matrices.expressions.blockmatrix import BlockMatrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import Identity +from sympy.utilities.lambdify import lambdify +from sympy import symbols, Min, Max + +from sympy.abc import x, i, j, a, b, c, d +from sympy.core import Pow +from sympy.codegen.matrix_nodes import MatrixSolve +from sympy.codegen.numpy_nodes import logaddexp, logaddexp2 +from sympy.codegen.cfunctions import log1p, expm1, hypot, log10, exp2, log2, Sqrt +from sympy.tensor.array import Array +from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayAdd, \ + PermuteDims, ArrayDiagonal +from sympy.printing.numpy import NumPyPrinter, SciPyPrinter, _numpy_known_constants, \ + _numpy_known_functions, _scipy_known_constants, _scipy_known_functions +from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array + +from sympy.testing.pytest import skip, raises +from sympy.external import import_module + +np = import_module('numpy') +jax = import_module('jax') + +if np: + deafult_float_info = np.finfo(np.array([]).dtype) + NUMPY_DEFAULT_EPSILON = deafult_float_info.eps + +def test_numpy_piecewise_regression(): + """ + NumPyPrinter needs to print Piecewise()'s choicelist as a list to avoid + breaking compatibility with numpy 1.8. This is not necessary in numpy 1.9+. + See gh-9747 and gh-9749 for details. + """ + printer = NumPyPrinter() + p = Piecewise((1, x < 0), (0, True)) + assert printer.doprint(p) == \ + 'numpy.select([numpy.less(x, 0),True], [1,0], default=numpy.nan)' + assert printer.module_imports == {'numpy': {'select', 'less', 'nan'}} + +def test_numpy_logaddexp(): + lae = logaddexp(a, b) + assert NumPyPrinter().doprint(lae) == 'numpy.logaddexp(a, b)' + lae2 = logaddexp2(a, b) + assert NumPyPrinter().doprint(lae2) == 'numpy.logaddexp2(a, b)' + + +def test_sum(): + if not np: + skip("NumPy not installed") + + s = Sum(x ** i, (i, a, b)) + f = lambdify((a, b, x), s, 'numpy') + + a_, b_ = 0, 10 + x_ = np.linspace(-1, +1, 10) + assert np.allclose(f(a_, b_, x_), sum(x_ ** i_ for i_ in range(a_, b_ + 1))) + + s = Sum(i * x, (i, a, b)) + f = lambdify((a, b, x), s, 'numpy') + + a_, b_ = 0, 10 + x_ = np.linspace(-1, +1, 10) + assert np.allclose(f(a_, b_, x_), sum(i_ * x_ for i_ in range(a_, b_ + 1))) + + +def test_multiple_sums(): + if not np: + skip("NumPy not installed") + + s = Sum((x + j) * i, (i, a, b), (j, c, d)) + f = lambdify((a, b, c, d, x), s, 'numpy') + + a_, b_ = 0, 10 + c_, d_ = 11, 21 + x_ = np.linspace(-1, +1, 10) + assert np.allclose(f(a_, b_, c_, d_, x_), + sum((x_ + j_) * i_ for i_ in range(a_, b_ + 1) for j_ in range(c_, d_ + 1))) + + +def test_codegen_einsum(): + if not np: + skip("NumPy not installed") + + M = MatrixSymbol("M", 2, 2) + N = MatrixSymbol("N", 2, 2) + + cg = convert_matrix_to_array(M * N) + f = lambdify((M, N), cg, 'numpy') + + ma = np.array([[1, 2], [3, 4]]) + mb = np.array([[1,-2], [-1, 3]]) + assert (f(ma, mb) == np.matmul(ma, mb)).all() + + +def test_codegen_extra(): + if not np: + skip("NumPy not installed") + + M = MatrixSymbol("M", 2, 2) + N = MatrixSymbol("N", 2, 2) + P = MatrixSymbol("P", 2, 2) + Q = MatrixSymbol("Q", 2, 2) + ma = np.array([[1, 2], [3, 4]]) + mb = np.array([[1,-2], [-1, 3]]) + mc = np.array([[2, 0], [1, 2]]) + md = np.array([[1,-1], [4, 7]]) + + cg = ArrayTensorProduct(M, N) + f = lambdify((M, N), cg, 'numpy') + assert (f(ma, mb) == np.einsum(ma, [0, 1], mb, [2, 3])).all() + + cg = ArrayAdd(M, N) + f = lambdify((M, N), cg, 'numpy') + assert (f(ma, mb) == ma+mb).all() + + cg = ArrayAdd(M, N, P) + f = lambdify((M, N, P), cg, 'numpy') + assert (f(ma, mb, mc) == ma+mb+mc).all() + + cg = ArrayAdd(M, N, P, Q) + f = lambdify((M, N, P, Q), cg, 'numpy') + assert (f(ma, mb, mc, md) == ma+mb+mc+md).all() + + cg = PermuteDims(M, [1, 0]) + f = lambdify((M,), cg, 'numpy') + assert (f(ma) == ma.T).all() + + cg = PermuteDims(ArrayTensorProduct(M, N), [1, 2, 3, 0]) + f = lambdify((M, N), cg, 'numpy') + assert (f(ma, mb) == np.transpose(np.einsum(ma, [0, 1], mb, [2, 3]), (1, 2, 3, 0))).all() + + cg = ArrayDiagonal(ArrayTensorProduct(M, N), (1, 2)) + f = lambdify((M, N), cg, 'numpy') + assert (f(ma, mb) == np.diagonal(np.einsum(ma, [0, 1], mb, [2, 3]), axis1=1, axis2=2)).all() + + +def test_relational(): + if not np: + skip("NumPy not installed") + + e = Equality(x, 1) + + f = lambdify((x,), e) + x_ = np.array([0, 1, 2]) + assert np.array_equal(f(x_), [False, True, False]) + + e = Unequality(x, 1) + + f = lambdify((x,), e) + x_ = np.array([0, 1, 2]) + assert np.array_equal(f(x_), [True, False, True]) + + e = (x < 1) + + f = lambdify((x,), e) + x_ = np.array([0, 1, 2]) + assert np.array_equal(f(x_), [True, False, False]) + + e = (x <= 1) + + f = lambdify((x,), e) + x_ = np.array([0, 1, 2]) + assert np.array_equal(f(x_), [True, True, False]) + + e = (x > 1) + + f = lambdify((x,), e) + x_ = np.array([0, 1, 2]) + assert np.array_equal(f(x_), [False, False, True]) + + e = (x >= 1) + + f = lambdify((x,), e) + x_ = np.array([0, 1, 2]) + assert np.array_equal(f(x_), [False, True, True]) + + +def test_mod(): + if not np: + skip("NumPy not installed") + + e = Mod(a, b) + f = lambdify((a, b), e) + + a_ = np.array([0, 1, 2, 3]) + b_ = 2 + assert np.array_equal(f(a_, b_), [0, 1, 0, 1]) + + a_ = np.array([0, 1, 2, 3]) + b_ = np.array([2, 2, 2, 2]) + assert np.array_equal(f(a_, b_), [0, 1, 0, 1]) + + a_ = np.array([2, 3, 4, 5]) + b_ = np.array([2, 3, 4, 5]) + assert np.array_equal(f(a_, b_), [0, 0, 0, 0]) + + +def test_pow(): + if not np: + skip('NumPy not installed') + + expr = Pow(2, -1, evaluate=False) + f = lambdify([], expr, 'numpy') + assert f() == 0.5 + + +def test_expm1(): + if not np: + skip("NumPy not installed") + + f = lambdify((a,), expm1(a), 'numpy') + assert abs(f(1e-10) - 1e-10 - 5e-21) <= 1e-10 * NUMPY_DEFAULT_EPSILON + + +def test_log1p(): + if not np: + skip("NumPy not installed") + + f = lambdify((a,), log1p(a), 'numpy') + assert abs(f(1e-99) - 1e-99) <= 1e-99 * NUMPY_DEFAULT_EPSILON + +def test_hypot(): + if not np: + skip("NumPy not installed") + assert abs(lambdify((a, b), hypot(a, b), 'numpy')(3, 4) - 5) <= NUMPY_DEFAULT_EPSILON + +def test_log10(): + if not np: + skip("NumPy not installed") + assert abs(lambdify((a,), log10(a), 'numpy')(100) - 2) <= NUMPY_DEFAULT_EPSILON + + +def test_exp2(): + if not np: + skip("NumPy not installed") + assert abs(lambdify((a,), exp2(a), 'numpy')(5) - 32) <= NUMPY_DEFAULT_EPSILON + + +def test_log2(): + if not np: + skip("NumPy not installed") + assert abs(lambdify((a,), log2(a), 'numpy')(256) - 8) <= NUMPY_DEFAULT_EPSILON + + +def test_Sqrt(): + if not np: + skip("NumPy not installed") + assert abs(lambdify((a,), Sqrt(a), 'numpy')(4) - 2) <= NUMPY_DEFAULT_EPSILON + + +def test_sqrt(): + if not np: + skip("NumPy not installed") + assert abs(lambdify((a,), sqrt(a), 'numpy')(4) - 2) <= NUMPY_DEFAULT_EPSILON + + +def test_matsolve(): + if not np: + skip("NumPy not installed") + + M = MatrixSymbol("M", 3, 3) + x = MatrixSymbol("x", 3, 1) + + expr = M**(-1) * x + x + matsolve_expr = MatrixSolve(M, x) + x + + f = lambdify((M, x), expr) + f_matsolve = lambdify((M, x), matsolve_expr) + + m0 = np.array([[1, 2, 3], [3, 2, 5], [5, 6, 7]]) + assert np.linalg.matrix_rank(m0) == 3 + + x0 = np.array([3, 4, 5]) + + assert np.allclose(f_matsolve(m0, x0), f(m0, x0)) + + +def test_16857(): + if not np: + skip("NumPy not installed") + + a_1 = MatrixSymbol('a_1', 10, 3) + a_2 = MatrixSymbol('a_2', 10, 3) + a_3 = MatrixSymbol('a_3', 10, 3) + a_4 = MatrixSymbol('a_4', 10, 3) + A = BlockMatrix([[a_1, a_2], [a_3, a_4]]) + assert A.shape == (20, 6) + + printer = NumPyPrinter() + assert printer.doprint(A) == 'numpy.block([[a_1, a_2], [a_3, a_4]])' + + +def test_issue_17006(): + if not np: + skip("NumPy not installed") + + M = MatrixSymbol("M", 2, 2) + + f = lambdify(M, M + Identity(2)) + ma = np.array([[1, 2], [3, 4]]) + mr = np.array([[2, 2], [3, 5]]) + + assert (f(ma) == mr).all() + + from sympy.core.symbol import symbols + n = symbols('n', integer=True) + N = MatrixSymbol("M", n, n) + raises(NotImplementedError, lambda: lambdify(N, N + Identity(n))) + +def test_jax_tuple_compatibility(): + if not jax: + skip("Jax not installed") + + x, y, z = symbols('x y z') + expr = Max(x, y, z) + Min(x, y, z) + func = lambdify((x, y, z), expr, 'jax') + input_tuple1, input_tuple2 = (1, 2, 3), (4, 5, 6) + input_array1, input_array2 = jax.numpy.asarray(input_tuple1), jax.numpy.asarray(input_tuple2) + assert np.allclose(func(*input_tuple1), func(*input_array1)) + assert np.allclose(func(*input_tuple2), func(*input_array2)) + +def test_numpy_array(): + p = NumPyPrinter() + assert p.doprint(Array([[1, 2], [3, 5]])) == 'numpy.array([[1, 2], [3, 5]])' + assert p.doprint(Array([1, 2])) == 'numpy.array([1, 2])' + assert p.doprint(Array([[[1, 2, 3]]])) == 'numpy.array([[[1, 2, 3]]])' + assert p.doprint(Array([], (0,))) == 'numpy.zeros((0,))' + assert p.doprint(Array([], (0, 0))) == 'numpy.zeros((0, 0))' + assert p.doprint(Array([], (0, 1))) == 'numpy.zeros((0, 1))' + assert p.doprint(Array([], (1, 0))) == 'numpy.zeros((1, 0))' + assert p.doprint(Array([1], ())) == 'numpy.array(1)' + +def test_numpy_matrix(): + p = NumPyPrinter() + assert p.doprint(Matrix([[1, 2], [3, 5]])) == 'numpy.array([[1, 2], [3, 5]])' + assert p.doprint(Matrix([1, 2])) == 'numpy.array([[1], [2]])' + assert p.doprint(Matrix(0, 0, [])) == 'numpy.zeros((0, 0))' + assert p.doprint(Matrix(0, 1, [])) == 'numpy.zeros((0, 1))' + assert p.doprint(Matrix(1, 0, [])) == 'numpy.zeros((1, 0))' + +def test_numpy_known_funcs_consts(): + assert _numpy_known_constants['NaN'] == 'numpy.nan' + assert _numpy_known_constants['EulerGamma'] == 'numpy.euler_gamma' + + assert _numpy_known_functions['acos'] == 'numpy.arccos' + assert _numpy_known_functions['log'] == 'numpy.log' + +def test_scipy_known_funcs_consts(): + assert _scipy_known_constants['GoldenRatio'] == 'scipy.constants.golden_ratio' + assert _scipy_known_constants['Pi'] == 'scipy.constants.pi' + + assert _scipy_known_functions['erf'] == 'scipy.special.erf' + assert _scipy_known_functions['factorial'] == 'scipy.special.factorial' + +def test_numpy_print_methods(): + prntr = NumPyPrinter() + assert hasattr(prntr, '_print_acos') + assert hasattr(prntr, '_print_log') + +def test_scipy_print_methods(): + prntr = SciPyPrinter() + assert hasattr(prntr, '_print_acos') + assert hasattr(prntr, '_print_log') + assert hasattr(prntr, '_print_erf') + assert hasattr(prntr, '_print_factorial') + assert hasattr(prntr, '_print_chebyshevt') + k = Symbol('k', integer=True, nonnegative=True) + x = Symbol('x', real=True) + assert prntr.doprint(polygamma(k, x)) == "scipy.special.polygamma(k, x)" + assert prntr.doprint(Si(x)) == "scipy.special.sici(x)[0]" + assert prntr.doprint(Ci(x)) == "scipy.special.sici(x)[1]" diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_octave.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_octave.py new file mode 100644 index 0000000000000000000000000000000000000000..1aba318f873c48ec702f1b4e3a6cc047f75d647d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_octave.py @@ -0,0 +1,515 @@ +from sympy.core import (S, pi, oo, symbols, Function, Rational, Integer, + Tuple, Symbol, EulerGamma, GoldenRatio, Catalan, + Lambda, Mul, Pow, Mod, Eq, Ne, Le, Lt, Gt, Ge) +from sympy.codegen.matrix_nodes import MatrixSolve +from sympy.functions import (arg, atan2, bernoulli, beta, ceiling, chebyshevu, + chebyshevt, conjugate, DiracDelta, exp, expint, + factorial, floor, harmonic, Heaviside, im, + laguerre, LambertW, log, Max, Min, Piecewise, + polylog, re, RisingFactorial, sign, sinc, sqrt, + zeta, binomial, legendre, dirichlet_eta, + riemann_xi) +from sympy.functions import (sin, cos, tan, cot, sec, csc, asin, acos, acot, + atan, asec, acsc, sinh, cosh, tanh, coth, csch, + sech, asinh, acosh, atanh, acoth, asech, acsch) +from sympy.testing.pytest import raises, XFAIL +from sympy.utilities.lambdify import implemented_function +from sympy.matrices import (eye, Matrix, MatrixSymbol, Identity, + HadamardProduct, SparseMatrix, HadamardPower) +from sympy.functions.special.bessel import (jn, yn, besselj, bessely, besseli, + besselk, hankel1, hankel2, airyai, + airybi, airyaiprime, airybiprime) +from sympy.functions.special.gamma_functions import (gamma, lowergamma, + uppergamma, loggamma, + polygamma) +from sympy.functions.special.error_functions import (Chi, Ci, erf, erfc, erfi, + erfcinv, erfinv, fresnelc, + fresnels, li, Shi, Si, Li, + erf2, Ei) +from sympy.printing.octave import octave_code, octave_code as mcode + +x, y, z = symbols('x,y,z') + + +def test_Integer(): + assert mcode(Integer(67)) == "67" + assert mcode(Integer(-1)) == "-1" + + +def test_Rational(): + assert mcode(Rational(3, 7)) == "3/7" + assert mcode(Rational(18, 9)) == "2" + assert mcode(Rational(3, -7)) == "-3/7" + assert mcode(Rational(-3, -7)) == "3/7" + assert mcode(x + Rational(3, 7)) == "x + 3/7" + assert mcode(Rational(3, 7)*x) == "3*x/7" + + +def test_Relational(): + assert mcode(Eq(x, y)) == "x == y" + assert mcode(Ne(x, y)) == "x != y" + assert mcode(Le(x, y)) == "x <= y" + assert mcode(Lt(x, y)) == "x < y" + assert mcode(Gt(x, y)) == "x > y" + assert mcode(Ge(x, y)) == "x >= y" + + +def test_Function(): + assert mcode(sin(x) ** cos(x)) == "sin(x).^cos(x)" + assert mcode(sign(x)) == "sign(x)" + assert mcode(exp(x)) == "exp(x)" + assert mcode(log(x)) == "log(x)" + assert mcode(factorial(x)) == "factorial(x)" + assert mcode(floor(x)) == "floor(x)" + assert mcode(atan2(y, x)) == "atan2(y, x)" + assert mcode(beta(x, y)) == 'beta(x, y)' + assert mcode(polylog(x, y)) == 'polylog(x, y)' + assert mcode(harmonic(x)) == 'harmonic(x)' + assert mcode(bernoulli(x)) == "bernoulli(x)" + assert mcode(bernoulli(x, y)) == "bernoulli(x, y)" + assert mcode(legendre(x, y)) == "legendre(x, y)" + + +def test_Function_change_name(): + assert mcode(abs(x)) == "abs(x)" + assert mcode(ceiling(x)) == "ceil(x)" + assert mcode(arg(x)) == "angle(x)" + assert mcode(im(x)) == "imag(x)" + assert mcode(re(x)) == "real(x)" + assert mcode(conjugate(x)) == "conj(x)" + assert mcode(chebyshevt(y, x)) == "chebyshevT(y, x)" + assert mcode(chebyshevu(y, x)) == "chebyshevU(y, x)" + assert mcode(laguerre(x, y)) == "laguerreL(x, y)" + assert mcode(Chi(x)) == "coshint(x)" + assert mcode(Shi(x)) == "sinhint(x)" + assert mcode(Ci(x)) == "cosint(x)" + assert mcode(Si(x)) == "sinint(x)" + assert mcode(li(x)) == "logint(x)" + assert mcode(loggamma(x)) == "gammaln(x)" + assert mcode(polygamma(x, y)) == "psi(x, y)" + assert mcode(RisingFactorial(x, y)) == "pochhammer(x, y)" + assert mcode(DiracDelta(x)) == "dirac(x)" + assert mcode(DiracDelta(x, 3)) == "dirac(3, x)" + assert mcode(Heaviside(x)) == "heaviside(x, 1/2)" + assert mcode(Heaviside(x, y)) == "heaviside(x, y)" + assert mcode(binomial(x, y)) == "bincoeff(x, y)" + assert mcode(Mod(x, y)) == "mod(x, y)" + + +def test_minmax(): + assert mcode(Max(x, y) + Min(x, y)) == "max(x, y) + min(x, y)" + assert mcode(Max(x, y, z)) == "max(x, max(y, z))" + assert mcode(Min(x, y, z)) == "min(x, min(y, z))" + + +def test_Pow(): + assert mcode(x**3) == "x.^3" + assert mcode(x**(y**3)) == "x.^(y.^3)" + assert mcode(x**Rational(2, 3)) == 'x.^(2/3)' + g = implemented_function('g', Lambda(x, 2*x)) + assert mcode(1/(g(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + "(3.5*2*x).^(-x + y.^x)./(x.^2 + y)" + # For issue 14160 + assert mcode(Mul(-2, x, Pow(Mul(y,y,evaluate=False), -1, evaluate=False), + evaluate=False)) == '-2*x./(y.*y)' + + +def test_basic_ops(): + assert mcode(x*y) == "x.*y" + assert mcode(x + y) == "x + y" + assert mcode(x - y) == "x - y" + assert mcode(-x) == "-x" + + +def test_1_over_x_and_sqrt(): + # 1.0 and 0.5 would do something different in regular StrPrinter, + # but these are exact in IEEE floating point so no different here. + assert mcode(1/x) == '1./x' + assert mcode(x**-1) == mcode(x**-1.0) == '1./x' + assert mcode(1/sqrt(x)) == '1./sqrt(x)' + assert mcode(x**-S.Half) == mcode(x**-0.5) == '1./sqrt(x)' + assert mcode(sqrt(x)) == 'sqrt(x)' + assert mcode(x**S.Half) == mcode(x**0.5) == 'sqrt(x)' + assert mcode(1/pi) == '1/pi' + assert mcode(pi**-1) == mcode(pi**-1.0) == '1/pi' + assert mcode(pi**-0.5) == '1/sqrt(pi)' + + +def test_mix_number_mult_symbols(): + assert mcode(3*x) == "3*x" + assert mcode(pi*x) == "pi*x" + assert mcode(3/x) == "3./x" + assert mcode(pi/x) == "pi./x" + assert mcode(x/3) == "x/3" + assert mcode(x/pi) == "x/pi" + assert mcode(x*y) == "x.*y" + assert mcode(3*x*y) == "3*x.*y" + assert mcode(3*pi*x*y) == "3*pi*x.*y" + assert mcode(x/y) == "x./y" + assert mcode(3*x/y) == "3*x./y" + assert mcode(x*y/z) == "x.*y./z" + assert mcode(x/y*z) == "x.*z./y" + assert mcode(1/x/y) == "1./(x.*y)" + assert mcode(2*pi*x/y/z) == "2*pi*x./(y.*z)" + assert mcode(3*pi/x) == "3*pi./x" + assert mcode(S(3)/5) == "3/5" + assert mcode(S(3)/5*x) == "3*x/5" + assert mcode(x/y/z) == "x./(y.*z)" + assert mcode((x+y)/z) == "(x + y)./z" + assert mcode((x+y)/(z+x)) == "(x + y)./(x + z)" + assert mcode((x+y)/EulerGamma) == "(x + y)/%s" % EulerGamma.evalf(17) + assert mcode(x/3/pi) == "x/(3*pi)" + assert mcode(S(3)/5*x*y/pi) == "3*x.*y/(5*pi)" + + +def test_mix_number_pow_symbols(): + assert mcode(pi**3) == 'pi^3' + assert mcode(x**2) == 'x.^2' + assert mcode(x**(pi**3)) == 'x.^(pi^3)' + assert mcode(x**y) == 'x.^y' + assert mcode(x**(y**z)) == 'x.^(y.^z)' + assert mcode((x**y)**z) == '(x.^y).^z' + + +def test_imag(): + I = S('I') + assert mcode(I) == "1i" + assert mcode(5*I) == "5i" + assert mcode((S(3)/2)*I) == "3*1i/2" + assert mcode(3+4*I) == "3 + 4i" + assert mcode(sqrt(3)*I) == "sqrt(3)*1i" + + +def test_constants(): + assert mcode(pi) == "pi" + assert mcode(oo) == "inf" + assert mcode(-oo) == "-inf" + assert mcode(S.NegativeInfinity) == "-inf" + assert mcode(S.NaN) == "NaN" + assert mcode(S.Exp1) == "exp(1)" + assert mcode(exp(1)) == "exp(1)" + + +def test_constants_other(): + assert mcode(2*GoldenRatio) == "2*(1+sqrt(5))/2" + assert mcode(2*Catalan) == "2*%s" % Catalan.evalf(17) + assert mcode(2*EulerGamma) == "2*%s" % EulerGamma.evalf(17) + + +def test_boolean(): + assert mcode(x & y) == "x & y" + assert mcode(x | y) == "x | y" + assert mcode(~x) == "~x" + assert mcode(x & y & z) == "x & y & z" + assert mcode(x | y | z) == "x | y | z" + assert mcode((x & y) | z) == "z | x & y" + assert mcode((x | y) & z) == "z & (x | y)" + + +def test_KroneckerDelta(): + from sympy.functions import KroneckerDelta + assert mcode(KroneckerDelta(x, y)) == "double(x == y)" + assert mcode(KroneckerDelta(x, y + 1)) == "double(x == (y + 1))" + assert mcode(KroneckerDelta(2**x, y)) == "double((2.^x) == y)" + + +def test_Matrices(): + assert mcode(Matrix(1, 1, [10])) == "10" + A = Matrix([[1, sin(x/2), abs(x)], + [0, 1, pi], + [0, exp(1), ceiling(x)]]) + expected = "[1 sin(x/2) abs(x); 0 1 pi; 0 exp(1) ceil(x)]" + assert mcode(A) == expected + # row and columns + assert mcode(A[:,0]) == "[1; 0; 0]" + assert mcode(A[0,:]) == "[1 sin(x/2) abs(x)]" + # empty matrices + assert mcode(Matrix(0, 0, [])) == '[]' + assert mcode(Matrix(0, 3, [])) == 'zeros(0, 3)' + # annoying to read but correct + assert mcode(Matrix([[x, x - y, -y]])) == "[x x - y -y]" + + +def test_vector_entries_hadamard(): + # For a row or column, user might to use the other dimension + A = Matrix([[1, sin(2/x), 3*pi/x/5]]) + assert mcode(A) == "[1 sin(2./x) 3*pi./(5*x)]" + assert mcode(A.T) == "[1; sin(2./x); 3*pi./(5*x)]" + + +@XFAIL +def test_Matrices_entries_not_hadamard(): + # For Matrix with col >= 2, row >= 2, they need to be scalars + # FIXME: is it worth worrying about this? Its not wrong, just + # leave it user's responsibility to put scalar data for x. + A = Matrix([[1, sin(2/x), 3*pi/x/5], [1, 2, x*y]]) + expected = ("[1 sin(2/x) 3*pi/(5*x);\n" + "1 2 x*y]") # <- we give x.*y + assert mcode(A) == expected + + +def test_MatrixSymbol(): + n = Symbol('n', integer=True) + A = MatrixSymbol('A', n, n) + B = MatrixSymbol('B', n, n) + assert mcode(A*B) == "A*B" + assert mcode(B*A) == "B*A" + assert mcode(2*A*B) == "2*A*B" + assert mcode(B*2*A) == "2*B*A" + assert mcode(A*(B + 3*Identity(n))) == "A*(3*eye(n) + B)" + assert mcode(A**(x**2)) == "A^(x.^2)" + assert mcode(A**3) == "A^3" + assert mcode(A**S.Half) == "A^(1/2)" + + +def test_MatrixSolve(): + n = Symbol('n', integer=True) + A = MatrixSymbol('A', n, n) + x = MatrixSymbol('x', n, 1) + assert mcode(MatrixSolve(A, x)) == "A \\ x" + +def test_special_matrices(): + assert mcode(6*Identity(3)) == "6*eye(3)" + + +def test_containers(): + assert mcode([1, 2, 3, [4, 5, [6, 7]], 8, [9, 10], 11]) == \ + "{1, 2, 3, {4, 5, {6, 7}}, 8, {9, 10}, 11}" + assert mcode((1, 2, (3, 4))) == "{1, 2, {3, 4}}" + assert mcode([1]) == "{1}" + assert mcode((1,)) == "{1}" + assert mcode(Tuple(*[1, 2, 3])) == "{1, 2, 3}" + assert mcode((1, x*y, (3, x**2))) == "{1, x.*y, {3, x.^2}}" + # scalar, matrix, empty matrix and empty list + assert mcode((1, eye(3), Matrix(0, 0, []), [])) == "{1, [1 0 0; 0 1 0; 0 0 1], [], {}}" + + +def test_octave_noninline(): + source = mcode((x+y)/Catalan, assign_to='me', inline=False) + expected = ( + "Catalan = %s;\n" + "me = (x + y)/Catalan;" + ) % Catalan.evalf(17) + assert source == expected + + +def test_octave_piecewise(): + expr = Piecewise((x, x < 1), (x**2, True)) + assert mcode(expr) == "((x < 1).*(x) + (~(x < 1)).*(x.^2))" + assert mcode(expr, assign_to="r") == ( + "r = ((x < 1).*(x) + (~(x < 1)).*(x.^2));") + assert mcode(expr, assign_to="r", inline=False) == ( + "if (x < 1)\n" + " r = x;\n" + "else\n" + " r = x.^2;\n" + "end") + expr = Piecewise((x**2, x < 1), (x**3, x < 2), (x**4, x < 3), (x**5, True)) + expected = ("((x < 1).*(x.^2) + (~(x < 1)).*( ...\n" + "(x < 2).*(x.^3) + (~(x < 2)).*( ...\n" + "(x < 3).*(x.^4) + (~(x < 3)).*(x.^5))))") + assert mcode(expr) == expected + assert mcode(expr, assign_to="r") == "r = " + expected + ";" + assert mcode(expr, assign_to="r", inline=False) == ( + "if (x < 1)\n" + " r = x.^2;\n" + "elseif (x < 2)\n" + " r = x.^3;\n" + "elseif (x < 3)\n" + " r = x.^4;\n" + "else\n" + " r = x.^5;\n" + "end") + # Check that Piecewise without a True (default) condition error + expr = Piecewise((x, x < 1), (x**2, x > 1), (sin(x), x > 0)) + raises(ValueError, lambda: mcode(expr)) + + +def test_octave_piecewise_times_const(): + pw = Piecewise((x, x < 1), (x**2, True)) + assert mcode(2*pw) == "2*((x < 1).*(x) + (~(x < 1)).*(x.^2))" + assert mcode(pw/x) == "((x < 1).*(x) + (~(x < 1)).*(x.^2))./x" + assert mcode(pw/(x*y)) == "((x < 1).*(x) + (~(x < 1)).*(x.^2))./(x.*y)" + assert mcode(pw/3) == "((x < 1).*(x) + (~(x < 1)).*(x.^2))/3" + + +def test_octave_matrix_assign_to(): + A = Matrix([[1, 2, 3]]) + assert mcode(A, assign_to='a') == "a = [1 2 3];" + A = Matrix([[1, 2], [3, 4]]) + assert mcode(A, assign_to='A') == "A = [1 2; 3 4];" + + +def test_octave_matrix_assign_to_more(): + # assigning to Symbol or MatrixSymbol requires lhs/rhs match + A = Matrix([[1, 2, 3]]) + B = MatrixSymbol('B', 1, 3) + C = MatrixSymbol('C', 2, 3) + assert mcode(A, assign_to=B) == "B = [1 2 3];" + raises(ValueError, lambda: mcode(A, assign_to=x)) + raises(ValueError, lambda: mcode(A, assign_to=C)) + + +def test_octave_matrix_1x1(): + A = Matrix([[3]]) + B = MatrixSymbol('B', 1, 1) + C = MatrixSymbol('C', 1, 2) + assert mcode(A, assign_to=B) == "B = 3;" + # FIXME? + #assert mcode(A, assign_to=x) == "x = 3;" + raises(ValueError, lambda: mcode(A, assign_to=C)) + + +def test_octave_matrix_elements(): + A = Matrix([[x, 2, x*y]]) + assert mcode(A[0, 0]**2 + A[0, 1] + A[0, 2]) == "x.^2 + x.*y + 2" + A = MatrixSymbol('AA', 1, 3) + assert mcode(A) == "AA" + assert mcode(A[0, 0]**2 + sin(A[0,1]) + A[0,2]) == \ + "sin(AA(1, 2)) + AA(1, 1).^2 + AA(1, 3)" + assert mcode(sum(A)) == "AA(1, 1) + AA(1, 2) + AA(1, 3)" + + +def test_octave_boolean(): + assert mcode(True) == "true" + assert mcode(S.true) == "true" + assert mcode(False) == "false" + assert mcode(S.false) == "false" + + +def test_octave_not_supported(): + with raises(NotImplementedError): + mcode(S.ComplexInfinity) + f = Function('f') + assert mcode(f(x).diff(x), strict=False) == ( + "% Not supported in Octave:\n" + "% Derivative\n" + "Derivative(f(x), x)" + ) + + +def test_octave_not_supported_not_on_whitelist(): + from sympy.functions.special.polynomials import assoc_laguerre + with raises(NotImplementedError): + mcode(assoc_laguerre(x, y, z)) + + +def test_octave_expint(): + assert mcode(expint(1, x)) == "expint(x)" + with raises(NotImplementedError): + mcode(expint(2, x)) + assert mcode(expint(y, x), strict=False) == ( + "% Not supported in Octave:\n" + "% expint\n" + "expint(y, x)" + ) + + +def test_trick_indent_with_end_else_words(): + # words starting with "end" or "else" do not confuse the indenter + t1 = S('endless') + t2 = S('elsewhere') + pw = Piecewise((t1, x < 0), (t2, x <= 1), (1, True)) + assert mcode(pw, inline=False) == ( + "if (x < 0)\n" + " endless\n" + "elseif (x <= 1)\n" + " elsewhere\n" + "else\n" + " 1\n" + "end") + + +def test_hadamard(): + A = MatrixSymbol('A', 3, 3) + B = MatrixSymbol('B', 3, 3) + v = MatrixSymbol('v', 3, 1) + h = MatrixSymbol('h', 1, 3) + C = HadamardProduct(A, B) + n = Symbol('n') + assert mcode(C) == "A.*B" + assert mcode(C*v) == "(A.*B)*v" + assert mcode(h*C*v) == "h*(A.*B)*v" + assert mcode(C*A) == "(A.*B)*A" + # mixing Hadamard and scalar strange b/c we vectorize scalars + assert mcode(C*x*y) == "(x.*y)*(A.*B)" + + # Testing HadamardPower: + assert mcode(HadamardPower(A, n)) == "A.**n" + assert mcode(HadamardPower(A, 1+n)) == "A.**(n + 1)" + assert mcode(HadamardPower(A*B.T, 1+n)) == "(A*B.T).**(n + 1)" + + +def test_sparse(): + M = SparseMatrix(5, 6, {}) + M[2, 2] = 10 + M[1, 2] = 20 + M[1, 3] = 22 + M[0, 3] = 30 + M[3, 0] = x*y + assert mcode(M) == ( + "sparse([4 2 3 1 2], [1 3 3 4 4], [x.*y 20 10 30 22], 5, 6)" + ) + + +def test_sinc(): + assert mcode(sinc(x)) == 'sinc(x/pi)' + assert mcode(sinc(x + 3)) == 'sinc((x + 3)/pi)' + assert mcode(sinc(pi*(x + 3))) == 'sinc(x + 3)' + + +def test_trigfun(): + for f in (sin, cos, tan, cot, sec, csc, asin, acos, acot, atan, asec, acsc, + sinh, cosh, tanh, coth, csch, sech, asinh, acosh, atanh, acoth, + asech, acsch): + assert octave_code(f(x) == f.__name__ + '(x)') + + +def test_specfun(): + n = Symbol('n') + for f in [besselj, bessely, besseli, besselk]: + assert octave_code(f(n, x)) == f.__name__ + '(n, x)' + for f in (erfc, erfi, erf, erfinv, erfcinv, fresnelc, fresnels, gamma): + assert octave_code(f(x)) == f.__name__ + '(x)' + assert octave_code(hankel1(n, x)) == 'besselh(n, 1, x)' + assert octave_code(hankel2(n, x)) == 'besselh(n, 2, x)' + assert octave_code(airyai(x)) == 'airy(0, x)' + assert octave_code(airyaiprime(x)) == 'airy(1, x)' + assert octave_code(airybi(x)) == 'airy(2, x)' + assert octave_code(airybiprime(x)) == 'airy(3, x)' + assert octave_code(uppergamma(n, x)) == '(gammainc(x, n, \'upper\').*gamma(n))' + assert octave_code(lowergamma(n, x)) == '(gammainc(x, n).*gamma(n))' + assert octave_code(z**lowergamma(n, x)) == 'z.^(gammainc(x, n).*gamma(n))' + assert octave_code(jn(n, x)) == 'sqrt(2)*sqrt(pi)*sqrt(1./x).*besselj(n + 1/2, x)/2' + assert octave_code(yn(n, x)) == 'sqrt(2)*sqrt(pi)*sqrt(1./x).*bessely(n + 1/2, x)/2' + assert octave_code(LambertW(x)) == 'lambertw(x)' + assert octave_code(LambertW(x, n)) == 'lambertw(n, x)' + + # Automatic rewrite + assert octave_code(Ei(x)) == '(logint(exp(x)))' + assert octave_code(dirichlet_eta(x)) == '(((x == 1).*(log(2)) + (~(x == 1)).*((1 - 2.^(1 - x)).*zeta(x))))' + assert octave_code(riemann_xi(x)) == '(pi.^(-x/2).*x.*(x - 1).*gamma(x/2).*zeta(x)/2)' + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert mcode(A[0, 0]) == "A(1, 1)" + assert mcode(3 * A[0, 0]) == "3*A(1, 1)" + + F = C[0, 0].subs(C, A - B) + assert mcode(F) == "(A - B)(1, 1)" + + +def test_zeta_printing_issue_14820(): + assert octave_code(zeta(x)) == 'zeta(x)' + with raises(NotImplementedError): + octave_code(zeta(x, y)) + + +def test_automatic_rewrite(): + assert octave_code(Li(x)) == '(logint(x) - logint(2))' + assert octave_code(erf2(x, y)) == '(-erf(x) + erf(y))' diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_precedence.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_precedence.py new file mode 100644 index 0000000000000000000000000000000000000000..d08ea07483857e8c2ee7f930aa53d2dacdc58193 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_precedence.py @@ -0,0 +1,128 @@ +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum +from sympy.core.function import Derivative, Function +from sympy.core.numbers import Integer, Rational, Float, oo +from sympy.core.relational import Rel +from sympy.core.symbol import symbols +from sympy.functions import sin +from sympy.integrals.integrals import Integral +from sympy.series.order import Order + +from sympy.printing.precedence import precedence, PRECEDENCE + +x, y = symbols("x,y") + + +def test_Add(): + assert precedence(x + y) == PRECEDENCE["Add"] + assert precedence(x*y + 1) == PRECEDENCE["Add"] + + +def test_Function(): + assert precedence(sin(x)) == PRECEDENCE["Func"] + +def test_Derivative(): + assert precedence(Derivative(x, y)) == PRECEDENCE["Atom"] + +def test_Integral(): + assert precedence(Integral(x, y)) == PRECEDENCE["Atom"] + + +def test_Mul(): + assert precedence(x*y) == PRECEDENCE["Mul"] + assert precedence(-x*y) == PRECEDENCE["Add"] + + +def test_Number(): + assert precedence(Integer(0)) == PRECEDENCE["Atom"] + assert precedence(Integer(1)) == PRECEDENCE["Atom"] + assert precedence(Integer(-1)) == PRECEDENCE["Add"] + assert precedence(Integer(10)) == PRECEDENCE["Atom"] + assert precedence(Rational(5, 2)) == PRECEDENCE["Mul"] + assert precedence(Rational(-5, 2)) == PRECEDENCE["Add"] + assert precedence(Float(5)) == PRECEDENCE["Atom"] + assert precedence(Float(-5)) == PRECEDENCE["Add"] + assert precedence(oo) == PRECEDENCE["Atom"] + assert precedence(-oo) == PRECEDENCE["Add"] + + +def test_Order(): + assert precedence(Order(x)) == PRECEDENCE["Atom"] + + +def test_Pow(): + assert precedence(x**y) == PRECEDENCE["Pow"] + assert precedence(-x**y) == PRECEDENCE["Add"] + assert precedence(x**-y) == PRECEDENCE["Pow"] + + +def test_Product(): + assert precedence(Product(x, (x, y, y + 1))) == PRECEDENCE["Atom"] + + +def test_Relational(): + assert precedence(Rel(x + y, y, "<")) == PRECEDENCE["Relational"] + + +def test_Sum(): + assert precedence(Sum(x, (x, y, y + 1))) == PRECEDENCE["Atom"] + + +def test_Symbol(): + assert precedence(x) == PRECEDENCE["Atom"] + + +def test_And_Or(): + # precedence relations between logical operators, ... + assert precedence(x & y) > precedence(x | y) + assert precedence(~y) > precedence(x & y) + # ... and with other operators (cfr. other programming languages) + assert precedence(x + y) > precedence(x | y) + assert precedence(x + y) > precedence(x & y) + assert precedence(x*y) > precedence(x | y) + assert precedence(x*y) > precedence(x & y) + assert precedence(~y) > precedence(x*y) + assert precedence(~y) > precedence(x - y) + # double checks + assert precedence(x & y) == PRECEDENCE["And"] + assert precedence(x | y) == PRECEDENCE["Or"] + assert precedence(~y) == PRECEDENCE["Not"] + + +def test_custom_function_precedence_comparison(): + """ + Test cases for custom functions with different precedence values, + specifically handling: + 1. Functions with precedence < PRECEDENCE["Mul"] (50) + 2. Functions with precedence = Func (70) + + Key distinction: + 1. Lower precedence functions (45) need parentheses: -2*(x F y) + 2. Higher precedence functions (70) don't: -2*x F y + """ + class LowPrecedenceF(Function): + precedence = PRECEDENCE["Mul"] - 5 + def _sympystr(self, printer): + return f"{printer._print(self.args[0])} F {printer._print(self.args[1])}" + + class HighPrecedenceF(Function): + precedence = PRECEDENCE["Func"] + def _sympystr(self, printer): + return f"{printer._print(self.args[0])} F {printer._print(self.args[1])}" + + def test_low_precedence(): + expr1 = 2 * LowPrecedenceF(x, y) + assert str(expr1) == "2*(x F y)" + + expr2 = -2 * LowPrecedenceF(x, y) + assert str(expr2) == "-2*(x F y)" + + def test_high_precedence(): + expr1 = 2 * HighPrecedenceF(x, y) + assert str(expr1) == "2*x F y" + + expr2 = -2 * HighPrecedenceF(x, y) + assert str(expr2) == "-2*x F y" + + test_low_precedence() + test_high_precedence() diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_preview.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_preview.py new file mode 100644 index 0000000000000000000000000000000000000000..91771ceb0466d6b0fee00570426713d02da14872 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_preview.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +from sympy.core.relational import Eq +from sympy.core.symbol import Symbol +from sympy.functions.elementary.piecewise import Piecewise +from sympy.printing.preview import preview + +from io import BytesIO + + +def test_preview(): + x = Symbol('x') + obj = BytesIO() + try: + preview(x, output='png', viewer='BytesIO', outputbuffer=obj) + except RuntimeError: + pass # latex not installed on CI server + + +def test_preview_unicode_symbol(): + # issue 9107 + a = Symbol('α') + obj = BytesIO() + try: + preview(a, output='png', viewer='BytesIO', outputbuffer=obj) + except RuntimeError: + pass # latex not installed on CI server + + +def test_preview_latex_construct_in_expr(): + # see PR 9801 + x = Symbol('x') + pw = Piecewise((1, Eq(x, 0)), (0, True)) + obj = BytesIO() + try: + preview(pw, output='png', viewer='BytesIO', outputbuffer=obj) + except RuntimeError: + pass # latex not installed on CI server diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_pycode.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_pycode.py new file mode 100644 index 0000000000000000000000000000000000000000..2c38fe81d830149cdce6b55f15e6e07513fdd146 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_pycode.py @@ -0,0 +1,493 @@ +from sympy import Not +from sympy.codegen import Assignment +from sympy.codegen.ast import none +from sympy.codegen.cfunctions import expm1, log1p +from sympy.codegen.scipy_nodes import cosm1 +from sympy.codegen.matrix_nodes import MatrixSolve +from sympy.core import Expr, Mod, symbols, Eq, Le, Gt, zoo, oo, Rational, Pow +from sympy.core.function import Derivative +from sympy.core.numbers import pi +from sympy.core.singleton import S +from sympy.functions import acos, KroneckerDelta, Piecewise, sign, sqrt, Min, Max, cot, acsch, asec, coth, sec, log, sin, cos, tan, asin, atan, sinh, cosh, tanh, asinh, acosh, atanh +from sympy.functions.elementary.trigonometric import atan2 +from sympy.logic import And, Or +from sympy.matrices import SparseMatrix, MatrixSymbol, Identity +from sympy.printing.codeprinter import PrintMethodNotImplementedError +from sympy.printing.pycode import ( + MpmathPrinter, CmathPrinter, PythonCodePrinter, pycode, SymPyPrinter +) +from sympy.printing.tensorflow import TensorflowPrinter +from sympy.printing.numpy import NumPyPrinter, SciPyPrinter +from sympy.testing.pytest import raises, skip +from sympy.tensor import IndexedBase, Idx +from sympy.tensor.array.expressions.array_expressions import ArraySymbol, ArrayDiagonal, ArrayContraction, ZeroArray, OneArray +from sympy.external import import_module +from sympy.functions.special.gamma_functions import loggamma + + + +x, y, z = symbols('x y z') +p = IndexedBase("p") + + +def test_PythonCodePrinter(): + prntr = PythonCodePrinter() + + assert not prntr.module_imports + + assert prntr.doprint(x**y) == 'x**y' + assert prntr.doprint(Mod(x, 2)) == 'x % 2' + assert prntr.doprint(-Mod(x, y)) == '-(x % y)' + assert prntr.doprint(Mod(-x, y)) == '(-x) % y' + assert prntr.doprint(And(x, y)) == 'x and y' + assert prntr.doprint(Or(x, y)) == 'x or y' + assert prntr.doprint(1/(x+y)) == '1/(x + y)' + assert prntr.doprint(Not(x)) == 'not x' + assert not prntr.module_imports + + assert prntr.doprint(pi) == 'math.pi' + assert prntr.module_imports == {'math': {'pi'}} + + assert prntr.doprint(x**Rational(1, 2)) == 'math.sqrt(x)' + assert prntr.doprint(sqrt(x)) == 'math.sqrt(x)' + assert prntr.module_imports == {'math': {'pi', 'sqrt'}} + + assert prntr.doprint(acos(x)) == 'math.acos(x)' + assert prntr.doprint(cot(x)) == '(1/math.tan(x))' + assert prntr.doprint(coth(x)) == '((math.exp(x) + math.exp(-x))/(math.exp(x) - math.exp(-x)))' + assert prntr.doprint(asec(x)) == '(math.acos(1/x))' + assert prntr.doprint(acsch(x)) == '(math.log(math.sqrt(1 + x**(-2)) + 1/x))' + + assert prntr.doprint(Assignment(x, 2)) == 'x = 2' + assert prntr.doprint(Piecewise((1, Eq(x, 0)), + (2, x>6))) == '((1) if (x == 0) else (2) if (x > 6) else None)' + assert prntr.doprint(Piecewise((2, Le(x, 0)), + (3, Gt(x, 0)), evaluate=False)) == '((2) if (x <= 0) else'\ + ' (3) if (x > 0) else None)' + assert prntr.doprint(sign(x)) == '(0.0 if x == 0 else math.copysign(1, x))' + assert prntr.doprint(p[0, 1]) == 'p[0, 1]' + assert prntr.doprint(KroneckerDelta(x,y)) == '(1 if x == y else 0)' + + assert prntr.doprint((2,3)) == "(2, 3)" + assert prntr.doprint([2,3]) == "[2, 3]" + + assert prntr.doprint(Min(x, y)) == "min(x, y)" + assert prntr.doprint(Max(x, y)) == "max(x, y)" + + +def test_PythonCodePrinter_standard(): + prntr = PythonCodePrinter() + + assert prntr.standard == 'python3' + + raises(ValueError, lambda: PythonCodePrinter({'standard':'python4'})) + + +def test_CmathPrinter(): + p = CmathPrinter() + + assert p.doprint(sqrt(x)) == 'cmath.sqrt(x)' + assert p.doprint(log(x)) == 'cmath.log(x)' + + assert p.doprint(sin(x)) == 'cmath.sin(x)' + assert p.doprint(cos(x)) == 'cmath.cos(x)' + assert p.doprint(tan(x)) == 'cmath.tan(x)' + + assert p.doprint(asin(x)) == 'cmath.asin(x)' + assert p.doprint(acos(x)) == 'cmath.acos(x)' + assert p.doprint(atan(x)) == 'cmath.atan(x)' + + assert p.doprint(sinh(x)) == 'cmath.sinh(x)' + assert p.doprint(cosh(x)) == 'cmath.cosh(x)' + assert p.doprint(tanh(x)) == 'cmath.tanh(x)' + + assert p.doprint(asinh(x)) == 'cmath.asinh(x)' + assert p.doprint(acosh(x)) == 'cmath.acosh(x)' + assert p.doprint(atanh(x)) == 'cmath.atanh(x)' + + +def test_MpmathPrinter(): + p = MpmathPrinter() + assert p.doprint(sign(x)) == 'mpmath.sign(x)' + assert p.doprint(Rational(1, 2)) == 'mpmath.mpf(1)/mpmath.mpf(2)' + + assert p.doprint(S.Exp1) == 'mpmath.e' + assert p.doprint(S.Pi) == 'mpmath.pi' + assert p.doprint(S.GoldenRatio) == 'mpmath.phi' + assert p.doprint(S.EulerGamma) == 'mpmath.euler' + assert p.doprint(S.NaN) == 'mpmath.nan' + assert p.doprint(S.Infinity) == 'mpmath.inf' + assert p.doprint(S.NegativeInfinity) == 'mpmath.ninf' + assert p.doprint(loggamma(x)) == 'mpmath.loggamma(x)' + + +def test_NumPyPrinter(): + from sympy.core.function import Lambda + from sympy.matrices.expressions.adjoint import Adjoint + from sympy.matrices.expressions.diagonal import (DiagMatrix, DiagonalMatrix, DiagonalOf) + from sympy.matrices.expressions.funcmatrix import FunctionMatrix + from sympy.matrices.expressions.hadamard import HadamardProduct + from sympy.matrices.expressions.kronecker import KroneckerProduct + from sympy.matrices.expressions.special import (OneMatrix, ZeroMatrix) + from sympy.abc import a, b + p = NumPyPrinter() + assert p.doprint(sign(x)) == 'numpy.sign(x)' + A = MatrixSymbol("A", 2, 2) + B = MatrixSymbol("B", 2, 2) + C = MatrixSymbol("C", 1, 5) + D = MatrixSymbol("D", 3, 4) + assert p.doprint(A**(-1)) == "numpy.linalg.inv(A)" + assert p.doprint(A**5) == "numpy.linalg.matrix_power(A, 5)" + assert p.doprint(Identity(3)) == "numpy.eye(3)" + + u = MatrixSymbol('x', 2, 1) + v = MatrixSymbol('y', 2, 1) + assert p.doprint(MatrixSolve(A, u)) == 'numpy.linalg.solve(A, x)' + assert p.doprint(MatrixSolve(A, u) + v) == 'numpy.linalg.solve(A, x) + y' + + assert p.doprint(ZeroMatrix(2, 3)) == "numpy.zeros((2, 3))" + assert p.doprint(OneMatrix(2, 3)) == "numpy.ones((2, 3))" + assert p.doprint(FunctionMatrix(4, 5, Lambda((a, b), a + b))) == \ + "numpy.fromfunction(lambda a, b: a + b, (4, 5))" + assert p.doprint(HadamardProduct(A, B)) == "numpy.multiply(A, B)" + assert p.doprint(KroneckerProduct(A, B)) == "numpy.kron(A, B)" + assert p.doprint(Adjoint(A)) == "numpy.conjugate(numpy.transpose(A))" + assert p.doprint(DiagonalOf(A)) == "numpy.reshape(numpy.diag(A), (-1, 1))" + assert p.doprint(DiagMatrix(C)) == "numpy.diagflat(C)" + assert p.doprint(DiagonalMatrix(D)) == "numpy.multiply(D, numpy.eye(3, 4))" + + # Workaround for numpy negative integer power errors + assert p.doprint(x**-1) == 'x**(-1.0)' + assert p.doprint(x**-2) == 'x**(-2.0)' + + expr = Pow(2, -1, evaluate=False) + assert p.doprint(expr) == "2**(-1.0)" + + assert p.doprint(S.Exp1) == 'numpy.e' + assert p.doprint(S.Pi) == 'numpy.pi' + assert p.doprint(S.EulerGamma) == 'numpy.euler_gamma' + assert p.doprint(S.NaN) == 'numpy.nan' + assert p.doprint(S.Infinity) == 'numpy.inf' + assert p.doprint(S.NegativeInfinity) == '-numpy.inf' + + # Function rewriting operator precedence fix + assert p.doprint(sec(x)**2) == '(numpy.cos(x)**(-1.0))**2' + + +def test_issue_18770(): + numpy = import_module('numpy') + if not numpy: + skip("numpy not installed.") + + from sympy.functions.elementary.miscellaneous import (Max, Min) + from sympy.utilities.lambdify import lambdify + + expr1 = Min(0.1*x + 3, x + 1, 0.5*x + 1) + func = lambdify(x, expr1, "numpy") + assert (func(numpy.linspace(0, 3, 3)) == [1.0, 1.75, 2.5 ]).all() + assert func(4) == 3 + + expr1 = Max(x**2, x**3) + func = lambdify(x,expr1, "numpy") + assert (func(numpy.linspace(-1, 2, 4)) == [1, 0, 1, 8] ).all() + assert func(4) == 64 + + +def test_SciPyPrinter(): + p = SciPyPrinter() + expr = acos(x) + assert 'numpy' not in p.module_imports + assert p.doprint(expr) == 'numpy.arccos(x)' + assert 'numpy' in p.module_imports + assert not any(m.startswith('scipy') for m in p.module_imports) + smat = SparseMatrix(2, 5, {(0, 1): 3}) + assert p.doprint(smat) == \ + 'scipy.sparse.coo_matrix(([3], ([0], [1])), shape=(2, 5))' + assert 'scipy.sparse' in p.module_imports + + assert p.doprint(S.GoldenRatio) == 'scipy.constants.golden_ratio' + assert p.doprint(S.Pi) == 'scipy.constants.pi' + assert p.doprint(S.Exp1) == 'numpy.e' + + +def test_pycode_reserved_words(): + s1, s2 = symbols('if else') + raises(ValueError, lambda: pycode(s1 + s2, error_on_reserved=True)) + py_str = pycode(s1 + s2) + assert py_str in ('else_ + if_', 'if_ + else_') + + +def test_issue_20762(): + # Make sure pycode removes curly braces from subscripted variables + a_b, b, a_11 = symbols('a_{b} b a_{11}') + expr = a_b*b + assert pycode(expr) == 'a_b*b' + expr = a_11*b + assert pycode(expr) == 'a_11*b' + + +def test_sqrt(): + prntr = PythonCodePrinter() + assert prntr._print_Pow(sqrt(x), rational=False) == 'math.sqrt(x)' + assert prntr._print_Pow(1/sqrt(x), rational=False) == '1/math.sqrt(x)' + + prntr = PythonCodePrinter({'standard' : 'python3'}) + assert prntr._print_Pow(sqrt(x), rational=True) == 'x**(1/2)' + assert prntr._print_Pow(1/sqrt(x), rational=True) == 'x**(-1/2)' + + prntr = MpmathPrinter() + assert prntr._print_Pow(sqrt(x), rational=False) == 'mpmath.sqrt(x)' + assert prntr._print_Pow(sqrt(x), rational=True) == \ + "x**(mpmath.mpf(1)/mpmath.mpf(2))" + + prntr = NumPyPrinter() + assert prntr._print_Pow(sqrt(x), rational=False) == 'numpy.sqrt(x)' + assert prntr._print_Pow(sqrt(x), rational=True) == 'x**(1/2)' + + prntr = SciPyPrinter() + assert prntr._print_Pow(sqrt(x), rational=False) == 'numpy.sqrt(x)' + assert prntr._print_Pow(sqrt(x), rational=True) == 'x**(1/2)' + + prntr = SymPyPrinter() + assert prntr._print_Pow(sqrt(x), rational=False) == 'sympy.sqrt(x)' + assert prntr._print_Pow(sqrt(x), rational=True) == 'x**(1/2)' + + +def test_frac(): + from sympy.functions.elementary.integers import frac + + expr = frac(x) + prntr = NumPyPrinter() + assert prntr.doprint(expr) == 'numpy.mod(x, 1)' + + prntr = SciPyPrinter() + assert prntr.doprint(expr) == 'numpy.mod(x, 1)' + + prntr = PythonCodePrinter() + assert prntr.doprint(expr) == 'x % 1' + + prntr = MpmathPrinter() + assert prntr.doprint(expr) == 'mpmath.frac(x)' + + prntr = SymPyPrinter() + assert prntr.doprint(expr) == 'sympy.functions.elementary.integers.frac(x)' + + +class CustomPrintedObject(Expr): + def _numpycode(self, printer): + return 'numpy' + + def _mpmathcode(self, printer): + return 'mpmath' + + +def test_printmethod(): + obj = CustomPrintedObject() + assert NumPyPrinter().doprint(obj) == 'numpy' + assert MpmathPrinter().doprint(obj) == 'mpmath' + + +def test_codegen_ast_nodes(): + assert pycode(none) == 'None' + + +def test_issue_14283(): + prntr = PythonCodePrinter() + + assert prntr.doprint(zoo) == "math.nan" + assert prntr.doprint(-oo) == "float('-inf')" + + +def test_NumPyPrinter_print_seq(): + n = NumPyPrinter() + + assert n._print_seq(range(2)) == '(0, 1,)' + + +def test_issue_16535_16536(): + from sympy.functions.special.gamma_functions import (lowergamma, uppergamma) + + a = symbols('a') + expr1 = lowergamma(a, x) + expr2 = uppergamma(a, x) + + prntr = SciPyPrinter() + assert prntr.doprint(expr1) == 'scipy.special.gamma(a)*scipy.special.gammainc(a, x)' + assert prntr.doprint(expr2) == 'scipy.special.gamma(a)*scipy.special.gammaincc(a, x)' + + p_numpy = NumPyPrinter() + p_pycode = PythonCodePrinter({'strict': False}) + + for expr in [expr1, expr2]: + with raises(NotImplementedError): + p_numpy.doprint(expr1) + assert "Not supported" in p_pycode.doprint(expr) + + +def test_Integral(): + from sympy.functions.elementary.exponential import exp + from sympy.integrals.integrals import Integral + + single = Integral(exp(-x), (x, 0, oo)) + double = Integral(x**2*exp(x*y), (x, -z, z), (y, 0, z)) + indefinite = Integral(x**2, x) + evaluateat = Integral(x**2, (x, 1)) + + prntr = SciPyPrinter() + assert prntr.doprint(single) == 'scipy.integrate.quad(lambda x: numpy.exp(-x), 0, numpy.inf)[0]' + assert prntr.doprint(double) == 'scipy.integrate.nquad(lambda x, y: x**2*numpy.exp(x*y), ((-z, z), (0, z)))[0]' + raises(NotImplementedError, lambda: prntr.doprint(indefinite)) + raises(NotImplementedError, lambda: prntr.doprint(evaluateat)) + + prntr = MpmathPrinter() + assert prntr.doprint(single) == 'mpmath.quad(lambda x: mpmath.exp(-x), (0, mpmath.inf))' + assert prntr.doprint(double) == 'mpmath.quad(lambda x, y: x**2*mpmath.exp(x*y), (-z, z), (0, z))' + raises(NotImplementedError, lambda: prntr.doprint(indefinite)) + raises(NotImplementedError, lambda: prntr.doprint(evaluateat)) + + +def test_fresnel_integrals(): + from sympy.functions.special.error_functions import (fresnelc, fresnels) + + expr1 = fresnelc(x) + expr2 = fresnels(x) + + prntr = SciPyPrinter() + assert prntr.doprint(expr1) == 'scipy.special.fresnel(x)[1]' + assert prntr.doprint(expr2) == 'scipy.special.fresnel(x)[0]' + + p_numpy = NumPyPrinter() + p_pycode = PythonCodePrinter() + p_mpmath = MpmathPrinter() + for expr in [expr1, expr2]: + with raises(NotImplementedError): + p_numpy.doprint(expr) + with raises(NotImplementedError): + p_pycode.doprint(expr) + + assert p_mpmath.doprint(expr1) == 'mpmath.fresnelc(x)' + assert p_mpmath.doprint(expr2) == 'mpmath.fresnels(x)' + + +def test_beta(): + from sympy.functions.special.beta_functions import beta + + expr = beta(x, y) + + prntr = SciPyPrinter() + assert prntr.doprint(expr) == 'scipy.special.beta(x, y)' + + prntr = NumPyPrinter() + assert prntr.doprint(expr) == '(math.gamma(x)*math.gamma(y)/math.gamma(x + y))' + + prntr = PythonCodePrinter() + assert prntr.doprint(expr) == '(math.gamma(x)*math.gamma(y)/math.gamma(x + y))' + + prntr = PythonCodePrinter({'allow_unknown_functions': True}) + assert prntr.doprint(expr) == '(math.gamma(x)*math.gamma(y)/math.gamma(x + y))' + + prntr = MpmathPrinter() + assert prntr.doprint(expr) == 'mpmath.beta(x, y)' + +def test_airy(): + from sympy.functions.special.bessel import (airyai, airybi) + + expr1 = airyai(x) + expr2 = airybi(x) + + prntr = SciPyPrinter() + assert prntr.doprint(expr1) == 'scipy.special.airy(x)[0]' + assert prntr.doprint(expr2) == 'scipy.special.airy(x)[2]' + + prntr = NumPyPrinter({'strict': False}) + assert "Not supported" in prntr.doprint(expr1) + assert "Not supported" in prntr.doprint(expr2) + + prntr = PythonCodePrinter({'strict': False}) + assert "Not supported" in prntr.doprint(expr1) + assert "Not supported" in prntr.doprint(expr2) + +def test_airy_prime(): + from sympy.functions.special.bessel import (airyaiprime, airybiprime) + + expr1 = airyaiprime(x) + expr2 = airybiprime(x) + + prntr = SciPyPrinter() + assert prntr.doprint(expr1) == 'scipy.special.airy(x)[1]' + assert prntr.doprint(expr2) == 'scipy.special.airy(x)[3]' + + prntr = NumPyPrinter({'strict': False}) + assert "Not supported" in prntr.doprint(expr1) + assert "Not supported" in prntr.doprint(expr2) + + prntr = PythonCodePrinter({'strict': False}) + assert "Not supported" in prntr.doprint(expr1) + assert "Not supported" in prntr.doprint(expr2) + + +def test_numerical_accuracy_functions(): + prntr = SciPyPrinter() + assert prntr.doprint(expm1(x)) == 'numpy.expm1(x)' + assert prntr.doprint(log1p(x)) == 'numpy.log1p(x)' + assert prntr.doprint(cosm1(x)) == 'scipy.special.cosm1(x)' + +def test_array_printer(): + A = ArraySymbol('A', (4,4,6,6,6)) + I = IndexedBase('I') + i,j,k = Idx('i', (0,1)), Idx('j', (2,3)), Idx('k', (4,5)) + + prntr = NumPyPrinter() + assert prntr.doprint(ZeroArray(5)) == 'numpy.zeros((5,))' + assert prntr.doprint(OneArray(5)) == 'numpy.ones((5,))' + assert prntr.doprint(ArrayContraction(A, [2,3])) == 'numpy.einsum("abccd->abd", A)' + assert prntr.doprint(I) == 'I' + assert prntr.doprint(ArrayDiagonal(A, [2,3,4])) == 'numpy.einsum("abccc->abc", A)' + assert prntr.doprint(ArrayDiagonal(A, [0,1], [2,3])) == 'numpy.einsum("aabbc->cab", A)' + assert prntr.doprint(ArrayContraction(A, [2], [3])) == 'numpy.einsum("abcde->abe", A)' + assert prntr.doprint(Assignment(I[i,j,k], I[i,j,k])) == 'I = I' + + prntr = TensorflowPrinter() + assert prntr.doprint(ZeroArray(5)) == 'tensorflow.zeros((5,))' + assert prntr.doprint(OneArray(5)) == 'tensorflow.ones((5,))' + assert prntr.doprint(ArrayContraction(A, [2,3])) == 'tensorflow.linalg.einsum("abccd->abd", A)' + assert prntr.doprint(I) == 'I' + assert prntr.doprint(ArrayDiagonal(A, [2,3,4])) == 'tensorflow.linalg.einsum("abccc->abc", A)' + assert prntr.doprint(ArrayDiagonal(A, [0,1], [2,3])) == 'tensorflow.linalg.einsum("aabbc->cab", A)' + assert prntr.doprint(ArrayContraction(A, [2], [3])) == 'tensorflow.linalg.einsum("abcde->abe", A)' + assert prntr.doprint(Assignment(I[i,j,k], I[i,j,k])) == 'I = I' + + +def test_custom_Derivative_methods(): + class MyPrinter(SciPyPrinter): + def _print_Derivative_cosm1(self, args, seq_orders): + arg, = args + order, = seq_orders + return 'my_custom_cosm1(%s, deriv_order=%d)' % (self._print(arg), order) + + def _print_Derivative_atan2(self, args, seq_orders): + arg1, arg2 = args + ord1, ord2 = seq_orders + return 'my_custom_atan2(%s, %s, deriv1=%d, deriv2=%d)' % ( + self._print(arg1), self._print(arg2), ord1, ord2 + ) + + p = MyPrinter() + cosm1_1 = cosm1(x).diff(x, evaluate=False) + assert p.doprint(cosm1_1) == 'my_custom_cosm1(x, deriv_order=1)' + atan2_2_3 = atan2(x, y).diff(x, 2, y, 3, evaluate=False) + assert p.doprint(atan2_2_3) == 'my_custom_atan2(x, y, deriv1=2, deriv2=3)' + + try: + p.doprint(expm1(x).diff(x, evaluate=False)) + except PrintMethodNotImplementedError as e: + assert '_print_Derivative_expm1' in repr(e) + else: + assert False # should have thrown + + try: + p.doprint(Derivative(cosm1(x**2),x)) + except ValueError as e: + assert '_print_Derivative(' in repr(e) + else: + assert False # should have thrown diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_python.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_python.py new file mode 100644 index 0000000000000000000000000000000000000000..fb94a662be90934a672d08b3de44a22e2580d8b6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_python.py @@ -0,0 +1,203 @@ +from sympy.core.function import (Derivative, Function) +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne) +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (Abs, conjugate) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin +from sympy.integrals.integrals import Integral +from sympy.matrices.dense import Matrix +from sympy.series.limits import limit + +from sympy.printing.python import python + +from sympy.testing.pytest import raises, XFAIL + +x, y = symbols('x,y') +th = Symbol('theta') +ph = Symbol('phi') + + +def test_python_basic(): + # Simple numbers/symbols + assert python(-Rational(1)/2) == "e = Rational(-1, 2)" + assert python(-Rational(13)/22) == "e = Rational(-13, 22)" + assert python(oo) == "e = oo" + + # Powers + assert python(x**2) == "x = Symbol(\'x\')\ne = x**2" + assert python(1/x) == "x = Symbol('x')\ne = 1/x" + assert python(y*x**-2) == "y = Symbol('y')\nx = Symbol('x')\ne = y/x**2" + assert python( + x**Rational(-5, 2)) == "x = Symbol('x')\ne = x**Rational(-5, 2)" + + # Sums of terms + assert python(x**2 + x + 1) in [ + "x = Symbol('x')\ne = 1 + x + x**2", + "x = Symbol('x')\ne = x + x**2 + 1", + "x = Symbol('x')\ne = x**2 + x + 1", ] + assert python(1 - x) in [ + "x = Symbol('x')\ne = 1 - x", + "x = Symbol('x')\ne = -x + 1"] + assert python(1 - 2*x) in [ + "x = Symbol('x')\ne = 1 - 2*x", + "x = Symbol('x')\ne = -2*x + 1"] + assert python(1 - Rational(3, 2)*y/x) in [ + "y = Symbol('y')\nx = Symbol('x')\ne = 1 - 3/2*y/x", + "y = Symbol('y')\nx = Symbol('x')\ne = -3/2*y/x + 1", + "y = Symbol('y')\nx = Symbol('x')\ne = 1 - 3*y/(2*x)"] + + # Multiplication + assert python(x/y) == "x = Symbol('x')\ny = Symbol('y')\ne = x/y" + assert python(-x/y) == "x = Symbol('x')\ny = Symbol('y')\ne = -x/y" + assert python((x + 2)/y) in [ + "y = Symbol('y')\nx = Symbol('x')\ne = 1/y*(2 + x)", + "y = Symbol('y')\nx = Symbol('x')\ne = 1/y*(x + 2)", + "x = Symbol('x')\ny = Symbol('y')\ne = 1/y*(2 + x)", + "x = Symbol('x')\ny = Symbol('y')\ne = (2 + x)/y", + "x = Symbol('x')\ny = Symbol('y')\ne = (x + 2)/y"] + assert python((1 + x)*y) in [ + "y = Symbol('y')\nx = Symbol('x')\ne = y*(1 + x)", + "y = Symbol('y')\nx = Symbol('x')\ne = y*(x + 1)", ] + + # Check for proper placement of negative sign + assert python(-5*x/(x + 10)) == "x = Symbol('x')\ne = -5*x/(x + 10)" + assert python(1 - Rational(3, 2)*(x + 1)) in [ + "x = Symbol('x')\ne = Rational(-3, 2)*x + Rational(-1, 2)", + "x = Symbol('x')\ne = -3*x/2 + Rational(-1, 2)", + "x = Symbol('x')\ne = -3*x/2 + Rational(-1, 2)" + ] + + +def test_python_keyword_symbol_name_escaping(): + # Check for escaping of keywords + assert python( + 5*Symbol("lambda")) == "lambda_ = Symbol('lambda')\ne = 5*lambda_" + assert (python(5*Symbol("lambda") + 7*Symbol("lambda_")) == + "lambda__ = Symbol('lambda')\nlambda_ = Symbol('lambda_')\ne = 7*lambda_ + 5*lambda__") + assert (python(5*Symbol("for") + Function("for_")(8)) == + "for__ = Symbol('for')\nfor_ = Function('for_')\ne = 5*for__ + for_(8)") + + +def test_python_keyword_function_name_escaping(): + assert python( + 5*Function("for")(8)) == "for_ = Function('for')\ne = 5*for_(8)" + + +def test_python_relational(): + assert python(Eq(x, y)) == "x = Symbol('x')\ny = Symbol('y')\ne = Eq(x, y)" + assert python(Ge(x, y)) == "x = Symbol('x')\ny = Symbol('y')\ne = x >= y" + assert python(Le(x, y)) == "x = Symbol('x')\ny = Symbol('y')\ne = x <= y" + assert python(Gt(x, y)) == "x = Symbol('x')\ny = Symbol('y')\ne = x > y" + assert python(Lt(x, y)) == "x = Symbol('x')\ny = Symbol('y')\ne = x < y" + assert python(Ne(x/(y + 1), y**2)) in [ + "x = Symbol('x')\ny = Symbol('y')\ne = Ne(x/(1 + y), y**2)", + "x = Symbol('x')\ny = Symbol('y')\ne = Ne(x/(y + 1), y**2)"] + + +def test_python_functions(): + # Simple + assert python(2*x + exp(x)) in "x = Symbol('x')\ne = 2*x + exp(x)" + assert python(sqrt(2)) == 'e = sqrt(2)' + assert python(2**Rational(1, 3)) == 'e = 2**Rational(1, 3)' + assert python(sqrt(2 + pi)) == 'e = sqrt(2 + pi)' + assert python((2 + pi)**Rational(1, 3)) == 'e = (2 + pi)**Rational(1, 3)' + assert python(2**Rational(1, 4)) == 'e = 2**Rational(1, 4)' + assert python(Abs(x)) == "x = Symbol('x')\ne = Abs(x)" + assert python( + Abs(x/(x**2 + 1))) in ["x = Symbol('x')\ne = Abs(x/(1 + x**2))", + "x = Symbol('x')\ne = Abs(x/(x**2 + 1))"] + + # Univariate/Multivariate functions + f = Function('f') + assert python(f(x)) == "x = Symbol('x')\nf = Function('f')\ne = f(x)" + assert python(f(x, y)) == "x = Symbol('x')\ny = Symbol('y')\nf = Function('f')\ne = f(x, y)" + assert python(f(x/(y + 1), y)) in [ + "x = Symbol('x')\ny = Symbol('y')\nf = Function('f')\ne = f(x/(1 + y), y)", + "x = Symbol('x')\ny = Symbol('y')\nf = Function('f')\ne = f(x/(y + 1), y)"] + + # Nesting of square roots + assert python(sqrt((sqrt(x + 1)) + 1)) in [ + "x = Symbol('x')\ne = sqrt(1 + sqrt(1 + x))", + "x = Symbol('x')\ne = sqrt(sqrt(x + 1) + 1)"] + + # Nesting of powers + assert python((((x + 1)**Rational(1, 3)) + 1)**Rational(1, 3)) in [ + "x = Symbol('x')\ne = (1 + (1 + x)**Rational(1, 3))**Rational(1, 3)", + "x = Symbol('x')\ne = ((x + 1)**Rational(1, 3) + 1)**Rational(1, 3)"] + + # Function powers + assert python(sin(x)**2) == "x = Symbol('x')\ne = sin(x)**2" + + +@XFAIL +def test_python_functions_conjugates(): + a, b = map(Symbol, 'ab') + assert python( conjugate(a + b*I) ) == '_ _\na - I*b' + assert python( conjugate(exp(a + b*I)) ) == ' _ _\n a - I*b\ne ' + + +def test_python_derivatives(): + # Simple + f_1 = Derivative(log(x), x, evaluate=False) + assert python(f_1) == "x = Symbol('x')\ne = Derivative(log(x), x)" + + f_2 = Derivative(log(x), x, evaluate=False) + x + assert python(f_2) == "x = Symbol('x')\ne = x + Derivative(log(x), x)" + + # Multiple symbols + f_3 = Derivative(log(x) + x**2, x, y, evaluate=False) + assert python(f_3) == \ + "x = Symbol('x')\ny = Symbol('y')\ne = Derivative(x**2 + log(x), x, y)" + + f_4 = Derivative(2*x*y, y, x, evaluate=False) + x**2 + assert python(f_4) in [ + "x = Symbol('x')\ny = Symbol('y')\ne = x**2 + Derivative(2*x*y, y, x)", + "x = Symbol('x')\ny = Symbol('y')\ne = Derivative(2*x*y, y, x) + x**2"] + + +def test_python_integrals(): + # Simple + f_1 = Integral(log(x), x) + assert python(f_1) == "x = Symbol('x')\ne = Integral(log(x), x)" + + f_2 = Integral(x**2, x) + assert python(f_2) == "x = Symbol('x')\ne = Integral(x**2, x)" + + # Double nesting of pow + f_3 = Integral(x**(2**x), x) + assert python(f_3) == "x = Symbol('x')\ne = Integral(x**(2**x), x)" + + # Definite integrals + f_4 = Integral(x**2, (x, 1, 2)) + assert python(f_4) == "x = Symbol('x')\ne = Integral(x**2, (x, 1, 2))" + + f_5 = Integral(x**2, (x, Rational(1, 2), 10)) + assert python( + f_5) == "x = Symbol('x')\ne = Integral(x**2, (x, Rational(1, 2), 10))" + + # Nested integrals + f_6 = Integral(x**2*y**2, x, y) + assert python(f_6) == "x = Symbol('x')\ny = Symbol('y')\ne = Integral(x**2*y**2, x, y)" + + +def test_python_matrix(): + p = python(Matrix([[x**2+1, 1], [y, x+y]])) + s = "x = Symbol('x')\ny = Symbol('y')\ne = MutableDenseMatrix([[x**2 + 1, 1], [y, x + y]])" + assert p == s + +def test_python_limits(): + assert python(limit(x, x, oo)) == 'e = oo' + assert python(limit(x**2, x, 0)) == 'e = 0' + +def test_issue_20762(): + # Make sure Python removes curly braces from subscripted variables + a_b = Symbol('a_{b}') + b = Symbol('b') + expr = a_b*b + assert python(expr) == "a_b = Symbol('a_{b}')\nb = Symbol('b')\ne = a_b*b" + + +def test_settings(): + raises(TypeError, lambda: python(x, method="garbage")) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_rcode.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_rcode.py new file mode 100644 index 0000000000000000000000000000000000000000..a83235b0654c6bf24c30846dbf68678d29cd3c80 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_rcode.py @@ -0,0 +1,476 @@ +from sympy.core import (S, pi, oo, Symbol, symbols, Rational, Integer, + GoldenRatio, EulerGamma, Catalan, Lambda, Dummy) +from sympy.functions import (Piecewise, sin, cos, Abs, exp, ceiling, sqrt, + gamma, sign, Max, Min, factorial, beta) +from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne) +from sympy.sets import Range +from sympy.logic import ITE +from sympy.codegen import For, aug_assign, Assignment +from sympy.testing.pytest import raises +from sympy.printing.rcode import RCodePrinter +from sympy.utilities.lambdify import implemented_function +from sympy.tensor import IndexedBase, Idx +from sympy.matrices import Matrix, MatrixSymbol + +from sympy.printing.rcode import rcode + +x, y, z = symbols('x,y,z') + + +def test_printmethod(): + class fabs(Abs): + def _rcode(self, printer): + return "abs(%s)" % printer._print(self.args[0]) + + assert rcode(fabs(x)) == "abs(x)" + + +def test_rcode_sqrt(): + assert rcode(sqrt(x)) == "sqrt(x)" + assert rcode(x**0.5) == "sqrt(x)" + assert rcode(sqrt(x)) == "sqrt(x)" + + +def test_rcode_Pow(): + assert rcode(x**3) == "x^3" + assert rcode(x**(y**3)) == "x^(y^3)" + g = implemented_function('g', Lambda(x, 2*x)) + assert rcode(1/(g(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + "(3.5*2*x)^(-x + y^x)/(x^2 + y)" + assert rcode(x**-1.0) == '1.0/x' + assert rcode(x**Rational(2, 3)) == 'x^(2.0/3.0)' + _cond_cfunc = [(lambda base, exp: exp.is_integer, "dpowi"), + (lambda base, exp: not exp.is_integer, "pow")] + assert rcode(x**3, user_functions={'Pow': _cond_cfunc}) == 'dpowi(x, 3)' + assert rcode(x**3.2, user_functions={'Pow': _cond_cfunc}) == 'pow(x, 3.2)' + + +def test_rcode_Max(): + # Test for gh-11926 + assert rcode(Max(x,x*x),user_functions={"Max":"my_max", "Pow":"my_pow"}) == 'my_max(x, my_pow(x, 2))' + + +def test_rcode_constants_mathh(): + assert rcode(exp(1)) == "exp(1)" + assert rcode(pi) == "pi" + assert rcode(oo) == "Inf" + assert rcode(-oo) == "-Inf" + + +def test_rcode_constants_other(): + assert rcode(2*GoldenRatio) == "GoldenRatio = 1.61803398874989;\n2*GoldenRatio" + assert rcode( + 2*Catalan) == "Catalan = 0.915965594177219;\n2*Catalan" + assert rcode(2*EulerGamma) == "EulerGamma = 0.577215664901533;\n2*EulerGamma" + + +def test_rcode_Rational(): + assert rcode(Rational(3, 7)) == "3.0/7.0" + assert rcode(Rational(18, 9)) == "2" + assert rcode(Rational(3, -7)) == "-3.0/7.0" + assert rcode(Rational(-3, -7)) == "3.0/7.0" + assert rcode(x + Rational(3, 7)) == "x + 3.0/7.0" + assert rcode(Rational(3, 7)*x) == "(3.0/7.0)*x" + + +def test_rcode_Integer(): + assert rcode(Integer(67)) == "67" + assert rcode(Integer(-1)) == "-1" + + +def test_rcode_functions(): + assert rcode(sin(x) ** cos(x)) == "sin(x)^cos(x)" + assert rcode(factorial(x) + gamma(y)) == "factorial(x) + gamma(y)" + assert rcode(beta(Min(x, y), Max(x, y))) == "beta(min(x, y), max(x, y))" + + +def test_rcode_inline_function(): + x = symbols('x') + g = implemented_function('g', Lambda(x, 2*x)) + assert rcode(g(x)) == "2*x" + g = implemented_function('g', Lambda(x, 2*x/Catalan)) + assert rcode( + g(x)) == "Catalan = %s;\n2*x/Catalan" % Catalan.n() + A = IndexedBase('A') + i = Idx('i', symbols('n', integer=True)) + g = implemented_function('g', Lambda(x, x*(1 + x)*(2 + x))) + res=rcode(g(A[i]), assign_to=A[i]) + ref=( + "for (i in 1:n){\n" + " A[i] = (A[i] + 1)*(A[i] + 2)*A[i];\n" + "}" + ) + assert res == ref + + +def test_rcode_exceptions(): + assert rcode(ceiling(x)) == "ceiling(x)" + assert rcode(Abs(x)) == "abs(x)" + assert rcode(gamma(x)) == "gamma(x)" + + +def test_rcode_user_functions(): + x = symbols('x', integer=False) + n = symbols('n', integer=True) + custom_functions = { + "ceiling": "myceil", + "Abs": [(lambda x: not x.is_integer, "fabs"), (lambda x: x.is_integer, "abs")], + } + assert rcode(ceiling(x), user_functions=custom_functions) == "myceil(x)" + assert rcode(Abs(x), user_functions=custom_functions) == "fabs(x)" + assert rcode(Abs(n), user_functions=custom_functions) == "abs(n)" + + +def test_rcode_boolean(): + assert rcode(True) == "True" + assert rcode(S.true) == "True" + assert rcode(False) == "False" + assert rcode(S.false) == "False" + assert rcode(x & y) == "x & y" + assert rcode(x | y) == "x | y" + assert rcode(~x) == "!x" + assert rcode(x & y & z) == "x & y & z" + assert rcode(x | y | z) == "x | y | z" + assert rcode((x & y) | z) == "z | x & y" + assert rcode((x | y) & z) == "z & (x | y)" + +def test_rcode_Relational(): + assert rcode(Eq(x, y)) == "x == y" + assert rcode(Ne(x, y)) == "x != y" + assert rcode(Le(x, y)) == "x <= y" + assert rcode(Lt(x, y)) == "x < y" + assert rcode(Gt(x, y)) == "x > y" + assert rcode(Ge(x, y)) == "x >= y" + + +def test_rcode_Piecewise(): + expr = Piecewise((x, x < 1), (x**2, True)) + res=rcode(expr) + ref="ifelse(x < 1,x,x^2)" + assert res == ref + tau=Symbol("tau") + res=rcode(expr,tau) + ref="tau = ifelse(x < 1,x,x^2);" + assert res == ref + + expr = 2*Piecewise((x, x < 1), (x**2, x<2), (x**3,True)) + assert rcode(expr) == "2*ifelse(x < 1,x,ifelse(x < 2,x^2,x^3))" + res = rcode(expr, assign_to='c') + assert res == "c = 2*ifelse(x < 1,x,ifelse(x < 2,x^2,x^3));" + + # Check that Piecewise without a True (default) condition error + #expr = Piecewise((x, x < 1), (x**2, x > 1), (sin(x), x > 0)) + #raises(ValueError, lambda: rcode(expr)) + expr = 2*Piecewise((x, x < 1), (x**2, x<2)) + assert(rcode(expr))== "2*ifelse(x < 1,x,ifelse(x < 2,x^2,NA))" + + +def test_rcode_sinc(): + from sympy.functions.elementary.trigonometric import sinc + expr = sinc(x) + res = rcode(expr) + ref = "(ifelse(x != 0,sin(x)/x,1))" + assert res == ref + + +def test_rcode_Piecewise_deep(): + p = rcode(2*Piecewise((x, x < 1), (x + 1, x < 2), (x**2, True))) + assert p == "2*ifelse(x < 1,x,ifelse(x < 2,x + 1,x^2))" + expr = x*y*z + x**2 + y**2 + Piecewise((0, x < 0.5), (1, True)) + cos(z) - 1 + p = rcode(expr) + ref="x^2 + x*y*z + y^2 + ifelse(x < 0.5,0,1) + cos(z) - 1" + assert p == ref + + ref="c = x^2 + x*y*z + y^2 + ifelse(x < 0.5,0,1) + cos(z) - 1;" + p = rcode(expr, assign_to='c') + assert p == ref + + +def test_rcode_ITE(): + expr = ITE(x < 1, y, z) + p = rcode(expr) + ref="ifelse(x < 1,y,z)" + assert p == ref + + +def test_rcode_settings(): + raises(TypeError, lambda: rcode(sin(x), method="garbage")) + + +def test_rcode_Indexed(): + n, m, o = symbols('n m o', integer=True) + i, j, k = Idx('i', n), Idx('j', m), Idx('k', o) + p = RCodePrinter() + p._not_r = set() + + x = IndexedBase('x')[j] + assert p._print_Indexed(x) == 'x[j]' + A = IndexedBase('A')[i, j] + assert p._print_Indexed(A) == 'A[i, j]' + B = IndexedBase('B')[i, j, k] + assert p._print_Indexed(B) == 'B[i, j, k]' + + assert p._not_r == set() + +def test_rcode_Indexed_without_looking_for_contraction(): + len_y = 5 + y = IndexedBase('y', shape=(len_y,)) + x = IndexedBase('x', shape=(len_y,)) + Dy = IndexedBase('Dy', shape=(len_y-1,)) + i = Idx('i', len_y-1) + e=Eq(Dy[i], (y[i+1]-y[i])/(x[i+1]-x[i])) + code0 = rcode(e.rhs, assign_to=e.lhs, contract=False) + assert code0 == 'Dy[i] = (y[%s] - y[i])/(x[%s] - x[i]);' % (i + 1, i + 1) + + +def test_rcode_loops_matrix_vector(): + n, m = symbols('n m', integer=True) + A = IndexedBase('A') + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + + s = ( + 'for (i in 1:m){\n' + ' y[i] = 0;\n' + '}\n' + 'for (i in 1:m){\n' + ' for (j in 1:n){\n' + ' y[i] = A[i, j]*x[j] + y[i];\n' + ' }\n' + '}' + ) + c = rcode(A[i, j]*x[j], assign_to=y[i]) + assert c == s + + +def test_dummy_loops(): + # the following line could also be + # [Dummy(s, integer=True) for s in 'im'] + # or [Dummy(integer=True) for s in 'im'] + i, m = symbols('i m', integer=True, cls=Dummy) + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx(i, m) + + expected = ( + 'for (i_%(icount)i in 1:m_%(mcount)i){\n' + ' y[i_%(icount)i] = x[i_%(icount)i];\n' + '}' + ) % {'icount': i.label.dummy_index, 'mcount': m.dummy_index} + code = rcode(x[i], assign_to=y[i]) + assert code == expected + + +def test_rcode_loops_add(): + n, m = symbols('n m', integer=True) + A = IndexedBase('A') + x = IndexedBase('x') + y = IndexedBase('y') + z = IndexedBase('z') + i = Idx('i', m) + j = Idx('j', n) + + s = ( + 'for (i in 1:m){\n' + ' y[i] = x[i] + z[i];\n' + '}\n' + 'for (i in 1:m){\n' + ' for (j in 1:n){\n' + ' y[i] = A[i, j]*x[j] + y[i];\n' + ' }\n' + '}' + ) + c = rcode(A[i, j]*x[j] + x[i] + z[i], assign_to=y[i]) + assert c == s + + +def test_rcode_loops_multiple_contractions(): + n, m, o, p = symbols('n m o p', integer=True) + a = IndexedBase('a') + b = IndexedBase('b') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + k = Idx('k', o) + l = Idx('l', p) + + s = ( + 'for (i in 1:m){\n' + ' y[i] = 0;\n' + '}\n' + 'for (i in 1:m){\n' + ' for (j in 1:n){\n' + ' for (k in 1:o){\n' + ' for (l in 1:p){\n' + ' y[i] = a[i, j, k, l]*b[j, k, l] + y[i];\n' + ' }\n' + ' }\n' + ' }\n' + '}' + ) + c = rcode(b[j, k, l]*a[i, j, k, l], assign_to=y[i]) + assert c == s + + +def test_rcode_loops_addfactor(): + n, m, o, p = symbols('n m o p', integer=True) + a = IndexedBase('a') + b = IndexedBase('b') + c = IndexedBase('c') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + k = Idx('k', o) + l = Idx('l', p) + + s = ( + 'for (i in 1:m){\n' + ' y[i] = 0;\n' + '}\n' + 'for (i in 1:m){\n' + ' for (j in 1:n){\n' + ' for (k in 1:o){\n' + ' for (l in 1:p){\n' + ' y[i] = (a[i, j, k, l] + b[i, j, k, l])*c[j, k, l] + y[i];\n' + ' }\n' + ' }\n' + ' }\n' + '}' + ) + c = rcode((a[i, j, k, l] + b[i, j, k, l])*c[j, k, l], assign_to=y[i]) + assert c == s + + +def test_rcode_loops_multiple_terms(): + n, m, o, p = symbols('n m o p', integer=True) + a = IndexedBase('a') + b = IndexedBase('b') + c = IndexedBase('c') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + k = Idx('k', o) + + s0 = ( + 'for (i in 1:m){\n' + ' y[i] = 0;\n' + '}\n' + ) + s1 = ( + 'for (i in 1:m){\n' + ' for (j in 1:n){\n' + ' for (k in 1:o){\n' + ' y[i] = b[j]*b[k]*c[i, j, k] + y[i];\n' + ' }\n' + ' }\n' + '}\n' + ) + s2 = ( + 'for (i in 1:m){\n' + ' for (k in 1:o){\n' + ' y[i] = a[i, k]*b[k] + y[i];\n' + ' }\n' + '}\n' + ) + s3 = ( + 'for (i in 1:m){\n' + ' for (j in 1:n){\n' + ' y[i] = a[i, j]*b[j] + y[i];\n' + ' }\n' + '}\n' + ) + c = rcode( + b[j]*a[i, j] + b[k]*a[i, k] + b[j]*b[k]*c[i, j, k], assign_to=y[i]) + + ref={} + ref[0] = s0 + s1 + s2 + s3[:-1] + ref[1] = s0 + s1 + s3 + s2[:-1] + ref[2] = s0 + s2 + s1 + s3[:-1] + ref[3] = s0 + s2 + s3 + s1[:-1] + ref[4] = s0 + s3 + s1 + s2[:-1] + ref[5] = s0 + s3 + s2 + s1[:-1] + + assert (c == ref[0] or + c == ref[1] or + c == ref[2] or + c == ref[3] or + c == ref[4] or + c == ref[5]) + + +def test_dereference_printing(): + expr = x + y + sin(z) + z + assert rcode(expr, dereference=[z]) == "x + y + (*z) + sin((*z))" + + +def test_Matrix_printing(): + # Test returning a Matrix + mat = Matrix([x*y, Piecewise((2 + x, y>0), (y, True)), sin(z)]) + A = MatrixSymbol('A', 3, 1) + p = rcode(mat, A) + assert p == ( + "A[0] = x*y;\n" + "A[1] = ifelse(y > 0,x + 2,y);\n" + "A[2] = sin(z);") + # Test using MatrixElements in expressions + expr = Piecewise((2*A[2, 0], x > 0), (A[2, 0], True)) + sin(A[1, 0]) + A[0, 0] + p = rcode(expr) + assert p == ("ifelse(x > 0,2*A[2],A[2]) + sin(A[1]) + A[0]") + # Test using MatrixElements in a Matrix + q = MatrixSymbol('q', 5, 1) + M = MatrixSymbol('M', 3, 3) + m = Matrix([[sin(q[1,0]), 0, cos(q[2,0])], + [q[1,0] + q[2,0], q[3, 0], 5], + [2*q[4, 0]/q[1,0], sqrt(q[0,0]) + 4, 0]]) + assert rcode(m, M) == ( + "M[0] = sin(q[1]);\n" + "M[1] = 0;\n" + "M[2] = cos(q[2]);\n" + "M[3] = q[1] + q[2];\n" + "M[4] = q[3];\n" + "M[5] = 5;\n" + "M[6] = 2*q[4]/q[1];\n" + "M[7] = sqrt(q[0]) + 4;\n" + "M[8] = 0;") + + +def test_rcode_sgn(): + + expr = sign(x) * y + assert rcode(expr) == 'y*sign(x)' + p = rcode(expr, 'z') + assert p == 'z = y*sign(x);' + + p = rcode(sign(2 * x + x**2) * x + x**2) + assert p == "x^2 + x*sign(x^2 + 2*x)" + + expr = sign(cos(x)) + p = rcode(expr) + assert p == 'sign(cos(x))' + +def test_rcode_Assignment(): + assert rcode(Assignment(x, y + z)) == 'x = y + z;' + assert rcode(aug_assign(x, '+', y + z)) == 'x += y + z;' + + +def test_rcode_For(): + f = For(x, Range(0, 10, 2), [aug_assign(y, '*', x)]) + sol = rcode(f) + assert sol == ("for(x in seq(from=0, to=9, by=2){\n" + " y *= x;\n" + "}") + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert(rcode(A[0, 0]) == "A[0]") + assert(rcode(3 * A[0, 0]) == "3*A[0]") + + F = C[0, 0].subs(C, A - B) + assert(rcode(F) == "(A - B)[0]") diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_repr.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_repr.py new file mode 100644 index 0000000000000000000000000000000000000000..da58883b4fb027ed82db842a0a1ce5f76a49a8bb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_repr.py @@ -0,0 +1,382 @@ +from __future__ import annotations +from typing import Any + +from sympy.external.gmpy import GROUND_TYPES +from sympy.testing.pytest import raises, warns_deprecated_sympy +from sympy.assumptions.ask import Q +from sympy.core.function import (Function, WildFunction) +from sympy.core.numbers import (AlgebraicNumber, Float, Integer, Rational) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, Wild, symbols) +from sympy.core.sympify import sympify +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.miscellaneous import (root, sqrt) +from sympy.functions.elementary.trigonometric import sin +from sympy.functions.special.delta_functions import Heaviside +from sympy.logic.boolalg import (false, true) +from sympy.matrices.dense import (Matrix, ones) +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.immutable import ImmutableDenseMatrix +from sympy.combinatorics import Cycle, Permutation +from sympy.core.symbol import Str +from sympy.geometry import Point, Ellipse +from sympy.printing import srepr +from sympy.polys import ring, field, ZZ, QQ, lex, grlex, Poly +from sympy.polys.polyclasses import DMP +from sympy.polys.agca.extensions import FiniteExtension + +x, y = symbols('x,y') + +# eval(srepr(expr)) == expr has to succeed in the right environment. The right +# environment is the scope of "from sympy import *" for most cases. +ENV: dict[str, Any] = {"Str": Str} +exec("from sympy import *", ENV) + + +def sT(expr, string, import_stmt=None, **kwargs): + """ + sT := sreprTest + + Tests that srepr delivers the expected string and that + the condition eval(srepr(expr))==expr holds. + """ + if import_stmt is None: + ENV2 = ENV + else: + ENV2 = ENV.copy() + exec(import_stmt, ENV2) + + assert srepr(expr, **kwargs) == string + assert eval(string, ENV2) == expr + + +def test_printmethod(): + class R(Abs): + def _sympyrepr(self, printer): + return "foo(%s)" % printer._print(self.args[0]) + assert srepr(R(x)) == "foo(Symbol('x'))" + + +def test_Add(): + sT(x + y, "Add(Symbol('x'), Symbol('y'))") + assert srepr(x**2 + 1, order='lex') == "Add(Pow(Symbol('x'), Integer(2)), Integer(1))" + assert srepr(x**2 + 1, order='old') == "Add(Integer(1), Pow(Symbol('x'), Integer(2)))" + assert srepr(sympify('x + 3 - 2', evaluate=False), order='none') == "Add(Symbol('x'), Integer(3), Mul(Integer(-1), Integer(2)))" + + +def test_more_than_255_args_issue_10259(): + from sympy.core.add import Add + from sympy.core.mul import Mul + for op in (Add, Mul): + expr = op(*symbols('x:256')) + assert eval(srepr(expr)) == expr + + +def test_Function(): + sT(Function("f")(x), "Function('f')(Symbol('x'))") + # test unapplied Function + sT(Function('f'), "Function('f')") + + sT(sin(x), "sin(Symbol('x'))") + sT(sin, "sin") + + +def test_Heaviside(): + sT(Heaviside(x), "Heaviside(Symbol('x'))") + sT(Heaviside(x, 1), "Heaviside(Symbol('x'), Integer(1))") + + +def test_Geometry(): + sT(Point(0, 0), "Point2D(Integer(0), Integer(0))") + sT(Ellipse(Point(0, 0), 5, 1), + "Ellipse(Point2D(Integer(0), Integer(0)), Integer(5), Integer(1))") + # TODO more tests + + +def test_Singletons(): + sT(S.Catalan, 'Catalan') + sT(S.ComplexInfinity, 'zoo') + sT(S.EulerGamma, 'EulerGamma') + sT(S.Exp1, 'E') + sT(S.GoldenRatio, 'GoldenRatio') + sT(S.TribonacciConstant, 'TribonacciConstant') + sT(S.Half, 'Rational(1, 2)') + sT(S.ImaginaryUnit, 'I') + sT(S.Infinity, 'oo') + sT(S.NaN, 'nan') + sT(S.NegativeInfinity, '-oo') + sT(S.NegativeOne, 'Integer(-1)') + sT(S.One, 'Integer(1)') + sT(S.Pi, 'pi') + sT(S.Zero, 'Integer(0)') + sT(S.Complexes, 'Complexes') + sT(S.EmptySequence, 'EmptySequence') + sT(S.EmptySet, 'EmptySet') + # sT(S.IdentityFunction, 'Lambda(_x, _x)') + sT(S.Naturals, 'Naturals') + sT(S.Naturals0, 'Naturals0') + sT(S.Rationals, 'Rationals') + sT(S.Reals, 'Reals') + sT(S.UniversalSet, 'UniversalSet') + + +def test_Integer(): + sT(Integer(4), "Integer(4)") + + +def test_list(): + sT([x, Integer(4)], "[Symbol('x'), Integer(4)]") + + +def test_Matrix(): + for cls, name in [(Matrix, "MutableDenseMatrix"), (ImmutableDenseMatrix, "ImmutableDenseMatrix")]: + sT(cls([[x**+1, 1], [y, x + y]]), + "%s([[Symbol('x'), Integer(1)], [Symbol('y'), Add(Symbol('x'), Symbol('y'))]])" % name) + + sT(cls(), "%s([])" % name) + + sT(cls([[x**+1, 1], [y, x + y]]), "%s([[Symbol('x'), Integer(1)], [Symbol('y'), Add(Symbol('x'), Symbol('y'))]])" % name) + + +def test_empty_Matrix(): + sT(ones(0, 3), "MutableDenseMatrix(0, 3, [])") + sT(ones(4, 0), "MutableDenseMatrix(4, 0, [])") + sT(ones(0, 0), "MutableDenseMatrix([])") + + +def test_Rational(): + sT(Rational(1, 3), "Rational(1, 3)") + sT(Rational(-1, 3), "Rational(-1, 3)") + + +def test_Float(): + sT(Float('1.23', dps=3), "Float('1.22998', precision=13)") + sT(Float('1.23456789', dps=9), "Float('1.23456788994', precision=33)") + sT(Float('1.234567890123456789', dps=19), + "Float('1.234567890123456789013', precision=66)") + sT(Float('0.60038617995049726', dps=15), + "Float('0.60038617995049726', precision=53)") + + sT(Float('1.23', precision=13), "Float('1.22998', precision=13)") + sT(Float('1.23456789', precision=33), + "Float('1.23456788994', precision=33)") + sT(Float('1.234567890123456789', precision=66), + "Float('1.234567890123456789013', precision=66)") + sT(Float('0.60038617995049726', precision=53), + "Float('0.60038617995049726', precision=53)") + + sT(Float('0.60038617995049726', 15), + "Float('0.60038617995049726', precision=53)") + + +def test_Symbol(): + sT(x, "Symbol('x')") + sT(y, "Symbol('y')") + sT(Symbol('x', negative=True), "Symbol('x', negative=True)") + + +def test_Symbol_two_assumptions(): + x = Symbol('x', negative=0, integer=1) + # order could vary + s1 = "Symbol('x', integer=True, negative=False)" + s2 = "Symbol('x', negative=False, integer=True)" + assert srepr(x) in (s1, s2) + assert eval(srepr(x), ENV) == x + + +def test_Symbol_no_special_commutative_treatment(): + sT(Symbol('x'), "Symbol('x')") + sT(Symbol('x', commutative=False), "Symbol('x', commutative=False)") + sT(Symbol('x', commutative=0), "Symbol('x', commutative=False)") + sT(Symbol('x', commutative=True), "Symbol('x', commutative=True)") + sT(Symbol('x', commutative=1), "Symbol('x', commutative=True)") + + +def test_Wild(): + sT(Wild('x', even=True), "Wild('x', even=True)") + + +def test_Dummy(): + d = Dummy('d') + sT(d, "Dummy('d', dummy_index=%s)" % str(d.dummy_index)) + + +def test_Dummy_assumption(): + d = Dummy('d', nonzero=True) + assert d == eval(srepr(d)) + s1 = "Dummy('d', dummy_index=%s, nonzero=True)" % str(d.dummy_index) + s2 = "Dummy('d', nonzero=True, dummy_index=%s)" % str(d.dummy_index) + assert srepr(d) in (s1, s2) + + +def test_Dummy_from_Symbol(): + # should not get the full dictionary of assumptions + n = Symbol('n', integer=True) + d = n.as_dummy() + assert srepr(d + ) == "Dummy('n', dummy_index=%s)" % str(d.dummy_index) + + +def test_tuple(): + sT((x,), "(Symbol('x'),)") + sT((x, y), "(Symbol('x'), Symbol('y'))") + + +def test_WildFunction(): + sT(WildFunction('w'), "WildFunction('w')") + + +def test_settins(): + raises(TypeError, lambda: srepr(x, method="garbage")) + + +def test_Mul(): + sT(3*x**3*y, "Mul(Integer(3), Pow(Symbol('x'), Integer(3)), Symbol('y'))") + assert srepr(3*x**3*y, order='old') == "Mul(Integer(3), Symbol('y'), Pow(Symbol('x'), Integer(3)))" + assert srepr(sympify('(x+4)*2*x*7', evaluate=False), order='none') == "Mul(Add(Symbol('x'), Integer(4)), Integer(2), Symbol('x'), Integer(7))" + + +def test_AlgebraicNumber(): + a = AlgebraicNumber(sqrt(2)) + sT(a, "AlgebraicNumber(Pow(Integer(2), Rational(1, 2)), [Integer(1), Integer(0)])") + a = AlgebraicNumber(root(-2, 3)) + sT(a, "AlgebraicNumber(Pow(Integer(-2), Rational(1, 3)), [Integer(1), Integer(0)])") + + +def test_PolyRing(): + assert srepr(ring("x", ZZ, lex)[0]) == "PolyRing((Symbol('x'),), ZZ, lex)" + assert srepr(ring("x,y", QQ, grlex)[0]) == "PolyRing((Symbol('x'), Symbol('y')), QQ, grlex)" + assert srepr(ring("x,y,z", ZZ["t"], lex)[0]) == "PolyRing((Symbol('x'), Symbol('y'), Symbol('z')), ZZ[t], lex)" + + +def test_FracField(): + assert srepr(field("x", ZZ, lex)[0]) == "FracField((Symbol('x'),), ZZ, lex)" + assert srepr(field("x,y", QQ, grlex)[0]) == "FracField((Symbol('x'), Symbol('y')), QQ, grlex)" + assert srepr(field("x,y,z", ZZ["t"], lex)[0]) == "FracField((Symbol('x'), Symbol('y'), Symbol('z')), ZZ[t], lex)" + + +def test_PolyElement(): + R, x, y = ring("x,y", ZZ) + assert srepr(3*x**2*y + 1) == "PolyElement(PolyRing((Symbol('x'), Symbol('y')), ZZ, lex), [((2, 1), 3), ((0, 0), 1)])" + + +def test_FracElement(): + F, x, y = field("x,y", ZZ) + assert srepr((3*x**2*y + 1)/(x - y**2)) == "FracElement(FracField((Symbol('x'), Symbol('y')), ZZ, lex), [((2, 1), 3), ((0, 0), 1)], [((1, 0), 1), ((0, 2), -1)])" + + +def test_FractionField(): + assert srepr(QQ.frac_field(x)) == \ + "FractionField(FracField((Symbol('x'),), QQ, lex))" + assert srepr(QQ.frac_field(x, y, order=grlex)) == \ + "FractionField(FracField((Symbol('x'), Symbol('y')), QQ, grlex))" + + +def test_PolynomialRingBase(): + assert srepr(ZZ.old_poly_ring(x)) == \ + "GlobalPolynomialRing(ZZ, Symbol('x'))" + assert srepr(ZZ[x].old_poly_ring(y)) == \ + "GlobalPolynomialRing(ZZ[x], Symbol('y'))" + assert srepr(QQ.frac_field(x).old_poly_ring(y)) == \ + "GlobalPolynomialRing(FractionField(FracField((Symbol('x'),), QQ, lex)), Symbol('y'))" + + +def test_DMP(): + p1 = DMP([1, 2], ZZ) + p2 = ZZ.old_poly_ring(x)([1, 2]) + if GROUND_TYPES != 'flint': + assert srepr(p1) == "DMP_Python([1, 2], ZZ)" + assert srepr(p2) == "DMP_Python([1, 2], ZZ)" + else: + assert srepr(p1) == "DUP_Flint([1, 2], ZZ)" + assert srepr(p2) == "DUP_Flint([1, 2], ZZ)" + + +def test_FiniteExtension(): + assert srepr(FiniteExtension(Poly(x**2 + 1, x))) == \ + "FiniteExtension(Poly(x**2 + 1, x, domain='ZZ'))" + + +def test_ExtensionElement(): + A = FiniteExtension(Poly(x**2 + 1, x)) + if GROUND_TYPES != 'flint': + ans = "ExtElem(DMP_Python([1, 0], ZZ), FiniteExtension(Poly(x**2 + 1, x, domain='ZZ')))" + else: + ans = "ExtElem(DUP_Flint([1, 0], ZZ), FiniteExtension(Poly(x**2 + 1, x, domain='ZZ')))" + assert srepr(A.generator) == ans + +def test_BooleanAtom(): + assert srepr(true) == "true" + assert srepr(false) == "false" + + +def test_Integers(): + sT(S.Integers, "Integers") + + +def test_Naturals(): + sT(S.Naturals, "Naturals") + + +def test_Naturals0(): + sT(S.Naturals0, "Naturals0") + + +def test_Reals(): + sT(S.Reals, "Reals") + + +def test_matrix_expressions(): + n = symbols('n', integer=True) + A = MatrixSymbol("A", n, n) + B = MatrixSymbol("B", n, n) + sT(A, "MatrixSymbol(Str('A'), Symbol('n', integer=True), Symbol('n', integer=True))") + sT(A*B, "MatMul(MatrixSymbol(Str('A'), Symbol('n', integer=True), Symbol('n', integer=True)), MatrixSymbol(Str('B'), Symbol('n', integer=True), Symbol('n', integer=True)))") + sT(A + B, "MatAdd(MatrixSymbol(Str('A'), Symbol('n', integer=True), Symbol('n', integer=True)), MatrixSymbol(Str('B'), Symbol('n', integer=True), Symbol('n', integer=True)))") + + +def test_Cycle(): + # FIXME: sT fails because Cycle is not immutable and calling srepr(Cycle(1, 2)) + # adds keys to the Cycle dict (GH-17661) + #import_stmt = "from sympy.combinatorics import Cycle" + #sT(Cycle(1, 2), "Cycle(1, 2)", import_stmt) + assert srepr(Cycle(1, 2)) == "Cycle(1, 2)" + + +def test_Permutation(): + import_stmt = "from sympy.combinatorics import Permutation" + sT(Permutation(1, 2)(3, 4), "Permutation([0, 2, 1, 4, 3])", import_stmt, perm_cyclic=False) + sT(Permutation(1, 2)(3, 4), "Permutation(1, 2)(3, 4)", import_stmt, perm_cyclic=True) + + with warns_deprecated_sympy(): + old_print_cyclic = Permutation.print_cyclic + Permutation.print_cyclic = False + sT(Permutation(1, 2)(3, 4), "Permutation([0, 2, 1, 4, 3])", import_stmt) + Permutation.print_cyclic = old_print_cyclic + +def test_dict(): + from sympy.abc import x, y, z + d = {} + assert srepr(d) == "{}" + d = {x: y} + assert srepr(d) == "{Symbol('x'): Symbol('y')}" + d = {x: y, y: z} + assert srepr(d) in ( + "{Symbol('x'): Symbol('y'), Symbol('y'): Symbol('z')}", + "{Symbol('y'): Symbol('z'), Symbol('x'): Symbol('y')}", + ) + d = {x: {y: z}} + assert srepr(d) == "{Symbol('x'): {Symbol('y'): Symbol('z')}}" + +def test_set(): + from sympy.abc import x, y + s = set() + assert srepr(s) == "set()" + s = {x, y} + assert srepr(s) in ("{Symbol('x'), Symbol('y')}", "{Symbol('y'), Symbol('x')}") + +def test_Predicate(): + sT(Q.even, "Q.even") + +def test_AppliedPredicate(): + sT(Q.even(Symbol('z')), "AppliedPredicate(Q.even, Symbol('z'))") diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_rust.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_rust.py new file mode 100644 index 0000000000000000000000000000000000000000..c81d592faca0d4a31e5a9618a48d67cb19ca94d8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_rust.py @@ -0,0 +1,363 @@ +from sympy.core import (S, pi, oo, symbols, Rational, Integer, + GoldenRatio, EulerGamma, Catalan, Lambda, Dummy, + Eq, Ne, Le, Lt, Gt, Ge, Mod) +from sympy.functions import (Piecewise, sin, cos, Abs, exp, ceiling, sqrt, + sign, floor) +from sympy.logic import ITE +from sympy.testing.pytest import raises +from sympy.utilities.lambdify import implemented_function +from sympy.tensor import IndexedBase, Idx +from sympy.matrices import MatrixSymbol, SparseMatrix, Matrix + +from sympy.printing.codeprinter import rust_code + +x, y, z = symbols('x,y,z', integer=False, real=True) +k, m, n = symbols('k,m,n', integer=True) + + +def test_Integer(): + assert rust_code(Integer(42)) == "42" + assert rust_code(Integer(-56)) == "-56" + + +def test_Relational(): + assert rust_code(Eq(x, y)) == "x == y" + assert rust_code(Ne(x, y)) == "x != y" + assert rust_code(Le(x, y)) == "x <= y" + assert rust_code(Lt(x, y)) == "x < y" + assert rust_code(Gt(x, y)) == "x > y" + assert rust_code(Ge(x, y)) == "x >= y" + + +def test_Rational(): + assert rust_code(Rational(3, 7)) == "3_f64/7.0" + assert rust_code(Rational(18, 9)) == "2" + assert rust_code(Rational(3, -7)) == "-3_f64/7.0" + assert rust_code(Rational(-3, -7)) == "3_f64/7.0" + assert rust_code(x + Rational(3, 7)) == "x + 3_f64/7.0" + assert rust_code(Rational(3, 7)*x) == "(3_f64/7.0)*x" + + +def test_basic_ops(): + assert rust_code(x + y) == "x + y" + assert rust_code(x - y) == "x - y" + assert rust_code(x * y) == "x*y" + assert rust_code(x / y) == "x*y.recip()" + assert rust_code(-x) == "-x" + assert rust_code(2 * x) == "2.0*x" + assert rust_code(y + 2) == "y + 2.0" + assert rust_code(x + n) == "n as f64 + x" + +def test_printmethod(): + class fabs(Abs): + def _rust_code(self, printer): + return "%s.fabs()" % printer._print(self.args[0]) + assert rust_code(fabs(x)) == "x.fabs()" + a = MatrixSymbol("a", 1, 3) + assert rust_code(a[0,0]) == 'a[0]' + + +def test_Functions(): + assert rust_code(sin(x) ** cos(x)) == "x.sin().powf(x.cos())" + assert rust_code(abs(x)) == "x.abs()" + assert rust_code(ceiling(x)) == "x.ceil()" + assert rust_code(floor(x)) == "x.floor()" + + # Automatic rewrite + assert rust_code(Mod(x, 3)) == 'x - 3.0*((1_f64/3.0)*x).floor()' + + +def test_Pow(): + assert rust_code(1/x) == "x.recip()" + assert rust_code(x**-1) == rust_code(x**-1.0) == "x.recip()" + assert rust_code(sqrt(x)) == "x.sqrt()" + assert rust_code(x**S.Half) == rust_code(x**0.5) == "x.sqrt()" + + assert rust_code(1/sqrt(x)) == "x.sqrt().recip()" + assert rust_code(x**-S.Half) == rust_code(x**-0.5) == "x.sqrt().recip()" + + assert rust_code(1/pi) == "PI.recip()" + assert rust_code(pi**-1) == rust_code(pi**-1.0) == "PI.recip()" + assert rust_code(pi**-0.5) == "PI.sqrt().recip()" + + assert rust_code(x**Rational(1, 3)) == "x.cbrt()" + assert rust_code(2**x) == "x.exp2()" + assert rust_code(exp(x)) == "x.exp()" + assert rust_code(x**3) == "x.powi(3)" + assert rust_code(x**(y**3)) == "x.powf(y.powi(3))" + assert rust_code(x**Rational(2, 3)) == "x.powf(2_f64/3.0)" + + g = implemented_function('g', Lambda(x, 2*x)) + assert rust_code(1/(g(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + "(3.5*2.0*x).powf(-x + y.powf(x))/(x.powi(2) + y)" + _cond_cfunc = [(lambda base, exp: exp.is_integer, "dpowi", 1), + (lambda base, exp: not exp.is_integer, "pow", 1)] + assert rust_code(x**3, user_functions={'Pow': _cond_cfunc}) == 'x.dpowi(3)' + assert rust_code(x**3.2, user_functions={'Pow': _cond_cfunc}) == 'x.pow(3.2)' + + +def test_constants(): + assert rust_code(pi) == "PI" + assert rust_code(oo) == "INFINITY" + assert rust_code(S.Infinity) == "INFINITY" + assert rust_code(-oo) == "NEG_INFINITY" + assert rust_code(S.NegativeInfinity) == "NEG_INFINITY" + assert rust_code(S.NaN) == "NAN" + assert rust_code(exp(1)) == "E" + assert rust_code(S.Exp1) == "E" + + +def test_constants_other(): + assert rust_code(2*GoldenRatio) == "const GoldenRatio: f64 = %s;\n2.0*GoldenRatio" % GoldenRatio.evalf(17) + assert rust_code( + 2*Catalan) == "const Catalan: f64 = %s;\n2.0*Catalan" % Catalan.evalf(17) + assert rust_code(2*EulerGamma) == "const EulerGamma: f64 = %s;\n2.0*EulerGamma" % EulerGamma.evalf(17) + + +def test_boolean(): + assert rust_code(True) == "true" + assert rust_code(S.true) == "true" + assert rust_code(False) == "false" + assert rust_code(S.false) == "false" + assert rust_code(k & m) == "k && m" + assert rust_code(k | m) == "k || m" + assert rust_code(~k) == "!k" + assert rust_code(k & m & n) == "k && m && n" + assert rust_code(k | m | n) == "k || m || n" + assert rust_code((k & m) | n) == "n || k && m" + assert rust_code((k | m) & n) == "n && (k || m)" + + +def test_Piecewise(): + expr = Piecewise((x, x < 1), (x + 2, True)) + assert rust_code(expr) == ( + "if (x < 1.0) {\n" + " x\n" + "} else {\n" + " x + 2.0\n" + "}") + assert rust_code(expr, assign_to="r") == ( + "r = if (x < 1.0) {\n" + " x\n" + "} else {\n" + " x + 2.0\n" + "};") + assert rust_code(expr, assign_to="r", inline=True) == ( + "r = if (x < 1.0) { x } else { x + 2.0 };") + expr = Piecewise((x, x < 1), (x + 1, x < 5), (x + 2, True)) + assert rust_code(expr, inline=True) == ( + "if (x < 1.0) { x } else if (x < 5.0) { x + 1.0 } else { x + 2.0 }") + assert rust_code(expr, assign_to="r", inline=True) == ( + "r = if (x < 1.0) { x } else if (x < 5.0) { x + 1.0 } else { x + 2.0 };") + assert rust_code(expr, assign_to="r") == ( + "r = if (x < 1.0) {\n" + " x\n" + "} else if (x < 5.0) {\n" + " x + 1.0\n" + "} else {\n" + " x + 2.0\n" + "};") + expr = 2*Piecewise((x, x < 1), (x + 1, x < 5), (x + 2, True)) + assert rust_code(expr, inline=True) == ( + "2.0*if (x < 1.0) { x } else if (x < 5.0) { x + 1.0 } else { x + 2.0 }") + expr = 2*Piecewise((x, x < 1), (x + 1, x < 5), (x + 2, True)) - 42 + assert rust_code(expr, inline=True) == ( + "2.0*if (x < 1.0) { x } else if (x < 5.0) { x + 1.0 } else { x + 2.0 } - 42.0") + # Check that Piecewise without a True (default) condition error + expr = Piecewise((x, x < 1), (x**2, x > 1), (sin(x), x > 0)) + raises(ValueError, lambda: rust_code(expr)) + + +def test_dereference_printing(): + expr = x + y + sin(z) + z + assert rust_code(expr, dereference=[z]) == "x + y + (*z) + (*z).sin()" + + +def test_sign(): + expr = sign(x) * y + assert rust_code(expr) == "y*(if (x == 0.0) { 0.0 } else { (x).signum() }) as f64" + assert rust_code(expr, assign_to='r') == "r = y*(if (x == 0.0) { 0.0 } else { (x).signum() }) as f64;" + + expr = sign(x + y) + 42 + assert rust_code(expr) == "(if (x + y == 0.0) { 0.0 } else { (x + y).signum() }) + 42" + assert rust_code(expr, assign_to='r') == "r = (if (x + y == 0.0) { 0.0 } else { (x + y).signum() }) + 42;" + + expr = sign(cos(x)) + assert rust_code(expr) == "(if (x.cos() == 0.0) { 0.0 } else { (x.cos()).signum() })" + + +def test_reserved_words(): + + x, y = symbols("x if") + + expr = sin(y) + assert rust_code(expr) == "if_.sin()" + assert rust_code(expr, dereference=[y]) == "(*if_).sin()" + assert rust_code(expr, reserved_word_suffix='_unreserved') == "if_unreserved.sin()" + + with raises(ValueError): + rust_code(expr, error_on_reserved=True) + + +def test_ITE(): + ekpr = ITE(k < 1, m, n) + assert rust_code(ekpr) == ( + "if (k < 1) {\n" + " m\n" + "} else {\n" + " n\n" + "}") + + +def test_Indexed(): + n, m, o = symbols('n m o', integer=True) + i, j, k = Idx('i', n), Idx('j', m), Idx('k', o) + + x = IndexedBase('x')[j] + assert rust_code(x) == "x[j]" + + A = IndexedBase('A')[i, j] + assert rust_code(A) == "A[m*i + j]" + + B = IndexedBase('B')[i, j, k] + assert rust_code(B) == "B[m*o*i + o*j + k]" + + +def test_dummy_loops(): + i, m = symbols('i m', integer=True, cls=Dummy) + x = IndexedBase('x') + y = IndexedBase('y') + i = Idx(i, m) + + assert rust_code(x[i], assign_to=y[i]) == ( + "for i in 0..m {\n" + " y[i] = x[i];\n" + "}") + + +def test_loops(): + m, n = symbols('m n', integer=True) + A = IndexedBase('A') + x = IndexedBase('x') + y = IndexedBase('y') + z = IndexedBase('z') + i = Idx('i', m) + j = Idx('j', n) + + assert rust_code(A[i, j]*x[j], assign_to=y[i]) == ( + "for i in 0..m {\n" + " y[i] = 0;\n" + "}\n" + "for i in 0..m {\n" + " for j in 0..n {\n" + " y[i] = A[n*i + j]*x[j] + y[i];\n" + " }\n" + "}") + + assert rust_code(A[i, j]*x[j] + x[i] + z[i], assign_to=y[i]) == ( + "for i in 0..m {\n" + " y[i] = x[i] + z[i];\n" + "}\n" + "for i in 0..m {\n" + " for j in 0..n {\n" + " y[i] = A[n*i + j]*x[j] + y[i];\n" + " }\n" + "}") + + +def test_loops_multiple_contractions(): + n, m, o, p = symbols('n m o p', integer=True) + a = IndexedBase('a') + b = IndexedBase('b') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + k = Idx('k', o) + l = Idx('l', p) + + assert rust_code(b[j, k, l]*a[i, j, k, l], assign_to=y[i]) == ( + "for i in 0..m {\n" + " y[i] = 0;\n" + "}\n" + "for i in 0..m {\n" + " for j in 0..n {\n" + " for k in 0..o {\n" + " for l in 0..p {\n" + " y[i] = a[%s]*b[%s] + y[i];\n" % (i*n*o*p + j*o*p + k*p + l, j*o*p + k*p + l) +\ + " }\n" + " }\n" + " }\n" + "}") + + +def test_loops_addfactor(): + m, n, o, p = symbols('m n o p', integer=True) + a = IndexedBase('a') + b = IndexedBase('b') + c = IndexedBase('c') + y = IndexedBase('y') + i = Idx('i', m) + j = Idx('j', n) + k = Idx('k', o) + l = Idx('l', p) + + code = rust_code((a[i, j, k, l] + b[i, j, k, l])*c[j, k, l], assign_to=y[i]) + assert code == ( + "for i in 0..m {\n" + " y[i] = 0;\n" + "}\n" + "for i in 0..m {\n" + " for j in 0..n {\n" + " for k in 0..o {\n" + " for l in 0..p {\n" + " y[i] = (a[%s] + b[%s])*c[%s] + y[i];\n" % (i*n*o*p + j*o*p + k*p + l, i*n*o*p + j*o*p + k*p + l, j*o*p + k*p + l) +\ + " }\n" + " }\n" + " }\n" + "}") + + +def test_settings(): + raises(TypeError, lambda: rust_code(sin(x), method="garbage")) + + +def test_inline_function(): + x = symbols('x') + g = implemented_function('g', Lambda(x, 2*x)) + assert rust_code(g(x)) == "2*x" + + g = implemented_function('g', Lambda(x, 2*x/Catalan)) + assert rust_code(g(x)) == ( + "const Catalan: f64 = %s;\n2.0*x/Catalan" % Catalan.evalf(17)) + + A = IndexedBase('A') + i = Idx('i', symbols('n', integer=True)) + g = implemented_function('g', Lambda(x, x*(1 + x)*(2 + x))) + assert rust_code(g(A[i]), assign_to=A[i]) == ( + "for i in 0..n {\n" + " A[i] = (A[i] + 1)*(A[i] + 2)*A[i];\n" + "}") + + +def test_user_functions(): + x = symbols('x', integer=False) + n = symbols('n', integer=True) + custom_functions = { + "ceiling": "ceil", + "Abs": [(lambda x: not x.is_integer, "fabs", 4), (lambda x: x.is_integer, "abs", 4)], + } + assert rust_code(ceiling(x), user_functions=custom_functions) == "x.ceil()" + assert rust_code(Abs(x), user_functions=custom_functions) == "fabs(x)" + assert rust_code(Abs(n), user_functions=custom_functions) == "abs(n)" + + +def test_matrix(): + assert rust_code(Matrix([1, 2, 3])) == '[1, 2, 3]' + with raises(ValueError): + rust_code(Matrix([[1, 2, 3]])) + + +def test_sparse_matrix(): + # gh-15791 + with raises(NotImplementedError): + rust_code(SparseMatrix([[1, 2, 3]])) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_smtlib.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_smtlib.py new file mode 100644 index 0000000000000000000000000000000000000000..48ff3d432d9042bf178f4e52dc46c787059937a3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_smtlib.py @@ -0,0 +1,553 @@ +import contextlib +import itertools +import re +import typing +from enum import Enum +from typing import Callable + +import sympy +from sympy import Add, Implies, sqrt +from sympy.core import Mul, Pow +from sympy.core import (S, pi, symbols, Function, Rational, Integer, + Symbol, Eq, Ne, Le, Lt, Gt, Ge) +from sympy.functions import Piecewise, exp, sin, cos +from sympy.assumptions.ask import Q +from sympy.printing.smtlib import smtlib_code +from sympy.testing.pytest import raises, Failed + +x, y, z = symbols('x,y,z') + + +class _W(Enum): + DEFAULTING_TO_FLOAT = re.compile("Could not infer type of `.+`. Defaulting to float.", re.IGNORECASE) + WILL_NOT_DECLARE = re.compile("Non-Symbol/Function `.+` will not be declared.", re.IGNORECASE) + WILL_NOT_ASSERT = re.compile("Non-Boolean expression `.+` will not be asserted. Converting to SMTLib verbatim.", re.IGNORECASE) + + +@contextlib.contextmanager +def _check_warns(expected: typing.Iterable[_W]): + warns: typing.List[str] = [] + log_warn = warns.append + yield log_warn + + errors = [] + for i, (w, e) in enumerate(itertools.zip_longest(warns, expected)): + if not e: + errors += [f"[{i}] Received unexpected warning `{w}`."] + elif not w: + errors += [f"[{i}] Did not receive expected warning `{e.name}`."] + elif not e.value.match(w): + errors += [f"[{i}] Warning `{w}` does not match expected {e.name}."] + + if errors: raise Failed('\n'.join(errors)) + + +def test_Integer(): + with _check_warns([_W.WILL_NOT_ASSERT] * 2) as w: + assert smtlib_code(Integer(67), log_warn=w) == "67" + assert smtlib_code(Integer(-1), log_warn=w) == "-1" + with _check_warns([]) as w: + assert smtlib_code(Integer(67)) == "67" + assert smtlib_code(Integer(-1)) == "-1" + + +def test_Rational(): + with _check_warns([_W.WILL_NOT_ASSERT] * 4) as w: + assert smtlib_code(Rational(3, 7), log_warn=w) == "(/ 3 7)" + assert smtlib_code(Rational(18, 9), log_warn=w) == "2" + assert smtlib_code(Rational(3, -7), log_warn=w) == "(/ -3 7)" + assert smtlib_code(Rational(-3, -7), log_warn=w) == "(/ 3 7)" + + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT] * 2) as w: + assert smtlib_code(x + Rational(3, 7), auto_declare=False, log_warn=w) == "(+ (/ 3 7) x)" + assert smtlib_code(Rational(3, 7) * x, log_warn=w) == "(declare-const x Real)\n" \ + "(* (/ 3 7) x)" + + +def test_Relational(): + with _check_warns([_W.DEFAULTING_TO_FLOAT] * 12) as w: + assert smtlib_code(Eq(x, y), auto_declare=False, log_warn=w) == "(assert (= x y))" + assert smtlib_code(Ne(x, y), auto_declare=False, log_warn=w) == "(assert (not (= x y)))" + assert smtlib_code(Le(x, y), auto_declare=False, log_warn=w) == "(assert (<= x y))" + assert smtlib_code(Lt(x, y), auto_declare=False, log_warn=w) == "(assert (< x y))" + assert smtlib_code(Gt(x, y), auto_declare=False, log_warn=w) == "(assert (> x y))" + assert smtlib_code(Ge(x, y), auto_declare=False, log_warn=w) == "(assert (>= x y))" + + +def test_AppliedBinaryRelation(): + with _check_warns([_W.DEFAULTING_TO_FLOAT] * 12) as w: + assert smtlib_code(Q.eq(x, y), auto_declare=False, log_warn=w) == "(assert (= x y))" + assert smtlib_code(Q.ne(x, y), auto_declare=False, log_warn=w) == "(assert (not (= x y)))" + assert smtlib_code(Q.lt(x, y), auto_declare=False, log_warn=w) == "(assert (< x y))" + assert smtlib_code(Q.le(x, y), auto_declare=False, log_warn=w) == "(assert (<= x y))" + assert smtlib_code(Q.gt(x, y), auto_declare=False, log_warn=w) == "(assert (> x y))" + assert smtlib_code(Q.ge(x, y), auto_declare=False, log_warn=w) == "(assert (>= x y))" + + raises(ValueError, lambda: smtlib_code(Q.complex(x), log_warn=w)) + + +def test_AppliedPredicate(): + with _check_warns([_W.DEFAULTING_TO_FLOAT] * 6) as w: + assert smtlib_code(Q.positive(x), auto_declare=False, log_warn=w) == "(assert (> x 0))" + assert smtlib_code(Q.negative(x), auto_declare=False, log_warn=w) == "(assert (< x 0))" + assert smtlib_code(Q.zero(x), auto_declare=False, log_warn=w) == "(assert (= x 0))" + assert smtlib_code(Q.nonpositive(x), auto_declare=False, log_warn=w) == "(assert (<= x 0))" + assert smtlib_code(Q.nonnegative(x), auto_declare=False, log_warn=w) == "(assert (>= x 0))" + assert smtlib_code(Q.nonzero(x), auto_declare=False, log_warn=w) == "(assert (not (= x 0)))" + +def test_Function(): + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(sin(x) ** cos(x), auto_declare=False, log_warn=w) == "(pow (sin x) (cos x))" + + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + abs(x), + symbol_table={x: int, y: bool}, + known_types={int: "INTEGER_TYPE"}, + known_functions={sympy.Abs: "ABSOLUTE_VALUE_OF"}, + log_warn=w + ) == "(declare-const x INTEGER_TYPE)\n" \ + "(ABSOLUTE_VALUE_OF x)" + + my_fun1 = Function('f1') + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + my_fun1(x), + symbol_table={my_fun1: Callable[[bool], float]}, + log_warn=w + ) == "(declare-const x Bool)\n" \ + "(declare-fun f1 (Bool) Real)\n" \ + "(f1 x)" + + with _check_warns([]) as w: + assert smtlib_code( + my_fun1(x), + symbol_table={my_fun1: Callable[[bool], bool]}, + log_warn=w + ) == "(declare-const x Bool)\n" \ + "(declare-fun f1 (Bool) Bool)\n" \ + "(assert (f1 x))" + + assert smtlib_code( + Eq(my_fun1(x, z), y), + symbol_table={my_fun1: Callable[[int, bool], bool]}, + log_warn=w + ) == "(declare-const x Int)\n" \ + "(declare-const y Bool)\n" \ + "(declare-const z Bool)\n" \ + "(declare-fun f1 (Int Bool) Bool)\n" \ + "(assert (= (f1 x z) y))" + + assert smtlib_code( + Eq(my_fun1(x, z), y), + symbol_table={my_fun1: Callable[[int, bool], bool]}, + known_functions={my_fun1: "MY_KNOWN_FUN", Eq: '=='}, + log_warn=w + ) == "(declare-const x Int)\n" \ + "(declare-const y Bool)\n" \ + "(declare-const z Bool)\n" \ + "(assert (== (MY_KNOWN_FUN x z) y))" + + with _check_warns([_W.DEFAULTING_TO_FLOAT] * 3) as w: + assert smtlib_code( + Eq(my_fun1(x, z), y), + known_functions={my_fun1: "MY_KNOWN_FUN", Eq: '=='}, + log_warn=w + ) == "(declare-const x Real)\n" \ + "(declare-const y Real)\n" \ + "(declare-const z Real)\n" \ + "(assert (== (MY_KNOWN_FUN x z) y))" + + +def test_Pow(): + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(x ** 3, auto_declare=False, log_warn=w) == "(pow x 3)" + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(x ** (y ** 3), auto_declare=False, log_warn=w) == "(pow x (pow y 3))" + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(x ** Rational(2, 3), auto_declare=False, log_warn=w) == '(pow x (/ 2 3))' + + a = Symbol('a', integer=True) + b = Symbol('b', real=True) + c = Symbol('c') + + def g(x): return 2 * x + + # if x=1, y=2, then expr=2.333... + expr = 1 / (g(a) * 3.5) ** (a - b ** a) / (a ** 2 + b) + + with _check_warns([]) as w: + assert smtlib_code( + [ + Eq(a < 2, c), + Eq(b > a, c), + c & True, + Eq(expr, 2 + Rational(1, 3)) + ], + log_warn=w + ) == '(declare-const a Int)\n' \ + '(declare-const b Real)\n' \ + '(declare-const c Bool)\n' \ + '(assert (= (< a 2) c))\n' \ + '(assert (= (> b a) c))\n' \ + '(assert c)\n' \ + '(assert (= ' \ + '(* (pow (* 7.0 a) (+ (pow b a) (* -1 a))) (pow (+ b (pow a 2)) -1)) ' \ + '(/ 7 3)' \ + '))' + + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + Mul(-2, c, Pow(Mul(b, b, evaluate=False), -1, evaluate=False), evaluate=False), + log_warn=w + ) == '(declare-const b Real)\n' \ + '(declare-const c Real)\n' \ + '(* -2 c (pow (* b b) -1))' + + +def test_basic_ops(): + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(x * y, auto_declare=False, log_warn=w) == "(* x y)" + + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(x + y, auto_declare=False, log_warn=w) == "(+ x y)" + + # with _check_warns([_SmtlibWarnings.DEFAULTING_TO_FLOAT, _SmtlibWarnings.DEFAULTING_TO_FLOAT, _SmtlibWarnings.WILL_NOT_ASSERT]) as w: + # todo: implement re-write, currently does '(+ x (* -1 y))' instead + # assert smtlib_code(x - y, auto_declare=False, log_warn=w) == "(- x y)" + + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(-x, auto_declare=False, log_warn=w) == "(* -1 x)" + + +def test_quantifier_extensions(): + from sympy.logic.boolalg import Boolean + from sympy import Interval, Tuple, sympify + + # start For-all quantifier class example + class ForAll(Boolean): + def _smtlib(self, printer): + bound_symbol_declarations = [ + printer._s_expr(sym.name, [ + printer._known_types[printer.symbol_table[sym]], + Interval(start, end) + ]) for sym, start, end in self.limits + ] + return printer._s_expr('forall', [ + printer._s_expr('', bound_symbol_declarations), + self.function + ]) + + @property + def bound_symbols(self): + return {s for s, _, _ in self.limits} + + @property + def free_symbols(self): + bound_symbol_names = {s.name for s in self.bound_symbols} + return { + s for s in self.function.free_symbols + if s.name not in bound_symbol_names + } + + def __new__(cls, *args): + limits = [sympify(a) for a in args if isinstance(a, (tuple, Tuple))] + function = [sympify(a) for a in args if isinstance(a, Boolean)] + assert len(limits) + len(function) == len(args) + assert len(function) == 1 + function = function[0] + + if isinstance(function, ForAll): return ForAll.__new__( + ForAll, *(limits + function.limits), function.function + ) + inst = Boolean.__new__(cls) + inst._args = tuple(limits + [function]) + inst.limits = limits + inst.function = function + return inst + + # end For-All Quantifier class example + + f = Function('f') + with _check_warns([_W.DEFAULTING_TO_FLOAT]) as w: + assert smtlib_code( + ForAll((x, -42, +21), Eq(f(x), f(x))), + symbol_table={f: Callable[[float], float]}, + log_warn=w + ) == '(assert (forall ( (x Real [-42, 21])) true))' + + with _check_warns([_W.DEFAULTING_TO_FLOAT] * 2) as w: + assert smtlib_code( + ForAll( + (x, -42, +21), (y, -100, 3), + Implies(Eq(x, y), Eq(f(x), f(y))) + ), + symbol_table={f: Callable[[float], float]}, + log_warn=w + ) == '(declare-fun f (Real) Real)\n' \ + '(assert (' \ + 'forall ( (x Real [-42, 21]) (y Real [-100, 3])) ' \ + '(=> (= x y) (= (f x) (f y)))' \ + '))' + + a = Symbol('a', integer=True) + b = Symbol('b', real=True) + c = Symbol('c') + + with _check_warns([]) as w: + assert smtlib_code( + ForAll( + (a, 2, 100), ForAll( + (b, 2, 100), + Implies(a < b, sqrt(a) < b) | c + )), + log_warn=w + ) == '(declare-const c Bool)\n' \ + '(assert (forall ( (a Int [2, 100]) (b Real [2, 100])) ' \ + '(or c (=> (< a b) (< (pow a (/ 1 2)) b)))' \ + '))' + + +def test_mix_number_mult_symbols(): + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + 1 / pi, + known_constants={pi: "MY_PI"}, + log_warn=w + ) == '(pow MY_PI -1)' + + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + [ + Eq(pi, 3.14, evaluate=False), + 1 / pi, + ], + known_constants={pi: "MY_PI"}, + log_warn=w + ) == '(assert (= MY_PI 3.14))\n' \ + '(pow MY_PI -1)' + + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + Add(S.Zero, S.One, S.NegativeOne, S.Half, + S.Exp1, S.Pi, S.GoldenRatio, evaluate=False), + known_constants={ + S.Pi: 'p', S.GoldenRatio: 'g', + S.Exp1: 'e' + }, + known_functions={ + Add: 'plus', + exp: 'exp' + }, + precision=3, + log_warn=w + ) == '(plus 0 1 -1 (/ 1 2) (exp 1) p g)' + + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + Add(S.Zero, S.One, S.NegativeOne, S.Half, + S.Exp1, S.Pi, S.GoldenRatio, evaluate=False), + known_constants={ + S.Pi: 'p' + }, + known_functions={ + Add: 'plus', + exp: 'exp' + }, + precision=3, + log_warn=w + ) == '(plus 0 1 -1 (/ 1 2) (exp 1) p 1.62)' + + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + Add(S.Zero, S.One, S.NegativeOne, S.Half, + S.Exp1, S.Pi, S.GoldenRatio, evaluate=False), + known_functions={Add: 'plus'}, + precision=3, + log_warn=w + ) == '(plus 0 1 -1 (/ 1 2) 2.72 3.14 1.62)' + + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + Add(S.Zero, S.One, S.NegativeOne, S.Half, + S.Exp1, S.Pi, S.GoldenRatio, evaluate=False), + known_constants={S.Exp1: 'e'}, + known_functions={Add: 'plus'}, + precision=3, + log_warn=w + ) == '(plus 0 1 -1 (/ 1 2) e 3.14 1.62)' + + +def test_boolean(): + with _check_warns([]) as w: + assert smtlib_code(x & y, log_warn=w) == '(declare-const x Bool)\n' \ + '(declare-const y Bool)\n' \ + '(assert (and x y))' + assert smtlib_code(x | y, log_warn=w) == '(declare-const x Bool)\n' \ + '(declare-const y Bool)\n' \ + '(assert (or x y))' + assert smtlib_code(~x, log_warn=w) == '(declare-const x Bool)\n' \ + '(assert (not x))' + assert smtlib_code(x & y & z, log_warn=w) == '(declare-const x Bool)\n' \ + '(declare-const y Bool)\n' \ + '(declare-const z Bool)\n' \ + '(assert (and x y z))' + + with _check_warns([_W.DEFAULTING_TO_FLOAT]) as w: + assert smtlib_code((x & ~y) | (z > 3), log_warn=w) == '(declare-const x Bool)\n' \ + '(declare-const y Bool)\n' \ + '(declare-const z Real)\n' \ + '(assert (or (> z 3) (and x (not y))))' + + f = Function('f') + g = Function('g') + h = Function('h') + with _check_warns([_W.DEFAULTING_TO_FLOAT]) as w: + assert smtlib_code( + [Gt(f(x), y), + Lt(y, g(z))], + symbol_table={ + f: Callable[[bool], int], g: Callable[[bool], int], + }, log_warn=w + ) == '(declare-const x Bool)\n' \ + '(declare-const y Real)\n' \ + '(declare-const z Bool)\n' \ + '(declare-fun f (Bool) Int)\n' \ + '(declare-fun g (Bool) Int)\n' \ + '(assert (> (f x) y))\n' \ + '(assert (< y (g z)))' + + with _check_warns([]) as w: + assert smtlib_code( + [Eq(f(x), y), + Lt(y, g(z))], + symbol_table={ + f: Callable[[bool], int], g: Callable[[bool], int], + }, log_warn=w + ) == '(declare-const x Bool)\n' \ + '(declare-const y Int)\n' \ + '(declare-const z Bool)\n' \ + '(declare-fun f (Bool) Int)\n' \ + '(declare-fun g (Bool) Int)\n' \ + '(assert (= (f x) y))\n' \ + '(assert (< y (g z)))' + + with _check_warns([]) as w: + assert smtlib_code( + [Eq(f(x), y), + Eq(g(f(x)), z), + Eq(h(g(f(x))), x)], + symbol_table={ + f: Callable[[float], int], + g: Callable[[int], bool], + h: Callable[[bool], float] + }, + log_warn=w + ) == '(declare-const x Real)\n' \ + '(declare-const y Int)\n' \ + '(declare-const z Bool)\n' \ + '(declare-fun f (Real) Int)\n' \ + '(declare-fun g (Int) Bool)\n' \ + '(declare-fun h (Bool) Real)\n' \ + '(assert (= (f x) y))\n' \ + '(assert (= (g (f x)) z))\n' \ + '(assert (= (h (g (f x))) x))' + + +# todo: make smtlib_code support arrays +# def test_containers(): +# assert julia_code([1, 2, 3, [4, 5, [6, 7]], 8, [9, 10], 11]) == \ +# "Any[1, 2, 3, Any[4, 5, Any[6, 7]], 8, Any[9, 10], 11]" +# assert julia_code((1, 2, (3, 4))) == "(1, 2, (3, 4))" +# assert julia_code([1]) == "Any[1]" +# assert julia_code((1,)) == "(1,)" +# assert julia_code(Tuple(*[1, 2, 3])) == "(1, 2, 3)" +# assert julia_code((1, x * y, (3, x ** 2))) == "(1, x .* y, (3, x .^ 2))" +# # scalar, matrix, empty matrix and empty list +# assert julia_code((1, eye(3), Matrix(0, 0, []), [])) == "(1, [1 0 0;\n0 1 0;\n0 0 1], zeros(0, 0), Any[])" + +def test_smtlib_piecewise(): + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + Piecewise((x, x < 1), + (x ** 2, True)), + auto_declare=False, + log_warn=w + ) == '(ite (< x 1) x (pow x 2))' + + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code( + Piecewise((x ** 2, x < 1), + (x ** 3, x < 2), + (x ** 4, x < 3), + (x ** 5, True)), + auto_declare=False, + log_warn=w + ) == '(ite (< x 1) (pow x 2) ' \ + '(ite (< x 2) (pow x 3) ' \ + '(ite (< x 3) (pow x 4) ' \ + '(pow x 5))))' + + # Check that Piecewise without a True (default) condition error + expr = Piecewise((x, x < 1), (x ** 2, x > 1), (sin(x), x > 0)) + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + raises(AssertionError, lambda: smtlib_code(expr, log_warn=w)) + + +def test_smtlib_piecewise_times_const(): + pw = Piecewise((x, x < 1), (x ** 2, True)) + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(2 * pw, log_warn=w) == '(declare-const x Real)\n(* 2 (ite (< x 1) x (pow x 2)))' + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(pw / x, log_warn=w) == '(declare-const x Real)\n(* (pow x -1) (ite (< x 1) x (pow x 2)))' + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(pw / (x * y), log_warn=w) == '(declare-const x Real)\n(declare-const y Real)\n(* (pow x -1) (pow y -1) (ite (< x 1) x (pow x 2)))' + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + assert smtlib_code(pw / 3, log_warn=w) == '(declare-const x Real)\n(* (/ 1 3) (ite (< x 1) x (pow x 2)))' + + +# todo: make smtlib_code support arrays / matrices ? +# def test_smtlib_matrix_assign_to(): +# A = Matrix([[1, 2, 3]]) +# assert smtlib_code(A, assign_to='a') == "a = [1 2 3]" +# A = Matrix([[1, 2], [3, 4]]) +# assert smtlib_code(A, assign_to='A') == "A = [1 2;\n3 4]" + +# def test_julia_matrix_1x1(): +# A = Matrix([[3]]) +# B = MatrixSymbol('B', 1, 1) +# C = MatrixSymbol('C', 1, 2) +# assert julia_code(A, assign_to=B) == "B = [3]" +# raises(ValueError, lambda: julia_code(A, assign_to=C)) + +# def test_julia_matrix_elements(): +# A = Matrix([[x, 2, x * y]]) +# assert julia_code(A[0, 0] ** 2 + A[0, 1] + A[0, 2]) == "x .^ 2 + x .* y + 2" +# A = MatrixSymbol('AA', 1, 3) +# assert julia_code(A) == "AA" +# assert julia_code(A[0, 0] ** 2 + sin(A[0, 1]) + A[0, 2]) == \ +# "sin(AA[1,2]) + AA[1,1] .^ 2 + AA[1,3]" +# assert julia_code(sum(A)) == "AA[1,1] + AA[1,2] + AA[1,3]" + +def test_smtlib_boolean(): + with _check_warns([]) as w: + assert smtlib_code(True, auto_assert=False, log_warn=w) == 'true' + assert smtlib_code(True, log_warn=w) == '(assert true)' + assert smtlib_code(S.true, log_warn=w) == '(assert true)' + assert smtlib_code(S.false, log_warn=w) == '(assert false)' + assert smtlib_code(False, log_warn=w) == '(assert false)' + assert smtlib_code(False, auto_assert=False, log_warn=w) == 'false' + + +def test_not_supported(): + f = Function('f') + with _check_warns([_W.DEFAULTING_TO_FLOAT, _W.WILL_NOT_ASSERT]) as w: + raises(KeyError, lambda: smtlib_code(f(x).diff(x), symbol_table={f: Callable[[float], float]}, log_warn=w)) + with _check_warns([_W.WILL_NOT_ASSERT]) as w: + raises(KeyError, lambda: smtlib_code(S.ComplexInfinity, log_warn=w)) + + +def test_Float(): + assert smtlib_code(0.0) == "0.0" + assert smtlib_code(0.000000000000000003) == '(* 3.0 (pow 10 -18))' + assert smtlib_code(5.3) == "5.3" diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_str.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_str.py new file mode 100644 index 0000000000000000000000000000000000000000..675212964b03bf9a9806088225c28d7f70971ca7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_str.py @@ -0,0 +1,1206 @@ +from sympy import MatAdd +from sympy.algebras.quaternion import Quaternion +from sympy.assumptions.ask import Q +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.combinatorics.partitions import Partition +from sympy.concrete.summations import (Sum, summation) +from sympy.core.add import Add +from sympy.core.containers import (Dict, Tuple) +from sympy.core.expr import UnevaluatedExpr, Expr +from sympy.core.function import (Derivative, Function, Lambda, Subs, WildFunction) +from sympy.core.mul import Mul +from sympy.core import (Catalan, EulerGamma, GoldenRatio, TribonacciConstant) +from sympy.core.numbers import (E, Float, I, Integer, Rational, nan, oo, pi, zoo) +from sympy.core.parameters import _exp_is_pow +from sympy.core.power import Pow +from sympy.core.relational import (Eq, Rel, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, Wild, symbols) +from sympy.functions.combinatorial.factorials import (factorial, factorial2, subfactorial) +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.delta_functions import Heaviside +from sympy.functions.special.zeta_functions import zeta +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import (Equivalent, false, true, Xor) +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions import Identity +from sympy.matrices.expressions.slice import MatrixSlice +from sympy.matrices import SparseMatrix +from sympy.polys.polytools import factor +from sympy.series.limits import Limit +from sympy.series.order import O +from sympy.sets.sets import (Complement, FiniteSet, Interval, SymmetricDifference) +from sympy.stats import (Covariance, Expectation, Probability, Variance) +from sympy.stats.rv import RandomSymbol +from sympy.external import import_module +from sympy.physics.control.lti import TransferFunction, Series, Parallel, \ + Feedback, TransferFunctionMatrix, MIMOSeries, MIMOParallel, MIMOFeedback +from sympy.physics.units import second, joule +from sympy.polys import (Poly, rootof, RootSum, groebner, ring, field, ZZ, QQ, + ZZ_I, QQ_I, lex, grlex) +from sympy.geometry import Point, Circle, Polygon, Ellipse, Triangle +from sympy.tensor import NDimArray +from sympy.tensor.array.expressions.array_expressions import ArraySymbol, ArrayElement + +from sympy.testing.pytest import raises, warns_deprecated_sympy + +from sympy.printing import sstr, sstrrepr, StrPrinter +from sympy.physics.quantum.trace import Tr + +x, y, z, w, t = symbols('x,y,z,w,t') +d = Dummy('d') + + +def test_printmethod(): + class R(Abs): + def _sympystr(self, printer): + return "foo(%s)" % printer._print(self.args[0]) + assert sstr(R(x)) == "foo(x)" + + class R(Abs): + def _sympystr(self, printer): + return "foo" + assert sstr(R(x)) == "foo" + + +def test_Abs(): + assert str(Abs(x)) == "Abs(x)" + assert str(Abs(Rational(1, 6))) == "1/6" + assert str(Abs(Rational(-1, 6))) == "1/6" + + +def test_Add(): + assert str(x + y) == "x + y" + assert str(x + 1) == "x + 1" + assert str(x + x**2) == "x**2 + x" + assert str(Add(0, 1, evaluate=False)) == "0 + 1" + assert str(Add(0, 0, 1, evaluate=False)) == "0 + 0 + 1" + assert str(1.0*x) == "1.0*x" + assert str(5 + x + y + x*y + x**2 + y**2) == "x**2 + x*y + x + y**2 + y + 5" + assert str(1 + x + x**2/2 + x**3/3) == "x**3/3 + x**2/2 + x + 1" + assert str(2*x - 7*x**2 + 2 + 3*y) == "-7*x**2 + 2*x + 3*y + 2" + assert str(x - y) == "x - y" + assert str(2 - x) == "2 - x" + assert str(x - 2) == "x - 2" + assert str(x - y - z - w) == "-w + x - y - z" + assert str(x - z*y**2*z*w) == "-w*y**2*z**2 + x" + assert str(x - 1*y*x*y) == "-x*y**2 + x" + assert str(sin(x).series(x, 0, 15)) == "x - x**3/6 + x**5/120 - x**7/5040 + x**9/362880 - x**11/39916800 + x**13/6227020800 + O(x**15)" + assert str(Add(Add(-w, x, evaluate=False), Add(-y, z, evaluate=False), evaluate=False)) == "(-w + x) + (-y + z)" + assert str(Add(Add(-x, -y, evaluate=False), -z, evaluate=False)) == "-z + (-x - y)" + assert str(Add(Add(Add(-x, -y, evaluate=False), -z, evaluate=False), -t, evaluate=False)) == "-t + (-z + (-x - y))" + + +def test_Catalan(): + assert str(Catalan) == "Catalan" + + +def test_ComplexInfinity(): + assert str(zoo) == "zoo" + + +def test_Derivative(): + assert str(Derivative(x, y)) == "Derivative(x, y)" + assert str(Derivative(x**2, x, evaluate=False)) == "Derivative(x**2, x)" + assert str(Derivative( + x**2/y, x, y, evaluate=False)) == "Derivative(x**2/y, x, y)" + + +def test_dict(): + assert str({1: 1 + x}) == sstr({1: 1 + x}) == "{1: x + 1}" + assert str({1: x**2, 2: y*x}) in ("{1: x**2, 2: x*y}", "{2: x*y, 1: x**2}") + assert sstr({1: x**2, 2: y*x}) == "{1: x**2, 2: x*y}" + + +def test_Dict(): + assert str(Dict({1: 1 + x})) == sstr({1: 1 + x}) == "{1: x + 1}" + assert str(Dict({1: x**2, 2: y*x})) in ( + "{1: x**2, 2: x*y}", "{2: x*y, 1: x**2}") + assert sstr(Dict({1: x**2, 2: y*x})) == "{1: x**2, 2: x*y}" + + +def test_Dummy(): + assert str(d) == "_d" + assert str(d + x) == "_d + x" + + +def test_EulerGamma(): + assert str(EulerGamma) == "EulerGamma" + + +def test_Exp(): + assert str(E) == "E" + with _exp_is_pow(True): + assert str(exp(x)) == "E**x" + + +def test_factorial(): + n = Symbol('n', integer=True) + assert str(factorial(-2)) == "zoo" + assert str(factorial(0)) == "1" + assert str(factorial(7)) == "5040" + assert str(factorial(n)) == "factorial(n)" + assert str(factorial(2*n)) == "factorial(2*n)" + assert str(factorial(factorial(n))) == 'factorial(factorial(n))' + assert str(factorial(factorial2(n))) == 'factorial(factorial2(n))' + assert str(factorial2(factorial(n))) == 'factorial2(factorial(n))' + assert str(factorial2(factorial2(n))) == 'factorial2(factorial2(n))' + assert str(subfactorial(3)) == "2" + assert str(subfactorial(n)) == "subfactorial(n)" + assert str(subfactorial(2*n)) == "subfactorial(2*n)" + + +def test_Function(): + f = Function('f') + fx = f(x) + w = WildFunction('w') + assert str(f) == "f" + assert str(fx) == "f(x)" + assert str(w) == "w_" + + +def test_Geometry(): + assert sstr(Point(0, 0)) == 'Point2D(0, 0)' + assert sstr(Circle(Point(0, 0), 3)) == 'Circle(Point2D(0, 0), 3)' + assert sstr(Ellipse(Point(1, 2), 3, 4)) == 'Ellipse(Point2D(1, 2), 3, 4)' + assert sstr(Triangle(Point(1, 1), Point(7, 8), Point(0, -1))) == \ + 'Triangle(Point2D(1, 1), Point2D(7, 8), Point2D(0, -1))' + assert sstr(Polygon(Point(5, 6), Point(-2, -3), Point(0, 0), Point(4, 7))) == \ + 'Polygon(Point2D(5, 6), Point2D(-2, -3), Point2D(0, 0), Point2D(4, 7))' + assert sstr(Triangle(Point(0, 0), Point(1, 0), Point(0, 1)), sympy_integers=True) == \ + 'Triangle(Point2D(S(0), S(0)), Point2D(S(1), S(0)), Point2D(S(0), S(1)))' + assert sstr(Ellipse(Point(1, 2), 3, 4), sympy_integers=True) == \ + 'Ellipse(Point2D(S(1), S(2)), S(3), S(4))' + + +def test_GoldenRatio(): + assert str(GoldenRatio) == "GoldenRatio" + + +def test_Heaviside(): + assert str(Heaviside(x)) == str(Heaviside(x, S.Half)) == "Heaviside(x)" + assert str(Heaviside(x, 1)) == "Heaviside(x, 1)" + + +def test_TribonacciConstant(): + assert str(TribonacciConstant) == "TribonacciConstant" + + +def test_ImaginaryUnit(): + assert str(I) == "I" + + +def test_Infinity(): + assert str(oo) == "oo" + assert str(oo*I) == "oo*I" + + +def test_Integer(): + assert str(Integer(-1)) == "-1" + assert str(Integer(1)) == "1" + assert str(Integer(-3)) == "-3" + assert str(Integer(0)) == "0" + assert str(Integer(25)) == "25" + + +def test_Integral(): + assert str(Integral(sin(x), y)) == "Integral(sin(x), y)" + assert str(Integral(sin(x), (y, 0, 1))) == "Integral(sin(x), (y, 0, 1))" + + +def test_Interval(): + n = (S.NegativeInfinity, 1, 2, S.Infinity) + for i in range(len(n)): + for j in range(i + 1, len(n)): + for l in (True, False): + for r in (True, False): + ival = Interval(n[i], n[j], l, r) + assert S(str(ival)) == ival + + +def test_AccumBounds(): + a = Symbol('a', real=True) + assert str(AccumBounds(0, a)) == "AccumBounds(0, a)" + assert str(AccumBounds(0, 1)) == "AccumBounds(0, 1)" + + +def test_Lambda(): + assert str(Lambda(d, d**2)) == "Lambda(_d, _d**2)" + # issue 2908 + assert str(Lambda((), 1)) == "Lambda((), 1)" + assert str(Lambda((), x)) == "Lambda((), x)" + assert str(Lambda((x, y), x+y)) == "Lambda((x, y), x + y)" + assert str(Lambda(((x, y),), x+y)) == "Lambda(((x, y),), x + y)" + + +def test_Limit(): + assert str(Limit(sin(x)/x, x, y)) == "Limit(sin(x)/x, x, y, dir='+')" + assert str(Limit(1/x, x, 0)) == "Limit(1/x, x, 0, dir='+')" + assert str( + Limit(sin(x)/x, x, y, dir="-")) == "Limit(sin(x)/x, x, y, dir='-')" + + +def test_list(): + assert str([x]) == sstr([x]) == "[x]" + assert str([x**2, x*y + 1]) == sstr([x**2, x*y + 1]) == "[x**2, x*y + 1]" + assert str([x**2, [y + x]]) == sstr([x**2, [y + x]]) == "[x**2, [x + y]]" + + +def test_Matrix_str(): + M = Matrix([[x**+1, 1], [y, x + y]]) + assert str(M) == "Matrix([[x, 1], [y, x + y]])" + assert sstr(M) == "Matrix([\n[x, 1],\n[y, x + y]])" + M = Matrix([[1]]) + assert str(M) == sstr(M) == "Matrix([[1]])" + M = Matrix([[1, 2]]) + assert str(M) == sstr(M) == "Matrix([[1, 2]])" + M = Matrix() + assert str(M) == sstr(M) == "Matrix(0, 0, [])" + M = Matrix(0, 1, lambda i, j: 0) + assert str(M) == sstr(M) == "Matrix(0, 1, [])" + + +def test_Mul(): + assert str(x/y) == "x/y" + assert str(y/x) == "y/x" + assert str(x/y/z) == "x/(y*z)" + assert str((x + 1)/(y + 2)) == "(x + 1)/(y + 2)" + assert str(2*x/3) == '2*x/3' + assert str(-2*x/3) == '-2*x/3' + assert str(-1.0*x) == '-1.0*x' + assert str(1.0*x) == '1.0*x' + assert str(Mul(0, 1, evaluate=False)) == '0*1' + assert str(Mul(1, 0, evaluate=False)) == '1*0' + assert str(Mul(1, 1, evaluate=False)) == '1*1' + assert str(Mul(1, 1, 1, evaluate=False)) == '1*1*1' + assert str(Mul(1, 2, evaluate=False)) == '1*2' + assert str(Mul(1, S.Half, evaluate=False)) == '1*(1/2)' + assert str(Mul(1, 1, S.Half, evaluate=False)) == '1*1*(1/2)' + assert str(Mul(1, 1, 2, 3, x, evaluate=False)) == '1*1*2*3*x' + assert str(Mul(1, -1, evaluate=False)) == '1*(-1)' + assert str(Mul(-1, 1, evaluate=False)) == '-1*1' + assert str(Mul(4, 3, 2, 1, 0, y, x, evaluate=False)) == '4*3*2*1*0*y*x' + assert str(Mul(4, 3, 2, 1+z, 0, y, x, evaluate=False)) == '4*3*2*(z + 1)*0*y*x' + assert str(Mul(Rational(2, 3), Rational(5, 7), evaluate=False)) == '(2/3)*(5/7)' + # For issue 14160 + assert str(Mul(-2, x, Pow(Mul(y,y,evaluate=False), -1, evaluate=False), + evaluate=False)) == '-2*x/(y*y)' + # issue 21537 + assert str(Mul(x, Pow(1/y, -1, evaluate=False), evaluate=False)) == 'x/(1/y)' + + # Issue 24108 + from sympy.core.parameters import evaluate + with evaluate(False): + assert str(Mul(Pow(Integer(2), Integer(-1)), Add(Integer(-1), Mul(Integer(-1), Integer(1))))) == "(-1 - 1*1)/2" + + class CustomClass1(Expr): + is_commutative = True + + class CustomClass2(Expr): + is_commutative = True + cc1 = CustomClass1() + cc2 = CustomClass2() + assert str(Rational(2)*cc1) == '2*CustomClass1()' + assert str(cc1*Rational(2)) == '2*CustomClass1()' + assert str(cc1*Float("1.5")) == '1.5*CustomClass1()' + assert str(cc2*Rational(2)) == '2*CustomClass2()' + assert str(cc2*Rational(2)*cc1) == '2*CustomClass1()*CustomClass2()' + assert str(cc1*Rational(2)*cc2) == '2*CustomClass1()*CustomClass2()' + + +def test_NaN(): + assert str(nan) == "nan" + + +def test_NegativeInfinity(): + assert str(-oo) == "-oo" + +def test_Order(): + assert str(O(x)) == "O(x)" + assert str(O(x**2)) == "O(x**2)" + assert str(O(x*y)) == "O(x*y, x, y)" + assert str(O(x, x)) == "O(x)" + assert str(O(x, (x, 0))) == "O(x)" + assert str(O(x, (x, oo))) == "O(x, (x, oo))" + assert str(O(x, x, y)) == "O(x, x, y)" + assert str(O(x, x, y)) == "O(x, x, y)" + assert str(O(x, (x, oo), (y, oo))) == "O(x, (x, oo), (y, oo))" + + +def test_Permutation_Cycle(): + from sympy.combinatorics import Permutation, Cycle + + # general principle: economically, canonically show all moved elements + # and the size of the permutation. + + for p, s in [ + (Cycle(), + '()'), + (Cycle(2), + '(2)'), + (Cycle(2, 1), + '(1 2)'), + (Cycle(1, 2)(5)(6, 7)(10), + '(1 2)(6 7)(10)'), + (Cycle(3, 4)(1, 2)(3, 4), + '(1 2)(4)'), + ]: + assert sstr(p) == s + + for p, s in [ + (Permutation([]), + 'Permutation([])'), + (Permutation([], size=1), + 'Permutation([0])'), + (Permutation([], size=2), + 'Permutation([0, 1])'), + (Permutation([], size=10), + 'Permutation([], size=10)'), + (Permutation([1, 0, 2]), + 'Permutation([1, 0, 2])'), + (Permutation([1, 0, 2, 3, 4, 5]), + 'Permutation([1, 0], size=6)'), + (Permutation([1, 0, 2, 3, 4, 5], size=10), + 'Permutation([1, 0], size=10)'), + ]: + assert sstr(p, perm_cyclic=False) == s + + for p, s in [ + (Permutation([]), + '()'), + (Permutation([], size=1), + '(0)'), + (Permutation([], size=2), + '(1)'), + (Permutation([], size=10), + '(9)'), + (Permutation([1, 0, 2]), + '(2)(0 1)'), + (Permutation([1, 0, 2, 3, 4, 5]), + '(5)(0 1)'), + (Permutation([1, 0, 2, 3, 4, 5], size=10), + '(9)(0 1)'), + (Permutation([0, 1, 3, 2, 4, 5], size=10), + '(9)(2 3)'), + ]: + assert sstr(p) == s + + + with warns_deprecated_sympy(): + old_print_cyclic = Permutation.print_cyclic + Permutation.print_cyclic = False + assert sstr(Permutation([1, 0, 2])) == 'Permutation([1, 0, 2])' + Permutation.print_cyclic = old_print_cyclic + +def test_Pi(): + assert str(pi) == "pi" + + +def test_Poly(): + assert str(Poly(0, x)) == "Poly(0, x, domain='ZZ')" + assert str(Poly(1, x)) == "Poly(1, x, domain='ZZ')" + assert str(Poly(x, x)) == "Poly(x, x, domain='ZZ')" + + assert str(Poly(2*x + 1, x)) == "Poly(2*x + 1, x, domain='ZZ')" + assert str(Poly(2*x - 1, x)) == "Poly(2*x - 1, x, domain='ZZ')" + + assert str(Poly(-1, x)) == "Poly(-1, x, domain='ZZ')" + assert str(Poly(-x, x)) == "Poly(-x, x, domain='ZZ')" + + assert str(Poly(-2*x + 1, x)) == "Poly(-2*x + 1, x, domain='ZZ')" + assert str(Poly(-2*x - 1, x)) == "Poly(-2*x - 1, x, domain='ZZ')" + + assert str(Poly(x - 1, x)) == "Poly(x - 1, x, domain='ZZ')" + assert str(Poly(2*x + x**5, x)) == "Poly(x**5 + 2*x, x, domain='ZZ')" + + assert str(Poly(3**(2*x), 3**x)) == "Poly((3**x)**2, 3**x, domain='ZZ')" + assert str(Poly((x**2)**x)) == "Poly(((x**2)**x), (x**2)**x, domain='ZZ')" + + assert str(Poly((x + y)**3, (x + y), expand=False) + ) == "Poly((x + y)**3, x + y, domain='ZZ')" + assert str(Poly((x - 1)**2, (x - 1), expand=False) + ) == "Poly((x - 1)**2, x - 1, domain='ZZ')" + + assert str( + Poly(x**2 + 1 + y, x)) == "Poly(x**2 + y + 1, x, domain='ZZ[y]')" + assert str( + Poly(x**2 - 1 + y, x)) == "Poly(x**2 + y - 1, x, domain='ZZ[y]')" + + assert str(Poly(x**2 + I*x, x)) == "Poly(x**2 + I*x, x, domain='ZZ_I')" + assert str(Poly(x**2 - I*x, x)) == "Poly(x**2 - I*x, x, domain='ZZ_I')" + + assert str(Poly(-x*y*z + x*y - 1, x, y, z) + ) == "Poly(-x*y*z + x*y - 1, x, y, z, domain='ZZ')" + assert str(Poly(-w*x**21*y**7*z + (1 + w)*z**3 - 2*x*z + 1, x, y, z)) == \ + "Poly(-w*x**21*y**7*z - 2*x*z + (w + 1)*z**3 + 1, x, y, z, domain='ZZ[w]')" + + assert str(Poly(x**2 + 1, x, modulus=2)) == "Poly(x**2 + 1, x, modulus=2)" + assert str(Poly(2*x**2 + 3*x + 4, x, modulus=17)) == "Poly(2*x**2 + 3*x + 4, x, modulus=17)" + + +def test_PolyRing(): + assert str(ring("x", ZZ, lex)[0]) == "Polynomial ring in x over ZZ with lex order" + assert str(ring("x,y", QQ, grlex)[0]) == "Polynomial ring in x, y over QQ with grlex order" + assert str(ring("x,y,z", ZZ["t"], lex)[0]) == "Polynomial ring in x, y, z over ZZ[t] with lex order" + + +def test_FracField(): + assert str(field("x", ZZ, lex)[0]) == "Rational function field in x over ZZ with lex order" + assert str(field("x,y", QQ, grlex)[0]) == "Rational function field in x, y over QQ with grlex order" + assert str(field("x,y,z", ZZ["t"], lex)[0]) == "Rational function field in x, y, z over ZZ[t] with lex order" + + +def test_PolyElement(): + Ruv, u,v = ring("u,v", ZZ) + Rxyz, x,y,z = ring("x,y,z", Ruv) + Rx_zzi, xz = ring("x", ZZ_I) + + assert str(x - x) == "0" + assert str(x - 1) == "x - 1" + assert str(x + 1) == "x + 1" + assert str(x**2) == "x**2" + + assert str((u**2 + 3*u*v + 1)*x**2*y + u + 1) == "(u**2 + 3*u*v + 1)*x**2*y + u + 1" + assert str((u**2 + 3*u*v + 1)*x**2*y + (u + 1)*x) == "(u**2 + 3*u*v + 1)*x**2*y + (u + 1)*x" + assert str((u**2 + 3*u*v + 1)*x**2*y + (u + 1)*x + 1) == "(u**2 + 3*u*v + 1)*x**2*y + (u + 1)*x + 1" + assert str((-u**2 + 3*u*v - 1)*x**2*y - (u + 1)*x - 1) == "-(u**2 - 3*u*v + 1)*x**2*y - (u + 1)*x - 1" + + assert str(-(v**2 + v + 1)*x + 3*u*v + 1) == "-(v**2 + v + 1)*x + 3*u*v + 1" + assert str(-(v**2 + v + 1)*x - 3*u*v + 1) == "-(v**2 + v + 1)*x - 3*u*v + 1" + + assert str((1+I)*xz + 2) == "(1 + 1*I)*x + (2 + 0*I)" + + +def test_FracElement(): + Fuv, u,v = field("u,v", ZZ) + Fxyzt, x,y,z,t = field("x,y,z,t", Fuv) + Rx_zzi, xz = field("x", QQ_I) + i = QQ_I(0, 1) + + assert str(x - x) == "0" + assert str(x - 1) == "x - 1" + assert str(x + 1) == "x + 1" + + assert str(x/3) == "x/3" + assert str(x/z) == "x/z" + assert str(x*y/z) == "x*y/z" + assert str(x/(z*t)) == "x/(z*t)" + assert str(x*y/(z*t)) == "x*y/(z*t)" + + assert str((x - 1)/y) == "(x - 1)/y" + assert str((x + 1)/y) == "(x + 1)/y" + assert str((-x - 1)/y) == "(-x - 1)/y" + assert str((x + 1)/(y*z)) == "(x + 1)/(y*z)" + assert str(-y/(x + 1)) == "-y/(x + 1)" + assert str(y*z/(x + 1)) == "y*z/(x + 1)" + + assert str(((u + 1)*x*y + 1)/((v - 1)*z - 1)) == "((u + 1)*x*y + 1)/((v - 1)*z - 1)" + assert str(((u + 1)*x*y + 1)/((v - 1)*z - t*u*v - 1)) == "((u + 1)*x*y + 1)/((v - 1)*z - u*v*t - 1)" + + assert str((1+i)/xz) == "(1 + 1*I)/x" + assert str(((1+i)*xz - i)/xz) == "((1 + 1*I)*x + (0 + -1*I))/x" + + +def test_GaussianInteger(): + assert str(ZZ_I(1, 0)) == "1" + assert str(ZZ_I(-1, 0)) == "-1" + assert str(ZZ_I(0, 1)) == "I" + assert str(ZZ_I(0, -1)) == "-I" + assert str(ZZ_I(0, 2)) == "2*I" + assert str(ZZ_I(0, -2)) == "-2*I" + assert str(ZZ_I(1, 1)) == "1 + I" + assert str(ZZ_I(-1, -1)) == "-1 - I" + assert str(ZZ_I(-1, -2)) == "-1 - 2*I" + + +def test_GaussianRational(): + assert str(QQ_I(1, 0)) == "1" + assert str(QQ_I(QQ(2, 3), 0)) == "2/3" + assert str(QQ_I(0, QQ(2, 3))) == "2*I/3" + assert str(QQ_I(QQ(1, 2), QQ(-2, 3))) == "1/2 - 2*I/3" + + +def test_Pow(): + assert str(x**-1) == "1/x" + assert str(x**-2) == "x**(-2)" + assert str(x**2) == "x**2" + assert str((x + y)**-1) == "1/(x + y)" + assert str((x + y)**-2) == "(x + y)**(-2)" + assert str((x + y)**2) == "(x + y)**2" + assert str((x + y)**(1 + x)) == "(x + y)**(x + 1)" + assert str(x**Rational(1, 3)) == "x**(1/3)" + assert str(1/x**Rational(1, 3)) == "x**(-1/3)" + assert str(sqrt(sqrt(x))) == "x**(1/4)" + # not the same as x**-1 + assert str(x**-1.0) == 'x**(-1.0)' + # see issue #2860 + assert str(Pow(S(2), -1.0, evaluate=False)) == '2**(-1.0)' + + +def test_sqrt(): + assert str(sqrt(x)) == "sqrt(x)" + assert str(sqrt(x**2)) == "sqrt(x**2)" + assert str(1/sqrt(x)) == "1/sqrt(x)" + assert str(1/sqrt(x**2)) == "1/sqrt(x**2)" + assert str(y/sqrt(x)) == "y/sqrt(x)" + assert str(x**0.5) == "x**0.5" + assert str(1/x**0.5) == "x**(-0.5)" + + +def test_Rational(): + n1 = Rational(1, 4) + n2 = Rational(1, 3) + n3 = Rational(2, 4) + n4 = Rational(2, -4) + n5 = Rational(0) + n7 = Rational(3) + n8 = Rational(-3) + assert str(n1*n2) == "1/12" + assert str(n1*n2) == "1/12" + assert str(n3) == "1/2" + assert str(n1*n3) == "1/8" + assert str(n1 + n3) == "3/4" + assert str(n1 + n2) == "7/12" + assert str(n1 + n4) == "-1/4" + assert str(n4*n4) == "1/4" + assert str(n4 + n2) == "-1/6" + assert str(n4 + n5) == "-1/2" + assert str(n4*n5) == "0" + assert str(n3 + n4) == "0" + assert str(n1**n7) == "1/64" + assert str(n2**n7) == "1/27" + assert str(n2**n8) == "27" + assert str(n7**n8) == "1/27" + assert str(Rational("-25")) == "-25" + assert str(Rational("1.25")) == "5/4" + assert str(Rational("-2.6e-2")) == "-13/500" + assert str(S("25/7")) == "25/7" + assert str(S("-123/569")) == "-123/569" + assert str(S("0.1[23]", rational=1)) == "61/495" + assert str(S("5.1[666]", rational=1)) == "31/6" + assert str(S("-5.1[666]", rational=1)) == "-31/6" + assert str(S("0.[9]", rational=1)) == "1" + assert str(S("-0.[9]", rational=1)) == "-1" + + assert str(sqrt(Rational(1, 4))) == "1/2" + assert str(sqrt(Rational(1, 36))) == "1/6" + + assert str((123**25) ** Rational(1, 25)) == "123" + assert str((123**25 + 1)**Rational(1, 25)) != "123" + assert str((123**25 - 1)**Rational(1, 25)) != "123" + assert str((123**25 - 1)**Rational(1, 25)) != "122" + + assert str(sqrt(Rational(81, 36))**3) == "27/8" + assert str(1/sqrt(Rational(81, 36))**3) == "8/27" + + assert str(sqrt(-4)) == str(2*I) + assert str(2**Rational(1, 10**10)) == "2**(1/10000000000)" + + assert sstr(Rational(2, 3), sympy_integers=True) == "S(2)/3" + x = Symbol("x") + assert sstr(x**Rational(2, 3), sympy_integers=True) == "x**(S(2)/3)" + assert sstr(Eq(x, Rational(2, 3)), sympy_integers=True) == "Eq(x, S(2)/3)" + assert sstr(Limit(x, x, Rational(7, 2)), sympy_integers=True) == \ + "Limit(x, x, S(7)/2, dir='+')" + + +def test_Float(): + # NOTE dps is the whole number of decimal digits + assert str(Float('1.23', dps=1 + 2)) == '1.23' + assert str(Float('1.23456789', dps=1 + 8)) == '1.23456789' + assert str( + Float('1.234567890123456789', dps=1 + 18)) == '1.234567890123456789' + assert str(pi.evalf(1 + 2)) == '3.14' + assert str(pi.evalf(1 + 14)) == '3.14159265358979' + assert str(pi.evalf(1 + 64)) == ('3.141592653589793238462643383279' + '5028841971693993751058209749445923') + assert str(pi.round(-1)) == '0.0' + assert str((pi**400 - (pi**400).round(1)).n(2)) == '-0.e+88' + assert sstr(Float("100"), full_prec=False, min=-2, max=2) == '1.0e+2' + assert sstr(Float("100"), full_prec=False, min=-2, max=3) == '100.0' + assert sstr(Float("0.1"), full_prec=False, min=-2, max=3) == '0.1' + assert sstr(Float("0.099"), min=-2, max=3) == '9.90000000000000e-2' + + +def test_Relational(): + assert str(Rel(x, y, "<")) == "x < y" + assert str(Rel(x + y, y, "==")) == "Eq(x + y, y)" + assert str(Rel(x, y, "!=")) == "Ne(x, y)" + assert str(Eq(x, 1) | Eq(x, 2)) == "Eq(x, 1) | Eq(x, 2)" + assert str(Ne(x, 1) & Ne(x, 2)) == "Ne(x, 1) & Ne(x, 2)" + + +def test_AppliedBinaryRelation(): + assert str(Q.eq(x, y)) == "Q.eq(x, y)" + assert str(Q.ne(x, y)) == "Q.ne(x, y)" + + +def test_CRootOf(): + assert str(rootof(x**5 + 2*x - 1, 0)) == "CRootOf(x**5 + 2*x - 1, 0)" + + +def test_RootSum(): + f = x**5 + 2*x - 1 + + assert str( + RootSum(f, Lambda(z, z), auto=False)) == "RootSum(x**5 + 2*x - 1)" + assert str(RootSum(f, Lambda( + z, z**2), auto=False)) == "RootSum(x**5 + 2*x - 1, Lambda(z, z**2))" + + +def test_GroebnerBasis(): + assert str(groebner( + [], x, y)) == "GroebnerBasis([], x, y, domain='ZZ', order='lex')" + + F = [x**2 - 3*y - x + 1, y**2 - 2*x + y - 1] + + assert str(groebner(F, order='grlex')) == \ + "GroebnerBasis([x**2 - x - 3*y + 1, y**2 - 2*x + y - 1], x, y, domain='ZZ', order='grlex')" + assert str(groebner(F, order='lex')) == \ + "GroebnerBasis([2*x - y**2 - y + 1, y**4 + 2*y**3 - 3*y**2 - 16*y + 7], x, y, domain='ZZ', order='lex')" + +def test_set(): + assert sstr(set()) == 'set()' + assert sstr(frozenset()) == 'frozenset()' + + assert sstr({1}) == '{1}' + assert sstr(frozenset([1])) == 'frozenset({1})' + assert sstr({1, 2, 3}) == '{1, 2, 3}' + assert sstr(frozenset([1, 2, 3])) == 'frozenset({1, 2, 3})' + + assert sstr( + {1, x, x**2, x**3, x**4}) == '{1, x, x**2, x**3, x**4}' + assert sstr( + frozenset([1, x, x**2, x**3, x**4])) == 'frozenset({1, x, x**2, x**3, x**4})' + + +def test_SparseMatrix(): + M = SparseMatrix([[x**+1, 1], [y, x + y]]) + assert str(M) == "Matrix([[x, 1], [y, x + y]])" + assert sstr(M) == "Matrix([\n[x, 1],\n[y, x + y]])" + + +def test_Sum(): + assert str(summation(cos(3*z), (z, x, y))) == "Sum(cos(3*z), (z, x, y))" + assert str(Sum(x*y**2, (x, -2, 2), (y, -5, 5))) == \ + "Sum(x*y**2, (x, -2, 2), (y, -5, 5))" + + +def test_Symbol(): + assert str(y) == "y" + assert str(x) == "x" + e = x + assert str(e) == "x" + + +def test_tuple(): + assert str((x,)) == sstr((x,)) == "(x,)" + assert str((x + y, 1 + x)) == sstr((x + y, 1 + x)) == "(x + y, x + 1)" + assert str((x + y, ( + 1 + x, x**2))) == sstr((x + y, (1 + x, x**2))) == "(x + y, (x + 1, x**2))" + + +def test_Series_str(): + tf1 = TransferFunction(x*y**2 - z, y**3 - t**3, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(t*x**2 - t**w*x + w, t - y, y) + assert str(Series(tf1, tf2)) == \ + "Series(TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y))" + assert str(Series(tf1, tf2, tf3)) == \ + "Series(TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y), TransferFunction(t*x**2 - t**w*x + w, t - y, y))" + assert str(Series(-tf2, tf1)) == \ + "Series(TransferFunction(-x + y, x + y, y), TransferFunction(x*y**2 - z, -t**3 + y**3, y))" + + +def test_MIMOSeries_str(): + tf1 = TransferFunction(x*y**2 - z, y**3 - t**3, y) + tf2 = TransferFunction(x - y, x + y, y) + tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]]) + tfm_2 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf2]]) + assert str(MIMOSeries(tfm_1, tfm_2)) == \ + "MIMOSeries(TransferFunctionMatrix(((TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y)), "\ + "(TransferFunction(x - y, x + y, y), TransferFunction(x*y**2 - z, -t**3 + y**3, y)))), "\ + "TransferFunctionMatrix(((TransferFunction(x - y, x + y, y), TransferFunction(x*y**2 - z, -t**3 + y**3, y)), "\ + "(TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y)))))" + + +def test_TransferFunction_str(): + tf1 = TransferFunction(x - 1, x + 1, x) + assert str(tf1) == "TransferFunction(x - 1, x + 1, x)" + tf2 = TransferFunction(x + 1, 2 - y, x) + assert str(tf2) == "TransferFunction(x + 1, 2 - y, x)" + tf3 = TransferFunction(y, y**2 + 2*y + 3, y) + assert str(tf3) == "TransferFunction(y, y**2 + 2*y + 3, y)" + + +def test_Parallel_str(): + tf1 = TransferFunction(x*y**2 - z, y**3 - t**3, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(t*x**2 - t**w*x + w, t - y, y) + assert str(Parallel(tf1, tf2)) == \ + "Parallel(TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y))" + assert str(Parallel(tf1, tf2, tf3)) == \ + "Parallel(TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y), TransferFunction(t*x**2 - t**w*x + w, t - y, y))" + assert str(Parallel(-tf2, tf1)) == \ + "Parallel(TransferFunction(-x + y, x + y, y), TransferFunction(x*y**2 - z, -t**3 + y**3, y))" + + +def test_MIMOParallel_str(): + tf1 = TransferFunction(x*y**2 - z, y**3 - t**3, y) + tf2 = TransferFunction(x - y, x + y, y) + tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]]) + tfm_2 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf2]]) + assert str(MIMOParallel(tfm_1, tfm_2)) == \ + "MIMOParallel(TransferFunctionMatrix(((TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y)), "\ + "(TransferFunction(x - y, x + y, y), TransferFunction(x*y**2 - z, -t**3 + y**3, y)))), "\ + "TransferFunctionMatrix(((TransferFunction(x - y, x + y, y), TransferFunction(x*y**2 - z, -t**3 + y**3, y)), "\ + "(TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y)))))" + + +def test_Feedback_str(): + tf1 = TransferFunction(x*y**2 - z, y**3 - t**3, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(t*x**2 - t**w*x + w, t - y, y) + assert str(Feedback(tf1*tf2, tf3)) == \ + "Feedback(Series(TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y)), " \ + "TransferFunction(t*x**2 - t**w*x + w, t - y, y), -1)" + assert str(Feedback(tf1, TransferFunction(1, 1, y), 1)) == \ + "Feedback(TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(1, 1, y), 1)" + + +def test_MIMOFeedback_str(): + tf1 = TransferFunction(x**2 - y**3, y - z, x) + tf2 = TransferFunction(y - x, z + y, x) + tfm_1 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf2]]) + tfm_2 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]]) + assert (str(MIMOFeedback(tfm_1, tfm_2)) \ + == "MIMOFeedback(TransferFunctionMatrix(((TransferFunction(-x + y, y + z, x), TransferFunction(x**2 - y**3, y - z, x))," \ + " (TransferFunction(x**2 - y**3, y - z, x), TransferFunction(-x + y, y + z, x)))), " \ + "TransferFunctionMatrix(((TransferFunction(x**2 - y**3, y - z, x), " \ + "TransferFunction(-x + y, y + z, x)), (TransferFunction(-x + y, y + z, x), TransferFunction(x**2 - y**3, y - z, x)))), -1)") + assert (str(MIMOFeedback(tfm_1, tfm_2, 1)) \ + == "MIMOFeedback(TransferFunctionMatrix(((TransferFunction(-x + y, y + z, x), TransferFunction(x**2 - y**3, y - z, x)), " \ + "(TransferFunction(x**2 - y**3, y - z, x), TransferFunction(-x + y, y + z, x)))), " \ + "TransferFunctionMatrix(((TransferFunction(x**2 - y**3, y - z, x), TransferFunction(-x + y, y + z, x)), "\ + "(TransferFunction(-x + y, y + z, x), TransferFunction(x**2 - y**3, y - z, x)))), 1)") + + +def test_TransferFunctionMatrix_str(): + tf1 = TransferFunction(x*y**2 - z, y**3 - t**3, y) + tf2 = TransferFunction(x - y, x + y, y) + tf3 = TransferFunction(t*x**2 - t**w*x + w, t - y, y) + assert str(TransferFunctionMatrix([[tf1], [tf2]])) == \ + "TransferFunctionMatrix(((TransferFunction(x*y**2 - z, -t**3 + y**3, y),), (TransferFunction(x - y, x + y, y),)))" + assert str(TransferFunctionMatrix([[tf1, tf2], [tf3, tf2]])) == \ + "TransferFunctionMatrix(((TransferFunction(x*y**2 - z, -t**3 + y**3, y), TransferFunction(x - y, x + y, y)), (TransferFunction(t*x**2 - t**w*x + w, t - y, y), TransferFunction(x - y, x + y, y))))" + + +def test_Quaternion_str_printer(): + q = Quaternion(x, y, z, t) + assert str(q) == "x + y*i + z*j + t*k" + q = Quaternion(x,y,z,x*t) + assert str(q) == "x + y*i + z*j + t*x*k" + q = Quaternion(x,y,z,x+t) + assert str(q) == "x + y*i + z*j + (t + x)*k" + + +def test_Quantity_str(): + assert sstr(second, abbrev=True) == "s" + assert sstr(joule, abbrev=True) == "J" + assert str(second) == "second" + assert str(joule) == "joule" + + +def test_wild_str(): + # Check expressions containing Wild not causing infinite recursion + w = Wild('x') + assert str(w + 1) == 'x_ + 1' + assert str(exp(2**w) + 5) == 'exp(2**x_) + 5' + assert str(3*w + 1) == '3*x_ + 1' + assert str(1/w + 1) == '1 + 1/x_' + assert str(w**2 + 1) == 'x_**2 + 1' + assert str(1/(1 - w)) == '1/(1 - x_)' + + +def test_wild_matchpy(): + from sympy.utilities.matchpy_connector import WildDot, WildPlus, WildStar + + matchpy = import_module("matchpy") + + if matchpy is None: + return + + wd = WildDot('w_') + wp = WildPlus('w__') + ws = WildStar('w___') + + assert str(wd) == 'w_' + assert str(wp) == 'w__' + assert str(ws) == 'w___' + + assert str(wp/ws + 2**wd) == '2**w_ + w__/w___' + assert str(sin(wd)*cos(wp)*sqrt(ws)) == 'sqrt(w___)*sin(w_)*cos(w__)' + + +def test_zeta(): + assert str(zeta(3)) == "zeta(3)" + + +def test_issue_3101(): + e = x - y + a = str(e) + b = str(e) + assert a == b + + +def test_issue_3103(): + e = -2*sqrt(x) - y/sqrt(x)/2 + assert str(e) not in ["(-2)*x**1/2(-1/2)*x**(-1/2)*y", + "-2*x**1/2(-1/2)*x**(-1/2)*y", "-2*x**1/2-1/2*x**-1/2*w"] + assert str(e) == "-2*sqrt(x) - y/(2*sqrt(x))" + + +def test_issue_4021(): + e = Integral(x, x) + 1 + assert str(e) == 'Integral(x, x) + 1' + + +def test_sstrrepr(): + assert sstr('abc') == 'abc' + assert sstrrepr('abc') == "'abc'" + + e = ['a', 'b', 'c', x] + assert sstr(e) == "[a, b, c, x]" + assert sstrrepr(e) == "['a', 'b', 'c', x]" + + +def test_infinity(): + assert sstr(oo*I) == "oo*I" + + +def test_full_prec(): + assert sstr(S("0.3"), full_prec=True) == "0.300000000000000" + assert sstr(S("0.3"), full_prec="auto") == "0.300000000000000" + assert sstr(S("0.3"), full_prec=False) == "0.3" + assert sstr(S("0.3")*x, full_prec=True) in [ + "0.300000000000000*x", + "x*0.300000000000000" + ] + assert sstr(S("0.3")*x, full_prec="auto") in [ + "0.3*x", + "x*0.3" + ] + assert sstr(S("0.3")*x, full_prec=False) in [ + "0.3*x", + "x*0.3" + ] + + +def test_noncommutative(): + A, B, C = symbols('A,B,C', commutative=False) + + assert sstr(A*B*C**-1) == "A*B*C**(-1)" + assert sstr(C**-1*A*B) == "C**(-1)*A*B" + assert sstr(A*C**-1*B) == "A*C**(-1)*B" + assert sstr(sqrt(A)) == "sqrt(A)" + assert sstr(1/sqrt(A)) == "A**(-1/2)" + + +def test_empty_printer(): + str_printer = StrPrinter() + assert str_printer.emptyPrinter("foo") == "foo" + assert str_printer.emptyPrinter(x*y) == "x*y" + assert str_printer.emptyPrinter(32) == "32" + +def test_decimal_printer(): + dec_printer = StrPrinter(settings={"dps":3}) + f = Function('f') + assert dec_printer.doprint(f(1.329294)) == "f(1.33)" + + +def test_settings(): + raises(TypeError, lambda: sstr(S(4), method="garbage")) + + +def test_RandomDomain(): + from sympy.stats import Normal, Die, Exponential, pspace, where + X = Normal('x1', 0, 1) + assert str(where(X > 0)) == "Domain: (0 < x1) & (x1 < oo)" + + D = Die('d1', 6) + assert str(where(D > 4)) == "Domain: Eq(d1, 5) | Eq(d1, 6)" + + A = Exponential('a', 1) + B = Exponential('b', 1) + assert str(pspace(Tuple(A, B)).domain) == "Domain: (0 <= a) & (0 <= b) & (a < oo) & (b < oo)" + + +def test_FiniteSet(): + assert str(FiniteSet(*range(1, 51))) == ( + '{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,' + ' 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,' + ' 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50}' + ) + assert str(FiniteSet(*range(1, 6))) == '{1, 2, 3, 4, 5}' + assert str(FiniteSet(*[x*y, x**2])) == '{x**2, x*y}' + assert str(FiniteSet(FiniteSet(FiniteSet(x, y), 5), FiniteSet(x,y), 5) + ) == 'FiniteSet(5, FiniteSet(5, {x, y}), {x, y})' + + +def test_Partition(): + assert str(Partition(FiniteSet(x, y), {z})) == 'Partition({z}, {x, y})' + +def test_UniversalSet(): + assert str(S.UniversalSet) == 'UniversalSet' + + +def test_PrettyPoly(): + F = QQ.frac_field(x, y) + R = QQ[x, y] + assert sstr(F.convert(x/(x + y))) == sstr(x/(x + y)) + assert sstr(R.convert(x + y)) == sstr(x + y) + + +def test_categories(): + from sympy.categories import (Object, NamedMorphism, + IdentityMorphism, Category) + + A = Object("A") + B = Object("B") + + f = NamedMorphism(A, B, "f") + id_A = IdentityMorphism(A) + + K = Category("K") + + assert str(A) == 'Object("A")' + assert str(f) == 'NamedMorphism(Object("A"), Object("B"), "f")' + assert str(id_A) == 'IdentityMorphism(Object("A"))' + + assert str(K) == 'Category("K")' + + +def test_Tr(): + A, B = symbols('A B', commutative=False) + t = Tr(A*B) + assert str(t) == 'Tr(A*B)' + + +def test_issue_6387(): + assert str(factor(-3.0*z + 3)) == '-3.0*(1.0*z - 1.0)' + + +def test_MatMul_MatAdd(): + X, Y = MatrixSymbol("X", 2, 2), MatrixSymbol("Y", 2, 2) + assert str(2*(X + Y)) == "2*X + 2*Y" + + assert str(I*X) == "I*X" + assert str(-I*X) == "-I*X" + assert str((1 + I)*X) == '(1 + I)*X' + assert str(-(1 + I)*X) == '(-1 - I)*X' + assert str(MatAdd(MatAdd(X, Y), MatAdd(X, Y))) == '(X + Y) + (X + Y)' + + +def test_MatrixSlice(): + n = Symbol('n', integer=True) + X = MatrixSymbol('X', n, n) + Y = MatrixSymbol('Y', 10, 10) + Z = MatrixSymbol('Z', 10, 10) + + assert str(MatrixSlice(X, (None, None, None), (None, None, None))) == 'X[:, :]' + assert str(X[x:x + 1, y:y + 1]) == 'X[x:x + 1, y:y + 1]' + assert str(X[x:x + 1:2, y:y + 1:2]) == 'X[x:x + 1:2, y:y + 1:2]' + assert str(X[:x, y:]) == 'X[:x, y:]' + assert str(X[:x, y:]) == 'X[:x, y:]' + assert str(X[x:, :y]) == 'X[x:, :y]' + assert str(X[x:y, z:w]) == 'X[x:y, z:w]' + assert str(X[x:y:t, w:t:x]) == 'X[x:y:t, w:t:x]' + assert str(X[x::y, t::w]) == 'X[x::y, t::w]' + assert str(X[:x:y, :t:w]) == 'X[:x:y, :t:w]' + assert str(X[::x, ::y]) == 'X[::x, ::y]' + assert str(MatrixSlice(X, (0, None, None), (0, None, None))) == 'X[:, :]' + assert str(MatrixSlice(X, (None, n, None), (None, n, None))) == 'X[:, :]' + assert str(MatrixSlice(X, (0, n, None), (0, n, None))) == 'X[:, :]' + assert str(MatrixSlice(X, (0, n, 2), (0, n, 2))) == 'X[::2, ::2]' + assert str(X[1:2:3, 4:5:6]) == 'X[1:2:3, 4:5:6]' + assert str(X[1:3:5, 4:6:8]) == 'X[1:3:5, 4:6:8]' + assert str(X[1:10:2]) == 'X[1:10:2, :]' + assert str(Y[:5, 1:9:2]) == 'Y[:5, 1:9:2]' + assert str(Y[:5, 1:10:2]) == 'Y[:5, 1::2]' + assert str(Y[5, :5:2]) == 'Y[5:6, :5:2]' + assert str(X[0:1, 0:1]) == 'X[:1, :1]' + assert str(X[0:1:2, 0:1:2]) == 'X[:1:2, :1:2]' + assert str((Y + Z)[2:, 2:]) == '(Y + Z)[2:, 2:]' + +def test_true_false(): + assert str(true) == repr(true) == sstr(true) == "True" + assert str(false) == repr(false) == sstr(false) == "False" + +def test_Equivalent(): + assert str(Equivalent(y, x)) == "Equivalent(x, y)" + +def test_Xor(): + assert str(Xor(y, x, evaluate=False)) == "x ^ y" + +def test_Complement(): + assert str(Complement(S.Reals, S.Naturals)) == 'Complement(Reals, Naturals)' + +def test_SymmetricDifference(): + assert str(SymmetricDifference(Interval(2, 3), Interval(3, 4),evaluate=False)) == \ + 'SymmetricDifference(Interval(2, 3), Interval(3, 4))' + + +def test_UnevaluatedExpr(): + a, b = symbols("a b") + expr1 = 2*UnevaluatedExpr(a+b) + assert str(expr1) == "2*(a + b)" + + +def test_MatrixElement_printing(): + # test cases for issue #11821 + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert(str(A[0, 0]) == "A[0, 0]") + assert(str(3 * A[0, 0]) == "3*A[0, 0]") + + F = C[0, 0].subs(C, A - B) + assert str(F) == "(A - B)[0, 0]" + + +def test_MatrixSymbol_printing(): + A = MatrixSymbol("A", 3, 3) + B = MatrixSymbol("B", 3, 3) + + assert str(A - A*B - B) == "A - A*B - B" + assert str(A*B - (A+B)) == "-A + A*B - B" + assert str(A**(-1)) == "A**(-1)" + assert str(A**3) == "A**3" + + +def test_MatrixExpressions(): + n = Symbol('n', integer=True) + X = MatrixSymbol('X', n, n) + + assert str(X) == "X" + + # Apply function elementwise (`ElementwiseApplyFunc`): + + expr = (X.T*X).applyfunc(sin) + assert str(expr) == 'Lambda(_d, sin(_d)).(X.T*X)' + + lamda = Lambda(x, 1/x) + expr = (n*X).applyfunc(lamda) + assert str(expr) == 'Lambda(x, 1/x).(n*X)' + + +def test_Subs_printing(): + assert str(Subs(x, (x,), (1,))) == 'Subs(x, x, 1)' + assert str(Subs(x + y, (x, y), (1, 2))) == 'Subs(x + y, (x, y), (1, 2))' + + +def test_issue_15716(): + e = Integral(factorial(x), (x, -oo, oo)) + assert e.as_terms() == ([(e, ((1.0, 0.0), (1,), ()))], [e]) + + +def test_str_special_matrices(): + from sympy.matrices import Identity, ZeroMatrix, OneMatrix + assert str(Identity(4)) == 'I' + assert str(ZeroMatrix(2, 2)) == '0' + assert str(OneMatrix(2, 2)) == '1' + + +def test_issue_14567(): + assert factorial(Sum(-1, (x, 0, 0))) + y # doesn't raise an error + + +def test_issue_21823(): + assert str(Partition([1, 2])) == 'Partition({1, 2})' + assert str(Partition({1, 2})) == 'Partition({1, 2})' + + +def test_issue_22689(): + assert str(Mul(Pow(x,-2, evaluate=False), Pow(3,-1,evaluate=False), evaluate=False)) == "1/(x**2*3)" + + +def test_issue_21119_21460(): + ss = lambda x: str(S(x, evaluate=False)) + assert ss('4/2') == '4/2' + assert ss('4/-2') == '4/(-2)' + assert ss('-4/2') == '-4/2' + assert ss('-4/-2') == '-4/(-2)' + assert ss('-2*3/-1') == '-2*3/(-1)' + assert ss('-2*3/-1/2') == '-2*3/(-1*2)' + assert ss('4/2/1') == '4/(2*1)' + assert ss('-2/-1/2') == '-2/(-1*2)' + assert ss('2*3*4**(-2*3)') == '2*3/4**(2*3)' + assert ss('2*3*1*4**(-2*3)') == '2*3*1/4**(2*3)' + + +def test_Str(): + from sympy.core.symbol import Str + assert str(Str('x')) == 'x' + assert sstrrepr(Str('x')) == "Str('x')" + + +def test_diffgeom(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseScalarField + x,y = symbols('x y', real=True) + m = Manifold('M', 2) + assert str(m) == "M" + p = Patch('P', m) + assert str(p) == "P" + rect = CoordSystem('rect', p, [x, y]) + assert str(rect) == "rect" + b = BaseScalarField(rect, 0) + assert str(b) == "x" + +def test_NDimArray(): + assert sstr(NDimArray(1.0), full_prec=True) == '1.00000000000000' + assert sstr(NDimArray(1.0), full_prec=False) == '1.0' + assert sstr(NDimArray([1.0, 2.0]), full_prec=True) == '[1.00000000000000, 2.00000000000000]' + assert sstr(NDimArray([1.0, 2.0]), full_prec=False) == '[1.0, 2.0]' + assert sstr(NDimArray([], (0,))) == 'ImmutableDenseNDimArray([], (0,))' + assert sstr(NDimArray([], (0, 0))) == 'ImmutableDenseNDimArray([], (0, 0))' + assert sstr(NDimArray([], (0, 1))) == 'ImmutableDenseNDimArray([], (0, 1))' + assert sstr(NDimArray([], (1, 0))) == 'ImmutableDenseNDimArray([], (1, 0))' + +def test_Predicate(): + assert sstr(Q.even) == 'Q.even' + +def test_AppliedPredicate(): + assert sstr(Q.even(x)) == 'Q.even(x)' + +def test_printing_str_array_expressions(): + assert sstr(ArraySymbol("A", (2, 3, 4))) == "A" + assert sstr(ArrayElement("A", (2, 1/(1-x), 0))) == "A[2, 1/(1 - x), 0]" + M = MatrixSymbol("M", 3, 3) + N = MatrixSymbol("N", 3, 3) + assert sstr(ArrayElement(M*N, [x, 0])) == "(M*N)[x, 0]" + +def test_printing_stats(): + # issue 24132 + x = RandomSymbol("x") + y = RandomSymbol("y") + z1 = Probability(x > 0)*Identity(2) + z2 = Expectation(x)*Identity(2) + z3 = Variance(x)*Identity(2) + z4 = Covariance(x, y) * Identity(2) + + assert str(z1) == "Probability(x > 0)*I" + assert str(z2) == "Expectation(x)*I" + assert str(z3) == "Variance(x)*I" + assert str(z4) == "Covariance(x, y)*I" + assert z1.is_commutative == False + assert z2.is_commutative == False + assert z3.is_commutative == False + assert z4.is_commutative == False + assert z2._eval_is_commutative() == False + assert z3._eval_is_commutative() == False + assert z4._eval_is_commutative() == False diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tableform.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tableform.py new file mode 100644 index 0000000000000000000000000000000000000000..05802dd104a12f2f53d137167ecf31d201ff8dfc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tableform.py @@ -0,0 +1,182 @@ +from sympy.core.singleton import S +from sympy.printing.tableform import TableForm +from sympy.printing.latex import latex +from sympy.abc import x +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin +from sympy.testing.pytest import raises + +from textwrap import dedent + + +def test_TableForm(): + s = str(TableForm([["a", "b"], ["c", "d"], ["e", 0]], + headings="automatic")) + assert s == ( + ' | 1 2\n' + '-------\n' + '1 | a b\n' + '2 | c d\n' + '3 | e ' + ) + s = str(TableForm([["a", "b"], ["c", "d"], ["e", 0]], + headings="automatic", wipe_zeros=False)) + assert s == dedent('''\ + | 1 2 + ------- + 1 | a b + 2 | c d + 3 | e 0''') + s = str(TableForm([[x**2, "b"], ["c", x**2], ["e", "f"]], + headings=("automatic", None))) + assert s == ( + '1 | x**2 b \n' + '2 | c x**2\n' + '3 | e f ' + ) + s = str(TableForm([["a", "b"], ["c", "d"], ["e", "f"]], + headings=(None, "automatic"))) + assert s == dedent('''\ + 1 2 + --- + a b + c d + e f''') + s = str(TableForm([[5, 7], [4, 2], [10, 3]], + headings=[["Group A", "Group B", "Group C"], ["y1", "y2"]])) + assert s == ( + ' | y1 y2\n' + '---------------\n' + 'Group A | 5 7 \n' + 'Group B | 4 2 \n' + 'Group C | 10 3 ' + ) + raises( + ValueError, + lambda: + TableForm( + [[5, 7], [4, 2], [10, 3]], + headings=[["Group A", "Group B", "Group C"], ["y1", "y2"]], + alignments="middle") + ) + s = str(TableForm([[5, 7], [4, 2], [10, 3]], + headings=[["Group A", "Group B", "Group C"], ["y1", "y2"]], + alignments="right")) + assert s == dedent('''\ + | y1 y2 + --------------- + Group A | 5 7 + Group B | 4 2 + Group C | 10 3''') + + # other alignment permutations + d = [[1, 100], [100, 1]] + s = TableForm(d, headings=(('xxx', 'x'), None), alignments='l') + assert str(s) == ( + 'xxx | 1 100\n' + ' x | 100 1 ' + ) + s = TableForm(d, headings=(('xxx', 'x'), None), alignments='lr') + assert str(s) == dedent('''\ + xxx | 1 100 + x | 100 1''') + s = TableForm(d, headings=(('xxx', 'x'), None), alignments='clr') + assert str(s) == dedent('''\ + xxx | 1 100 + x | 100 1''') + + s = TableForm(d, headings=(('xxx', 'x'), None)) + assert str(s) == ( + 'xxx | 1 100\n' + ' x | 100 1 ' + ) + + raises(ValueError, lambda: TableForm(d, alignments='clr')) + + #pad + s = str(TableForm([[None, "-", 2], [1]], pad='?')) + assert s == dedent('''\ + ? - 2 + 1 ? ?''') + + +def test_TableForm_latex(): + s = latex(TableForm([[0, x**3], ["c", S.One/4], [sqrt(x), sin(x**2)]], + wipe_zeros=True, headings=("automatic", "automatic"))) + assert s == ( + '\\begin{tabular}{r l l}\n' + ' & 1 & 2 \\\\\n' + '\\hline\n' + '1 & & $x^{3}$ \\\\\n' + '2 & $c$ & $\\frac{1}{4}$ \\\\\n' + '3 & $\\sqrt{x}$ & $\\sin{\\left(x^{2} \\right)}$ \\\\\n' + '\\end{tabular}' + ) + s = latex(TableForm([[0, x**3], ["c", S.One/4], [sqrt(x), sin(x**2)]], + wipe_zeros=True, headings=("automatic", "automatic"), alignments='l')) + assert s == ( + '\\begin{tabular}{r l l}\n' + ' & 1 & 2 \\\\\n' + '\\hline\n' + '1 & & $x^{3}$ \\\\\n' + '2 & $c$ & $\\frac{1}{4}$ \\\\\n' + '3 & $\\sqrt{x}$ & $\\sin{\\left(x^{2} \\right)}$ \\\\\n' + '\\end{tabular}' + ) + s = latex(TableForm([[0, x**3], ["c", S.One/4], [sqrt(x), sin(x**2)]], + wipe_zeros=True, headings=("automatic", "automatic"), alignments='l'*3)) + assert s == ( + '\\begin{tabular}{l l l}\n' + ' & 1 & 2 \\\\\n' + '\\hline\n' + '1 & & $x^{3}$ \\\\\n' + '2 & $c$ & $\\frac{1}{4}$ \\\\\n' + '3 & $\\sqrt{x}$ & $\\sin{\\left(x^{2} \\right)}$ \\\\\n' + '\\end{tabular}' + ) + s = latex(TableForm([["a", x**3], ["c", S.One/4], [sqrt(x), sin(x**2)]], + headings=("automatic", "automatic"))) + assert s == ( + '\\begin{tabular}{r l l}\n' + ' & 1 & 2 \\\\\n' + '\\hline\n' + '1 & $a$ & $x^{3}$ \\\\\n' + '2 & $c$ & $\\frac{1}{4}$ \\\\\n' + '3 & $\\sqrt{x}$ & $\\sin{\\left(x^{2} \\right)}$ \\\\\n' + '\\end{tabular}' + ) + s = latex(TableForm([["a", x**3], ["c", S.One/4], [sqrt(x), sin(x**2)]], + formats=['(%s)', None], headings=("automatic", "automatic"))) + assert s == ( + '\\begin{tabular}{r l l}\n' + ' & 1 & 2 \\\\\n' + '\\hline\n' + '1 & (a) & $x^{3}$ \\\\\n' + '2 & (c) & $\\frac{1}{4}$ \\\\\n' + '3 & (sqrt(x)) & $\\sin{\\left(x^{2} \\right)}$ \\\\\n' + '\\end{tabular}' + ) + + def neg_in_paren(x, i, j): + if i % 2: + return ('(%s)' if x < 0 else '%s') % x + else: + pass # use default print + s = latex(TableForm([[-1, 2], [-3, 4]], + formats=[neg_in_paren]*2, headings=("automatic", "automatic"))) + assert s == ( + '\\begin{tabular}{r l l}\n' + ' & 1 & 2 \\\\\n' + '\\hline\n' + '1 & -1 & 2 \\\\\n' + '2 & (-3) & 4 \\\\\n' + '\\end{tabular}' + ) + s = latex(TableForm([["a", x**3], ["c", S.One/4], [sqrt(x), sin(x**2)]])) + assert s == ( + '\\begin{tabular}{l l}\n' + '$a$ & $x^{3}$ \\\\\n' + '$c$ & $\\frac{1}{4}$ \\\\\n' + '$\\sqrt{x}$ & $\\sin{\\left(x^{2} \\right)}$ \\\\\n' + '\\end{tabular}' + ) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tensorflow.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tensorflow.py new file mode 100644 index 0000000000000000000000000000000000000000..e9c92cd17b13e1148ebf83f13f66854b983491fe --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tensorflow.py @@ -0,0 +1,493 @@ +import random +from sympy.core.function import Derivative +from sympy.core.symbol import symbols +from sympy import Piecewise +from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayAdd, \ + PermuteDims, ArrayDiagonal +from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt +from sympy.external import import_module +from sympy.functions import \ + Abs, ceiling, exp, floor, sign, sin, asin, sqrt, cos, \ + acos, tan, atan, atan2, cosh, acosh, sinh, asinh, tanh, atanh, \ + re, im, arg, erf, loggamma, log +from sympy.codegen.cfunctions import isnan, isinf +from sympy.matrices import Matrix, MatrixBase, eye, randMatrix +from sympy.matrices.expressions import \ + Determinant, HadamardProduct, Inverse, MatrixSymbol, Trace +from sympy.printing.tensorflow import tensorflow_code +from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array +from sympy.utilities.lambdify import lambdify +from sympy.testing.pytest import skip +from sympy.testing.pytest import XFAIL + + +tf = tensorflow = import_module("tensorflow") + +if tensorflow: + # Hide Tensorflow warnings + import os + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' + + +M = MatrixSymbol("M", 3, 3) +N = MatrixSymbol("N", 3, 3) +P = MatrixSymbol("P", 3, 3) +Q = MatrixSymbol("Q", 3, 3) + +x, y, z, t = symbols("x y z t") + +if tf is not None: + llo = [list(range(i, i+3)) for i in range(0, 9, 3)] + m3x3 = tf.constant(llo) + m3x3sympy = Matrix(llo) + + +def _compare_tensorflow_matrix(variables, expr, use_float=False): + f = lambdify(variables, expr, 'tensorflow') + if not use_float: + random_matrices = [randMatrix(v.rows, v.cols) for v in variables] + else: + random_matrices = [randMatrix(v.rows, v.cols)/100. for v in variables] + + graph = tf.Graph() + r = None + with graph.as_default(): + random_variables = [eval(tensorflow_code(i)) for i in random_matrices] + session = tf.compat.v1.Session(graph=graph) + r = session.run(f(*random_variables)) + + e = expr.subs(dict(zip(variables, random_matrices))) + e = e.doit() + if e.is_Matrix: + if not isinstance(e, MatrixBase): + e = e.as_explicit() + e = e.tolist() + + if not use_float: + assert (r == e).all() + else: + r = [i for row in r for i in row] + e = [i for row in e for i in row] + assert all( + abs(a-b) < 10**-(4-int(log(abs(a), 10))) for a, b in zip(r, e)) + + +# Creating a custom inverse test. +# See https://github.com/sympy/sympy/issues/18469 +def _compare_tensorflow_matrix_inverse(variables, expr, use_float=False): + f = lambdify(variables, expr, 'tensorflow') + if not use_float: + random_matrices = [eye(v.rows, v.cols)*4 for v in variables] + else: + random_matrices = [eye(v.rows, v.cols)*3.14 for v in variables] + + graph = tf.Graph() + r = None + with graph.as_default(): + random_variables = [eval(tensorflow_code(i)) for i in random_matrices] + session = tf.compat.v1.Session(graph=graph) + r = session.run(f(*random_variables)) + + e = expr.subs(dict(zip(variables, random_matrices))) + e = e.doit() + if e.is_Matrix: + if not isinstance(e, MatrixBase): + e = e.as_explicit() + e = e.tolist() + + if not use_float: + assert (r == e).all() + else: + r = [i for row in r for i in row] + e = [i for row in e for i in row] + assert all( + abs(a-b) < 10**-(4-int(log(abs(a), 10))) for a, b in zip(r, e)) + + +def _compare_tensorflow_matrix_scalar(variables, expr): + f = lambdify(variables, expr, 'tensorflow') + random_matrices = [ + randMatrix(v.rows, v.cols).evalf() / 100 for v in variables] + + graph = tf.Graph() + r = None + with graph.as_default(): + random_variables = [eval(tensorflow_code(i)) for i in random_matrices] + session = tf.compat.v1.Session(graph=graph) + r = session.run(f(*random_variables)) + + e = expr.subs(dict(zip(variables, random_matrices))) + e = e.doit() + assert abs(r-e) < 10**-6 + + +def _compare_tensorflow_scalar( + variables, expr, rng=lambda: random.randint(0, 10)): + f = lambdify(variables, expr, 'tensorflow') + rvs = [rng() for v in variables] + + graph = tf.Graph() + r = None + with graph.as_default(): + tf_rvs = [eval(tensorflow_code(i)) for i in rvs] + session = tf.compat.v1.Session(graph=graph) + r = session.run(f(*tf_rvs)) + + e = expr.subs(dict(zip(variables, rvs))).evalf().doit() + assert abs(r-e) < 10**-6 + + +def _compare_tensorflow_relational( + variables, expr, rng=lambda: random.randint(0, 10)): + f = lambdify(variables, expr, 'tensorflow') + rvs = [rng() for v in variables] + + graph = tf.Graph() + r = None + with graph.as_default(): + tf_rvs = [eval(tensorflow_code(i)) for i in rvs] + session = tf.compat.v1.Session(graph=graph) + r = session.run(f(*tf_rvs)) + + e = expr.subs(dict(zip(variables, rvs))).doit() + assert r == e + + +def test_tensorflow_printing(): + assert tensorflow_code(eye(3)) == \ + "tensorflow.constant([[1, 0, 0], [0, 1, 0], [0, 0, 1]])" + + expr = Matrix([[x, sin(y)], [exp(z), -t]]) + assert tensorflow_code(expr) == \ + "tensorflow.Variable(" \ + "[[x, tensorflow.math.sin(y)]," \ + " [tensorflow.math.exp(z), -t]])" + + +# This (random) test is XFAIL because it fails occasionally +# See https://github.com/sympy/sympy/issues/18469 +@XFAIL +def test_tensorflow_math(): + if not tf: + skip("TensorFlow not installed") + + expr = Abs(x) + assert tensorflow_code(expr) == "tensorflow.math.abs(x)" + _compare_tensorflow_scalar((x,), expr) + + expr = sign(x) + assert tensorflow_code(expr) == "tensorflow.math.sign(x)" + _compare_tensorflow_scalar((x,), expr) + + expr = ceiling(x) + assert tensorflow_code(expr) == "tensorflow.math.ceil(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = floor(x) + assert tensorflow_code(expr) == "tensorflow.math.floor(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = exp(x) + assert tensorflow_code(expr) == "tensorflow.math.exp(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = sqrt(x) + assert tensorflow_code(expr) == "tensorflow.math.sqrt(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = x ** 4 + assert tensorflow_code(expr) == "tensorflow.math.pow(x, 4)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = cos(x) + assert tensorflow_code(expr) == "tensorflow.math.cos(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = acos(x) + assert tensorflow_code(expr) == "tensorflow.math.acos(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.uniform(0, 0.95)) + + expr = sin(x) + assert tensorflow_code(expr) == "tensorflow.math.sin(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = asin(x) + assert tensorflow_code(expr) == "tensorflow.math.asin(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = tan(x) + assert tensorflow_code(expr) == "tensorflow.math.tan(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = atan(x) + assert tensorflow_code(expr) == "tensorflow.math.atan(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = atan2(y, x) + assert tensorflow_code(expr) == "tensorflow.math.atan2(y, x)" + _compare_tensorflow_scalar((y, x), expr, rng=lambda: random.random()) + + expr = cosh(x) + assert tensorflow_code(expr) == "tensorflow.math.cosh(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.random()) + + expr = acosh(x) + assert tensorflow_code(expr) == "tensorflow.math.acosh(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.uniform(1, 2)) + + expr = sinh(x) + assert tensorflow_code(expr) == "tensorflow.math.sinh(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.uniform(1, 2)) + + expr = asinh(x) + assert tensorflow_code(expr) == "tensorflow.math.asinh(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.uniform(1, 2)) + + expr = tanh(x) + assert tensorflow_code(expr) == "tensorflow.math.tanh(x)" + _compare_tensorflow_scalar((x,), expr, rng=lambda: random.uniform(1, 2)) + + expr = atanh(x) + assert tensorflow_code(expr) == "tensorflow.math.atanh(x)" + _compare_tensorflow_scalar( + (x,), expr, rng=lambda: random.uniform(-.5, .5)) + + expr = erf(x) + assert tensorflow_code(expr) == "tensorflow.math.erf(x)" + _compare_tensorflow_scalar( + (x,), expr, rng=lambda: random.random()) + + expr = loggamma(x) + assert tensorflow_code(expr) == "tensorflow.math.lgamma(x)" + _compare_tensorflow_scalar( + (x,), expr, rng=lambda: random.random()) + + +def test_tensorflow_complexes(): + assert tensorflow_code(re(x)) == "tensorflow.math.real(x)" + assert tensorflow_code(im(x)) == "tensorflow.math.imag(x)" + assert tensorflow_code(arg(x)) == "tensorflow.math.angle(x)" + + +def test_tensorflow_relational(): + if not tf: + skip("TensorFlow not installed") + + expr = Eq(x, y) + assert tensorflow_code(expr) == "tensorflow.math.equal(x, y)" + _compare_tensorflow_relational((x, y), expr) + + expr = Ne(x, y) + assert tensorflow_code(expr) == "tensorflow.math.not_equal(x, y)" + _compare_tensorflow_relational((x, y), expr) + + expr = Ge(x, y) + assert tensorflow_code(expr) == "tensorflow.math.greater_equal(x, y)" + _compare_tensorflow_relational((x, y), expr) + + expr = Gt(x, y) + assert tensorflow_code(expr) == "tensorflow.math.greater(x, y)" + _compare_tensorflow_relational((x, y), expr) + + expr = Le(x, y) + assert tensorflow_code(expr) == "tensorflow.math.less_equal(x, y)" + _compare_tensorflow_relational((x, y), expr) + + expr = Lt(x, y) + assert tensorflow_code(expr) == "tensorflow.math.less(x, y)" + _compare_tensorflow_relational((x, y), expr) + + +# This (random) test is XFAIL because it fails occasionally +# See https://github.com/sympy/sympy/issues/18469 +@XFAIL +def test_tensorflow_matrices(): + if not tf: + skip("TensorFlow not installed") + + expr = M + assert tensorflow_code(expr) == "M" + _compare_tensorflow_matrix((M,), expr) + + expr = M + N + assert tensorflow_code(expr) == "tensorflow.math.add(M, N)" + _compare_tensorflow_matrix((M, N), expr) + + expr = M * N + assert tensorflow_code(expr) == "tensorflow.linalg.matmul(M, N)" + _compare_tensorflow_matrix((M, N), expr) + + expr = HadamardProduct(M, N) + assert tensorflow_code(expr) == "tensorflow.math.multiply(M, N)" + _compare_tensorflow_matrix((M, N), expr) + + expr = M*N*P*Q + assert tensorflow_code(expr) == \ + "tensorflow.linalg.matmul(" \ + "tensorflow.linalg.matmul(" \ + "tensorflow.linalg.matmul(M, N), P), Q)" + _compare_tensorflow_matrix((M, N, P, Q), expr) + + expr = M**3 + assert tensorflow_code(expr) == \ + "tensorflow.linalg.matmul(tensorflow.linalg.matmul(M, M), M)" + _compare_tensorflow_matrix((M,), expr) + + expr = Trace(M) + assert tensorflow_code(expr) == "tensorflow.linalg.trace(M)" + _compare_tensorflow_matrix((M,), expr) + + expr = Determinant(M) + assert tensorflow_code(expr) == "tensorflow.linalg.det(M)" + _compare_tensorflow_matrix_scalar((M,), expr) + + expr = Inverse(M) + assert tensorflow_code(expr) == "tensorflow.linalg.inv(M)" + _compare_tensorflow_matrix_inverse((M,), expr, use_float=True) + + expr = M.T + assert tensorflow_code(expr, tensorflow_version='1.14') == \ + "tensorflow.linalg.matrix_transpose(M)" + assert tensorflow_code(expr, tensorflow_version='1.13') == \ + "tensorflow.matrix_transpose(M)" + + _compare_tensorflow_matrix((M,), expr) + + +def test_codegen_einsum(): + if not tf: + skip("TensorFlow not installed") + + graph = tf.Graph() + with graph.as_default(): + session = tf.compat.v1.Session(graph=graph) + + M = MatrixSymbol("M", 2, 2) + N = MatrixSymbol("N", 2, 2) + + cg = convert_matrix_to_array(M * N) + f = lambdify((M, N), cg, 'tensorflow') + + ma = tf.constant([[1, 2], [3, 4]]) + mb = tf.constant([[1,-2], [-1, 3]]) + y = session.run(f(ma, mb)) + c = session.run(tf.matmul(ma, mb)) + assert (y == c).all() + + +def test_codegen_extra(): + if not tf: + skip("TensorFlow not installed") + + graph = tf.Graph() + with graph.as_default(): + session = tf.compat.v1.Session() + + M = MatrixSymbol("M", 2, 2) + N = MatrixSymbol("N", 2, 2) + P = MatrixSymbol("P", 2, 2) + Q = MatrixSymbol("Q", 2, 2) + ma = tf.constant([[1, 2], [3, 4]]) + mb = tf.constant([[1,-2], [-1, 3]]) + mc = tf.constant([[2, 0], [1, 2]]) + md = tf.constant([[1,-1], [4, 7]]) + + cg = ArrayTensorProduct(M, N) + assert tensorflow_code(cg) == \ + 'tensorflow.linalg.einsum("ab,cd", M, N)' + f = lambdify((M, N), cg, 'tensorflow') + y = session.run(f(ma, mb)) + c = session.run(tf.einsum("ij,kl", ma, mb)) + assert (y == c).all() + + cg = ArrayAdd(M, N) + assert tensorflow_code(cg) == 'tensorflow.math.add(M, N)' + f = lambdify((M, N), cg, 'tensorflow') + y = session.run(f(ma, mb)) + c = session.run(ma + mb) + assert (y == c).all() + + cg = ArrayAdd(M, N, P) + assert tensorflow_code(cg) == \ + 'tensorflow.math.add(tensorflow.math.add(M, N), P)' + f = lambdify((M, N, P), cg, 'tensorflow') + y = session.run(f(ma, mb, mc)) + c = session.run(ma + mb + mc) + assert (y == c).all() + + cg = ArrayAdd(M, N, P, Q) + assert tensorflow_code(cg) == \ + 'tensorflow.math.add(' \ + 'tensorflow.math.add(tensorflow.math.add(M, N), P), Q)' + f = lambdify((M, N, P, Q), cg, 'tensorflow') + y = session.run(f(ma, mb, mc, md)) + c = session.run(ma + mb + mc + md) + assert (y == c).all() + + cg = PermuteDims(M, [1, 0]) + assert tensorflow_code(cg) == 'tensorflow.transpose(M, [1, 0])' + f = lambdify((M,), cg, 'tensorflow') + y = session.run(f(ma)) + c = session.run(tf.transpose(ma)) + assert (y == c).all() + + cg = PermuteDims(ArrayTensorProduct(M, N), [1, 2, 3, 0]) + assert tensorflow_code(cg) == \ + 'tensorflow.transpose(' \ + 'tensorflow.linalg.einsum("ab,cd", M, N), [1, 2, 3, 0])' + f = lambdify((M, N), cg, 'tensorflow') + y = session.run(f(ma, mb)) + c = session.run(tf.transpose(tf.einsum("ab,cd", ma, mb), [1, 2, 3, 0])) + assert (y == c).all() + + cg = ArrayDiagonal(ArrayTensorProduct(M, N), (1, 2)) + assert tensorflow_code(cg) == \ + 'tensorflow.linalg.einsum("ab,bc->acb", M, N)' + f = lambdify((M, N), cg, 'tensorflow') + y = session.run(f(ma, mb)) + c = session.run(tf.einsum("ab,bc->acb", ma, mb)) + assert (y == c).all() + + +def test_MatrixElement_printing(): + A = MatrixSymbol("A", 1, 3) + B = MatrixSymbol("B", 1, 3) + C = MatrixSymbol("C", 1, 3) + + assert tensorflow_code(A[0, 0]) == "A[0, 0]" + assert tensorflow_code(3 * A[0, 0]) == "3*A[0, 0]" + + F = C[0, 0].subs(C, A - B) + assert tensorflow_code(F) == "(tensorflow.math.add((-1)*B, A))[0, 0]" + + +def test_tensorflow_Derivative(): + expr = Derivative(sin(x), x) + assert tensorflow_code(expr) == \ + "tensorflow.gradients(tensorflow.math.sin(x), x)[0]" + +def test_tensorflow_isnan_isinf(): + if not tf: + skip("TensorFlow not installed") + + # Test for isnan + x = symbols("x") + # Return 0 if x is of nan value, and 1 otherwise + expression = Piecewise((0.0, isnan(x)), (1.0, True)) + printed_code = tensorflow_code(expression) + expected_printed_code = "tensorflow.where(tensorflow.math.is_nan(x), 0.0, 1.0)" + assert tensorflow_code(expression) == expected_printed_code, f"Incorrect printed result {printed_code}, expected {expected_printed_code}" + for _input, _expected in [(float('nan'), 0.0), (float('inf'), 1.0), (float('-inf'), 1.0), (1.0, 1.0)]: + _output = lambdify((x), expression, modules="tensorflow")(x=tf.constant([_input])) + assert (_output == _expected).numpy().all() + + # Test for isinf + x = symbols("x") + # Return 0 if x is of nan value, and 1 otherwise + expression = Piecewise((0.0, isinf(x)), (1.0, True)) + printed_code = tensorflow_code(expression) + expected_printed_code = "tensorflow.where(tensorflow.math.is_inf(x), 0.0, 1.0)" + assert tensorflow_code(expression) == expected_printed_code, f"Incorrect printed result {printed_code}, expected {expected_printed_code}" + for _input, _expected in [(float('inf'), 0.0), (float('-inf'), 0.0), (float('nan'), 1.0), (1.0, 1.0)]: + _output = lambdify((x), expression, modules="tensorflow")(x=tf.constant([_input])) + assert (_output == _expected).numpy().all() diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_theanocode.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_theanocode.py new file mode 100644 index 0000000000000000000000000000000000000000..6ff40f78cb4de16149cb5e780756b7e32b574b71 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_theanocode.py @@ -0,0 +1,639 @@ +""" +Important note on tests in this module - the Theano printing functions use a +global cache by default, which means that tests using it will modify global +state and thus not be independent from each other. Instead of using the "cache" +keyword argument each time, this module uses the theano_code_ and +theano_function_ functions defined below which default to using a new, empty +cache instead. +""" + +import logging + +from sympy.external import import_module +from sympy.testing.pytest import raises, SKIP, warns_deprecated_sympy + +theanologger = logging.getLogger('theano.configdefaults') +theanologger.setLevel(logging.CRITICAL) +theano = import_module('theano') +theanologger.setLevel(logging.WARNING) + + +if theano: + import numpy as np + ts = theano.scalar + tt = theano.tensor + xt, yt, zt = [tt.scalar(name, 'floatX') for name in 'xyz'] + Xt, Yt, Zt = [tt.tensor('floatX', (False, False), name=n) for n in 'XYZ'] +else: + #bin/test will not execute any tests now + disabled = True + +import sympy as sy +from sympy.core.singleton import S +from sympy.abc import x, y, z, t +from sympy.printing.theanocode import (theano_code, dim_handling, + theano_function) + + +# Default set of matrix symbols for testing - make square so we can both +# multiply and perform elementwise operations between them. +X, Y, Z = [sy.MatrixSymbol(n, 4, 4) for n in 'XYZ'] + +# For testing AppliedUndef +f_t = sy.Function('f')(t) + + +def theano_code_(expr, **kwargs): + """ Wrapper for theano_code that uses a new, empty cache by default. """ + kwargs.setdefault('cache', {}) + with warns_deprecated_sympy(): + return theano_code(expr, **kwargs) + +def theano_function_(inputs, outputs, **kwargs): + """ Wrapper for theano_function that uses a new, empty cache by default. """ + kwargs.setdefault('cache', {}) + with warns_deprecated_sympy(): + return theano_function(inputs, outputs, **kwargs) + + +def fgraph_of(*exprs): + """ Transform SymPy expressions into Theano Computation. + + Parameters + ========== + exprs + SymPy expressions + + Returns + ======= + theano.gof.FunctionGraph + """ + outs = list(map(theano_code_, exprs)) + ins = theano.gof.graph.inputs(outs) + ins, outs = theano.gof.graph.clone(ins, outs) + return theano.gof.FunctionGraph(ins, outs) + + +def theano_simplify(fgraph): + """ Simplify a Theano Computation. + + Parameters + ========== + fgraph : theano.gof.FunctionGraph + + Returns + ======= + theano.gof.FunctionGraph + """ + mode = theano.compile.get_default_mode().excluding("fusion") + fgraph = fgraph.clone() + mode.optimizer.optimize(fgraph) + return fgraph + + +def theq(a, b): + """ Test two Theano objects for equality. + + Also accepts numeric types and lists/tuples of supported types. + + Note - debugprint() has a bug where it will accept numeric types but does + not respect the "file" argument and in this case and instead prints the number + to stdout and returns an empty string. This can lead to tests passing where + they should fail because any two numbers will always compare as equal. To + prevent this we treat numbers as a separate case. + """ + numeric_types = (int, float, np.number) + a_is_num = isinstance(a, numeric_types) + b_is_num = isinstance(b, numeric_types) + + # Compare numeric types using regular equality + if a_is_num or b_is_num: + if not (a_is_num and b_is_num): + return False + + return a == b + + # Compare sequences element-wise + a_is_seq = isinstance(a, (tuple, list)) + b_is_seq = isinstance(b, (tuple, list)) + + if a_is_seq or b_is_seq: + if not (a_is_seq and b_is_seq) or type(a) != type(b): + return False + + return list(map(theq, a)) == list(map(theq, b)) + + # Otherwise, assume debugprint() can handle it + astr = theano.printing.debugprint(a, file='str') + bstr = theano.printing.debugprint(b, file='str') + + # Check for bug mentioned above + for argname, argval, argstr in [('a', a, astr), ('b', b, bstr)]: + if argstr == '': + raise TypeError( + 'theano.printing.debugprint(%s) returned empty string ' + '(%s is instance of %r)' + % (argname, argname, type(argval)) + ) + + return astr == bstr + + +def test_example_symbols(): + """ + Check that the example symbols in this module print to their Theano + equivalents, as many of the other tests depend on this. + """ + assert theq(xt, theano_code_(x)) + assert theq(yt, theano_code_(y)) + assert theq(zt, theano_code_(z)) + assert theq(Xt, theano_code_(X)) + assert theq(Yt, theano_code_(Y)) + assert theq(Zt, theano_code_(Z)) + + +def test_Symbol(): + """ Test printing a Symbol to a theano variable. """ + xx = theano_code_(x) + assert isinstance(xx, (tt.TensorVariable, ts.ScalarVariable)) + assert xx.broadcastable == () + assert xx.name == x.name + + xx2 = theano_code_(x, broadcastables={x: (False,)}) + assert xx2.broadcastable == (False,) + assert xx2.name == x.name + +def test_MatrixSymbol(): + """ Test printing a MatrixSymbol to a theano variable. """ + XX = theano_code_(X) + assert isinstance(XX, tt.TensorVariable) + assert XX.broadcastable == (False, False) + +@SKIP # TODO - this is currently not checked but should be implemented +def test_MatrixSymbol_wrong_dims(): + """ Test MatrixSymbol with invalid broadcastable. """ + bcs = [(), (False,), (True,), (True, False), (False, True,), (True, True)] + for bc in bcs: + with raises(ValueError): + theano_code_(X, broadcastables={X: bc}) + +def test_AppliedUndef(): + """ Test printing AppliedUndef instance, which works similarly to Symbol. """ + ftt = theano_code_(f_t) + assert isinstance(ftt, tt.TensorVariable) + assert ftt.broadcastable == () + assert ftt.name == 'f_t' + + +def test_add(): + expr = x + y + comp = theano_code_(expr) + assert comp.owner.op == theano.tensor.add + +def test_trig(): + assert theq(theano_code_(sy.sin(x)), tt.sin(xt)) + assert theq(theano_code_(sy.tan(x)), tt.tan(xt)) + +def test_many(): + """ Test printing a complex expression with multiple symbols. """ + expr = sy.exp(x**2 + sy.cos(y)) * sy.log(2*z) + comp = theano_code_(expr) + expected = tt.exp(xt**2 + tt.cos(yt)) * tt.log(2*zt) + assert theq(comp, expected) + + +def test_dtype(): + """ Test specifying specific data types through the dtype argument. """ + for dtype in ['float32', 'float64', 'int8', 'int16', 'int32', 'int64']: + assert theano_code_(x, dtypes={x: dtype}).type.dtype == dtype + + # "floatX" type + assert theano_code_(x, dtypes={x: 'floatX'}).type.dtype in ('float32', 'float64') + + # Type promotion + assert theano_code_(x + 1, dtypes={x: 'float32'}).type.dtype == 'float32' + assert theano_code_(x + y, dtypes={x: 'float64', y: 'float32'}).type.dtype == 'float64' + + +def test_broadcastables(): + """ Test the "broadcastables" argument when printing symbol-like objects. """ + + # No restrictions on shape + for s in [x, f_t]: + for bc in [(), (False,), (True,), (False, False), (True, False)]: + assert theano_code_(s, broadcastables={s: bc}).broadcastable == bc + + # TODO - matrix broadcasting? + +def test_broadcasting(): + """ Test "broadcastable" attribute after applying element-wise binary op. """ + + expr = x + y + + cases = [ + [(), (), ()], + [(False,), (False,), (False,)], + [(True,), (False,), (False,)], + [(False, True), (False, False), (False, False)], + [(True, False), (False, False), (False, False)], + ] + + for bc1, bc2, bc3 in cases: + comp = theano_code_(expr, broadcastables={x: bc1, y: bc2}) + assert comp.broadcastable == bc3 + + +def test_MatMul(): + expr = X*Y*Z + expr_t = theano_code_(expr) + assert isinstance(expr_t.owner.op, tt.Dot) + assert theq(expr_t, Xt.dot(Yt).dot(Zt)) + +def test_Transpose(): + assert isinstance(theano_code_(X.T).owner.op, tt.DimShuffle) + +def test_MatAdd(): + expr = X+Y+Z + assert isinstance(theano_code_(expr).owner.op, tt.Elemwise) + + +def test_Rationals(): + assert theq(theano_code_(sy.Integer(2) / 3), tt.true_div(2, 3)) + assert theq(theano_code_(S.Half), tt.true_div(1, 2)) + +def test_Integers(): + assert theano_code_(sy.Integer(3)) == 3 + +def test_factorial(): + n = sy.Symbol('n') + assert theano_code_(sy.factorial(n)) + +def test_Derivative(): + simp = lambda expr: theano_simplify(fgraph_of(expr)) + assert theq(simp(theano_code_(sy.Derivative(sy.sin(x), x, evaluate=False))), + simp(theano.grad(tt.sin(xt), xt))) + + +def test_theano_function_simple(): + """ Test theano_function() with single output. """ + f = theano_function_([x, y], [x+y]) + assert f(2, 3) == 5 + +def test_theano_function_multi(): + """ Test theano_function() with multiple outputs. """ + f = theano_function_([x, y], [x+y, x-y]) + o1, o2 = f(2, 3) + assert o1 == 5 + assert o2 == -1 + +def test_theano_function_numpy(): + """ Test theano_function() vs Numpy implementation. """ + f = theano_function_([x, y], [x+y], dim=1, + dtypes={x: 'float64', y: 'float64'}) + assert np.linalg.norm(f([1, 2], [3, 4]) - np.asarray([4, 6])) < 1e-9 + + f = theano_function_([x, y], [x+y], dtypes={x: 'float64', y: 'float64'}, + dim=1) + xx = np.arange(3).astype('float64') + yy = 2*np.arange(3).astype('float64') + assert np.linalg.norm(f(xx, yy) - 3*np.arange(3)) < 1e-9 + + +def test_theano_function_matrix(): + m = sy.Matrix([[x, y], [z, x + y + z]]) + expected = np.array([[1.0, 2.0], [3.0, 1.0 + 2.0 + 3.0]]) + f = theano_function_([x, y, z], [m]) + np.testing.assert_allclose(f(1.0, 2.0, 3.0), expected) + f = theano_function_([x, y, z], [m], scalar=True) + np.testing.assert_allclose(f(1.0, 2.0, 3.0), expected) + f = theano_function_([x, y, z], [m, m]) + assert isinstance(f(1.0, 2.0, 3.0), type([])) + np.testing.assert_allclose(f(1.0, 2.0, 3.0)[0], expected) + np.testing.assert_allclose(f(1.0, 2.0, 3.0)[1], expected) + +def test_dim_handling(): + assert dim_handling([x], dim=2) == {x: (False, False)} + assert dim_handling([x, y], dims={x: 1, y: 2}) == {x: (False, True), + y: (False, False)} + assert dim_handling([x], broadcastables={x: (False,)}) == {x: (False,)} + +def test_theano_function_kwargs(): + """ + Test passing additional kwargs from theano_function() to theano.function(). + """ + import numpy as np + f = theano_function_([x, y, z], [x+y], dim=1, on_unused_input='ignore', + dtypes={x: 'float64', y: 'float64', z: 'float64'}) + assert np.linalg.norm(f([1, 2], [3, 4], [0, 0]) - np.asarray([4, 6])) < 1e-9 + + f = theano_function_([x, y, z], [x+y], + dtypes={x: 'float64', y: 'float64', z: 'float64'}, + dim=1, on_unused_input='ignore') + xx = np.arange(3).astype('float64') + yy = 2*np.arange(3).astype('float64') + zz = 2*np.arange(3).astype('float64') + assert np.linalg.norm(f(xx, yy, zz) - 3*np.arange(3)) < 1e-9 + +def test_theano_function_scalar(): + """ Test the "scalar" argument to theano_function(). """ + + args = [ + ([x, y], [x + y], None, [0]), # Single 0d output + ([X, Y], [X + Y], None, [2]), # Single 2d output + ([x, y], [x + y], {x: 0, y: 1}, [1]), # Single 1d output + ([x, y], [x + y, x - y], None, [0, 0]), # Two 0d outputs + ([x, y, X, Y], [x + y, X + Y], None, [0, 2]), # One 0d output, one 2d + ] + + # Create and test functions with and without the scalar setting + for inputs, outputs, in_dims, out_dims in args: + for scalar in [False, True]: + + f = theano_function_(inputs, outputs, dims=in_dims, scalar=scalar) + + # Check the theano_function attribute is set whether wrapped or not + assert isinstance(f.theano_function, theano.compile.function_module.Function) + + # Feed in inputs of the appropriate size and get outputs + in_values = [ + np.ones([1 if bc else 5 for bc in i.type.broadcastable]) + for i in f.theano_function.input_storage + ] + out_values = f(*in_values) + if not isinstance(out_values, list): + out_values = [out_values] + + # Check output types and shapes + assert len(out_dims) == len(out_values) + for d, value in zip(out_dims, out_values): + + if scalar and d == 0: + # Should have been converted to a scalar value + assert isinstance(value, np.number) + + else: + # Otherwise should be an array + assert isinstance(value, np.ndarray) + assert value.ndim == d + +def test_theano_function_bad_kwarg(): + """ + Passing an unknown keyword argument to theano_function() should raise an + exception. + """ + raises(Exception, lambda : theano_function_([x], [x+1], foobar=3)) + + +def test_slice(): + assert theano_code_(slice(1, 2, 3)) == slice(1, 2, 3) + + def theq_slice(s1, s2): + for attr in ['start', 'stop', 'step']: + a1 = getattr(s1, attr) + a2 = getattr(s2, attr) + if a1 is None or a2 is None: + if not (a1 is None or a2 is None): + return False + elif not theq(a1, a2): + return False + return True + + dtypes = {x: 'int32', y: 'int32'} + assert theq_slice(theano_code_(slice(x, y), dtypes=dtypes), slice(xt, yt)) + assert theq_slice(theano_code_(slice(1, x, 3), dtypes=dtypes), slice(1, xt, 3)) + +def test_MatrixSlice(): + from theano import Constant + + cache = {} + + n = sy.Symbol('n', integer=True) + X = sy.MatrixSymbol('X', n, n) + + Y = X[1:2:3, 4:5:6] + Yt = theano_code_(Y, cache=cache) + + s = ts.Scalar('int64') + assert tuple(Yt.owner.op.idx_list) == (slice(s, s, s), slice(s, s, s)) + assert Yt.owner.inputs[0] == theano_code_(X, cache=cache) + # == doesn't work in theano like it does in SymPy. You have to use + # equals. + assert all(Yt.owner.inputs[i].equals(Constant(s, i)) for i in range(1, 7)) + + k = sy.Symbol('k') + theano_code_(k, dtypes={k: 'int32'}) + start, stop, step = 4, k, 2 + Y = X[start:stop:step] + Yt = theano_code_(Y, dtypes={n: 'int32', k: 'int32'}) + # assert Yt.owner.op.idx_list[0].stop == kt + +def test_BlockMatrix(): + n = sy.Symbol('n', integer=True) + A, B, C, D = [sy.MatrixSymbol(name, n, n) for name in 'ABCD'] + At, Bt, Ct, Dt = map(theano_code_, (A, B, C, D)) + Block = sy.BlockMatrix([[A, B], [C, D]]) + Blockt = theano_code_(Block) + solutions = [tt.join(0, tt.join(1, At, Bt), tt.join(1, Ct, Dt)), + tt.join(1, tt.join(0, At, Ct), tt.join(0, Bt, Dt))] + assert any(theq(Blockt, solution) for solution in solutions) + +@SKIP +def test_BlockMatrix_Inverse_execution(): + k, n = 2, 4 + dtype = 'float32' + A = sy.MatrixSymbol('A', n, k) + B = sy.MatrixSymbol('B', n, n) + inputs = A, B + output = B.I*A + + cutsizes = {A: [(n//2, n//2), (k//2, k//2)], + B: [(n//2, n//2), (n//2, n//2)]} + cutinputs = [sy.blockcut(i, *cutsizes[i]) for i in inputs] + cutoutput = output.subs(dict(zip(inputs, cutinputs))) + + dtypes = dict(zip(inputs, [dtype]*len(inputs))) + f = theano_function_(inputs, [output], dtypes=dtypes, cache={}) + fblocked = theano_function_(inputs, [sy.block_collapse(cutoutput)], + dtypes=dtypes, cache={}) + + ninputs = [np.random.rand(*x.shape).astype(dtype) for x in inputs] + ninputs = [np.arange(n*k).reshape(A.shape).astype(dtype), + np.eye(n).astype(dtype)] + ninputs[1] += np.ones(B.shape)*1e-5 + + assert np.allclose(f(*ninputs), fblocked(*ninputs), rtol=1e-5) + +def test_DenseMatrix(): + t = sy.Symbol('theta') + for MatrixType in [sy.Matrix, sy.ImmutableMatrix]: + X = MatrixType([[sy.cos(t), -sy.sin(t)], [sy.sin(t), sy.cos(t)]]) + tX = theano_code_(X) + assert isinstance(tX, tt.TensorVariable) + assert tX.owner.op == tt.join_ + + +def test_cache_basic(): + """ Test single symbol-like objects are cached when printed by themselves. """ + + # Pairs of objects which should be considered equivalent with respect to caching + pairs = [ + (x, sy.Symbol('x')), + (X, sy.MatrixSymbol('X', *X.shape)), + (f_t, sy.Function('f')(sy.Symbol('t'))), + ] + + for s1, s2 in pairs: + cache = {} + st = theano_code_(s1, cache=cache) + + # Test hit with same instance + assert theano_code_(s1, cache=cache) is st + + # Test miss with same instance but new cache + assert theano_code_(s1, cache={}) is not st + + # Test hit with different but equivalent instance + assert theano_code_(s2, cache=cache) is st + +def test_global_cache(): + """ Test use of the global cache. """ + from sympy.printing.theanocode import global_cache + + backup = dict(global_cache) + try: + # Temporarily empty global cache + global_cache.clear() + + for s in [x, X, f_t]: + with warns_deprecated_sympy(): + st = theano_code(s) + assert theano_code(s) is st + + finally: + # Restore global cache + global_cache.update(backup) + +def test_cache_types_distinct(): + """ + Test that symbol-like objects of different types (Symbol, MatrixSymbol, + AppliedUndef) are distinguished by the cache even if they have the same + name. + """ + symbols = [sy.Symbol('f_t'), sy.MatrixSymbol('f_t', 4, 4), f_t] + + cache = {} # Single shared cache + printed = {} + + for s in symbols: + st = theano_code_(s, cache=cache) + assert st not in printed.values() + printed[s] = st + + # Check all printed objects are distinct + assert len(set(map(id, printed.values()))) == len(symbols) + + # Check retrieving + for s, st in printed.items(): + with warns_deprecated_sympy(): + assert theano_code(s, cache=cache) is st + +def test_symbols_are_created_once(): + """ + Test that a symbol is cached and reused when it appears in an expression + more than once. + """ + expr = sy.Add(x, x, evaluate=False) + comp = theano_code_(expr) + + assert theq(comp, xt + xt) + assert not theq(comp, xt + theano_code_(x)) + +def test_cache_complex(): + """ + Test caching on a complicated expression with multiple symbols appearing + multiple times. + """ + expr = x ** 2 + (y - sy.exp(x)) * sy.sin(z - x * y) + symbol_names = {s.name for s in expr.free_symbols} + expr_t = theano_code_(expr) + + # Iterate through variables in the Theano computational graph that the + # printed expression depends on + seen = set() + for v in theano.gof.graph.ancestors([expr_t]): + # Owner-less, non-constant variables should be our symbols + if v.owner is None and not isinstance(v, theano.gof.graph.Constant): + # Check it corresponds to a symbol and appears only once + assert v.name in symbol_names + assert v.name not in seen + seen.add(v.name) + + # Check all were present + assert seen == symbol_names + + +def test_Piecewise(): + # A piecewise linear + expr = sy.Piecewise((0, x<0), (x, x<2), (1, True)) # ___/III + result = theano_code_(expr) + assert result.owner.op == tt.switch + + expected = tt.switch(xt<0, 0, tt.switch(xt<2, xt, 1)) + assert theq(result, expected) + + expr = sy.Piecewise((x, x < 0)) + result = theano_code_(expr) + expected = tt.switch(xt < 0, xt, np.nan) + assert theq(result, expected) + + expr = sy.Piecewise((0, sy.And(x>0, x<2)), \ + (x, sy.Or(x>2, x<0))) + result = theano_code_(expr) + expected = tt.switch(tt.and_(xt>0,xt<2), 0, \ + tt.switch(tt.or_(xt>2, xt<0), xt, np.nan)) + assert theq(result, expected) + + +def test_Relationals(): + assert theq(theano_code_(sy.Eq(x, y)), tt.eq(xt, yt)) + # assert theq(theano_code_(sy.Ne(x, y)), tt.neq(xt, yt)) # TODO - implement + assert theq(theano_code_(x > y), xt > yt) + assert theq(theano_code_(x < y), xt < yt) + assert theq(theano_code_(x >= y), xt >= yt) + assert theq(theano_code_(x <= y), xt <= yt) + + +def test_complexfunctions(): + with warns_deprecated_sympy(): + xt, yt = theano_code_(x, dtypes={x:'complex128'}), theano_code_(y, dtypes={y: 'complex128'}) + from sympy.functions.elementary.complexes import conjugate + from theano.tensor import as_tensor_variable as atv + from theano.tensor import complex as cplx + with warns_deprecated_sympy(): + assert theq(theano_code_(y*conjugate(x)), yt*(xt.conj())) + assert theq(theano_code_((1+2j)*x), xt*(atv(1.0)+atv(2.0)*cplx(0,1))) + + +def test_constantfunctions(): + with warns_deprecated_sympy(): + tf = theano_function_([],[1+1j]) + assert(tf()==1+1j) + + +def test_Exp1(): + """ + Test that exp(1) prints without error and evaluates close to SymPy's E + """ + # sy.exp(1) should yield same instance of E as sy.E (singleton), but extra + # check added for sanity + e_a = sy.exp(1) + e_b = sy.E + + np.testing.assert_allclose(float(e_a), np.e) + np.testing.assert_allclose(float(e_b), np.e) + + e = theano_code_(e_a) + np.testing.assert_allclose(float(e_a), e.eval()) + + e = theano_code_(e_b) + np.testing.assert_allclose(float(e_b), e.eval()) diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_torch.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_torch.py new file mode 100644 index 0000000000000000000000000000000000000000..8ce2c6cec75e03264f93b472a79eb073742e3486 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_torch.py @@ -0,0 +1,531 @@ +import random +import math + +from sympy import symbols, Derivative +from sympy.printing.pytorch import torch_code +from sympy import (eye, MatrixSymbol, Matrix) +from sympy.tensor.array import NDimArray +from sympy.tensor.array.expressions.array_expressions import ( + ArrayTensorProduct, ArrayAdd, + PermuteDims, ArrayDiagonal, _CodegenArrayAbstract) +from sympy.utilities.lambdify import lambdify +from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt +from sympy.functions import \ + Abs, ceiling, exp, floor, sign, sin, asin, cos, \ + acos, tan, atan, atan2, cosh, acosh, sinh, asinh, tanh, atanh, \ + re, im, arg, erf, loggamma, sqrt +from sympy.testing.pytest import skip +from sympy.external import import_module +from sympy.matrices.expressions import \ + Determinant, HadamardProduct, Inverse, Trace +from sympy.matrices import randMatrix +from sympy.matrices import Identity, ZeroMatrix, OneMatrix +from sympy import conjugate, I +from sympy import Heaviside, gamma, polygamma + + + +torch = import_module("torch") + +M = MatrixSymbol("M", 3, 3) +N = MatrixSymbol("N", 3, 3) +P = MatrixSymbol("P", 3, 3) +Q = MatrixSymbol("Q", 3, 3) + +x, y, z, t = symbols("x y z t") + +if torch is not None: + llo = [list(range(i, i + 3)) for i in range(0, 9, 3)] + m3x3 = torch.tensor(llo, dtype=torch.float64) + m3x3sympy = Matrix(llo) + + +def _compare_torch_matrix(variables, expr): + f = lambdify(variables, expr, 'torch') + + random_matrices = [randMatrix(i.shape[0], i.shape[1]) for i in variables] + random_variables = [torch.tensor(i.tolist(), dtype=torch.float64) for i in random_matrices] + r = f(*random_variables) + e = expr.subs(dict(zip(variables, random_matrices))).doit() + + if isinstance(e, _CodegenArrayAbstract): + e = e.doit() + + if hasattr(e, 'is_number') and e.is_number: + if isinstance(r, torch.Tensor) and r.dim() == 0: + r = r.item() + e = float(e) + assert abs(r - e) < 1e-6 + return + + if e.is_Matrix or isinstance(e, NDimArray): + e = torch.tensor(e.tolist(), dtype=torch.float64) + assert torch.allclose(r, e, atol=1e-6) + else: + raise TypeError(f"Cannot compare {type(r)} with {type(e)}") + + +def _compare_torch_scalar(variables, expr, rng=lambda: random.uniform(-5, 5)): + f = lambdify(variables, expr, 'torch') + rvs = [rng() for v in variables] + t_rvs = [torch.tensor(i, dtype=torch.float64) for i in rvs] + r = f(*t_rvs) + if isinstance(r, torch.Tensor): + r = r.item() + e = expr.subs(dict(zip(variables, rvs))).doit() + assert abs(r - e) < 1e-6 + + +def _compare_torch_relational(variables, expr, rng=lambda: random.randint(0, 10)): + f = lambdify(variables, expr, 'torch') + rvs = [rng() for v in variables] + t_rvs = [torch.tensor(i, dtype=torch.float64) for i in rvs] + r = f(*t_rvs) + e = bool(expr.subs(dict(zip(variables, rvs))).doit()) + assert r.item() == e + + +def test_torch_math(): + if not torch: + skip("PyTorch not installed") + + expr = Abs(x) + assert torch_code(expr) == "torch.abs(x)" + f = lambdify(x, expr, 'torch') + ma = torch.tensor([[-1, 2, -3, -4]], dtype=torch.float64) + y_abs = f(ma) + c = torch.abs(ma) + assert torch.all(y_abs == c) + + expr = sign(x) + assert torch_code(expr) == "torch.sign(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-10, 10)) + + expr = ceiling(x) + assert torch_code(expr) == "torch.ceil(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.random()) + + expr = floor(x) + assert torch_code(expr) == "torch.floor(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.random()) + + expr = exp(x) + assert torch_code(expr) == "torch.exp(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-2, 2)) + + expr = sqrt(x) + assert torch_code(expr) == "torch.sqrt(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.random()) + + expr = x ** 4 + assert torch_code(expr) == "torch.pow(x, 4)" + _compare_torch_scalar((x,), expr, rng=lambda: random.random()) + + expr = cos(x) + assert torch_code(expr) == "torch.cos(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.random()) + + expr = acos(x) + assert torch_code(expr) == "torch.acos(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-0.99, 0.99)) + + expr = sin(x) + assert torch_code(expr) == "torch.sin(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.random()) + + expr = asin(x) + assert torch_code(expr) == "torch.asin(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-0.99, 0.99)) + + expr = tan(x) + assert torch_code(expr) == "torch.tan(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-1.5, 1.5)) + + expr = atan(x) + assert torch_code(expr) == "torch.atan(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-5, 5)) + + expr = atan2(y, x) + assert torch_code(expr) == "torch.atan2(y, x)" + _compare_torch_scalar((y, x), expr, rng=lambda: random.uniform(-5, 5)) + + expr = cosh(x) + assert torch_code(expr) == "torch.cosh(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-2, 2)) + + expr = acosh(x) + assert torch_code(expr) == "torch.acosh(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(1.1, 5)) + + expr = sinh(x) + assert torch_code(expr) == "torch.sinh(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-2, 2)) + + expr = asinh(x) + assert torch_code(expr) == "torch.asinh(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-5, 5)) + + expr = tanh(x) + assert torch_code(expr) == "torch.tanh(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-2, 2)) + + expr = atanh(x) + assert torch_code(expr) == "torch.atanh(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-0.9, 0.9)) + + expr = erf(x) + assert torch_code(expr) == "torch.erf(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(-2, 2)) + + expr = loggamma(x) + assert torch_code(expr) == "torch.lgamma(x)" + _compare_torch_scalar((x,), expr, rng=lambda: random.uniform(0.5, 5)) + + +def test_torch_complexes(): + assert torch_code(re(x)) == "torch.real(x)" + assert torch_code(im(x)) == "torch.imag(x)" + assert torch_code(arg(x)) == "torch.angle(x)" + + +def test_torch_relational(): + if not torch: + skip("PyTorch not installed") + + expr = Eq(x, y) + assert torch_code(expr) == "torch.eq(x, y)" + _compare_torch_relational((x, y), expr) + + expr = Ne(x, y) + assert torch_code(expr) == "torch.ne(x, y)" + _compare_torch_relational((x, y), expr) + + expr = Ge(x, y) + assert torch_code(expr) == "torch.ge(x, y)" + _compare_torch_relational((x, y), expr) + + expr = Gt(x, y) + assert torch_code(expr) == "torch.gt(x, y)" + _compare_torch_relational((x, y), expr) + + expr = Le(x, y) + assert torch_code(expr) == "torch.le(x, y)" + _compare_torch_relational((x, y), expr) + + expr = Lt(x, y) + assert torch_code(expr) == "torch.lt(x, y)" + _compare_torch_relational((x, y), expr) + + +def test_torch_matrix(): + if torch is None: + skip("PyTorch not installed") + + expr = M + assert torch_code(expr) == "M" + f = lambdify((M,), expr, "torch") + eye_mat = eye(3) + eye_tensor = torch.tensor(eye_mat.tolist(), dtype=torch.float64) + assert torch.allclose(f(eye_tensor), eye_tensor) + + expr = M * N + assert torch_code(expr) == "torch.matmul(M, N)" + _compare_torch_matrix((M, N), expr) + + expr = M ** 3 + assert torch_code(expr) == "torch.mm(torch.mm(M, M), M)" + _compare_torch_matrix((M,), expr) + + expr = M * N * P * Q + assert torch_code(expr) == "torch.matmul(torch.matmul(torch.matmul(M, N), P), Q)" + _compare_torch_matrix((M, N, P, Q), expr) + + expr = Trace(M) + assert torch_code(expr) == "torch.trace(M)" + _compare_torch_matrix((M,), expr) + + expr = Determinant(M) + assert torch_code(expr) == "torch.det(M)" + _compare_torch_matrix((M,), expr) + + expr = HadamardProduct(M, N) + assert torch_code(expr) == "torch.mul(M, N)" + _compare_torch_matrix((M, N), expr) + + expr = Inverse(M) + assert torch_code(expr) == "torch.linalg.inv(M)" + + # For inverse, use a matrix that's guaranteed to be invertible + eye_mat = eye(3) + eye_tensor = torch.tensor(eye_mat.tolist(), dtype=torch.float64) + f = lambdify((M,), expr, "torch") + result = f(eye_tensor) + expected = torch.linalg.inv(eye_tensor) + assert torch.allclose(result, expected) + + +def test_torch_array_operations(): + if not torch: + skip("PyTorch not installed") + + M = MatrixSymbol("M", 2, 2) + N = MatrixSymbol("N", 2, 2) + P = MatrixSymbol("P", 2, 2) + Q = MatrixSymbol("Q", 2, 2) + + ma = torch.tensor([[1., 2.], [3., 4.]], dtype=torch.float64) + mb = torch.tensor([[1., -2.], [-1., 3.]], dtype=torch.float64) + mc = torch.tensor([[2., 0.], [1., 2.]], dtype=torch.float64) + md = torch.tensor([[1., -1.], [4., 7.]], dtype=torch.float64) + + cg = ArrayTensorProduct(M, N) + assert torch_code(cg) == 'torch.einsum("ab,cd", M, N)' + f = lambdify((M, N), cg, 'torch') + y = f(ma, mb) + c = torch.einsum("ij,kl", ma, mb) + assert torch.allclose(y, c) + + cg = ArrayAdd(M, N) + assert torch_code(cg) == 'torch.add(M, N)' + f = lambdify((M, N), cg, 'torch') + y = f(ma, mb) + c = ma + mb + assert torch.allclose(y, c) + + cg = ArrayAdd(M, N, P) + assert torch_code(cg) == 'torch.add(torch.add(M, N), P)' + f = lambdify((M, N, P), cg, 'torch') + y = f(ma, mb, mc) + c = ma + mb + mc + assert torch.allclose(y, c) + + cg = ArrayAdd(M, N, P, Q) + assert torch_code(cg) == 'torch.add(torch.add(torch.add(M, N), P), Q)' + f = lambdify((M, N, P, Q), cg, 'torch') + y = f(ma, mb, mc, md) + c = ma + mb + mc + md + assert torch.allclose(y, c) + + cg = PermuteDims(M, [1, 0]) + assert torch_code(cg) == 'M.permute(1, 0)' + f = lambdify((M,), cg, 'torch') + y = f(ma) + c = ma.T + assert torch.allclose(y, c) + + cg = PermuteDims(ArrayTensorProduct(M, N), [1, 2, 3, 0]) + assert torch_code(cg) == 'torch.einsum("ab,cd", M, N).permute(1, 2, 3, 0)' + f = lambdify((M, N), cg, 'torch') + y = f(ma, mb) + c = torch.einsum("ab,cd", ma, mb).permute(1, 2, 3, 0) + assert torch.allclose(y, c) + + cg = ArrayDiagonal(ArrayTensorProduct(M, N), (1, 2)) + assert torch_code(cg) == 'torch.einsum("ab,bc->acb", M, N)' + f = lambdify((M, N), cg, 'torch') + y = f(ma, mb) + c = torch.einsum("ab,bc->acb", ma, mb) + assert torch.allclose(y, c) + + +def test_torch_derivative(): + """Test derivative handling.""" + expr = Derivative(sin(x), x) + assert torch_code(expr) == 'torch.autograd.grad(torch.sin(x), x)[0]' + + +def test_torch_printing_dtype(): + if not torch: + skip("PyTorch not installed") + + # matrix printing with default dtype + expr = Matrix([[x, sin(y)], [exp(z), -t]]) + assert "dtype=torch.float64" in torch_code(expr) + + # explicit dtype + assert "dtype=torch.float32" in torch_code(expr, dtype="torch.float32") + + # with requires_grad + result = torch_code(expr, requires_grad=True) + assert "requires_grad=True" in result + assert "dtype=torch.float64" in result + + # both + result = torch_code(expr, requires_grad=True, dtype="torch.float32") + assert "requires_grad=True" in result + assert "dtype=torch.float32" in result + + +def test_requires_grad(): + if not torch: + skip("PyTorch not installed") + + expr = sin(x) + cos(y) + f = lambdify([x, y], expr, 'torch') + + # make sure the gradients flow + x_val = torch.tensor(1.0, requires_grad=True) + y_val = torch.tensor(2.0, requires_grad=True) + result = f(x_val, y_val) + assert result.requires_grad + result.backward() + + # x_val.grad should be cos(x_val) which is close to cos(1.0) + assert abs(x_val.grad.item() - float(cos(1.0).evalf())) < 1e-6 + + # y_val.grad should be -sin(y_val) which is close to -sin(2.0) + assert abs(y_val.grad.item() - float(-sin(2.0).evalf())) < 1e-6 + + +def test_torch_multi_variable_derivatives(): + if not torch: + skip("PyTorch not installed") + + x, y, z = symbols("x y z") + + expr = Derivative(sin(x), x) + assert torch_code(expr) == "torch.autograd.grad(torch.sin(x), x)[0]" + + expr = Derivative(sin(x), (x, 2)) + assert torch_code( + expr) == "torch.autograd.grad(torch.autograd.grad(torch.sin(x), x, create_graph=True)[0], x, create_graph=True)[0]" + + expr = Derivative(sin(x * y), x, y) + result = torch_code(expr) + expected = "torch.autograd.grad(torch.autograd.grad(torch.sin(x*y), x, create_graph=True)[0], y, create_graph=True)[0]" + normalized_result = result.replace(" ", "") + normalized_expected = expected.replace(" ", "") + assert normalized_result == normalized_expected + + expr = Derivative(sin(x), x, x) + result = torch_code(expr) + expected = "torch.autograd.grad(torch.autograd.grad(torch.sin(x), x, create_graph=True)[0], x, create_graph=True)[0]" + assert result == expected + + expr = Derivative(sin(x * y * z), x, (y, 2), z) + result = torch_code(expr) + expected = "torch.autograd.grad(torch.autograd.grad(torch.autograd.grad(torch.autograd.grad(torch.sin(x*y*z), x, create_graph=True)[0], y, create_graph=True)[0], y, create_graph=True)[0], z, create_graph=True)[0]" + normalized_result = result.replace(" ", "") + normalized_expected = expected.replace(" ", "") + assert normalized_result == normalized_expected + + +def test_torch_derivative_lambdify(): + if not torch: + skip("PyTorch not installed") + + x = symbols("x") + y = symbols("y") + + expr = Derivative(x ** 2, x) + f = lambdify(x, expr, 'torch') + x_val = torch.tensor(2.0, requires_grad=True) + result = f(x_val) + assert torch.isclose(result, torch.tensor(4.0)) + + expr = Derivative(sin(x), (x, 2)) + f = lambdify(x, expr, 'torch') + # Second derivative of sin(x) at x=0 is 0, not -1 + x_val = torch.tensor(0.0, requires_grad=True) + result = f(x_val) + assert torch.isclose(result, torch.tensor(0.0), atol=1e-5) + + x_val = torch.tensor(math.pi / 2, requires_grad=True) + result = f(x_val) + assert torch.isclose(result, torch.tensor(-1.0), atol=1e-5) + + expr = Derivative(x * y ** 2, x, y) + f = lambdify((x, y), expr, 'torch') + x_val = torch.tensor(2.0, requires_grad=True) + y_val = torch.tensor(3.0, requires_grad=True) + result = f(x_val, y_val) + assert torch.isclose(result, torch.tensor(6.0)) + + +def test_torch_special_matrices(): + if not torch: + skip("PyTorch not installed") + + expr = Identity(3) + assert torch_code(expr) == "torch.eye(3)" + + n = symbols("n") + expr = Identity(n) + assert torch_code(expr) == "torch.eye(n, n)" + + expr = ZeroMatrix(2, 3) + assert torch_code(expr) == "torch.zeros((2, 3))" + + m, n = symbols("m n") + expr = ZeroMatrix(m, n) + assert torch_code(expr) == "torch.zeros((m, n))" + + expr = OneMatrix(2, 3) + assert torch_code(expr) == "torch.ones((2, 3))" + + expr = OneMatrix(m, n) + assert torch_code(expr) == "torch.ones((m, n))" + + +def test_torch_special_matrices_lambdify(): + if not torch: + skip("PyTorch not installed") + + expr = Identity(3) + f = lambdify([], expr, 'torch') + result = f() + expected = torch.eye(3) + assert torch.allclose(result, expected) + + expr = ZeroMatrix(2, 3) + f = lambdify([], expr, 'torch') + result = f() + expected = torch.zeros((2, 3)) + assert torch.allclose(result, expected) + + expr = OneMatrix(2, 3) + f = lambdify([], expr, 'torch') + result = f() + expected = torch.ones((2, 3)) + assert torch.allclose(result, expected) + + +def test_torch_complex_operations(): + if not torch: + skip("PyTorch not installed") + + expr = conjugate(x) + assert torch_code(expr) == "torch.conj(x)" + + # SymPy distributes conjugate over addition and applies specific rules for each term + expr = conjugate(sin(x) + I * cos(y)) + assert torch_code(expr) == "torch.sin(torch.conj(x)) - 1j*torch.cos(torch.conj(y))" + + expr = I + assert torch_code(expr) == "1j" + + expr = 2 * I + x + assert torch_code(expr) == "x + 2*1j" + + expr = exp(I * x) + assert torch_code(expr) == "torch.exp(1j*x)" + + +def test_torch_special_functions(): + if not torch: + skip("PyTorch not installed") + + expr = Heaviside(x) + assert torch_code(expr) == "torch.heaviside(x, 1/2)" + + expr = Heaviside(x, 0) + assert torch_code(expr) == "torch.heaviside(x, 0)" + + expr = gamma(x) + assert torch_code(expr) == "torch.special.gamma(x)" + + expr = polygamma(0, x) # Use polygamma instead of digamma because sympy will default to that anyway + assert torch_code(expr) == "torch.special.digamma(x)" + + expr = gamma(sin(x)) + assert torch_code(expr) == "torch.special.gamma(torch.sin(x))" diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tree.py b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tree.py new file mode 100644 index 0000000000000000000000000000000000000000..cf116d0cac5d38f225815fcd2d4ac90cd0dd96d7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tests/test_tree.py @@ -0,0 +1,196 @@ +from sympy.printing.tree import tree +from sympy.testing.pytest import XFAIL + + +# Remove this flag after making _assumptions cache deterministic. +@XFAIL +def test_print_tree_MatAdd(): + from sympy.matrices.expressions import MatrixSymbol + A = MatrixSymbol('A', 3, 3) + B = MatrixSymbol('B', 3, 3) + + test_str = [ + 'MatAdd: A + B\n', + 'algebraic: False\n', + 'commutative: False\n', + 'complex: False\n', + 'composite: False\n', + 'even: False\n', + 'extended_negative: False\n', + 'extended_nonnegative: False\n', + 'extended_nonpositive: False\n', + 'extended_nonzero: False\n', + 'extended_positive: False\n', + 'extended_real: False\n', + 'imaginary: False\n', + 'integer: False\n', + 'irrational: False\n', + 'negative: False\n', + 'noninteger: False\n', + 'nonnegative: False\n', + 'nonpositive: False\n', + 'nonzero: False\n', + 'odd: False\n', + 'positive: False\n', + 'prime: False\n', + 'rational: False\n', + 'real: False\n', + 'transcendental: False\n', + 'zero: False\n', + '+-MatrixSymbol: A\n', + '| algebraic: False\n', + '| commutative: False\n', + '| complex: False\n', + '| composite: False\n', + '| even: False\n', + '| extended_negative: False\n', + '| extended_nonnegative: False\n', + '| extended_nonpositive: False\n', + '| extended_nonzero: False\n', + '| extended_positive: False\n', + '| extended_real: False\n', + '| imaginary: False\n', + '| integer: False\n', + '| irrational: False\n', + '| negative: False\n', + '| noninteger: False\n', + '| nonnegative: False\n', + '| nonpositive: False\n', + '| nonzero: False\n', + '| odd: False\n', + '| positive: False\n', + '| prime: False\n', + '| rational: False\n', + '| real: False\n', + '| transcendental: False\n', + '| zero: False\n', + '| +-Symbol: A\n', + '| | commutative: True\n', + '| +-Integer: 3\n', + '| | algebraic: True\n', + '| | commutative: True\n', + '| | complex: True\n', + '| | extended_negative: False\n', + '| | extended_nonnegative: True\n', + '| | extended_real: True\n', + '| | finite: True\n', + '| | hermitian: True\n', + '| | imaginary: False\n', + '| | infinite: False\n', + '| | integer: True\n', + '| | irrational: False\n', + '| | negative: False\n', + '| | noninteger: False\n', + '| | nonnegative: True\n', + '| | rational: True\n', + '| | real: True\n', + '| | transcendental: False\n', + '| +-Integer: 3\n', + '| algebraic: True\n', + '| commutative: True\n', + '| complex: True\n', + '| extended_negative: False\n', + '| extended_nonnegative: True\n', + '| extended_real: True\n', + '| finite: True\n', + '| hermitian: True\n', + '| imaginary: False\n', + '| infinite: False\n', + '| integer: True\n', + '| irrational: False\n', + '| negative: False\n', + '| noninteger: False\n', + '| nonnegative: True\n', + '| rational: True\n', + '| real: True\n', + '| transcendental: False\n', + '+-MatrixSymbol: B\n', + ' algebraic: False\n', + ' commutative: False\n', + ' complex: False\n', + ' composite: False\n', + ' even: False\n', + ' extended_negative: False\n', + ' extended_nonnegative: False\n', + ' extended_nonpositive: False\n', + ' extended_nonzero: False\n', + ' extended_positive: False\n', + ' extended_real: False\n', + ' imaginary: False\n', + ' integer: False\n', + ' irrational: False\n', + ' negative: False\n', + ' noninteger: False\n', + ' nonnegative: False\n', + ' nonpositive: False\n', + ' nonzero: False\n', + ' odd: False\n', + ' positive: False\n', + ' prime: False\n', + ' rational: False\n', + ' real: False\n', + ' transcendental: False\n', + ' zero: False\n', + ' +-Symbol: B\n', + ' | commutative: True\n', + ' +-Integer: 3\n', + ' | algebraic: True\n', + ' | commutative: True\n', + ' | complex: True\n', + ' | extended_negative: False\n', + ' | extended_nonnegative: True\n', + ' | extended_real: True\n', + ' | finite: True\n', + ' | hermitian: True\n', + ' | imaginary: False\n', + ' | infinite: False\n', + ' | integer: True\n', + ' | irrational: False\n', + ' | negative: False\n', + ' | noninteger: False\n', + ' | nonnegative: True\n', + ' | rational: True\n', + ' | real: True\n', + ' | transcendental: False\n', + ' +-Integer: 3\n', + ' algebraic: True\n', + ' commutative: True\n', + ' complex: True\n', + ' extended_negative: False\n', + ' extended_nonnegative: True\n', + ' extended_real: True\n', + ' finite: True\n', + ' hermitian: True\n', + ' imaginary: False\n', + ' infinite: False\n', + ' integer: True\n', + ' irrational: False\n', + ' negative: False\n', + ' noninteger: False\n', + ' nonnegative: True\n', + ' rational: True\n', + ' real: True\n', + ' transcendental: False\n' + ] + + assert tree(A + B) == "".join(test_str) + + +def test_print_tree_MatAdd_noassumptions(): + from sympy.matrices.expressions import MatrixSymbol + A = MatrixSymbol('A', 3, 3) + B = MatrixSymbol('B', 3, 3) + + test_str = \ +"""MatAdd: A + B ++-MatrixSymbol: A +| +-Str: A +| +-Integer: 3 +| +-Integer: 3 ++-MatrixSymbol: B + +-Str: B + +-Integer: 3 + +-Integer: 3 +""" + + assert tree(A + B, assumptions=False) == test_str diff --git a/venv/lib/python3.10/site-packages/sympy/printing/theanocode.py b/venv/lib/python3.10/site-packages/sympy/printing/theanocode.py new file mode 100644 index 0000000000000000000000000000000000000000..dce908865d426dabede2b6749ad944e5a420e4cf --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/theanocode.py @@ -0,0 +1,571 @@ +""" +.. deprecated:: 1.8 + + ``sympy.printing.theanocode`` is deprecated. Theano has been renamed to + Aesara. Use ``sympy.printing.aesaracode`` instead. See + :ref:`theanocode-deprecated` for more information. + +""" +from __future__ import annotations +import math +from typing import Any + +from sympy.external import import_module +from sympy.printing.printer import Printer +from sympy.utilities.iterables import is_sequence +import sympy +from functools import partial + +from sympy.utilities.decorator import doctest_depends_on +from sympy.utilities.exceptions import sympy_deprecation_warning + + +__doctest_requires__ = {('theano_function',): ['theano']} + + +theano = import_module('theano') + + +if theano: + ts = theano.scalar + tt = theano.tensor + from theano.sandbox import linalg as tlinalg + + mapping = { + sympy.Add: tt.add, + sympy.Mul: tt.mul, + sympy.Abs: tt.abs_, + sympy.sign: tt.sgn, + sympy.ceiling: tt.ceil, + sympy.floor: tt.floor, + sympy.log: tt.log, + sympy.exp: tt.exp, + sympy.sqrt: tt.sqrt, + sympy.cos: tt.cos, + sympy.acos: tt.arccos, + sympy.sin: tt.sin, + sympy.asin: tt.arcsin, + sympy.tan: tt.tan, + sympy.atan: tt.arctan, + sympy.atan2: tt.arctan2, + sympy.cosh: tt.cosh, + sympy.acosh: tt.arccosh, + sympy.sinh: tt.sinh, + sympy.asinh: tt.arcsinh, + sympy.tanh: tt.tanh, + sympy.atanh: tt.arctanh, + sympy.re: tt.real, + sympy.im: tt.imag, + sympy.arg: tt.angle, + sympy.erf: tt.erf, + sympy.gamma: tt.gamma, + sympy.loggamma: tt.gammaln, + sympy.Pow: tt.pow, + sympy.Eq: tt.eq, + sympy.StrictGreaterThan: tt.gt, + sympy.StrictLessThan: tt.lt, + sympy.LessThan: tt.le, + sympy.GreaterThan: tt.ge, + sympy.And: tt.and_, + sympy.Or: tt.or_, + sympy.Max: tt.maximum, # SymPy accept >2 inputs, Theano only 2 + sympy.Min: tt.minimum, # SymPy accept >2 inputs, Theano only 2 + sympy.conjugate: tt.conj, + sympy.core.numbers.ImaginaryUnit: lambda:tt.complex(0,1), + # Matrices + sympy.MatAdd: tt.Elemwise(ts.add), + sympy.HadamardProduct: tt.Elemwise(ts.mul), + sympy.Trace: tlinalg.trace, + sympy.Determinant : tlinalg.det, + sympy.Inverse: tlinalg.matrix_inverse, + sympy.Transpose: tt.DimShuffle((False, False), [1, 0]), + } + + +class TheanoPrinter(Printer): + """ Code printer which creates Theano symbolic expression graphs. + + Parameters + ========== + + cache : dict + Cache dictionary to use. If None (default) will use + the global cache. To create a printer which does not depend on or alter + global state pass an empty dictionary. Note: the dictionary is not + copied on initialization of the printer and will be updated in-place, + so using the same dict object when creating multiple printers or making + multiple calls to :func:`.theano_code` or :func:`.theano_function` means + the cache is shared between all these applications. + + Attributes + ========== + + cache : dict + A cache of Theano variables which have been created for SymPy + symbol-like objects (e.g. :class:`sympy.core.symbol.Symbol` or + :class:`sympy.matrices.expressions.MatrixSymbol`). This is used to + ensure that all references to a given symbol in an expression (or + multiple expressions) are printed as the same Theano variable, which is + created only once. Symbols are differentiated only by name and type. The + format of the cache's contents should be considered opaque to the user. + """ + printmethod = "_theano" + + def __init__(self, *args, **kwargs): + self.cache = kwargs.pop('cache', {}) + super().__init__(*args, **kwargs) + + def _get_key(self, s, name=None, dtype=None, broadcastable=None): + """ Get the cache key for a SymPy object. + + Parameters + ========== + + s : sympy.core.basic.Basic + SymPy object to get key for. + + name : str + Name of object, if it does not have a ``name`` attribute. + """ + + if name is None: + name = s.name + + return (name, type(s), s.args, dtype, broadcastable) + + def _get_or_create(self, s, name=None, dtype=None, broadcastable=None): + """ + Get the Theano variable for a SymPy symbol from the cache, or create it + if it does not exist. + """ + + # Defaults + if name is None: + name = s.name + if dtype is None: + dtype = 'floatX' + if broadcastable is None: + broadcastable = () + + key = self._get_key(s, name, dtype=dtype, broadcastable=broadcastable) + + if key in self.cache: + return self.cache[key] + + value = tt.tensor(name=name, dtype=dtype, broadcastable=broadcastable) + self.cache[key] = value + return value + + def _print_Symbol(self, s, **kwargs): + dtype = kwargs.get('dtypes', {}).get(s) + bc = kwargs.get('broadcastables', {}).get(s) + return self._get_or_create(s, dtype=dtype, broadcastable=bc) + + def _print_AppliedUndef(self, s, **kwargs): + name = str(type(s)) + '_' + str(s.args[0]) + dtype = kwargs.get('dtypes', {}).get(s) + bc = kwargs.get('broadcastables', {}).get(s) + return self._get_or_create(s, name=name, dtype=dtype, broadcastable=bc) + + def _print_Basic(self, expr, **kwargs): + op = mapping[type(expr)] + children = [self._print(arg, **kwargs) for arg in expr.args] + return op(*children) + + def _print_Number(self, n, **kwargs): + # Integers already taken care of below, interpret as float + return float(n.evalf()) + + def _print_MatrixSymbol(self, X, **kwargs): + dtype = kwargs.get('dtypes', {}).get(X) + return self._get_or_create(X, dtype=dtype, broadcastable=(None, None)) + + def _print_DenseMatrix(self, X, **kwargs): + if not hasattr(tt, 'stacklists'): + raise NotImplementedError( + "Matrix translation not yet supported in this version of Theano") + + return tt.stacklists([ + [self._print(arg, **kwargs) for arg in L] + for L in X.tolist() + ]) + + _print_ImmutableMatrix = _print_ImmutableDenseMatrix = _print_DenseMatrix + + def _print_MatMul(self, expr, **kwargs): + children = [self._print(arg, **kwargs) for arg in expr.args] + result = children[0] + for child in children[1:]: + result = tt.dot(result, child) + return result + + def _print_MatPow(self, expr, **kwargs): + children = [self._print(arg, **kwargs) for arg in expr.args] + result = 1 + if isinstance(children[1], int) and children[1] > 0: + for i in range(children[1]): + result = tt.dot(result, children[0]) + else: + raise NotImplementedError('''Only non-negative integer + powers of matrices can be handled by Theano at the moment''') + return result + + def _print_MatrixSlice(self, expr, **kwargs): + parent = self._print(expr.parent, **kwargs) + rowslice = self._print(slice(*expr.rowslice), **kwargs) + colslice = self._print(slice(*expr.colslice), **kwargs) + return parent[rowslice, colslice] + + def _print_BlockMatrix(self, expr, **kwargs): + nrows, ncols = expr.blocks.shape + blocks = [[self._print(expr.blocks[r, c], **kwargs) + for c in range(ncols)] + for r in range(nrows)] + return tt.join(0, *[tt.join(1, *row) for row in blocks]) + + + def _print_slice(self, expr, **kwargs): + return slice(*[self._print(i, **kwargs) + if isinstance(i, sympy.Basic) else i + for i in (expr.start, expr.stop, expr.step)]) + + def _print_Pi(self, expr, **kwargs): + return math.pi + + def _print_Exp1(self, expr, **kwargs): + return ts.exp(1) + + def _print_Piecewise(self, expr, **kwargs): + import numpy as np + e, cond = expr.args[0].args # First condition and corresponding value + + # Print conditional expression and value for first condition + p_cond = self._print(cond, **kwargs) + p_e = self._print(e, **kwargs) + + # One condition only + if len(expr.args) == 1: + # Return value if condition else NaN + return tt.switch(p_cond, p_e, np.nan) + + # Return value_1 if condition_1 else evaluate remaining conditions + p_remaining = self._print(sympy.Piecewise(*expr.args[1:]), **kwargs) + return tt.switch(p_cond, p_e, p_remaining) + + def _print_Rational(self, expr, **kwargs): + return tt.true_div(self._print(expr.p, **kwargs), + self._print(expr.q, **kwargs)) + + def _print_Integer(self, expr, **kwargs): + return expr.p + + def _print_factorial(self, expr, **kwargs): + return self._print(sympy.gamma(expr.args[0] + 1), **kwargs) + + def _print_Derivative(self, deriv, **kwargs): + rv = self._print(deriv.expr, **kwargs) + for var in deriv.variables: + var = self._print(var, **kwargs) + rv = tt.Rop(rv, var, tt.ones_like(var)) + return rv + + def emptyPrinter(self, expr): + return expr + + def doprint(self, expr, dtypes=None, broadcastables=None): + """ Convert a SymPy expression to a Theano graph variable. + + The ``dtypes`` and ``broadcastables`` arguments are used to specify the + data type, dimension, and broadcasting behavior of the Theano variables + corresponding to the free symbols in ``expr``. Each is a mapping from + SymPy symbols to the value of the corresponding argument to + ``theano.tensor.Tensor``. + + See the corresponding `documentation page`__ for more information on + broadcasting in Theano. + + .. __: http://deeplearning.net/software/theano/tutorial/broadcasting.html + + Parameters + ========== + + expr : sympy.core.expr.Expr + SymPy expression to print. + + dtypes : dict + Mapping from SymPy symbols to Theano datatypes to use when creating + new Theano variables for those symbols. Corresponds to the ``dtype`` + argument to ``theano.tensor.Tensor``. Defaults to ``'floatX'`` + for symbols not included in the mapping. + + broadcastables : dict + Mapping from SymPy symbols to the value of the ``broadcastable`` + argument to ``theano.tensor.Tensor`` to use when creating Theano + variables for those symbols. Defaults to the empty tuple for symbols + not included in the mapping (resulting in a scalar). + + Returns + ======= + + theano.gof.graph.Variable + A variable corresponding to the expression's value in a Theano + symbolic expression graph. + + """ + if dtypes is None: + dtypes = {} + if broadcastables is None: + broadcastables = {} + + return self._print(expr, dtypes=dtypes, broadcastables=broadcastables) + + +global_cache: dict[Any, Any] = {} + + +def theano_code(expr, cache=None, **kwargs): + """ + Convert a SymPy expression into a Theano graph variable. + + .. deprecated:: 1.8 + + ``sympy.printing.theanocode`` is deprecated. Theano has been renamed to + Aesara. Use ``sympy.printing.aesaracode`` instead. See + :ref:`theanocode-deprecated` for more information. + + Parameters + ========== + + expr : sympy.core.expr.Expr + SymPy expression object to convert. + + cache : dict + Cached Theano variables (see :class:`TheanoPrinter.cache + `). Defaults to the module-level global cache. + + dtypes : dict + Passed to :meth:`.TheanoPrinter.doprint`. + + broadcastables : dict + Passed to :meth:`.TheanoPrinter.doprint`. + + Returns + ======= + + theano.gof.graph.Variable + A variable corresponding to the expression's value in a Theano symbolic + expression graph. + + """ + sympy_deprecation_warning( + """ + sympy.printing.theanocode is deprecated. Theano has been renamed to + Aesara. Use sympy.printing.aesaracode instead.""", + deprecated_since_version="1.8", + active_deprecations_target='theanocode-deprecated') + + if not theano: + raise ImportError("theano is required for theano_code") + + if cache is None: + cache = global_cache + + return TheanoPrinter(cache=cache, settings={}).doprint(expr, **kwargs) + + +def dim_handling(inputs, dim=None, dims=None, broadcastables=None): + r""" + Get value of ``broadcastables`` argument to :func:`.theano_code` from + keyword arguments to :func:`.theano_function`. + + Included for backwards compatibility. + + Parameters + ========== + + inputs + Sequence of input symbols. + + dim : int + Common number of dimensions for all inputs. Overrides other arguments + if given. + + dims : dict + Mapping from input symbols to number of dimensions. Overrides + ``broadcastables`` argument if given. + + broadcastables : dict + Explicit value of ``broadcastables`` argument to + :meth:`.TheanoPrinter.doprint`. If not None function will return this value unchanged. + + Returns + ======= + dict + Dictionary mapping elements of ``inputs`` to their "broadcastable" + values (tuple of ``bool``\ s). + """ + if dim is not None: + return dict.fromkeys(inputs, (False,) * dim) + + if dims is not None: + maxdim = max(dims.values()) + return { + s: (False,) * d + (True,) * (maxdim - d) + for s, d in dims.items() + } + + if broadcastables is not None: + return broadcastables + + return {} + + +@doctest_depends_on(modules=('theano',)) +def theano_function(inputs, outputs, scalar=False, *, + dim=None, dims=None, broadcastables=None, **kwargs): + """ + Create a Theano function from SymPy expressions. + + .. deprecated:: 1.8 + + ``sympy.printing.theanocode`` is deprecated. Theano has been renamed to + Aesara. Use ``sympy.printing.aesaracode`` instead. See + :ref:`theanocode-deprecated` for more information. + + The inputs and outputs are converted to Theano variables using + :func:`.theano_code` and then passed to ``theano.function``. + + Parameters + ========== + + inputs + Sequence of symbols which constitute the inputs of the function. + + outputs + Sequence of expressions which constitute the outputs(s) of the + function. The free symbols of each expression must be a subset of + ``inputs``. + + scalar : bool + Convert 0-dimensional arrays in output to scalars. This will return a + Python wrapper function around the Theano function object. + + cache : dict + Cached Theano variables (see :class:`TheanoPrinter.cache + `). Defaults to the module-level global cache. + + dtypes : dict + Passed to :meth:`.TheanoPrinter.doprint`. + + broadcastables : dict + Passed to :meth:`.TheanoPrinter.doprint`. + + dims : dict + Alternative to ``broadcastables`` argument. Mapping from elements of + ``inputs`` to integers indicating the dimension of their associated + arrays/tensors. Overrides ``broadcastables`` argument if given. + + dim : int + Another alternative to the ``broadcastables`` argument. Common number of + dimensions to use for all arrays/tensors. + ``theano_function([x, y], [...], dim=2)`` is equivalent to using + ``broadcastables={x: (False, False), y: (False, False)}``. + + Returns + ======= + callable + A callable object which takes values of ``inputs`` as positional + arguments and returns an output array for each of the expressions + in ``outputs``. If ``outputs`` is a single expression the function will + return a Numpy array, if it is a list of multiple expressions the + function will return a list of arrays. See description of the ``squeeze`` + argument above for the behavior when a single output is passed in a list. + The returned object will either be an instance of + ``theano.compile.function_module.Function`` or a Python wrapper + function around one. In both cases, the returned value will have a + ``theano_function`` attribute which points to the return value of + ``theano.function``. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.printing.theanocode import theano_function + + A simple function with one input and one output: + + >>> f1 = theano_function([x], [x**2 - 1], scalar=True) + >>> f1(3) + 8.0 + + A function with multiple inputs and one output: + + >>> f2 = theano_function([x, y, z], [(x**z + y**z)**(1/z)], scalar=True) + >>> f2(3, 4, 2) + 5.0 + + A function with multiple inputs and multiple outputs: + + >>> f3 = theano_function([x, y], [x**2 + y**2, x**2 - y**2], scalar=True) + >>> f3(2, 3) + [13.0, -5.0] + + See also + ======== + + dim_handling + + """ + sympy_deprecation_warning( + """ + sympy.printing.theanocode is deprecated. Theano has been renamed to Aesara. Use sympy.printing.aesaracode instead""", + deprecated_since_version="1.8", + active_deprecations_target='theanocode-deprecated') + + if not theano: + raise ImportError("theano is required for theano_function") + + # Pop off non-theano keyword args + cache = kwargs.pop('cache', {}) + dtypes = kwargs.pop('dtypes', {}) + + broadcastables = dim_handling( + inputs, dim=dim, dims=dims, broadcastables=broadcastables, + ) + + # Print inputs/outputs + code = partial(theano_code, cache=cache, dtypes=dtypes, + broadcastables=broadcastables) + tinputs = list(map(code, inputs)) + toutputs = list(map(code, outputs)) + + #fix constant expressions as variables + toutputs = [output if isinstance(output, theano.Variable) else tt.as_tensor_variable(output) for output in toutputs] + + if len(toutputs) == 1: + toutputs = toutputs[0] + + # Compile theano func + func = theano.function(tinputs, toutputs, **kwargs) + + is_0d = [len(o.variable.broadcastable) == 0 for o in func.outputs] + + # No wrapper required + if not scalar or not any(is_0d): + func.theano_function = func + return func + + # Create wrapper to convert 0-dimensional outputs to scalars + def wrapper(*args): + out = func(*args) + # out can be array(1.0) or [array(1.0), array(2.0)] + + if is_sequence(out): + return [o[()] if is_0d[i] else o for i, o in enumerate(out)] + else: + return out[()] + + wrapper.__wrapped__ = func + wrapper.__doc__ = func.__doc__ + wrapper.theano_function = func + return wrapper diff --git a/venv/lib/python3.10/site-packages/sympy/printing/tree.py b/venv/lib/python3.10/site-packages/sympy/printing/tree.py new file mode 100644 index 0000000000000000000000000000000000000000..82dac013419fbe93f63dcf5b90b3a529d72a32bc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/printing/tree.py @@ -0,0 +1,175 @@ +def pprint_nodes(subtrees): + """ + Prettyprints systems of nodes. + + Examples + ======== + + >>> from sympy.printing.tree import pprint_nodes + >>> print(pprint_nodes(["a", "b1\\nb2", "c"])) + +-a + +-b1 + | b2 + +-c + + """ + def indent(s, type=1): + x = s.split("\n") + r = "+-%s\n" % x[0] + for a in x[1:]: + if a == "": + continue + if type == 1: + r += "| %s\n" % a + else: + r += " %s\n" % a + return r + if not subtrees: + return "" + f = "" + for a in subtrees[:-1]: + f += indent(a) + f += indent(subtrees[-1], 2) + return f + + +def print_node(node, assumptions=True): + """ + Returns information about the "node". + + This includes class name, string representation and assumptions. + + Parameters + ========== + + assumptions : bool, optional + See the ``assumptions`` keyword in ``tree`` + """ + s = "%s: %s\n" % (node.__class__.__name__, str(node)) + + if assumptions: + d = node._assumptions + else: + d = None + + if d: + for a in sorted(d): + v = d[a] + if v is None: + continue + s += "%s: %s\n" % (a, v) + + return s + + +def tree(node, assumptions=True): + """ + Returns a tree representation of "node" as a string. + + It uses print_node() together with pprint_nodes() on node.args recursively. + + Parameters + ========== + + assumptions : bool, optional + The flag to decide whether to print out all the assumption data + (such as ``is_integer`, ``is_real``) associated with the + expression or not. + + Enabling the flag makes the result verbose, and the printed + result may not be deterministic because of the randomness used + in backtracing the assumptions. + + See Also + ======== + + print_tree + + """ + subtrees = [] + for arg in node.args: + subtrees.append(tree(arg, assumptions=assumptions)) + s = print_node(node, assumptions=assumptions) + pprint_nodes(subtrees) + return s + + +def print_tree(node, assumptions=True): + """ + Prints a tree representation of "node". + + Parameters + ========== + + assumptions : bool, optional + The flag to decide whether to print out all the assumption data + (such as ``is_integer`, ``is_real``) associated with the + expression or not. + + Enabling the flag makes the result verbose, and the printed + result may not be deterministic because of the randomness used + in backtracing the assumptions. + + Examples + ======== + + >>> from sympy.printing import print_tree + >>> from sympy import Symbol + >>> x = Symbol('x', odd=True) + >>> y = Symbol('y', even=True) + + Printing with full assumptions information: + + >>> print_tree(y**x) + Pow: y**x + +-Symbol: y + | algebraic: True + | commutative: True + | complex: True + | even: True + | extended_real: True + | finite: True + | hermitian: True + | imaginary: False + | infinite: False + | integer: True + | irrational: False + | noninteger: False + | odd: False + | rational: True + | real: True + | transcendental: False + +-Symbol: x + algebraic: True + commutative: True + complex: True + even: False + extended_nonzero: True + extended_real: True + finite: True + hermitian: True + imaginary: False + infinite: False + integer: True + irrational: False + noninteger: False + nonzero: True + odd: True + rational: True + real: True + transcendental: False + zero: False + + Hiding the assumptions: + + >>> print_tree(y**x, assumptions=False) + Pow: y**x + +-Symbol: y + +-Symbol: x + + See Also + ======== + + tree + + """ + print(tree(node, assumptions=assumptions)) diff --git a/venv/lib/python3.10/site-packages/sympy/series/__init__.py b/venv/lib/python3.10/site-packages/sympy/series/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..953653e21856b82bc0b708ccd922efb728a084ed --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/__init__.py @@ -0,0 +1,23 @@ +"""A module that handles series: find a limit, order the series etc. +""" +from .order import Order +from .limits import limit, Limit +from .gruntz import gruntz +from .series import series +from .approximants import approximants +from .residues import residue +from .sequences import SeqPer, SeqFormula, sequence, SeqAdd, SeqMul +from .fourier import fourier_series +from .formal import fps +from .limitseq import difference_delta, limit_seq + +from sympy.core.singleton import S +EmptySequence = S.EmptySequence + +O = Order + +__all__ = ['Order', 'O', 'limit', 'Limit', 'gruntz', 'series', 'approximants', + 'residue', 'EmptySequence', 'SeqPer', 'SeqFormula', 'sequence', + 'SeqAdd', 'SeqMul', 'fourier_series', 'fps', 'difference_delta', + 'limit_seq' + ] diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a632be6bdd42e0d9a819447b1aeea0f207649f5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/acceleration.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/acceleration.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8810d704e45220dfd3f625119274b663e2b64e4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/acceleration.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/approximants.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/approximants.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2cae091f9332c92c251429c372f02563c58fbda Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/approximants.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/aseries.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/aseries.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e54d30aaa2d6738d14d414c5ee80f0b0d1977e70 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/aseries.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/formal.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/formal.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e15e007a4b8b3fc6054d13aa77fc76395ecd2e99 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/formal.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/fourier.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/fourier.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b199489ddaeb3e315c8021de26daea183cc08fed Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/fourier.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/gruntz.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/gruntz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a22cfe257e1bc6a0a5a217dbf3c7c71592bbc8e9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/gruntz.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/kauers.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/kauers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..114fad0f1731095558eaf55002a8fc6d68f19601 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/kauers.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/limits.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/limits.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43eb2adcde1c7aff9d4baa0fa73bc6f236f71224 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/limits.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/limitseq.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/limitseq.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a56530a6eef66b1335fc92521dd981b9b26a98a1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/limitseq.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/order.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/order.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81d0bc7d41aeadaa98db116e4785152075711277 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/order.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/residues.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/residues.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..442c5c3febb511f0d76d787ddd6c096078f5b7f0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/residues.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/sequences.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/sequences.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b547ce1ae7e1649993c09ee6893e30902672883 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/sequences.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/series.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/series.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3227cbcd255bdcd4a79f54ccf1ddf00dc76d2f05 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/series.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/__pycache__/series_class.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/series_class.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..520f55dc3325d0eccd6c802a6157eb98067a68ae Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/__pycache__/series_class.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/acceleration.py b/venv/lib/python3.10/site-packages/sympy/series/acceleration.py new file mode 100644 index 0000000000000000000000000000000000000000..e2c7c1629a4b0d52e2aa33bd415886dfed515693 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/acceleration.py @@ -0,0 +1,101 @@ +""" +Convergence acceleration / extrapolation methods for series and +sequences. + +References: +Carl M. Bender & Steven A. Orszag, "Advanced Mathematical Methods for +Scientists and Engineers: Asymptotic Methods and Perturbation Theory", +Springer 1999. (Shanks transformation: pp. 368-375, Richardson +extrapolation: pp. 375-377.) +""" + +from sympy.core.numbers import Integer +from sympy.core.singleton import S +from sympy.functions.combinatorial.factorials import factorial + + +def richardson(A, k, n, N): + """ + Calculate an approximation for lim k->oo A(k) using Richardson + extrapolation with the terms A(n), A(n+1), ..., A(n+N+1). + Choosing N ~= 2*n often gives good results. + + Examples + ======== + + A simple example is to calculate exp(1) using the limit definition. + This limit converges slowly; n = 100 only produces two accurate + digits: + + >>> from sympy.abc import n + >>> e = (1 + 1/n)**n + >>> print(round(e.subs(n, 100).evalf(), 10)) + 2.7048138294 + + Richardson extrapolation with 11 appropriately chosen terms gives + a value that is accurate to the indicated precision: + + >>> from sympy import E + >>> from sympy.series.acceleration import richardson + >>> print(round(richardson(e, n, 10, 20).evalf(), 10)) + 2.7182818285 + >>> print(round(E.evalf(), 10)) + 2.7182818285 + + Another useful application is to speed up convergence of series. + Computing 100 terms of the zeta(2) series 1/k**2 yields only + two accurate digits: + + >>> from sympy.abc import k, n + >>> from sympy import Sum + >>> A = Sum(k**-2, (k, 1, n)) + >>> print(round(A.subs(n, 100).evalf(), 10)) + 1.6349839002 + + Richardson extrapolation performs much better: + + >>> from sympy import pi + >>> print(round(richardson(A, n, 10, 20).evalf(), 10)) + 1.6449340668 + >>> print(round(((pi**2)/6).evalf(), 10)) # Exact value + 1.6449340668 + + """ + s = S.Zero + for j in range(0, N + 1): + s += (A.subs(k, Integer(n + j)).doit() * (n + j)**N * + S.NegativeOne**(j + N) / (factorial(j) * factorial(N - j))) + return s + + +def shanks(A, k, n, m=1): + """ + Calculate an approximation for lim k->oo A(k) using the n-term Shanks + transformation S(A)(n). With m > 1, calculate the m-fold recursive + Shanks transformation S(S(...S(A)...))(n). + + The Shanks transformation is useful for summing Taylor series that + converge slowly near a pole or singularity, e.g. for log(2): + + >>> from sympy.abc import k, n + >>> from sympy import Sum, Integer + >>> from sympy.series.acceleration import shanks + >>> A = Sum(Integer(-1)**(k+1) / k, (k, 1, n)) + >>> print(round(A.subs(n, 100).doit().evalf(), 10)) + 0.6881721793 + >>> print(round(shanks(A, n, 25).evalf(), 10)) + 0.6931396564 + >>> print(round(shanks(A, n, 25, 5).evalf(), 10)) + 0.6931471806 + + The correct value is 0.6931471805599453094172321215. + """ + table = [A.subs(k, Integer(j)).doit() for j in range(n + m + 2)] + table2 = table.copy() + + for i in range(1, m + 1): + for j in range(i, n + m + 1): + x, y, z = table[j - 1], table[j], table[j + 1] + table2[j] = (z*x - y**2) / (z + x - 2*y) + table = table2.copy() + return table[n] diff --git a/venv/lib/python3.10/site-packages/sympy/series/approximants.py b/venv/lib/python3.10/site-packages/sympy/series/approximants.py new file mode 100644 index 0000000000000000000000000000000000000000..3d54ce41bc7367606ae6260f8e9ac00149cedc0f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/approximants.py @@ -0,0 +1,103 @@ +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.polys.polytools import lcm +from sympy.utilities import public + +@public +def approximants(l, X=Symbol('x'), simplify=False): + """ + Return a generator for consecutive Pade approximants for a series. + It can also be used for computing the rational generating function of a + series when possible, since the last approximant returned by the generator + will be the generating function (if any). + + Explanation + =========== + + The input list can contain more complex expressions than integer or rational + numbers; symbols may also be involved in the computation. An example below + show how to compute the generating function of the whole Pascal triangle. + + The generator can be asked to apply the sympy.simplify function on each + generated term, which will make the computation slower; however it may be + useful when symbols are involved in the expressions. + + Examples + ======== + + >>> from sympy.series import approximants + >>> from sympy import lucas, fibonacci, symbols, binomial + >>> g = [lucas(k) for k in range(16)] + >>> [e for e in approximants(g)] + [2, -4/(x - 2), (5*x - 2)/(3*x - 1), (x - 2)/(x**2 + x - 1)] + + >>> h = [fibonacci(k) for k in range(16)] + >>> [e for e in approximants(h)] + [x, -x/(x - 1), (x**2 - x)/(2*x - 1), -x/(x**2 + x - 1)] + + >>> x, t = symbols("x,t") + >>> p=[sum(binomial(k,i)*x**i for i in range(k+1)) for k in range(16)] + >>> y = approximants(p, t) + >>> for k in range(3): print(next(y)) + 1 + (x + 1)/((-x - 1)*(t*(x + 1) + (x + 1)/(-x - 1))) + nan + + >>> y = approximants(p, t, simplify=True) + >>> for k in range(3): print(next(y)) + 1 + -1/(t*(x + 1) - 1) + nan + + See Also + ======== + + sympy.concrete.guess.guess_generating_function_rational + mpmath.pade + """ + from sympy.simplify import simplify as simp + from sympy.simplify.radsimp import denom + p1, q1 = [S.One], [S.Zero] + p2, q2 = [S.Zero], [S.One] + while len(l): + b = 0 + while l[b]==0: + b += 1 + if b == len(l): + return + m = [S.One/l[b]] + for k in range(b+1, len(l)): + s = 0 + for j in range(b, k): + s -= l[j+1] * m[b-j-1] + m.append(s/l[b]) + l = m + a, l[0] = l[0], 0 + p = [0] * max(len(p2), b+len(p1)) + q = [0] * max(len(q2), b+len(q1)) + for k in range(len(p2)): + p[k] = a*p2[k] + for k in range(b, b+len(p1)): + p[k] += p1[k-b] + for k in range(len(q2)): + q[k] = a*q2[k] + for k in range(b, b+len(q1)): + q[k] += q1[k-b] + while p[-1]==0: p.pop() + while q[-1]==0: q.pop() + p1, p2 = p2, p + q1, q2 = q2, q + + # yield result + c = 1 + for x in p: + c = lcm(c, denom(x)) + for x in q: + c = lcm(c, denom(x)) + out = ( sum(c*e*X**k for k, e in enumerate(p)) + / sum(c*e*X**k for k, e in enumerate(q)) ) + if simplify: + yield(simp(out)) + else: + yield out + return diff --git a/venv/lib/python3.10/site-packages/sympy/series/aseries.py b/venv/lib/python3.10/site-packages/sympy/series/aseries.py new file mode 100644 index 0000000000000000000000000000000000000000..dbbe0664e6d43a9329f37789c16c48143eda5413 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/aseries.py @@ -0,0 +1,10 @@ +from sympy.core.sympify import sympify + + +def aseries(expr, x=None, n=6, bound=0, hir=False): + """ + See the docstring of Expr.aseries() for complete details of this wrapper. + + """ + expr = sympify(expr) + return expr.aseries(x, n, bound, hir) diff --git a/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__init__.py b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..340f1e15d6c577bb578e900f3ee2db4fe9ae9ba8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/bench_limit.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/bench_limit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b702c9ec3e23ef8a677aba93a4f1ab2544dc7e38 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/bench_limit.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/bench_order.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/bench_order.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2692554bf2c0f5ad8a27691b78345c83758e7cd3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/__pycache__/bench_order.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/benchmarks/bench_limit.py b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/bench_limit.py new file mode 100644 index 0000000000000000000000000000000000000000..eafc28328848dad4b3ea433537971f5785253afe --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/bench_limit.py @@ -0,0 +1,9 @@ +from sympy.core.numbers import oo +from sympy.core.symbol import Symbol +from sympy.series.limits import limit + +x = Symbol('x') + + +def timeit_limit_1x(): + limit(1/x, x, oo) diff --git a/venv/lib/python3.10/site-packages/sympy/series/benchmarks/bench_order.py b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/bench_order.py new file mode 100644 index 0000000000000000000000000000000000000000..1c85fa173dfc2a478792de8ab816c23ba9d408ef --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/benchmarks/bench_order.py @@ -0,0 +1,10 @@ +from sympy.core.add import Add +from sympy.core.symbol import Symbol +from sympy.series.order import O + +x = Symbol('x') +l = [x**i for i in range(1000)] +l.append(O(x**1001)) + +def timeit_order_1x(): + Add(*l) diff --git a/venv/lib/python3.10/site-packages/sympy/series/formal.py b/venv/lib/python3.10/site-packages/sympy/series/formal.py new file mode 100644 index 0000000000000000000000000000000000000000..ada591e03dd607daf8f8a5da1cf38cf283a3ed86 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/formal.py @@ -0,0 +1,1863 @@ +"""Formal Power Series""" + +from collections import defaultdict + +from sympy.core.numbers import (nan, oo, zoo) +from sympy.core.add import Add +from sympy.core.expr import Expr +from sympy.core.function import Derivative, Function, expand +from sympy.core.mul import Mul +from sympy.core.numbers import Rational +from sympy.core.relational import Eq +from sympy.sets.sets import Interval +from sympy.core.singleton import S +from sympy.core.symbol import Wild, Dummy, symbols, Symbol +from sympy.core.sympify import sympify +from sympy.discrete.convolutions import convolution +from sympy.functions.combinatorial.factorials import binomial, factorial, rf +from sympy.functions.combinatorial.numbers import bell +from sympy.functions.elementary.integers import floor, frac, ceiling +from sympy.functions.elementary.miscellaneous import Min, Max +from sympy.functions.elementary.piecewise import Piecewise +from sympy.series.limits import Limit +from sympy.series.order import Order +from sympy.series.sequences import sequence +from sympy.series.series_class import SeriesBase +from sympy.utilities.iterables import iterable + + + +def rational_algorithm(f, x, k, order=4, full=False): + """ + Rational algorithm for computing + formula of coefficients of Formal Power Series + of a function. + + Explanation + =========== + + Applicable when f(x) or some derivative of f(x) + is a rational function in x. + + :func:`rational_algorithm` uses :func:`~.apart` function for partial fraction + decomposition. :func:`~.apart` by default uses 'undetermined coefficients + method'. By setting ``full=True``, 'Bronstein's algorithm' can be used + instead. + + Looks for derivative of a function up to 4'th order (by default). + This can be overridden using order option. + + Parameters + ========== + + x : Symbol + order : int, optional + Order of the derivative of ``f``, Default is 4. + full : bool + + Returns + ======= + + formula : Expr + ind : Expr + Independent terms. + order : int + full : bool + + Examples + ======== + + >>> from sympy import log, atan + >>> from sympy.series.formal import rational_algorithm as ra + >>> from sympy.abc import x, k + + >>> ra(1 / (1 - x), x, k) + (1, 0, 0) + >>> ra(log(1 + x), x, k) + (-1/((-1)**k*k), 0, 1) + + >>> ra(atan(x), x, k, full=True) + ((-I/(2*(-I)**k) + I/(2*I**k))/k, 0, 1) + + Notes + ===== + + By setting ``full=True``, range of admissible functions to be solved using + ``rational_algorithm`` can be increased. This option should be used + carefully as it can significantly slow down the computation as ``doit`` is + performed on the :class:`~.RootSum` object returned by the :func:`~.apart` + function. Use ``full=False`` whenever possible. + + See Also + ======== + + sympy.polys.partfrac.apart + + References + ========== + + .. [1] Formal Power Series - Dominik Gruntz, Wolfram Koepf + .. [2] Power Series in Computer Algebra - Wolfram Koepf + + """ + from sympy.polys import RootSum, apart + from sympy.integrals import integrate + + diff = f + ds = [] # list of diff + + for i in range(order + 1): + if i: + diff = diff.diff(x) + + if diff.is_rational_function(x): + coeff, sep = S.Zero, S.Zero + + terms = apart(diff, x, full=full) + if terms.has(RootSum): + terms = terms.doit() + + for t in Add.make_args(terms): + num, den = t.as_numer_denom() + if not den.has(x): + sep += t + else: + if isinstance(den, Mul): + # m*(n*x - a)**j -> (n*x - a)**j + ind = den.as_independent(x) + den = ind[1] + num /= ind[0] + + # (n*x - a)**j -> (x - b) + den, j = den.as_base_exp() + a, xterm = den.as_coeff_add(x) + + # term -> m/x**n + if not a: + sep += t + continue + + xc = xterm[0].coeff(x) + a /= -xc + num /= xc**j + + ak = ((-1)**j * num * + binomial(j + k - 1, k).rewrite(factorial) / + a**(j + k)) + coeff += ak + + # Hacky, better way? + if coeff.is_zero: + return None + if (coeff.has(x) or coeff.has(zoo) or coeff.has(oo) or + coeff.has(nan)): + return None + + for j in range(i): + coeff = (coeff / (k + j + 1)) + sep = integrate(sep, x) + sep += (ds.pop() - sep).limit(x, 0) # constant of integration + return (coeff.subs(k, k - i), sep, i) + + else: + ds.append(diff) + + return None + + +def rational_independent(terms, x): + """ + Returns a list of all the rationally independent terms. + + Examples + ======== + + >>> from sympy import sin, cos + >>> from sympy.series.formal import rational_independent + >>> from sympy.abc import x + + >>> rational_independent([cos(x), sin(x)], x) + [cos(x), sin(x)] + >>> rational_independent([x**2, sin(x), x*sin(x), x**3], x) + [x**3 + x**2, x*sin(x) + sin(x)] + """ + if not terms: + return [] + + ind = terms[0:1] + + for t in terms[1:]: + n = t.as_independent(x)[1] + for i, term in enumerate(ind): + d = term.as_independent(x)[1] + q = (n / d).cancel() + if q.is_rational_function(x): + ind[i] += t + break + else: + ind.append(t) + return ind + + +def simpleDE(f, x, g, order=4): + r""" + Generates simple DE. + + Explanation + =========== + + DE is of the form + + .. math:: + f^k(x) + \sum\limits_{j=0}^{k-1} A_j f^j(x) = 0 + + where :math:`A_j` should be rational function in x. + + Generates DE's upto order 4 (default). DE's can also have free parameters. + + By increasing order, higher order DE's can be found. + + Yields a tuple of (DE, order). + """ + from sympy.solvers.solveset import linsolve + + a = symbols('a:%d' % (order)) + + def _makeDE(k): + eq = f.diff(x, k) + Add(*[a[i]*f.diff(x, i) for i in range(0, k)]) + DE = g(x).diff(x, k) + Add(*[a[i]*g(x).diff(x, i) for i in range(0, k)]) + return eq, DE + + found = False + for k in range(1, order + 1): + eq, DE = _makeDE(k) + eq = eq.expand() + terms = eq.as_ordered_terms() + ind = rational_independent(terms, x) + if found or len(ind) == k: + sol = dict(zip(a, (i for s in linsolve(ind, a[:k]) for i in s))) + if sol: + found = True + DE = DE.subs(sol) + DE = DE.as_numer_denom()[0] + DE = DE.factor().as_coeff_mul(Derivative)[1][0] + yield DE.collect(Derivative(g(x))), k + + +def exp_re(DE, r, k): + """Converts a DE with constant coefficients (explike) into a RE. + + Explanation + =========== + + Performs the substitution: + + .. math:: + f^j(x) \\to r(k + j) + + Normalises the terms so that lowest order of a term is always r(k). + + Examples + ======== + + >>> from sympy import Function, Derivative + >>> from sympy.series.formal import exp_re + >>> from sympy.abc import x, k + >>> f, r = Function('f'), Function('r') + + >>> exp_re(-f(x) + Derivative(f(x)), r, k) + -r(k) + r(k + 1) + >>> exp_re(Derivative(f(x), x) + Derivative(f(x), (x, 2)), r, k) + r(k) + r(k + 1) + + See Also + ======== + + sympy.series.formal.hyper_re + """ + RE = S.Zero + + g = DE.atoms(Function).pop() + + mini = None + for t in Add.make_args(DE): + coeff, d = t.as_independent(g) + if isinstance(d, Derivative): + j = d.derivative_count + else: + j = 0 + if mini is None or j < mini: + mini = j + RE += coeff * r(k + j) + if mini: + RE = RE.subs(k, k - mini) + return RE + + +def hyper_re(DE, r, k): + """ + Converts a DE into a RE. + + Explanation + =========== + + Performs the substitution: + + .. math:: + x^l f^j(x) \\to (k + 1 - l)_j . a_{k + j - l} + + Normalises the terms so that lowest order of a term is always r(k). + + Examples + ======== + + >>> from sympy import Function, Derivative + >>> from sympy.series.formal import hyper_re + >>> from sympy.abc import x, k + >>> f, r = Function('f'), Function('r') + + >>> hyper_re(-f(x) + Derivative(f(x)), r, k) + (k + 1)*r(k + 1) - r(k) + >>> hyper_re(-x*f(x) + Derivative(f(x), (x, 2)), r, k) + (k + 2)*(k + 3)*r(k + 3) - r(k) + + See Also + ======== + + sympy.series.formal.exp_re + """ + RE = S.Zero + + g = DE.atoms(Function).pop() + x = g.atoms(Symbol).pop() + + mini = None + for t in Add.make_args(DE.expand()): + coeff, d = t.as_independent(g) + c, v = coeff.as_independent(x) + l = v.as_coeff_exponent(x)[1] + if isinstance(d, Derivative): + j = d.derivative_count + else: + j = 0 + RE += c * rf(k + 1 - l, j) * r(k + j - l) + if mini is None or j - l < mini: + mini = j - l + + RE = RE.subs(k, k - mini) + + m = Wild('m') + return RE.collect(r(k + m)) + + +def _transformation_a(f, x, P, Q, k, m, shift): + f *= x**(-shift) + P = P.subs(k, k + shift) + Q = Q.subs(k, k + shift) + return f, P, Q, m + + +def _transformation_c(f, x, P, Q, k, m, scale): + f = f.subs(x, x**scale) + P = P.subs(k, k / scale) + Q = Q.subs(k, k / scale) + m *= scale + return f, P, Q, m + + +def _transformation_e(f, x, P, Q, k, m): + f = f.diff(x) + P = P.subs(k, k + 1) * (k + m + 1) + Q = Q.subs(k, k + 1) * (k + 1) + return f, P, Q, m + + +def _apply_shift(sol, shift): + return [(res, cond + shift) for res, cond in sol] + + +def _apply_scale(sol, scale): + return [(res, cond / scale) for res, cond in sol] + + +def _apply_integrate(sol, x, k): + return [(res / ((cond + 1)*(cond.as_coeff_Add()[1].coeff(k))), cond + 1) + for res, cond in sol] + + +def _compute_formula(f, x, P, Q, k, m, k_max): + """Computes the formula for f.""" + from sympy.polys import roots + + sol = [] + for i in range(k_max + 1, k_max + m + 1): + if (i < 0) == True: + continue + r = f.diff(x, i).limit(x, 0) / factorial(i) + if r.is_zero: + continue + + kterm = m*k + i + res = r + + p = P.subs(k, kterm) + q = Q.subs(k, kterm) + c1 = p.subs(k, 1/k).leadterm(k)[0] + c2 = q.subs(k, 1/k).leadterm(k)[0] + res *= (-c1 / c2)**k + + res *= Mul(*[rf(-r, k)**mul for r, mul in roots(p, k).items()]) + res /= Mul(*[rf(-r, k)**mul for r, mul in roots(q, k).items()]) + + sol.append((res, kterm)) + + return sol + + +def _rsolve_hypergeometric(f, x, P, Q, k, m): + """ + Recursive wrapper to rsolve_hypergeometric. + + Explanation + =========== + + Returns a Tuple of (formula, series independent terms, + maximum power of x in independent terms) if successful + otherwise ``None``. + + See :func:`rsolve_hypergeometric` for details. + """ + from sympy.polys import lcm, roots + from sympy.integrals import integrate + + # transformation - c + proots, qroots = roots(P, k), roots(Q, k) + all_roots = dict(proots) + all_roots.update(qroots) + scale = lcm([r.as_numer_denom()[1] for r, t in all_roots.items() + if r.is_rational]) + f, P, Q, m = _transformation_c(f, x, P, Q, k, m, scale) + + # transformation - a + qroots = roots(Q, k) + if qroots: + k_min = Min(*qroots.keys()) + else: + k_min = S.Zero + shift = k_min + m + f, P, Q, m = _transformation_a(f, x, P, Q, k, m, shift) + + l = (x*f).limit(x, 0) + if not isinstance(l, Limit) and l != 0: # Ideally should only be l != 0 + return None + + qroots = roots(Q, k) + if qroots: + k_max = Max(*qroots.keys()) + else: + k_max = S.Zero + + ind, mp = S.Zero, -oo + for i in range(k_max + m + 1): + r = f.diff(x, i).limit(x, 0) / factorial(i) + if r.is_finite is False: + old_f = f + f, P, Q, m = _transformation_a(f, x, P, Q, k, m, i) + f, P, Q, m = _transformation_e(f, x, P, Q, k, m) + sol, ind, mp = _rsolve_hypergeometric(f, x, P, Q, k, m) + sol = _apply_integrate(sol, x, k) + sol = _apply_shift(sol, i) + ind = integrate(ind, x) + ind += (old_f - ind).limit(x, 0) # constant of integration + mp += 1 + return sol, ind, mp + elif r: + ind += r*x**(i + shift) + pow_x = Rational((i + shift), scale) + if pow_x > mp: + mp = pow_x # maximum power of x + ind = ind.subs(x, x**(1/scale)) + + sol = _compute_formula(f, x, P, Q, k, m, k_max) + sol = _apply_shift(sol, shift) + sol = _apply_scale(sol, scale) + + return sol, ind, mp + + +def rsolve_hypergeometric(f, x, P, Q, k, m): + """ + Solves RE of hypergeometric type. + + Explanation + =========== + + Attempts to solve RE of the form + + Q(k)*a(k + m) - P(k)*a(k) + + Transformations that preserve Hypergeometric type: + + a. x**n*f(x): b(k + m) = R(k - n)*b(k) + b. f(A*x): b(k + m) = A**m*R(k)*b(k) + c. f(x**n): b(k + n*m) = R(k/n)*b(k) + d. f(x**(1/m)): b(k + 1) = R(k*m)*b(k) + e. f'(x): b(k + m) = ((k + m + 1)/(k + 1))*R(k + 1)*b(k) + + Some of these transformations have been used to solve the RE. + + Returns + ======= + + formula : Expr + ind : Expr + Independent terms. + order : int + + Examples + ======== + + >>> from sympy import exp, ln, S + >>> from sympy.series.formal import rsolve_hypergeometric as rh + >>> from sympy.abc import x, k + + >>> rh(exp(x), x, -S.One, (k + 1), k, 1) + (Piecewise((1/factorial(k), Eq(Mod(k, 1), 0)), (0, True)), 1, 1) + + >>> rh(ln(1 + x), x, k**2, k*(k + 1), k, 1) + (Piecewise(((-1)**(k - 1)*factorial(k - 1)/RisingFactorial(2, k - 1), + Eq(Mod(k, 1), 0)), (0, True)), x, 2) + + References + ========== + + .. [1] Formal Power Series - Dominik Gruntz, Wolfram Koepf + .. [2] Power Series in Computer Algebra - Wolfram Koepf + """ + result = _rsolve_hypergeometric(f, x, P, Q, k, m) + + if result is None: + return None + + sol_list, ind, mp = result + + sol_dict = defaultdict(lambda: S.Zero) + for res, cond in sol_list: + j, mk = cond.as_coeff_Add() + c = mk.coeff(k) + + if j.is_integer is False: + res *= x**frac(j) + j = floor(j) + + res = res.subs(k, (k - j) / c) + cond = Eq(k % c, j % c) + sol_dict[cond] += res # Group together formula for same conditions + + sol = [(res, cond) for cond, res in sol_dict.items()] + sol.append((S.Zero, True)) + sol = Piecewise(*sol) + + if mp is -oo: + s = S.Zero + elif mp.is_integer is False: + s = ceiling(mp) + else: + s = mp + 1 + + # save all the terms of + # form 1/x**k in ind + if s < 0: + ind += sum(sequence(sol * x**k, (k, s, -1))) + s = S.Zero + + return (sol, ind, s) + + +def _solve_hyper_RE(f, x, RE, g, k): + """See docstring of :func:`rsolve_hypergeometric` for details.""" + terms = Add.make_args(RE) + + if len(terms) == 2: + gs = list(RE.atoms(Function)) + P, Q = map(RE.coeff, gs) + m = gs[1].args[0] - gs[0].args[0] + if m < 0: + P, Q = Q, P + m = abs(m) + return rsolve_hypergeometric(f, x, P, Q, k, m) + + +def _solve_explike_DE(f, x, DE, g, k): + """Solves DE with constant coefficients.""" + from sympy.solvers import rsolve + + for t in Add.make_args(DE): + coeff, d = t.as_independent(g) + if coeff.free_symbols: + return + + RE = exp_re(DE, g, k) + + init = {} + for i in range(len(Add.make_args(RE))): + if i: + f = f.diff(x) + init[g(k).subs(k, i)] = f.limit(x, 0) + + sol = rsolve(RE, g(k), init) + + if sol: + return (sol / factorial(k), S.Zero, S.Zero) + + +def _solve_simple(f, x, DE, g, k): + """Converts DE into RE and solves using :func:`rsolve`.""" + from sympy.solvers import rsolve + + RE = hyper_re(DE, g, k) + + init = {} + for i in range(len(Add.make_args(RE))): + if i: + f = f.diff(x) + init[g(k).subs(k, i)] = f.limit(x, 0) / factorial(i) + + sol = rsolve(RE, g(k), init) + + if sol: + return (sol, S.Zero, S.Zero) + + +def _transform_explike_DE(DE, g, x, order, syms): + """Converts DE with free parameters into DE with constant coefficients.""" + from sympy.solvers.solveset import linsolve + + eq = [] + highest_coeff = DE.coeff(Derivative(g(x), x, order)) + for i in range(order): + coeff = DE.coeff(Derivative(g(x), x, i)) + coeff = (coeff / highest_coeff).expand().collect(x) + eq.extend(Add.make_args(coeff)) + temp = [] + for e in eq: + if e.has(x): + break + elif e.has(Symbol): + temp.append(e) + else: + eq = temp + if eq: + sol = dict(zip(syms, (i for s in linsolve(eq, list(syms)) for i in s))) + if sol: + DE = DE.subs(sol) + DE = DE.factor().as_coeff_mul(Derivative)[1][0] + DE = DE.collect(Derivative(g(x))) + return DE + + +def _transform_DE_RE(DE, g, k, order, syms): + """Converts DE with free parameters into RE of hypergeometric type.""" + from sympy.solvers.solveset import linsolve + + RE = hyper_re(DE, g, k) + + eq = [RE.coeff(g(k + i)) for i in range(1, order)] + sol = dict(zip(syms, (i for s in linsolve(eq, list(syms)) for i in s))) + if sol: + m = Wild('m') + RE = RE.subs(sol) + RE = RE.factor().as_numer_denom()[0].collect(g(k + m)) + RE = RE.as_coeff_mul(g)[1][0] + for i in range(order): # smallest order should be g(k) + if RE.coeff(g(k + i)) and i: + RE = RE.subs(k, k - i) + break + return RE + + +def solve_de(f, x, DE, order, g, k): + """ + Solves the DE. + + Explanation + =========== + + Tries to solve DE by either converting into a RE containing two terms or + converting into a DE having constant coefficients. + + Returns + ======= + + formula : Expr + ind : Expr + Independent terms. + order : int + + Examples + ======== + + >>> from sympy import Derivative as D, Function + >>> from sympy import exp, ln + >>> from sympy.series.formal import solve_de + >>> from sympy.abc import x, k + >>> f = Function('f') + + >>> solve_de(exp(x), x, D(f(x), x) - f(x), 1, f, k) + (Piecewise((1/factorial(k), Eq(Mod(k, 1), 0)), (0, True)), 1, 1) + + >>> solve_de(ln(1 + x), x, (x + 1)*D(f(x), x, 2) + D(f(x)), 2, f, k) + (Piecewise(((-1)**(k - 1)*factorial(k - 1)/RisingFactorial(2, k - 1), + Eq(Mod(k, 1), 0)), (0, True)), x, 2) + """ + sol = None + syms = DE.free_symbols.difference({g, x}) + + if syms: + RE = _transform_DE_RE(DE, g, k, order, syms) + else: + RE = hyper_re(DE, g, k) + if not RE.free_symbols.difference({k}): + sol = _solve_hyper_RE(f, x, RE, g, k) + + if sol: + return sol + + if syms: + DE = _transform_explike_DE(DE, g, x, order, syms) + if not DE.free_symbols.difference({x}): + sol = _solve_explike_DE(f, x, DE, g, k) + + if sol: + return sol + + +def hyper_algorithm(f, x, k, order=4): + """ + Hypergeometric algorithm for computing Formal Power Series. + + Explanation + =========== + + Steps: + * Generates DE + * Convert the DE into RE + * Solves the RE + + Examples + ======== + + >>> from sympy import exp, ln + >>> from sympy.series.formal import hyper_algorithm + + >>> from sympy.abc import x, k + + >>> hyper_algorithm(exp(x), x, k) + (Piecewise((1/factorial(k), Eq(Mod(k, 1), 0)), (0, True)), 1, 1) + + >>> hyper_algorithm(ln(1 + x), x, k) + (Piecewise(((-1)**(k - 1)*factorial(k - 1)/RisingFactorial(2, k - 1), + Eq(Mod(k, 1), 0)), (0, True)), x, 2) + + See Also + ======== + + sympy.series.formal.simpleDE + sympy.series.formal.solve_de + """ + g = Function('g') + + des = [] # list of DE's + sol = None + for DE, i in simpleDE(f, x, g, order): + if DE is not None: + sol = solve_de(f, x, DE, i, g, k) + if sol: + return sol + if not DE.free_symbols.difference({x}): + des.append(DE) + + # If nothing works + # Try plain rsolve + for DE in des: + sol = _solve_simple(f, x, DE, g, k) + if sol: + return sol + + +def _compute_fps(f, x, x0, dir, hyper, order, rational, full): + """Recursive wrapper to compute fps. + + See :func:`compute_fps` for details. + """ + if x0 in [S.Infinity, S.NegativeInfinity]: + dir = S.One if x0 is S.Infinity else -S.One + temp = f.subs(x, 1/x) + result = _compute_fps(temp, x, 0, dir, hyper, order, rational, full) + if result is None: + return None + return (result[0], result[1].subs(x, 1/x), result[2].subs(x, 1/x)) + elif x0 or dir == -S.One: + if dir == -S.One: + rep = -x + x0 + rep2 = -x + rep2b = x0 + else: + rep = x + x0 + rep2 = x + rep2b = -x0 + temp = f.subs(x, rep) + result = _compute_fps(temp, x, 0, S.One, hyper, order, rational, full) + if result is None: + return None + return (result[0], result[1].subs(x, rep2 + rep2b), + result[2].subs(x, rep2 + rep2b)) + + if f.is_polynomial(x): + k = Dummy('k') + ak = sequence(Coeff(f, x, k), (k, 1, oo)) + xk = sequence(x**k, (k, 0, oo)) + ind = f.coeff(x, 0) + return ak, xk, ind + + # Break instances of Add + # this allows application of different + # algorithms on different terms increasing the + # range of admissible functions. + if isinstance(f, Add): + result = False + ak = sequence(S.Zero, (0, oo)) + ind, xk = S.Zero, None + for t in Add.make_args(f): + res = _compute_fps(t, x, 0, S.One, hyper, order, rational, full) + if res: + if not result: + result = True + xk = res[1] + if res[0].start > ak.start: + seq = ak + s, f = ak.start, res[0].start + else: + seq = res[0] + s, f = res[0].start, ak.start + save = Add(*[z[0]*z[1] for z in zip(seq[0:(f - s)], xk[s:f])]) + ak += res[0] + ind += res[2] + save + else: + ind += t + if result: + return ak, xk, ind + return None + + # The symbolic term - symb, if present, is being separated from the function + # Otherwise symb is being set to S.One + syms = f.free_symbols.difference({x}) + (f, symb) = expand(f).as_independent(*syms) + + result = None + + # from here on it's x0=0 and dir=1 handling + k = Dummy('k') + if rational: + result = rational_algorithm(f, x, k, order, full) + + if result is None and hyper: + result = hyper_algorithm(f, x, k, order) + + if result is None: + return None + + from sympy.simplify.powsimp import powsimp + if symb.is_zero: + symb = S.One + else: + symb = powsimp(symb) + ak = sequence(result[0], (k, result[2], oo)) + xk_formula = powsimp(x**k * symb) + xk = sequence(xk_formula, (k, 0, oo)) + ind = powsimp(result[1] * symb) + + return ak, xk, ind + + +def compute_fps(f, x, x0=0, dir=1, hyper=True, order=4, rational=True, + full=False): + """ + Computes the formula for Formal Power Series of a function. + + Explanation + =========== + + Tries to compute the formula by applying the following techniques + (in order): + + * rational_algorithm + * Hypergeometric algorithm + + Parameters + ========== + + x : Symbol + x0 : number, optional + Point to perform series expansion about. Default is 0. + dir : {1, -1, '+', '-'}, optional + If dir is 1 or '+' the series is calculated from the right and + for -1 or '-' the series is calculated from the left. For smooth + functions this flag will not alter the results. Default is 1. + hyper : {True, False}, optional + Set hyper to False to skip the hypergeometric algorithm. + By default it is set to False. + order : int, optional + Order of the derivative of ``f``, Default is 4. + rational : {True, False}, optional + Set rational to False to skip rational algorithm. By default it is set + to True. + full : {True, False}, optional + Set full to True to increase the range of rational algorithm. + See :func:`rational_algorithm` for details. By default it is set to + False. + + Returns + ======= + + ak : sequence + Sequence of coefficients. + xk : sequence + Sequence of powers of x. + ind : Expr + Independent terms. + mul : Pow + Common terms. + + See Also + ======== + + sympy.series.formal.rational_algorithm + sympy.series.formal.hyper_algorithm + """ + f = sympify(f) + x = sympify(x) + + if not f.has(x): + return None + + x0 = sympify(x0) + + if dir == '+': + dir = S.One + elif dir == '-': + dir = -S.One + elif dir not in [S.One, -S.One]: + raise ValueError("Dir must be '+' or '-'") + else: + dir = sympify(dir) + + return _compute_fps(f, x, x0, dir, hyper, order, rational, full) + + +class Coeff(Function): + """ + Coeff(p, x, n) represents the nth coefficient of the polynomial p in x + """ + @classmethod + def eval(cls, p, x, n): + if p.is_polynomial(x) and n.is_integer: + return p.coeff(x, n) + + +class FormalPowerSeries(SeriesBase): + """ + Represents Formal Power Series of a function. + + Explanation + =========== + + No computation is performed. This class should only to be used to represent + a series. No checks are performed. + + For computing a series use :func:`fps`. + + See Also + ======== + + sympy.series.formal.fps + """ + def __new__(cls, *args): + args = map(sympify, args) + return Expr.__new__(cls, *args) + + def __init__(self, *args): + ak = args[4][0] + k = ak.variables[0] + self.ak_seq = sequence(ak.formula, (k, 1, oo)) + self.fact_seq = sequence(factorial(k), (k, 1, oo)) + self.bell_coeff_seq = self.ak_seq * self.fact_seq + self.sign_seq = sequence((-1, 1), (k, 1, oo)) + + @property + def function(self): + return self.args[0] + + @property + def x(self): + return self.args[1] + + @property + def x0(self): + return self.args[2] + + @property + def dir(self): + return self.args[3] + + @property + def ak(self): + return self.args[4][0] + + @property + def xk(self): + return self.args[4][1] + + @property + def ind(self): + return self.args[4][2] + + @property + def interval(self): + return Interval(0, oo) + + @property + def start(self): + return self.interval.inf + + @property + def stop(self): + return self.interval.sup + + @property + def length(self): + return oo + + @property + def infinite(self): + """Returns an infinite representation of the series""" + from sympy.concrete import Sum + ak, xk = self.ak, self.xk + k = ak.variables[0] + inf_sum = Sum(ak.formula * xk.formula, (k, ak.start, ak.stop)) + + return self.ind + inf_sum + + def _get_pow_x(self, term): + """Returns the power of x in a term.""" + xterm, pow_x = term.as_independent(self.x)[1].as_base_exp() + if not xterm.has(self.x): + return S.Zero + return pow_x + + def polynomial(self, n=6): + """ + Truncated series as polynomial. + + Explanation + =========== + + Returns series expansion of ``f`` upto order ``O(x**n)`` + as a polynomial(without ``O`` term). + """ + terms = [] + sym = self.free_symbols + for i, t in enumerate(self): + xp = self._get_pow_x(t) + if xp.has(*sym): + xp = xp.as_coeff_add(*sym)[0] + if xp >= n: + break + elif xp.is_integer is True and i == n + 1: + break + elif t is not S.Zero: + terms.append(t) + + return Add(*terms) + + def truncate(self, n=6): + """ + Truncated series. + + Explanation + =========== + + Returns truncated series expansion of f upto + order ``O(x**n)``. + + If n is ``None``, returns an infinite iterator. + """ + if n is None: + return iter(self) + + x, x0 = self.x, self.x0 + pt_xk = self.xk.coeff(n) + if x0 is S.NegativeInfinity: + x0 = S.Infinity + + return self.polynomial(n) + Order(pt_xk, (x, x0)) + + def zero_coeff(self): + return self._eval_term(0) + + def _eval_term(self, pt): + try: + pt_xk = self.xk.coeff(pt) + pt_ak = self.ak.coeff(pt).simplify() # Simplify the coefficients + except IndexError: + term = S.Zero + else: + term = (pt_ak * pt_xk) + + if self.ind: + ind = S.Zero + sym = self.free_symbols + for t in Add.make_args(self.ind): + pow_x = self._get_pow_x(t) + if pow_x.has(*sym): + pow_x = pow_x.as_coeff_add(*sym)[0] + if pt == 0 and pow_x < 1: + ind += t + elif pow_x >= pt and pow_x < pt + 1: + ind += t + term += ind + + return term.collect(self.x) + + def _eval_subs(self, old, new): + x = self.x + if old.has(x): + return self + + def _eval_as_leading_term(self, x, logx, cdir): + for t in self: + if t is not S.Zero: + return t + + def _eval_derivative(self, x): + f = self.function.diff(x) + ind = self.ind.diff(x) + + pow_xk = self._get_pow_x(self.xk.formula) + ak = self.ak + k = ak.variables[0] + if ak.formula.has(x): + form = [] + for e, c in ak.formula.args: + temp = S.Zero + for t in Add.make_args(e): + pow_x = self._get_pow_x(t) + temp += t * (pow_xk + pow_x) + form.append((temp, c)) + form = Piecewise(*form) + ak = sequence(form.subs(k, k + 1), (k, ak.start - 1, ak.stop)) + else: + ak = sequence((ak.formula * pow_xk).subs(k, k + 1), + (k, ak.start - 1, ak.stop)) + + return self.func(f, self.x, self.x0, self.dir, (ak, self.xk, ind)) + + def integrate(self, x=None, **kwargs): + """ + Integrate Formal Power Series. + + Examples + ======== + + >>> from sympy import fps, sin, integrate + >>> from sympy.abc import x + >>> f = fps(sin(x)) + >>> f.integrate(x).truncate() + -1 + x**2/2 - x**4/24 + O(x**6) + >>> integrate(f, (x, 0, 1)) + 1 - cos(1) + """ + from sympy.integrals import integrate + + if x is None: + x = self.x + elif iterable(x): + return integrate(self.function, x) + + f = integrate(self.function, x) + ind = integrate(self.ind, x) + ind += (f - ind).limit(x, 0) # constant of integration + + pow_xk = self._get_pow_x(self.xk.formula) + ak = self.ak + k = ak.variables[0] + if ak.formula.has(x): + form = [] + for e, c in ak.formula.args: + temp = S.Zero + for t in Add.make_args(e): + pow_x = self._get_pow_x(t) + temp += t / (pow_xk + pow_x + 1) + form.append((temp, c)) + form = Piecewise(*form) + ak = sequence(form.subs(k, k - 1), (k, ak.start + 1, ak.stop)) + else: + ak = sequence((ak.formula / (pow_xk + 1)).subs(k, k - 1), + (k, ak.start + 1, ak.stop)) + + return self.func(f, self.x, self.x0, self.dir, (ak, self.xk, ind)) + + def product(self, other, x=None, n=6): + """ + Multiplies two Formal Power Series, using discrete convolution and + return the truncated terms upto specified order. + + Parameters + ========== + + n : Number, optional + Specifies the order of the term up to which the polynomial should + be truncated. + + Examples + ======== + + >>> from sympy import fps, sin, exp + >>> from sympy.abc import x + >>> f1 = fps(sin(x)) + >>> f2 = fps(exp(x)) + + >>> f1.product(f2, x).truncate(4) + x + x**2 + x**3/3 + O(x**4) + + See Also + ======== + + sympy.discrete.convolutions + sympy.series.formal.FormalPowerSeriesProduct + + """ + + if n is None: + return iter(self) + + other = sympify(other) + + if not isinstance(other, FormalPowerSeries): + raise ValueError("Both series should be an instance of FormalPowerSeries" + " class.") + + if self.dir != other.dir: + raise ValueError("Both series should be calculated from the" + " same direction.") + elif self.x0 != other.x0: + raise ValueError("Both series should be calculated about the" + " same point.") + + elif self.x != other.x: + raise ValueError("Both series should have the same symbol.") + + return FormalPowerSeriesProduct(self, other) + + def coeff_bell(self, n): + r""" + self.coeff_bell(n) returns a sequence of Bell polynomials of the second kind. + Note that ``n`` should be a integer. + + The second kind of Bell polynomials (are sometimes called "partial" Bell + polynomials or incomplete Bell polynomials) are defined as + + .. math:: + B_{n,k}(x_1, x_2,\dotsc x_{n-k+1}) = + \sum_{j_1+j_2+j_2+\dotsb=k \atop j_1+2j_2+3j_2+\dotsb=n} + \frac{n!}{j_1!j_2!\dotsb j_{n-k+1}!} + \left(\frac{x_1}{1!} \right)^{j_1} + \left(\frac{x_2}{2!} \right)^{j_2} \dotsb + \left(\frac{x_{n-k+1}}{(n-k+1)!} \right) ^{j_{n-k+1}}. + + * ``bell(n, k, (x1, x2, ...))`` gives Bell polynomials of the second kind, + `B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1})`. + + See Also + ======== + + sympy.functions.combinatorial.numbers.bell + + """ + + inner_coeffs = [bell(n, j, tuple(self.bell_coeff_seq[:n-j+1])) for j in range(1, n+1)] + + k = Dummy('k') + return sequence(tuple(inner_coeffs), (k, 1, oo)) + + def compose(self, other, x=None, n=6): + r""" + Returns the truncated terms of the formal power series of the composed function, + up to specified ``n``. + + Explanation + =========== + + If ``f`` and ``g`` are two formal power series of two different functions, + then the coefficient sequence ``ak`` of the composed formal power series `fp` + will be as follows. + + .. math:: + \sum\limits_{k=0}^{n} b_k B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1}) + + Parameters + ========== + + n : Number, optional + Specifies the order of the term up to which the polynomial should + be truncated. + + Examples + ======== + + >>> from sympy import fps, sin, exp + >>> from sympy.abc import x + >>> f1 = fps(exp(x)) + >>> f2 = fps(sin(x)) + + >>> f1.compose(f2, x).truncate() + 1 + x + x**2/2 - x**4/8 - x**5/15 + O(x**6) + + >>> f1.compose(f2, x).truncate(8) + 1 + x + x**2/2 - x**4/8 - x**5/15 - x**6/240 + x**7/90 + O(x**8) + + See Also + ======== + + sympy.functions.combinatorial.numbers.bell + sympy.series.formal.FormalPowerSeriesCompose + + References + ========== + + .. [1] Comtet, Louis: Advanced combinatorics; the art of finite and infinite expansions. Reidel, 1974. + + """ + + if n is None: + return iter(self) + + other = sympify(other) + + if not isinstance(other, FormalPowerSeries): + raise ValueError("Both series should be an instance of FormalPowerSeries" + " class.") + + if self.dir != other.dir: + raise ValueError("Both series should be calculated from the" + " same direction.") + elif self.x0 != other.x0: + raise ValueError("Both series should be calculated about the" + " same point.") + + elif self.x != other.x: + raise ValueError("Both series should have the same symbol.") + + if other._eval_term(0).as_coeff_mul(other.x)[0] is not S.Zero: + raise ValueError("The formal power series of the inner function should not have any " + "constant coefficient term.") + + return FormalPowerSeriesCompose(self, other) + + def inverse(self, x=None, n=6): + r""" + Returns the truncated terms of the inverse of the formal power series, + up to specified ``n``. + + Explanation + =========== + + If ``f`` and ``g`` are two formal power series of two different functions, + then the coefficient sequence ``ak`` of the composed formal power series ``fp`` + will be as follows. + + .. math:: + \sum\limits_{k=0}^{n} (-1)^{k} x_0^{-k-1} B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1}) + + Parameters + ========== + + n : Number, optional + Specifies the order of the term up to which the polynomial should + be truncated. + + Examples + ======== + + >>> from sympy import fps, exp, cos + >>> from sympy.abc import x + >>> f1 = fps(exp(x)) + >>> f2 = fps(cos(x)) + + >>> f1.inverse(x).truncate() + 1 - x + x**2/2 - x**3/6 + x**4/24 - x**5/120 + O(x**6) + + >>> f2.inverse(x).truncate(8) + 1 + x**2/2 + 5*x**4/24 + 61*x**6/720 + O(x**8) + + See Also + ======== + + sympy.functions.combinatorial.numbers.bell + sympy.series.formal.FormalPowerSeriesInverse + + References + ========== + + .. [1] Comtet, Louis: Advanced combinatorics; the art of finite and infinite expansions. Reidel, 1974. + + """ + + if n is None: + return iter(self) + + if self._eval_term(0).is_zero: + raise ValueError("Constant coefficient should exist for an inverse of a formal" + " power series to exist.") + + return FormalPowerSeriesInverse(self) + + def __add__(self, other): + other = sympify(other) + + if isinstance(other, FormalPowerSeries): + if self.dir != other.dir: + raise ValueError("Both series should be calculated from the" + " same direction.") + elif self.x0 != other.x0: + raise ValueError("Both series should be calculated about the" + " same point.") + + x, y = self.x, other.x + f = self.function + other.function.subs(y, x) + + if self.x not in f.free_symbols: + return f + + ak = self.ak + other.ak + if self.ak.start > other.ak.start: + seq = other.ak + s, e = other.ak.start, self.ak.start + else: + seq = self.ak + s, e = self.ak.start, other.ak.start + save = Add(*[z[0]*z[1] for z in zip(seq[0:(e - s)], self.xk[s:e])]) + ind = self.ind + other.ind + save + + return self.func(f, x, self.x0, self.dir, (ak, self.xk, ind)) + + elif not other.has(self.x): + f = self.function + other + ind = self.ind + other + + return self.func(f, self.x, self.x0, self.dir, + (self.ak, self.xk, ind)) + + return Add(self, other) + + def __radd__(self, other): + return self.__add__(other) + + def __neg__(self): + return self.func(-self.function, self.x, self.x0, self.dir, + (-self.ak, self.xk, -self.ind)) + + def __sub__(self, other): + return self.__add__(-other) + + def __rsub__(self, other): + return (-self).__add__(other) + + def __mul__(self, other): + other = sympify(other) + + if other.has(self.x): + return Mul(self, other) + + f = self.function * other + ak = self.ak.coeff_mul(other) + ind = self.ind * other + + return self.func(f, self.x, self.x0, self.dir, (ak, self.xk, ind)) + + def __rmul__(self, other): + return self.__mul__(other) + + +class FiniteFormalPowerSeries(FormalPowerSeries): + """Base Class for Product, Compose and Inverse classes""" + + def __init__(self, *args): + pass + + @property + def ffps(self): + return self.args[0] + + @property + def gfps(self): + return self.args[1] + + @property + def f(self): + return self.ffps.function + + @property + def g(self): + return self.gfps.function + + @property + def infinite(self): + raise NotImplementedError("No infinite version for an object of" + " FiniteFormalPowerSeries class.") + + def _eval_terms(self, n): + raise NotImplementedError("(%s)._eval_terms()" % self) + + def _eval_term(self, pt): + raise NotImplementedError("By the current logic, one can get terms" + "upto a certain order, instead of getting term by term.") + + def polynomial(self, n): + return self._eval_terms(n) + + def truncate(self, n=6): + ffps = self.ffps + pt_xk = ffps.xk.coeff(n) + x, x0 = ffps.x, ffps.x0 + + return self.polynomial(n) + Order(pt_xk, (x, x0)) + + def _eval_derivative(self, x): + raise NotImplementedError + + def integrate(self, x): + raise NotImplementedError + + +class FormalPowerSeriesProduct(FiniteFormalPowerSeries): + """Represents the product of two formal power series of two functions. + + Explanation + =========== + + No computation is performed. Terms are calculated using a term by term logic, + instead of a point by point logic. + + There are two differences between a :obj:`FormalPowerSeries` object and a + :obj:`FormalPowerSeriesProduct` object. The first argument contains the two + functions involved in the product. Also, the coefficient sequence contains + both the coefficient sequence of the formal power series of the involved functions. + + See Also + ======== + + sympy.series.formal.FormalPowerSeries + sympy.series.formal.FiniteFormalPowerSeries + + """ + + def __init__(self, *args): + ffps, gfps = self.ffps, self.gfps + + k = ffps.ak.variables[0] + self.coeff1 = sequence(ffps.ak.formula, (k, 0, oo)) + + k = gfps.ak.variables[0] + self.coeff2 = sequence(gfps.ak.formula, (k, 0, oo)) + + @property + def function(self): + """Function of the product of two formal power series.""" + return self.f * self.g + + def _eval_terms(self, n): + """ + Returns the first ``n`` terms of the product formal power series. + Term by term logic is implemented here. + + Examples + ======== + + >>> from sympy import fps, sin, exp + >>> from sympy.abc import x + >>> f1 = fps(sin(x)) + >>> f2 = fps(exp(x)) + >>> fprod = f1.product(f2, x) + + >>> fprod._eval_terms(4) + x**3/3 + x**2 + x + + See Also + ======== + + sympy.series.formal.FormalPowerSeries.product + + """ + coeff1, coeff2 = self.coeff1, self.coeff2 + + aks = convolution(coeff1[:n], coeff2[:n]) + + terms = [] + for i in range(0, n): + terms.append(aks[i] * self.ffps.xk.coeff(i)) + + return Add(*terms) + + +class FormalPowerSeriesCompose(FiniteFormalPowerSeries): + """ + Represents the composed formal power series of two functions. + + Explanation + =========== + + No computation is performed. Terms are calculated using a term by term logic, + instead of a point by point logic. + + There are two differences between a :obj:`FormalPowerSeries` object and a + :obj:`FormalPowerSeriesCompose` object. The first argument contains the outer + function and the inner function involved in the omposition. Also, the + coefficient sequence contains the generic sequence which is to be multiplied + by a custom ``bell_seq`` finite sequence. The finite terms will then be added up to + get the final terms. + + See Also + ======== + + sympy.series.formal.FormalPowerSeries + sympy.series.formal.FiniteFormalPowerSeries + + """ + + @property + def function(self): + """Function for the composed formal power series.""" + f, g, x = self.f, self.g, self.ffps.x + return f.subs(x, g) + + def _eval_terms(self, n): + """ + Returns the first `n` terms of the composed formal power series. + Term by term logic is implemented here. + + Explanation + =========== + + The coefficient sequence of the :obj:`FormalPowerSeriesCompose` object is the generic sequence. + It is multiplied by ``bell_seq`` to get a sequence, whose terms are added up to get + the final terms for the polynomial. + + Examples + ======== + + >>> from sympy import fps, sin, exp + >>> from sympy.abc import x + >>> f1 = fps(exp(x)) + >>> f2 = fps(sin(x)) + >>> fcomp = f1.compose(f2, x) + + >>> fcomp._eval_terms(6) + -x**5/15 - x**4/8 + x**2/2 + x + 1 + + >>> fcomp._eval_terms(8) + x**7/90 - x**6/240 - x**5/15 - x**4/8 + x**2/2 + x + 1 + + See Also + ======== + + sympy.series.formal.FormalPowerSeries.compose + sympy.series.formal.FormalPowerSeries.coeff_bell + + """ + + ffps, gfps = self.ffps, self.gfps + terms = [ffps.zero_coeff()] + + for i in range(1, n): + bell_seq = gfps.coeff_bell(i) + seq = (ffps.bell_coeff_seq * bell_seq) + terms.append(Add(*(seq[:i])) / ffps.fact_seq[i-1] * ffps.xk.coeff(i)) + + return Add(*terms) + + +class FormalPowerSeriesInverse(FiniteFormalPowerSeries): + """ + Represents the Inverse of a formal power series. + + Explanation + =========== + + No computation is performed. Terms are calculated using a term by term logic, + instead of a point by point logic. + + There is a single difference between a :obj:`FormalPowerSeries` object and a + :obj:`FormalPowerSeriesInverse` object. The coefficient sequence contains the + generic sequence which is to be multiplied by a custom ``bell_seq`` finite sequence. + The finite terms will then be added up to get the final terms. + + See Also + ======== + + sympy.series.formal.FormalPowerSeries + sympy.series.formal.FiniteFormalPowerSeries + + """ + def __init__(self, *args): + ffps = self.ffps + k = ffps.xk.variables[0] + + inv = ffps.zero_coeff() + inv_seq = sequence(inv ** (-(k + 1)), (k, 1, oo)) + self.aux_seq = ffps.sign_seq * ffps.fact_seq * inv_seq + + @property + def function(self): + """Function for the inverse of a formal power series.""" + f = self.f + return 1 / f + + @property + def g(self): + raise ValueError("Only one function is considered while performing" + "inverse of a formal power series.") + + @property + def gfps(self): + raise ValueError("Only one function is considered while performing" + "inverse of a formal power series.") + + def _eval_terms(self, n): + """ + Returns the first ``n`` terms of the composed formal power series. + Term by term logic is implemented here. + + Explanation + =========== + + The coefficient sequence of the `FormalPowerSeriesInverse` object is the generic sequence. + It is multiplied by ``bell_seq`` to get a sequence, whose terms are added up to get + the final terms for the polynomial. + + Examples + ======== + + >>> from sympy import fps, exp, cos + >>> from sympy.abc import x + >>> f1 = fps(exp(x)) + >>> f2 = fps(cos(x)) + >>> finv1, finv2 = f1.inverse(), f2.inverse() + + >>> finv1._eval_terms(6) + -x**5/120 + x**4/24 - x**3/6 + x**2/2 - x + 1 + + >>> finv2._eval_terms(8) + 61*x**6/720 + 5*x**4/24 + x**2/2 + 1 + + See Also + ======== + + sympy.series.formal.FormalPowerSeries.inverse + sympy.series.formal.FormalPowerSeries.coeff_bell + + """ + ffps = self.ffps + terms = [ffps.zero_coeff()] + + for i in range(1, n): + bell_seq = ffps.coeff_bell(i) + seq = (self.aux_seq * bell_seq) + terms.append(Add(*(seq[:i])) / ffps.fact_seq[i-1] * ffps.xk.coeff(i)) + + return Add(*terms) + + +def fps(f, x=None, x0=0, dir=1, hyper=True, order=4, rational=True, full=False): + """ + Generates Formal Power Series of ``f``. + + Explanation + =========== + + Returns the formal series expansion of ``f`` around ``x = x0`` + with respect to ``x`` in the form of a ``FormalPowerSeries`` object. + + Formal Power Series is represented using an explicit formula + computed using different algorithms. + + See :func:`compute_fps` for the more details regarding the computation + of formula. + + Parameters + ========== + + x : Symbol, optional + If x is None and ``f`` is univariate, the univariate symbols will be + supplied, otherwise an error will be raised. + x0 : number, optional + Point to perform series expansion about. Default is 0. + dir : {1, -1, '+', '-'}, optional + If dir is 1 or '+' the series is calculated from the right and + for -1 or '-' the series is calculated from the left. For smooth + functions this flag will not alter the results. Default is 1. + hyper : {True, False}, optional + Set hyper to False to skip the hypergeometric algorithm. + By default it is set to False. + order : int, optional + Order of the derivative of ``f``, Default is 4. + rational : {True, False}, optional + Set rational to False to skip rational algorithm. By default it is set + to True. + full : {True, False}, optional + Set full to True to increase the range of rational algorithm. + See :func:`rational_algorithm` for details. By default it is set to + False. + + Examples + ======== + + >>> from sympy import fps, ln, atan, sin + >>> from sympy.abc import x, n + + Rational Functions + + >>> fps(ln(1 + x)).truncate() + x - x**2/2 + x**3/3 - x**4/4 + x**5/5 + O(x**6) + + >>> fps(atan(x), full=True).truncate() + x - x**3/3 + x**5/5 + O(x**6) + + Symbolic Functions + + >>> fps(x**n*sin(x**2), x).truncate(8) + -x**(n + 6)/6 + x**(n + 2) + O(x**(n + 8)) + + See Also + ======== + + sympy.series.formal.FormalPowerSeries + sympy.series.formal.compute_fps + """ + f = sympify(f) + + if x is None: + free = f.free_symbols + if len(free) == 1: + x = free.pop() + elif not free: + return f + else: + raise NotImplementedError("multivariate formal power series") + + result = compute_fps(f, x, x0, dir, hyper, order, rational, full) + + if result is None: + return f + + return FormalPowerSeries(f, x, x0, dir, result) diff --git a/venv/lib/python3.10/site-packages/sympy/series/fourier.py b/venv/lib/python3.10/site-packages/sympy/series/fourier.py new file mode 100644 index 0000000000000000000000000000000000000000..c4ad43a75d0e1d24bfc591383f68965fd8b504c7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/fourier.py @@ -0,0 +1,811 @@ +"""Fourier Series""" + +from sympy.core.numbers import (oo, pi) +from sympy.core.symbol import Wild +from sympy.core.expr import Expr +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.singleton import S +from sympy.core.symbol import Dummy, Symbol +from sympy.core.sympify import sympify +from sympy.functions.elementary.trigonometric import sin, cos, sinc +from sympy.series.series_class import SeriesBase +from sympy.series.sequences import SeqFormula +from sympy.sets.sets import Interval +from sympy.utilities.iterables import is_sequence + + +__doctest_requires__ = {('fourier_series',): ['matplotlib']} + + +def fourier_cos_seq(func, limits, n): + """Returns the cos sequence in a Fourier series""" + from sympy.integrals import integrate + x, L = limits[0], limits[2] - limits[1] + cos_term = cos(2*n*pi*x / L) + formula = 2 * cos_term * integrate(func * cos_term, limits) / L + a0 = formula.subs(n, S.Zero) / 2 + return a0, SeqFormula(2 * cos_term * integrate(func * cos_term, limits) + / L, (n, 1, oo)) + + +def fourier_sin_seq(func, limits, n): + """Returns the sin sequence in a Fourier series""" + from sympy.integrals import integrate + x, L = limits[0], limits[2] - limits[1] + sin_term = sin(2*n*pi*x / L) + return SeqFormula(2 * sin_term * integrate(func * sin_term, limits) + / L, (n, 1, oo)) + + +def _process_limits(func, limits): + """ + Limits should be of the form (x, start, stop). + x should be a symbol. Both start and stop should be bounded. + + Explanation + =========== + + * If x is not given, x is determined from func. + * If limits is None. Limit of the form (x, -pi, pi) is returned. + + Examples + ======== + + >>> from sympy.series.fourier import _process_limits as pari + >>> from sympy.abc import x + >>> pari(x**2, (x, -2, 2)) + (x, -2, 2) + >>> pari(x**2, (-2, 2)) + (x, -2, 2) + >>> pari(x**2, None) + (x, -pi, pi) + """ + def _find_x(func): + free = func.free_symbols + if len(free) == 1: + return free.pop() + elif not free: + return Dummy('k') + else: + raise ValueError( + " specify dummy variables for %s. If the function contains" + " more than one free symbol, a dummy variable should be" + " supplied explicitly e.g. FourierSeries(m*n**2, (n, -pi, pi))" + % func) + + x, start, stop = None, None, None + if limits is None: + x, start, stop = _find_x(func), -pi, pi + if is_sequence(limits, Tuple): + if len(limits) == 3: + x, start, stop = limits + elif len(limits) == 2: + x = _find_x(func) + start, stop = limits + + if not isinstance(x, Symbol) or start is None or stop is None: + raise ValueError('Invalid limits given: %s' % str(limits)) + + unbounded = [S.NegativeInfinity, S.Infinity] + if start in unbounded or stop in unbounded: + raise ValueError("Both the start and end value should be bounded") + + return sympify((x, start, stop)) + + +def finite_check(f, x, L): + + def check_fx(exprs, x): + return x not in exprs.free_symbols + + def check_sincos(_expr, x, L): + if isinstance(_expr, (sin, cos)): + sincos_args = _expr.args[0] + + if sincos_args.match(a*(pi/L)*x + b) is not None: + return True + else: + return False + + from sympy.simplify.fu import TR2, TR1, sincos_to_sum + _expr = sincos_to_sum(TR2(TR1(f))) + add_coeff = _expr.as_coeff_add() + + a = Wild('a', properties=[lambda k: k.is_Integer, lambda k: k != S.Zero, ]) + b = Wild('b', properties=[lambda k: x not in k.free_symbols, ]) + + for s in add_coeff[1]: + mul_coeffs = s.as_coeff_mul()[1] + for t in mul_coeffs: + if not (check_fx(t, x) or check_sincos(t, x, L)): + return False, f + + return True, _expr + + +class FourierSeries(SeriesBase): + r"""Represents Fourier sine/cosine series. + + Explanation + =========== + + This class only represents a fourier series. + No computation is performed. + + For how to compute Fourier series, see the :func:`fourier_series` + docstring. + + See Also + ======== + + sympy.series.fourier.fourier_series + """ + def __new__(cls, *args): + args = map(sympify, args) + return Expr.__new__(cls, *args) + + @property + def function(self): + return self.args[0] + + @property + def x(self): + return self.args[1][0] + + @property + def period(self): + return (self.args[1][1], self.args[1][2]) + + @property + def a0(self): + return self.args[2][0] + + @property + def an(self): + return self.args[2][1] + + @property + def bn(self): + return self.args[2][2] + + @property + def interval(self): + return Interval(0, oo) + + @property + def start(self): + return self.interval.inf + + @property + def stop(self): + return self.interval.sup + + @property + def length(self): + return oo + + @property + def L(self): + return abs(self.period[1] - self.period[0]) / 2 + + def _eval_subs(self, old, new): + x = self.x + if old.has(x): + return self + + def truncate(self, n=3): + """ + Return the first n nonzero terms of the series. + + If ``n`` is None return an iterator. + + Parameters + ========== + + n : int or None + Amount of non-zero terms in approximation or None. + + Returns + ======= + + Expr or iterator : + Approximation of function expanded into Fourier series. + + Examples + ======== + + >>> from sympy import fourier_series, pi + >>> from sympy.abc import x + >>> s = fourier_series(x, (x, -pi, pi)) + >>> s.truncate(4) + 2*sin(x) - sin(2*x) + 2*sin(3*x)/3 - sin(4*x)/2 + + See Also + ======== + + sympy.series.fourier.FourierSeries.sigma_approximation + """ + if n is None: + return iter(self) + + terms = [] + for t in self: + if len(terms) == n: + break + if t is not S.Zero: + terms.append(t) + + return Add(*terms) + + def sigma_approximation(self, n=3): + r""" + Return :math:`\sigma`-approximation of Fourier series with respect + to order n. + + Explanation + =========== + + Sigma approximation adjusts a Fourier summation to eliminate the Gibbs + phenomenon which would otherwise occur at discontinuities. + A sigma-approximated summation for a Fourier series of a T-periodical + function can be written as + + .. math:: + s(\theta) = \frac{1}{2} a_0 + \sum _{k=1}^{m-1} + \operatorname{sinc} \Bigl( \frac{k}{m} \Bigr) \cdot + \left[ a_k \cos \Bigl( \frac{2\pi k}{T} \theta \Bigr) + + b_k \sin \Bigl( \frac{2\pi k}{T} \theta \Bigr) \right], + + where :math:`a_0, a_k, b_k, k=1,\ldots,{m-1}` are standard Fourier + series coefficients and + :math:`\operatorname{sinc} \Bigl( \frac{k}{m} \Bigr)` is a Lanczos + :math:`\sigma` factor (expressed in terms of normalized + :math:`\operatorname{sinc}` function). + + Parameters + ========== + + n : int + Highest order of the terms taken into account in approximation. + + Returns + ======= + + Expr : + Sigma approximation of function expanded into Fourier series. + + Examples + ======== + + >>> from sympy import fourier_series, pi + >>> from sympy.abc import x + >>> s = fourier_series(x, (x, -pi, pi)) + >>> s.sigma_approximation(4) + 2*sin(x)*sinc(pi/4) - 2*sin(2*x)/pi + 2*sin(3*x)*sinc(3*pi/4)/3 + + See Also + ======== + + sympy.series.fourier.FourierSeries.truncate + + Notes + ===== + + The behaviour of + :meth:`~sympy.series.fourier.FourierSeries.sigma_approximation` + is different from :meth:`~sympy.series.fourier.FourierSeries.truncate` + - it takes all nonzero terms of degree smaller than n, rather than + first n nonzero ones. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gibbs_phenomenon + .. [2] https://en.wikipedia.org/wiki/Sigma_approximation + """ + terms = [sinc(pi * i / n) * t for i, t in enumerate(self[:n]) + if t is not S.Zero] + return Add(*terms) + + def shift(self, s): + """ + Shift the function by a term independent of x. + + Explanation + =========== + + f(x) -> f(x) + s + + This is fast, if Fourier series of f(x) is already + computed. + + Examples + ======== + + >>> from sympy import fourier_series, pi + >>> from sympy.abc import x + >>> s = fourier_series(x**2, (x, -pi, pi)) + >>> s.shift(1).truncate() + -4*cos(x) + cos(2*x) + 1 + pi**2/3 + """ + s, x = sympify(s), self.x + + if x in s.free_symbols: + raise ValueError("'%s' should be independent of %s" % (s, x)) + + a0 = self.a0 + s + sfunc = self.function + s + + return self.func(sfunc, self.args[1], (a0, self.an, self.bn)) + + def shiftx(self, s): + """ + Shift x by a term independent of x. + + Explanation + =========== + + f(x) -> f(x + s) + + This is fast, if Fourier series of f(x) is already + computed. + + Examples + ======== + + >>> from sympy import fourier_series, pi + >>> from sympy.abc import x + >>> s = fourier_series(x**2, (x, -pi, pi)) + >>> s.shiftx(1).truncate() + -4*cos(x + 1) + cos(2*x + 2) + pi**2/3 + """ + s, x = sympify(s), self.x + + if x in s.free_symbols: + raise ValueError("'%s' should be independent of %s" % (s, x)) + + an = self.an.subs(x, x + s) + bn = self.bn.subs(x, x + s) + sfunc = self.function.subs(x, x + s) + + return self.func(sfunc, self.args[1], (self.a0, an, bn)) + + def scale(self, s): + """ + Scale the function by a term independent of x. + + Explanation + =========== + + f(x) -> s * f(x) + + This is fast, if Fourier series of f(x) is already + computed. + + Examples + ======== + + >>> from sympy import fourier_series, pi + >>> from sympy.abc import x + >>> s = fourier_series(x**2, (x, -pi, pi)) + >>> s.scale(2).truncate() + -8*cos(x) + 2*cos(2*x) + 2*pi**2/3 + """ + s, x = sympify(s), self.x + + if x in s.free_symbols: + raise ValueError("'%s' should be independent of %s" % (s, x)) + + an = self.an.coeff_mul(s) + bn = self.bn.coeff_mul(s) + a0 = self.a0 * s + sfunc = self.args[0] * s + + return self.func(sfunc, self.args[1], (a0, an, bn)) + + def scalex(self, s): + """ + Scale x by a term independent of x. + + Explanation + =========== + + f(x) -> f(s*x) + + This is fast, if Fourier series of f(x) is already + computed. + + Examples + ======== + + >>> from sympy import fourier_series, pi + >>> from sympy.abc import x + >>> s = fourier_series(x**2, (x, -pi, pi)) + >>> s.scalex(2).truncate() + -4*cos(2*x) + cos(4*x) + pi**2/3 + """ + s, x = sympify(s), self.x + + if x in s.free_symbols: + raise ValueError("'%s' should be independent of %s" % (s, x)) + + an = self.an.subs(x, x * s) + bn = self.bn.subs(x, x * s) + sfunc = self.function.subs(x, x * s) + + return self.func(sfunc, self.args[1], (self.a0, an, bn)) + + def _eval_as_leading_term(self, x, logx, cdir): + for t in self: + if t is not S.Zero: + return t + + def _eval_term(self, pt): + if pt == 0: + return self.a0 + return self.an.coeff(pt) + self.bn.coeff(pt) + + def __neg__(self): + return self.scale(-1) + + def __add__(self, other): + if isinstance(other, FourierSeries): + if self.period != other.period: + raise ValueError("Both the series should have same periods") + + x, y = self.x, other.x + function = self.function + other.function.subs(y, x) + + if self.x not in function.free_symbols: + return function + + an = self.an + other.an + bn = self.bn + other.bn + a0 = self.a0 + other.a0 + + return self.func(function, self.args[1], (a0, an, bn)) + + return Add(self, other) + + def __sub__(self, other): + return self.__add__(-other) + + +class FiniteFourierSeries(FourierSeries): + r"""Represents Finite Fourier sine/cosine series. + + For how to compute Fourier series, see the :func:`fourier_series` + docstring. + + Parameters + ========== + + f : Expr + Expression for finding fourier_series + + limits : ( x, start, stop) + x is the independent variable for the expression f + (start, stop) is the period of the fourier series + + exprs: (a0, an, bn) or Expr + a0 is the constant term a0 of the fourier series + an is a dictionary of coefficients of cos terms + an[k] = coefficient of cos(pi*(k/L)*x) + bn is a dictionary of coefficients of sin terms + bn[k] = coefficient of sin(pi*(k/L)*x) + + or exprs can be an expression to be converted to fourier form + + Methods + ======= + + This class is an extension of FourierSeries class. + Please refer to sympy.series.fourier.FourierSeries for + further information. + + See Also + ======== + + sympy.series.fourier.FourierSeries + sympy.series.fourier.fourier_series + """ + + def __new__(cls, f, limits, exprs): + f = sympify(f) + limits = sympify(limits) + exprs = sympify(exprs) + + if not (isinstance(exprs, Tuple) and len(exprs) == 3): # exprs is not of form (a0, an, bn) + # Converts the expression to fourier form + c, e = exprs.as_coeff_add() + from sympy.simplify.fu import TR10 + rexpr = c + Add(*[TR10(i) for i in e]) + a0, exp_ls = rexpr.expand(trig=False, power_base=False, power_exp=False, log=False).as_coeff_add() + + x = limits[0] + L = abs(limits[2] - limits[1]) / 2 + + a = Wild('a', properties=[lambda k: k.is_Integer, lambda k: k is not S.Zero, ]) + b = Wild('b', properties=[lambda k: x not in k.free_symbols, ]) + + an = {} + bn = {} + + # separates the coefficients of sin and cos terms in dictionaries an, and bn + for p in exp_ls: + t = p.match(b * cos(a * (pi / L) * x)) + q = p.match(b * sin(a * (pi / L) * x)) + if t: + an[t[a]] = t[b] + an.get(t[a], S.Zero) + elif q: + bn[q[a]] = q[b] + bn.get(q[a], S.Zero) + else: + a0 += p + + exprs = Tuple(a0, an, bn) + + return Expr.__new__(cls, f, limits, exprs) + + @property + def interval(self): + _length = 1 if self.a0 else 0 + _length += max(set(self.an.keys()).union(set(self.bn.keys()))) + 1 + return Interval(0, _length) + + @property + def length(self): + return self.stop - self.start + + def shiftx(self, s): + s, x = sympify(s), self.x + + if x in s.free_symbols: + raise ValueError("'%s' should be independent of %s" % (s, x)) + + _expr = self.truncate().subs(x, x + s) + sfunc = self.function.subs(x, x + s) + + return self.func(sfunc, self.args[1], _expr) + + def scale(self, s): + s, x = sympify(s), self.x + + if x in s.free_symbols: + raise ValueError("'%s' should be independent of %s" % (s, x)) + + _expr = self.truncate() * s + sfunc = self.function * s + + return self.func(sfunc, self.args[1], _expr) + + def scalex(self, s): + s, x = sympify(s), self.x + + if x in s.free_symbols: + raise ValueError("'%s' should be independent of %s" % (s, x)) + + _expr = self.truncate().subs(x, x * s) + sfunc = self.function.subs(x, x * s) + + return self.func(sfunc, self.args[1], _expr) + + def _eval_term(self, pt): + if pt == 0: + return self.a0 + + _term = self.an.get(pt, S.Zero) * cos(pt * (pi / self.L) * self.x) \ + + self.bn.get(pt, S.Zero) * sin(pt * (pi / self.L) * self.x) + return _term + + def __add__(self, other): + if isinstance(other, FourierSeries): + return other.__add__(fourier_series(self.function, self.args[1],\ + finite=False)) + elif isinstance(other, FiniteFourierSeries): + if self.period != other.period: + raise ValueError("Both the series should have same periods") + + x, y = self.x, other.x + function = self.function + other.function.subs(y, x) + + if self.x not in function.free_symbols: + return function + + return fourier_series(function, limits=self.args[1]) + + +def fourier_series(f, limits=None, finite=True): + r"""Computes the Fourier trigonometric series expansion. + + Explanation + =========== + + Fourier trigonometric series of $f(x)$ over the interval $(a, b)$ + is defined as: + + .. math:: + \frac{a_0}{2} + \sum_{n=1}^{\infty} + (a_n \cos(\frac{2n \pi x}{L}) + b_n \sin(\frac{2n \pi x}{L})) + + where the coefficients are: + + .. math:: + L = b - a + + .. math:: + a_0 = \frac{2}{L} \int_{a}^{b}{f(x) dx} + + .. math:: + a_n = \frac{2}{L} \int_{a}^{b}{f(x) \cos(\frac{2n \pi x}{L}) dx} + + .. math:: + b_n = \frac{2}{L} \int_{a}^{b}{f(x) \sin(\frac{2n \pi x}{L}) dx} + + The condition whether the function $f(x)$ given should be periodic + or not is more than necessary, because it is sufficient to consider + the series to be converging to $f(x)$ only in the given interval, + not throughout the whole real line. + + This also brings a lot of ease for the computation because + you do not have to make $f(x)$ artificially periodic by + wrapping it with piecewise, modulo operations, + but you can shape the function to look like the desired periodic + function only in the interval $(a, b)$, and the computed series will + automatically become the series of the periodic version of $f(x)$. + + This property is illustrated in the examples section below. + + Parameters + ========== + + limits : (sym, start, end), optional + *sym* denotes the symbol the series is computed with respect to. + + *start* and *end* denotes the start and the end of the interval + where the fourier series converges to the given function. + + Default range is specified as $-\pi$ and $\pi$. + + Returns + ======= + + FourierSeries + A symbolic object representing the Fourier trigonometric series. + + Examples + ======== + + Computing the Fourier series of $f(x) = x^2$: + + >>> from sympy import fourier_series, pi + >>> from sympy.abc import x + >>> f = x**2 + >>> s = fourier_series(f, (x, -pi, pi)) + >>> s1 = s.truncate(n=3) + >>> s1 + -4*cos(x) + cos(2*x) + pi**2/3 + + Shifting of the Fourier series: + + >>> s.shift(1).truncate() + -4*cos(x) + cos(2*x) + 1 + pi**2/3 + >>> s.shiftx(1).truncate() + -4*cos(x + 1) + cos(2*x + 2) + pi**2/3 + + Scaling of the Fourier series: + + >>> s.scale(2).truncate() + -8*cos(x) + 2*cos(2*x) + 2*pi**2/3 + >>> s.scalex(2).truncate() + -4*cos(2*x) + cos(4*x) + pi**2/3 + + Computing the Fourier series of $f(x) = x$: + + This illustrates how truncating to the higher order gives better + convergence. + + .. plot:: + :context: reset + :format: doctest + :include-source: True + + >>> from sympy import fourier_series, pi, plot + >>> from sympy.abc import x + >>> f = x + >>> s = fourier_series(f, (x, -pi, pi)) + >>> s1 = s.truncate(n = 3) + >>> s2 = s.truncate(n = 5) + >>> s3 = s.truncate(n = 7) + >>> p = plot(f, s1, s2, s3, (x, -pi, pi), show=False, legend=True) + + >>> p[0].line_color = (0, 0, 0) + >>> p[0].label = 'x' + >>> p[1].line_color = (0.7, 0.7, 0.7) + >>> p[1].label = 'n=3' + >>> p[2].line_color = (0.5, 0.5, 0.5) + >>> p[2].label = 'n=5' + >>> p[3].line_color = (0.3, 0.3, 0.3) + >>> p[3].label = 'n=7' + + >>> p.show() + + This illustrates how the series converges to different sawtooth + waves if the different ranges are specified. + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> s1 = fourier_series(x, (x, -1, 1)).truncate(10) + >>> s2 = fourier_series(x, (x, -pi, pi)).truncate(10) + >>> s3 = fourier_series(x, (x, 0, 1)).truncate(10) + >>> p = plot(x, s1, s2, s3, (x, -5, 5), show=False, legend=True) + + >>> p[0].line_color = (0, 0, 0) + >>> p[0].label = 'x' + >>> p[1].line_color = (0.7, 0.7, 0.7) + >>> p[1].label = '[-1, 1]' + >>> p[2].line_color = (0.5, 0.5, 0.5) + >>> p[2].label = '[-pi, pi]' + >>> p[3].line_color = (0.3, 0.3, 0.3) + >>> p[3].label = '[0, 1]' + + >>> p.show() + + Notes + ===== + + Computing Fourier series can be slow + due to the integration required in computing + an, bn. + + It is faster to compute Fourier series of a function + by using shifting and scaling on an already + computed Fourier series rather than computing + again. + + e.g. If the Fourier series of ``x**2`` is known + the Fourier series of ``x**2 - 1`` can be found by shifting by ``-1``. + + See Also + ======== + + sympy.series.fourier.FourierSeries + + References + ========== + + .. [1] https://mathworld.wolfram.com/FourierSeries.html + """ + f = sympify(f) + + limits = _process_limits(f, limits) + x = limits[0] + + if x not in f.free_symbols: + return f + + if finite: + L = abs(limits[2] - limits[1]) / 2 + is_finite, res_f = finite_check(f, x, L) + if is_finite: + return FiniteFourierSeries(f, limits, res_f) + + n = Dummy('n') + center = (limits[1] + limits[2]) / 2 + if center.is_zero: + neg_f = f.subs(x, -x) + if f == neg_f: + a0, an = fourier_cos_seq(f, limits, n) + bn = SeqFormula(0, (1, oo)) + return FourierSeries(f, limits, (a0, an, bn)) + elif f == -neg_f: + a0 = S.Zero + an = SeqFormula(0, (1, oo)) + bn = fourier_sin_seq(f, limits, n) + return FourierSeries(f, limits, (a0, an, bn)) + a0, an = fourier_cos_seq(f, limits, n) + bn = fourier_sin_seq(f, limits, n) + return FourierSeries(f, limits, (a0, an, bn)) diff --git a/venv/lib/python3.10/site-packages/sympy/series/gruntz.py b/venv/lib/python3.10/site-packages/sympy/series/gruntz.py new file mode 100644 index 0000000000000000000000000000000000000000..20ba3150e9918384141e755a39f5bacb0e76db55 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/gruntz.py @@ -0,0 +1,701 @@ +""" +Limits +====== + +Implemented according to the PhD thesis +https://www.cybertester.com/data/gruntz.pdf, which contains very thorough +descriptions of the algorithm including many examples. We summarize here +the gist of it. + +All functions are sorted according to how rapidly varying they are at +infinity using the following rules. Any two functions f and g can be +compared using the properties of L: + +L=lim log|f(x)| / log|g(x)| (for x -> oo) + +We define >, < ~ according to:: + + 1. f > g .... L=+-oo + + we say that: + - f is greater than any power of g + - f is more rapidly varying than g + - f goes to infinity/zero faster than g + + 2. f < g .... L=0 + + we say that: + - f is lower than any power of g + + 3. f ~ g .... L!=0, +-oo + + we say that: + - both f and g are bounded from above and below by suitable integral + powers of the other + +Examples +======== +:: + 2 < x < exp(x) < exp(x**2) < exp(exp(x)) + 2 ~ 3 ~ -5 + x ~ x**2 ~ x**3 ~ 1/x ~ x**m ~ -x + exp(x) ~ exp(-x) ~ exp(2x) ~ exp(x)**2 ~ exp(x+exp(-x)) + f ~ 1/f + +So we can divide all the functions into comparability classes (x and x^2 +belong to one class, exp(x) and exp(-x) belong to some other class). In +principle, we could compare any two functions, but in our algorithm, we +do not compare anything below the class 2~3~-5 (for example log(x) is +below this), so we set 2~3~-5 as the lowest comparability class. + +Given the function f, we find the list of most rapidly varying (mrv set) +subexpressions of it. This list belongs to the same comparability class. +Let's say it is {exp(x), exp(2x)}. Using the rule f ~ 1/f we find an +element "w" (either from the list or a new one) from the same +comparability class which goes to zero at infinity. In our example we +set w=exp(-x) (but we could also set w=exp(-2x) or w=exp(-3x) ...). We +rewrite the mrv set using w, in our case {1/w, 1/w^2}, and substitute it +into f. Then we expand f into a series in w:: + + f = c0*w^e0 + c1*w^e1 + ... + O(w^en), where e0oo, lim f = lim c0*w^e0, because all the other terms go to zero, +because w goes to zero faster than the ci and ei. So:: + + for e0>0, lim f = 0 + for e0<0, lim f = +-oo (the sign depends on the sign of c0) + for e0=0, lim f = lim c0 + +We need to recursively compute limits at several places of the algorithm, but +as is shown in the PhD thesis, it always finishes. + +Important functions from the implementation: + +compare(a, b, x) compares "a" and "b" by computing the limit L. +mrv(e, x) returns list of most rapidly varying (mrv) subexpressions of "e" +rewrite(e, Omega, x, wsym) rewrites "e" in terms of w +leadterm(f, x) returns the lowest power term in the series of f +mrv_leadterm(e, x) returns the lead term (c0, e0) for e +limitinf(e, x) computes lim e (for x->oo) +limit(e, z, z0) computes any limit by converting it to the case x->oo + +All the functions are really simple and straightforward except +rewrite(), which is the most difficult/complex part of the algorithm. +When the algorithm fails, the bugs are usually in the series expansion +(i.e. in SymPy) or in rewrite. + +This code is almost exact rewrite of the Maple code inside the Gruntz +thesis. + +Debugging +--------- + +Because the gruntz algorithm is highly recursive, it's difficult to +figure out what went wrong inside a debugger. Instead, turn on nice +debug prints by defining the environment variable SYMPY_DEBUG. For +example: + +[user@localhost]: SYMPY_DEBUG=True ./bin/isympy + +In [1]: limit(sin(x)/x, x, 0) +limitinf(_x*sin(1/_x), _x) = 1 ++-mrv_leadterm(_x*sin(1/_x), _x) = (1, 0) +| +-mrv(_x*sin(1/_x), _x) = set([_x]) +| | +-mrv(_x, _x) = set([_x]) +| | +-mrv(sin(1/_x), _x) = set([_x]) +| | +-mrv(1/_x, _x) = set([_x]) +| | +-mrv(_x, _x) = set([_x]) +| +-mrv_leadterm(exp(_x)*sin(exp(-_x)), _x, set([exp(_x)])) = (1, 0) +| +-rewrite(exp(_x)*sin(exp(-_x)), set([exp(_x)]), _x, _w) = (1/_w*sin(_w), -_x) +| +-sign(_x, _x) = 1 +| +-mrv_leadterm(1, _x) = (1, 0) ++-sign(0, _x) = 0 ++-limitinf(1, _x) = 1 + +And check manually which line is wrong. Then go to the source code and +debug this function to figure out the exact problem. + +""" +from functools import reduce + +from sympy.core import Basic, S, Mul, PoleError +from sympy.core.cache import cacheit +from sympy.core.function import AppliedUndef +from sympy.core.intfunc import ilcm +from sympy.core.numbers import I, oo +from sympy.core.symbol import Dummy, Wild +from sympy.core.traversal import bottom_up + +from sympy.functions import log, exp, sign as _sign +from sympy.series.order import Order +from sympy.utilities.misc import debug_decorator as debug +from sympy.utilities.timeutils import timethis + +timeit = timethis('gruntz') + + +def compare(a, b, x): + """Returns "<" if a" for a>b""" + # log(exp(...)) must always be simplified here for termination + la, lb = log(a), log(b) + if isinstance(a, Basic) and (isinstance(a, exp) or (a.is_Pow and a.base == S.Exp1)): + la = a.exp + if isinstance(b, Basic) and (isinstance(b, exp) or (b.is_Pow and b.base == S.Exp1)): + lb = b.exp + + c = limitinf(la/lb, x) + if c == 0: + return "<" + elif c.is_infinite: + return ">" + else: + return "=" + + +class SubsSet(dict): + """ + Stores (expr, dummy) pairs, and how to rewrite expr-s. + + Explanation + =========== + + The gruntz algorithm needs to rewrite certain expressions in term of a new + variable w. We cannot use subs, because it is just too smart for us. For + example:: + + > Omega=[exp(exp(_p - exp(-_p))/(1 - 1/_p)), exp(exp(_p))] + > O2=[exp(-exp(_p) + exp(-exp(-_p))*exp(_p)/(1 - 1/_p))/_w, 1/_w] + > e = exp(exp(_p - exp(-_p))/(1 - 1/_p)) - exp(exp(_p)) + > e.subs(Omega[0],O2[0]).subs(Omega[1],O2[1]) + -1/w + exp(exp(p)*exp(-exp(-p))/(1 - 1/p)) + + is really not what we want! + + So we do it the hard way and keep track of all the things we potentially + want to substitute by dummy variables. Consider the expression:: + + exp(x - exp(-x)) + exp(x) + x. + + The mrv set is {exp(x), exp(-x), exp(x - exp(-x))}. + We introduce corresponding dummy variables d1, d2, d3 and rewrite:: + + d3 + d1 + x. + + This class first of all keeps track of the mapping expr->variable, i.e. + will at this stage be a dictionary:: + + {exp(x): d1, exp(-x): d2, exp(x - exp(-x)): d3}. + + [It turns out to be more convenient this way round.] + But sometimes expressions in the mrv set have other expressions from the + mrv set as subexpressions, and we need to keep track of that as well. In + this case, d3 is really exp(x - d2), so rewrites at this stage is:: + + {d3: exp(x-d2)}. + + The function rewrite uses all this information to correctly rewrite our + expression in terms of w. In this case w can be chosen to be exp(-x), + i.e. d2. The correct rewriting then is:: + + exp(-w)/w + 1/w + x. + """ + def __init__(self): + self.rewrites = {} + + def __repr__(self): + return super().__repr__() + ', ' + self.rewrites.__repr__() + + def __getitem__(self, key): + if key not in self: + self[key] = Dummy() + return dict.__getitem__(self, key) + + def do_subs(self, e): + """Substitute the variables with expressions""" + for expr, var in self.items(): + e = e.xreplace({var: expr}) + return e + + def meets(self, s2): + """Tell whether or not self and s2 have non-empty intersection""" + return set(self.keys()).intersection(list(s2.keys())) != set() + + def union(self, s2, exps=None): + """Compute the union of self and s2, adjusting exps""" + res = self.copy() + tr = {} + for expr, var in s2.items(): + if expr in self: + if exps: + exps = exps.xreplace({var: res[expr]}) + tr[var] = res[expr] + else: + res[expr] = var + for var, rewr in s2.rewrites.items(): + res.rewrites[var] = rewr.xreplace(tr) + return res, exps + + def copy(self): + """Create a shallow copy of SubsSet""" + r = SubsSet() + r.rewrites = self.rewrites.copy() + for expr, var in self.items(): + r[expr] = var + return r + + +@debug +def mrv(e, x): + """Returns a SubsSet of most rapidly varying (mrv) subexpressions of 'e', + and e rewritten in terms of these""" + from sympy.simplify.powsimp import powsimp + e = powsimp(e, deep=True, combine='exp') + if not isinstance(e, Basic): + raise TypeError("e should be an instance of Basic") + if not e.has(x): + return SubsSet(), e + elif e == x: + s = SubsSet() + return s, s[x] + elif e.is_Mul or e.is_Add: + i, d = e.as_independent(x) # throw away x-independent terms + if d.func != e.func: + s, expr = mrv(d, x) + return s, e.func(i, expr) + a, b = d.as_two_terms() + s1, e1 = mrv(a, x) + s2, e2 = mrv(b, x) + return mrv_max1(s1, s2, e.func(i, e1, e2), x) + elif e.is_Pow and e.base != S.Exp1: + e1 = S.One + while e.is_Pow: + b1 = e.base + e1 *= e.exp + e = b1 + if b1 == 1: + return SubsSet(), b1 + if e1.has(x): + return mrv(exp(e1*log(b1)), x) + else: + s, expr = mrv(b1, x) + return s, expr**e1 + elif isinstance(e, log): + s, expr = mrv(e.args[0], x) + return s, log(expr) + elif isinstance(e, exp) or (e.is_Pow and e.base == S.Exp1): + # We know from the theory of this algorithm that exp(log(...)) may always + # be simplified here, and doing so is vital for termination. + if isinstance(e.exp, log): + return mrv(e.exp.args[0], x) + # if a product has an infinite factor the result will be + # infinite if there is no zero, otherwise NaN; here, we + # consider the result infinite if any factor is infinite + li = limitinf(e.exp, x) + if any(_.is_infinite for _ in Mul.make_args(li)): + s1 = SubsSet() + e1 = s1[e] + s2, e2 = mrv(e.exp, x) + su = s1.union(s2)[0] + su.rewrites[e1] = exp(e2) + return mrv_max3(s1, e1, s2, exp(e2), su, e1, x) + else: + s, expr = mrv(e.exp, x) + return s, exp(expr) + elif isinstance(e, AppliedUndef): + raise ValueError("MRV set computation for UndefinedFunction is not allowed") + elif e.is_Function: + l = [mrv(a, x) for a in e.args] + l2 = [s for (s, _) in l if s != SubsSet()] + if len(l2) != 1: + # e.g. something like BesselJ(x, x) + raise NotImplementedError("MRV set computation for functions in" + " several variables not implemented.") + s, ss = l2[0], SubsSet() + args = [ss.do_subs(x[1]) for x in l] + return s, e.func(*args) + elif e.is_Derivative: + raise NotImplementedError("MRV set computation for derivatives" + " not implemented yet.") + raise NotImplementedError( + "Don't know how to calculate the mrv of '%s'" % e) + + +def mrv_max3(f, expsf, g, expsg, union, expsboth, x): + """ + Computes the maximum of two sets of expressions f and g, which + are in the same comparability class, i.e. max() compares (two elements of) + f and g and returns either (f, expsf) [if f is larger], (g, expsg) + [if g is larger] or (union, expsboth) [if f, g are of the same class]. + """ + if not isinstance(f, SubsSet): + raise TypeError("f should be an instance of SubsSet") + if not isinstance(g, SubsSet): + raise TypeError("g should be an instance of SubsSet") + if f == SubsSet(): + return g, expsg + elif g == SubsSet(): + return f, expsf + elif f.meets(g): + return union, expsboth + + c = compare(list(f.keys())[0], list(g.keys())[0], x) + if c == ">": + return f, expsf + elif c == "<": + return g, expsg + else: + if c != "=": + raise ValueError("c should be =") + return union, expsboth + + +def mrv_max1(f, g, exps, x): + """Computes the maximum of two sets of expressions f and g, which + are in the same comparability class, i.e. mrv_max1() compares (two elements of) + f and g and returns the set, which is in the higher comparability class + of the union of both, if they have the same order of variation. + Also returns exps, with the appropriate substitutions made. + """ + u, b = f.union(g, exps) + return mrv_max3(f, g.do_subs(exps), g, f.do_subs(exps), + u, b, x) + + +@debug +@cacheit +@timeit +def sign(e, x): + """ + Returns a sign of an expression e(x) for x->oo. + + :: + + e > 0 for x sufficiently large ... 1 + e == 0 for x sufficiently large ... 0 + e < 0 for x sufficiently large ... -1 + + The result of this function is currently undefined if e changes sign + arbitrarily often for arbitrarily large x (e.g. sin(x)). + + Note that this returns zero only if e is *constantly* zero + for x sufficiently large. [If e is constant, of course, this is just + the same thing as the sign of e.] + """ + if not isinstance(e, Basic): + raise TypeError("e should be an instance of Basic") + + if e.is_positive: + return 1 + elif e.is_negative: + return -1 + elif e.is_zero: + return 0 + + elif not e.has(x): + from sympy.simplify import logcombine + e = logcombine(e) + return _sign(e) + elif e == x: + return 1 + elif e.is_Mul: + a, b = e.as_two_terms() + sa = sign(a, x) + if not sa: + return 0 + return sa * sign(b, x) + elif isinstance(e, exp): + return 1 + elif e.is_Pow: + if e.base == S.Exp1: + return 1 + s = sign(e.base, x) + if s == 1: + return 1 + if e.exp.is_Integer: + return s**e.exp + elif isinstance(e, log) and e.args[0].is_positive: + return sign(e.args[0] - 1, x) + + # if all else fails, do it the hard way + c0, e0 = mrv_leadterm(e, x) + return sign(c0, x) + + +@debug +@timeit +@cacheit +def limitinf(e, x): + """Limit e(x) for x-> oo.""" + # rewrite e in terms of tractable functions only + + old = e + if not e.has(x): + return e # e is a constant + from sympy.simplify.powsimp import powdenest + from sympy.calculus.util import AccumBounds + if e.has(Order): + e = e.expand().removeO() + if not x.is_positive or x.is_integer: + # We make sure that x.is_positive is True and x.is_integer is None + # so we get all the correct mathematical behavior from the expression. + # We need a fresh variable. + p = Dummy('p', positive=True) + e = e.subs(x, p) + x = p + e = e.rewrite('tractable', deep=True, limitvar=x) + e = powdenest(e) + if isinstance(e, AccumBounds): + if mrv_leadterm(e.min, x) != mrv_leadterm(e.max, x): + raise NotImplementedError + c0, e0 = mrv_leadterm(e.min, x) + else: + c0, e0 = mrv_leadterm(e, x) + sig = sign(e0, x) + if sig == 1: + return S.Zero # e0>0: lim f = 0 + elif sig == -1: # e0<0: lim f = +-oo (the sign depends on the sign of c0) + if c0.match(I*Wild("a", exclude=[I])): + return c0*oo + s = sign(c0, x) + # the leading term shouldn't be 0: + if s == 0: + raise ValueError("Leading term should not be 0") + return s*oo + elif sig == 0: + if c0 == old: + c0 = c0.cancel() + return limitinf(c0, x) # e0=0: lim f = lim c0 + else: + raise ValueError("{} could not be evaluated".format(sig)) + + +def moveup2(s, x): + r = SubsSet() + for expr, var in s.items(): + r[expr.xreplace({x: exp(x)})] = var + for var, expr in s.rewrites.items(): + r.rewrites[var] = s.rewrites[var].xreplace({x: exp(x)}) + return r + + +def moveup(l, x): + return [e.xreplace({x: exp(x)}) for e in l] + + +@debug +@timeit +@cacheit +def mrv_leadterm(e, x): + """Returns (c0, e0) for e.""" + Omega = SubsSet() + if not e.has(x): + return (e, S.Zero) + if Omega == SubsSet(): + Omega, exps = mrv(e, x) + if not Omega: + # e really does not depend on x after simplification + return exps, S.Zero + if x in Omega: + # move the whole omega up (exponentiate each term): + Omega_up = moveup2(Omega, x) + exps_up = moveup([exps], x)[0] + # NOTE: there is no need to move this down! + Omega = Omega_up + exps = exps_up + # + # The positive dummy, w, is used here so log(w*2) etc. will expand; + # a unique dummy is needed in this algorithm + # + # For limits of complex functions, the algorithm would have to be + # improved, or just find limits of Re and Im components separately. + # + w = Dummy("w", positive=True) + f, logw = rewrite(exps, Omega, x, w) + + # Ensure expressions of the form exp(log(...)) don't get simplified automatically in the previous steps. + # see: https://github.com/sympy/sympy/issues/15323#issuecomment-478639399 + f = f.replace(lambda f: f.is_Pow and f.has(x), lambda f: exp(log(f.base)*f.exp)) + + try: + lt = f.leadterm(w, logx=logw) + except (NotImplementedError, PoleError, ValueError): + n0 = 1 + _series = Order(1) + incr = S.One + while _series.is_Order: + _series = f._eval_nseries(w, n=n0+incr, logx=logw) + incr *= 2 + series = _series.expand().removeO() + try: + lt = series.leadterm(w, logx=logw) + except (NotImplementedError, PoleError, ValueError): + lt = f.as_coeff_exponent(w) + if lt[0].has(w): + base = f.as_base_exp()[0].as_coeff_exponent(w) + ex = f.as_base_exp()[1] + lt = (base[0]**ex, base[1]*ex) + return (lt[0].subs(log(w), logw), lt[1]) + + +def build_expression_tree(Omega, rewrites): + r""" Helper function for rewrite. + + We need to sort Omega (mrv set) so that we replace an expression before + we replace any expression in terms of which it has to be rewritten:: + + e1 ---> e2 ---> e3 + \ + -> e4 + + Here we can do e1, e2, e3, e4 or e1, e2, e4, e3. + To do this we assemble the nodes into a tree, and sort them by height. + + This function builds the tree, rewrites then sorts the nodes. + """ + class Node: + def __init__(self): + self.before = [] + self.expr = None + self.var = None + def ht(self): + return reduce(lambda x, y: x + y, + [x.ht() for x in self.before], 1) + nodes = {} + for expr, v in Omega: + n = Node() + n.var = v + n.expr = expr + nodes[v] = n + for _, v in Omega: + if v in rewrites: + n = nodes[v] + r = rewrites[v] + for _, v2 in Omega: + if r.has(v2): + n.before.append(nodes[v2]) + + return nodes + + +@debug +@timeit +def rewrite(e, Omega, x, wsym): + """e(x) ... the function + Omega ... the mrv set + wsym ... the symbol which is going to be used for w + + Returns the rewritten e in terms of w and log(w). See test_rewrite1() + for examples and correct results. + """ + + from sympy import AccumBounds + if not isinstance(Omega, SubsSet): + raise TypeError("Omega should be an instance of SubsSet") + if len(Omega) == 0: + raise ValueError("Length cannot be 0") + # all items in Omega must be exponentials + for t in Omega.keys(): + if not isinstance(t, exp): + raise ValueError("Value should be exp") + rewrites = Omega.rewrites + Omega = list(Omega.items()) + + nodes = build_expression_tree(Omega, rewrites) + Omega.sort(key=lambda x: nodes[x[1]].ht(), reverse=True) + + # make sure we know the sign of each exp() term; after the loop, + # g is going to be the "w" - the simplest one in the mrv set + for g, _ in Omega: + sig = sign(g.exp, x) + if sig != 1 and sig != -1 and not sig.has(AccumBounds): + raise NotImplementedError('Result depends on the sign of %s' % sig) + if sig == 1: + wsym = 1/wsym # if g goes to oo, substitute 1/w + # O2 is a list, which results by rewriting each item in Omega using "w" + O2 = [] + denominators = [] + for f, var in Omega: + c = limitinf(f.exp/g.exp, x) + if c.is_Rational: + denominators.append(c.q) + arg = f.exp + if var in rewrites: + if not isinstance(rewrites[var], exp): + raise ValueError("Value should be exp") + arg = rewrites[var].args[0] + O2.append((var, exp((arg - c*g.exp))*wsym**c)) + + # Remember that Omega contains subexpressions of "e". So now we find + # them in "e" and substitute them for our rewriting, stored in O2 + + # the following powsimp is necessary to automatically combine exponentials, + # so that the .xreplace() below succeeds: + # TODO this should not be necessary + from sympy.simplify.powsimp import powsimp + f = powsimp(e, deep=True, combine='exp') + for a, b in O2: + f = f.xreplace({a: b}) + + for _, var in Omega: + assert not f.has(var) + + # finally compute the logarithm of w (logw). + logw = g.exp + if sig == 1: + logw = -logw # log(w)->log(1/w)=-log(w) + + # Some parts of SymPy have difficulty computing series expansions with + # non-integral exponents. The following heuristic improves the situation: + exponent = reduce(ilcm, denominators, 1) + f = f.subs({wsym: wsym**exponent}) + logw /= exponent + + # bottom_up function is required for a specific case - when f is + # -exp(p/(p + 1)) + exp(-p**2/(p + 1) + p). No current simplification + # methods reduce this to 0 while not expanding polynomials. + f = bottom_up(f, lambda w: getattr(w, 'normal', lambda: w)()) + + return f, logw + + +def gruntz(e, z, z0, dir="+"): + """ + Compute the limit of e(z) at the point z0 using the Gruntz algorithm. + + Explanation + =========== + + ``z0`` can be any expression, including oo and -oo. + + For ``dir="+"`` (default) it calculates the limit from the right + (z->z0+) and for ``dir="-"`` the limit from the left (z->z0-). For infinite z0 + (oo or -oo), the dir argument does not matter. + + This algorithm is fully described in the module docstring in the gruntz.py + file. It relies heavily on the series expansion. Most frequently, gruntz() + is only used if the faster limit() function (which uses heuristics) fails. + """ + if not z.is_symbol: + raise NotImplementedError("Second argument must be a Symbol") + + # convert all limits to the limit z->oo; sign of z is handled in limitinf + r = None + if z0 in (oo, I*oo): + e0 = e + elif z0 in (-oo, -I*oo): + e0 = e.subs(z, -z) + else: + if str(dir) == "-": + e0 = e.subs(z, z0 - 1/z) + elif str(dir) == "+": + e0 = e.subs(z, z0 + 1/z) + else: + raise NotImplementedError("dir must be '+' or '-'") + + r = limitinf(e0, z) + + # This is a bit of a heuristic for nice results... we always rewrite + # tractable functions in terms of familiar intractable ones. + # It might be nicer to rewrite the exactly to what they were initially, + # but that would take some work to implement. + return r.rewrite('intractable', deep=True) diff --git a/venv/lib/python3.10/site-packages/sympy/series/kauers.py b/venv/lib/python3.10/site-packages/sympy/series/kauers.py new file mode 100644 index 0000000000000000000000000000000000000000..9e9645ff15ee5ae3c1d1c8709f76aed1b366f50a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/kauers.py @@ -0,0 +1,51 @@ +def finite_diff(expression, variable, increment=1): + """ + Takes as input a polynomial expression and the variable used to construct + it and returns the difference between function's value when the input is + incremented to 1 and the original function value. If you want an increment + other than one supply it as a third argument. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.series.kauers import finite_diff + >>> finite_diff(x**2, x) + 2*x + 1 + >>> finite_diff(y**3 + 2*y**2 + 3*y + 4, y) + 3*y**2 + 7*y + 6 + >>> finite_diff(x**2 + 3*x + 8, x, 2) + 4*x + 10 + >>> finite_diff(z**3 + 8*z, z, 3) + 9*z**2 + 27*z + 51 + """ + expression = expression.expand() + expression2 = expression.subs(variable, variable + increment) + expression2 = expression2.expand() + return expression2 - expression + +def finite_diff_kauers(sum): + """ + Takes as input a Sum instance and returns the difference between the sum + with the upper index incremented by 1 and the original sum. For example, + if S(n) is a sum, then finite_diff_kauers will return S(n + 1) - S(n). + + Examples + ======== + + >>> from sympy.series.kauers import finite_diff_kauers + >>> from sympy import Sum + >>> from sympy.abc import x, y, m, n, k + >>> finite_diff_kauers(Sum(k, (k, 1, n))) + n + 1 + >>> finite_diff_kauers(Sum(1/k, (k, 1, n))) + 1/(n + 1) + >>> finite_diff_kauers(Sum((x*y**2), (x, 1, n), (y, 1, m))) + (m + 1)**2*(n + 1) + >>> finite_diff_kauers(Sum((x*y), (x, 1, m), (y, 1, n))) + (m + 1)*(n + 1) + """ + function = sum.function + for l in sum.limits: + function = function.subs(l[0], l[- 1] + 1) + return function diff --git a/venv/lib/python3.10/site-packages/sympy/series/limits.py b/venv/lib/python3.10/site-packages/sympy/series/limits.py new file mode 100644 index 0000000000000000000000000000000000000000..e15f7a1243452075a76553903cabf60e43942d8c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/limits.py @@ -0,0 +1,394 @@ +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.core import S, Symbol, Add, sympify, Expr, PoleError, Mul +from sympy.core.exprtools import factor_terms +from sympy.core.numbers import Float, _illegal +from sympy.core.function import AppliedUndef +from sympy.core.symbol import Dummy +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (Abs, sign, arg, re) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.special.gamma_functions import gamma +from sympy.polys import PolynomialError, factor +from sympy.series.order import Order +from .gruntz import gruntz + +def limit(e, z, z0, dir="+"): + """Computes the limit of ``e(z)`` at the point ``z0``. + + Parameters + ========== + + e : expression, the limit of which is to be taken + + z : symbol representing the variable in the limit. + Other symbols are treated as constants. Multivariate limits + are not supported. + + z0 : the value toward which ``z`` tends. Can be any expression, + including ``oo`` and ``-oo``. + + dir : string, optional (default: "+") + The limit is bi-directional if ``dir="+-"``, from the right + (z->z0+) if ``dir="+"``, and from the left (z->z0-) if + ``dir="-"``. For infinite ``z0`` (``oo`` or ``-oo``), the ``dir`` + argument is determined from the direction of the infinity + (i.e., ``dir="-"`` for ``oo``). + + Examples + ======== + + >>> from sympy import limit, sin, oo + >>> from sympy.abc import x + >>> limit(sin(x)/x, x, 0) + 1 + >>> limit(1/x, x, 0) # default dir='+' + oo + >>> limit(1/x, x, 0, dir="-") + -oo + >>> limit(1/x, x, 0, dir='+-') + zoo + >>> limit(1/x, x, oo) + 0 + + Notes + ===== + + First we try some heuristics for easy and frequent cases like "x", "1/x", + "x**2" and similar, so that it's fast. For all other cases, we use the + Gruntz algorithm (see the gruntz() function). + + See Also + ======== + + limit_seq : returns the limit of a sequence. + """ + + return Limit(e, z, z0, dir).doit(deep=False) + + +def heuristics(e, z, z0, dir): + """Computes the limit of an expression term-wise. + Parameters are the same as for the ``limit`` function. + Works with the arguments of expression ``e`` one by one, computing + the limit of each and then combining the results. This approach + works only for simple limits, but it is fast. + """ + + rv = None + if z0 is S.Infinity: + rv = limit(e.subs(z, 1/z), z, S.Zero, "+") + if isinstance(rv, Limit): + return + elif (e.is_Mul or e.is_Add or e.is_Pow or (e.is_Function and not isinstance(e, AppliedUndef))): + r = [] + from sympy.simplify.simplify import together + for a in e.args: + l = limit(a, z, z0, dir) + if l.has(S.Infinity) and l.is_finite is None: + if isinstance(e, Add): + m = factor_terms(e) + if not isinstance(m, Mul): # try together + m = together(m) + if not isinstance(m, Mul): # try factor if the previous methods failed + m = factor(e) + if isinstance(m, Mul): + return heuristics(m, z, z0, dir) + return + return + elif isinstance(l, Limit): + return + elif l is S.NaN: + return + else: + r.append(l) + if r: + rv = e.func(*r) + if rv is S.NaN and e.is_Mul and any(isinstance(rr, AccumBounds) for rr in r): + r2 = [] + e2 = [] + for ii, rval in enumerate(r): + if isinstance(rval, AccumBounds): + r2.append(rval) + else: + e2.append(e.args[ii]) + + if len(e2) > 0: + e3 = Mul(*e2).simplify() + l = limit(e3, z, z0, dir) + rv = l * Mul(*r2) + + if rv is S.NaN: + try: + from sympy.simplify.ratsimp import ratsimp + rat_e = ratsimp(e) + except PolynomialError: + return + if rat_e is S.NaN or rat_e == e: + return + return limit(rat_e, z, z0, dir) + return rv + + +class Limit(Expr): + """Represents an unevaluated limit. + + Examples + ======== + + >>> from sympy import Limit, sin + >>> from sympy.abc import x + >>> Limit(sin(x)/x, x, 0) + Limit(sin(x)/x, x, 0, dir='+') + >>> Limit(1/x, x, 0, dir="-") + Limit(1/x, x, 0, dir='-') + + """ + + def __new__(cls, e, z, z0, dir="+"): + e = sympify(e) + z = sympify(z) + z0 = sympify(z0) + + if z0 in (S.Infinity, S.ImaginaryUnit*S.Infinity): + dir = "-" + elif z0 in (S.NegativeInfinity, S.ImaginaryUnit*S.NegativeInfinity): + dir = "+" + + if(z0.has(z)): + raise NotImplementedError("Limits approaching a variable point are" + " not supported (%s -> %s)" % (z, z0)) + if isinstance(dir, str): + dir = Symbol(dir) + elif not isinstance(dir, Symbol): + raise TypeError("direction must be of type basestring or " + "Symbol, not %s" % type(dir)) + if str(dir) not in ('+', '-', '+-'): + raise ValueError("direction must be one of '+', '-' " + "or '+-', not %s" % dir) + + obj = Expr.__new__(cls) + obj._args = (e, z, z0, dir) + return obj + + + @property + def free_symbols(self): + e = self.args[0] + isyms = e.free_symbols + isyms.difference_update(self.args[1].free_symbols) + isyms.update(self.args[2].free_symbols) + return isyms + + + def pow_heuristics(self, e): + _, z, z0, _ = self.args + b1, e1 = e.base, e.exp + if not b1.has(z): + res = limit(e1*log(b1), z, z0) + return exp(res) + + ex_lim = limit(e1, z, z0) + base_lim = limit(b1, z, z0) + + if base_lim is S.One: + if ex_lim in (S.Infinity, S.NegativeInfinity): + res = limit(e1*(b1 - 1), z, z0) + return exp(res) + if base_lim is S.NegativeInfinity and ex_lim is S.Infinity: + return S.ComplexInfinity + + + def doit(self, **hints): + """Evaluates the limit. + + Parameters + ========== + + deep : bool, optional (default: True) + Invoke the ``doit`` method of the expressions involved before + taking the limit. + + hints : optional keyword arguments + To be passed to ``doit`` methods; only used if deep is True. + """ + + e, z, z0, dir = self.args + + if str(dir) == '+-': + r = limit(e, z, z0, dir='+') + l = limit(e, z, z0, dir='-') + if isinstance(r, Limit) and isinstance(l, Limit): + if r.args[0] == l.args[0]: + return self + if r == l: + return l + if r.is_infinite and l.is_infinite: + return S.ComplexInfinity + raise ValueError("The limit does not exist since " + "left hand limit = %s and right hand limit = %s" + % (l, r)) + + if z0 is S.ComplexInfinity: + raise NotImplementedError("Limits at complex " + "infinity are not implemented") + + if z0.is_infinite: + cdir = sign(z0) + cdir = cdir/abs(cdir) + e = e.subs(z, cdir*z) + dir = "-" + z0 = S.Infinity + + if hints.get('deep', True): + e = e.doit(**hints) + z = z.doit(**hints) + z0 = z0.doit(**hints) + + if e == z: + return z0 + + if not e.has(z): + return e + + if z0 is S.NaN: + return S.NaN + + if e.has(*_illegal): + return self + + if e.is_Order: + return Order(limit(e.expr, z, z0), *e.args[1:]) + + cdir = S.Zero + if str(dir) == "+": + cdir = S.One + elif str(dir) == "-": + cdir = S.NegativeOne + + def set_signs(expr): + if not expr.args: + return expr + newargs = tuple(set_signs(arg) for arg in expr.args) + if newargs != expr.args: + expr = expr.func(*newargs) + abs_flag = isinstance(expr, Abs) + arg_flag = isinstance(expr, arg) + sign_flag = isinstance(expr, sign) + if abs_flag or sign_flag or arg_flag: + try: + sig = limit(expr.args[0], z, z0, dir) + if sig.is_zero: + sig = limit(1/expr.args[0], z, z0, dir) + except NotImplementedError: + return expr + else: + if sig.is_extended_real: + if (sig < 0) == True: + return (-expr.args[0] if abs_flag else + S.NegativeOne if sign_flag else S.Pi) + elif (sig > 0) == True: + return (expr.args[0] if abs_flag else + S.One if sign_flag else S.Zero) + return expr + + if e.has(Float): + # Convert floats like 0.5 to exact SymPy numbers like S.Half, to + # prevent rounding errors which can lead to unexpected execution + # of conditional blocks that work on comparisons + # Also see comments in https://github.com/sympy/sympy/issues/19453 + from sympy.simplify.simplify import nsimplify + e = nsimplify(e) + e = set_signs(e) + + + if e.is_meromorphic(z, z0): + if z0 is S.Infinity: + newe = e.subs(z, 1/z) + # cdir changes sign as oo- should become 0+ + cdir = -cdir + else: + newe = e.subs(z, z + z0) + try: + coeff, ex = newe.leadterm(z, cdir=cdir) + except ValueError: + pass + else: + if ex > 0: + return S.Zero + elif ex == 0: + return coeff + if cdir == 1 or not(int(ex) & 1): + return S.Infinity*sign(coeff) + elif cdir == -1: + return S.NegativeInfinity*sign(coeff) + else: + return S.ComplexInfinity + + if z0 is S.Infinity: + if e.is_Mul: + e = factor_terms(e) + dummy = Dummy('z', positive=z.is_positive, negative=z.is_negative, real=z.is_real) + newe = e.subs(z, 1/dummy) + # cdir changes sign as oo- should become 0+ + cdir = -cdir + newz = dummy + else: + newe = e.subs(z, z + z0) + newz = z + try: + coeff, ex = newe.leadterm(newz, cdir=cdir) + except (ValueError, NotImplementedError, PoleError): + # The NotImplementedError catching is for custom functions + from sympy.simplify.powsimp import powsimp + e = powsimp(e) + if e.is_Pow: + r = self.pow_heuristics(e) + if r is not None: + return r + try: + coeff = newe.as_leading_term(newz, cdir=cdir) + if coeff != newe and (coeff.has(exp) or coeff.has(S.Exp1)): + return gruntz(coeff, newz, 0, "-" if re(cdir).is_negative else "+") + except (ValueError, NotImplementedError, PoleError): + pass + else: + if isinstance(coeff, AccumBounds) and ex == S.Zero: + return coeff + if coeff.has(S.Infinity, S.NegativeInfinity, S.ComplexInfinity, S.NaN): + return self + if not coeff.has(newz): + if ex.is_positive: + return S.Zero + elif ex == 0: + return coeff + elif ex.is_negative: + if cdir == 1: + return S.Infinity*sign(coeff) + elif cdir == -1: + return S.NegativeInfinity*sign(coeff)*S.NegativeOne**(S.One + ex) + else: + return S.ComplexInfinity + else: + raise NotImplementedError("Not sure of sign of %s" % ex) + + # gruntz fails on factorials but works with the gamma function + # If no factorial term is present, e should remain unchanged. + # factorial is defined to be zero for negative inputs (which + # differs from gamma) so only rewrite for non-negative z0. + if z0.is_extended_nonnegative: + e = e.rewrite(factorial, gamma) + + l = None + + try: + r = gruntz(e, z, z0, dir) + if r is S.NaN or l is S.NaN: + raise PoleError() + except (PoleError, ValueError): + if l is not None: + raise + r = heuristics(e, z, z0, dir) + if r is None: + return self + + return r diff --git a/venv/lib/python3.10/site-packages/sympy/series/limitseq.py b/venv/lib/python3.10/site-packages/sympy/series/limitseq.py new file mode 100644 index 0000000000000000000000000000000000000000..ceac4e7b63bfc09d9dfc26c12c7d2acc8b8d44da --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/limitseq.py @@ -0,0 +1,257 @@ +"""Limits of sequences""" + +from sympy.calculus.accumulationbounds import AccumulationBounds +from sympy.core.add import Add +from sympy.core.function import PoleError +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.numbers import fibonacci +from sympy.functions.combinatorial.factorials import factorial, subfactorial +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.miscellaneous import Max, Min +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.series.limits import Limit + + +def difference_delta(expr, n=None, step=1): + """Difference Operator. + + Explanation + =========== + + Discrete analog of differential operator. Given a sequence x[n], + returns the sequence x[n + step] - x[n]. + + Examples + ======== + + >>> from sympy import difference_delta as dd + >>> from sympy.abc import n + >>> dd(n*(n + 1), n) + 2*n + 2 + >>> dd(n*(n + 1), n, 2) + 4*n + 6 + + References + ========== + + .. [1] https://reference.wolfram.com/language/ref/DifferenceDelta.html + """ + expr = sympify(expr) + + if n is None: + f = expr.free_symbols + if len(f) == 1: + n = f.pop() + elif len(f) == 0: + return S.Zero + else: + raise ValueError("Since there is more than one variable in the" + " expression, a variable must be supplied to" + " take the difference of %s" % expr) + step = sympify(step) + if step.is_number is False or step.is_finite is False: + raise ValueError("Step should be a finite number.") + + if hasattr(expr, '_eval_difference_delta'): + result = expr._eval_difference_delta(n, step) + if result: + return result + + return expr.subs(n, n + step) - expr + + +def dominant(expr, n): + """Finds the dominant term in a sum, that is a term that dominates + every other term. + + Explanation + =========== + + If limit(a/b, n, oo) is oo then a dominates b. + If limit(a/b, n, oo) is 0 then b dominates a. + Otherwise, a and b are comparable. + + If there is no unique dominant term, then returns ``None``. + + Examples + ======== + + >>> from sympy import Sum + >>> from sympy.series.limitseq import dominant + >>> from sympy.abc import n, k + >>> dominant(5*n**3 + 4*n**2 + n + 1, n) + 5*n**3 + >>> dominant(2**n + Sum(k, (k, 0, n)), n) + 2**n + + See Also + ======== + + sympy.series.limitseq.dominant + """ + terms = Add.make_args(expr.expand(func=True)) + term0 = terms[-1] + comp = [term0] # comparable terms + for t in terms[:-1]: + r = term0/t + e = r.gammasimp() + if e == r: + e = r.factor() + l = limit_seq(e, n) + if l is None: + return None + elif l.is_zero: + term0 = t + comp = [term0] + elif l not in [S.Infinity, S.NegativeInfinity]: + comp.append(t) + if len(comp) > 1: + return None + return term0 + + +def _limit_inf(expr, n): + try: + return Limit(expr, n, S.Infinity).doit(deep=False) + except (NotImplementedError, PoleError): + return None + + +def _limit_seq(expr, n, trials): + from sympy.concrete.summations import Sum + + for i in range(trials): + if not expr.has(Sum): + result = _limit_inf(expr, n) + if result is not None: + return result + + num, den = expr.as_numer_denom() + if not den.has(n) or not num.has(n): + result = _limit_inf(expr.doit(), n) + if result is not None: + return result + return None + + num, den = (difference_delta(t.expand(), n) for t in [num, den]) + expr = (num / den).gammasimp() + + if not expr.has(Sum): + result = _limit_inf(expr, n) + if result is not None: + return result + + num, den = expr.as_numer_denom() + + num = dominant(num, n) + if num is None: + return None + + den = dominant(den, n) + if den is None: + return None + + expr = (num / den).gammasimp() + + +def limit_seq(expr, n=None, trials=5): + """Finds the limit of a sequence as index ``n`` tends to infinity. + + Parameters + ========== + + expr : Expr + SymPy expression for the ``n-th`` term of the sequence + n : Symbol, optional + The index of the sequence, an integer that tends to positive + infinity. If None, inferred from the expression unless it has + multiple symbols. + trials: int, optional + The algorithm is highly recursive. ``trials`` is a safeguard from + infinite recursion in case the limit is not easily computed by the + algorithm. Try increasing ``trials`` if the algorithm returns ``None``. + + Admissible Terms + ================ + + The algorithm is designed for sequences built from rational functions, + indefinite sums, and indefinite products over an indeterminate n. Terms of + alternating sign are also allowed, but more complex oscillatory behavior is + not supported. + + Examples + ======== + + >>> from sympy import limit_seq, Sum, binomial + >>> from sympy.abc import n, k, m + >>> limit_seq((5*n**3 + 3*n**2 + 4) / (3*n**3 + 4*n - 5), n) + 5/3 + >>> limit_seq(binomial(2*n, n) / Sum(binomial(2*k, k), (k, 1, n)), n) + 3/4 + >>> limit_seq(Sum(k**2 * Sum(2**m/m, (m, 1, k)), (k, 1, n)) / (2**n*n), n) + 4 + + See Also + ======== + + sympy.series.limitseq.dominant + + References + ========== + + .. [1] Computing Limits of Sequences - Manuel Kauers + """ + + from sympy.concrete.summations import Sum + if n is None: + free = expr.free_symbols + if len(free) == 1: + n = free.pop() + elif not free: + return expr + else: + raise ValueError("Expression has more than one variable. " + "Please specify a variable.") + elif n not in expr.free_symbols: + return expr + + expr = expr.rewrite(fibonacci, S.GoldenRatio) + expr = expr.rewrite(factorial, subfactorial, gamma) + n_ = Dummy("n", integer=True, positive=True) + n1 = Dummy("n", odd=True, positive=True) + n2 = Dummy("n", even=True, positive=True) + + # If there is a negative term raised to a power involving n, or a + # trigonometric function, then consider even and odd n separately. + powers = (p.as_base_exp() for p in expr.atoms(Pow)) + if (any(b.is_negative and e.has(n) for b, e in powers) or + expr.has(cos, sin)): + L1 = _limit_seq(expr.xreplace({n: n1}), n1, trials) + if L1 is not None: + L2 = _limit_seq(expr.xreplace({n: n2}), n2, trials) + if L1 != L2: + if L1.is_comparable and L2.is_comparable: + return AccumulationBounds(Min(L1, L2), Max(L1, L2)) + else: + return None + else: + L1 = _limit_seq(expr.xreplace({n: n_}), n_, trials) + if L1 is not None: + return L1 + else: + if expr.is_Add: + limits = [limit_seq(term, n, trials) for term in expr.args] + if any(result is None for result in limits): + return None + else: + return Add(*limits) + # Maybe the absolute value is easier to deal with (though not if + # it has a Sum). If it tends to 0, the limit is 0. + elif not expr.has(Sum): + lim = _limit_seq(Abs(expr.xreplace({n: n_})), n_, trials) + if lim is not None and lim.is_zero: + return S.Zero diff --git a/venv/lib/python3.10/site-packages/sympy/series/order.py b/venv/lib/python3.10/site-packages/sympy/series/order.py new file mode 100644 index 0000000000000000000000000000000000000000..9cfd4309c2b7094ce02feab129e5f051c442d8cd --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/order.py @@ -0,0 +1,522 @@ +from sympy.core import S, sympify, Expr, Dummy, Add, Mul +from sympy.core.cache import cacheit +from sympy.core.containers import Tuple +from sympy.core.function import Function, PoleError, expand_power_base, expand_log +from sympy.core.sorting import default_sort_key +from sympy.functions.elementary.exponential import exp, log +from sympy.sets.sets import Complement +from sympy.utilities.iterables import uniq, is_sequence + + +class Order(Expr): + r""" Represents the limiting behavior of some function. + + Explanation + =========== + + The order of a function characterizes the function based on the limiting + behavior of the function as it goes to some limit. Only taking the limit + point to be a number is currently supported. This is expressed in + big O notation [1]_. + + The formal definition for the order of a function `g(x)` about a point `a` + is such that `g(x) = O(f(x))` as `x \rightarrow a` if and only if there + exists a `\delta > 0` and an `M > 0` such that `|g(x)| \leq M|f(x)|` for + `|x-a| < \delta`. This is equivalent to `\limsup_{x \rightarrow a} + |g(x)/f(x)| < \infty`. + + Let's illustrate it on the following example by taking the expansion of + `\sin(x)` about 0: + + .. math :: + \sin(x) = x - x^3/3! + O(x^5) + + where in this case `O(x^5) = x^5/5! - x^7/7! + \cdots`. By the definition + of `O`, there is a `\delta > 0` and an `M` such that: + + .. math :: + |x^5/5! - x^7/7! + ....| <= M|x^5| \text{ for } |x| < \delta + + or by the alternate definition: + + .. math :: + \lim_{x \rightarrow 0} | (x^5/5! - x^7/7! + ....) / x^5| < \infty + + which surely is true, because + + .. math :: + \lim_{x \rightarrow 0} | (x^5/5! - x^7/7! + ....) / x^5| = 1/5! + + + As it is usually used, the order of a function can be intuitively thought + of representing all terms of powers greater than the one specified. For + example, `O(x^3)` corresponds to any terms proportional to `x^3, + x^4,\ldots` and any higher power. For a polynomial, this leaves terms + proportional to `x^2`, `x` and constants. + + Examples + ======== + + >>> from sympy import O, oo, cos, pi + >>> from sympy.abc import x, y + + >>> O(x + x**2) + O(x) + >>> O(x + x**2, (x, 0)) + O(x) + >>> O(x + x**2, (x, oo)) + O(x**2, (x, oo)) + + >>> O(1 + x*y) + O(1, x, y) + >>> O(1 + x*y, (x, 0), (y, 0)) + O(1, x, y) + >>> O(1 + x*y, (x, oo), (y, oo)) + O(x*y, (x, oo), (y, oo)) + + >>> O(1) in O(1, x) + True + >>> O(1, x) in O(1) + False + >>> O(x) in O(1, x) + True + >>> O(x**2) in O(x) + True + + >>> O(x)*x + O(x**2) + >>> O(x) - O(x) + O(x) + >>> O(cos(x)) + O(1) + >>> O(cos(x), (x, pi/2)) + O(x - pi/2, (x, pi/2)) + + References + ========== + + .. [1] `Big O notation `_ + + Notes + ===== + + In ``O(f(x), x)`` the expression ``f(x)`` is assumed to have a leading + term. ``O(f(x), x)`` is automatically transformed to + ``O(f(x).as_leading_term(x),x)``. + + ``O(expr*f(x), x)`` is ``O(f(x), x)`` + + ``O(expr, x)`` is ``O(1)`` + + ``O(0, x)`` is 0. + + Multivariate O is also supported: + + ``O(f(x, y), x, y)`` is transformed to + ``O(f(x, y).as_leading_term(x,y).as_leading_term(y), x, y)`` + + In the multivariate case, it is assumed the limits w.r.t. the various + symbols commute. + + If no symbols are passed then all symbols in the expression are used + and the limit point is assumed to be zero. + + """ + + is_Order = True + + __slots__ = () + + @cacheit + def __new__(cls, expr, *args, **kwargs): + expr = sympify(expr) + + if not args: + if expr.is_Order: + variables = expr.variables + point = expr.point + else: + variables = list(expr.free_symbols) + point = [S.Zero]*len(variables) + else: + args = list(args if is_sequence(args) else [args]) + variables, point = [], [] + if is_sequence(args[0]): + for a in args: + v, p = list(map(sympify, a)) + variables.append(v) + point.append(p) + else: + variables = list(map(sympify, args)) + point = [S.Zero]*len(variables) + + if not all(v.is_symbol for v in variables): + raise TypeError('Variables are not symbols, got %s' % variables) + + if len(list(uniq(variables))) != len(variables): + raise ValueError('Variables are supposed to be unique symbols, got %s' % variables) + + if expr.is_Order: + expr_vp = dict(expr.args[1:]) + new_vp = dict(expr_vp) + vp = dict(zip(variables, point)) + for v, p in vp.items(): + if v in new_vp.keys(): + if p != new_vp[v]: + raise NotImplementedError( + "Mixing Order at different points is not supported.") + else: + new_vp[v] = p + if set(expr_vp.keys()) == set(new_vp.keys()): + return expr + else: + variables = list(new_vp.keys()) + point = [new_vp[v] for v in variables] + + if expr is S.NaN: + return S.NaN + + if any(x in p.free_symbols for x in variables for p in point): + raise ValueError('Got %s as a point.' % point) + + if variables: + if any(p != point[0] for p in point): + raise NotImplementedError( + "Multivariable orders at different points are not supported.") + if point[0] in (S.Infinity, S.Infinity*S.ImaginaryUnit): + s = {k: 1/Dummy() for k in variables} + rs = {1/v: 1/k for k, v in s.items()} + ps = [S.Zero for p in point] + elif point[0] in (S.NegativeInfinity, S.NegativeInfinity*S.ImaginaryUnit): + s = {k: -1/Dummy() for k in variables} + rs = {-1/v: -1/k for k, v in s.items()} + ps = [S.Zero for p in point] + elif point[0] is not S.Zero: + s = {k: Dummy() + point[0] for k in variables} + rs = {(v - point[0]).together(): k - point[0] for k, v in s.items()} + ps = [S.Zero for p in point] + else: + s = () + rs = () + ps = list(point) + + expr = expr.subs(s) + + if expr.is_Add: + expr = expr.factor() + + if s: + args = tuple([r[0] for r in rs.items()]) + else: + args = tuple(variables) + + if len(variables) > 1: + # XXX: better way? We need this expand() to + # workaround e.g: expr = x*(x + y). + # (x*(x + y)).as_leading_term(x, y) currently returns + # x*y (wrong order term!). That's why we want to deal with + # expand()'ed expr (handled in "if expr.is_Add" branch below). + expr = expr.expand() + + old_expr = None + while old_expr != expr: + old_expr = expr + if expr.is_Add: + lst = expr.extract_leading_order(args) + expr = Add(*[f.expr for (e, f) in lst]) + + elif expr: + try: + expr = expr.as_leading_term(*args) + except PoleError: + if isinstance(expr, Function) or\ + all(isinstance(arg, Function) for arg in expr.args): + # It is not possible to simplify an expression + # containing only functions (which raise error on + # call to leading term) further + pass + else: + orders = [] + pts = tuple(zip(args, ps)) + for arg in expr.args: + try: + lt = arg.as_leading_term(*args) + except PoleError: + lt = arg + if lt not in args: + order = Order(lt) + else: + order = Order(lt, *pts) + orders.append(order) + if expr.is_Add: + new_expr = Order(Add(*orders), *pts) + if new_expr.is_Add: + new_expr = Order(Add(*[a.expr for a in new_expr.args]), *pts) + expr = new_expr.expr + elif expr.is_Mul: + expr = Mul(*[a.expr for a in orders]) + elif expr.is_Pow: + e = expr.exp + b = expr.base + expr = exp(e * log(b)) + + # It would probably be better to handle this somewhere + # else. This is needed for a testcase in which there is a + # symbol with the assumptions zero=True. + if expr.is_zero: + expr = S.Zero + else: + expr = expr.as_independent(*args, as_Add=False)[1] + + expr = expand_power_base(expr) + expr = expand_log(expr) + + if len(args) == 1: + # The definition of O(f(x)) symbol explicitly stated that + # the argument of f(x) is irrelevant. That's why we can + # combine some power exponents (only "on top" of the + # expression tree for f(x)), e.g.: + # x**p * (-x)**q -> x**(p+q) for real p, q. + x = args[0] + margs = list(Mul.make_args( + expr.as_independent(x, as_Add=False)[1])) + + for i, t in enumerate(margs): + if t.is_Pow: + b, q = t.args + if b in (x, -x) and q.is_real and not q.has(x): + margs[i] = x**q + elif b.is_Pow and not b.exp.has(x): + b, r = b.args + if b in (x, -x) and r.is_real: + margs[i] = x**(r*q) + elif b.is_Mul and b.args[0] is S.NegativeOne: + b = -b + if b.is_Pow and not b.exp.has(x): + b, r = b.args + if b in (x, -x) and r.is_real: + margs[i] = x**(r*q) + + expr = Mul(*margs) + + expr = expr.subs(rs) + + if expr.is_Order: + expr = expr.expr + + if not expr.has(*variables) and not expr.is_zero: + expr = S.One + + # create Order instance: + vp = dict(zip(variables, point)) + variables.sort(key=default_sort_key) + point = [vp[v] for v in variables] + args = (expr,) + Tuple(*zip(variables, point)) + obj = Expr.__new__(cls, *args) + return obj + + def _eval_nseries(self, x, n, logx, cdir=0): + return self + + @property + def expr(self): + return self.args[0] + + @property + def variables(self): + if self.args[1:]: + return tuple(x[0] for x in self.args[1:]) + else: + return () + + @property + def point(self): + if self.args[1:]: + return tuple(x[1] for x in self.args[1:]) + else: + return () + + @property + def free_symbols(self): + return self.expr.free_symbols | set(self.variables) + + def _eval_power(b, e): + if e.is_Number and e.is_nonnegative: + return b.func(b.expr ** e, *b.args[1:]) + if e == O(1): + return b + return + + def as_expr_variables(self, order_symbols): + if order_symbols is None: + order_symbols = self.args[1:] + else: + if (not all(o[1] == order_symbols[0][1] for o in order_symbols) and + not all(p == self.point[0] for p in self.point)): # pragma: no cover + raise NotImplementedError('Order at points other than 0 ' + 'or oo not supported, got %s as a point.' % self.point) + if order_symbols and order_symbols[0][1] != self.point[0]: + raise NotImplementedError( + "Multiplying Order at different points is not supported.") + order_symbols = dict(order_symbols) + for s, p in dict(self.args[1:]).items(): + if s not in order_symbols.keys(): + order_symbols[s] = p + order_symbols = sorted(order_symbols.items(), key=lambda x: default_sort_key(x[0])) + return self.expr, tuple(order_symbols) + + def removeO(self): + return S.Zero + + def getO(self): + return self + + @cacheit + def contains(self, expr): + r""" + Return True if expr belongs to Order(self.expr, \*self.variables). + Return False if self belongs to expr. + Return None if the inclusion relation cannot be determined + (e.g. when self and expr have different symbols). + """ + expr = sympify(expr) + if expr.is_zero: + return True + if expr is S.NaN: + return False + point = self.point[0] if self.point else S.Zero + if expr.is_Order: + if (any(p != point for p in expr.point) or + any(p != point for p in self.point)): + return None + if expr.expr == self.expr: + # O(1) + O(1), O(1) + O(1, x), etc. + return all(x in self.args[1:] for x in expr.args[1:]) + if expr.expr.is_Add: + return all(self.contains(x) for x in expr.expr.args) + if self.expr.is_Add and point.is_zero: + return any(self.func(x, *self.args[1:]).contains(expr) + for x in self.expr.args) + if self.variables and expr.variables: + common_symbols = tuple( + [s for s in self.variables if s in expr.variables]) + elif self.variables: + common_symbols = self.variables + else: + common_symbols = expr.variables + if not common_symbols: + return None + if (self.expr.is_Pow and len(self.variables) == 1 + and self.variables == expr.variables): + symbol = self.variables[0] + other = expr.expr.as_independent(symbol, as_Add=False)[1] + if (other.is_Pow and other.base == symbol and + self.expr.base == symbol): + if point.is_zero: + rv = (self.expr.exp - other.exp).is_nonpositive + if point.is_infinite: + rv = (self.expr.exp - other.exp).is_nonnegative + if rv is not None: + return rv + + from sympy.simplify.powsimp import powsimp + r = None + ratio = self.expr/expr.expr + ratio = powsimp(ratio, deep=True, combine='exp') + for s in common_symbols: + from sympy.series.limits import Limit + l = Limit(ratio, s, point).doit(heuristics=False) + if not isinstance(l, Limit): + l = l != 0 + else: + l = None + if r is None: + r = l + else: + if r != l: + return + return r + + if self.expr.is_Pow and len(self.variables) == 1: + symbol = self.variables[0] + other = expr.as_independent(symbol, as_Add=False)[1] + if (other.is_Pow and other.base == symbol and + self.expr.base == symbol): + if point.is_zero: + rv = (self.expr.exp - other.exp).is_nonpositive + if point.is_infinite: + rv = (self.expr.exp - other.exp).is_nonnegative + if rv is not None: + return rv + + obj = self.func(expr, *self.args[1:]) + return self.contains(obj) + + def __contains__(self, other): + result = self.contains(other) + if result is None: + raise TypeError('contains did not evaluate to a bool') + return result + + def _eval_subs(self, old, new): + if old in self.variables: + newexpr = self.expr.subs(old, new) + i = self.variables.index(old) + newvars = list(self.variables) + newpt = list(self.point) + if new.is_symbol: + newvars[i] = new + else: + syms = new.free_symbols + if len(syms) == 1 or old in syms: + if old in syms: + var = self.variables[i] + else: + var = syms.pop() + # First, try to substitute self.point in the "new" + # expr to see if this is a fixed point. + # E.g. O(y).subs(y, sin(x)) + from sympy import limit + if new.has(Order) and limit(new.getO().expr, var, new.getO().point[0]) == self.point[i]: + point = new.getO().point[0] + return Order(newexpr, *zip([var], [point])) + else: + point = new.subs(var, self.point[i]) + if point != self.point[i]: + from sympy.solvers.solveset import solveset + d = Dummy() + sol = solveset(old - new.subs(var, d), d) + if isinstance(sol, Complement): + e1 = sol.args[0] + e2 = sol.args[1] + sol = set(e1) - set(e2) + res = [dict(zip((d, ), sol))] + point = d.subs(res[0]).limit(old, self.point[i]) + newvars[i] = var + newpt[i] = point + elif old not in syms: + del newvars[i], newpt[i] + if not syms and new == self.point[i]: + newvars.extend(syms) + newpt.extend([S.Zero]*len(syms)) + else: + return + return Order(newexpr, *zip(newvars, newpt)) + + def _eval_conjugate(self): + expr = self.expr._eval_conjugate() + if expr is not None: + return self.func(expr, *self.args[1:]) + + def _eval_derivative(self, x): + return self.func(self.expr.diff(x), *self.args[1:]) or self + + def _eval_transpose(self): + expr = self.expr._eval_transpose() + if expr is not None: + return self.func(expr, *self.args[1:]) + + def __neg__(self): + return self + +O = Order diff --git a/venv/lib/python3.10/site-packages/sympy/series/residues.py b/venv/lib/python3.10/site-packages/sympy/series/residues.py new file mode 100644 index 0000000000000000000000000000000000000000..a426f9e799bd040eea5124f718c2fa43e5de026b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/residues.py @@ -0,0 +1,73 @@ +""" +This module implements the Residue function and related tools for working +with residues. +""" + +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.core.sympify import sympify +from sympy.utilities.timeutils import timethis + + +@timethis('residue') +def residue(expr, x, x0): + """ + Finds the residue of ``expr`` at the point x=x0. + + The residue is defined as the coefficient of ``1/(x-x0)`` in the power series + expansion about ``x=x0``. + + Examples + ======== + + >>> from sympy import Symbol, residue, sin + >>> x = Symbol("x") + >>> residue(1/x, x, 0) + 1 + >>> residue(1/x**2, x, 0) + 0 + >>> residue(2/sin(x), x, 0) + 2 + + This function is essential for the Residue Theorem [1]. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Residue_theorem + """ + # The current implementation uses series expansion to + # calculate it. A more general implementation is explained in + # the section 5.6 of the Bronstein's book {M. Bronstein: + # Symbolic Integration I, Springer Verlag (2005)}. For purely + # rational functions, the algorithm is much easier. See + # sections 2.4, 2.5, and 2.7 (this section actually gives an + # algorithm for computing any Laurent series coefficient for + # a rational function). The theory in section 2.4 will help to + # understand why the resultant works in the general algorithm. + # For the definition of a resultant, see section 1.4 (and any + # previous sections for more review). + + from sympy.series.order import Order + from sympy.simplify.radsimp import collect + expr = sympify(expr) + if x0 != 0: + expr = expr.subs(x, x + x0) + for n in (0, 1, 2, 4, 8, 16, 32): + s = expr.nseries(x, n=n) + if not s.has(Order) or s.getn() >= 0: + break + s = collect(s.removeO(), x) + if s.is_Add: + args = s.args + else: + args = [s] + res = S.Zero + for arg in args: + c, m = arg.as_coeff_mul(x) + m = Mul(*m) + if not (m in (S.One, x) or (m.is_Pow and m.exp.is_Integer)): + raise NotImplementedError('term of unexpected form: %s' % m) + if m == 1/x: + res += c + return res diff --git a/venv/lib/python3.10/site-packages/sympy/series/sequences.py b/venv/lib/python3.10/site-packages/sympy/series/sequences.py new file mode 100644 index 0000000000000000000000000000000000000000..7787515ddb05afaf34751bf451544935723d0921 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/sequences.py @@ -0,0 +1,1239 @@ +from sympy.core.basic import Basic +from sympy.core.cache import cacheit +from sympy.core.containers import Tuple +from sympy.core.decorators import call_highest_priority +from sympy.core.parameters import global_parameters +from sympy.core.function import AppliedUndef, expand +from sympy.core.mul import Mul +from sympy.core.numbers import Integer +from sympy.core.relational import Eq +from sympy.core.singleton import S, Singleton +from sympy.core.sorting import ordered +from sympy.core.symbol import Dummy, Symbol, Wild +from sympy.core.sympify import sympify +from sympy.matrices import Matrix +from sympy.polys import lcm, factor +from sympy.sets.sets import Interval, Intersection +from sympy.tensor.indexed import Idx +from sympy.utilities.iterables import flatten, is_sequence, iterable + + +############################################################################### +# SEQUENCES # +############################################################################### + + +class SeqBase(Basic): + """Base class for sequences""" + + is_commutative = True + _op_priority = 15 + + @staticmethod + def _start_key(expr): + """Return start (if possible) else S.Infinity. + + adapted from Set._infimum_key + """ + try: + start = expr.start + except NotImplementedError: + start = S.Infinity + return start + + def _intersect_interval(self, other): + """Returns start and stop. + + Takes intersection over the two intervals. + """ + interval = Intersection(self.interval, other.interval) + return interval.inf, interval.sup + + @property + def gen(self): + """Returns the generator for the sequence""" + raise NotImplementedError("(%s).gen" % self) + + @property + def interval(self): + """The interval on which the sequence is defined""" + raise NotImplementedError("(%s).interval" % self) + + @property + def start(self): + """The starting point of the sequence. This point is included""" + raise NotImplementedError("(%s).start" % self) + + @property + def stop(self): + """The ending point of the sequence. This point is included""" + raise NotImplementedError("(%s).stop" % self) + + @property + def length(self): + """Length of the sequence""" + raise NotImplementedError("(%s).length" % self) + + @property + def variables(self): + """Returns a tuple of variables that are bounded""" + return () + + @property + def free_symbols(self): + """ + This method returns the symbols in the object, excluding those + that take on a specific value (i.e. the dummy symbols). + + Examples + ======== + + >>> from sympy import SeqFormula + >>> from sympy.abc import n, m + >>> SeqFormula(m*n**2, (n, 0, 5)).free_symbols + {m} + """ + return ({j for i in self.args for j in i.free_symbols + .difference(self.variables)}) + + @cacheit + def coeff(self, pt): + """Returns the coefficient at point pt""" + if pt < self.start or pt > self.stop: + raise IndexError("Index %s out of bounds %s" % (pt, self.interval)) + return self._eval_coeff(pt) + + def _eval_coeff(self, pt): + raise NotImplementedError("The _eval_coeff method should be added to" + "%s to return coefficient so it is available" + "when coeff calls it." + % self.func) + + def _ith_point(self, i): + """Returns the i'th point of a sequence. + + Explanation + =========== + + If start point is negative infinity, point is returned from the end. + Assumes the first point to be indexed zero. + + Examples + ========= + + >>> from sympy import oo + >>> from sympy.series.sequences import SeqPer + + bounded + + >>> SeqPer((1, 2, 3), (-10, 10))._ith_point(0) + -10 + >>> SeqPer((1, 2, 3), (-10, 10))._ith_point(5) + -5 + + End is at infinity + + >>> SeqPer((1, 2, 3), (0, oo))._ith_point(5) + 5 + + Starts at negative infinity + + >>> SeqPer((1, 2, 3), (-oo, 0))._ith_point(5) + -5 + """ + if self.start is S.NegativeInfinity: + initial = self.stop + else: + initial = self.start + + if self.start is S.NegativeInfinity: + step = -1 + else: + step = 1 + + return initial + i*step + + def _add(self, other): + """ + Should only be used internally. + + Explanation + =========== + + self._add(other) returns a new, term-wise added sequence if self + knows how to add with other, otherwise it returns ``None``. + + ``other`` should only be a sequence object. + + Used within :class:`SeqAdd` class. + """ + return None + + def _mul(self, other): + """ + Should only be used internally. + + Explanation + =========== + + self._mul(other) returns a new, term-wise multiplied sequence if self + knows how to multiply with other, otherwise it returns ``None``. + + ``other`` should only be a sequence object. + + Used within :class:`SeqMul` class. + """ + return None + + def coeff_mul(self, other): + """ + Should be used when ``other`` is not a sequence. Should be + defined to define custom behaviour. + + Examples + ======== + + >>> from sympy import SeqFormula + >>> from sympy.abc import n + >>> SeqFormula(n**2).coeff_mul(2) + SeqFormula(2*n**2, (n, 0, oo)) + + Notes + ===== + + '*' defines multiplication of sequences with sequences only. + """ + return Mul(self, other) + + def __add__(self, other): + """Returns the term-wise addition of 'self' and 'other'. + + ``other`` should be a sequence. + + Examples + ======== + + >>> from sympy import SeqFormula + >>> from sympy.abc import n + >>> SeqFormula(n**2) + SeqFormula(n**3) + SeqFormula(n**3 + n**2, (n, 0, oo)) + """ + if not isinstance(other, SeqBase): + raise TypeError('cannot add sequence and %s' % type(other)) + return SeqAdd(self, other) + + @call_highest_priority('__add__') + def __radd__(self, other): + return self + other + + def __sub__(self, other): + """Returns the term-wise subtraction of ``self`` and ``other``. + + ``other`` should be a sequence. + + Examples + ======== + + >>> from sympy import SeqFormula + >>> from sympy.abc import n + >>> SeqFormula(n**2) - (SeqFormula(n)) + SeqFormula(n**2 - n, (n, 0, oo)) + """ + if not isinstance(other, SeqBase): + raise TypeError('cannot subtract sequence and %s' % type(other)) + return SeqAdd(self, -other) + + @call_highest_priority('__sub__') + def __rsub__(self, other): + return (-self) + other + + def __neg__(self): + """Negates the sequence. + + Examples + ======== + + >>> from sympy import SeqFormula + >>> from sympy.abc import n + >>> -SeqFormula(n**2) + SeqFormula(-n**2, (n, 0, oo)) + """ + return self.coeff_mul(-1) + + def __mul__(self, other): + """Returns the term-wise multiplication of 'self' and 'other'. + + ``other`` should be a sequence. For ``other`` not being a + sequence see :func:`coeff_mul` method. + + Examples + ======== + + >>> from sympy import SeqFormula + >>> from sympy.abc import n + >>> SeqFormula(n**2) * (SeqFormula(n)) + SeqFormula(n**3, (n, 0, oo)) + """ + if not isinstance(other, SeqBase): + raise TypeError('cannot multiply sequence and %s' % type(other)) + return SeqMul(self, other) + + @call_highest_priority('__mul__') + def __rmul__(self, other): + return self * other + + def __iter__(self): + for i in range(self.length): + pt = self._ith_point(i) + yield self.coeff(pt) + + def __getitem__(self, index): + if isinstance(index, int): + index = self._ith_point(index) + return self.coeff(index) + elif isinstance(index, slice): + start, stop = index.start, index.stop + if start is None: + start = 0 + if stop is None: + stop = self.length + return [self.coeff(self._ith_point(i)) for i in + range(start, stop, index.step or 1)] + + def find_linear_recurrence(self,n,d=None,gfvar=None): + r""" + Finds the shortest linear recurrence that satisfies the first n + terms of sequence of order `\leq` ``n/2`` if possible. + If ``d`` is specified, find shortest linear recurrence of order + `\leq` min(d, n/2) if possible. + Returns list of coefficients ``[b(1), b(2), ...]`` corresponding to the + recurrence relation ``x(n) = b(1)*x(n-1) + b(2)*x(n-2) + ...`` + Returns ``[]`` if no recurrence is found. + If gfvar is specified, also returns ordinary generating function as a + function of gfvar. + + Examples + ======== + + >>> from sympy import sequence, sqrt, oo, lucas + >>> from sympy.abc import n, x, y + >>> sequence(n**2).find_linear_recurrence(10, 2) + [] + >>> sequence(n**2).find_linear_recurrence(10) + [3, -3, 1] + >>> sequence(2**n).find_linear_recurrence(10) + [2] + >>> sequence(23*n**4+91*n**2).find_linear_recurrence(10) + [5, -10, 10, -5, 1] + >>> sequence(sqrt(5)*(((1 + sqrt(5))/2)**n - (-(1 + sqrt(5))/2)**(-n))/5).find_linear_recurrence(10) + [1, 1] + >>> sequence(x+y*(-2)**(-n), (n, 0, oo)).find_linear_recurrence(30) + [1/2, 1/2] + >>> sequence(3*5**n + 12).find_linear_recurrence(20,gfvar=x) + ([6, -5], 3*(5 - 21*x)/((x - 1)*(5*x - 1))) + >>> sequence(lucas(n)).find_linear_recurrence(15,gfvar=x) + ([1, 1], (x - 2)/(x**2 + x - 1)) + """ + from sympy.simplify import simplify + x = [simplify(expand(t)) for t in self[:n]] + lx = len(x) + if d is None: + r = lx//2 + else: + r = min(d,lx//2) + coeffs = [] + for l in range(1, r+1): + l2 = 2*l + mlist = [] + for k in range(l): + mlist.append(x[k:k+l]) + m = Matrix(mlist) + if m.det() != 0: + y = simplify(m.LUsolve(Matrix(x[l:l2]))) + if lx == l2: + coeffs = flatten(y[::-1]) + break + mlist = [] + for k in range(l,lx-l): + mlist.append(x[k:k+l]) + m = Matrix(mlist) + if m*y == Matrix(x[l2:]): + coeffs = flatten(y[::-1]) + break + if gfvar is None: + return coeffs + else: + l = len(coeffs) + if l == 0: + return [], None + else: + n, d = x[l-1]*gfvar**(l-1), 1 - coeffs[l-1]*gfvar**l + for i in range(l-1): + n += x[i]*gfvar**i + for j in range(l-i-1): + n -= coeffs[i]*x[j]*gfvar**(i+j+1) + d -= coeffs[i]*gfvar**(i+1) + return coeffs, simplify(factor(n)/factor(d)) + +class EmptySequence(SeqBase, metaclass=Singleton): + """Represents an empty sequence. + + The empty sequence is also available as a singleton as + ``S.EmptySequence``. + + Examples + ======== + + >>> from sympy import EmptySequence, SeqPer + >>> from sympy.abc import x + >>> EmptySequence + EmptySequence + >>> SeqPer((1, 2), (x, 0, 10)) + EmptySequence + SeqPer((1, 2), (x, 0, 10)) + >>> SeqPer((1, 2)) * EmptySequence + EmptySequence + >>> EmptySequence.coeff_mul(-1) + EmptySequence + """ + + @property + def interval(self): + return S.EmptySet + + @property + def length(self): + return S.Zero + + def coeff_mul(self, coeff): + """See docstring of SeqBase.coeff_mul""" + return self + + def __iter__(self): + return iter([]) + + +class SeqExpr(SeqBase): + """Sequence expression class. + + Various sequences should inherit from this class. + + Examples + ======== + + >>> from sympy.series.sequences import SeqExpr + >>> from sympy.abc import x + >>> from sympy import Tuple + >>> s = SeqExpr(Tuple(1, 2, 3), Tuple(x, 0, 10)) + >>> s.gen + (1, 2, 3) + >>> s.interval + Interval(0, 10) + >>> s.length + 11 + + See Also + ======== + + sympy.series.sequences.SeqPer + sympy.series.sequences.SeqFormula + """ + + @property + def gen(self): + return self.args[0] + + @property + def interval(self): + return Interval(self.args[1][1], self.args[1][2]) + + @property + def start(self): + return self.interval.inf + + @property + def stop(self): + return self.interval.sup + + @property + def length(self): + return self.stop - self.start + 1 + + @property + def variables(self): + return (self.args[1][0],) + + +class SeqPer(SeqExpr): + """ + Represents a periodic sequence. + + The elements are repeated after a given period. + + Examples + ======== + + >>> from sympy import SeqPer, oo + >>> from sympy.abc import k + + >>> s = SeqPer((1, 2, 3), (0, 5)) + >>> s.periodical + (1, 2, 3) + >>> s.period + 3 + + For value at a particular point + + >>> s.coeff(3) + 1 + + supports slicing + + >>> s[:] + [1, 2, 3, 1, 2, 3] + + iterable + + >>> list(s) + [1, 2, 3, 1, 2, 3] + + sequence starts from negative infinity + + >>> SeqPer((1, 2, 3), (-oo, 0))[0:6] + [1, 2, 3, 1, 2, 3] + + Periodic formulas + + >>> SeqPer((k, k**2, k**3), (k, 0, oo))[0:6] + [0, 1, 8, 3, 16, 125] + + See Also + ======== + + sympy.series.sequences.SeqFormula + """ + + def __new__(cls, periodical, limits=None): + periodical = sympify(periodical) + + def _find_x(periodical): + free = periodical.free_symbols + if len(periodical.free_symbols) == 1: + return free.pop() + else: + return Dummy('k') + + x, start, stop = None, None, None + if limits is None: + x, start, stop = _find_x(periodical), 0, S.Infinity + if is_sequence(limits, Tuple): + if len(limits) == 3: + x, start, stop = limits + elif len(limits) == 2: + x = _find_x(periodical) + start, stop = limits + + if not isinstance(x, (Symbol, Idx)) or start is None or stop is None: + raise ValueError('Invalid limits given: %s' % str(limits)) + + if start is S.NegativeInfinity and stop is S.Infinity: + raise ValueError("Both the start and end value" + "cannot be unbounded") + + limits = sympify((x, start, stop)) + + if is_sequence(periodical, Tuple): + periodical = sympify(tuple(flatten(periodical))) + else: + raise ValueError("invalid period %s should be something " + "like e.g (1, 2) " % periodical) + + if Interval(limits[1], limits[2]) is S.EmptySet: + return S.EmptySequence + + return Basic.__new__(cls, periodical, limits) + + @property + def period(self): + return len(self.gen) + + @property + def periodical(self): + return self.gen + + def _eval_coeff(self, pt): + if self.start is S.NegativeInfinity: + idx = (self.stop - pt) % self.period + else: + idx = (pt - self.start) % self.period + return self.periodical[idx].subs(self.variables[0], pt) + + def _add(self, other): + """See docstring of SeqBase._add""" + if isinstance(other, SeqPer): + per1, lper1 = self.periodical, self.period + per2, lper2 = other.periodical, other.period + + per_length = lcm(lper1, lper2) + + new_per = [] + for x in range(per_length): + ele1 = per1[x % lper1] + ele2 = per2[x % lper2] + new_per.append(ele1 + ele2) + + start, stop = self._intersect_interval(other) + return SeqPer(new_per, (self.variables[0], start, stop)) + + def _mul(self, other): + """See docstring of SeqBase._mul""" + if isinstance(other, SeqPer): + per1, lper1 = self.periodical, self.period + per2, lper2 = other.periodical, other.period + + per_length = lcm(lper1, lper2) + + new_per = [] + for x in range(per_length): + ele1 = per1[x % lper1] + ele2 = per2[x % lper2] + new_per.append(ele1 * ele2) + + start, stop = self._intersect_interval(other) + return SeqPer(new_per, (self.variables[0], start, stop)) + + def coeff_mul(self, coeff): + """See docstring of SeqBase.coeff_mul""" + coeff = sympify(coeff) + per = [x * coeff for x in self.periodical] + return SeqPer(per, self.args[1]) + + +class SeqFormula(SeqExpr): + """ + Represents sequence based on a formula. + + Elements are generated using a formula. + + Examples + ======== + + >>> from sympy import SeqFormula, oo, Symbol + >>> n = Symbol('n') + >>> s = SeqFormula(n**2, (n, 0, 5)) + >>> s.formula + n**2 + + For value at a particular point + + >>> s.coeff(3) + 9 + + supports slicing + + >>> s[:] + [0, 1, 4, 9, 16, 25] + + iterable + + >>> list(s) + [0, 1, 4, 9, 16, 25] + + sequence starts from negative infinity + + >>> SeqFormula(n**2, (-oo, 0))[0:6] + [0, 1, 4, 9, 16, 25] + + See Also + ======== + + sympy.series.sequences.SeqPer + """ + + def __new__(cls, formula, limits=None): + formula = sympify(formula) + + def _find_x(formula): + free = formula.free_symbols + if len(free) == 1: + return free.pop() + elif not free: + return Dummy('k') + else: + raise ValueError( + " specify dummy variables for %s. If the formula contains" + " more than one free symbol, a dummy variable should be" + " supplied explicitly e.g., SeqFormula(m*n**2, (n, 0, 5))" + % formula) + + x, start, stop = None, None, None + if limits is None: + x, start, stop = _find_x(formula), 0, S.Infinity + if is_sequence(limits, Tuple): + if len(limits) == 3: + x, start, stop = limits + elif len(limits) == 2: + x = _find_x(formula) + start, stop = limits + + if not isinstance(x, (Symbol, Idx)) or start is None or stop is None: + raise ValueError('Invalid limits given: %s' % str(limits)) + + if start is S.NegativeInfinity and stop is S.Infinity: + raise ValueError("Both the start and end value " + "cannot be unbounded") + limits = sympify((x, start, stop)) + + if Interval(limits[1], limits[2]) is S.EmptySet: + return S.EmptySequence + + return Basic.__new__(cls, formula, limits) + + @property + def formula(self): + return self.gen + + def _eval_coeff(self, pt): + d = self.variables[0] + return self.formula.subs(d, pt) + + def _add(self, other): + """See docstring of SeqBase._add""" + if isinstance(other, SeqFormula): + form1, v1 = self.formula, self.variables[0] + form2, v2 = other.formula, other.variables[0] + formula = form1 + form2.subs(v2, v1) + start, stop = self._intersect_interval(other) + return SeqFormula(formula, (v1, start, stop)) + + def _mul(self, other): + """See docstring of SeqBase._mul""" + if isinstance(other, SeqFormula): + form1, v1 = self.formula, self.variables[0] + form2, v2 = other.formula, other.variables[0] + formula = form1 * form2.subs(v2, v1) + start, stop = self._intersect_interval(other) + return SeqFormula(formula, (v1, start, stop)) + + def coeff_mul(self, coeff): + """See docstring of SeqBase.coeff_mul""" + coeff = sympify(coeff) + formula = self.formula * coeff + return SeqFormula(formula, self.args[1]) + + def expand(self, *args, **kwargs): + return SeqFormula(expand(self.formula, *args, **kwargs), self.args[1]) + +class RecursiveSeq(SeqBase): + """ + A finite degree recursive sequence. + + Explanation + =========== + + That is, a sequence a(n) that depends on a fixed, finite number of its + previous values. The general form is + + a(n) = f(a(n - 1), a(n - 2), ..., a(n - d)) + + for some fixed, positive integer d, where f is some function defined by a + SymPy expression. + + Parameters + ========== + + recurrence : SymPy expression defining recurrence + This is *not* an equality, only the expression that the nth term is + equal to. For example, if :code:`a(n) = f(a(n - 1), ..., a(n - d))`, + then the expression should be :code:`f(a(n - 1), ..., a(n - d))`. + + yn : applied undefined function + Represents the nth term of the sequence as e.g. :code:`y(n)` where + :code:`y` is an undefined function and `n` is the sequence index. + + n : symbolic argument + The name of the variable that the recurrence is in, e.g., :code:`n` if + the recurrence function is :code:`y(n)`. + + initial : iterable with length equal to the degree of the recurrence + The initial values of the recurrence. + + start : start value of sequence (inclusive) + + Examples + ======== + + >>> from sympy import Function, symbols + >>> from sympy.series.sequences import RecursiveSeq + >>> y = Function("y") + >>> n = symbols("n") + >>> fib = RecursiveSeq(y(n - 1) + y(n - 2), y(n), n, [0, 1]) + + >>> fib.coeff(3) # Value at a particular point + 2 + + >>> fib[:6] # supports slicing + [0, 1, 1, 2, 3, 5] + + >>> fib.recurrence # inspect recurrence + Eq(y(n), y(n - 2) + y(n - 1)) + + >>> fib.degree # automatically determine degree + 2 + + >>> for x in zip(range(10), fib): # supports iteration + ... print(x) + (0, 0) + (1, 1) + (2, 1) + (3, 2) + (4, 3) + (5, 5) + (6, 8) + (7, 13) + (8, 21) + (9, 34) + + See Also + ======== + + sympy.series.sequences.SeqFormula + + """ + + def __new__(cls, recurrence, yn, n, initial=None, start=0): + if not isinstance(yn, AppliedUndef): + raise TypeError("recurrence sequence must be an applied undefined function" + ", found `{}`".format(yn)) + + if not isinstance(n, Basic) or not n.is_symbol: + raise TypeError("recurrence variable must be a symbol" + ", found `{}`".format(n)) + + if yn.args != (n,): + raise TypeError("recurrence sequence does not match symbol") + + y = yn.func + + k = Wild("k", exclude=(n,)) + degree = 0 + + # Find all applications of y in the recurrence and check that: + # 1. The function y is only being used with a single argument; and + # 2. All arguments are n + k for constant negative integers k. + + prev_ys = recurrence.find(y) + for prev_y in prev_ys: + if len(prev_y.args) != 1: + raise TypeError("Recurrence should be in a single variable") + + shift = prev_y.args[0].match(n + k)[k] + if not (shift.is_constant() and shift.is_integer and shift < 0): + raise TypeError("Recurrence should have constant," + " negative, integer shifts" + " (found {})".format(prev_y)) + + if -shift > degree: + degree = -shift + + if not initial: + initial = [Dummy("c_{}".format(k)) for k in range(degree)] + + if len(initial) != degree: + raise ValueError("Number of initial terms must equal degree") + + degree = Integer(degree) + start = sympify(start) + + initial = Tuple(*(sympify(x) for x in initial)) + + seq = Basic.__new__(cls, recurrence, yn, n, initial, start) + + seq.cache = {y(start + k): init for k, init in enumerate(initial)} + seq.degree = degree + + return seq + + @property + def _recurrence(self): + """Equation defining recurrence.""" + return self.args[0] + + @property + def recurrence(self): + """Equation defining recurrence.""" + return Eq(self.yn, self.args[0]) + + @property + def yn(self): + """Applied function representing the nth term""" + return self.args[1] + + @property + def y(self): + """Undefined function for the nth term of the sequence""" + return self.yn.func + + @property + def n(self): + """Sequence index symbol""" + return self.args[2] + + @property + def initial(self): + """The initial values of the sequence""" + return self.args[3] + + @property + def start(self): + """The starting point of the sequence. This point is included""" + return self.args[4] + + @property + def stop(self): + """The ending point of the sequence. (oo)""" + return S.Infinity + + @property + def interval(self): + """Interval on which sequence is defined.""" + return (self.start, S.Infinity) + + def _eval_coeff(self, index): + if index - self.start < len(self.cache): + return self.cache[self.y(index)] + + for current in range(len(self.cache), index + 1): + # Use xreplace over subs for performance. + # See issue #10697. + seq_index = self.start + current + current_recurrence = self._recurrence.xreplace({self.n: seq_index}) + new_term = current_recurrence.xreplace(self.cache) + + self.cache[self.y(seq_index)] = new_term + + return self.cache[self.y(self.start + current)] + + def __iter__(self): + index = self.start + while True: + yield self._eval_coeff(index) + index += 1 + + +def sequence(seq, limits=None): + """ + Returns appropriate sequence object. + + Explanation + =========== + + If ``seq`` is a SymPy sequence, returns :class:`SeqPer` object + otherwise returns :class:`SeqFormula` object. + + Examples + ======== + + >>> from sympy import sequence + >>> from sympy.abc import n + >>> sequence(n**2, (n, 0, 5)) + SeqFormula(n**2, (n, 0, 5)) + >>> sequence((1, 2, 3), (n, 0, 5)) + SeqPer((1, 2, 3), (n, 0, 5)) + + See Also + ======== + + sympy.series.sequences.SeqPer + sympy.series.sequences.SeqFormula + """ + seq = sympify(seq) + + if is_sequence(seq, Tuple): + return SeqPer(seq, limits) + else: + return SeqFormula(seq, limits) + + +############################################################################### +# OPERATIONS # +############################################################################### + + +class SeqExprOp(SeqBase): + """ + Base class for operations on sequences. + + Examples + ======== + + >>> from sympy.series.sequences import SeqExprOp, sequence + >>> from sympy.abc import n + >>> s1 = sequence(n**2, (n, 0, 10)) + >>> s2 = sequence((1, 2, 3), (n, 5, 10)) + >>> s = SeqExprOp(s1, s2) + >>> s.gen + (n**2, (1, 2, 3)) + >>> s.interval + Interval(5, 10) + >>> s.length + 6 + + See Also + ======== + + sympy.series.sequences.SeqAdd + sympy.series.sequences.SeqMul + """ + @property + def gen(self): + """Generator for the sequence. + + returns a tuple of generators of all the argument sequences. + """ + return tuple(a.gen for a in self.args) + + @property + def interval(self): + """Sequence is defined on the intersection + of all the intervals of respective sequences + """ + return Intersection(*(a.interval for a in self.args)) + + @property + def start(self): + return self.interval.inf + + @property + def stop(self): + return self.interval.sup + + @property + def variables(self): + """Cumulative of all the bound variables""" + return tuple(flatten([a.variables for a in self.args])) + + @property + def length(self): + return self.stop - self.start + 1 + + +class SeqAdd(SeqExprOp): + """Represents term-wise addition of sequences. + + Rules: + * The interval on which sequence is defined is the intersection + of respective intervals of sequences. + * Anything + :class:`EmptySequence` remains unchanged. + * Other rules are defined in ``_add`` methods of sequence classes. + + Examples + ======== + + >>> from sympy import EmptySequence, oo, SeqAdd, SeqPer, SeqFormula + >>> from sympy.abc import n + >>> SeqAdd(SeqPer((1, 2), (n, 0, oo)), EmptySequence) + SeqPer((1, 2), (n, 0, oo)) + >>> SeqAdd(SeqPer((1, 2), (n, 0, 5)), SeqPer((1, 2), (n, 6, 10))) + EmptySequence + >>> SeqAdd(SeqPer((1, 2), (n, 0, oo)), SeqFormula(n**2, (n, 0, oo))) + SeqAdd(SeqFormula(n**2, (n, 0, oo)), SeqPer((1, 2), (n, 0, oo))) + >>> SeqAdd(SeqFormula(n**3), SeqFormula(n**2)) + SeqFormula(n**3 + n**2, (n, 0, oo)) + + See Also + ======== + + sympy.series.sequences.SeqMul + """ + + def __new__(cls, *args, **kwargs): + evaluate = kwargs.get('evaluate', global_parameters.evaluate) + + # flatten inputs + args = list(args) + + # adapted from sympy.sets.sets.Union + def _flatten(arg): + if isinstance(arg, SeqBase): + if isinstance(arg, SeqAdd): + return sum(map(_flatten, arg.args), []) + else: + return [arg] + if iterable(arg): + return sum(map(_flatten, arg), []) + raise TypeError("Input must be Sequences or " + " iterables of Sequences") + args = _flatten(args) + + args = [a for a in args if a is not S.EmptySequence] + + # Addition of no sequences is EmptySequence + if not args: + return S.EmptySequence + + if Intersection(*(a.interval for a in args)) is S.EmptySet: + return S.EmptySequence + + # reduce using known rules + if evaluate: + return SeqAdd.reduce(args) + + args = list(ordered(args, SeqBase._start_key)) + + return Basic.__new__(cls, *args) + + @staticmethod + def reduce(args): + """Simplify :class:`SeqAdd` using known rules. + + Iterates through all pairs and ask the constituent + sequences if they can simplify themselves with any other constituent. + + Notes + ===== + + adapted from ``Union.reduce`` + + """ + new_args = True + while new_args: + for id1, s in enumerate(args): + new_args = False + for id2, t in enumerate(args): + if id1 == id2: + continue + new_seq = s._add(t) + # This returns None if s does not know how to add + # with t. Returns the newly added sequence otherwise + if new_seq is not None: + new_args = [a for a in args if a not in (s, t)] + new_args.append(new_seq) + break + if new_args: + args = new_args + break + + if len(args) == 1: + return args.pop() + else: + return SeqAdd(args, evaluate=False) + + def _eval_coeff(self, pt): + """adds up the coefficients of all the sequences at point pt""" + return sum(a.coeff(pt) for a in self.args) + + +class SeqMul(SeqExprOp): + r"""Represents term-wise multiplication of sequences. + + Explanation + =========== + + Handles multiplication of sequences only. For multiplication + with other objects see :func:`SeqBase.coeff_mul`. + + Rules: + * The interval on which sequence is defined is the intersection + of respective intervals of sequences. + * Anything \* :class:`EmptySequence` returns :class:`EmptySequence`. + * Other rules are defined in ``_mul`` methods of sequence classes. + + Examples + ======== + + >>> from sympy import EmptySequence, oo, SeqMul, SeqPer, SeqFormula + >>> from sympy.abc import n + >>> SeqMul(SeqPer((1, 2), (n, 0, oo)), EmptySequence) + EmptySequence + >>> SeqMul(SeqPer((1, 2), (n, 0, 5)), SeqPer((1, 2), (n, 6, 10))) + EmptySequence + >>> SeqMul(SeqPer((1, 2), (n, 0, oo)), SeqFormula(n**2)) + SeqMul(SeqFormula(n**2, (n, 0, oo)), SeqPer((1, 2), (n, 0, oo))) + >>> SeqMul(SeqFormula(n**3), SeqFormula(n**2)) + SeqFormula(n**5, (n, 0, oo)) + + See Also + ======== + + sympy.series.sequences.SeqAdd + """ + + def __new__(cls, *args, **kwargs): + evaluate = kwargs.get('evaluate', global_parameters.evaluate) + + # flatten inputs + args = list(args) + + # adapted from sympy.sets.sets.Union + def _flatten(arg): + if isinstance(arg, SeqBase): + if isinstance(arg, SeqMul): + return sum(map(_flatten, arg.args), []) + else: + return [arg] + elif iterable(arg): + return sum(map(_flatten, arg), []) + raise TypeError("Input must be Sequences or " + " iterables of Sequences") + args = _flatten(args) + + # Multiplication of no sequences is EmptySequence + if not args: + return S.EmptySequence + + if Intersection(*(a.interval for a in args)) is S.EmptySet: + return S.EmptySequence + + # reduce using known rules + if evaluate: + return SeqMul.reduce(args) + + args = list(ordered(args, SeqBase._start_key)) + + return Basic.__new__(cls, *args) + + @staticmethod + def reduce(args): + """Simplify a :class:`SeqMul` using known rules. + + Explanation + =========== + + Iterates through all pairs and ask the constituent + sequences if they can simplify themselves with any other constituent. + + Notes + ===== + + adapted from ``Union.reduce`` + + """ + new_args = True + while new_args: + for id1, s in enumerate(args): + new_args = False + for id2, t in enumerate(args): + if id1 == id2: + continue + new_seq = s._mul(t) + # This returns None if s does not know how to multiply + # with t. Returns the newly multiplied sequence otherwise + if new_seq is not None: + new_args = [a for a in args if a not in (s, t)] + new_args.append(new_seq) + break + if new_args: + args = new_args + break + + if len(args) == 1: + return args.pop() + else: + return SeqMul(args, evaluate=False) + + def _eval_coeff(self, pt): + """multiplies the coefficients of all the sequences at point pt""" + val = 1 + for a in self.args: + val *= a.coeff(pt) + return val diff --git a/venv/lib/python3.10/site-packages/sympy/series/series.py b/venv/lib/python3.10/site-packages/sympy/series/series.py new file mode 100644 index 0000000000000000000000000000000000000000..e9feec7d3b1987bfaa5238969f531e9f98b88b25 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/series.py @@ -0,0 +1,63 @@ +from sympy.core.sympify import sympify + + +def series(expr, x=None, x0=0, n=6, dir="+"): + """Series expansion of expr around point `x = x0`. + + Parameters + ========== + + expr : Expression + The expression whose series is to be expanded. + + x : Symbol + It is the variable of the expression to be calculated. + + x0 : Value + The value around which ``x`` is calculated. Can be any value + from ``-oo`` to ``oo``. + + n : Value + The number of terms upto which the series is to be expanded. + + dir : String, optional + The series-expansion can be bi-directional. If ``dir="+"``, + then (x->x0+). If ``dir="-"``, then (x->x0-). For infinite + ``x0`` (``oo`` or ``-oo``), the ``dir`` argument is determined + from the direction of the infinity (i.e., ``dir="-"`` for + ``oo``). + + Examples + ======== + + >>> from sympy import series, tan, oo + >>> from sympy.abc import x + >>> f = tan(x) + >>> series(f, x, 2, 6, "+") + tan(2) + (1 + tan(2)**2)*(x - 2) + (x - 2)**2*(tan(2)**3 + tan(2)) + + (x - 2)**3*(1/3 + 4*tan(2)**2/3 + tan(2)**4) + (x - 2)**4*(tan(2)**5 + + 5*tan(2)**3/3 + 2*tan(2)/3) + (x - 2)**5*(2/15 + 17*tan(2)**2/15 + + 2*tan(2)**4 + tan(2)**6) + O((x - 2)**6, (x, 2)) + + >>> series(f, x, 2, 3, "-") + tan(2) + (2 - x)*(-tan(2)**2 - 1) + (2 - x)**2*(tan(2)**3 + tan(2)) + + O((x - 2)**3, (x, 2)) + + >>> series(f, x, 2, oo, "+") + Traceback (most recent call last): + ... + TypeError: 'Infinity' object cannot be interpreted as an integer + + Returns + ======= + + Expr + Series expansion of the expression about x0 + + See Also + ======== + + sympy.core.expr.Expr.series: See the docstring of Expr.series() for complete details of this wrapper. + """ + expr = sympify(expr) + return expr.series(x, x0, n, dir) diff --git a/venv/lib/python3.10/site-packages/sympy/series/series_class.py b/venv/lib/python3.10/site-packages/sympy/series/series_class.py new file mode 100644 index 0000000000000000000000000000000000000000..ff04993b266a3cbd3f767042d4325fb11edb2168 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/series_class.py @@ -0,0 +1,99 @@ +""" +Contains the base class for series +Made using sequences in mind +""" + +from sympy.core.expr import Expr +from sympy.core.singleton import S +from sympy.core.cache import cacheit + + +class SeriesBase(Expr): + """Base Class for series""" + + @property + def interval(self): + """The interval on which the series is defined""" + raise NotImplementedError("(%s).interval" % self) + + @property + def start(self): + """The starting point of the series. This point is included""" + raise NotImplementedError("(%s).start" % self) + + @property + def stop(self): + """The ending point of the series. This point is included""" + raise NotImplementedError("(%s).stop" % self) + + @property + def length(self): + """Length of the series expansion""" + raise NotImplementedError("(%s).length" % self) + + @property + def variables(self): + """Returns a tuple of variables that are bounded""" + return () + + @property + def free_symbols(self): + """ + This method returns the symbols in the object, excluding those + that take on a specific value (i.e. the dummy symbols). + """ + return ({j for i in self.args for j in i.free_symbols} + .difference(self.variables)) + + @cacheit + def term(self, pt): + """Term at point pt of a series""" + if pt < self.start or pt > self.stop: + raise IndexError("Index %s out of bounds %s" % (pt, self.interval)) + return self._eval_term(pt) + + def _eval_term(self, pt): + raise NotImplementedError("The _eval_term method should be added to" + "%s to return series term so it is available" + "when 'term' calls it." + % self.func) + + def _ith_point(self, i): + """ + Returns the i'th point of a series + If start point is negative infinity, point is returned from the end. + Assumes the first point to be indexed zero. + + Examples + ======== + + TODO + """ + if self.start is S.NegativeInfinity: + initial = self.stop + step = -1 + else: + initial = self.start + step = 1 + + return initial + i*step + + def __iter__(self): + i = 0 + while i < self.length: + pt = self._ith_point(i) + yield self.term(pt) + i += 1 + + def __getitem__(self, index): + if isinstance(index, int): + index = self._ith_point(index) + return self.term(index) + elif isinstance(index, slice): + start, stop = index.start, index.stop + if start is None: + start = 0 + if stop is None: + stop = self.length + return [self.term(self._ith_point(i)) for i in + range(start, stop, index.step or 1)] diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/series/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34eb809311d8b5d2440f8e1d783e84745a849935 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_approximants.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_approximants.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67d8eac506a9d65d6907168b14bc46951e1266e6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_approximants.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_aseries.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_aseries.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..530d7366a4d3a1d0e79e88fcd02913a5929d0afd Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_aseries.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_demidovich.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_demidovich.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6197369088498b8a35842ae4d2c79c7b65fb9bb3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_demidovich.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_formal.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_formal.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9d443d40b426817471827e0d1065c9f7b68ec4b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_formal.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_fourier.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_fourier.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e6409e50ad92445c244efe685702cb5e1444bcb Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_fourier.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_gruntz.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_gruntz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c129f873b34125c6126ae7fa748bc068326da297 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_gruntz.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_kauers.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_kauers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0be746c9830a98a5b006fd5530a34e7758e4d8c7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_kauers.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_limits.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_limits.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..acb796c7f23d8e23eb52fc8c379cec73c1c94551 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_limits.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_limitseq.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_limitseq.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df7ca148f835d6c303530076d2b0af7535e36e1c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_limitseq.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_lseries.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_lseries.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b85ba0fdbcfd0af4ea1f25cf4ddde3cbdb4c4504 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_lseries.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_nseries.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_nseries.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a87385b05a208a1c3f783e9ded2d5f419f67979 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_nseries.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_order.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_order.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af816d7cd4b7e438b35bc9924148a20aa3c06c21 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_order.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_residues.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_residues.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c8a6739082a2baebdb778b1d810079cebce2bdf Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_residues.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_sequences.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_sequences.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b2316d9d054d33835f4714353cafd1b8016bbc8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_sequences.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_series.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_series.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e5b25c20080e42f734c85fab7b67a9d5814215b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/series/tests/__pycache__/test_series.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_approximants.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_approximants.py new file mode 100644 index 0000000000000000000000000000000000000000..9c03d2ce38add99b0dce8725b6c8d8844b31f76b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_approximants.py @@ -0,0 +1,23 @@ +from sympy.series import approximants +from sympy.core.symbol import symbols +from sympy.functions.combinatorial.factorials import binomial +from sympy.functions.combinatorial.numbers import (fibonacci, lucas) + + +def test_approximants(): + x, t = symbols("x,t") + g = [lucas(k) for k in range(16)] + assert list(approximants(g)) == ( + [2, -4/(x - 2), (5*x - 2)/(3*x - 1), (x - 2)/(x**2 + x - 1)] ) + g = [lucas(k)+fibonacci(k+2) for k in range(16)] + assert list(approximants(g)) == ( + [3, -3/(x - 1), (3*x - 3)/(2*x - 1), -3/(x**2 + x - 1)] ) + g = [lucas(k)**2 for k in range(16)] + assert list(approximants(g)) == ( + [4, -16/(x - 4), (35*x - 4)/(9*x - 1), (37*x - 28)/(13*x**2 + 11*x - 7), + (50*x**2 + 63*x - 52)/(37*x**2 + 19*x - 13), + (-x**2 - 7*x + 4)/(x**3 - 2*x**2 - 2*x + 1)] ) + p = [sum(binomial(k,i)*x**i for i in range(k+1)) for k in range(16)] + y = approximants(p, t, simplify=True) + assert next(y) == 1 + assert next(y) == -1/(t*(x + 1) - 1) diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_aseries.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_aseries.py new file mode 100644 index 0000000000000000000000000000000000000000..cae0ac0a43f2406dd96e45c6a31939ac6b4cdcaa --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_aseries.py @@ -0,0 +1,55 @@ +from sympy.core.function import PoleError +from sympy.core.numbers import oo +from sympy.core.symbol import Symbol +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.series.order import O +from sympy.abc import x + +from sympy.testing.pytest import raises + +def test_simple(): + # Gruntz' theses pp. 91 to 96 + # 6.6 + e = sin(1/x + exp(-x)) - sin(1/x) + assert e.aseries(x) == (1/(24*x**4) - 1/(2*x**2) + 1 + O(x**(-6), (x, oo)))*exp(-x) + + e = exp(x) * (exp(1/x + exp(-x)) - exp(1/x)) + assert e.aseries(x, n=4) == 1/(6*x**3) + 1/(2*x**2) + 1/x + 1 + O(x**(-4), (x, oo)) + + e = exp(exp(x) / (1 - 1/x)) + assert e.aseries(x) == exp(exp(x) / (1 - 1/x)) + + # The implementation of bound in aseries is incorrect currently. This test + # should be commented out when that is fixed. + # assert e.aseries(x, bound=3) == exp(exp(x) / x**2)*exp(exp(x) / x)*exp(-exp(x) + exp(x)/(1 - 1/x) - \ + # exp(x) / x - exp(x) / x**2) * exp(exp(x)) + + e = exp(sin(1/x + exp(-exp(x)))) - exp(sin(1/x)) + assert e.aseries(x, n=4) == (-1/(2*x**3) + 1/x + 1 + O(x**(-4), (x, oo)))*exp(-exp(x)) + + e3 = lambda x:exp(exp(exp(x))) + e = e3(x)/e3(x - 1/e3(x)) + assert e.aseries(x, n=3) == 1 + exp(2*x + 2*exp(x))*exp(-2*exp(exp(x)))/2\ + - exp(2*x + exp(x))*exp(-2*exp(exp(x)))/2 - exp(x + exp(x))*exp(-2*exp(exp(x)))/2\ + + exp(x + exp(x))*exp(-exp(exp(x))) + O(exp(-3*exp(exp(x))), (x, oo)) + + e = exp(exp(x)) * (exp(sin(1/x + 1/exp(exp(x)))) - exp(sin(1/x))) + assert e.aseries(x, n=4) == -1/(2*x**3) + 1/x + 1 + O(x**(-4), (x, oo)) + + n = Symbol('n', integer=True) + e = (sqrt(n)*log(n)**2*exp(sqrt(log(n))*log(log(n))**2*exp(sqrt(log(log(n)))*log(log(log(n)))**3)))/n + assert e.aseries(n) == \ + exp(exp(sqrt(log(log(n)))*log(log(log(n)))**3)*sqrt(log(n))*log(log(n))**2)*log(n)**2/sqrt(n) + + +def test_hierarchical(): + e = sin(1/x + exp(-x)) + assert e.aseries(x, n=3, hir=True) == -exp(-2*x)*sin(1/x)/2 + \ + exp(-x)*cos(1/x) + sin(1/x) + O(exp(-3*x), (x, oo)) + + e = sin(x) * cos(exp(-x)) + assert e.aseries(x, hir=True) == exp(-4*x)*sin(x)/24 - \ + exp(-2*x)*sin(x)/2 + sin(x) + O(exp(-6*x), (x, oo)) + raises(PoleError, lambda: e.aseries(x)) diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_demidovich.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_demidovich.py new file mode 100644 index 0000000000000000000000000000000000000000..98cafbae6f019dd3d97d306099d5780ed2f37f04 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_demidovich.py @@ -0,0 +1,143 @@ +from sympy.core.numbers import (Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import (root, sqrt) +from sympy.functions.elementary.trigonometric import (asin, cos, sin, tan) +from sympy.polys.rationaltools import together +from sympy.series.limits import limit + +# Numbers listed with the tests refer to problem numbers in the book +# "Anti-demidovich, problemas resueltos, Ed. URSS" + +x = Symbol("x") + + +def test_leadterm(): + assert (3 + 2*x**(log(3)/log(2) - 1)).leadterm(x) == (3, 0) + + +def root3(x): + return root(x, 3) + + +def root4(x): + return root(x, 4) + + +def test_Limits_simple_0(): + assert limit((2**(x + 1) + 3**(x + 1))/(2**x + 3**x), x, oo) == 3 # 175 + + +def test_Limits_simple_1(): + assert limit((x + 1)*(x + 2)*(x + 3)/x**3, x, oo) == 1 # 172 + assert limit(sqrt(x + 1) - sqrt(x), x, oo) == 0 # 179 + assert limit((2*x - 3)*(3*x + 5)*(4*x - 6)/(3*x**3 + x - 1), x, oo) == 8 # Primjer 1 + assert limit(x/root3(x**3 + 10), x, oo) == 1 # Primjer 2 + assert limit((x + 1)**2/(x**2 + 1), x, oo) == 1 # 181 + + +def test_Limits_simple_2(): + assert limit(1000*x/(x**2 - 1), x, oo) == 0 # 182 + assert limit((x**2 - 5*x + 1)/(3*x + 7), x, oo) is oo # 183 + assert limit((2*x**2 - x + 3)/(x**3 - 8*x + 5), x, oo) == 0 # 184 + assert limit((2*x**2 - 3*x - 4)/sqrt(x**4 + 1), x, oo) == 2 # 186 + assert limit((2*x + 3)/(x + root3(x)), x, oo) == 2 # 187 + assert limit(x**2/(10 + x*sqrt(x)), x, oo) is oo # 188 + assert limit(root3(x**2 + 1)/(x + 1), x, oo) == 0 # 189 + assert limit(sqrt(x)/sqrt(x + sqrt(x + sqrt(x))), x, oo) == 1 # 190 + + +def test_Limits_simple_3a(): + a = Symbol('a') + #issue 3513 + assert together(limit((x**2 - (a + 1)*x + a)/(x**3 - a**3), x, a)) == \ + (a - 1)/(3*a**2) # 196 + + +def test_Limits_simple_3b(): + h = Symbol("h") + assert limit(((x + h)**3 - x**3)/h, h, 0) == 3*x**2 # 197 + assert limit((1/(1 - x) - 3/(1 - x**3)), x, 1) == -1 # 198 + assert limit((sqrt(1 + x) - 1)/(root3(1 + x) - 1), x, 0) == Rational(3)/2 # Primer 4 + assert limit((sqrt(x) - 1)/(x - 1), x, 1) == Rational(1)/2 # 199 + assert limit((sqrt(x) - 8)/(root3(x) - 4), x, 64) == 3 # 200 + assert limit((root3(x) - 1)/(root4(x) - 1), x, 1) == Rational(4)/3 # 201 + assert limit( + (root3(x**2) - 2*root3(x) + 1)/(x - 1)**2, x, 1) == Rational(1)/9 # 202 + + +def test_Limits_simple_4a(): + a = Symbol('a') + assert limit((sqrt(x) - sqrt(a))/(x - a), x, a) == 1/(2*sqrt(a)) # Primer 5 + assert limit((sqrt(x) - 1)/(root3(x) - 1), x, 1) == Rational(3, 2) # 205 + assert limit((sqrt(1 + x) - sqrt(1 - x))/x, x, 0) == 1 # 207 + assert limit(sqrt(x**2 - 5*x + 6) - x, x, oo) == Rational(-5, 2) # 213 + + +def test_limits_simple_4aa(): + assert limit(x*(sqrt(x**2 + 1) - x), x, oo) == Rational(1)/2 # 214 + + +def test_Limits_simple_4b(): + #issue 3511 + assert limit(x - root3(x**3 - 1), x, oo) == 0 # 215 + + +def test_Limits_simple_4c(): + assert limit(log(1 + exp(x))/x, x, -oo) == 0 # 267a + assert limit(log(1 + exp(x))/x, x, oo) == 1 # 267b + + +def test_bounded(): + assert limit(sin(x)/x, x, oo) == 0 # 216b + assert limit(x*sin(1/x), x, 0) == 0 # 227a + + +def test_f1a(): + #issue 3508: + assert limit((sin(2*x)/x)**(1 + x), x, 0) == 2 # Primer 7 + + +def test_f1a2(): + #issue 3509: + assert limit(((x - 1)/(x + 1))**x, x, oo) == exp(-2) # Primer 9 + + +def test_f1b(): + m = Symbol("m") + n = Symbol("n") + h = Symbol("h") + a = Symbol("a") + assert limit(sin(x)/x, x, 2) == sin(2)/2 # 216a + assert limit(sin(3*x)/x, x, 0) == 3 # 217 + assert limit(sin(5*x)/sin(2*x), x, 0) == Rational(5, 2) # 218 + assert limit(sin(pi*x)/sin(3*pi*x), x, 0) == Rational(1, 3) # 219 + assert limit(x*sin(pi/x), x, oo) == pi # 220 + assert limit((1 - cos(x))/x**2, x, 0) == S.Half # 221 + assert limit(x*sin(1/x), x, oo) == 1 # 227b + assert limit((cos(m*x) - cos(n*x))/x**2, x, 0) == -m**2/2 + n**2/2 # 232 + assert limit((tan(x) - sin(x))/x**3, x, 0) == S.Half # 233 + assert limit((x - sin(2*x))/(x + sin(3*x)), x, 0) == -Rational(1, 4) # 237 + assert limit((1 - sqrt(cos(x)))/x**2, x, 0) == Rational(1, 4) # 239 + assert limit((sqrt(1 + sin(x)) - sqrt(1 - sin(x)))/x, x, 0) == 1 # 240 + + assert limit((1 + h/x)**x, x, oo) == exp(h) # Primer 9 + assert limit((sin(x) - sin(a))/(x - a), x, a) == cos(a) # 222, *176 + assert limit((cos(x) - cos(a))/(x - a), x, a) == -sin(a) # 223 + assert limit((sin(x + h) - sin(x))/h, h, 0) == cos(x) # 225 + + +def test_f2a(): + assert limit(((x + 1)/(2*x + 1))**(x**2), x, oo) == 0 # Primer 8 + + +def test_f2(): + assert limit((sqrt( + cos(x)) - root3(cos(x)))/(sin(x)**2), x, 0) == -Rational(1, 12) # *184 + + +def test_f3(): + a = Symbol('a') + #issue 3504 + assert limit(asin(a*x)/x, x, 0) == a diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_formal.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_formal.py new file mode 100644 index 0000000000000000000000000000000000000000..cac60b12534152a5783bb8f0faab2c06da6691fb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_formal.py @@ -0,0 +1,618 @@ +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.function import (Derivative, Function) +from sympy.core.mul import Mul +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (acosh, asech) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sin) +from sympy.functions.special.bessel import airyai +from sympy.functions.special.error_functions import erf +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import integrate +from sympy.series.formal import fps +from sympy.series.order import O +from sympy.series.formal import (rational_algorithm, FormalPowerSeries, + FormalPowerSeriesProduct, FormalPowerSeriesCompose, + FormalPowerSeriesInverse, simpleDE, + rational_independent, exp_re, hyper_re) +from sympy.testing.pytest import raises, XFAIL, slow + +x, y, z = symbols('x y z') +n, m, k = symbols('n m k', integer=True) +f, r = Function('f'), Function('r') + + +def test_rational_algorithm(): + f = 1 / ((x - 1)**2 * (x - 2)) + assert rational_algorithm(f, x, k) == \ + (-2**(-k - 1) + 1 - (factorial(k + 1) / factorial(k)), 0, 0) + + f = (1 + x + x**2 + x**3) / ((x - 1) * (x - 2)) + assert rational_algorithm(f, x, k) == \ + (-15*2**(-k - 1) + 4, x + 4, 0) + + f = z / (y*m - m*x - y*x + x**2) + assert rational_algorithm(f, x, k) == \ + (((-y**(-k - 1)*z) / (y - m)) + ((m**(-k - 1)*z) / (y - m)), 0, 0) + + f = x / (1 - x - x**2) + assert rational_algorithm(f, x, k) is None + assert rational_algorithm(f, x, k, full=True) == \ + (((Rational(-1, 2) + sqrt(5)/2)**(-k - 1) * + (-sqrt(5)/10 + S.Half)) + + ((-sqrt(5)/2 - S.Half)**(-k - 1) * + (sqrt(5)/10 + S.Half)), 0, 0) + + f = 1 / (x**2 + 2*x + 2) + assert rational_algorithm(f, x, k) is None + assert rational_algorithm(f, x, k, full=True) == \ + ((I*(-1 + I)**(-k - 1)) / 2 - (I*(-1 - I)**(-k - 1)) / 2, 0, 0) + + f = log(1 + x) + assert rational_algorithm(f, x, k) == \ + (-(-1)**(-k) / k, 0, 1) + + f = atan(x) + assert rational_algorithm(f, x, k) is None + assert rational_algorithm(f, x, k, full=True) == \ + (((I*I**(-k)) / 2 - (I*(-I)**(-k)) / 2) / k, 0, 1) + + f = x*atan(x) - log(1 + x**2) / 2 + assert rational_algorithm(f, x, k) is None + assert rational_algorithm(f, x, k, full=True) == \ + (((I*I**(-k + 1)) / 2 - (I*(-I)**(-k + 1)) / 2) / + (k*(k - 1)), 0, 2) + + f = log((1 + x) / (1 - x)) / 2 - atan(x) + assert rational_algorithm(f, x, k) is None + assert rational_algorithm(f, x, k, full=True) == \ + ((-(-1)**(-k) / 2 - (I*I**(-k)) / 2 + (I*(-I)**(-k)) / 2 + + S.Half) / k, 0, 1) + + assert rational_algorithm(cos(x), x, k) is None + + +def test_rational_independent(): + ri = rational_independent + assert ri([], x) == [] + assert ri([cos(x), sin(x)], x) == [cos(x), sin(x)] + assert ri([x**2, sin(x), x*sin(x), x**3], x) == \ + [x**3 + x**2, x*sin(x) + sin(x)] + assert ri([S.One, x*log(x), log(x), sin(x)/x, cos(x), sin(x), x], x) == \ + [x + 1, x*log(x) + log(x), sin(x)/x + sin(x), cos(x)] + + +def test_simpleDE(): + # Tests just the first valid DE + for DE in simpleDE(exp(x), x, f): + assert DE == (-f(x) + Derivative(f(x), x), 1) + break + for DE in simpleDE(sin(x), x, f): + assert DE == (f(x) + Derivative(f(x), x, x), 2) + break + for DE in simpleDE(log(1 + x), x, f): + assert DE == ((x + 1)*Derivative(f(x), x, 2) + Derivative(f(x), x), 2) + break + for DE in simpleDE(asin(x), x, f): + assert DE == (x*Derivative(f(x), x) + (x**2 - 1)*Derivative(f(x), x, x), + 2) + break + for DE in simpleDE(exp(x)*sin(x), x, f): + assert DE == (2*f(x) - 2*Derivative(f(x)) + Derivative(f(x), x, x), 2) + break + for DE in simpleDE(((1 + x)/(1 - x))**n, x, f): + assert DE == (2*n*f(x) + (x**2 - 1)*Derivative(f(x), x), 1) + break + for DE in simpleDE(airyai(x), x, f): + assert DE == (-x*f(x) + Derivative(f(x), x, x), 2) + break + + +def test_exp_re(): + d = -f(x) + Derivative(f(x), x) + assert exp_re(d, r, k) == -r(k) + r(k + 1) + + d = f(x) + Derivative(f(x), x, x) + assert exp_re(d, r, k) == r(k) + r(k + 2) + + d = f(x) + Derivative(f(x), x) + Derivative(f(x), x, x) + assert exp_re(d, r, k) == r(k) + r(k + 1) + r(k + 2) + + d = Derivative(f(x), x) + Derivative(f(x), x, x) + assert exp_re(d, r, k) == r(k) + r(k + 1) + + d = Derivative(f(x), x, 3) + Derivative(f(x), x, 4) + Derivative(f(x)) + assert exp_re(d, r, k) == r(k) + r(k + 2) + r(k + 3) + + +def test_hyper_re(): + d = f(x) + Derivative(f(x), x, x) + assert hyper_re(d, r, k) == r(k) + (k+1)*(k+2)*r(k + 2) + + d = -x*f(x) + Derivative(f(x), x, x) + assert hyper_re(d, r, k) == (k + 2)*(k + 3)*r(k + 3) - r(k) + + d = 2*f(x) - 2*Derivative(f(x), x) + Derivative(f(x), x, x) + assert hyper_re(d, r, k) == \ + (-2*k - 2)*r(k + 1) + (k + 1)*(k + 2)*r(k + 2) + 2*r(k) + + d = 2*n*f(x) + (x**2 - 1)*Derivative(f(x), x) + assert hyper_re(d, r, k) == \ + k*r(k) + 2*n*r(k + 1) + (-k - 2)*r(k + 2) + + d = (x**10 + 4)*Derivative(f(x), x) + x*(x**10 - 1)*Derivative(f(x), x, x) + assert hyper_re(d, r, k) == \ + (k*(k - 1) + k)*r(k) + (4*k - (k + 9)*(k + 10) + 40)*r(k + 10) + + d = ((x**2 - 1)*Derivative(f(x), x, 3) + 3*x*Derivative(f(x), x, x) + + Derivative(f(x), x)) + assert hyper_re(d, r, k) == \ + ((k*(k - 2)*(k - 1) + 3*k*(k - 1) + k)*r(k) + + (-k*(k + 1)*(k + 2))*r(k + 2)) + + +def test_fps(): + assert fps(1) == 1 + assert fps(2, x) == 2 + assert fps(2, x, dir='+') == 2 + assert fps(2, x, dir='-') == 2 + assert fps(1/x + 1/x**2) == 1/x + 1/x**2 + assert fps(log(1 + x), hyper=False, rational=False) == log(1 + x) + + f = fps(x**2 + x + 1) + assert isinstance(f, FormalPowerSeries) + assert f.function == x**2 + x + 1 + assert f[0] == 1 + assert f[2] == x**2 + assert f.truncate(4) == x**2 + x + 1 + O(x**4) + assert f.polynomial() == x**2 + x + 1 + + f = fps(log(1 + x)) + assert isinstance(f, FormalPowerSeries) + assert f.function == log(1 + x) + assert f.subs(x, y) == f + assert f[:5] == [0, x, -x**2/2, x**3/3, -x**4/4] + assert f.as_leading_term(x) == x + assert f.polynomial(6) == x - x**2/2 + x**3/3 - x**4/4 + x**5/5 + + k = f.ak.variables[0] + assert f.infinite == Sum((-(-1)**(-k)*x**k)/k, (k, 1, oo)) + + ft, s = f.truncate(n=None), f[:5] + for i, t in enumerate(ft): + if i == 5: + break + assert s[i] == t + + f = sin(x).fps(x) + assert isinstance(f, FormalPowerSeries) + assert f.truncate() == x - x**3/6 + x**5/120 + O(x**6) + + raises(NotImplementedError, lambda: fps(y*x)) + raises(ValueError, lambda: fps(x, dir=0)) + + +@slow +def test_fps__rational(): + assert fps(1/x) == (1/x) + assert fps((x**2 + x + 1) / x**3, dir=-1) == (x**2 + x + 1) / x**3 + + f = 1 / ((x - 1)**2 * (x - 2)) + assert fps(f, x).truncate() == \ + (Rational(-1, 2) - x*Rational(5, 4) - 17*x**2/8 - 49*x**3/16 - 129*x**4/32 - + 321*x**5/64 + O(x**6)) + + f = (1 + x + x**2 + x**3) / ((x - 1) * (x - 2)) + assert fps(f, x).truncate() == \ + (S.Half + x*Rational(5, 4) + 17*x**2/8 + 49*x**3/16 + 113*x**4/32 + + 241*x**5/64 + O(x**6)) + + f = x / (1 - x - x**2) + assert fps(f, x, full=True).truncate() == \ + x + x**2 + 2*x**3 + 3*x**4 + 5*x**5 + O(x**6) + + f = 1 / (x**2 + 2*x + 2) + assert fps(f, x, full=True).truncate() == \ + S.Half - x/2 + x**2/4 - x**4/8 + x**5/8 + O(x**6) + + f = log(1 + x) + assert fps(f, x).truncate() == \ + x - x**2/2 + x**3/3 - x**4/4 + x**5/5 + O(x**6) + assert fps(f, x, dir=1).truncate() == fps(f, x, dir=-1).truncate() + assert fps(f, x, 2).truncate() == \ + (log(3) - Rational(2, 3) - (x - 2)**2/18 + (x - 2)**3/81 - + (x - 2)**4/324 + (x - 2)**5/1215 + x/3 + O((x - 2)**6, (x, 2))) + assert fps(f, x, 2, dir=-1).truncate() == \ + (log(3) - Rational(2, 3) - (-x + 2)**2/18 - (-x + 2)**3/81 - + (-x + 2)**4/324 - (-x + 2)**5/1215 + x/3 + O((x - 2)**6, (x, 2))) + + f = atan(x) + assert fps(f, x, full=True).truncate() == x - x**3/3 + x**5/5 + O(x**6) + assert fps(f, x, full=True, dir=1).truncate() == \ + fps(f, x, full=True, dir=-1).truncate() + assert fps(f, x, 2, full=True).truncate() == \ + (atan(2) - Rational(2, 5) - 2*(x - 2)**2/25 + 11*(x - 2)**3/375 - + 6*(x - 2)**4/625 + 41*(x - 2)**5/15625 + x/5 + O((x - 2)**6, (x, 2))) + assert fps(f, x, 2, full=True, dir=-1).truncate() == \ + (atan(2) - Rational(2, 5) - 2*(-x + 2)**2/25 - 11*(-x + 2)**3/375 - + 6*(-x + 2)**4/625 - 41*(-x + 2)**5/15625 + x/5 + O((x - 2)**6, (x, 2))) + + f = x*atan(x) - log(1 + x**2) / 2 + assert fps(f, x, full=True).truncate() == x**2/2 - x**4/12 + O(x**6) + + f = log((1 + x) / (1 - x)) / 2 - atan(x) + assert fps(f, x, full=True).truncate(n=10) == 2*x**3/3 + 2*x**7/7 + O(x**10) + + +@slow +def test_fps__hyper(): + f = sin(x) + assert fps(f, x).truncate() == x - x**3/6 + x**5/120 + O(x**6) + + f = cos(x) + assert fps(f, x).truncate() == 1 - x**2/2 + x**4/24 + O(x**6) + + f = exp(x) + assert fps(f, x).truncate() == \ + 1 + x + x**2/2 + x**3/6 + x**4/24 + x**5/120 + O(x**6) + + f = atan(x) + assert fps(f, x).truncate() == x - x**3/3 + x**5/5 + O(x**6) + + f = exp(acos(x)) + assert fps(f, x).truncate() == \ + (exp(pi/2) - x*exp(pi/2) + x**2*exp(pi/2)/2 - x**3*exp(pi/2)/3 + + 5*x**4*exp(pi/2)/24 - x**5*exp(pi/2)/6 + O(x**6)) + + f = exp(acosh(x)) + assert fps(f, x).truncate() == I + x - I*x**2/2 - I*x**4/8 + O(x**6) + + f = atan(1/x) + assert fps(f, x).truncate() == pi/2 - x + x**3/3 - x**5/5 + O(x**6) + + f = x*atan(x) - log(1 + x**2) / 2 + assert fps(f, x, rational=False).truncate() == x**2/2 - x**4/12 + O(x**6) + + f = log(1 + x) + assert fps(f, x, rational=False).truncate() == \ + x - x**2/2 + x**3/3 - x**4/4 + x**5/5 + O(x**6) + + f = airyai(x**2) + assert fps(f, x).truncate() == \ + (3**Rational(5, 6)*gamma(Rational(1, 3))/(6*pi) - + 3**Rational(2, 3)*x**2/(3*gamma(Rational(1, 3))) + O(x**6)) + + f = exp(x)*sin(x) + assert fps(f, x).truncate() == x + x**2 + x**3/3 - x**5/30 + O(x**6) + + f = exp(x)*sin(x)/x + assert fps(f, x).truncate() == 1 + x + x**2/3 - x**4/30 - x**5/90 + O(x**6) + + f = sin(x) * cos(x) + assert fps(f, x).truncate() == x - 2*x**3/3 + 2*x**5/15 + O(x**6) + + +def test_fps_shift(): + f = x**-5*sin(x) + assert fps(f, x).truncate() == \ + 1/x**4 - 1/(6*x**2) + Rational(1, 120) - x**2/5040 + x**4/362880 + O(x**6) + + f = x**2*atan(x) + assert fps(f, x, rational=False).truncate() == \ + x**3 - x**5/3 + O(x**6) + + f = cos(sqrt(x))*x + assert fps(f, x).truncate() == \ + x - x**2/2 + x**3/24 - x**4/720 + x**5/40320 + O(x**6) + + f = x**2*cos(sqrt(x)) + assert fps(f, x).truncate() == \ + x**2 - x**3/2 + x**4/24 - x**5/720 + O(x**6) + + +def test_fps__Add_expr(): + f = x*atan(x) - log(1 + x**2) / 2 + assert fps(f, x).truncate() == x**2/2 - x**4/12 + O(x**6) + + f = sin(x) + cos(x) - exp(x) + log(1 + x) + assert fps(f, x).truncate() == x - 3*x**2/2 - x**4/4 + x**5/5 + O(x**6) + + f = 1/x + sin(x) + assert fps(f, x).truncate() == 1/x + x - x**3/6 + x**5/120 + O(x**6) + + f = sin(x) - cos(x) + 1/(x - 1) + assert fps(f, x).truncate() == \ + -2 - x**2/2 - 7*x**3/6 - 25*x**4/24 - 119*x**5/120 + O(x**6) + + +def test_fps__asymptotic(): + f = exp(x) + assert fps(f, x, oo) == f + assert fps(f, x, -oo).truncate() == O(1/x**6, (x, oo)) + + f = erf(x) + assert fps(f, x, oo).truncate() == 1 + O(1/x**6, (x, oo)) + assert fps(f, x, -oo).truncate() == -1 + O(1/x**6, (x, oo)) + + f = atan(x) + assert fps(f, x, oo, full=True).truncate() == \ + -1/(5*x**5) + 1/(3*x**3) - 1/x + pi/2 + O(1/x**6, (x, oo)) + assert fps(f, x, -oo, full=True).truncate() == \ + -1/(5*x**5) + 1/(3*x**3) - 1/x - pi/2 + O(1/x**6, (x, oo)) + + f = log(1 + x) + assert fps(f, x, oo) != \ + (-1/(5*x**5) - 1/(4*x**4) + 1/(3*x**3) - 1/(2*x**2) + 1/x - log(1/x) + + O(1/x**6, (x, oo))) + assert fps(f, x, -oo) != \ + (-1/(5*x**5) - 1/(4*x**4) + 1/(3*x**3) - 1/(2*x**2) + 1/x + I*pi - + log(-1/x) + O(1/x**6, (x, oo))) + + +def test_fps__fractional(): + f = sin(sqrt(x)) / x + assert fps(f, x).truncate() == \ + (1/sqrt(x) - sqrt(x)/6 + x**Rational(3, 2)/120 - + x**Rational(5, 2)/5040 + x**Rational(7, 2)/362880 - + x**Rational(9, 2)/39916800 + x**Rational(11, 2)/6227020800 + O(x**6)) + + f = sin(sqrt(x)) * x + assert fps(f, x).truncate() == \ + (x**Rational(3, 2) - x**Rational(5, 2)/6 + x**Rational(7, 2)/120 - + x**Rational(9, 2)/5040 + x**Rational(11, 2)/362880 + O(x**6)) + + f = atan(sqrt(x)) / x**2 + assert fps(f, x).truncate() == \ + (x**Rational(-3, 2) - x**Rational(-1, 2)/3 + x**S.Half/5 - + x**Rational(3, 2)/7 + x**Rational(5, 2)/9 - x**Rational(7, 2)/11 + + x**Rational(9, 2)/13 - x**Rational(11, 2)/15 + O(x**6)) + + f = exp(sqrt(x)) + assert fps(f, x).truncate().expand() == \ + (1 + x/2 + x**2/24 + x**3/720 + x**4/40320 + x**5/3628800 + sqrt(x) + + x**Rational(3, 2)/6 + x**Rational(5, 2)/120 + x**Rational(7, 2)/5040 + + x**Rational(9, 2)/362880 + x**Rational(11, 2)/39916800 + O(x**6)) + + f = exp(sqrt(x))*x + assert fps(f, x).truncate().expand() == \ + (x + x**2/2 + x**3/24 + x**4/720 + x**5/40320 + x**Rational(3, 2) + + x**Rational(5, 2)/6 + x**Rational(7, 2)/120 + x**Rational(9, 2)/5040 + + x**Rational(11, 2)/362880 + O(x**6)) + + +def test_fps__logarithmic_singularity(): + f = log(1 + 1/x) + assert fps(f, x) != \ + -log(x) + x - x**2/2 + x**3/3 - x**4/4 + x**5/5 + O(x**6) + assert fps(f, x, rational=False) != \ + -log(x) + x - x**2/2 + x**3/3 - x**4/4 + x**5/5 + O(x**6) + + +@XFAIL +def test_fps__logarithmic_singularity_fail(): + f = asech(x) # Algorithms for computing limits probably needs improvements + assert fps(f, x) == log(2) - log(x) - x**2/4 - 3*x**4/64 + O(x**6) + + +def test_fps_symbolic(): + f = x**n*sin(x**2) + assert fps(f, x).truncate(8) == x**(n + 2) - x**(n + 6)/6 + O(x**(n + 8), x) + + f = x**n*log(1 + x) + fp = fps(f, x) + k = fp.ak.variables[0] + assert fp.infinite == \ + Sum((-(-1)**(-k)*x**(k + n))/k, (k, 1, oo)) + + f = (x - 2)**n*log(1 + x) + assert fps(f, x, 2).truncate() == \ + ((x - 2)**n*log(3) + (x - 2)**(n + 1)/3 - (x - 2)**(n + 2)/18 + (x - 2)**(n + 3)/81 - + (x - 2)**(n + 4)/324 + (x - 2)**(n + 5)/1215 + O((x - 2)**(n + 6), (x, 2))) + + f = x**(n - 2)*cos(x) + assert fps(f, x).truncate() == \ + (x**(n - 2) - x**n/2 + x**(n + 2)/24 + O(x**(n + 4), x)) + + f = x**(n - 2)*sin(x) + x**n*exp(x) + assert fps(f, x).truncate() == \ + (x**(n - 1) + x**(n + 1) + x**(n + 2)/2 + x**n + + x**(n + 4)/24 + x**(n + 5)/60 + O(x**(n + 6), x)) + + f = x**n*atan(x) + assert fps(f, x, oo).truncate() == \ + (-x**(n - 5)/5 + x**(n - 3)/3 + x**n*(pi/2 - 1/x) + + O((1/x)**(-n)/x**6, (x, oo))) + + f = x**(n/2)*cos(x) + assert fps(f, x).truncate() == \ + x**(n/2) - x**(n/2 + 2)/2 + x**(n/2 + 4)/24 + O(x**(n/2 + 6), x) + + f = x**(n + m)*sin(x) + assert fps(f, x).truncate() == \ + x**(m + n + 1) - x**(m + n + 3)/6 + x**(m + n + 5)/120 + O(x**(m + n + 6), x) + + +def test_fps__slow(): + f = x*exp(x)*sin(2*x) # TODO: rsolve needs improvement + assert fps(f, x).truncate() == 2*x**2 + 2*x**3 - x**4/3 - x**5 + O(x**6) + + +def test_fps__operations(): + f1, f2 = fps(sin(x)), fps(cos(x)) + + fsum = f1 + f2 + assert fsum.function == sin(x) + cos(x) + assert fsum.truncate() == \ + 1 + x - x**2/2 - x**3/6 + x**4/24 + x**5/120 + O(x**6) + + fsum = f1 + 1 + assert fsum.function == sin(x) + 1 + assert fsum.truncate() == 1 + x - x**3/6 + x**5/120 + O(x**6) + + fsum = 1 + f2 + assert fsum.function == cos(x) + 1 + assert fsum.truncate() == 2 - x**2/2 + x**4/24 + O(x**6) + + assert (f1 + x) == Add(f1, x) + + assert -f2.truncate() == -1 + x**2/2 - x**4/24 + O(x**6) + assert (f1 - f1) is S.Zero + + fsub = f1 - f2 + assert fsub.function == sin(x) - cos(x) + assert fsub.truncate() == \ + -1 + x + x**2/2 - x**3/6 - x**4/24 + x**5/120 + O(x**6) + + fsub = f1 - 1 + assert fsub.function == sin(x) - 1 + assert fsub.truncate() == -1 + x - x**3/6 + x**5/120 + O(x**6) + + fsub = 1 - f2 + assert fsub.function == -cos(x) + 1 + assert fsub.truncate() == x**2/2 - x**4/24 + O(x**6) + + raises(ValueError, lambda: f1 + fps(exp(x), dir=-1)) + raises(ValueError, lambda: f1 + fps(exp(x), x0=1)) + + fm = f1 * 3 + + assert fm.function == 3*sin(x) + assert fm.truncate() == 3*x - x**3/2 + x**5/40 + O(x**6) + + fm = 3 * f2 + + assert fm.function == 3*cos(x) + assert fm.truncate() == 3 - 3*x**2/2 + x**4/8 + O(x**6) + + assert (f1 * f2) == Mul(f1, f2) + assert (f1 * x) == Mul(f1, x) + + fd = f1.diff() + assert fd.function == cos(x) + assert fd.truncate() == 1 - x**2/2 + x**4/24 + O(x**6) + + fd = f2.diff() + assert fd.function == -sin(x) + assert fd.truncate() == -x + x**3/6 - x**5/120 + O(x**6) + + fd = f2.diff().diff() + assert fd.function == -cos(x) + assert fd.truncate() == -1 + x**2/2 - x**4/24 + O(x**6) + + f3 = fps(exp(sqrt(x))) + fd = f3.diff() + assert fd.truncate().expand() == \ + (1/(2*sqrt(x)) + S.Half + x/12 + x**2/240 + x**3/10080 + x**4/725760 + + x**5/79833600 + sqrt(x)/4 + x**Rational(3, 2)/48 + x**Rational(5, 2)/1440 + + x**Rational(7, 2)/80640 + x**Rational(9, 2)/7257600 + x**Rational(11, 2)/958003200 + + O(x**6)) + + assert f1.integrate((x, 0, 1)) == -cos(1) + 1 + assert integrate(f1, (x, 0, 1)) == -cos(1) + 1 + + fi = integrate(f1, x) + assert fi.function == -cos(x) + assert fi.truncate() == -1 + x**2/2 - x**4/24 + O(x**6) + + fi = f2.integrate(x) + assert fi.function == sin(x) + assert fi.truncate() == x - x**3/6 + x**5/120 + O(x**6) + +def test_fps__product(): + f1, f2, f3 = fps(sin(x)), fps(exp(x)), fps(cos(x)) + + raises(ValueError, lambda: f1.product(exp(x), x)) + raises(ValueError, lambda: f1.product(fps(exp(x), dir=-1), x, 4)) + raises(ValueError, lambda: f1.product(fps(exp(x), x0=1), x, 4)) + raises(ValueError, lambda: f1.product(fps(exp(y)), x, 4)) + + fprod = f1.product(f2, x) + assert isinstance(fprod, FormalPowerSeriesProduct) + assert isinstance(fprod.ffps, FormalPowerSeries) + assert isinstance(fprod.gfps, FormalPowerSeries) + assert fprod.f == sin(x) + assert fprod.g == exp(x) + assert fprod.function == sin(x) * exp(x) + assert fprod._eval_terms(4) == x + x**2 + x**3/3 + assert fprod.truncate(4) == x + x**2 + x**3/3 + O(x**4) + assert fprod.polynomial(4) == x + x**2 + x**3/3 + + raises(NotImplementedError, lambda: fprod._eval_term(5)) + raises(NotImplementedError, lambda: fprod.infinite) + raises(NotImplementedError, lambda: fprod._eval_derivative(x)) + raises(NotImplementedError, lambda: fprod.integrate(x)) + + assert f1.product(f3, x)._eval_terms(4) == x - 2*x**3/3 + assert f1.product(f3, x).truncate(4) == x - 2*x**3/3 + O(x**4) + + +def test_fps__compose(): + f1, f2, f3 = fps(exp(x)), fps(sin(x)), fps(cos(x)) + + raises(ValueError, lambda: f1.compose(sin(x), x)) + raises(ValueError, lambda: f1.compose(fps(sin(x), dir=-1), x, 4)) + raises(ValueError, lambda: f1.compose(fps(sin(x), x0=1), x, 4)) + raises(ValueError, lambda: f1.compose(fps(sin(y)), x, 4)) + + raises(ValueError, lambda: f1.compose(f3, x)) + raises(ValueError, lambda: f2.compose(f3, x)) + + fcomp = f1.compose(f2, x) + assert isinstance(fcomp, FormalPowerSeriesCompose) + assert isinstance(fcomp.ffps, FormalPowerSeries) + assert isinstance(fcomp.gfps, FormalPowerSeries) + assert fcomp.f == exp(x) + assert fcomp.g == sin(x) + assert fcomp.function == exp(sin(x)) + assert fcomp._eval_terms(6) == 1 + x + x**2/2 - x**4/8 - x**5/15 + assert fcomp.truncate() == 1 + x + x**2/2 - x**4/8 - x**5/15 + O(x**6) + assert fcomp.truncate(5) == 1 + x + x**2/2 - x**4/8 + O(x**5) + + raises(NotImplementedError, lambda: fcomp._eval_term(5)) + raises(NotImplementedError, lambda: fcomp.infinite) + raises(NotImplementedError, lambda: fcomp._eval_derivative(x)) + raises(NotImplementedError, lambda: fcomp.integrate(x)) + + assert f1.compose(f2, x).truncate(4) == 1 + x + x**2/2 + O(x**4) + assert f1.compose(f2, x).truncate(8) == \ + 1 + x + x**2/2 - x**4/8 - x**5/15 - x**6/240 + x**7/90 + O(x**8) + assert f1.compose(f2, x).truncate(6) == \ + 1 + x + x**2/2 - x**4/8 - x**5/15 + O(x**6) + + assert f2.compose(f2, x).truncate(4) == x - x**3/3 + O(x**4) + assert f2.compose(f2, x).truncate(8) == x - x**3/3 + x**5/10 - 8*x**7/315 + O(x**8) + assert f2.compose(f2, x).truncate(6) == x - x**3/3 + x**5/10 + O(x**6) + + +def test_fps__inverse(): + f1, f2, f3 = fps(sin(x)), fps(exp(x)), fps(cos(x)) + + raises(ValueError, lambda: f1.inverse(x)) + + finv = f2.inverse(x) + assert isinstance(finv, FormalPowerSeriesInverse) + assert isinstance(finv.ffps, FormalPowerSeries) + raises(ValueError, lambda: finv.gfps) + + assert finv.f == exp(x) + assert finv.function == exp(-x) + assert finv._eval_terms(5) == 1 - x + x**2/2 - x**3/6 + x**4/24 + assert finv.truncate() == 1 - x + x**2/2 - x**3/6 + x**4/24 - x**5/120 + O(x**6) + assert finv.truncate(5) == 1 - x + x**2/2 - x**3/6 + x**4/24 + O(x**5) + + raises(NotImplementedError, lambda: finv._eval_term(5)) + raises(ValueError, lambda: finv.g) + raises(NotImplementedError, lambda: finv.infinite) + raises(NotImplementedError, lambda: finv._eval_derivative(x)) + raises(NotImplementedError, lambda: finv.integrate(x)) + + assert f2.inverse(x).truncate(8) == \ + 1 - x + x**2/2 - x**3/6 + x**4/24 - x**5/120 + x**6/720 - x**7/5040 + O(x**8) + + assert f3.inverse(x).truncate() == 1 + x**2/2 + 5*x**4/24 + O(x**6) + assert f3.inverse(x).truncate(8) == 1 + x**2/2 + 5*x**4/24 + 61*x**6/720 + O(x**8) diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_fourier.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_fourier.py new file mode 100644 index 0000000000000000000000000000000000000000..994f182088b09b038e0e1b3885fec1c27f69f2b0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_fourier.py @@ -0,0 +1,165 @@ +from sympy.core.add import Add +from sympy.core.numbers import (Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (cos, sin, sinc, tan) +from sympy.series.fourier import fourier_series +from sympy.series.fourier import FourierSeries +from sympy.testing.pytest import raises +from functools import lru_cache + +x, y, z = symbols('x y z') + +# Don't declare these during import because they are slow +@lru_cache() +def _get_examples(): + fo = fourier_series(x, (x, -pi, pi)) + fe = fourier_series(x**2, (-pi, pi)) + fp = fourier_series(Piecewise((0, x < 0), (pi, True)), (x, -pi, pi)) + return fo, fe, fp + + +def test_FourierSeries(): + fo, fe, fp = _get_examples() + + assert fourier_series(1, (-pi, pi)) == 1 + assert (Piecewise((0, x < 0), (pi, True)). + fourier_series((x, -pi, pi)).truncate()) == fp.truncate() + assert isinstance(fo, FourierSeries) + assert fo.function == x + assert fo.x == x + assert fo.period == (-pi, pi) + + assert fo.term(3) == 2*sin(3*x) / 3 + assert fe.term(3) == -4*cos(3*x) / 9 + assert fp.term(3) == 2*sin(3*x) / 3 + + assert fo.as_leading_term(x) == 2*sin(x) + assert fe.as_leading_term(x) == pi**2 / 3 + assert fp.as_leading_term(x) == pi / 2 + + assert fo.truncate() == 2*sin(x) - sin(2*x) + (2*sin(3*x) / 3) + assert fe.truncate() == -4*cos(x) + cos(2*x) + pi**2 / 3 + assert fp.truncate() == 2*sin(x) + (2*sin(3*x) / 3) + pi / 2 + + fot = fo.truncate(n=None) + s = [0, 2*sin(x), -sin(2*x)] + for i, t in enumerate(fot): + if i == 3: + break + assert s[i] == t + + def _check_iter(f, i): + for ind, t in enumerate(f): + assert t == f[ind] # noqa: PLR1736 + if ind == i: + break + + _check_iter(fo, 3) + _check_iter(fe, 3) + _check_iter(fp, 3) + + assert fo.subs(x, x**2) == fo + + raises(ValueError, lambda: fourier_series(x, (0, 1, 2))) + raises(ValueError, lambda: fourier_series(x, (x, 0, oo))) + raises(ValueError, lambda: fourier_series(x*y, (0, oo))) + + +def test_FourierSeries_2(): + p = Piecewise((0, x < 0), (x, True)) + f = fourier_series(p, (x, -2, 2)) + + assert f.term(3) == (2*sin(3*pi*x / 2) / (3*pi) - + 4*cos(3*pi*x / 2) / (9*pi**2)) + assert f.truncate() == (2*sin(pi*x / 2) / pi - sin(pi*x) / pi - + 4*cos(pi*x / 2) / pi**2 + S.Half) + + +def test_square_wave(): + """Test if fourier_series approximates discontinuous function correctly.""" + square_wave = Piecewise((1, x < pi), (-1, True)) + s = fourier_series(square_wave, (x, 0, 2*pi)) + + assert s.truncate(3) == 4 / pi * sin(x) + 4 / (3 * pi) * sin(3 * x) + \ + 4 / (5 * pi) * sin(5 * x) + assert s.sigma_approximation(4) == 4 / pi * sin(x) * sinc(pi / 4) + \ + 4 / (3 * pi) * sin(3 * x) * sinc(3 * pi / 4) + + +def test_sawtooth_wave(): + s = fourier_series(x, (x, 0, pi)) + assert s.truncate(4) == \ + pi/2 - sin(2*x) - sin(4*x)/2 - sin(6*x)/3 + s = fourier_series(x, (x, 0, 1)) + assert s.truncate(4) == \ + S.Half - sin(2*pi*x)/pi - sin(4*pi*x)/(2*pi) - sin(6*pi*x)/(3*pi) + + +def test_FourierSeries__operations(): + fo, fe, fp = _get_examples() + + fes = fe.scale(-1).shift(pi**2) + assert fes.truncate() == 4*cos(x) - cos(2*x) + 2*pi**2 / 3 + + assert fp.shift(-pi/2).truncate() == (2*sin(x) + (2*sin(3*x) / 3) + + (2*sin(5*x) / 5)) + + fos = fo.scale(3) + assert fos.truncate() == 6*sin(x) - 3*sin(2*x) + 2*sin(3*x) + + fx = fe.scalex(2).shiftx(1) + assert fx.truncate() == -4*cos(2*x + 2) + cos(4*x + 4) + pi**2 / 3 + + fl = fe.scalex(3).shift(-pi).scalex(2).shiftx(1).scale(4) + assert fl.truncate() == (-16*cos(6*x + 6) + 4*cos(12*x + 12) - + 4*pi + 4*pi**2 / 3) + + raises(ValueError, lambda: fo.shift(x)) + raises(ValueError, lambda: fo.shiftx(sin(x))) + raises(ValueError, lambda: fo.scale(x*y)) + raises(ValueError, lambda: fo.scalex(x**2)) + + +def test_FourierSeries__neg(): + fo, fe, fp = _get_examples() + + assert (-fo).truncate() == -2*sin(x) + sin(2*x) - (2*sin(3*x) / 3) + assert (-fe).truncate() == +4*cos(x) - cos(2*x) - pi**2 / 3 + + +def test_FourierSeries__add__sub(): + fo, fe, fp = _get_examples() + + assert fo + fo == fo.scale(2) + assert fo - fo == 0 + assert -fe - fe == fe.scale(-2) + + assert (fo + fe).truncate() == 2*sin(x) - sin(2*x) - 4*cos(x) + cos(2*x) \ + + pi**2 / 3 + assert (fo - fe).truncate() == 2*sin(x) - sin(2*x) + 4*cos(x) - cos(2*x) \ + - pi**2 / 3 + + assert isinstance(fo + 1, Add) + + raises(ValueError, lambda: fo + fourier_series(x, (x, 0, 2))) + + +def test_FourierSeries_finite(): + + assert fourier_series(sin(x)).truncate(1) == sin(x) + # assert type(fourier_series(sin(x)*log(x))).truncate() == FourierSeries + # assert type(fourier_series(sin(x**2+6))).truncate() == FourierSeries + assert fourier_series(sin(x)*log(y)*exp(z),(x,pi,-pi)).truncate() == sin(x)*log(y)*exp(z) + assert fourier_series(sin(x)**6).truncate(oo) == -15*cos(2*x)/32 + 3*cos(4*x)/16 - cos(6*x)/32 \ + + Rational(5, 16) + assert fourier_series(sin(x) ** 6).truncate() == -15 * cos(2 * x) / 32 + 3 * cos(4 * x) / 16 \ + + Rational(5, 16) + assert fourier_series(sin(4*x+3) + cos(3*x+4)).truncate(oo) == -sin(4)*sin(3*x) + sin(4*x)*cos(3) \ + + cos(4)*cos(3*x) + sin(3)*cos(4*x) + assert fourier_series(sin(x)+cos(x)*tan(x)).truncate(oo) == 2*sin(x) + assert fourier_series(cos(pi*x), (x, -1, 1)).truncate(oo) == cos(pi*x) + assert fourier_series(cos(3*pi*x + 4) - sin(4*pi*x)*log(pi*y), (x, -1, 1)).truncate(oo) == -log(pi*y)*sin(4*pi*x)\ + - sin(4)*sin(3*pi*x) + cos(4)*cos(3*pi*x) diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_gruntz.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_gruntz.py new file mode 100644 index 0000000000000000000000000000000000000000..4cae15297048bc52a69a3d9ca57a7614cfcdc61c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_gruntz.py @@ -0,0 +1,490 @@ +from sympy.core import EulerGamma +from sympy.core.function import Function +from sympy.core.numbers import (E, I, Integer, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acot, atan, cos, sin) +from sympy.functions.special.error_functions import (Ei, erf) +from sympy.functions.special.gamma_functions import (digamma, gamma, loggamma) +from sympy.functions.special.zeta_functions import zeta +from sympy.polys.polytools import cancel +from sympy.functions.elementary.hyperbolic import cosh, coth, sinh, tanh +from sympy.series.gruntz import compare, mrv, rewrite, mrv_leadterm, gruntz, \ + sign +from sympy.testing.pytest import XFAIL, raises, skip, slow + +""" +This test suite is testing the limit algorithm using the bottom up approach. +See the documentation in limits2.py. The algorithm itself is highly recursive +by nature, so "compare" is logically the lowest part of the algorithm, yet in +some sense it's the most complex part, because it needs to calculate a limit +to return the result. + +Nevertheless, the rest of the algorithm depends on compare working correctly. +""" + +x = Symbol('x', real=True) +m = Symbol('m', real=True) + + +runslow = False + + +def _sskip(): + if not runslow: + skip("slow") + + +@slow +def test_gruntz_evaluation(): + # Gruntz' thesis pp. 122 to 123 + # 8.1 + assert gruntz(exp(x)*(exp(1/x - exp(-x)) - exp(1/x)), x, oo) == -1 + # 8.2 + assert gruntz(exp(x)*(exp(1/x + exp(-x) + exp(-x**2)) + - exp(1/x - exp(-exp(x)))), x, oo) == 1 + # 8.3 + assert gruntz(exp(exp(x - exp(-x))/(1 - 1/x)) - exp(exp(x)), x, oo) is oo + # 8.5 + assert gruntz(exp(exp(exp(x + exp(-x)))) / exp(exp(exp(x))), x, oo) is oo + # 8.6 + assert gruntz(exp(exp(exp(x))) / exp(exp(exp(x - exp(-exp(x))))), + x, oo) is oo + # 8.7 + assert gruntz(exp(exp(exp(x))) / exp(exp(exp(x - exp(-exp(exp(x)))))), + x, oo) == 1 + # 8.8 + assert gruntz(exp(exp(x)) / exp(exp(x - exp(-exp(exp(x))))), x, oo) == 1 + # 8.9 + assert gruntz(log(x)**2 * exp(sqrt(log(x))*(log(log(x)))**2 + * exp(sqrt(log(log(x))) * (log(log(log(x))))**3)) / sqrt(x), + x, oo) == 0 + # 8.10 + assert gruntz((x*log(x)*(log(x*exp(x) - x**2))**2) + / (log(log(x**2 + 2*exp(exp(3*x**3*log(x)))))), x, oo) == Rational(1, 3) + # 8.11 + assert gruntz((exp(x*exp(-x)/(exp(-x) + exp(-2*x**2/(x + 1)))) - exp(x))/x, + x, oo) == -exp(2) + # 8.12 + assert gruntz((3**x + 5**x)**(1/x), x, oo) == 5 + # 8.13 + assert gruntz(x/log(x**(log(x**(log(2)/log(x))))), x, oo) is oo + # 8.14 + assert gruntz(exp(exp(2*log(x**5 + x)*log(log(x)))) + / exp(exp(10*log(x)*log(log(x)))), x, oo) is oo + # 8.15 + assert gruntz(exp(exp(Rational(5, 2)*x**Rational(-5, 7) + Rational(21, 8)*x**Rational(6, 11) + + 2*x**(-8) + Rational(54, 17)*x**Rational(49, 45)))**8 + / log(log(-log(Rational(4, 3)*x**Rational(-5, 14))))**Rational(7, 6), x, oo) is oo + # 8.16 + assert gruntz((exp(4*x*exp(-x)/(1/exp(x) + 1/exp(2*x**2/(x + 1)))) - exp(x)) + / exp(x)**4, x, oo) == 1 + # 8.17 + assert gruntz(exp(x*exp(-x)/(exp(-x) + exp(-2*x**2/(x + 1))))/exp(x), x, oo) \ + == 1 + # 8.19 + assert gruntz(log(x)*(log(log(x) + log(log(x))) - log(log(x))) + / (log(log(x) + log(log(log(x))))), x, oo) == 1 + # 8.20 + assert gruntz(exp((log(log(x + exp(log(x)*log(log(x)))))) + / (log(log(log(exp(x) + x + log(x)))))), x, oo) == E + # Another + assert gruntz(exp(exp(exp(x + exp(-x)))) / exp(exp(x)), x, oo) is oo + + +def test_gruntz_evaluation_slow(): + _sskip() + # 8.4 + assert gruntz(exp(exp(exp(x)/(1 - 1/x))) + - exp(exp(exp(x)/(1 - 1/x - log(x)**(-log(x))))), x, oo) is -oo + # 8.18 + assert gruntz((exp(exp(-x/(1 + exp(-x))))*exp(-x/(1 + exp(-x/(1 + exp(-x))))) + *exp(exp(-x + exp(-x/(1 + exp(-x)))))) + / (exp(-x/(1 + exp(-x))))**2 - exp(x) + x, x, oo) == 2 + + +@slow +def test_gruntz_eval_special(): + # Gruntz, p. 126 + assert gruntz(exp(x)*(sin(1/x + exp(-x)) - sin(1/x + exp(-x**2))), x, oo) == 1 + assert gruntz((erf(x - exp(-exp(x))) - erf(x)) * exp(exp(x)) * exp(x**2), + x, oo) == -2/sqrt(pi) + assert gruntz(exp(exp(x)) * (exp(sin(1/x + exp(-exp(x)))) - exp(sin(1/x))), + x, oo) == 1 + assert gruntz(exp(x)*(gamma(x + exp(-x)) - gamma(x)), x, oo) is oo + assert gruntz(exp(exp(digamma(digamma(x))))/x, x, oo) == exp(Rational(-1, 2)) + assert gruntz(exp(exp(digamma(log(x))))/x, x, oo) == exp(Rational(-1, 2)) + assert gruntz(digamma(digamma(digamma(x))), x, oo) is oo + assert gruntz(loggamma(loggamma(x)), x, oo) is oo + assert gruntz(((gamma(x + 1/gamma(x)) - gamma(x))/log(x) - cos(1/x)) + * x*log(x), x, oo) == Rational(-1, 2) + assert gruntz(x * (gamma(x - 1/gamma(x)) - gamma(x) + log(x)), x, oo) \ + == S.Half + assert gruntz((gamma(x + 1/gamma(x)) - gamma(x)) / log(x), x, oo) == 1 + + +def test_gruntz_eval_special_slow(): + _sskip() + assert gruntz(gamma(x + 1)/sqrt(2*pi) + - exp(-x)*(x**(x + S.Half) + x**(x - S.Half)/12), x, oo) is oo + assert gruntz(exp(exp(exp(digamma(digamma(digamma(x))))))/x, x, oo) == 0 + + +@XFAIL +def test_grunts_eval_special_slow_sometimes_fail(): + _sskip() + # XXX This sometimes fails!!! + assert gruntz(exp(gamma(x - exp(-x))*exp(1/x)) - exp(gamma(x)), x, oo) is oo + + +def test_gruntz_Ei(): + assert gruntz((Ei(x - exp(-exp(x))) - Ei(x)) *exp(-x)*exp(exp(x))*x, x, oo) == -1 + + +@XFAIL +def test_gruntz_eval_special_fail(): + # TODO zeta function series + assert gruntz( + exp((log(2) + 1)*x) * (zeta(x + exp(-x)) - zeta(x)), x, oo) == -log(2) + + # TODO 8.35 - 8.37 (bessel, max-min) + + +def test_gruntz_hyperbolic(): + assert gruntz(cosh(x), x, oo) is oo + assert gruntz(cosh(x), x, -oo) is oo + assert gruntz(sinh(x), x, oo) is oo + assert gruntz(sinh(x), x, -oo) is -oo + assert gruntz(2*cosh(x)*exp(x), x, oo) is oo + assert gruntz(2*cosh(x)*exp(x), x, -oo) == 1 + assert gruntz(2*sinh(x)*exp(x), x, oo) is oo + assert gruntz(2*sinh(x)*exp(x), x, -oo) == -1 + assert gruntz(tanh(x), x, oo) == 1 + assert gruntz(tanh(x), x, -oo) == -1 + assert gruntz(coth(x), x, oo) == 1 + assert gruntz(coth(x), x, -oo) == -1 + + +def test_compare1(): + assert compare(2, x, x) == "<" + assert compare(x, exp(x), x) == "<" + assert compare(exp(x), exp(x**2), x) == "<" + assert compare(exp(x**2), exp(exp(x)), x) == "<" + assert compare(1, exp(exp(x)), x) == "<" + + assert compare(x, 2, x) == ">" + assert compare(exp(x), x, x) == ">" + assert compare(exp(x**2), exp(x), x) == ">" + assert compare(exp(exp(x)), exp(x**2), x) == ">" + assert compare(exp(exp(x)), 1, x) == ">" + + assert compare(2, 3, x) == "=" + assert compare(3, -5, x) == "=" + assert compare(2, -5, x) == "=" + + assert compare(x, x**2, x) == "=" + assert compare(x**2, x**3, x) == "=" + assert compare(x**3, 1/x, x) == "=" + assert compare(1/x, x**m, x) == "=" + assert compare(x**m, -x, x) == "=" + + assert compare(exp(x), exp(-x), x) == "=" + assert compare(exp(-x), exp(2*x), x) == "=" + assert compare(exp(2*x), exp(x)**2, x) == "=" + assert compare(exp(x)**2, exp(x + exp(-x)), x) == "=" + assert compare(exp(x), exp(x + exp(-x)), x) == "=" + + assert compare(exp(x**2), 1/exp(x**2), x) == "=" + + +def test_compare2(): + assert compare(exp(x), x**5, x) == ">" + assert compare(exp(x**2), exp(x)**2, x) == ">" + assert compare(exp(x), exp(x + exp(-x)), x) == "=" + assert compare(exp(x + exp(-x)), exp(x), x) == "=" + assert compare(exp(x + exp(-x)), exp(-x), x) == "=" + assert compare(exp(-x), x, x) == ">" + assert compare(x, exp(-x), x) == "<" + assert compare(exp(x + 1/x), x, x) == ">" + assert compare(exp(-exp(x)), exp(x), x) == ">" + assert compare(exp(exp(-exp(x)) + x), exp(-exp(x)), x) == "<" + + +def test_compare3(): + assert compare(exp(exp(x)), exp(x + exp(-exp(x))), x) == ">" + + +def test_sign1(): + assert sign(Rational(0), x) == 0 + assert sign(Rational(3), x) == 1 + assert sign(Rational(-5), x) == -1 + assert sign(log(x), x) == 1 + assert sign(exp(-x), x) == 1 + assert sign(exp(x), x) == 1 + assert sign(-exp(x), x) == -1 + assert sign(3 - 1/x, x) == 1 + assert sign(-3 - 1/x, x) == -1 + assert sign(sin(1/x), x) == 1 + assert sign((x**Integer(2)), x) == 1 + assert sign(x**2, x) == 1 + assert sign(x**5, x) == 1 + + +def test_sign2(): + assert sign(x, x) == 1 + assert sign(-x, x) == -1 + y = Symbol("y", positive=True) + assert sign(y, x) == 1 + assert sign(-y, x) == -1 + assert sign(y*x, x) == 1 + assert sign(-y*x, x) == -1 + + +def mmrv(a, b): + return set(mrv(a, b)[0].keys()) + + +def test_mrv1(): + assert mmrv(x, x) == {x} + assert mmrv(x + 1/x, x) == {x} + assert mmrv(x**2, x) == {x} + assert mmrv(log(x), x) == {x} + assert mmrv(exp(x), x) == {exp(x)} + assert mmrv(exp(-x), x) == {exp(-x)} + assert mmrv(exp(x**2), x) == {exp(x**2)} + assert mmrv(-exp(1/x), x) == {x} + assert mmrv(exp(x + 1/x), x) == {exp(x + 1/x)} + + +def test_mrv2a(): + assert mmrv(exp(x + exp(-exp(x))), x) == {exp(-exp(x))} + assert mmrv(exp(x + exp(-x)), x) == {exp(x + exp(-x)), exp(-x)} + assert mmrv(exp(1/x + exp(-x)), x) == {exp(-x)} + +#sometimes infinite recursion due to log(exp(x**2)) not simplifying + + +def test_mrv2b(): + assert mmrv(exp(x + exp(-x**2)), x) == {exp(-x**2)} + +#sometimes infinite recursion due to log(exp(x**2)) not simplifying + + +def test_mrv2c(): + assert mmrv( + exp(-x + 1/x**2) - exp(x + 1/x), x) == {exp(x + 1/x), exp(1/x**2 - x)} + +#sometimes infinite recursion due to log(exp(x**2)) not simplifying + + +def test_mrv3(): + assert mmrv(exp(x**2) + x*exp(x) + log(x)**x/x, x) == {exp(x**2)} + assert mmrv( + exp(x)*(exp(1/x + exp(-x)) - exp(1/x)), x) == {exp(x), exp(-x)} + assert mmrv(log( + x**2 + 2*exp(exp(3*x**3*log(x)))), x) == {exp(exp(3*x**3*log(x)))} + assert mmrv(log(x - log(x))/log(x), x) == {x} + assert mmrv( + (exp(1/x - exp(-x)) - exp(1/x))*exp(x), x) == {exp(x), exp(-x)} + assert mmrv( + 1/exp(-x + exp(-x)) - exp(x), x) == {exp(x), exp(-x), exp(x - exp(-x))} + assert mmrv(log(log(x*exp(x*exp(x)) + 1)), x) == {exp(x*exp(x))} + assert mmrv(exp(exp(log(log(x) + 1/x))), x) == {x} + + +def test_mrv4(): + ln = log + assert mmrv((ln(ln(x) + ln(ln(x))) - ln(ln(x)))/ln(ln(x) + ln(ln(ln(x))))*ln(x), + x) == {x} + assert mmrv(log(log(x*exp(x*exp(x)) + 1)) - exp(exp(log(log(x) + 1/x))), x) == \ + {exp(x*exp(x))} + + +def mrewrite(a, b, c): + return rewrite(a[1], a[0], b, c) + + +def test_rewrite1(): + e = exp(x) + assert mrewrite(mrv(e, x), x, m) == (1/m, -x) + e = exp(x**2) + assert mrewrite(mrv(e, x), x, m) == (1/m, -x**2) + e = exp(x + 1/x) + assert mrewrite(mrv(e, x), x, m) == (1/m, -x - 1/x) + e = 1/exp(-x + exp(-x)) - exp(x) + assert mrewrite(mrv(e, x), x, m) == ((-m*exp(m) + m)*exp(-m)/m**2, -x) + + +def test_rewrite2(): + e = exp(x)*log(log(exp(x))) + assert mmrv(e, x) == {exp(x)} + assert mrewrite(mrv(e, x), x, m) == (1/m*log(x), -x) + +#sometimes infinite recursion due to log(exp(x**2)) not simplifying + + +def test_rewrite3(): + e = exp(-x + 1/x**2) - exp(x + 1/x) + #both of these are correct and should be equivalent: + assert mrewrite(mrv(e, x), x, m) in [(-1/m + m*exp( + (x**2 + x)/x**3), -x - 1/x), ((m**2 - exp((x**2 + x)/x**3))/m, x**(-2) - x)] + + +def test_mrv_leadterm1(): + assert mrv_leadterm(-exp(1/x), x) == (-1, 0) + assert mrv_leadterm(1/exp(-x + exp(-x)) - exp(x), x) == (-1, 0) + assert mrv_leadterm( + (exp(1/x - exp(-x)) - exp(1/x))*exp(x), x) == (-exp(1/x), 0) + + +def test_mrv_leadterm2(): + #Gruntz: p51, 3.25 + assert mrv_leadterm((log(exp(x) + x) - x)/log(exp(x) + log(x))*exp(x), x) == \ + (1, 0) + + +def test_mrv_leadterm3(): + #Gruntz: p56, 3.27 + assert mmrv(exp(-x + exp(-x)*exp(-x*log(x))), x) == {exp(-x - x*log(x))} + assert mrv_leadterm(exp(-x + exp(-x)*exp(-x*log(x))), x) == (exp(-x), 0) + + +def test_limit1(): + assert gruntz(x, x, oo) is oo + assert gruntz(x, x, -oo) is -oo + assert gruntz(-x, x, oo) is -oo + assert gruntz(x**2, x, -oo) is oo + assert gruntz(-x**2, x, oo) is -oo + assert gruntz(x*log(x), x, 0, dir="+") == 0 + assert gruntz(1/x, x, oo) == 0 + assert gruntz(exp(x), x, oo) is oo + assert gruntz(-exp(x), x, oo) is -oo + assert gruntz(exp(x)/x, x, oo) is oo + assert gruntz(1/x - exp(-x), x, oo) == 0 + assert gruntz(x + 1/x, x, oo) is oo + + +def test_limit2(): + assert gruntz(x**x, x, 0, dir="+") == 1 + assert gruntz((exp(x) - 1)/x, x, 0) == 1 + assert gruntz(1 + 1/x, x, oo) == 1 + assert gruntz(-exp(1/x), x, oo) == -1 + assert gruntz(x + exp(-x), x, oo) is oo + assert gruntz(x + exp(-x**2), x, oo) is oo + assert gruntz(x + exp(-exp(x)), x, oo) is oo + assert gruntz(13 + 1/x - exp(-x), x, oo) == 13 + + +def test_limit3(): + a = Symbol('a') + assert gruntz(x - log(1 + exp(x)), x, oo) == 0 + assert gruntz(x - log(a + exp(x)), x, oo) == 0 + assert gruntz(exp(x)/(1 + exp(x)), x, oo) == 1 + assert gruntz(exp(x)/(a + exp(x)), x, oo) == 1 + + +def test_limit4(): + #issue 3463 + assert gruntz((3**x + 5**x)**(1/x), x, oo) == 5 + #issue 3463 + assert gruntz((3**(1/x) + 5**(1/x))**x, x, 0) == 5 + + +@XFAIL +def test_MrvTestCase_page47_ex3_21(): + h = exp(-x/(1 + exp(-x))) + expr = exp(h)*exp(-x/(1 + h))*exp(exp(-x + h))/h**2 - exp(x) + x + assert mmrv(expr, x) == {1/h, exp(-x), exp(x), exp(x - h), exp(x/(1 + h))} + + +def test_gruntz_I(): + y = Symbol("y") + assert gruntz(I*x, x, oo) == I*oo + assert gruntz(y*I*x, x, oo) == y*I*oo + assert gruntz(y*3*I*x, x, oo) == y*I*oo + assert gruntz(y*3*sin(I)*x, x, oo) == y*I*oo + + +def test_issue_4814(): + assert gruntz((x + 1)**(1/log(x + 1)), x, oo) == E + + +def test_intractable(): + assert gruntz(1/gamma(x), x, oo) == 0 + assert gruntz(1/loggamma(x), x, oo) == 0 + assert gruntz(gamma(x)/loggamma(x), x, oo) is oo + assert gruntz(exp(gamma(x))/gamma(x), x, oo) is oo + assert gruntz(gamma(x), x, 3) == 2 + assert gruntz(gamma(Rational(1, 7) + 1/x), x, oo) == gamma(Rational(1, 7)) + assert gruntz(log(x**x)/log(gamma(x)), x, oo) == 1 + assert gruntz(log(gamma(gamma(x)))/exp(x), x, oo) is oo + + +def test_aseries_trig(): + assert cancel(gruntz(1/log(atan(x)), x, oo) + - 1/(log(pi) + log(S.Half))) == 0 + assert gruntz(1/acot(x), x, -oo) is -oo + + +def test_exp_log_series(): + assert gruntz(x/log(log(x*exp(x))), x, oo) is oo + + +def test_issue_3644(): + assert gruntz(((x**7 + x + 1)/(2**x + x**2))**(-1/x), x, oo) == 2 + + +def test_issue_6843(): + n = Symbol('n', integer=True, positive=True) + r = (n + 1)*x**(n + 1)/(x**(n + 1) - 1) - x/(x - 1) + assert gruntz(r, x, 1).simplify() == n/2 + + +def test_issue_4190(): + assert gruntz(x - gamma(1/x), x, oo) == S.EulerGamma + + +@XFAIL +def test_issue_5172(): + n = Symbol('n') + r = Symbol('r', positive=True) + c = Symbol('c') + p = Symbol('p', positive=True) + m = Symbol('m', negative=True) + expr = ((2*n*(n - r + 1)/(n + r*(n - r + 1)))**c + \ + (r - 1)*(n*(n - r + 2)/(n + r*(n - r + 1)))**c - n)/(n**c - n) + expr = expr.subs(c, c + 1) + assert gruntz(expr.subs(c, m), n, oo) == 1 + # fail: + assert gruntz(expr.subs(c, p), n, oo).simplify() == \ + (2**(p + 1) + r - 1)/(r + 1)**(p + 1) + + +def test_issue_4109(): + assert gruntz(1/gamma(x), x, 0) == 0 + assert gruntz(x*gamma(x), x, 0) == 1 + + +def test_issue_6682(): + assert gruntz(exp(2*Ei(-x))/x**2, x, 0) == exp(2*EulerGamma) + + +def test_issue_7096(): + from sympy.functions import sign + assert gruntz(x**-pi, x, 0, dir='-') == oo*sign((-1)**(-pi)) + + +def test_issue_7391_8166(): + f = Function('f') + # limit should depend on the continuity of the expression at the point passed + raises(ValueError, lambda: gruntz(f(x), x, 4)) + raises(ValueError, lambda: gruntz(x*f(x)**2/(x**2 + f(x)**4), x, 0)) + + +def test_issue_24210_25885(): + eq = exp(x)/(1+1/x)**x**2 + ans = sqrt(E) + assert gruntz(eq, x, oo) == ans + assert gruntz(1/eq, x, oo) == 1/ans diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_kauers.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_kauers.py new file mode 100644 index 0000000000000000000000000000000000000000..bfb9044b33416bc38879649b258150ba2906250c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_kauers.py @@ -0,0 +1,23 @@ +from sympy.series.kauers import finite_diff +from sympy.series.kauers import finite_diff_kauers +from sympy.abc import x, y, z, m, n, w +from sympy.core.numbers import pi +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.concrete.summations import Sum + + +def test_finite_diff(): + assert finite_diff(x**2 + 2*x + 1, x) == 2*x + 3 + assert finite_diff(y**3 + 2*y**2 + 3*y + 5, y) == 3*y**2 + 7*y + 6 + assert finite_diff(z**2 - 2*z + 3, z) == 2*z - 1 + assert finite_diff(w**2 + 3*w - 2, w) == 2*w + 4 + assert finite_diff(sin(x), x, pi/6) == -sin(x) + sin(x + pi/6) + assert finite_diff(cos(y), y, pi/3) == -cos(y) + cos(y + pi/3) + assert finite_diff(x**2 - 2*x + 3, x, 2) == 4*x + assert finite_diff(n**2 - 2*n + 3, n, 3) == 6*n + 3 + +def test_finite_diff_kauers(): + assert finite_diff_kauers(Sum(x**2, (x, 1, n))) == (n + 1)**2 + assert finite_diff_kauers(Sum(y, (y, 1, m))) == (m + 1) + assert finite_diff_kauers(Sum((x*y), (x, 1, m), (y, 1, n))) == (m + 1)*(n + 1) + assert finite_diff_kauers(Sum((x*y**2), (x, 1, m), (y, 1, n))) == (n + 1)**2*(m + 1) diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_limits.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_limits.py new file mode 100644 index 0000000000000000000000000000000000000000..aa3ab7683f057424f1c3215a06381d27687710dc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_limits.py @@ -0,0 +1,1440 @@ +from itertools import product + +from sympy.concrete.summations import Sum +from sympy.core.function import (Function, diff) +from sympy.core import EulerGamma, GoldenRatio +from sympy.core.mod import Mod +from sympy.core.numbers import (E, I, Rational, oo, pi, zoo) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.combinatorial.numbers import fibonacci +from sympy.functions.combinatorial.factorials import (binomial, factorial, subfactorial) +from sympy.functions.elementary.complexes import (Abs, re, sign) +from sympy.functions.elementary.exponential import (LambertW, exp, log) +from sympy.functions.elementary.hyperbolic import (atanh, asinh, acosh, acoth, acsch, asech, tanh, sinh) +from sympy.functions.elementary.integers import (ceiling, floor, frac) +from sympy.functions.elementary.miscellaneous import (cbrt, real_root, sqrt) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acos, acot, acsc, asec, asin, + atan, cos, cot, csc, sec, sin, tan) +from sympy.functions.special.bessel import (besseli, bessely, besselj, besselk) +from sympy.functions.special.error_functions import (Ei, erf, erfc, erfi, fresnelc, fresnels) +from sympy.functions.special.gamma_functions import (digamma, gamma, uppergamma) +from sympy.functions.special.hyper import meijerg +from sympy.integrals.integrals import (Integral, integrate) +from sympy.series.limits import (Limit, limit) +from sympy.simplify.simplify import (logcombine, simplify) +from sympy.simplify.hyperexpand import hyperexpand + +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.core.mul import Mul +from sympy.series.limits import heuristics +from sympy.series.order import Order +from sympy.testing.pytest import XFAIL, raises + +from sympy import elliptic_e, elliptic_k + +from sympy.abc import x, y, z, k +n = Symbol('n', integer=True, positive=True) + + +def test_basic1(): + assert limit(x, x, oo) is oo + assert limit(x, x, -oo) is -oo + assert limit(-x, x, oo) is -oo + assert limit(x**2, x, -oo) is oo + assert limit(-x**2, x, oo) is -oo + assert limit(x*log(x), x, 0, dir="+") == 0 + assert limit(1/x, x, oo) == 0 + assert limit(exp(x), x, oo) is oo + assert limit(-exp(x), x, oo) is -oo + assert limit(exp(x)/x, x, oo) is oo + assert limit(1/x - exp(-x), x, oo) == 0 + assert limit(x + 1/x, x, oo) is oo + assert limit(x - x**2, x, oo) is -oo + assert limit((1 + x)**(1 + sqrt(2)), x, 0) == 1 + assert limit((1 + x)**oo, x, 0) == Limit((x + 1)**oo, x, 0) + assert limit((1 + x)**oo, x, 0, dir='-') == Limit((x + 1)**oo, x, 0, dir='-') + assert limit((1 + x + y)**oo, x, 0, dir='-') == Limit((1 + x + y)**oo, x, 0, dir='-') + assert limit(y/x/log(x), x, 0) == -oo*sign(y) + assert limit(cos(x + y)/x, x, 0) == sign(cos(y))*oo + assert limit(gamma(1/x + 3), x, oo) == 2 + assert limit(S.NaN, x, -oo) is S.NaN + assert limit(Order(2)*x, x, S.NaN) is S.NaN + assert limit(1/(x - 1), x, 1, dir="+") is oo + assert limit(1/(x - 1), x, 1, dir="-") is -oo + assert limit(1/(5 - x)**3, x, 5, dir="+") is -oo + assert limit(1/(5 - x)**3, x, 5, dir="-") is oo + assert limit(1/sin(x), x, pi, dir="+") is -oo + assert limit(1/sin(x), x, pi, dir="-") is oo + assert limit(1/cos(x), x, pi/2, dir="+") is -oo + assert limit(1/cos(x), x, pi/2, dir="-") is oo + assert limit(1/tan(x**3), x, (2*pi)**Rational(1, 3), dir="+") is oo + assert limit(1/tan(x**3), x, (2*pi)**Rational(1, 3), dir="-") is -oo + assert limit(1/cot(x)**3, x, (pi*Rational(3, 2)), dir="+") is -oo + assert limit(1/cot(x)**3, x, (pi*Rational(3, 2)), dir="-") is oo + assert limit(tan(x), x, oo) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert limit(cot(x), x, oo) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert limit(sec(x), x, oo) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert limit(csc(x), x, oo) == AccumBounds(S.NegativeInfinity, S.Infinity) + + # test bi-directional limits + assert limit(sin(x)/x, x, 0, dir="+-") == 1 + assert limit(x**2, x, 0, dir="+-") == 0 + assert limit(1/x**2, x, 0, dir="+-") is oo + + # test failing bi-directional limits + assert limit(1/x, x, 0, dir="+-") is zoo + # approaching 0 + # from dir="+" + assert limit(1 + 1/x, x, 0) is oo + # from dir='-' + # Add + assert limit(1 + 1/x, x, 0, dir='-') is -oo + # Pow + assert limit(x**(-2), x, 0, dir='-') is oo + assert limit(x**(-3), x, 0, dir='-') is -oo + assert limit(1/sqrt(x), x, 0, dir='-') == (-oo)*I + assert limit(x**2, x, 0, dir='-') == 0 + assert limit(sqrt(x), x, 0, dir='-') == 0 + assert limit(x**-pi, x, 0, dir='-') == -oo*(-1)**(1 - pi) + assert limit((1 + cos(x))**oo, x, 0) == Limit((cos(x) + 1)**oo, x, 0) + + # test pull request 22491 + assert limit(1/asin(x), x, 0, dir = '+') == oo + assert limit(1/asin(x), x, 0, dir = '-') == -oo + assert limit(1/sinh(x), x, 0, dir = '+') == oo + assert limit(1/sinh(x), x, 0, dir = '-') == -oo + assert limit(log(1/x) + 1/sin(x), x, 0, dir = '+') == oo + assert limit(log(1/x) + 1/x, x, 0, dir = '+') == oo + + +def test_basic2(): + assert limit(x**x, x, 0, dir="+") == 1 + assert limit((exp(x) - 1)/x, x, 0) == 1 + assert limit(1 + 1/x, x, oo) == 1 + assert limit(-exp(1/x), x, oo) == -1 + assert limit(x + exp(-x), x, oo) is oo + assert limit(x + exp(-x**2), x, oo) is oo + assert limit(x + exp(-exp(x)), x, oo) is oo + assert limit(13 + 1/x - exp(-x), x, oo) == 13 + + +def test_basic3(): + assert limit(1/x, x, 0, dir="+") is oo + assert limit(1/x, x, 0, dir="-") is -oo + + +def test_basic4(): + assert limit(2*x + y*x, x, 0) == 0 + assert limit(2*x + y*x, x, 1) == 2 + y + assert limit(2*x**8 + y*x**(-3), x, -2) == 512 - y/8 + assert limit(sqrt(x + 1) - sqrt(x), x, oo) == 0 + assert integrate(1/(x**3 + 1), (x, 0, oo)) == 2*pi*sqrt(3)/9 + + +def test_log(): + # https://github.com/sympy/sympy/issues/21598 + a, b, c = symbols('a b c', positive=True) + A = log(a/b) - (log(a) - log(b)) + assert A.limit(a, oo) == 0 + assert (A * c).limit(a, oo) == 0 + + tau, x = symbols('tau x', positive=True) + # The value of manualintegrate in the issue + expr = tau**2*((tau - 1)*(tau + 1)*log(x + 1)/(tau**2 + 1)**2 + 1/((tau**2\ + + 1)*(x + 1)) - (-2*tau*atan(x/tau) + (tau**2/2 - 1/2)*log(tau**2\ + + x**2))/(tau**2 + 1)**2) + assert limit(expr, x, oo) == pi*tau**3/(tau**2 + 1)**2 + + +def test_piecewise(): + # https://github.com/sympy/sympy/issues/18363 + assert limit((real_root(x - 6, 3) + 2)/(x + 2), x, -2, '+') == Rational(1, 12) + + +def test_piecewise2(): + func1 = 2*sqrt(x)*Piecewise(((4*x - 2)/Abs(sqrt(4 - 4*(2*x - 1)**2)), 4*x - 2\ + >= 0), ((2 - 4*x)/Abs(sqrt(4 - 4*(2*x - 1)**2)), True)) + func2 = Piecewise((x**2/2, x <= 0.5), (x/2 - 0.125, True)) + func3 = Piecewise(((x - 9) / 5, x < -1), ((x - 9) / 5, x > 4), (sqrt(Abs(x - 3)), True)) + assert limit(func1, x, 0) == 1 + assert limit(func2, x, 0) == 0 + assert limit(func3, x, -1) == 2 + + +def test_basic5(): + class my(Function): + @classmethod + def eval(cls, arg): + if arg is S.Infinity: + return S.NaN + assert limit(my(x), x, oo) == Limit(my(x), x, oo) + + +def test_issue_3885(): + assert limit(x*y + x*z, z, 2) == x*y + 2*x + + +def test_Limit(): + assert Limit(sin(x)/x, x, 0) != 1 + assert Limit(sin(x)/x, x, 0).doit() == 1 + assert Limit(x, x, 0, dir='+-').args == (x, x, 0, Symbol('+-')) + + +def test_floor(): + assert limit(floor(x), x, -2, "+") == -2 + assert limit(floor(x), x, -2, "-") == -3 + assert limit(floor(x), x, -1, "+") == -1 + assert limit(floor(x), x, -1, "-") == -2 + assert limit(floor(x), x, 0, "+") == 0 + assert limit(floor(x), x, 0, "-") == -1 + assert limit(floor(x), x, 1, "+") == 1 + assert limit(floor(x), x, 1, "-") == 0 + assert limit(floor(x), x, 2, "+") == 2 + assert limit(floor(x), x, 2, "-") == 1 + assert limit(floor(x), x, 248, "+") == 248 + assert limit(floor(x), x, 248, "-") == 247 + + # https://github.com/sympy/sympy/issues/14478 + assert limit(x*floor(3/x)/2, x, 0, '+') == Rational(3, 2) + assert limit(floor(x + 1/2) - floor(x), x, oo) == AccumBounds(-S.Half, S(3)/2) + + # test issue 9158 + assert limit(floor(atan(x)), x, oo) == 1 + assert limit(floor(atan(x)), x, -oo) == -2 + assert limit(ceiling(atan(x)), x, oo) == 2 + assert limit(ceiling(atan(x)), x, -oo) == -1 + + +def test_floor_requires_robust_assumptions(): + assert limit(floor(sin(x)), x, 0, "+") == 0 + assert limit(floor(sin(x)), x, 0, "-") == -1 + assert limit(floor(cos(x)), x, 0, "+") == 0 + assert limit(floor(cos(x)), x, 0, "-") == 0 + assert limit(floor(5 + sin(x)), x, 0, "+") == 5 + assert limit(floor(5 + sin(x)), x, 0, "-") == 4 + assert limit(floor(5 + cos(x)), x, 0, "+") == 5 + assert limit(floor(5 + cos(x)), x, 0, "-") == 5 + + +def test_ceiling(): + assert limit(ceiling(x), x, -2, "+") == -1 + assert limit(ceiling(x), x, -2, "-") == -2 + assert limit(ceiling(x), x, -1, "+") == 0 + assert limit(ceiling(x), x, -1, "-") == -1 + assert limit(ceiling(x), x, 0, "+") == 1 + assert limit(ceiling(x), x, 0, "-") == 0 + assert limit(ceiling(x), x, 1, "+") == 2 + assert limit(ceiling(x), x, 1, "-") == 1 + assert limit(ceiling(x), x, 2, "+") == 3 + assert limit(ceiling(x), x, 2, "-") == 2 + assert limit(ceiling(x), x, 248, "+") == 249 + assert limit(ceiling(x), x, 248, "-") == 248 + + # https://github.com/sympy/sympy/issues/14478 + assert limit(x*ceiling(3/x)/2, x, 0, '+') == Rational(3, 2) + assert limit(ceiling(x + 1/2) - ceiling(x), x, oo) == AccumBounds(-S.Half, S(3)/2) + + +def test_ceiling_requires_robust_assumptions(): + assert limit(ceiling(sin(x)), x, 0, "+") == 1 + assert limit(ceiling(sin(x)), x, 0, "-") == 0 + assert limit(ceiling(cos(x)), x, 0, "+") == 1 + assert limit(ceiling(cos(x)), x, 0, "-") == 1 + assert limit(ceiling(5 + sin(x)), x, 0, "+") == 6 + assert limit(ceiling(5 + sin(x)), x, 0, "-") == 5 + assert limit(ceiling(5 + cos(x)), x, 0, "+") == 6 + assert limit(ceiling(5 + cos(x)), x, 0, "-") == 6 + + +def test_frac(): + assert limit(frac(x), x, oo) == AccumBounds(0, 1) + assert limit(frac(x)**(1/x), x, oo) == AccumBounds(0, 1) + assert limit(frac(x)**(1/x), x, -oo) == AccumBounds(1, oo) + assert limit(frac(x)**x, x, oo) == AccumBounds(0, oo) # wolfram gives (0, 1) + assert limit(frac(sin(x)), x, 0, "+") == 0 + assert limit(frac(sin(x)), x, 0, "-") == 1 + assert limit(frac(cos(x)), x, 0, "+-") == 1 + assert limit(frac(x**2), x, 0, "+-") == 0 + raises(ValueError, lambda: limit(frac(x), x, 0, '+-')) + assert limit(frac(-2*x + 1), x, 0, "+") == 1 + assert limit(frac(-2*x + 1), x, 0, "-") == 0 + assert limit(frac(x + S.Half), x, 0, "+-") == S(1)/2 + assert limit(frac(1/x), x, 0) == AccumBounds(0, 1) + + +def test_issue_14355(): + assert limit(floor(sin(x)/x), x, 0, '+') == 0 + assert limit(floor(sin(x)/x), x, 0, '-') == 0 + # test comment https://github.com/sympy/sympy/issues/14355#issuecomment-372121314 + assert limit(floor(-tan(x)/x), x, 0, '+') == -2 + assert limit(floor(-tan(x)/x), x, 0, '-') == -2 + + +def test_atan(): + x = Symbol("x", real=True) + assert limit(atan(x)*sin(1/x), x, 0) == 0 + assert limit(atan(x) + sqrt(x + 1) - sqrt(x), x, oo) == pi/2 + + +def test_set_signs(): + assert limit(abs(x), x, 0) == 0 + assert limit(abs(sin(x)), x, 0) == 0 + assert limit(abs(cos(x)), x, 0) == 1 + assert limit(abs(sin(x + 1)), x, 0) == sin(1) + + # https://github.com/sympy/sympy/issues/9449 + assert limit((Abs(x + y) - Abs(x - y))/(2*x), x, 0) == sign(y) + + # https://github.com/sympy/sympy/issues/12398 + assert limit(Abs(log(x)/x**3), x, oo) == 0 + assert limit(x*(Abs(log(x)/x**3)/Abs(log(x + 1)/(x + 1)**3) - 1), x, oo) == 3 + + # https://github.com/sympy/sympy/issues/18501 + assert limit(Abs(log(x - 1)**3 - 1), x, 1, '+') == oo + + # https://github.com/sympy/sympy/issues/18997 + assert limit(Abs(log(x)), x, 0) == oo + assert limit(Abs(log(Abs(x))), x, 0) == oo + + # https://github.com/sympy/sympy/issues/19026 + z = Symbol('z', positive=True) + assert limit(Abs(log(z) + 1)/log(z), z, oo) == 1 + + # https://github.com/sympy/sympy/issues/20704 + assert limit(z*(Abs(1/z + y) - Abs(y - 1/z))/2, z, 0) == 0 + + # https://github.com/sympy/sympy/issues/21606 + assert limit(cos(z)/sign(z), z, pi, '-') == -1 + + +def test_heuristic(): + x = Symbol("x", real=True) + assert heuristics(sin(1/x) + atan(x), x, 0, '+') == AccumBounds(-1, 1) + assert limit(log(2 + sqrt(atan(x))*sqrt(sin(1/x))), x, 0) == log(2) + + +def test_issue_3871(): + z = Symbol("z", positive=True) + f = -1/z*exp(-z*x) + assert limit(f, x, oo) == 0 + assert f.limit(x, oo) == 0 + + +def test_exponential(): + n = Symbol('n') + x = Symbol('x', real=True) + assert limit((1 + x/n)**n, n, oo) == exp(x) + assert limit((1 + x/(2*n))**n, n, oo) == exp(x/2) + assert limit((1 + x/(2*n + 1))**n, n, oo) == exp(x/2) + assert limit(((x - 1)/(x + 1))**x, x, oo) == exp(-2) + assert limit(1 + (1 + 1/x)**x, x, oo) == 1 + S.Exp1 + assert limit((2 + 6*x)**x/(6*x)**x, x, oo) == exp(S('1/3')) + + +def test_exponential2(): + n = Symbol('n') + assert limit((1 + x/(n + sin(n)))**n, n, oo) == exp(x) + + +def test_doit(): + f = Integral(2 * x, x) + l = Limit(f, x, oo) + assert l.doit() is oo + + +def test_series_AccumBounds(): + assert limit(sin(k) - sin(k + 1), k, oo) == AccumBounds(-2, 2) + assert limit(cos(k) - cos(k + 1) + 1, k, oo) == AccumBounds(-1, 3) + + # not the exact bound + assert limit(sin(k) - sin(k)*cos(k), k, oo) == AccumBounds(-2, 2) + + # test for issue #9934 + lo = (-3 + cos(1))/2 + hi = (1 + cos(1))/2 + t1 = Mul(AccumBounds(lo, hi), 1/(-1 + cos(1)), evaluate=False) + assert limit(simplify(Sum(cos(n).rewrite(exp), (n, 0, k)).doit().rewrite(sin)), k, oo) == t1 + + t2 = Mul(AccumBounds(-1 + sin(1)/2, sin(1)/2 + 1), 1/(1 - cos(1))) + assert limit(simplify(Sum(sin(n).rewrite(exp), (n, 0, k)).doit().rewrite(sin)), k, oo) == t2 + + assert limit(((sin(x) + 1)/2)**x, x, oo) == AccumBounds(0, oo) # wolfram says 0 + + # https://github.com/sympy/sympy/issues/12312 + e = 2**(-x)*(sin(x) + 1)**x + assert limit(e, x, oo) == AccumBounds(0, oo) + + +def test_bessel_functions_at_infinity(): + # Pull Request 23844 implements limits for all bessel and modified bessel + # functions approaching infinity along any direction i.e. abs(z0) tends to oo + + assert limit(besselj(1, x), x, oo) == 0 + assert limit(besselj(1, x), x, -oo) == 0 + assert limit(besselj(1, x), x, I*oo) == oo*I + assert limit(besselj(1, x), x, -I*oo) == -oo*I + assert limit(bessely(1, x), x, oo) == 0 + assert limit(bessely(1, x), x, -oo) == 0 + assert limit(bessely(1, x), x, I*oo) == -oo + assert limit(bessely(1, x), x, -I*oo) == -oo + assert limit(besseli(1, x), x, oo) == oo + assert limit(besseli(1, x), x, -oo) == -oo + assert limit(besseli(1, x), x, I*oo) == 0 + assert limit(besseli(1, x), x, -I*oo) == 0 + assert limit(besselk(1, x), x, oo) == 0 + assert limit(besselk(1, x), x, -oo) == -oo*I + assert limit(besselk(1, x), x, I*oo) == 0 + assert limit(besselk(1, x), x, -I*oo) == 0 + + # test issue 14874 + assert limit(besselk(0, x), x, oo) == 0 + + +@XFAIL +def test_doit2(): + f = Integral(2 * x, x) + l = Limit(f, x, oo) + # limit() breaks on the contained Integral. + assert l.doit(deep=False) == l + + +def test_issue_2929(): + assert limit((x * exp(x))/(exp(x) - 1), x, -oo) == 0 + + +def test_issue_3792(): + assert limit((1 - cos(x))/x**2, x, S.Half) == 4 - 4*cos(S.Half) + assert limit(sin(sin(x + 1) + 1), x, 0) == sin(1 + sin(1)) + assert limit(abs(sin(x + 1) + 1), x, 0) == 1 + sin(1) + + +def test_issue_4090(): + assert limit(1/(x + 3), x, 2) == Rational(1, 5) + assert limit(1/(x + pi), x, 2) == S.One/(2 + pi) + assert limit(log(x)/(x**2 + 3), x, 2) == log(2)/7 + assert limit(log(x)/(x**2 + pi), x, 2) == log(2)/(4 + pi) + + +def test_issue_4547(): + assert limit(cot(x), x, 0, dir='+') is oo + assert limit(cot(x), x, pi/2, dir='+') == 0 + + +def test_issue_5164(): + assert limit(x**0.5, x, oo) == oo**0.5 is oo + assert limit(x**0.5, x, 16) == 4 # Should this be a float? + assert limit(x**0.5, x, 0) == 0 + assert limit(x**(-0.5), x, oo) == 0 + assert limit(x**(-0.5), x, 4) == S.Half # Should this be a float? + + +def test_issue_5383(): + func = (1.0 * 1 + 1.0 * x)**(1.0 * 1 / x) + assert limit(func, x, 0) == E + + +def test_issue_14793(): + expr = ((x + S(1)/2) * log(x) - x + log(2*pi)/2 - \ + log(factorial(x)) + S(1)/(12*x))*x**3 + assert limit(expr, x, oo) == S(1)/360 + + +def test_issue_5183(): + # using list(...) so py.test can recalculate values + tests = list(product([x, -x], + [-1, 1], + [2, 3, S.Half, Rational(2, 3)], + ['-', '+'])) + results = (oo, oo, -oo, oo, -oo*I, oo, -oo*(-1)**Rational(1, 3), oo, + 0, 0, 0, 0, 0, 0, 0, 0, + oo, oo, oo, -oo, oo, -oo*I, oo, -oo*(-1)**Rational(1, 3), + 0, 0, 0, 0, 0, 0, 0, 0) + assert len(tests) == len(results) + for i, (args, res) in enumerate(zip(tests, results)): + y, s, e, d = args + eq = y**(s*e) + try: + assert limit(eq, x, 0, dir=d) == res + except AssertionError: + if 0: # change to 1 if you want to see the failing tests + print() + print(i, res, eq, d, limit(eq, x, 0, dir=d)) + else: + assert None + + +def test_issue_5184(): + assert limit(sin(x)/x, x, oo) == 0 + assert limit(atan(x), x, oo) == pi/2 + assert limit(gamma(x), x, oo) is oo + assert limit(cos(x)/x, x, oo) == 0 + assert limit(gamma(x), x, S.Half) == sqrt(pi) + + r = Symbol('r', real=True) + assert limit(r*sin(1/r), r, 0) == 0 + + +def test_issue_5229(): + assert limit((1 + y)**(1/y) - S.Exp1, y, 0) == 0 + + +def test_issue_4546(): + # using list(...) so py.test can recalculate values + tests = list(product([cot, tan], + [-pi/2, 0, pi/2, pi, pi*Rational(3, 2)], + ['-', '+'])) + results = (0, 0, -oo, oo, 0, 0, -oo, oo, 0, 0, + oo, -oo, 0, 0, oo, -oo, 0, 0, oo, -oo) + assert len(tests) == len(results) + for i, (args, res) in enumerate(zip(tests, results)): + f, l, d = args + eq = f(x) + try: + assert limit(eq, x, l, dir=d) == res + except AssertionError: + if 0: # change to 1 if you want to see the failing tests + print() + print(i, res, eq, l, d, limit(eq, x, l, dir=d)) + else: + assert None + + +def test_issue_3934(): + assert limit((1 + x**log(3))**(1/x), x, 0) == 1 + assert limit((5**(1/x) + 3**(1/x))**x, x, 0) == 5 + + +def test_issue_5955(): + assert limit((x**16)/(1 + x**16), x, oo) == 1 + assert limit((x**100)/(1 + x**100), x, oo) == 1 + assert limit((x**1885)/(1 + x**1885), x, oo) == 1 + assert limit((x**1000/((x + 1)**1000 + exp(-x))), x, oo) == 1 + + +def test_newissue(): + assert limit(exp(1/sin(x))/exp(cot(x)), x, 0) == 1 + + +def test_extended_real_line(): + assert limit(x - oo, x, oo) == Limit(x - oo, x, oo) + assert limit(1/(x + sin(x)) - oo, x, 0) == Limit(1/(x + sin(x)) - oo, x, 0) + assert limit(oo/x, x, oo) == Limit(oo/x, x, oo) + assert limit(x - oo + 1/x, x, oo) == Limit(x - oo + 1/x, x, oo) + + +@XFAIL +def test_order_oo(): + x = Symbol('x', positive=True) + assert Order(x)*oo != Order(1, x) + assert limit(oo/(x**2 - 4), x, oo) is oo + + +def test_issue_5436(): + raises(NotImplementedError, lambda: limit(exp(x*y), x, oo)) + raises(NotImplementedError, lambda: limit(exp(-x*y), x, oo)) + + +def test_Limit_dir(): + raises(TypeError, lambda: Limit(x, x, 0, dir=0)) + raises(ValueError, lambda: Limit(x, x, 0, dir='0')) + + +def test_polynomial(): + assert limit((x + 1)**1000/((x + 1)**1000 + 1), x, oo) == 1 + assert limit((x + 1)**1000/((x + 1)**1000 + 1), x, -oo) == 1 + assert limit(x ** Rational(77, 3) / (1 + x ** Rational(77, 3)), x, oo) == 1 + assert limit(x ** 101.1 / (1 + x ** 101.1), x, oo) == 1 + + +def test_rational(): + assert limit(1/y - (1/(y + x) + x/(y + x)/y)/z, x, oo) == (z - 1)/(y*z) + assert limit(1/y - (1/(y + x) + x/(y + x)/y)/z, x, -oo) == (z - 1)/(y*z) + + +def test_issue_5740(): + assert limit(log(x)*z - log(2*x)*y, x, 0) == oo*sign(y - z) + + +def test_issue_6366(): + n = Symbol('n', integer=True, positive=True) + r = (n + 1)*x**(n + 1)/(x**(n + 1) - 1) - x/(x - 1) + assert limit(r, x, 1).cancel() == n/2 + + +def test_factorial(): + f = factorial(x) + assert limit(f, x, oo) is oo + assert limit(x/f, x, oo) == 0 + # see Stirling's approximation: + # https://en.wikipedia.org/wiki/Stirling's_approximation + assert limit(f/(sqrt(2*pi*x)*(x/E)**x), x, oo) == 1 + assert limit(f, x, -oo) == gamma(-oo) + + +def test_issue_6560(): + e = (5*x**3/4 - x*Rational(3, 4) + (y*(3*x**2/2 - S.Half) + + 35*x**4/8 - 15*x**2/4 + Rational(3, 8))/(2*(y + 1))) + assert limit(e, y, oo) == 5*x**3/4 + 3*x**2/4 - 3*x/4 - Rational(1, 4) + +@XFAIL +def test_issue_5172(): + n = Symbol('n') + r = Symbol('r', positive=True) + c = Symbol('c') + p = Symbol('p', positive=True) + m = Symbol('m', negative=True) + expr = ((2*n*(n - r + 1)/(n + r*(n - r + 1)))**c + + (r - 1)*(n*(n - r + 2)/(n + r*(n - r + 1)))**c - n)/(n**c - n) + expr = expr.subs(c, c + 1) + raises(NotImplementedError, lambda: limit(expr, n, oo)) + assert limit(expr.subs(c, m), n, oo) == 1 + assert limit(expr.subs(c, p), n, oo).simplify() == \ + (2**(p + 1) + r - 1)/(r + 1)**(p + 1) + + +def test_issue_7088(): + a = Symbol('a') + assert limit(sqrt(x/(x + a)), x, oo) == 1 + + +def test_branch_cuts(): + assert limit(asin(I*x + 2), x, 0) == pi - asin(2) + assert limit(asin(I*x + 2), x, 0, '-') == asin(2) + assert limit(asin(I*x - 2), x, 0) == -asin(2) + assert limit(asin(I*x - 2), x, 0, '-') == -pi + asin(2) + assert limit(acos(I*x + 2), x, 0) == -acos(2) + assert limit(acos(I*x + 2), x, 0, '-') == acos(2) + assert limit(acos(I*x - 2), x, 0) == acos(-2) + assert limit(acos(I*x - 2), x, 0, '-') == 2*pi - acos(-2) + assert limit(atan(x + 2*I), x, 0) == I*atanh(2) + assert limit(atan(x + 2*I), x, 0, '-') == -pi + I*atanh(2) + assert limit(atan(x - 2*I), x, 0) == pi - I*atanh(2) + assert limit(atan(x - 2*I), x, 0, '-') == -I*atanh(2) + assert limit(atan(1/x), x, 0) == pi/2 + assert limit(atan(1/x), x, 0, '-') == -pi/2 + assert limit(atan(x), x, oo) == pi/2 + assert limit(atan(x), x, -oo) == -pi/2 + assert limit(acot(x + S(1)/2*I), x, 0) == pi - I*acoth(S(1)/2) + assert limit(acot(x + S(1)/2*I), x, 0, '-') == -I*acoth(S(1)/2) + assert limit(acot(x - S(1)/2*I), x, 0) == I*acoth(S(1)/2) + assert limit(acot(x - S(1)/2*I), x, 0, '-') == -pi + I*acoth(S(1)/2) + assert limit(acot(x), x, 0) == pi/2 + assert limit(acot(x), x, 0, '-') == -pi/2 + assert limit(asec(I*x + S(1)/2), x, 0) == asec(S(1)/2) + assert limit(asec(I*x + S(1)/2), x, 0, '-') == -asec(S(1)/2) + assert limit(asec(I*x - S(1)/2), x, 0) == 2*pi - asec(-S(1)/2) + assert limit(asec(I*x - S(1)/2), x, 0, '-') == asec(-S(1)/2) + assert limit(acsc(I*x + S(1)/2), x, 0) == acsc(S(1)/2) + assert limit(acsc(I*x + S(1)/2), x, 0, '-') == pi - acsc(S(1)/2) + assert limit(acsc(I*x - S(1)/2), x, 0) == -pi + acsc(S(1)/2) + assert limit(acsc(I*x - S(1)/2), x, 0, '-') == -acsc(S(1)/2) + + assert limit(log(I*x - 1), x, 0) == I*pi + assert limit(log(I*x - 1), x, 0, '-') == -I*pi + assert limit(log(-I*x - 1), x, 0) == -I*pi + assert limit(log(-I*x - 1), x, 0, '-') == I*pi + + assert limit(sqrt(I*x - 1), x, 0) == I + assert limit(sqrt(I*x - 1), x, 0, '-') == -I + assert limit(sqrt(-I*x - 1), x, 0) == -I + assert limit(sqrt(-I*x - 1), x, 0, '-') == I + + assert limit(cbrt(I*x - 1), x, 0) == (-1)**(S(1)/3) + assert limit(cbrt(I*x - 1), x, 0, '-') == -(-1)**(S(2)/3) + assert limit(cbrt(-I*x - 1), x, 0) == -(-1)**(S(2)/3) + assert limit(cbrt(-I*x - 1), x, 0, '-') == (-1)**(S(1)/3) + + +def test_issue_6364(): + a = Symbol('a') + e = z/(1 - sqrt(1 + z)*sin(a)**2 - sqrt(1 - z)*cos(a)**2) + assert limit(e, z, 0) == 1/(cos(a)**2 - S.Half) + + +def test_issue_6682(): + assert limit(exp(2*Ei(-x))/x**2, x, 0) == exp(2*EulerGamma) + + +def test_issue_4099(): + a = Symbol('a') + assert limit(a/x, x, 0) == oo*sign(a) + assert limit(-a/x, x, 0) == -oo*sign(a) + assert limit(-a*x, x, oo) == -oo*sign(a) + assert limit(a*x, x, oo) == oo*sign(a) + + +def test_issue_4503(): + dx = Symbol('dx') + assert limit((sqrt(1 + exp(x + dx)) - sqrt(1 + exp(x)))/dx, dx, 0) == \ + exp(x)/(2*sqrt(exp(x) + 1)) + + +def test_issue_6052(): + G = meijerg((), (), (1,), (0,), -x) + g = hyperexpand(G) + assert limit(g, x, 0, '+-') == 0 + assert limit(g, x, oo) == -oo + + +def test_issue_7224(): + expr = sqrt(x)*besseli(1,sqrt(8*x)) + assert limit(x*diff(expr, x, x)/expr, x, 0) == 2 + assert limit(x*diff(expr, x, x)/expr, x, 1).evalf() == 2.0 + + +def test_issue_7391_8166(): + f = Function('f') + # limit should depend on the continuity of the expression at the point passed + assert limit(f(x), x, 4) == Limit(f(x), x, 4, dir='+') + assert limit(x*f(x)**2/(x**2 + f(x)**4), x, 0) == Limit(x*f(x)**2/(x**2 + f(x)**4), x, 0, dir='+') + + +def test_issue_8208(): + assert limit(n**(Rational(1, 1e9) - 1), n, oo) == 0 + + +def test_issue_8229(): + assert limit((x**Rational(1, 4) - 2)/(sqrt(x) - 4)**Rational(2, 3), x, 16) == 0 + + +def test_issue_8433(): + d, t = symbols('d t', positive=True) + assert limit(erf(1 - t/d), t, oo) == -1 + + +def test_issue_8481(): + k = Symbol('k', integer=True, nonnegative=True) + lamda = Symbol('lamda', positive=True) + assert limit(lamda**k * exp(-lamda) / factorial(k), k, oo) == 0 + + +def test_issue_8462(): + assert limit(binomial(n, n/2), n, oo) == oo + assert limit(binomial(n, n/2) * 3 ** (-n), n, oo) == 0 + + +def test_issue_8634(): + n = Symbol('n', integer=True, positive=True) + x = Symbol('x') + assert limit(x**n, x, -oo) == oo*sign((-1)**n) + + +def test_issue_8635_18176(): + x = Symbol('x', real=True) + k = Symbol('k', positive=True) + assert limit(x**n - x**(n - 0), x, oo) == 0 + assert limit(x**n - x**(n - 5), x, oo) == oo + assert limit(x**n - x**(n - 2.5), x, oo) == oo + assert limit(x**n - x**(n - k - 1), x, oo) == oo + x = Symbol('x', positive=True) + assert limit(x**n - x**(n - 1), x, oo) == oo + assert limit(x**n - x**(n + 2), x, oo) == -oo + + +def test_issue_8730(): + assert limit(subfactorial(x), x, oo) is oo + + +def test_issue_9252(): + n = Symbol('n', integer=True) + c = Symbol('c', positive=True) + assert limit((log(n))**(n/log(n)) / (1 + c)**n, n, oo) == 0 + # limit should depend on the value of c + raises(NotImplementedError, lambda: limit((log(n))**(n/log(n)) / c**n, n, oo)) + + +def test_issue_9558(): + assert limit(sin(x)**15, x, 0, '-') == 0 + + +def test_issue_10801(): + # make sure limits work with binomial + assert limit(16**k / (k * binomial(2*k, k)**2), k, oo) == pi + + +def test_issue_10976(): + s, x = symbols('s x', real=True) + assert limit(erf(s*x)/erf(s), s, 0) == x + + +def test_issue_9041(): + assert limit(factorial(n) / ((n/exp(1))**n * sqrt(2*pi*n)), n, oo) == 1 + + +def test_issue_9205(): + x, y, a = symbols('x, y, a') + assert Limit(x, x, a).free_symbols == {a} + assert Limit(x, x, a, '-').free_symbols == {a} + assert Limit(x + y, x + y, a).free_symbols == {a} + assert Limit(-x**2 + y, x**2, a).free_symbols == {y, a} + + +def test_issue_9471(): + assert limit(((27**(log(n,3)))/n**3),n,oo) == 1 + assert limit(((27**(log(n,3)+1))/n**3),n,oo) == 27 + + +def test_issue_10382(): + assert limit(fibonacci(n + 1)/fibonacci(n), n, oo) == GoldenRatio + + +def test_issue_11496(): + assert limit(erfc(log(1/x)), x, oo) == 2 + + +def test_issue_11879(): + assert simplify(limit(((x+y)**n-x**n)/y, y, 0)) == n*x**(n-1) + + +def test_limit_with_Float(): + k = symbols("k") + assert limit(1.0 ** k, k, oo) == 1 + assert limit(0.3*1.0**k, k, oo) == Rational(3, 10) + + +def test_issue_10610(): + assert limit(3**x*3**(-x - 1)*(x + 1)**2/x**2, x, oo) == Rational(1, 3) + + +def test_issue_10868(): + assert limit(log(x) + asech(x), x, 0, '+') == log(2) + assert limit(log(x) + asech(x), x, 0, '-') == log(2) + 2*I*pi + raises(ValueError, lambda: limit(log(x) + asech(x), x, 0, '+-')) + assert limit(log(x) + asech(x), x, oo) == oo + assert limit(log(x) + acsch(x), x, 0, '+') == log(2) + assert limit(log(x) + acsch(x), x, 0, '-') == -oo + raises(ValueError, lambda: limit(log(x) + acsch(x), x, 0, '+-')) + assert limit(log(x) + acsch(x), x, oo) == oo + + +def test_issue_6599(): + assert limit((n + cos(n))/n, n, oo) == 1 + + +def test_issue_12555(): + assert limit((3**x + 2* x**10) / (x**10 + exp(x)), x, -oo) == 2 + assert limit((3**x + 2* x**10) / (x**10 + exp(x)), x, oo) is oo + + +def test_issue_12769(): + r, z, x = symbols('r z x', real=True) + a, b, s0, K, F0, s, T = symbols('a b s0 K F0 s T', positive=True, real=True) + fx = (F0**b*K**b*r*s0 - sqrt((F0**2*K**(2*b)*a**2*(b - 1) + \ + F0**(2*b)*K**2*a**2*(b - 1) + F0**(2*b)*K**(2*b)*s0**2*(b - 1)*(b**2 - 2*b + 1) - \ + 2*F0**(2*b)*K**(b + 1)*a*r*s0*(b**2 - 2*b + 1) + \ + 2*F0**(b + 1)*K**(2*b)*a*r*s0*(b**2 - 2*b + 1) - \ + 2*F0**(b + 1)*K**(b + 1)*a**2*(b - 1))/((b - 1)*(b**2 - 2*b + 1))))*(b*r - b - r + 1) + + assert fx.subs(K, F0).factor(deep=True) == limit(fx, K, F0).factor(deep=True) + + +def test_issue_13332(): + assert limit(sqrt(30)*5**(-5*x - 1)*(46656*x)**x*(5*x + 2)**(5*x + 5*S.Half) * + (6*x + 2)**(-6*x - 5*S.Half), x, oo) == Rational(25, 36) + + +def test_issue_12564(): + assert limit(x**2 + x*sin(x) + cos(x), x, -oo) is oo + assert limit(x**2 + x*sin(x) + cos(x), x, oo) is oo + assert limit(((x + cos(x))**2).expand(), x, oo) is oo + assert limit(((x + sin(x))**2).expand(), x, oo) is oo + assert limit(((x + cos(x))**2).expand(), x, -oo) is oo + assert limit(((x + sin(x))**2).expand(), x, -oo) is oo + + +def test_issue_14456(): + raises(NotImplementedError, lambda: Limit(exp(x), x, zoo).doit()) + raises(NotImplementedError, lambda: Limit(x**2/(x+1), x, zoo).doit()) + + +def test_issue_14411(): + assert limit(3*sec(4*pi*x - x/3), x, 3*pi/(24*pi - 2)) is -oo + + +def test_issue_13382(): + assert limit(x*(((x + 1)**2 + 1)/(x**2 + 1) - 1), x, oo) == 2 + + +def test_issue_13403(): + assert limit(x*(-1 + (x + log(x + 1) + 1)/(x + log(x))), x, oo) == 1 + + +def test_issue_13416(): + assert limit((-x**3*log(x)**3 + (x - 1)*(x + 1)**2*log(x + 1)**3)/(x**2*log(x)**3), x, oo) == 1 + + +def test_issue_13462(): + assert limit(n**2*(2*n*(-(1 - 1/(2*n))**x + 1) - x - (-x**2/4 + x/4)/n), n, oo) == x**3/24 - x**2/8 + x/12 + + +def test_issue_13750(): + a = Symbol('a') + assert limit(erf(a - x), x, oo) == -1 + assert limit(erf(sqrt(x) - x), x, oo) == -1 + + +def test_issue_14276(): + assert isinstance(limit(sin(x)**log(x), x, oo), Limit) + assert isinstance(limit(sin(x)**cos(x), x, oo), Limit) + assert isinstance(limit(sin(log(cos(x))), x, oo), Limit) + assert limit((1 + 1/(x**2 + cos(x)))**(x**2 + x), x, oo) == E + + +def test_issue_14514(): + assert limit((1/(log(x)**log(x)))**(1/x), x, oo) == 1 + + +def test_issues_14525(): + assert limit(sin(x)**2 - cos(x) + tan(x)*csc(x), x, oo) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert limit(sin(x)**2 - cos(x) + sin(x)*cot(x), x, oo) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert limit(cot(x) - tan(x)**2, x, oo) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert limit(cos(x) - tan(x)**2, x, oo) == AccumBounds(S.NegativeInfinity, S.One) + assert limit(sin(x) - tan(x)**2, x, oo) == AccumBounds(S.NegativeInfinity, S.One) + assert limit(cos(x)**2 - tan(x)**2, x, oo) == AccumBounds(S.NegativeInfinity, S.One) + assert limit(tan(x)**2 + sin(x)**2 - cos(x), x, oo) == AccumBounds(-S.One, S.Infinity) + + +def test_issue_14574(): + assert limit(sqrt(x)*cos(x - x**2) / (x + 1), x, oo) == 0 + + +def test_issue_10102(): + assert limit(fresnels(x), x, oo) == S.Half + assert limit(3 + fresnels(x), x, oo) == 3 + S.Half + assert limit(5*fresnels(x), x, oo) == Rational(5, 2) + assert limit(fresnelc(x), x, oo) == S.Half + assert limit(fresnels(x), x, -oo) == Rational(-1, 2) + assert limit(4*fresnelc(x), x, -oo) == -2 + + +def test_issue_14377(): + raises(NotImplementedError, lambda: limit(exp(I*x)*sin(pi*x), x, oo)) + + +def test_issue_15146(): + e = (x/2) * (-2*x**3 - 2*(x**3 - 1) * x**2 * digamma(x**3 + 1) + \ + 2*(x**3 - 1) * x**2 * digamma(x**3 + x + 1) + x + 3) + assert limit(e, x, oo) == S(1)/3 + + +def test_issue_15202(): + e = (2**x*(2 + 2**(-x)*(-2*2**x + x + 2))/(x + 1))**(x + 1) + assert limit(e, x, oo) == exp(1) + + e = (log(x, 2)**7 + 10*x*factorial(x) + 5**x) / (factorial(x + 1) + 3*factorial(x) + 10**x) + assert limit(e, x, oo) == 10 + + +def test_issue_15282(): + assert limit((x**2000 - (x + 1)**2000) / x**1999, x, oo) == -2000 + + +def test_issue_15984(): + assert limit((-x + log(exp(x) + 1))/x, x, oo, dir='-') == 0 + + +def test_issue_13571(): + assert limit(uppergamma(x, 1) / gamma(x), x, oo) == 1 + + +def test_issue_13575(): + assert limit(acos(erfi(x)), x, 1) == acos(erfi(S.One)) + + +def test_issue_17325(): + assert Limit(sin(x)/x, x, 0, dir="+-").doit() == 1 + assert Limit(x**2, x, 0, dir="+-").doit() == 0 + assert Limit(1/x**2, x, 0, dir="+-").doit() is oo + assert Limit(1/x, x, 0, dir="+-").doit() is zoo + + +def test_issue_10978(): + assert LambertW(x).limit(x, 0) == 0 + + +def test_issue_14313_comment(): + assert limit(floor(n/2), n, oo) is oo + + +def test_issue_15323(): + d = ((1 - 1/x)**x).diff(x) + assert limit(d, x, 1, dir='+') == 1 + + +def test_issue_12571(): + assert limit(-LambertW(-log(x))/log(x), x, 1) == 1 + + +def test_issue_14590(): + assert limit((x**3*((x + 1)/x)**x)/((x + 1)*(x + 2)*(x + 3)), x, oo) == exp(1) + + +def test_issue_14393(): + a, b = symbols('a b') + assert limit((x**b - y**b)/(x**a - y**a), x, y) == b*y**(-a + b)/a + + +def test_issue_14556(): + assert limit(factorial(n + 1)**(1/(n + 1)) - factorial(n)**(1/n), n, oo) == exp(-1) + + +def test_issue_14811(): + assert limit(((1 + ((S(2)/3)**(x + 1)))**(2**x))/(2**((S(4)/3)**(x - 1))), x, oo) == oo + + +def test_issue_16222(): + assert limit(exp(x), x, 1000000000) == exp(1000000000) + + +def test_issue_16714(): + assert limit(((x**(x + 1) + (x + 1)**x) / x**(x + 1))**x, x, oo) == exp(exp(1)) + + +def test_issue_16722(): + z = symbols('z', positive=True) + assert limit(binomial(n + z, n)*n**-z, n, oo) == 1/gamma(z + 1) + z = symbols('z', positive=True, integer=True) + assert limit(binomial(n + z, n)*n**-z, n, oo) == 1/gamma(z + 1) + + +def test_issue_17431(): + assert limit(((n + 1) + 1) / (((n + 1) + 2) * factorial(n + 1)) * + (n + 2) * factorial(n) / (n + 1), n, oo) == 0 + assert limit((n + 2)**2*factorial(n)/((n + 1)*(n + 3)*factorial(n + 1)) + , n, oo) == 0 + assert limit((n + 1) * factorial(n) / (n * factorial(n + 1)), n, oo) == 0 + + +def test_issue_17671(): + assert limit(Ei(-log(x)) - log(log(x))/x, x, 1) == EulerGamma + + +def test_issue_17751(): + a, b, c, x = symbols('a b c x', positive=True) + assert limit((a + 1)*x - sqrt((a + 1)**2*x**2 + b*x + c), x, oo) == -b/(2*a + 2) + + +def test_issue_17792(): + assert limit(factorial(n)/sqrt(n)*(exp(1)/n)**n, n, oo) == sqrt(2)*sqrt(pi) + + +def test_issue_18118(): + assert limit(sign(sin(x)), x, 0, "-") == -1 + assert limit(sign(sin(x)), x, 0, "+") == 1 + + +def test_issue_18306(): + assert limit(sin(sqrt(x))/sqrt(sin(x)), x, 0, '+') == 1 + + +def test_issue_18378(): + assert limit(log(exp(3*x) + x)/log(exp(x) + x**100), x, oo) == 3 + + +def test_issue_18399(): + assert limit((1 - S(1)/2*x)**(3*x), x, oo) is zoo + assert limit((-x)**x, x, oo) is zoo + + +def test_issue_18442(): + assert limit(tan(x)**(2**(sqrt(pi))), x, oo, dir='-') == Limit(tan(x)**(2**(sqrt(pi))), x, oo, dir='-') + + +def test_issue_18452(): + assert limit(abs(log(x))**x, x, 0) == 1 + assert limit(abs(log(x))**x, x, 0, "-") == 1 + + +def test_issue_18473(): + assert limit(sin(x)**(1/x), x, oo) == Limit(sin(x)**(1/x), x, oo, dir='-') + assert limit(cos(x)**(1/x), x, oo) == Limit(cos(x)**(1/x), x, oo, dir='-') + assert limit(tan(x)**(1/x), x, oo) == Limit(tan(x)**(1/x), x, oo, dir='-') + assert limit((cos(x) + 2)**(1/x), x, oo) == 1 + assert limit((sin(x) + 10)**(1/x), x, oo) == 1 + assert limit((cos(x) - 2)**(1/x), x, oo) == Limit((cos(x) - 2)**(1/x), x, oo, dir='-') + assert limit((cos(x) + 1)**(1/x), x, oo) == AccumBounds(0, 1) + assert limit((tan(x)**2)**(2/x) , x, oo) == AccumBounds(0, oo) + assert limit((sin(x)**2)**(1/x), x, oo) == AccumBounds(0, 1) + # Tests for issue #23751 + assert limit((cos(x) + 1)**(1/x), x, -oo) == AccumBounds(1, oo) + assert limit((sin(x)**2)**(1/x), x, -oo) == AccumBounds(1, oo) + assert limit((tan(x)**2)**(2/x) , x, -oo) == AccumBounds(0, oo) + + +def test_issue_18482(): + assert limit((2*exp(3*x)/(exp(2*x) + 1))**(1/x), x, oo) == exp(1) + + +def test_issue_18508(): + assert limit(sin(x)/sqrt(1-cos(x)), x, 0) == sqrt(2) + assert limit(sin(x)/sqrt(1-cos(x)), x, 0, dir='+') == sqrt(2) + assert limit(sin(x)/sqrt(1-cos(x)), x, 0, dir='-') == -sqrt(2) + + +def test_issue_18521(): + raises(NotImplementedError, lambda: limit(exp((2 - n) * x), x, oo)) + + +def test_issue_18969(): + a, b = symbols('a b', positive=True) + assert limit(LambertW(a), a, b) == LambertW(b) + assert limit(exp(LambertW(a)), a, b) == exp(LambertW(b)) + + +def test_issue_18992(): + assert limit(n/(factorial(n)**(1/n)), n, oo) == exp(1) + + +def test_issue_19067(): + x = Symbol('x') + assert limit(gamma(x)/(gamma(x - 1)*gamma(x + 2)), x, 0) == -1 + + +def test_issue_19586(): + assert limit(x**(2**x*3**(-x)), x, oo) == 1 + + +def test_issue_13715(): + n = Symbol('n') + p = Symbol('p', zero=True) + assert limit(n + p, n, 0) == 0 + + +def test_issue_15055(): + assert limit(n**3*((-n - 1)*sin(1/n) + (n + 2)*sin(1/(n + 1)))/(-n + 1), n, oo) == 1 + + +def test_issue_16708(): + m, vi = symbols('m vi', positive=True) + B, ti, d = symbols('B ti d') + assert limit((B*ti*vi - sqrt(m)*sqrt(-2*B*d*vi + m*(vi)**2) + m*vi)/(B*vi), B, 0) == (d + ti*vi)/vi + + +def test_issue_19154(): + assert limit(besseli(1, 3 *x)/(x *besseli(1, x)**3), x , oo) == 2*sqrt(3)*pi/3 + assert limit(besseli(1, 3 *x)/(x *besseli(1, x)**3), x , -oo) == -2*sqrt(3)*pi/3 + + +def test_issue_19453(): + beta = Symbol("beta", positive=True) + h = Symbol("h", positive=True) + m = Symbol("m", positive=True) + w = Symbol("omega", positive=True) + g = Symbol("g", positive=True) + + e = exp(1) + q = 3*h**2*beta*g*e**(0.5*h*beta*w) + p = m**2*w**2 + s = e**(h*beta*w) - 1 + Z = -q/(4*p*s) - q/(2*p*s**2) - q*(e**(h*beta*w) + 1)/(2*p*s**3)\ + + e**(0.5*h*beta*w)/s + E = -diff(log(Z), beta) + + assert limit(E - 0.5*h*w, beta, oo) == 0 + assert limit(E.simplify() - 0.5*h*w, beta, oo) == 0 + + +def test_issue_19739(): + assert limit((-S(1)/4)**x, x, oo) == 0 + + +def test_issue_19766(): + assert limit(2**(-x)*sqrt(4**(x + 1) + 1), x, oo) == 2 + + +def test_issue_19770(): + m = Symbol('m') + # the result is not 0 for non-real m + assert limit(cos(m*x)/x, x, oo) == Limit(cos(m*x)/x, x, oo, dir='-') + m = Symbol('m', real=True) + # can be improved to give the correct result 0 + assert limit(cos(m*x)/x, x, oo) == Limit(cos(m*x)/x, x, oo, dir='-') + m = Symbol('m', nonzero=True) + assert limit(cos(m*x), x, oo) == AccumBounds(-1, 1) + assert limit(cos(m*x)/x, x, oo) == 0 + + +def test_issue_7535(): + assert limit(tan(x)/sin(tan(x)), x, pi/2) == Limit(tan(x)/sin(tan(x)), x, pi/2, dir='+') + assert limit(tan(x)/sin(tan(x)), x, pi/2, dir='-') == Limit(tan(x)/sin(tan(x)), x, pi/2, dir='-') + assert limit(tan(x)/sin(tan(x)), x, pi/2, dir='+-') == Limit(tan(x)/sin(tan(x)), x, pi/2, dir='+-') + assert limit(sin(tan(x)),x,pi/2) == AccumBounds(-1, 1) + assert -oo*(1/sin(-oo)) == AccumBounds(-oo, oo) + assert oo*(1/sin(oo)) == AccumBounds(-oo, oo) + assert oo*(1/sin(-oo)) == AccumBounds(-oo, oo) + assert -oo*(1/sin(oo)) == AccumBounds(-oo, oo) + + +def test_issue_20365(): + assert limit(((x + 1)**(1/x) - E)/x, x, 0) == -E/2 + + +def test_issue_21031(): + assert limit(((1 + x)**(1/x) - (1 + 2*x)**(1/(2*x)))/asin(x), x, 0) == E/2 + + +def test_issue_21038(): + assert limit(sin(pi*x)/(3*x - 12), x, 4) == pi/3 + + +def test_issue_20578(): + expr = abs(x) * sin(1/x) + assert limit(expr,x,0,'+') == 0 + assert limit(expr,x,0,'-') == 0 + assert limit(expr,x,0,'+-') == 0 + + +def test_issue_21227(): + f = log(x) + + assert f.nseries(x, logx=y) == y + assert f.nseries(x, logx=-x) == -x + + f = log(-log(x)) + + assert f.nseries(x, logx=y) == log(-y) + assert f.nseries(x, logx=-x) == log(x) + + f = log(log(x)) + + assert f.nseries(x, logx=y) == log(y) + assert f.nseries(x, logx=-x) == log(-x) + assert f.nseries(x, logx=x) == log(x) + + f = log(log(log(1/x))) + + assert f.nseries(x, logx=y) == log(log(-y)) + assert f.nseries(x, logx=-y) == log(log(y)) + assert f.nseries(x, logx=x) == log(log(-x)) + assert f.nseries(x, logx=-x) == log(log(x)) + + +def test_issue_21415(): + exp = (x-1)*cos(1/(x-1)) + assert exp.limit(x,1) == 0 + assert exp.expand().limit(x,1) == 0 + + +def test_issue_21530(): + assert limit(sinh(n + 1)/sinh(n), n, oo) == E + + +def test_issue_21550(): + r = (sqrt(5) - 1)/2 + assert limit((x - r)/(x**2 + x - 1), x, r) == sqrt(5)/5 + + +def test_issue_21661(): + out = limit((x**(x + 1) * (log(x) + 1) + 1) / x, x, 11) + assert out == S(3138428376722)/11 + 285311670611*log(11) + + +def test_issue_21701(): + assert limit((besselj(z, x)/x**z).subs(z, 7), x, 0) == S(1)/645120 + + +def test_issue_21721(): + a = Symbol('a', real=True) + I = integrate(1/(pi*(1 + (x - a)**2)), x) + assert I.limit(x, oo) == S.Half + + +def test_issue_21756(): + term = (1 - exp(-2*I*pi*z))/(1 - exp(-2*I*pi*z/5)) + assert term.limit(z, 0) == 5 + assert re(term).limit(z, 0) == 5 + + +def test_issue_21785(): + a = Symbol('a') + assert sqrt((-a**2 + x**2)/(1 - x**2)).limit(a, 1, '-') == I + + +def test_issue_22181(): + assert limit((-1)**x * 2**(-x), x, oo) == 0 + + +def test_issue_22220(): + e1 = sqrt(30)*atan(sqrt(30)*tan(x/2)/6)/30 + e2 = sqrt(30)*I*(-log(sqrt(2)*tan(x/2) - 2*sqrt(15)*I/5) + + +log(sqrt(2)*tan(x/2) + 2*sqrt(15)*I/5))/60 + + assert limit(e1, x, -pi) == -sqrt(30)*pi/60 + assert limit(e2, x, -pi) == -sqrt(30)*pi/30 + + assert limit(e1, x, -pi, '-') == sqrt(30)*pi/60 + assert limit(e2, x, -pi, '-') == 0 + + # test https://github.com/sympy/sympy/issues/22220#issuecomment-972727694 + expr = log(x - I) - log(-x - I) + expr2 = logcombine(expr, force=True) + assert limit(expr, x, oo) == limit(expr2, x, oo) == I*pi + + # test https://github.com/sympy/sympy/issues/22220#issuecomment-1077618340 + expr = expr = (-log(tan(x/2) - I) +log(tan(x/2) + I)) + assert limit(expr, x, pi, '+') == 2*I*pi + assert limit(expr, x, pi, '-') == 0 + + +def test_issue_22334(): + k, n = symbols('k, n', positive=True) + assert limit((n+1)**k/((n+1)**(k+1) - (n)**(k+1)), n, oo) == 1/(k + 1) + assert limit((n+1)**k/((n+1)**(k+1) - (n)**(k+1)).expand(), n, oo) == 1/(k + 1) + assert limit((n+1)**k/(n*(-n**k + (n + 1)**k) + (n + 1)**k), n, oo) == 1/(k + 1) + + +def test_issue_22836_limit(): + assert limit(2**(1/x)/factorial(1/(x)), x, 0) == S.Zero + + +def test_sympyissue_22986(): + assert limit(acosh(1 + 1/x)*sqrt(x), x, oo) == sqrt(2) + + +def test_issue_23231(): + f = (2**x - 2**(-x))/(2**x + 2**(-x)) + assert limit(f, x, -oo) == -1 + + +def test_issue_23596(): + assert integrate(((1 + x)/x**2)*exp(-1/x), (x, 0, oo)) == oo + + +def test_issue_23752(): + expr1 = sqrt(-I*x**2 + x - 3) + expr2 = sqrt(-I*x**2 + I*x - 3) + assert limit(expr1, x, 0, '+') == -sqrt(3)*I + assert limit(expr1, x, 0, '-') == -sqrt(3)*I + assert limit(expr2, x, 0, '+') == sqrt(3)*I + assert limit(expr2, x, 0, '-') == -sqrt(3)*I + + +def test_issue_24276(): + fx = log(tan(pi/2*tanh(x))).diff(x) + assert fx.limit(x, oo) == 2 + assert fx.simplify().limit(x, oo) == 2 + assert fx.rewrite(sin).limit(x, oo) == 2 + assert fx.rewrite(sin).simplify().limit(x, oo) == 2 + +def test_issue_25230(): + a = Symbol('a', real = True) + b = Symbol('b', positive = True) + c = Symbol('c', negative = True) + n = Symbol('n', integer = True) + raises(NotImplementedError, lambda: limit(Mod(x, a), x, a)) + assert limit(Mod(x, b), x, n*b, '+') == 0 + assert limit(Mod(x, b), x, n*b, '-') == b + assert limit(Mod(x, c), x, n*c, '+') == c + assert limit(Mod(x, c), x, n*c, '-') == 0 + + +def test_issue_25582(): + + assert limit(asin(exp(x)), x, oo, '-') == -oo*I + assert limit(acos(exp(x)), x, oo, '-') == oo*I + assert limit(atan(exp(x)), x, oo, '-') == pi/2 + assert limit(acot(exp(x)), x, oo, '-') == 0 + assert limit(asec(exp(x)), x, oo, '-') == pi/2 + assert limit(acsc(exp(x)), x, oo, '-') == 0 + + +def test_issue_25847(): + #atan + assert limit(atan(sin(x)/x), x, 0, '+-') == pi/4 + assert limit(atan(exp(1/x)), x, 0, '+') == pi/2 + assert limit(atan(exp(1/x)), x, 0, '-') == 0 + + #asin + assert limit(asin(sin(x)/x), x, 0, '+-') == pi/2 + assert limit(asin(exp(1/x)), x, 0, '+') == -oo*I + assert limit(asin(exp(1/x)), x, 0, '-') == 0 + + #acos + assert limit(acos(sin(x)/x), x, 0, '+-') == 0 + assert limit(acos(exp(1/x)), x, 0, '+') == oo*I + assert limit(acos(exp(1/x)), x, 0, '-') == pi/2 + + #acot + assert limit(acot(sin(x)/x), x, 0, '+-') == pi/4 + assert limit(acot(exp(1/x)), x, 0, '+') == 0 + assert limit(acot(exp(1/x)), x, 0, '-') == pi/2 + + #asec + assert limit(asec(sin(x)/x), x, 0, '+-') == 0 + assert limit(asec(exp(1/x)), x, 0, '+') == pi/2 + assert limit(asec(exp(1/x)), x, 0, '-') == oo*I + + #acsc + assert limit(acsc(sin(x)/x), x, 0, '+-') == pi/2 + assert limit(acsc(exp(1/x)), x, 0, '+') == 0 + assert limit(acsc(exp(1/x)), x, 0, '-') == -oo*I + + #atanh + assert limit(atanh(sin(x)/x), x, 0, '+-') == oo + assert limit(atanh(exp(1/x)), x, 0, '+') == -I*pi/2 + assert limit(atanh(exp(1/x)), x, 0, '-') == 0 + + #asinh + assert limit(asinh(sin(x)/x), x, 0, '+-') == log(1 + sqrt(2)) + assert limit(asinh(exp(1/x)), x, 0, '+') == oo + assert limit(asinh(exp(1/x)), x, 0, '-') == 0 + + #acosh + assert limit(acosh(sin(x)/x), x, 0, '+-') == 0 + assert limit(acosh(exp(1/x)), x, 0, '+') == oo + assert limit(acosh(exp(1/x)), x, 0, '-') == I*pi/2 + + #acoth + assert limit(acoth(sin(x)/x), x, 0, '+-') == oo + assert limit(acoth(exp(1/x)), x, 0, '+') == 0 + assert limit(acoth(exp(1/x)), x, 0, '-') == -I*pi/2 + + #asech + assert limit(asech(sin(x)/x), x, 0, '+-') == 0 + assert limit(asech(exp(1/x)), x, 0, '+') == I*pi/2 + assert limit(asech(exp(1/x)), x, 0, '-') == oo + + #acsch + assert limit(acsch(sin(x)/x), x, 0, '+-') == log(1 + sqrt(2)) + assert limit(acsch(exp(1/x)), x, 0, '+') == 0 + assert limit(acsch(exp(1/x)), x, 0, '-') == oo + + +def test_issue_26040(): + assert limit(besseli(0, x + 1)/besseli(0, x), x, oo) == S.Exp1 + + +def test_issue_26250(): + e = elliptic_e(4*x/(x**2 + 2*x + 1)) + k = elliptic_k(4*x/(x**2 + 2*x + 1)) + e1 = ((1-3*x**2)*e**2/2 - (x**2-2*x+1)*e*k/2) + e2 = pi**2*(x**8 - 2*x**7 - x**6 + 4*x**5 - x**4 - 2*x**3 + x**2) + assert limit(e1/e2, x, 0) == -S(1)/8 + + +def test_issue_26513(): + assert limit(abs((-x/(x+1))**x), x ,oo) == exp(-1) + assert limit((x/(x + 1))**x, x, oo) == exp(-1) + raises (NotImplementedError, lambda: limit((-x/(x+1))**x, x, oo)) + + +def test_issue_26916(): + assert limit(Ei(x)*exp(-x), x, +oo) == 0 + assert limit(Ei(x)*exp(-x), x, -oo) == 0 + + +def test_issue_22982_15323(): + assert limit((log(E + 1/x) - 1)**(1 - sqrt(E + 1/x)), x, oo) == oo + assert limit((1 - 1/x)**x*(log(1 - 1/x) + 1/(x*(1 - 1/x))), x, 1, dir='+') == 1 + assert limit((log(E + 1/x) )**(1 - sqrt(E + 1/x)), x, oo) == 1 + assert limit((log(E + 1/x) - 1)**(- sqrt(E + 1/x)), x, oo) == oo + + +def test_issue_26991(): + assert limit(x/((x - 6)*sinh(tanh(0.03*x)) + tanh(x) - 0.5), x, oo) == 1/sinh(1) + +def test_issue_27278(): + expr = (1/(x*log((x + 3)/x)))**x*((x + 1)*log((x + 4)/(x + 1)))**(x + 1)/3 + assert limit(expr, x, oo) == 1 diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_limitseq.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_limitseq.py new file mode 100644 index 0000000000000000000000000000000000000000..362bb0397feb0ec63929920855c81279eca0bd6a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_limitseq.py @@ -0,0 +1,177 @@ +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.combinatorial.factorials import (binomial, factorial, subfactorial) +from sympy.functions.combinatorial.numbers import (fibonacci, harmonic) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.gamma_functions import gamma +from sympy.series.limitseq import limit_seq +from sympy.series.limitseq import difference_delta as dd +from sympy.testing.pytest import raises, XFAIL +from sympy.calculus.accumulationbounds import AccumulationBounds + +n, m, k = symbols('n m k', integer=True) + + +def test_difference_delta(): + e = n*(n + 1) + e2 = e * k + + assert dd(e) == 2*n + 2 + assert dd(e2, n, 2) == k*(4*n + 6) + + raises(ValueError, lambda: dd(e2)) + raises(ValueError, lambda: dd(e2, n, oo)) + + +def test_difference_delta__Sum(): + e = Sum(1/k, (k, 1, n)) + assert dd(e, n) == 1/(n + 1) + assert dd(e, n, 5) == Add(*[1/(i + n + 1) for i in range(5)]) + + e = Sum(1/k, (k, 1, 3*n)) + assert dd(e, n) == Add(*[1/(i + 3*n + 1) for i in range(3)]) + + e = n * Sum(1/k, (k, 1, n)) + assert dd(e, n) == 1 + Sum(1/k, (k, 1, n)) + + e = Sum(1/k, (k, 1, n), (m, 1, n)) + assert dd(e, n) == harmonic(n) + + +def test_difference_delta__Add(): + e = n + n*(n + 1) + assert dd(e, n) == 2*n + 3 + assert dd(e, n, 2) == 4*n + 8 + + e = n + Sum(1/k, (k, 1, n)) + assert dd(e, n) == 1 + 1/(n + 1) + assert dd(e, n, 5) == 5 + Add(*[1/(i + n + 1) for i in range(5)]) + + +def test_difference_delta__Pow(): + e = 4**n + assert dd(e, n) == 3*4**n + assert dd(e, n, 2) == 15*4**n + + e = 4**(2*n) + assert dd(e, n) == 15*4**(2*n) + assert dd(e, n, 2) == 255*4**(2*n) + + e = n**4 + assert dd(e, n) == (n + 1)**4 - n**4 + + e = n**n + assert dd(e, n) == (n + 1)**(n + 1) - n**n + + +def test_limit_seq(): + e = binomial(2*n, n) / Sum(binomial(2*k, k), (k, 1, n)) + assert limit_seq(e) == S(3) / 4 + assert limit_seq(e, m) == e + + e = (5*n**3 + 3*n**2 + 4) / (3*n**3 + 4*n - 5) + assert limit_seq(e, n) == S(5) / 3 + + e = (harmonic(n) * Sum(harmonic(k), (k, 1, n))) / (n * harmonic(2*n)**2) + assert limit_seq(e, n) == 1 + + e = Sum(k**2 * Sum(2**m/m, (m, 1, k)), (k, 1, n)) / (2**n*n) + assert limit_seq(e, n) == 4 + + e = (Sum(binomial(3*k, k) * binomial(5*k, k), (k, 1, n)) / + (binomial(3*n, n) * binomial(5*n, n))) + assert limit_seq(e, n) == S(84375) / 83351 + + e = Sum(harmonic(k)**2/k, (k, 1, 2*n)) / harmonic(n)**3 + assert limit_seq(e, n) == S.One / 3 + + raises(ValueError, lambda: limit_seq(e * m)) + + +def test_alternating_sign(): + assert limit_seq((-1)**n/n**2, n) == 0 + assert limit_seq((-2)**(n+1)/(n + 3**n), n) == 0 + assert limit_seq((2*n + (-1)**n)/(n + 1), n) == 2 + assert limit_seq(sin(pi*n), n) == 0 + assert limit_seq(cos(2*pi*n), n) == 1 + assert limit_seq((S.NegativeOne/5)**n, n) == 0 + assert limit_seq((Rational(-1, 5))**n, n) == 0 + assert limit_seq((I/3)**n, n) == 0 + assert limit_seq(sqrt(n)*(I/2)**n, n) == 0 + assert limit_seq(n**7*(I/3)**n, n) == 0 + assert limit_seq(n/(n + 1) + (I/2)**n, n) == 1 + + +def test_accum_bounds(): + assert limit_seq((-1)**n, n) == AccumulationBounds(-1, 1) + assert limit_seq(cos(pi*n), n) == AccumulationBounds(-1, 1) + assert limit_seq(sin(pi*n/2)**2, n) == AccumulationBounds(0, 1) + assert limit_seq(2*(-3)**n/(n + 3**n), n) == AccumulationBounds(-2, 2) + assert limit_seq(3*n/(n + 1) + 2*(-1)**n, n) == AccumulationBounds(1, 5) + + +def test_limitseq_sum(): + from sympy.abc import x, y, z + assert limit_seq(Sum(1/x, (x, 1, y)) - log(y), y) == S.EulerGamma + assert limit_seq(Sum(1/x, (x, 1, y)) - 1/y, y) is S.Infinity + assert (limit_seq(binomial(2*x, x) / Sum(binomial(2*y, y), (y, 1, x)), x) == + S(3) / 4) + assert (limit_seq(Sum(y**2 * Sum(2**z/z, (z, 1, y)), (y, 1, x)) / + (2**x*x), x) == 4) + + +def test_issue_9308(): + assert limit_seq(subfactorial(n)/factorial(n), n) == exp(-1) + + +def test_issue_10382(): + n = Symbol('n', integer=True) + assert limit_seq(fibonacci(n+1)/fibonacci(n), n).together() == S.GoldenRatio + + +def test_issue_11672(): + assert limit_seq(Rational(-1, 2)**n, n) == 0 + + +def test_issue_14196(): + k, n = symbols('k, n', positive=True) + m = Symbol('m') + assert limit_seq(Sum(m**k, (m, 1, n)).doit()/(n**(k + 1)), n) == 1/(k + 1) + + +def test_issue_16735(): + assert limit_seq(5**n/factorial(n), n) == 0 + + +def test_issue_19868(): + assert limit_seq(1/gamma(n + S.One/2), n) == 0 + + +@XFAIL +def test_limit_seq_fail(): + # improve Summation algorithm or add ad-hoc criteria + e = (harmonic(n)**3 * Sum(1/harmonic(k), (k, 1, n)) / + (n * Sum(harmonic(k)/k, (k, 1, n)))) + assert limit_seq(e, n) == 2 + + # No unique dominant term + e = (Sum(2**k * binomial(2*k, k) / k**2, (k, 1, n)) / + (Sum(2**k/k*2, (k, 1, n)) * Sum(binomial(2*k, k), (k, 1, n)))) + assert limit_seq(e, n) == S(3) / 7 + + # Simplifications of summations needs to be improved. + e = n**3*Sum(2**k/k**2, (k, 1, n))**2 / (2**n * Sum(2**k/k, (k, 1, n))) + assert limit_seq(e, n) == 2 + + e = (harmonic(n) * Sum(2**k/k, (k, 1, n)) / + (n * Sum(2**k*harmonic(k)/k**2, (k, 1, n)))) + assert limit_seq(e, n) == 1 + + e = (Sum(2**k*factorial(k) / k**2, (k, 1, 2*n)) / + (Sum(4**k/k**2, (k, 1, n)) * Sum(factorial(k), (k, 1, 2*n)))) + assert limit_seq(e, n) == S(3) / 16 diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_lseries.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_lseries.py new file mode 100644 index 0000000000000000000000000000000000000000..42d327bf60c76eebdc4570d631efef4bc84b58e3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_lseries.py @@ -0,0 +1,65 @@ +from sympy.core.numbers import E +from sympy.core.singleton import S +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.hyperbolic import tanh +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.series.order import Order +from sympy.abc import x, y + + +def test_sin(): + e = sin(x).lseries(x) + assert next(e) == x + assert next(e) == -x**3/6 + assert next(e) == x**5/120 + + +def test_cos(): + e = cos(x).lseries(x) + assert next(e) == 1 + assert next(e) == -x**2/2 + assert next(e) == x**4/24 + + +def test_exp(): + e = exp(x).lseries(x) + assert next(e) == 1 + assert next(e) == x + assert next(e) == x**2/2 + assert next(e) == x**3/6 + + +def test_exp2(): + e = exp(cos(x)).lseries(x) + assert next(e) == E + assert next(e) == -E*x**2/2 + assert next(e) == E*x**4/6 + assert next(e) == -31*E*x**6/720 + + +def test_simple(): + assert list(x.lseries()) == [x] + assert list(S.One.lseries(x)) == [1] + assert not next((x/(x + y)).lseries(y)).has(Order) + + +def test_issue_5183(): + s = (x + 1/x).lseries() + assert list(s) == [1/x, x] + assert next((x + x**2).lseries()) == x + assert next(((1 + x)**7).lseries(x)) == 1 + assert next((sin(x + y)).series(x, n=3).lseries(y)) == x + # it would be nice if all terms were grouped, but in the + # following case that would mean that all the terms would have + # to be known since, for example, every term has a constant in it. + s = ((1 + x)**7).series(x, 1, n=None) + assert [next(s) for i in range(2)] == [128, -448 + 448*x] + + +def test_issue_6999(): + s = tanh(x).lseries(x, 1) + assert next(s) == tanh(1) + assert next(s) == x - (x - 1)*tanh(1)**2 - 1 + assert next(s) == -(x - 1)**2*tanh(1) + (x - 1)**2*tanh(1)**3 + assert next(s) == -(x - 1)**3*tanh(1)**4 - (x - 1)**3/3 + \ + 4*(x - 1)**3*tanh(1)**2/3 diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_nseries.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_nseries.py new file mode 100644 index 0000000000000000000000000000000000000000..a2f20add82d3e858e2ce145fc9fcd4a6548a48cc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_nseries.py @@ -0,0 +1,557 @@ +from sympy.calculus.util import AccumBounds +from sympy.core.function import (Derivative, PoleError) +from sympy.core.numbers import (E, I, Integer, Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import sign +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (acosh, acoth, asinh, atanh, cosh, coth, sinh, tanh) +from sympy.functions.elementary.integers import (ceiling, floor, frac) +from sympy.functions.elementary.miscellaneous import (cbrt, sqrt) +from sympy.functions.elementary.trigonometric import (asin, cos, cot, sin, tan) +from sympy.series.limits import limit +from sympy.series.order import O +from sympy.abc import x, y, z + +from sympy.testing.pytest import raises, XFAIL + + +def test_simple_1(): + assert x.nseries(x, n=5) == x + assert y.nseries(x, n=5) == y + assert (1/(x*y)).nseries(y, n=5) == 1/(x*y) + assert Rational(3, 4).nseries(x, n=5) == Rational(3, 4) + assert x.nseries() == x + + +def test_mul_0(): + assert (x*log(x)).nseries(x, n=5) == x*log(x) + + +def test_mul_1(): + assert (x*log(2 + x)).nseries(x, n=5) == x*log(2) + x**2/2 - x**3/8 + \ + x**4/24 + O(x**5) + assert (x*log(1 + x)).nseries( + x, n=5) == x**2 - x**3/2 + x**4/3 + O(x**5) + + +def test_pow_0(): + assert (x**2).nseries(x, n=5) == x**2 + assert (1/x).nseries(x, n=5) == 1/x + assert (1/x**2).nseries(x, n=5) == 1/x**2 + assert (x**Rational(2, 3)).nseries(x, n=5) == (x**Rational(2, 3)) + assert (sqrt(x)**3).nseries(x, n=5) == (sqrt(x)**3) + + +def test_pow_1(): + assert ((1 + x)**2).nseries(x, n=5) == x**2 + 2*x + 1 + + # https://github.com/sympy/sympy/issues/21075 + assert ((sqrt(x) + 1)**2).nseries(x) == 2*sqrt(x) + x + 1 + assert ((sqrt(x) + cbrt(x))**2).nseries(x) == 2*x**Rational(5, 6)\ + + x**Rational(2, 3) + x + + +def test_geometric_1(): + assert (1/(1 - x)).nseries(x, n=5) == 1 + x + x**2 + x**3 + x**4 + O(x**5) + assert (x/(1 - x)).nseries(x, n=6) == x + x**2 + x**3 + x**4 + x**5 + O(x**6) + assert (x**3/(1 - x)).nseries(x, n=8) == x**3 + x**4 + x**5 + x**6 + \ + x**7 + O(x**8) + + +def test_sqrt_1(): + assert sqrt(1 + x).nseries(x, n=5) == 1 + x/2 - x**2/8 + x**3/16 - 5*x**4/128 + O(x**5) + + +def test_exp_1(): + assert exp(x).nseries(x, n=5) == 1 + x + x**2/2 + x**3/6 + x**4/24 + O(x**5) + assert exp(x).nseries(x, n=12) == 1 + x + x**2/2 + x**3/6 + x**4/24 + x**5/120 + \ + x**6/720 + x**7/5040 + x**8/40320 + x**9/362880 + x**10/3628800 + \ + x**11/39916800 + O(x**12) + assert exp(1/x).nseries(x, n=5) == exp(1/x) + assert exp(1/(1 + x)).nseries(x, n=4) == \ + (E*(1 - x - 13*x**3/6 + 3*x**2/2)).expand() + O(x**4) + assert exp(2 + x).nseries(x, n=5) == \ + (exp(2)*(1 + x + x**2/2 + x**3/6 + x**4/24)).expand() + O(x**5) + + +def test_exp_sqrt_1(): + assert exp(1 + sqrt(x)).nseries(x, n=3) == \ + (exp(1)*(1 + sqrt(x) + x/2 + sqrt(x)*x/6)).expand() + O(sqrt(x)**3) + + +def test_power_x_x1(): + assert (exp(x*log(x))).nseries(x, n=4) == \ + 1 + x*log(x) + x**2*log(x)**2/2 + x**3*log(x)**3/6 + O(x**4*log(x)**4) + + +def test_power_x_x2(): + assert (x**x).nseries(x, n=4) == \ + 1 + x*log(x) + x**2*log(x)**2/2 + x**3*log(x)**3/6 + O(x**4*log(x)**4) + + +def test_log_singular1(): + assert log(1 + 1/x).nseries(x, n=5) == x - log(x) - x**2/2 + x**3/3 - \ + x**4/4 + O(x**5) + + +def test_log_power1(): + e = 1 / (1/x + x ** (log(3)/log(2))) + assert e.nseries(x, n=5) == -x**(log(3)/log(2) + 2) + x + O(x**5) + + +def test_log_series(): + l = Symbol('l') + e = 1/(1 - log(x)) + assert e.nseries(x, n=5, logx=l) == 1/(1 - l) + + +def test_log2(): + e = log(-1/x) + assert e.nseries(x, n=5) == -log(x) + log(-1) + + +def test_log3(): + l = Symbol('l') + e = 1/log(-1/x) + assert e.nseries(x, n=4, logx=l) == 1/(-l + log(-1)) + + +def test_series1(): + e = sin(x) + assert e.nseries(x, 0, 0) != 0 + assert e.nseries(x, 0, 0) == O(1, x) + assert e.nseries(x, 0, 1) == O(x, x) + assert e.nseries(x, 0, 2) == x + O(x**2, x) + assert e.nseries(x, 0, 3) == x + O(x**3, x) + assert e.nseries(x, 0, 4) == x - x**3/6 + O(x**4, x) + + e = (exp(x) - 1)/x + assert e.nseries(x, 0, 3) == 1 + x/2 + x**2/6 + O(x**3) + + assert x.nseries(x, 0, 2) == x + + +@XFAIL +def test_series1_failing(): + assert x.nseries(x, 0, 0) == O(1, x) + assert x.nseries(x, 0, 1) == O(x, x) + + +def test_seriesbug1(): + assert (1/x).nseries(x, 0, 3) == 1/x + assert (x + 1/x).nseries(x, 0, 3) == x + 1/x + + +def test_series2x(): + assert ((x + 1)**(-2)).nseries(x, 0, 4) == 1 - 2*x + 3*x**2 - 4*x**3 + O(x**4, x) + assert ((x + 1)**(-1)).nseries(x, 0, 4) == 1 - x + x**2 - x**3 + O(x**4, x) + assert ((x + 1)**0).nseries(x, 0, 3) == 1 + assert ((x + 1)**1).nseries(x, 0, 3) == 1 + x + assert ((x + 1)**2).nseries(x, 0, 3) == x**2 + 2*x + 1 + assert ((x + 1)**3).nseries(x, 0, 3) == 1 + 3*x + 3*x**2 + O(x**3) + + assert (1/(1 + x)).nseries(x, 0, 4) == 1 - x + x**2 - x**3 + O(x**4, x) + assert (x + 3/(1 + 2*x)).nseries(x, 0, 4) == 3 - 5*x + 12*x**2 - 24*x**3 + O(x**4, x) + + assert ((1/x + 1)**3).nseries(x, 0, 3) == 1 + 3/x + 3/x**2 + x**(-3) + assert (1/(1 + 1/x)).nseries(x, 0, 4) == x - x**2 + x**3 - O(x**4, x) + assert (1/(1 + 1/x**2)).nseries(x, 0, 6) == x**2 - x**4 + O(x**6, x) + + +def test_bug2(): # 1/log(0)*log(0) problem + w = Symbol("w") + e = (w**(-1) + w**( + -log(3)*log(2)**(-1)))**(-1)*(3*w**(-log(3)*log(2)**(-1)) + 2*w**(-1)) + e = e.expand() + assert e.nseries(w, 0, 4).subs(w, 0) == 3 + + +def test_exp(): + e = (1 + x)**(1/x) + assert e.nseries(x, n=3) == exp(1) - x*exp(1)/2 + 11*exp(1)*x**2/24 + O(x**3) + + +def test_exp2(): + w = Symbol("w") + e = w**(1 - log(x)/(log(2) + log(x))) + logw = Symbol("logw") + assert e.nseries( + w, 0, 1, logx=logw) == exp(logw*log(2)/(log(x) + log(2))) + + +def test_bug3(): + e = (2/x + 3/x**2)/(1/x + 1/x**2) + assert e.nseries(x, n=3) == 3 - x + x**2 + O(x**3) + + +def test_generalexponent(): + p = 2 + e = (2/x + 3/x**p)/(1/x + 1/x**p) + assert e.nseries(x, 0, 3) == 3 - x + x**2 + O(x**3) + p = S.Half + e = (2/x + 3/x**p)/(1/x + 1/x**p) + assert e.nseries(x, 0, 2) == 2 - x + sqrt(x) + x**(S(3)/2) + O(x**2) + + e = 1 + sqrt(x) + assert e.nseries(x, 0, 4) == 1 + sqrt(x) + +# more complicated example + + +def test_genexp_x(): + e = 1/(1 + sqrt(x)) + assert e.nseries(x, 0, 2) == \ + 1 + x - sqrt(x) - sqrt(x)**3 + O(x**2, x) + +# more complicated example + + +def test_genexp_x2(): + p = Rational(3, 2) + e = (2/x + 3/x**p)/(1/x + 1/x**p) + assert e.nseries(x, 0, 3) == 3 + x + x**2 - sqrt(x) - x**(S(3)/2) - x**(S(5)/2) + O(x**3) + + +def test_seriesbug2(): + w = Symbol("w") + #simple case (1): + e = ((2*w)/w)**(1 + w) + assert e.nseries(w, 0, 1) == 2 + O(w, w) + assert e.nseries(w, 0, 1).subs(w, 0) == 2 + + +def test_seriesbug2b(): + w = Symbol("w") + #test sin + e = sin(2*w)/w + assert e.nseries(w, 0, 3) == 2 - 4*w**2/3 + O(w**3) + + +def test_seriesbug2d(): + w = Symbol("w", real=True) + e = log(sin(2*w)/w) + assert e.series(w, n=5) == log(2) - 2*w**2/3 - 4*w**4/45 + O(w**5) + + +def test_seriesbug2c(): + w = Symbol("w", real=True) + #more complicated case, but sin(x)~x, so the result is the same as in (1) + e = (sin(2*w)/w)**(1 + w) + assert e.series(w, 0, 1) == 2 + O(w) + assert e.series(w, 0, 3) == 2 + 2*w*log(2) + \ + w**2*(Rational(-4, 3) + log(2)**2) + O(w**3) + assert e.series(w, 0, 2).subs(w, 0) == 2 + + +def test_expbug4(): + x = Symbol("x", real=True) + assert (log( + sin(2*x)/x)*(1 + x)).series(x, 0, 2) == log(2) + x*log(2) + O(x**2, x) + assert exp( + log(sin(2*x)/x)*(1 + x)).series(x, 0, 2) == 2 + 2*x*log(2) + O(x**2) + + assert exp(log(2) + O(x)).nseries(x, 0, 2) == 2 + O(x) + assert ((2 + O(x))**(1 + x)).nseries(x, 0, 2) == 2 + O(x) + + +def test_logbug4(): + assert log(2 + O(x)).nseries(x, 0, 2) == log(2) + O(x, x) + + +def test_expbug5(): + assert exp(log(1 + x)/x).nseries(x, n=3) == exp(1) + -exp(1)*x/2 + 11*exp(1)*x**2/24 + O(x**3) + + assert exp(O(x)).nseries(x, 0, 2) == 1 + O(x) + + +def test_sinsinbug(): + assert sin(sin(x)).nseries(x, 0, 8) == x - x**3/3 + x**5/10 - 8*x**7/315 + O(x**8) + + +def test_issue_3258(): + a = x/(exp(x) - 1) + assert a.nseries(x, 0, 5) == 1 - x/2 - x**4/720 + x**2/12 + O(x**5) + + +def test_issue_3204(): + x = Symbol("x", nonnegative=True) + f = sin(x**3)**Rational(1, 3) + assert f.nseries(x, 0, 17) == x - x**7/18 - x**13/3240 + O(x**17) + + +def test_issue_3224(): + f = sqrt(1 - sqrt(y)) + assert f.nseries(y, 0, 2) == 1 - sqrt(y)/2 - y/8 - sqrt(y)**3/16 + O(y**2) + + +def test_issue_3463(): + w, i = symbols('w,i') + r = log(5)/log(3) + p = w**(-1 + r) + e = 1/x*(-log(w**(1 + r)) + log(w + w**r)) + e_ser = -r*log(w)/x + p/x - p**2/(2*x) + O(w) + assert e.nseries(w, n=1) == e_ser + + +def test_sin(): + assert sin(8*x).nseries(x, n=4) == 8*x - 256*x**3/3 + O(x**4) + assert sin(x + y).nseries(x, n=1) == sin(y) + O(x) + assert sin(x + y).nseries(x, n=2) == sin(y) + cos(y)*x + O(x**2) + assert sin(x + y).nseries(x, n=5) == sin(y) + cos(y)*x - sin(y)*x**2/2 - \ + cos(y)*x**3/6 + sin(y)*x**4/24 + O(x**5) + + +def test_issue_3515(): + e = sin(8*x)/x + assert e.nseries(x, n=6) == 8 - 256*x**2/3 + 4096*x**4/15 + O(x**6) + + +def test_issue_3505(): + e = sin(x)**(-4)*(sqrt(cos(x))*sin(x)**2 - + cos(x)**Rational(1, 3)*sin(x)**2) + assert e.nseries(x, n=9) == Rational(-1, 12) - 7*x**2/288 - \ + 43*x**4/10368 - 1123*x**6/2488320 + 377*x**8/29859840 + O(x**9) + + +def test_issue_3501(): + a = Symbol("a") + e = x**(-2)*(x*sin(a + x) - x*sin(a)) + assert e.nseries(x, n=6) == cos(a) - sin(a)*x/2 - cos(a)*x**2/6 + \ + x**3*sin(a)/24 + x**4*cos(a)/120 - x**5*sin(a)/720 + O(x**6) + e = x**(-2)*(x*cos(a + x) - x*cos(a)) + assert e.nseries(x, n=6) == -sin(a) - cos(a)*x/2 + sin(a)*x**2/6 + \ + cos(a)*x**3/24 - x**4*sin(a)/120 - x**5*cos(a)/720 + O(x**6) + + +def test_issue_3502(): + e = sin(5*x)/sin(2*x) + assert e.nseries(x, n=2) == Rational(5, 2) + O(x**2) + assert e.nseries(x, n=6) == \ + Rational(5, 2) - 35*x**2/4 + 329*x**4/48 + O(x**6) + + +def test_issue_3503(): + e = sin(2 + x)/(2 + x) + assert e.nseries(x, n=2) == sin(2)/2 + x*cos(2)/2 - x*sin(2)/4 + O(x**2) + + +def test_issue_3506(): + e = (x + sin(3*x))**(-2)*(x*(x + sin(3*x)) - (x + sin(3*x))*sin(2*x)) + assert e.nseries(x, n=7) == \ + Rational(-1, 4) + 5*x**2/96 + 91*x**4/768 + 11117*x**6/129024 + O(x**7) + + +def test_issue_3508(): + x = Symbol("x", real=True) + assert log(sin(x)).series(x, n=5) == log(x) - x**2/6 - x**4/180 + O(x**5) + e = -log(x) + x*(-log(x) + log(sin(2*x))) + log(sin(2*x)) + assert e.series(x, n=5) == \ + log(2) + log(2)*x - 2*x**2/3 - 2*x**3/3 - 4*x**4/45 + O(x**5) + + +def test_issue_3507(): + e = x**(-4)*(x**2 - x**2*sqrt(cos(x))) + assert e.nseries(x, n=9) == \ + Rational(1, 4) + x**2/96 + 19*x**4/5760 + 559*x**6/645120 + 29161*x**8/116121600 + O(x**9) + + +def test_issue_3639(): + assert sin(cos(x)).nseries(x, n=5) == \ + sin(1) - x**2*cos(1)/2 - x**4*sin(1)/8 + x**4*cos(1)/24 + O(x**5) + + +def test_hyperbolic(): + assert sinh(x).nseries(x, n=6) == x + x**3/6 + x**5/120 + O(x**6) + assert cosh(x).nseries(x, n=5) == 1 + x**2/2 + x**4/24 + O(x**5) + assert tanh(x).nseries(x, n=6) == x - x**3/3 + 2*x**5/15 + O(x**6) + assert coth(x).nseries(x, n=6) == \ + 1/x - x**3/45 + x/3 + 2*x**5/945 + O(x**6) + assert asinh(x).nseries(x, n=6) == x - x**3/6 + 3*x**5/40 + O(x**6) + assert acosh(x).nseries(x, n=6) == \ + pi*I/2 - I*x - 3*I*x**5/40 - I*x**3/6 + O(x**6) + assert atanh(x).nseries(x, n=6) == x + x**3/3 + x**5/5 + O(x**6) + assert acoth(x).nseries(x, n=6) == -I*pi/2 + x + x**3/3 + x**5/5 + O(x**6) + + +def test_series2(): + w = Symbol("w", real=True) + x = Symbol("x", real=True) + e = w**(-2)*(w*exp(1/x - w) - w*exp(1/x)) + assert e.nseries(w, n=4) == -exp(1/x) + w*exp(1/x)/2 - w**2*exp(1/x)/6 + w**3*exp(1/x)/24 + O(w**4) + + +def test_series3(): + w = Symbol("w", real=True) + e = w**(-6)*(w**3*tan(w) - w**3*sin(w)) + assert e.nseries(w, n=8) == Integer(1)/2 + w**2/8 + 13*w**4/240 + 529*w**6/24192 + O(w**8) + + +def test_bug4(): + w = Symbol("w") + e = x/(w**4 + x**2*w**4 + 2*x*w**4)*w**4 + assert e.nseries(w, n=2).removeO().expand() in [x/(1 + 2*x + x**2), + 1/(1 + x/2 + 1/x/2)/2, 1/x/(1 + 2/x + x**(-2))] + + +def test_bug5(): + w = Symbol("w") + l = Symbol('l') + e = (-log(w) + log(1 + w*log(x)))**(-2)*w**(-2)*((-log(w) + + log(1 + x*w))*(-log(w) + log(1 + w*log(x)))*w - x*(-log(w) + + log(1 + w*log(x)))*w) + assert e.nseries(w, n=0, logx=l) == x/w/l + 1/w + O(1, w) + assert e.nseries(w, n=1, logx=l) == x/w/l + 1/w - x/l + 1/l*log(x) \ + + x*log(x)/l**2 + O(w) + + +def test_issue_4115(): + assert (sin(x)/(1 - cos(x))).nseries(x, n=1) == 2/x + O(x) + assert (sin(x)**2/(1 - cos(x))).nseries(x, n=1) == 2 + O(x) + + +def test_pole(): + raises(PoleError, lambda: sin(1/x).series(x, 0, 5)) + raises(PoleError, lambda: sin(1 + 1/x).series(x, 0, 5)) + raises(PoleError, lambda: (x*sin(1/x)).series(x, 0, 5)) + + +def test_expsinbug(): + assert exp(sin(x)).series(x, 0, 0) == O(1, x) + assert exp(sin(x)).series(x, 0, 1) == 1 + O(x) + assert exp(sin(x)).series(x, 0, 2) == 1 + x + O(x**2) + assert exp(sin(x)).series(x, 0, 3) == 1 + x + x**2/2 + O(x**3) + assert exp(sin(x)).series(x, 0, 4) == 1 + x + x**2/2 + O(x**4) + assert exp(sin(x)).series(x, 0, 5) == 1 + x + x**2/2 - x**4/8 + O(x**5) + + +def test_floor(): + x = Symbol('x') + assert floor(x).series(x) == 0 + assert floor(-x).series(x) == -1 + assert floor(sin(x)).series(x) == 0 + assert floor(sin(-x)).series(x) == -1 + assert floor(x**3).series(x) == 0 + assert floor(-x**3).series(x) == -1 + assert floor(cos(x)).series(x) == 0 + assert floor(cos(-x)).series(x) == 0 + assert floor(5 + sin(x)).series(x) == 5 + assert floor(5 + sin(-x)).series(x) == 4 + + assert floor(x).series(x, 2) == 2 + assert floor(-x).series(x, 2) == -3 + + x = Symbol('x', negative=True) + assert floor(x + 1.5).series(x) == 1 + + +def test_frac(): + assert frac(x).series(x, cdir=1) == x + assert frac(x).series(x, cdir=-1) == 1 + x + assert frac(2*x + 1).series(x, cdir=1) == 2*x + assert frac(2*x + 1).series(x, cdir=-1) == 1 + 2*x + assert frac(x**2).series(x, cdir=1) == x**2 + assert frac(x**2).series(x, cdir=-1) == x**2 + assert frac(sin(x) + 5).series(x, cdir=1) == x - x**3/6 + x**5/120 + O(x**6) + assert frac(sin(x) + 5).series(x, cdir=-1) == 1 + x - x**3/6 + x**5/120 + O(x**6) + assert frac(sin(x) + S.Half).series(x) == S.Half + x - x**3/6 + x**5/120 + O(x**6) + assert frac(x**8).series(x, cdir=1) == O(x**6) + assert frac(1/x).series(x) == AccumBounds(0, 1) + O(x**6) + + +def test_ceiling(): + assert ceiling(x).series(x) == 1 + assert ceiling(-x).series(x) == 0 + assert ceiling(sin(x)).series(x) == 1 + assert ceiling(sin(-x)).series(x) == 0 + assert ceiling(1 - cos(x)).series(x) == 1 + assert ceiling(1 - cos(-x)).series(x) == 1 + assert ceiling(x).series(x, 2) == 3 + assert ceiling(-x).series(x, 2) == -2 + + +def test_abs(): + a = Symbol('a') + assert abs(x).nseries(x, n=4) == x + assert abs(-x).nseries(x, n=4) == x + assert abs(x + 1).nseries(x, n=4) == x + 1 + assert abs(sin(x)).nseries(x, n=4) == x - Rational(1, 6)*x**3 + O(x**4) + assert abs(sin(-x)).nseries(x, n=4) == x - Rational(1, 6)*x**3 + O(x**4) + assert abs(x - a).nseries(x, 1) == -a*sign(1 - a) + (x - 1)*sign(1 - a) + sign(1 - a) + + +def test_dir(): + assert abs(x).series(x, 0, dir="+") == x + assert abs(x).series(x, 0, dir="-") == -x + assert floor(x + 2).series(x, 0, dir='+') == 2 + assert floor(x + 2).series(x, 0, dir='-') == 1 + assert floor(x + 2.2).series(x, 0, dir='-') == 2 + assert ceiling(x + 2.2).series(x, 0, dir='-') == 3 + assert sin(x + y).series(x, 0, dir='-') == sin(x + y).series(x, 0, dir='+') + + +def test_cdir(): + assert abs(x).series(x, 0, cdir=1) == x + assert abs(x).series(x, 0, cdir=-1) == -x + assert floor(x + 2).series(x, 0, cdir=1) == 2 + assert floor(x + 2).series(x, 0, cdir=-1) == 1 + assert floor(x + 2.2).series(x, 0, cdir=1) == 2 + assert ceiling(x + 2.2).series(x, 0, cdir=-1) == 3 + assert sin(x + y).series(x, 0, cdir=-1) == sin(x + y).series(x, 0, cdir=1) + + +def test_issue_3504(): + a = Symbol("a") + e = asin(a*x)/x + assert e.series(x, 4, n=2).removeO() == \ + (x - 4)*(a/(4*sqrt(-16*a**2 + 1)) - asin(4*a)/16) + asin(4*a)/4 + + +def test_issue_4441(): + a, b = symbols('a,b') + f = 1/(1 + a*x) + assert f.series(x, 0, 5) == 1 - a*x + a**2*x**2 - a**3*x**3 + \ + a**4*x**4 + O(x**5) + f = 1/(1 + (a + b)*x) + assert f.series(x, 0, 3) == 1 + x*(-a - b)\ + + x**2*(a + b)**2 + O(x**3) + + +def test_issue_4329(): + assert tan(x).series(x, pi/2, n=3).removeO() == \ + -pi/6 + x/3 - 1/(x - pi/2) + assert cot(x).series(x, pi, n=3).removeO() == \ + -x/3 + pi/3 + 1/(x - pi) + assert limit(tan(x)**tan(2*x), x, pi/4) == exp(-1) + + +def test_issue_5183(): + assert abs(x + x**2).series(n=1) == O(x) + assert abs(x + x**2).series(n=2) == x + O(x**2) + assert ((1 + x)**2).series(x, n=6) == x**2 + 2*x + 1 + assert (1 + 1/x).series() == 1 + 1/x + assert Derivative(exp(x).series(), x).doit() == \ + 1 + x + x**2/2 + x**3/6 + x**4/24 + O(x**5) + + +def test_issue_5654(): + a = Symbol('a') + assert (1/(x**2+a**2)**2).nseries(x, x0=I*a, n=0) == \ + -I/(4*a**3*(-I*a + x)) - 1/(4*a**2*(-I*a + x)**2) + O(1, (x, I*a)) + assert (1/(x**2+a**2)**2).nseries(x, x0=I*a, n=1) == 3/(16*a**4) \ + -I/(4*a**3*(-I*a + x)) - 1/(4*a**2*(-I*a + x)**2) + O(-I*a + x, (x, I*a)) + + +def test_issue_5925(): + sx = sqrt(x + z).series(z, 0, 1) + sxy = sqrt(x + y + z).series(z, 0, 1) + s1, s2 = sx.subs(x, x + y), sxy + assert (s1 - s2).expand().removeO().simplify() == 0 + + sx = sqrt(x + z).series(z, 0, 1) + sxy = sqrt(x + y + z).series(z, 0, 1) + assert sxy.subs({x:1, y:2}) == sx.subs(x, 3) + + +def test_exp_2(): + assert exp(x**3).nseries(x, 0, 14) == 1 + x**3 + x**6/2 + x**9/6 + x**12/24 + O(x**14) diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_order.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_order.py new file mode 100644 index 0000000000000000000000000000000000000000..50fcb861ee2a76c730baae6d26cc1e7a00347176 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_order.py @@ -0,0 +1,503 @@ +from sympy.core.add import Add +from sympy.core.function import (Function, expand) +from sympy.core.numbers import (I, Rational, nan, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (conjugate, transpose) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.integrals.integrals import Integral +from sympy.series.order import O, Order +from sympy.core.expr import unchanged +from sympy.testing.pytest import raises +from sympy.abc import w, x, y, z +from sympy.testing.pytest import XFAIL + + +def test_caching_bug(): + #needs to be a first test, so that all caches are clean + #cache it + O(w) + #and test that this won't raise an exception + O(w**(-1/x/log(3)*log(5)), w) + + +def test_free_symbols(): + assert Order(1).free_symbols == set() + assert Order(x).free_symbols == {x} + assert Order(1, x).free_symbols == {x} + assert Order(x*y).free_symbols == {x, y} + assert Order(x, x, y).free_symbols == {x, y} + + +def test_simple_1(): + o = Rational(0) + assert Order(2*x) == Order(x) + assert Order(x)*3 == Order(x) + assert -28*Order(x) == Order(x) + assert Order(Order(x)) == Order(x) + assert Order(Order(x), y) == Order(Order(x), x, y) + assert Order(-23) == Order(1) + assert Order(exp(x)) == Order(1, x) + assert Order(exp(1/x)).expr == exp(1/x) + assert Order(x*exp(1/x)).expr == x*exp(1/x) + assert Order(x**(o/3)).expr == x**(o/3) + assert Order(x**(o*Rational(5, 3))).expr == x**(o*Rational(5, 3)) + assert Order(x**2 + x + y, x) == O(1, x) + assert Order(x**2 + x + y, y) == O(1, y) + raises(ValueError, lambda: Order(exp(x), x, x)) + raises(TypeError, lambda: Order(x, 2 - x)) + + +def test_simple_2(): + assert Order(2*x)*x == Order(x**2) + assert Order(2*x)/x == Order(1, x) + assert Order(2*x)*x*exp(1/x) == Order(x**2*exp(1/x)) + assert (Order(2*x)*x*exp(1/x)/log(x)**3).expr == x**2*exp(1/x)*log(x)**-3 + + +def test_simple_3(): + assert Order(x) + x == Order(x) + assert Order(x) + 2 == 2 + Order(x) + assert Order(x) + x**2 == Order(x) + assert Order(x) + 1/x == 1/x + Order(x) + assert Order(1/x) + 1/x**2 == 1/x**2 + Order(1/x) + assert Order(x) + exp(1/x) == Order(x) + exp(1/x) + + +def test_simple_4(): + assert Order(x)**2 == Order(x**2) + + +def test_simple_5(): + assert Order(x) + Order(x**2) == Order(x) + assert Order(x) + Order(x**-2) == Order(x**-2) + assert Order(x) + Order(1/x) == Order(1/x) + + +def test_simple_6(): + assert Order(x) - Order(x) == Order(x) + assert Order(x) + Order(1) == Order(1) + assert Order(x) + Order(x**2) == Order(x) + assert Order(1/x) + Order(1) == Order(1/x) + assert Order(x) + Order(exp(1/x)) == Order(exp(1/x)) + assert Order(x**3) + Order(exp(2/x)) == Order(exp(2/x)) + assert Order(x**-3) + Order(exp(2/x)) == Order(exp(2/x)) + + +def test_simple_7(): + assert 1 + O(1) == O(1) + assert 2 + O(1) == O(1) + assert x + O(1) == O(1) + assert 1/x + O(1) == 1/x + O(1) + + +def test_simple_8(): + assert O(sqrt(-x)) == O(sqrt(x)) + assert O(x**2*sqrt(x)) == O(x**Rational(5, 2)) + assert O(x**3*sqrt(-(-x)**3)) == O(x**Rational(9, 2)) + assert O(x**Rational(3, 2)*sqrt((-x)**3)) == O(x**3) + assert O(x*(-2*x)**(I/2)) == O(x*(-x)**(I/2)) + + +def test_as_expr_variables(): + assert Order(x).as_expr_variables(None) == (x, ((x, 0),)) + assert Order(x).as_expr_variables(((x, 0),)) == (x, ((x, 0),)) + assert Order(y).as_expr_variables(((x, 0),)) == (y, ((x, 0), (y, 0))) + assert Order(y).as_expr_variables(((x, 0), (y, 0))) == (y, ((x, 0), (y, 0))) + + +def test_contains_0(): + assert Order(1, x).contains(Order(1, x)) + assert Order(1, x).contains(Order(1)) + assert Order(1).contains(Order(1, x)) is False + + +def test_contains_1(): + assert Order(x).contains(Order(x)) + assert Order(x).contains(Order(x**2)) + assert not Order(x**2).contains(Order(x)) + assert not Order(x).contains(Order(1/x)) + assert not Order(1/x).contains(Order(exp(1/x))) + assert not Order(x).contains(Order(exp(1/x))) + assert Order(1/x).contains(Order(x)) + assert Order(exp(1/x)).contains(Order(x)) + assert Order(exp(1/x)).contains(Order(1/x)) + assert Order(exp(1/x)).contains(Order(exp(1/x))) + assert Order(exp(2/x)).contains(Order(exp(1/x))) + assert not Order(exp(1/x)).contains(Order(exp(2/x))) + + +def test_contains_2(): + assert Order(x).contains(Order(y)) is None + assert Order(x).contains(Order(y*x)) + assert Order(y*x).contains(Order(x)) + assert Order(y).contains(Order(x*y)) + assert Order(x).contains(Order(y**2*x)) + + +def test_contains_3(): + assert Order(x*y**2).contains(Order(x**2*y)) is None + assert Order(x**2*y).contains(Order(x*y**2)) is None + + +def test_contains_4(): + assert Order(sin(1/x**2)).contains(Order(cos(1/x**2))) is True + assert Order(cos(1/x**2)).contains(Order(sin(1/x**2))) is True + + +def test_contains(): + assert Order(1, x) not in Order(1) + assert Order(1) in Order(1, x) + raises(TypeError, lambda: Order(x*y**2) in Order(x**2*y)) + + +def test_add_1(): + assert Order(x + x) == Order(x) + assert Order(3*x - 2*x**2) == Order(x) + assert Order(1 + x) == Order(1, x) + assert Order(1 + 1/x) == Order(1/x) + # TODO : A better output for Order(log(x) + 1/log(x)) + # could be Order(log(x)). Currently Order for expressions + # where all arguments would involve a log term would fall + # in this category and outputs for these should be improved. + assert Order(log(x) + 1/log(x)) == Order((log(x)**2 + 1)/log(x)) + assert Order(exp(1/x) + x) == Order(exp(1/x)) + assert Order(exp(1/x) + 1/x**20) == Order(exp(1/x)) + + +def test_ln_args(): + assert O(log(x)) + O(log(2*x)) == O(log(x)) + assert O(log(x)) + O(log(x**3)) == O(log(x)) + assert O(log(x*y)) + O(log(x) + log(y)) == O(log(x) + log(y), x, y) + + +def test_multivar_0(): + assert Order(x*y).expr == x*y + assert Order(x*y**2).expr == x*y**2 + assert Order(x*y, x).expr == x + assert Order(x*y**2, y).expr == y**2 + assert Order(x*y*z).expr == x*y*z + assert Order(x/y).expr == x/y + assert Order(x*exp(1/y)).expr == x*exp(1/y) + assert Order(exp(x)*exp(1/y)).expr == exp(x)*exp(1/y) + + +def test_multivar_0a(): + assert Order(exp(1/x)*exp(1/y)).expr == exp(1/x)*exp(1/y) + + +def test_multivar_1(): + assert Order(x + y).expr == x + y + assert Order(x + 2*y).expr == x + y + assert (Order(x + y) + x).expr == (x + y) + assert (Order(x + y) + x**2) == Order(x + y) + assert (Order(x + y) + 1/x) == 1/x + Order(x + y) + assert Order(x**2 + y*x).expr == x**2 + y*x + + +def test_multivar_2(): + assert Order(x**2*y + y**2*x, x, y).expr == x**2*y + y**2*x + + +def test_multivar_mul_1(): + assert Order(x + y)*x == Order(x**2 + y*x, x, y) + + +def test_multivar_3(): + assert (Order(x) + Order(y)).args in [ + (Order(x), Order(y)), + (Order(y), Order(x))] + assert Order(x) + Order(y) + Order(x + y) == Order(x + y) + assert (Order(x**2*y) + Order(y**2*x)).args in [ + (Order(x*y**2), Order(y*x**2)), + (Order(y*x**2), Order(x*y**2))] + assert (Order(x**2*y) + Order(y*x)) == Order(x*y) + + +def test_issue_3468(): + y = Symbol('y', negative=True) + z = Symbol('z', complex=True) + + # check that Order does not modify assumptions about symbols + Order(x) + Order(y) + Order(z) + + assert x.is_positive is None + assert y.is_positive is False + assert z.is_positive is None + + +def test_leading_order(): + assert (x + 1 + 1/x**5).extract_leading_order(x) == ((1/x**5, O(1/x**5)),) + assert (1 + 1/x).extract_leading_order(x) == ((1/x, O(1/x)),) + assert (1 + x).extract_leading_order(x) == ((1, O(1, x)),) + assert (1 + x**2).extract_leading_order(x) == ((1, O(1, x)),) + assert (2 + x**2).extract_leading_order(x) == ((2, O(1, x)),) + assert (x + x**2).extract_leading_order(x) == ((x, O(x)),) + + +def test_leading_order2(): + assert set((2 + pi + x**2).extract_leading_order(x)) == {(pi, O(1, x)), + (S(2), O(1, x))} + assert set((2*x + pi*x + x**2).extract_leading_order(x)) == {(2*x, O(x)), + (x*pi, O(x))} + + +def test_order_leadterm(): + assert O(x**2)._eval_as_leading_term(x, None, 1) == O(x**2) + + +def test_order_symbols(): + e = x*y*sin(x)*Integral(x, (x, 1, 2)) + assert O(e) == O(x**2*y, x, y) + assert O(e, x) == O(x**2) + + +def test_nan(): + assert O(nan) is nan + assert not O(x).contains(nan) + + +def test_O1(): + assert O(1, x) * x == O(x) + assert O(1, y) * x == O(1, y) + + +def test_getn(): + # other lines are tested incidentally by the suite + assert O(x).getn() == 1 + assert O(x/log(x)).getn() == 1 + assert O(x**2/log(x)**2).getn() == 2 + assert O(x*log(x)).getn() == 1 + raises(NotImplementedError, lambda: (O(x) + O(y)).getn()) + + +def test_diff(): + assert O(x**2).diff(x) == O(x) + + +def test_getO(): + assert (x).getO() is None + assert (x).removeO() == x + assert (O(x)).getO() == O(x) + assert (O(x)).removeO() == 0 + assert (z + O(x) + O(y)).getO() == O(x) + O(y) + assert (z + O(x) + O(y)).removeO() == z + raises(NotImplementedError, lambda: (O(x) + O(y)).getn()) + + +def test_leading_term(): + from sympy.functions.special.gamma_functions import digamma + assert O(1/digamma(1/x)) == O(1/log(x)) + + +def test_eval(): + assert Order(x).subs(Order(x), 1) == 1 + assert Order(x).subs(x, y) == Order(y) + assert Order(x).subs(y, x) == Order(x) + assert Order(x).subs(x, x + y) == Order(x + y, (x, -y)) + assert (O(1)**x).is_Pow + + +def test_issue_4279(): + a, b = symbols('a b') + assert O(a, a, b) + O(1, a, b) == O(1, a, b) + assert O(b, a, b) + O(1, a, b) == O(1, a, b) + assert O(a + b, a, b) + O(1, a, b) == O(1, a, b) + assert O(1, a, b) + O(a, a, b) == O(1, a, b) + assert O(1, a, b) + O(b, a, b) == O(1, a, b) + assert O(1, a, b) + O(a + b, a, b) == O(1, a, b) + + +def test_issue_4855(): + assert 1/O(1) != O(1) + assert 1/O(x) != O(1/x) + assert 1/O(x, (x, oo)) != O(1/x, (x, oo)) + + f = Function('f') + assert 1/O(f(x)) != O(1/x) + + +def test_order_conjugate_transpose(): + x = Symbol('x', real=True) + y = Symbol('y', imaginary=True) + assert conjugate(Order(x)) == Order(conjugate(x)) + assert conjugate(Order(y)) == Order(conjugate(y)) + assert conjugate(Order(x**2)) == Order(conjugate(x)**2) + assert conjugate(Order(y**2)) == Order(conjugate(y)**2) + assert transpose(Order(x)) == Order(transpose(x)) + assert transpose(Order(y)) == Order(transpose(y)) + assert transpose(Order(x**2)) == Order(transpose(x)**2) + assert transpose(Order(y**2)) == Order(transpose(y)**2) + + +def test_order_noncommutative(): + A = Symbol('A', commutative=False) + assert Order(A + A*x, x) == Order(1, x) + assert (A + A*x)*Order(x) == Order(x) + assert (A*x)*Order(x) == Order(x**2, x) + assert expand((1 + Order(x))*A*A*x) == A*A*x + Order(x**2, x) + assert expand((A*A + Order(x))*x) == A*A*x + Order(x**2, x) + assert expand((A + Order(x))*A*x) == A*A*x + Order(x**2, x) + + +def test_issue_6753(): + assert (1 + x**2)**10000*O(x) == O(x) + + +def test_order_at_infinity(): + assert Order(1 + x, (x, oo)) == Order(x, (x, oo)) + assert Order(3*x, (x, oo)) == Order(x, (x, oo)) + assert Order(x, (x, oo))*3 == Order(x, (x, oo)) + assert -28*Order(x, (x, oo)) == Order(x, (x, oo)) + assert Order(Order(x, (x, oo)), (x, oo)) == Order(x, (x, oo)) + assert Order(Order(x, (x, oo)), (y, oo)) == Order(x, (x, oo), (y, oo)) + assert Order(3, (x, oo)) == Order(1, (x, oo)) + assert Order(x**2 + x + y, (x, oo)) == O(x**2, (x, oo)) + assert Order(x**2 + x + y, (y, oo)) == O(y, (y, oo)) + + assert Order(2*x, (x, oo))*x == Order(x**2, (x, oo)) + assert Order(2*x, (x, oo))/x == Order(1, (x, oo)) + assert Order(2*x, (x, oo))*x*exp(1/x) == Order(x**2*exp(1/x), (x, oo)) + assert Order(2*x, (x, oo))*x*exp(1/x)/log(x)**3 == Order(x**2*exp(1/x)*log(x)**-3, (x, oo)) + + assert Order(x, (x, oo)) + 1/x == 1/x + Order(x, (x, oo)) == Order(x, (x, oo)) + assert Order(x, (x, oo)) + 1 == 1 + Order(x, (x, oo)) == Order(x, (x, oo)) + assert Order(x, (x, oo)) + x == x + Order(x, (x, oo)) == Order(x, (x, oo)) + assert Order(x, (x, oo)) + x**2 == x**2 + Order(x, (x, oo)) + assert Order(1/x, (x, oo)) + 1/x**2 == 1/x**2 + Order(1/x, (x, oo)) == Order(1/x, (x, oo)) + assert Order(x, (x, oo)) + exp(1/x) == exp(1/x) + Order(x, (x, oo)) + + assert Order(x, (x, oo))**2 == Order(x**2, (x, oo)) + + assert Order(x, (x, oo)) + Order(x**2, (x, oo)) == Order(x**2, (x, oo)) + assert Order(x, (x, oo)) + Order(x**-2, (x, oo)) == Order(x, (x, oo)) + assert Order(x, (x, oo)) + Order(1/x, (x, oo)) == Order(x, (x, oo)) + + assert Order(x, (x, oo)) - Order(x, (x, oo)) == Order(x, (x, oo)) + assert Order(x, (x, oo)) + Order(1, (x, oo)) == Order(x, (x, oo)) + assert Order(x, (x, oo)) + Order(x**2, (x, oo)) == Order(x**2, (x, oo)) + assert Order(1/x, (x, oo)) + Order(1, (x, oo)) == Order(1, (x, oo)) + assert Order(x, (x, oo)) + Order(exp(1/x), (x, oo)) == Order(x, (x, oo)) + assert Order(x**3, (x, oo)) + Order(exp(2/x), (x, oo)) == Order(x**3, (x, oo)) + assert Order(x**-3, (x, oo)) + Order(exp(2/x), (x, oo)) == Order(exp(2/x), (x, oo)) + + # issue 7207 + assert Order(exp(x), (x, oo)).expr == Order(2*exp(x), (x, oo)).expr == exp(x) + assert Order(y**x, (x, oo)).expr == Order(2*y**x, (x, oo)).expr == exp(x*log(y)) + + # issue 19545 + assert Order(1/x - 3/(3*x + 2), (x, oo)).expr == x**(-2) + +def test_mixing_order_at_zero_and_infinity(): + assert (Order(x, (x, 0)) + Order(x, (x, oo))).is_Add + assert Order(x, (x, 0)) + Order(x, (x, oo)) == Order(x, (x, oo)) + Order(x, (x, 0)) + assert Order(Order(x, (x, oo))) == Order(x, (x, oo)) + + # not supported (yet) + raises(NotImplementedError, lambda: Order(x, (x, 0))*Order(x, (x, oo))) + raises(NotImplementedError, lambda: Order(x, (x, oo))*Order(x, (x, 0))) + raises(NotImplementedError, lambda: Order(Order(x, (x, oo)), y)) + raises(NotImplementedError, lambda: Order(Order(x), (x, oo))) + + +def test_order_at_some_point(): + assert Order(x, (x, 1)) == Order(1, (x, 1)) + assert Order(2*x - 2, (x, 1)) == Order(x - 1, (x, 1)) + assert Order(-x + 1, (x, 1)) == Order(x - 1, (x, 1)) + assert Order(x - 1, (x, 1))**2 == Order((x - 1)**2, (x, 1)) + assert Order(x - 2, (x, 2)) - O(x - 2, (x, 2)) == Order(x - 2, (x, 2)) + + +def test_order_subs_limits(): + # issue 3333 + assert (1 + Order(x)).subs(x, 1/x) == 1 + Order(1/x, (x, oo)) + assert (1 + Order(x)).limit(x, 0) == 1 + # issue 5769 + assert ((x + Order(x**2))/x).limit(x, 0) == 1 + + assert Order(x**2).subs(x, y - 1) == Order((y - 1)**2, (y, 1)) + assert Order(10*x**2, (x, 2)).subs(x, y - 1) == Order(1, (y, 3)) + + #issue 19120 + assert O(x).subs(x, O(x)) == O(x) + assert O(x**2).subs(x, x + O(x)) == O(x**2) + assert O(x, (x, oo)).subs(x, O(x, (x, oo))) == O(x, (x, oo)) + assert O(x**2, (x, oo)).subs(x, x + O(x, (x, oo))) == O(x**2, (x, oo)) + assert (x + O(x**2)).subs(x, x + O(x**2)) == x + O(x**2) + assert (x**2 + O(x**2) + 1/x**2).subs(x, x + O(x**2)) == (x + O(x**2))**(-2) + O(x**2) + assert (x**2 + O(x**2) + 1).subs(x, x + O(x**2)) == 1 + O(x**2) + assert O(x, (x, oo)).subs(x, x + O(x**2, (x, oo))) == O(x**2, (x, oo)) + assert sin(x).series(n=8).subs(x,sin(x).series(n=8)).expand() == x - x**3/3 + x**5/10 - 8*x**7/315 + O(x**8) + assert cos(x).series(n=8).subs(x,sin(x).series(n=8)).expand() == 1 - x**2/2 + 5*x**4/24 - 37*x**6/720 + O(x**8) + assert O(x).subs(x, O(1/x, (x, oo))) == O(1/x, (x, oo)) + +@XFAIL +def test_order_failing_due_to_solveset(): + assert O(x**3).subs(x, exp(-x**2)) == O(exp(-3*x**2), (x, -oo)) + raises(NotImplementedError, lambda: O(x).subs(x, O(1/x))) # mixing of order at different points + + +def test_issue_9351(): + assert exp(x).series(x, 10, 1) == exp(10) + Order(x - 10, (x, 10)) + + +def test_issue_9192(): + assert O(1)*O(1) == O(1) + assert O(1)**O(1) == O(1) + + +def test_issue_9910(): + assert O(x*log(x) + sin(x), (x, oo)) == O(x*log(x), (x, oo)) + + +def test_performance_of_adding_order(): + l = [x**i for i in range(1000)] + l.append(O(x**1001)) + assert Add(*l).subs(x,1) == O(1) + +def test_issue_14622(): + assert (x**(-4) + x**(-3) + x**(-1) + O(x**(-6), (x, oo))).as_numer_denom() == ( + x**4 + x**5 + x**7 + O(x**2, (x, oo)), x**8) + assert (x**3 + O(x**2, (x, oo))).is_Add + assert O(x**2, (x, oo)).contains(x**3) is False + assert O(x, (x, oo)).contains(O(x, (x, 0))) is None + assert O(x, (x, 0)).contains(O(x, (x, oo))) is None + raises(NotImplementedError, lambda: O(x**3).contains(x**w)) + + +def test_issue_15539(): + assert O(1/x**2 + 1/x**4, (x, -oo)) == O(1/x**2, (x, -oo)) + assert O(1/x**4 + exp(x), (x, -oo)) == O(1/x**4, (x, -oo)) + assert O(1/x**4 + exp(-x), (x, -oo)) == O(exp(-x), (x, -oo)) + assert O(1/x, (x, oo)).subs(x, -x) == O(-1/x, (x, -oo)) + +def test_issue_18606(): + assert unchanged(Order, 0) + + +def test_issue_22165(): + assert O(log(x)).contains(2) + + +def test_issue_23231(): + # This test checks Order for expressions having + # arguments containing variables in exponents/powers. + assert O(x**x + 2**x, (x, oo)) == O(exp(x*log(x)), (x, oo)) + assert O(x**x + x**2, (x, oo)) == O(exp(x*log(x)), (x, oo)) + assert O(x**x + 1/x**2, (x, oo)) == O(exp(x*log(x)), (x, oo)) + assert O(2**x + 3**x , (x, oo)) == O(exp(x*log(3)), (x, oo)) + + +def test_issue_9917(): + assert O(x*sin(x) + 1, (x, oo)) == O(x, (x, oo)) + + +def test_issue_22836(): + assert O(2**x + factorial(x), (x, oo)) == O(factorial(x), (x, oo)) + assert O(2**x + factorial(x) + x**x, (x, oo)) == O(exp(x*log(x)), (x, oo)) + assert O(x + factorial(x), (x, oo)) == O(factorial(x), (x, oo)) diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_residues.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_residues.py new file mode 100644 index 0000000000000000000000000000000000000000..9f7d075a56500d008e3c8b46c1fda5db890fd76a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_residues.py @@ -0,0 +1,101 @@ +from sympy.core.function import Function +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import tanh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cot, sin, tan) +from sympy.series.residues import residue +from sympy.testing.pytest import XFAIL, raises +from sympy.abc import x, z, a, s, k + + +def test_basic1(): + assert residue(1/x, x, 0) == 1 + assert residue(-2/x, x, 0) == -2 + assert residue(81/x, x, 0) == 81 + assert residue(1/x**2, x, 0) == 0 + assert residue(0, x, 0) == 0 + assert residue(5, x, 0) == 0 + assert residue(x, x, 0) == 0 + assert residue(x**2, x, 0) == 0 + + +def test_basic2(): + assert residue(1/x, x, 1) == 0 + assert residue(-2/x, x, 1) == 0 + assert residue(81/x, x, -1) == 0 + assert residue(1/x**2, x, 1) == 0 + assert residue(0, x, 1) == 0 + assert residue(5, x, 1) == 0 + assert residue(x, x, 1) == 0 + assert residue(x**2, x, 5) == 0 + + +def test_f(): + f = Function("f") + assert residue(f(x)/x**5, x, 0) == f(x).diff(x, 4).subs(x, 0)/24 + + +def test_functions(): + assert residue(1/sin(x), x, 0) == 1 + assert residue(2/sin(x), x, 0) == 2 + assert residue(1/sin(x)**2, x, 0) == 0 + assert residue(1/sin(x)**5, x, 0) == Rational(3, 8) + + +def test_expressions(): + assert residue(1/(x + 1), x, 0) == 0 + assert residue(1/(x + 1), x, -1) == 1 + assert residue(1/(x**2 + 1), x, -1) == 0 + assert residue(1/(x**2 + 1), x, I) == -I/2 + assert residue(1/(x**2 + 1), x, -I) == I/2 + assert residue(1/(x**4 + 1), x, 0) == 0 + assert residue(1/(x**4 + 1), x, exp(I*pi/4)).equals(-(Rational(1, 4) + I/4)/sqrt(2)) + assert residue(1/(x**2 + a**2)**2, x, a*I) == -I/4/a**3 + + +@XFAIL +def test_expressions_failing(): + n = Symbol('n', integer=True, positive=True) + assert residue(exp(z)/(z - pi*I/4*a)**n, z, I*pi*a) == \ + exp(I*pi*a/4)/factorial(n - 1) + + +def test_NotImplemented(): + raises(NotImplementedError, lambda: residue(exp(1/z), z, 0)) + + +def test_bug(): + assert residue(2**(z)*(s + z)*(1 - s - z)/z**2, z, 0) == \ + 1 + s*log(2) - s**2*log(2) - 2*s + + +def test_issue_5654(): + assert residue(1/(x**2 + a**2)**2, x, a*I) == -I/(4*a**3) + assert residue(1/s*1/(z - exp(s)), s, 0) == 1/(z - 1) + assert residue((1 + k)/s*1/(z - exp(s)), s, 0) == k/(z - 1) + 1/(z - 1) + + +def test_issue_6499(): + assert residue(1/(exp(z) - 1), z, 0) == 1 + + +def test_issue_14037(): + assert residue(sin(x**50)/x**51, x, 0) == 1 + + +def test_issue_21176(): + f = x**2*cot(pi*x)/(x**4 + 1) + assert residue(f, x, -sqrt(2)/2 - sqrt(2)*I/2).cancel().together(deep=True)\ + == sqrt(2)*(1 - I)/(8*tan(sqrt(2)*pi*(1 + I)/2)) + + +def test_issue_21177(): + r = -sqrt(3)*tanh(sqrt(3)*pi/2)/3 + a = residue(cot(pi*x)/((x - 1)*(x - 2) + 1), x, S(3)/2 - sqrt(3)*I/2) + b = residue(cot(pi*x)/(x**2 - 3*x + 3), x, S(3)/2 - sqrt(3)*I/2) + assert a == r + assert (b - a).cancel() == 0 diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_sequences.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_sequences.py new file mode 100644 index 0000000000000000000000000000000000000000..61e276ad67982f0a9877de3548d70238976d28a5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_sequences.py @@ -0,0 +1,312 @@ +from sympy.core.containers import Tuple +from sympy.core.function import Function +from sympy.core.numbers import oo, Rational +from sympy.core.singleton import S +from sympy.core.symbol import symbols, Symbol +from sympy.functions.combinatorial.numbers import tribonacci, fibonacci +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.series import EmptySequence +from sympy.series.sequences import (SeqMul, SeqAdd, SeqPer, SeqFormula, + sequence) +from sympy.sets.sets import Interval +from sympy.tensor.indexed import Indexed, Idx +from sympy.series.sequences import SeqExpr, SeqExprOp, RecursiveSeq +from sympy.testing.pytest import raises, slow + +x, y, z = symbols('x y z') +n, m = symbols('n m') + + +def test_EmptySequence(): + assert S.EmptySequence is EmptySequence + + assert S.EmptySequence.interval is S.EmptySet + assert S.EmptySequence.length is S.Zero + + assert list(S.EmptySequence) == [] + + +def test_SeqExpr(): + #SeqExpr is a baseclass and does not take care of + #ensuring all arguments are Basics hence the use of + #Tuple(...) here. + s = SeqExpr(Tuple(1, n, y), Tuple(x, 0, 10)) + + assert isinstance(s, SeqExpr) + assert s.gen == (1, n, y) + assert s.interval == Interval(0, 10) + assert s.start == 0 + assert s.stop == 10 + assert s.length == 11 + assert s.variables == (x,) + + assert SeqExpr(Tuple(1, 2, 3), Tuple(x, 0, oo)).length is oo + + +def test_SeqPer(): + s = SeqPer((1, n, 3), (x, 0, 5)) + + assert isinstance(s, SeqPer) + assert s.periodical == Tuple(1, n, 3) + assert s.period == 3 + assert s.coeff(3) == 1 + assert s.free_symbols == {n} + + assert list(s) == [1, n, 3, 1, n, 3] + assert s[:] == [1, n, 3, 1, n, 3] + assert SeqPer((1, n, 3), (x, -oo, 0))[0:6] == [1, n, 3, 1, n, 3] + + raises(ValueError, lambda: SeqPer((1, 2, 3), (0, 1, 2))) + raises(ValueError, lambda: SeqPer((1, 2, 3), (x, -oo, oo))) + raises(ValueError, lambda: SeqPer(n**2, (0, oo))) + + assert SeqPer((n, n**2, n**3), (m, 0, oo))[:6] == \ + [n, n**2, n**3, n, n**2, n**3] + assert SeqPer((n, n**2, n**3), (n, 0, oo))[:6] == [0, 1, 8, 3, 16, 125] + assert SeqPer((n, m), (n, 0, oo))[:6] == [0, m, 2, m, 4, m] + + +def test_SeqFormula(): + s = SeqFormula(n**2, (n, 0, 5)) + + assert isinstance(s, SeqFormula) + assert s.formula == n**2 + assert s.coeff(3) == 9 + + assert list(s) == [i**2 for i in range(6)] + assert s[:] == [i**2 for i in range(6)] + assert SeqFormula(n**2, (n, -oo, 0))[0:6] == [i**2 for i in range(6)] + + assert SeqFormula(n**2, (0, oo)) == SeqFormula(n**2, (n, 0, oo)) + + assert SeqFormula(n**2, (0, m)).subs(m, x) == SeqFormula(n**2, (0, x)) + assert SeqFormula(m*n**2, (n, 0, oo)).subs(m, x) == \ + SeqFormula(x*n**2, (n, 0, oo)) + + raises(ValueError, lambda: SeqFormula(n**2, (0, 1, 2))) + raises(ValueError, lambda: SeqFormula(n**2, (n, -oo, oo))) + raises(ValueError, lambda: SeqFormula(m*n**2, (0, oo))) + + seq = SeqFormula(x*(y**2 + z), (z, 1, 100)) + assert seq.expand() == SeqFormula(x*y**2 + x*z, (z, 1, 100)) + seq = SeqFormula(sin(x*(y**2 + z)),(z, 1, 100)) + assert seq.expand(trig=True) == SeqFormula(sin(x*y**2)*cos(x*z) + sin(x*z)*cos(x*y**2), (z, 1, 100)) + assert seq.expand() == SeqFormula(sin(x*y**2 + x*z), (z, 1, 100)) + assert seq.expand(trig=False) == SeqFormula(sin(x*y**2 + x*z), (z, 1, 100)) + seq = SeqFormula(exp(x*(y**2 + z)), (z, 1, 100)) + assert seq.expand() == SeqFormula(exp(x*y**2)*exp(x*z), (z, 1, 100)) + assert seq.expand(power_exp=False) == SeqFormula(exp(x*y**2 + x*z), (z, 1, 100)) + assert seq.expand(mul=False, power_exp=False) == SeqFormula(exp(x*(y**2 + z)), (z, 1, 100)) + +def test_sequence(): + form = SeqFormula(n**2, (n, 0, 5)) + per = SeqPer((1, 2, 3), (n, 0, 5)) + inter = SeqFormula(n**2) + + assert sequence(n**2, (n, 0, 5)) == form + assert sequence((1, 2, 3), (n, 0, 5)) == per + assert sequence(n**2) == inter + + +def test_SeqExprOp(): + form = SeqFormula(n**2, (n, 0, 10)) + per = SeqPer((1, 2, 3), (m, 5, 10)) + + s = SeqExprOp(form, per) + assert s.gen == (n**2, (1, 2, 3)) + assert s.interval == Interval(5, 10) + assert s.start == 5 + assert s.stop == 10 + assert s.length == 6 + assert s.variables == (n, m) + + +def test_SeqAdd(): + per = SeqPer((1, 2, 3), (n, 0, oo)) + form = SeqFormula(n**2) + + per_bou = SeqPer((1, 2), (n, 1, 5)) + form_bou = SeqFormula(n**2, (6, 10)) + form_bou2 = SeqFormula(n**2, (1, 5)) + + assert SeqAdd() == S.EmptySequence + assert SeqAdd(S.EmptySequence) == S.EmptySequence + assert SeqAdd(per) == per + assert SeqAdd(per, S.EmptySequence) == per + assert SeqAdd(per_bou, form_bou) == S.EmptySequence + + s = SeqAdd(per_bou, form_bou2, evaluate=False) + assert s.args == (form_bou2, per_bou) + assert s[:] == [2, 6, 10, 18, 26] + assert list(s) == [2, 6, 10, 18, 26] + + assert isinstance(SeqAdd(per, per_bou, evaluate=False), SeqAdd) + + s1 = SeqAdd(per, per_bou) + assert isinstance(s1, SeqPer) + assert s1 == SeqPer((2, 4, 4, 3, 3, 5), (n, 1, 5)) + s2 = SeqAdd(form, form_bou) + assert isinstance(s2, SeqFormula) + assert s2 == SeqFormula(2*n**2, (6, 10)) + + assert SeqAdd(form, form_bou, per) == \ + SeqAdd(per, SeqFormula(2*n**2, (6, 10))) + assert SeqAdd(form, SeqAdd(form_bou, per)) == \ + SeqAdd(per, SeqFormula(2*n**2, (6, 10))) + assert SeqAdd(per, SeqAdd(form, form_bou), evaluate=False) == \ + SeqAdd(per, SeqFormula(2*n**2, (6, 10))) + + assert SeqAdd(SeqPer((1, 2), (n, 0, oo)), SeqPer((1, 2), (m, 0, oo))) == \ + SeqPer((2, 4), (n, 0, oo)) + + +def test_SeqMul(): + per = SeqPer((1, 2, 3), (n, 0, oo)) + form = SeqFormula(n**2) + + per_bou = SeqPer((1, 2), (n, 1, 5)) + form_bou = SeqFormula(n**2, (n, 6, 10)) + form_bou2 = SeqFormula(n**2, (1, 5)) + + assert SeqMul() == S.EmptySequence + assert SeqMul(S.EmptySequence) == S.EmptySequence + assert SeqMul(per) == per + assert SeqMul(per, S.EmptySequence) == S.EmptySequence + assert SeqMul(per_bou, form_bou) == S.EmptySequence + + s = SeqMul(per_bou, form_bou2, evaluate=False) + assert s.args == (form_bou2, per_bou) + assert s[:] == [1, 8, 9, 32, 25] + assert list(s) == [1, 8, 9, 32, 25] + + assert isinstance(SeqMul(per, per_bou, evaluate=False), SeqMul) + + s1 = SeqMul(per, per_bou) + assert isinstance(s1, SeqPer) + assert s1 == SeqPer((1, 4, 3, 2, 2, 6), (n, 1, 5)) + s2 = SeqMul(form, form_bou) + assert isinstance(s2, SeqFormula) + assert s2 == SeqFormula(n**4, (6, 10)) + + assert SeqMul(form, form_bou, per) == \ + SeqMul(per, SeqFormula(n**4, (6, 10))) + assert SeqMul(form, SeqMul(form_bou, per)) == \ + SeqMul(per, SeqFormula(n**4, (6, 10))) + assert SeqMul(per, SeqMul(form, form_bou2, + evaluate=False), evaluate=False) == \ + SeqMul(form, per, form_bou2, evaluate=False) + + assert SeqMul(SeqPer((1, 2), (n, 0, oo)), SeqPer((1, 2), (n, 0, oo))) == \ + SeqPer((1, 4), (n, 0, oo)) + + +def test_add(): + per = SeqPer((1, 2), (n, 0, oo)) + form = SeqFormula(n**2) + + assert per + (SeqPer((2, 3))) == SeqPer((3, 5), (n, 0, oo)) + assert form + SeqFormula(n**3) == SeqFormula(n**2 + n**3) + + assert per + form == SeqAdd(per, form) + + raises(TypeError, lambda: per + n) + raises(TypeError, lambda: n + per) + + +def test_sub(): + per = SeqPer((1, 2), (n, 0, oo)) + form = SeqFormula(n**2) + + assert per - (SeqPer((2, 3))) == SeqPer((-1, -1), (n, 0, oo)) + assert form - (SeqFormula(n**3)) == SeqFormula(n**2 - n**3) + + assert per - form == SeqAdd(per, -form) + + raises(TypeError, lambda: per - n) + raises(TypeError, lambda: n - per) + + +def test_mul__coeff_mul(): + assert SeqPer((1, 2), (n, 0, oo)).coeff_mul(2) == SeqPer((2, 4), (n, 0, oo)) + assert SeqFormula(n**2).coeff_mul(2) == SeqFormula(2*n**2) + assert S.EmptySequence.coeff_mul(100) == S.EmptySequence + + assert SeqPer((1, 2), (n, 0, oo)) * (SeqPer((2, 3))) == \ + SeqPer((2, 6), (n, 0, oo)) + assert SeqFormula(n**2) * SeqFormula(n**3) == SeqFormula(n**5) + + assert S.EmptySequence * SeqFormula(n**2) == S.EmptySequence + assert SeqFormula(n**2) * S.EmptySequence == S.EmptySequence + + raises(TypeError, lambda: sequence(n**2) * n) + raises(TypeError, lambda: n * sequence(n**2)) + + +def test_neg(): + assert -SeqPer((1, -2), (n, 0, oo)) == SeqPer((-1, 2), (n, 0, oo)) + assert -SeqFormula(n**2) == SeqFormula(-n**2) + + +def test_operations(): + per = SeqPer((1, 2), (n, 0, oo)) + per2 = SeqPer((2, 4), (n, 0, oo)) + form = SeqFormula(n**2) + form2 = SeqFormula(n**3) + + assert per + form + form2 == SeqAdd(per, form, form2) + assert per + form - form2 == SeqAdd(per, form, -form2) + assert per + form - S.EmptySequence == SeqAdd(per, form) + assert per + per2 + form == SeqAdd(SeqPer((3, 6), (n, 0, oo)), form) + assert S.EmptySequence - per == -per + assert form + form == SeqFormula(2*n**2) + + assert per * form * form2 == SeqMul(per, form, form2) + assert form * form == SeqFormula(n**4) + assert form * -form == SeqFormula(-n**4) + + assert form * (per + form2) == SeqMul(form, SeqAdd(per, form2)) + assert form * (per + per) == SeqMul(form, per2) + + assert form.coeff_mul(m) == SeqFormula(m*n**2, (n, 0, oo)) + assert per.coeff_mul(m) == SeqPer((m, 2*m), (n, 0, oo)) + + +def test_Idx_limits(): + i = symbols('i', cls=Idx) + r = Indexed('r', i) + + assert SeqFormula(r, (i, 0, 5))[:] == [r.subs(i, j) for j in range(6)] + assert SeqPer((1, 2), (i, 0, 5))[:] == [1, 2, 1, 2, 1, 2] + + +@slow +def test_find_linear_recurrence(): + assert sequence((0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55), \ + (n, 0, 10)).find_linear_recurrence(11) == [1, 1] + assert sequence((1, 2, 4, 7, 28, 128, 582, 2745, 13021, 61699, 292521, \ + 1387138), (n, 0, 11)).find_linear_recurrence(12) == [5, -2, 6, -11] + assert sequence(x*n**3+y*n, (n, 0, oo)).find_linear_recurrence(10) \ + == [4, -6, 4, -1] + assert sequence(x**n, (n,0,20)).find_linear_recurrence(21) == [x] + assert sequence((1,2,3)).find_linear_recurrence(10, 5) == [0, 0, 1] + assert sequence(((1 + sqrt(5))/2)**n + \ + (-(1 + sqrt(5))/2)**(-n)).find_linear_recurrence(10) == [1, 1] + assert sequence(x*((1 + sqrt(5))/2)**n + y*(-(1 + sqrt(5))/2)**(-n), \ + (n,0,oo)).find_linear_recurrence(10) == [1, 1] + assert sequence((1,2,3,4,6),(n, 0, 4)).find_linear_recurrence(5) == [] + assert sequence((2,3,4,5,6,79),(n, 0, 5)).find_linear_recurrence(6,gfvar=x) \ + == ([], None) + assert sequence((2,3,4,5,8,30),(n, 0, 5)).find_linear_recurrence(6,gfvar=x) \ + == ([Rational(19, 2), -20, Rational(27, 2)], (-31*x**2 + 32*x - 4)/(27*x**3 - 40*x**2 + 19*x -2)) + assert sequence(fibonacci(n)).find_linear_recurrence(30,gfvar=x) \ + == ([1, 1], -x/(x**2 + x - 1)) + assert sequence(tribonacci(n)).find_linear_recurrence(30,gfvar=x) \ + == ([1, 1, 1], -x/(x**3 + x**2 + x - 1)) + +def test_RecursiveSeq(): + y = Function('y') + n = Symbol('n') + fib = RecursiveSeq(y(n - 1) + y(n - 2), y(n), n, [0, 1]) + assert fib.coeff(3) == 2 diff --git a/venv/lib/python3.10/site-packages/sympy/series/tests/test_series.py b/venv/lib/python3.10/site-packages/sympy/series/tests/test_series.py new file mode 100644 index 0000000000000000000000000000000000000000..e3f3c122b98c14a58c6d5c6636cbb53e1e66a75d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/series/tests/test_series.py @@ -0,0 +1,421 @@ +from sympy.core.evalf import N +from sympy.core.function import (Derivative, Function, PoleError, Subs) +from sympy.core.numbers import (E, Float, Rational, oo, pi, I) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import (LambertW, exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (atan, cos, sin) +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import Integral, integrate +from sympy.series.order import O +from sympy.series.series import series +from sympy.abc import x, y, n, k +from sympy.testing.pytest import raises +from sympy.core import EulerGamma + + +def test_sin(): + e1 = sin(x).series(x, 0) + e2 = series(sin(x), x, 0) + assert e1 == e2 + + +def test_cos(): + e1 = cos(x).series(x, 0) + e2 = series(cos(x), x, 0) + assert e1 == e2 + + +def test_exp(): + e1 = exp(x).series(x, 0) + e2 = series(exp(x), x, 0) + assert e1 == e2 + + +def test_exp2(): + e1 = exp(cos(x)).series(x, 0) + e2 = series(exp(cos(x)), x, 0) + assert e1 == e2 + + +def test_issue_5223(): + assert series(1, x) == 1 + assert next(S.Zero.lseries(x)) == 0 + assert cos(x).series() == cos(x).series(x) + raises(ValueError, lambda: cos(x + y).series()) + raises(ValueError, lambda: x.series(dir="")) + + assert (cos(x).series(x, 1) - + cos(x + 1).series(x).subs(x, x - 1)).removeO() == 0 + e = cos(x).series(x, 1, n=None) + assert [next(e) for i in range(2)] == [cos(1), -((x - 1)*sin(1))] + e = cos(x).series(x, 1, n=None, dir='-') + assert [next(e) for i in range(2)] == [cos(1), (1 - x)*sin(1)] + # the following test is exact so no need for x -> x - 1 replacement + assert abs(x).series(x, 1, dir='-') == x + assert exp(x).series(x, 1, dir='-', n=3).removeO() == \ + E - E*(-x + 1) + E*(-x + 1)**2/2 + + D = Derivative + assert D(x**2 + x**3*y**2, x, 2, y, 1).series(x).doit() == 12*x*y + assert next(D(cos(x), x).lseries()) == D(1, x) + assert D( + exp(x), x).series(n=3) == D(1, x) + D(x, x) + D(x**2/2, x) + D(x**3/6, x) + O(x**3) + + assert Integral(x, (x, 1, 3), (y, 1, x)).series(x) == -4 + 4*x + + assert (1 + x + O(x**2)).getn() == 2 + assert (1 + x).getn() is None + + raises(PoleError, lambda: ((1/sin(x))**oo).series()) + logx = Symbol('logx') + assert ((sin(x))**y).nseries(x, n=1, logx=logx) == \ + exp(y*logx) + O(x*exp(y*logx), x) + + assert sin(1/x).series(x, oo, n=5) == 1/x - 1/(6*x**3) + O(x**(-5), (x, oo)) + assert abs(x).series(x, oo, n=5, dir='+') == x + assert abs(x).series(x, -oo, n=5, dir='-') == -x + assert abs(-x).series(x, oo, n=5, dir='+') == x + assert abs(-x).series(x, -oo, n=5, dir='-') == -x + + assert exp(x*log(x)).series(n=3) == \ + 1 + x*log(x) + x**2*log(x)**2/2 + O(x**3*log(x)**3) + # XXX is this right? If not, fix "ngot > n" handling in expr. + p = Symbol('p', positive=True) + assert exp(sqrt(p)**3*log(p)).series(n=3) == \ + 1 + p**S('3/2')*log(p) + O(p**3*log(p)**3) + + assert exp(sin(x)*log(x)).series(n=2) == 1 + x*log(x) + O(x**2*log(x)**2) + + +def test_issue_6350(): + expr = integrate(exp(k*(y**3 - 3*y)), (y, 0, oo), conds='none') + assert expr.series(k, 0, 3) == -(-1)**(S(2)/3)*sqrt(3)*gamma(S(1)/3)**2*gamma(S(2)/3)/(6*pi*k**(S(1)/3)) - \ + sqrt(3)*k*gamma(-S(2)/3)*gamma(-S(1)/3)/(6*pi) - \ + (-1)**(S(1)/3)*sqrt(3)*k**(S(1)/3)*gamma(-S(1)/3)*gamma(S(1)/3)*gamma(S(2)/3)/(6*pi) - \ + (-1)**(S(2)/3)*sqrt(3)*k**(S(5)/3)*gamma(S(1)/3)**2*gamma(S(2)/3)/(4*pi) - \ + (-1)**(S(1)/3)*sqrt(3)*k**(S(7)/3)*gamma(-S(1)/3)*gamma(S(1)/3)*gamma(S(2)/3)/(8*pi) + O(k**3) + + +def test_issue_11313(): + assert Integral(cos(x), x).series(x) == sin(x).series(x) + assert Derivative(sin(x), x).series(x, n=3).doit() == cos(x).series(x, n=3) + + assert Derivative(x**3, x).as_leading_term(x) == 3*x**2 + assert Derivative(x**3, y).as_leading_term(x) == 0 + assert Derivative(sin(x), x).as_leading_term(x) == 1 + assert Derivative(cos(x), x).as_leading_term(x) == -x + + # This result is equivalent to zero, zero is not return because + # `Expr.series` doesn't currently detect an `x` in its `free_symbol`s. + assert Derivative(1, x).as_leading_term(x) == Derivative(1, x) + + assert Derivative(exp(x), x).series(x).doit() == exp(x).series(x) + assert 1 + Integral(exp(x), x).series(x) == exp(x).series(x) + + assert Derivative(log(x), x).series(x).doit() == (1/x).series(x) + assert Integral(log(x), x).series(x) == Integral(log(x), x).doit().series(x).removeO() + + +def test_series_of_Subs(): + from sympy.abc import z + + subs1 = Subs(sin(x), x, y) + subs2 = Subs(sin(x) * cos(z), x, y) + subs3 = Subs(sin(x * z), (x, z), (y, x)) + + assert subs1.series(x) == subs1 + subs1_series = (Subs(x, x, y) + Subs(-x**3/6, x, y) + + Subs(x**5/120, x, y) + O(y**6)) + assert subs1.series() == subs1_series + assert subs1.series(y) == subs1_series + assert subs1.series(z) == subs1 + assert subs2.series(z) == (Subs(z**4*sin(x)/24, x, y) + + Subs(-z**2*sin(x)/2, x, y) + Subs(sin(x), x, y) + O(z**6)) + assert subs3.series(x).doit() == subs3.doit().series(x) + assert subs3.series(z).doit() == sin(x*y) + + raises(ValueError, lambda: Subs(x + 2*y, y, z).series()) + assert Subs(x + y, y, z).series(x).doit() == x + z + + +def test_issue_3978(): + f = Function('f') + assert f(x).series(x, 0, 3, dir='-') == \ + f(0) + x*Subs(Derivative(f(x), x), x, 0) + \ + x**2*Subs(Derivative(f(x), x, x), x, 0)/2 + O(x**3) + assert f(x).series(x, 0, 3) == \ + f(0) + x*Subs(Derivative(f(x), x), x, 0) + \ + x**2*Subs(Derivative(f(x), x, x), x, 0)/2 + O(x**3) + assert f(x**2).series(x, 0, 3) == \ + f(0) + x**2*Subs(Derivative(f(x), x), x, 0) + O(x**3) + assert f(x**2+1).series(x, 0, 3) == \ + f(1) + x**2*Subs(Derivative(f(x), x), x, 1) + O(x**3) + + class TestF(Function): + pass + + assert TestF(x).series(x, 0, 3) == TestF(0) + \ + x*Subs(Derivative(TestF(x), x), x, 0) + \ + x**2*Subs(Derivative(TestF(x), x, x), x, 0)/2 + O(x**3) + +from sympy.series.acceleration import richardson, shanks +from sympy.concrete.summations import Sum +from sympy.core.numbers import Integer + + +def test_acceleration(): + e = (1 + 1/n)**n + assert round(richardson(e, n, 10, 20).evalf(), 10) == round(E.evalf(), 10) + + A = Sum(Integer(-1)**(k + 1) / k, (k, 1, n)) + assert round(shanks(A, n, 25).evalf(), 4) == round(log(2).evalf(), 4) + assert round(shanks(A, n, 25, 5).evalf(), 10) == round(log(2).evalf(), 10) + + +def test_issue_5852(): + assert series(1/cos(x/log(x)), x, 0) == 1 + x**2/(2*log(x)**2) + \ + 5*x**4/(24*log(x)**4) + O(x**6) + + +def test_issue_4583(): + assert cos(1 + x + x**2).series(x, 0, 5) == cos(1) - x*sin(1) + \ + x**2*(-sin(1) - cos(1)/2) + x**3*(-cos(1) + sin(1)/6) + \ + x**4*(-11*cos(1)/24 + sin(1)/2) + O(x**5) + + +def test_issue_6318(): + eq = (1/x)**Rational(2, 3) + assert (eq + 1).as_leading_term(x) == eq + + +def test_x_is_base_detection(): + eq = (x**2)**Rational(2, 3) + assert eq.series() == x**Rational(4, 3) + + +def test_issue_7203(): + assert series(cos(x), x, pi, 3) == \ + -1 + (x - pi)**2/2 + O((x - pi)**3, (x, pi)) + + +def test_exp_product_positive_factors(): + a, b = symbols('a, b', positive=True) + x = a * b + assert series(exp(x), x, n=8) == 1 + a*b + a**2*b**2/2 + \ + a**3*b**3/6 + a**4*b**4/24 + a**5*b**5/120 + a**6*b**6/720 + \ + a**7*b**7/5040 + O(a**8*b**8, a, b) + + +def test_issue_8805(): + assert series(1, n=8) == 1 + + +def test_issue_9173(): + p0,p1,p2,p3,b0,b1,b2=symbols('p0 p1 p2 p3 b0 b1 b2') + Q=(p0+(p1+(p2+p3/y)/y)/y)/(1+((p3/(b0*y)+(b0*p2-b1*p3)/b0**2)/y+\ + (b0**2*p1-b0*b1*p2-p3*(b0*b2-b1**2))/b0**3)/y) + + series = Q.series(y,n=3) + + assert series == y*(b0*p2/p3+b0*(-p2/p3+b1/b0))+y**2*(b0*p1/p3+b0*p2*\ + (-p2/p3+b1/b0)/p3+b0*(-p1/p3+(p2/p3-b1/b0)**2+b1*p2/(b0*p3)+\ + b2/b0-b1**2/b0**2))+b0+O(y**3) + assert series.simplify() == b2*y**2 + b1*y + b0 + O(y**3) + + +def test_issue_9549(): + y = (x**2 + x + 1) / (x**3 + x**2) + assert series(y, x, oo) == x**(-5) - 1/x**4 + x**(-3) + 1/x + O(x**(-6), (x, oo)) + + +def test_issue_10761(): + assert series(1/(x**-2 + x**-3), x, 0) == x**3 - x**4 + x**5 + O(x**6) + + +def test_issue_12578(): + y = (1 - 1/(x/2 - 1/(2*x))**4)**(S(1)/8) + assert y.series(x, 0, n=17) == 1 - 2*x**4 - 8*x**6 - 34*x**8 - 152*x**10 - 714*x**12 - \ + 3472*x**14 - 17318*x**16 + O(x**17) + + +def test_issue_12791(): + beta = symbols('beta', positive=True) + theta, varphi = symbols('theta varphi', real=True) + + expr = (-beta**2*varphi*sin(theta) + beta**2*cos(theta) + \ + beta*varphi*sin(theta) - beta*cos(theta) - beta + 1)/(beta*cos(theta) - 1)**2 + + sol = (0.5/(0.5*cos(theta) - 1.0)**2 - 0.25*cos(theta)/(0.5*cos(theta) - 1.0)**2 + + (beta - 0.5)*(-0.25*varphi*sin(2*theta) - 1.5*cos(theta) + + 0.25*cos(2*theta) + 1.25)/((0.5*cos(theta) - 1.0)**2*(0.5*cos(theta) - 1.0)) + + 0.25*varphi*sin(theta)/(0.5*cos(theta) - 1.0)**2 + + O((beta - S.Half)**2, (beta, S.Half))) + + assert expr.series(beta, 0.5, 2).trigsimp() == sol + + +def test_issue_14384(): + x, a = symbols('x a') + assert series(x**a, x) == x**a + assert series(x**(-2*a), x) == x**(-2*a) + assert series(exp(a*log(x)), x) == exp(a*log(x)) + raises(PoleError, lambda: series(x**I, x)) + raises(PoleError, lambda: series(x**(I + 1), x)) + raises(PoleError, lambda: series(exp(I*log(x)), x)) + + +def test_issue_14885(): + assert series(x**Rational(-3, 2)*exp(x), x, 0) == (x**Rational(-3, 2) + 1/sqrt(x) + + sqrt(x)/2 + x**Rational(3, 2)/6 + x**Rational(5, 2)/24 + x**Rational(7, 2)/120 + + x**Rational(9, 2)/720 + x**Rational(11, 2)/5040 + O(x**6)) + + +def test_issue_15539(): + assert series(atan(x), x, -oo) == (-1/(5*x**5) + 1/(3*x**3) - 1/x - pi/2 + + O(x**(-6), (x, -oo))) + assert series(atan(x), x, oo) == (-1/(5*x**5) + 1/(3*x**3) - 1/x + pi/2 + + O(x**(-6), (x, oo))) + + +def test_issue_7259(): + assert series(LambertW(x), x) == x - x**2 + 3*x**3/2 - 8*x**4/3 + 125*x**5/24 + O(x**6) + assert series(LambertW(x**2), x, n=8) == x**2 - x**4 + 3*x**6/2 + O(x**8) + assert series(LambertW(sin(x)), x, n=4) == x - x**2 + 4*x**3/3 + O(x**4) + +def test_issue_11884(): + assert cos(x).series(x, 1, n=1) == cos(1) + O(x - 1, (x, 1)) + + +def test_issue_18008(): + y = x*(1 + x*(1 - x))/((1 + x*(1 - x)) - (1 - x)*(1 - x)) + assert y.series(x, oo, n=4) == -9/(32*x**3) - 3/(16*x**2) - 1/(8*x) + S(1)/4 + x/2 + \ + O(x**(-4), (x, oo)) + + +def test_issue_18842(): + f = log(x/(1 - x)) + assert f.series(x, 0.491, n=1).removeO().nsimplify() == \ + -S(180019443780011)/5000000000000000 + + +def test_issue_19534(): + dt = symbols('dt', real=True) + expr = 16*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0)/45 + \ + 49*dt*(-0.049335189898860408029*dt*(2.0*dt + 1.0) + \ + 0.29601113939316244817*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) - \ + 0.12564355335492979587*dt*(0.074074074074074074074*dt*(2.0*dt + 1.0) + \ + 0.2962962962962962963*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.96296296296296296296*dt + 1.0) + 0.051640768506639183825*dt + \ + dt*(1/2 - sqrt(21)/14) + 1.0)/180 + 49*dt*(-0.23637909581542530626*dt*(2.0*dt + 1.0) - \ + 0.74817562366625959291*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.88085458023927036857*dt*(0.074074074074074074074*dt*(2.0*dt + 1.0) + \ + 0.2962962962962962963*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.96296296296296296296*dt + 1.0) + \ + 2.1165151389911680013*dt*(-0.049335189898860408029*dt*(2.0*dt + 1.0) + \ + 0.29601113939316244817*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) - \ + 0.12564355335492979587*dt*(0.074074074074074074074*dt*(2.0*dt + 1.0) + \ + 0.2962962962962962963*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.96296296296296296296*dt + 1.0) + 0.22431393315265061193*dt + 1.0) - \ + 1.1854881643947648988*dt + dt*(sqrt(21)/14 + 1/2) + 1.0)/180 + \ + dt*(0.66666666666666666667*dt*(2.0*dt + 1.0) + \ + 6.0173399699313066769*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) - \ + 4.1117044797036320069*dt*(0.074074074074074074074*dt*(2.0*dt + 1.0) + \ + 0.2962962962962962963*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.96296296296296296296*dt + 1.0) - \ + 7.0189140975801991157*dt*(-0.049335189898860408029*dt*(2.0*dt + 1.0) + \ + 0.29601113939316244817*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) - \ + 0.12564355335492979587*dt*(0.074074074074074074074*dt*(2.0*dt + 1.0) + \ + 0.2962962962962962963*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.96296296296296296296*dt + 1.0) + 0.22431393315265061193*dt + 1.0) + \ + 0.94010945196161777522*dt*(-0.23637909581542530626*dt*(2.0*dt + 1.0) - \ + 0.74817562366625959291*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.88085458023927036857*dt*(0.074074074074074074074*dt*(2.0*dt + 1.0) + \ + 0.2962962962962962963*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.96296296296296296296*dt + 1.0) + \ + 2.1165151389911680013*dt*(-0.049335189898860408029*dt*(2.0*dt + 1.0) + \ + 0.29601113939316244817*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) - \ + 0.12564355335492979587*dt*(0.074074074074074074074*dt*(2.0*dt + 1.0) + \ + 0.2962962962962962963*dt*(0.125*dt*(2.0*dt + 1.0) + 0.875*dt + 1.0) + \ + 0.96296296296296296296*dt + 1.0) + 0.22431393315265061193*dt + 1.0) - \ + 0.35816132904077632692*dt + 1.0) + 5.5065024887242400038*dt + 1.0)/20 + dt/20 + 1 + + assert N(expr.series(dt, 0, 8), 20) == ( + - Float('0.00092592592592592596126289', precision=70) * dt**7 + + Float('0.0027777777777777783174695', precision=70) * dt**6 + + Float('0.016666666666666656027029', precision=70) * dt**5 + + Float('0.083333333333333300951828', precision=70) * dt**4 + + Float('0.33333333333333337034077', precision=70) * dt**3 + + Float('1.0', precision=70) * dt**2 + + Float('1.0', precision=70) * dt + + Float('1.0', precision=70) + ) + + +def test_issue_11407(): + a, b, c, x = symbols('a b c x') + assert series(sqrt(a + b + c*x), x, 0, 1) == sqrt(a + b) + O(x) + assert series(sqrt(a + b + c + c*x), x, 0, 1) == sqrt(a + b + c) + O(x) + + +def test_issue_14037(): + assert (sin(x**50)/x**51).series(x, n=0) == 1/x + O(1, x) + + +def test_issue_20551(): + expr = (exp(x)/x).series(x, n=None) + terms = [ next(expr) for i in range(3) ] + assert terms == [1/x, 1, x/2] + + +def test_issue_20697(): + p_0, p_1, p_2, p_3, b_0, b_1, b_2 = symbols('p_0 p_1 p_2 p_3 b_0 b_1 b_2') + Q = (p_0 + (p_1 + (p_2 + p_3/y)/y)/y)/(1 + ((p_3/(b_0*y) + (b_0*p_2\ + - b_1*p_3)/b_0**2)/y + (b_0**2*p_1 - b_0*b_1*p_2 - p_3*(b_0*b_2\ + - b_1**2))/b_0**3)/y) + assert Q.series(y, n=3).ratsimp() == b_2*y**2 + b_1*y + b_0 + O(y**3) + + +def test_issue_21245(): + fi = (1 + sqrt(5))/2 + assert (1/(1 - x - x**2)).series(x, 1/fi, 1).factor() == \ + (-37*sqrt(5) - 83 + 13*sqrt(5)*x + 29*x + O((x - 2/(1 + sqrt(5)))**2, (x\ + , 2/(1 + sqrt(5)))))/((2*sqrt(5) + 5)**2*(x + sqrt(5)*x - 2)) + + + +def test_issue_21938(): + expr = sin(1/x + exp(-x)) - sin(1/x) + assert expr.series(x, oo) == (1/(24*x**4) - 1/(2*x**2) + 1 + O(x**(-6), (x, oo)))*exp(-x) + + +def test_issue_23432(): + expr = 1/sqrt(1 - x**2) + result = expr.series(x, 0.5) + assert result.is_Add and len(result.args) == 7 + + +def test_issue_23727(): + res = series(sqrt(1 - x**2), x, 0.1) + assert res.is_Add == True + + +def test_issue_24266(): + #type1: exp(f(x)) + assert (exp(-I*pi*(2*x+1))).series(x, 0, 3) == -1 + 2*I*pi*x + 2*pi**2*x**2 + O(x**3) + assert (exp(-I*pi*(2*x+1))*gamma(1+x)).series(x, 0, 3) == -1 + x*(EulerGamma + 2*I*pi) + \ + x**2*(-EulerGamma**2/2 + 23*pi**2/12 - 2*EulerGamma*I*pi) + O(x**3) + + #type2: c**f(x) + assert ((2*I)**(-I*pi*(2*x+1))).series(x, 0, 2) == exp(pi**2/2 - I*pi*log(2)) + \ + x*(pi**2*exp(pi**2/2 - I*pi*log(2)) - 2*I*pi*exp(pi**2/2 - I*pi*log(2))*log(2)) + O(x**2) + assert ((2)**(-I*pi*(2*x+1))).series(x, 0, 2) == exp(-I*pi*log(2)) - 2*I*pi*x*exp(-I*pi*log(2))*log(2) + O(x**2) + + #type3: f(y)**g(x) + assert ((y)**(I*pi*(2*x+1))).series(x, 0, 2) == exp(I*pi*log(y)) + 2*I*pi*x*exp(I*pi*log(y))*log(y) + O(x**2) + assert ((I*y)**(I*pi*(2*x+1))).series(x, 0, 2) == exp(I*pi*log(I*y)) + 2*I*pi*x*exp(I*pi*log(I*y))*log(I*y) + O(x**2) + + +def test_issue_26856(): + raises(ValueError, lambda: (2**x).series(x, oo, -1)) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__init__.py b/venv/lib/python3.10/site-packages/sympy/stats/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..adb79261954924305c1837555d7d47cd53b8430b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/__init__.py @@ -0,0 +1,202 @@ +""" +SymPy statistics module + +Introduces a random variable type into the SymPy language. + +Random variables may be declared using prebuilt functions such as +Normal, Exponential, Coin, Die, etc... or built with functions like FiniteRV. + +Queries on random expressions can be made using the functions + +========================= ============================= + Expression Meaning +------------------------- ----------------------------- + ``P(condition)`` Probability + ``E(expression)`` Expected value + ``H(expression)`` Entropy + ``variance(expression)`` Variance + ``density(expression)`` Probability Density Function + ``sample(expression)`` Produce a realization + ``where(condition)`` Where the condition is true +========================= ============================= + +Examples +======== + +>>> from sympy.stats import P, E, variance, Die, Normal +>>> from sympy import simplify +>>> X, Y = Die('X', 6), Die('Y', 6) # Define two six sided dice +>>> Z = Normal('Z', 0, 1) # Declare a Normal random variable with mean 0, std 1 +>>> P(X>3) # Probability X is greater than 3 +1/2 +>>> E(X+Y) # Expectation of the sum of two dice +7 +>>> variance(X+Y) # Variance of the sum of two dice +35/6 +>>> simplify(P(Z>1)) # Probability of Z being greater than 1 +1/2 - erf(sqrt(2)/2)/2 + + +One could also create custom distribution and define custom random variables +as follows: + +1. If you want to create a Continuous Random Variable: + +>>> from sympy.stats import ContinuousRV, P, E +>>> from sympy import exp, Symbol, Interval, oo +>>> x = Symbol('x') +>>> pdf = exp(-x) # pdf of the Continuous Distribution +>>> Z = ContinuousRV(x, pdf, set=Interval(0, oo)) +>>> E(Z) +1 +>>> P(Z > 5) +exp(-5) + +1.1 To create an instance of Continuous Distribution: + +>>> from sympy.stats import ContinuousDistributionHandmade +>>> from sympy import Lambda +>>> dist = ContinuousDistributionHandmade(Lambda(x, pdf), set=Interval(0, oo)) +>>> dist.pdf(x) +exp(-x) + +2. If you want to create a Discrete Random Variable: + +>>> from sympy.stats import DiscreteRV, P, E +>>> from sympy import Symbol, S +>>> p = S(1)/2 +>>> x = Symbol('x', integer=True, positive=True) +>>> pdf = p*(1 - p)**(x - 1) +>>> D = DiscreteRV(x, pdf, set=S.Naturals) +>>> E(D) +2 +>>> P(D > 3) +1/8 + +2.1 To create an instance of Discrete Distribution: + +>>> from sympy.stats import DiscreteDistributionHandmade +>>> from sympy import Lambda +>>> dist = DiscreteDistributionHandmade(Lambda(x, pdf), set=S.Naturals) +>>> dist.pdf(x) +2**(1 - x)/2 + +3. If you want to create a Finite Random Variable: + +>>> from sympy.stats import FiniteRV, P, E +>>> from sympy import Rational, Eq +>>> pmf = {1: Rational(1, 3), 2: Rational(1, 6), 3: Rational(1, 4), 4: Rational(1, 4)} +>>> X = FiniteRV('X', pmf) +>>> E(X) +29/12 +>>> P(X > 3) +1/4 + +3.1 To create an instance of Finite Distribution: + +>>> from sympy.stats import FiniteDistributionHandmade +>>> dist = FiniteDistributionHandmade(pmf) +>>> dist.pmf(x) +Lambda(x, Piecewise((1/3, Eq(x, 1)), (1/6, Eq(x, 2)), (1/4, Eq(x, 3) | Eq(x, 4)), (0, True))) +""" + +__all__ = [ + 'P', 'E', 'H', 'density', 'where', 'given', 'sample', 'cdf','median', + 'characteristic_function', 'pspace', 'sample_iter', 'variance', 'std', + 'skewness', 'kurtosis', 'covariance', 'dependent', 'entropy', 'independent', + 'random_symbols', 'correlation', 'factorial_moment', 'moment', 'cmoment', + 'sampling_density', 'moment_generating_function', 'smoment', 'quantile', + 'coskewness', 'sample_stochastic_process', + + 'FiniteRV', 'DiscreteUniform', 'Die', 'Bernoulli', 'Coin', 'Binomial', + 'BetaBinomial', 'Hypergeometric', 'Rademacher', 'IdealSoliton', 'RobustSoliton', + 'FiniteDistributionHandmade', + + 'ContinuousRV', 'Arcsin', 'Benini', 'Beta', 'BetaNoncentral', 'BetaPrime', + 'BoundedPareto', 'Cauchy', 'Chi', 'ChiNoncentral', 'ChiSquared', 'Dagum', 'Davis', 'Erlang', + 'ExGaussian', 'Exponential', 'ExponentialPower', 'FDistribution', + 'FisherZ', 'Frechet', 'Gamma', 'GammaInverse', 'Gompertz', 'Gumbel', + 'Kumaraswamy', 'Laplace', 'Levy', 'Logistic','LogCauchy', 'LogLogistic', 'LogitNormal', 'LogNormal', 'Lomax', + 'Moyal', 'Maxwell', 'Nakagami', 'Normal', 'GaussianInverse', 'Pareto', 'PowerFunction', + 'QuadraticU', 'RaisedCosine', 'Rayleigh','Reciprocal', 'StudentT', 'ShiftedGompertz', + 'Trapezoidal', 'Triangular', 'Uniform', 'UniformSum', 'VonMises', 'Wald', + 'Weibull', 'WignerSemicircle', 'ContinuousDistributionHandmade', + + 'FlorySchulz', 'Geometric','Hermite', 'Logarithmic', 'NegativeBinomial', 'Poisson', 'Skellam', + 'YuleSimon', 'Zeta', 'DiscreteRV', 'DiscreteDistributionHandmade', + + 'JointRV', 'Dirichlet', 'GeneralizedMultivariateLogGamma', + 'GeneralizedMultivariateLogGammaOmega', 'Multinomial', 'MultivariateBeta', + 'MultivariateEwens', 'MultivariateT', 'NegativeMultinomial', + 'NormalGamma', 'MultivariateNormal', 'MultivariateLaplace', 'marginal_distribution', + + 'StochasticProcess', 'DiscreteTimeStochasticProcess', + 'DiscreteMarkovChain', 'TransitionMatrixOf', 'StochasticStateSpaceOf', + 'GeneratorMatrixOf', 'ContinuousMarkovChain', 'BernoulliProcess', + 'PoissonProcess', 'WienerProcess', 'GammaProcess', + + 'CircularEnsemble', 'CircularUnitaryEnsemble', + 'CircularOrthogonalEnsemble', 'CircularSymplecticEnsemble', + 'GaussianEnsemble', 'GaussianUnitaryEnsemble', + 'GaussianOrthogonalEnsemble', 'GaussianSymplecticEnsemble', + 'joint_eigen_distribution', 'JointEigenDistribution', + 'level_spacing_distribution', + + 'MatrixGamma', 'Wishart', 'MatrixNormal', 'MatrixStudentT', + + 'Probability', 'Expectation', 'Variance', 'Covariance', 'Moment', + 'CentralMoment', + + 'ExpectationMatrix', 'VarianceMatrix', 'CrossCovarianceMatrix' + +] +from .rv_interface import (P, E, H, density, where, given, sample, cdf, median, + characteristic_function, pspace, sample_iter, variance, std, skewness, + kurtosis, covariance, dependent, entropy, independent, random_symbols, + correlation, factorial_moment, moment, cmoment, sampling_density, + moment_generating_function, smoment, quantile, coskewness, + sample_stochastic_process) + +from .frv_types import (FiniteRV, DiscreteUniform, Die, Bernoulli, Coin, + Binomial, BetaBinomial, Hypergeometric, Rademacher, + FiniteDistributionHandmade, IdealSoliton, RobustSoliton) + +from .crv_types import (ContinuousRV, Arcsin, Benini, Beta, BetaNoncentral, + BetaPrime, BoundedPareto, Cauchy, Chi, ChiNoncentral, ChiSquared, + Dagum, Davis, Erlang, ExGaussian, Exponential, ExponentialPower, + FDistribution, FisherZ, Frechet, Gamma, GammaInverse, GaussianInverse, + Gompertz, Gumbel, Kumaraswamy, Laplace, Levy, Logistic, LogCauchy, + LogLogistic, LogitNormal, LogNormal, Lomax, Maxwell, Moyal, Nakagami, + Normal, Pareto, QuadraticU, RaisedCosine, Rayleigh, Reciprocal, + StudentT, PowerFunction, ShiftedGompertz, Trapezoidal, Triangular, + Uniform, UniformSum, VonMises, Wald, Weibull, WignerSemicircle, + ContinuousDistributionHandmade) + +from .drv_types import (FlorySchulz, Geometric, Hermite, Logarithmic, NegativeBinomial, Poisson, + Skellam, YuleSimon, Zeta, DiscreteRV, DiscreteDistributionHandmade) + +from .joint_rv_types import (JointRV, Dirichlet, + GeneralizedMultivariateLogGamma, GeneralizedMultivariateLogGammaOmega, + Multinomial, MultivariateBeta, MultivariateEwens, MultivariateT, + NegativeMultinomial, NormalGamma, MultivariateNormal, MultivariateLaplace, + marginal_distribution) + +from .stochastic_process_types import (StochasticProcess, + DiscreteTimeStochasticProcess, DiscreteMarkovChain, + TransitionMatrixOf, StochasticStateSpaceOf, GeneratorMatrixOf, + ContinuousMarkovChain, BernoulliProcess, PoissonProcess, WienerProcess, + GammaProcess) + +from .random_matrix_models import (CircularEnsemble, CircularUnitaryEnsemble, + CircularOrthogonalEnsemble, CircularSymplecticEnsemble, + GaussianEnsemble, GaussianUnitaryEnsemble, GaussianOrthogonalEnsemble, + GaussianSymplecticEnsemble, joint_eigen_distribution, + JointEigenDistribution, level_spacing_distribution) + +from .matrix_distributions import MatrixGamma, Wishart, MatrixNormal, MatrixStudentT + +from .symbolic_probability import (Probability, Expectation, Variance, + Covariance, Moment, CentralMoment) + +from .symbolic_multivariate_probability import (ExpectationMatrix, VarianceMatrix, + CrossCovarianceMatrix) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d6822d28e33e992ef50392241cfaa74cd4c8c53 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/compound_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/compound_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4adefd2ebf81fbc625233baf4da27d70838b65a2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/compound_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/crv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/crv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6acaf9b51cd7e724106410cdf0c967b63421700f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/crv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/drv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/drv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9754409aa9029193e8bc2da7d7be5656efe16786 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/drv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/drv_types.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/drv_types.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a54f655a3adebbe82f146319b24c20d00dfda371 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/drv_types.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/error_prop.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/error_prop.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d4ce42ad280f8595be36570a3cfdeef19b38695 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/error_prop.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/frv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/frv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b73dd87fbda5f1e43b35a4dfa75e8e1d75495aaa Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/frv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/frv_types.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/frv_types.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3665dab2516321e4e9e8c757e717b36a15f7efb7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/frv_types.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/joint_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/joint_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7b10503b92c41359c302c59853d4dfebe1aef27 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/joint_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/joint_rv_types.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/joint_rv_types.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..700c801f1a3390c7675c4fc90a0928709bf7793f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/joint_rv_types.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/matrix_distributions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/matrix_distributions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d364317c5eff39dc4d5692a1ae69fa150bf78468 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/matrix_distributions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/random_matrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/random_matrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d03f63d2930ec5eb2ad6e13166fe303c931bcd3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/random_matrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/random_matrix_models.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/random_matrix_models.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8e5e5aee899b88edb43d9fd29dd5fe474368a2d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/random_matrix_models.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1afe9cb303946758701933306b9f3293e964cf1 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/rv_interface.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/rv_interface.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa673647a578bdb95a6586140eb32589e96c7a05 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/rv_interface.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/stochastic_process.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/stochastic_process.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4692a57d9cf60b12ccdf4ee6f528b06b9823bc2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/stochastic_process.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/stochastic_process_types.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/stochastic_process_types.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9ccb673a1a819671d9d240dd2f7ba16f25d4ab5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/stochastic_process_types.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/symbolic_multivariate_probability.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/symbolic_multivariate_probability.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bf7612d808b7e57b4a11351e2a94edec32fbe50 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/symbolic_multivariate_probability.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/symbolic_probability.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/symbolic_probability.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2966ee603c11672febde6c8175880022c5eb23b4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/__pycache__/symbolic_probability.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/compound_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/compound_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..27555f4233fe691bac303800a87736205acbdee6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/compound_rv.py @@ -0,0 +1,223 @@ +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.function import Lambda +from sympy.core.symbol import Dummy +from sympy.integrals.integrals import Integral +from sympy.stats.rv import (NamedArgsMixin, random_symbols, _symbol_converter, + PSpace, RandomSymbol, is_random, Distribution) +from sympy.stats.crv import ContinuousDistribution, SingleContinuousPSpace +from sympy.stats.drv import DiscreteDistribution, SingleDiscretePSpace +from sympy.stats.frv import SingleFiniteDistribution, SingleFinitePSpace +from sympy.stats.crv_types import ContinuousDistributionHandmade +from sympy.stats.drv_types import DiscreteDistributionHandmade +from sympy.stats.frv_types import FiniteDistributionHandmade + + +class CompoundPSpace(PSpace): + """ + A temporary Probability Space for the Compound Distribution. After + Marginalization, this returns the corresponding Probability Space of the + parent distribution. + """ + + def __new__(cls, s, distribution): + s = _symbol_converter(s) + if isinstance(distribution, ContinuousDistribution): + return SingleContinuousPSpace(s, distribution) + if isinstance(distribution, DiscreteDistribution): + return SingleDiscretePSpace(s, distribution) + if isinstance(distribution, SingleFiniteDistribution): + return SingleFinitePSpace(s, distribution) + if not isinstance(distribution, CompoundDistribution): + raise ValueError("%s should be an isinstance of " + "CompoundDistribution"%(distribution)) + return Basic.__new__(cls, s, distribution) + + @property + def value(self): + return RandomSymbol(self.symbol, self) + + @property + def symbol(self): + return self.args[0] + + @property + def is_Continuous(self): + return self.distribution.is_Continuous + + @property + def is_Finite(self): + return self.distribution.is_Finite + + @property + def is_Discrete(self): + return self.distribution.is_Discrete + + @property + def distribution(self): + return self.args[1] + + @property + def pdf(self): + return self.distribution.pdf(self.symbol) + + @property + def set(self): + return self.distribution.set + + @property + def domain(self): + return self._get_newpspace().domain + + def _get_newpspace(self, evaluate=False): + x = Dummy('x') + parent_dist = self.distribution.args[0] + func = Lambda(x, self.distribution.pdf(x, evaluate)) + new_pspace = self._transform_pspace(self.symbol, parent_dist, func) + if new_pspace is not None: + return new_pspace + message = ("Compound Distribution for %s is not implemented yet" % str(parent_dist)) + raise NotImplementedError(message) + + def _transform_pspace(self, sym, dist, pdf): + """ + This function returns the new pspace of the distribution using handmade + Distributions and their corresponding pspace. + """ + pdf = Lambda(sym, pdf(sym)) + _set = dist.set + if isinstance(dist, ContinuousDistribution): + return SingleContinuousPSpace(sym, ContinuousDistributionHandmade(pdf, _set)) + elif isinstance(dist, DiscreteDistribution): + return SingleDiscretePSpace(sym, DiscreteDistributionHandmade(pdf, _set)) + elif isinstance(dist, SingleFiniteDistribution): + dens = {k: pdf(k) for k in _set} + return SingleFinitePSpace(sym, FiniteDistributionHandmade(dens)) + + def compute_density(self, expr, *, compound_evaluate=True, **kwargs): + new_pspace = self._get_newpspace(compound_evaluate) + expr = expr.subs({self.value: new_pspace.value}) + return new_pspace.compute_density(expr, **kwargs) + + def compute_cdf(self, expr, *, compound_evaluate=True, **kwargs): + new_pspace = self._get_newpspace(compound_evaluate) + expr = expr.subs({self.value: new_pspace.value}) + return new_pspace.compute_cdf(expr, **kwargs) + + def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs): + new_pspace = self._get_newpspace(evaluate) + expr = expr.subs({self.value: new_pspace.value}) + if rvs: + rvs = rvs.subs({self.value: new_pspace.value}) + if isinstance(new_pspace, SingleFinitePSpace): + return new_pspace.compute_expectation(expr, rvs, **kwargs) + return new_pspace.compute_expectation(expr, rvs, evaluate, **kwargs) + + def probability(self, condition, *, compound_evaluate=True, **kwargs): + new_pspace = self._get_newpspace(compound_evaluate) + condition = condition.subs({self.value: new_pspace.value}) + return new_pspace.probability(condition) + + def conditional_space(self, condition, *, compound_evaluate=True, **kwargs): + new_pspace = self._get_newpspace(compound_evaluate) + condition = condition.subs({self.value: new_pspace.value}) + return new_pspace.conditional_space(condition) + + +class CompoundDistribution(Distribution, NamedArgsMixin): + """ + Class for Compound Distributions. + + Parameters + ========== + + dist : Distribution + Distribution must contain a random parameter + + Examples + ======== + + >>> from sympy.stats.compound_rv import CompoundDistribution + >>> from sympy.stats.crv_types import NormalDistribution + >>> from sympy.stats import Normal + >>> from sympy.abc import x + >>> X = Normal('X', 2, 4) + >>> N = NormalDistribution(X, 4) + >>> C = CompoundDistribution(N) + >>> C.set + Interval(-oo, oo) + >>> C.pdf(x, evaluate=True).simplify() + exp(-x**2/64 + x/16 - 1/16)/(8*sqrt(pi)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Compound_probability_distribution + + """ + + def __new__(cls, dist): + if not isinstance(dist, (ContinuousDistribution, + SingleFiniteDistribution, DiscreteDistribution)): + message = "Compound Distribution for %s is not implemented yet" % str(dist) + raise NotImplementedError(message) + if not cls._compound_check(dist): + return dist + return Basic.__new__(cls, dist) + + @property + def set(self): + return self.args[0].set + + @property + def is_Continuous(self): + return isinstance(self.args[0], ContinuousDistribution) + + @property + def is_Finite(self): + return isinstance(self.args[0], SingleFiniteDistribution) + + @property + def is_Discrete(self): + return isinstance(self.args[0], DiscreteDistribution) + + def pdf(self, x, evaluate=False): + dist = self.args[0] + randoms = [rv for rv in dist.args if is_random(rv)] + if isinstance(dist, SingleFiniteDistribution): + y = Dummy('y', integer=True, negative=False) + expr = dist.pmf(y) + else: + y = Dummy('y') + expr = dist.pdf(y) + for rv in randoms: + expr = self._marginalise(expr, rv, evaluate) + return Lambda(y, expr)(x) + + def _marginalise(self, expr, rv, evaluate): + if isinstance(rv.pspace.distribution, SingleFiniteDistribution): + rv_dens = rv.pspace.distribution.pmf(rv) + else: + rv_dens = rv.pspace.distribution.pdf(rv) + rv_dom = rv.pspace.domain.set + if rv.pspace.is_Discrete or rv.pspace.is_Finite: + expr = Sum(expr*rv_dens, (rv, rv_dom._inf, + rv_dom._sup)) + else: + expr = Integral(expr*rv_dens, (rv, rv_dom._inf, + rv_dom._sup)) + if evaluate: + return expr.doit() + return expr + + @classmethod + def _compound_check(self, dist): + """ + Checks if the given distribution contains random parameters. + """ + randoms = [] + for arg in dist.args: + randoms.extend(random_symbols(arg)) + if len(randoms) == 0: + return False + return True diff --git a/venv/lib/python3.10/site-packages/sympy/stats/crv.py b/venv/lib/python3.10/site-packages/sympy/stats/crv.py new file mode 100644 index 0000000000000000000000000000000000000000..0a5184029679f663c83d81aa6c1b6ca4d948c70f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/crv.py @@ -0,0 +1,570 @@ +""" +Continuous Random Variables Module + +See Also +======== +sympy.stats.crv_types +sympy.stats.rv +sympy.stats.frv +""" + + +from sympy.core.basic import Basic +from sympy.core.cache import cacheit +from sympy.core.function import Lambda, PoleError +from sympy.core.numbers import (I, nan, oo) +from sympy.core.relational import (Eq, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.core.sympify import _sympify, sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.delta_functions import DiracDelta +from sympy.integrals.integrals import (Integral, integrate) +from sympy.logic.boolalg import (And, Or) +from sympy.polys.polyerrors import PolynomialError +from sympy.polys.polytools import poly +from sympy.series.series import series +from sympy.sets.sets import (FiniteSet, Intersection, Interval, Union) +from sympy.solvers.solveset import solveset +from sympy.solvers.inequalities import reduce_rational_inequalities +from sympy.stats.rv import (RandomDomain, SingleDomain, ConditionalDomain, is_random, + ProductDomain, PSpace, SinglePSpace, random_symbols, NamedArgsMixin, Distribution) + + +class ContinuousDomain(RandomDomain): + """ + A domain with continuous support + + Represented using symbols and Intervals. + """ + is_Continuous = True + + def as_boolean(self): + raise NotImplementedError("Not Implemented for generic Domains") + + +class SingleContinuousDomain(ContinuousDomain, SingleDomain): + """ + A univariate domain with continuous support + + Represented using a single symbol and interval. + """ + def compute_expectation(self, expr, variables=None, **kwargs): + if variables is None: + variables = self.symbols + if not variables: + return expr + if frozenset(variables) != frozenset(self.symbols): + raise ValueError("Values should be equal") + # assumes only intervals + return Integral(expr, (self.symbol, self.set), **kwargs) + + def as_boolean(self): + return self.set.as_relational(self.symbol) + + +class ProductContinuousDomain(ProductDomain, ContinuousDomain): + """ + A collection of independent domains with continuous support + """ + + def compute_expectation(self, expr, variables=None, **kwargs): + if variables is None: + variables = self.symbols + for domain in self.domains: + domain_vars = frozenset(variables) & frozenset(domain.symbols) + if domain_vars: + expr = domain.compute_expectation(expr, domain_vars, **kwargs) + return expr + + def as_boolean(self): + return And(*[domain.as_boolean() for domain in self.domains]) + + +class ConditionalContinuousDomain(ContinuousDomain, ConditionalDomain): + """ + A domain with continuous support that has been further restricted by a + condition such as $x > 3$. + """ + + def compute_expectation(self, expr, variables=None, **kwargs): + if variables is None: + variables = self.symbols + if not variables: + return expr + # Extract the full integral + fullintgrl = self.fulldomain.compute_expectation(expr, variables) + # separate into integrand and limits + integrand, limits = fullintgrl.function, list(fullintgrl.limits) + + conditions = [self.condition] + while conditions: + cond = conditions.pop() + if cond.is_Boolean: + if isinstance(cond, And): + conditions.extend(cond.args) + elif isinstance(cond, Or): + raise NotImplementedError("Or not implemented here") + elif cond.is_Relational: + if cond.is_Equality: + # Add the appropriate Delta to the integrand + integrand *= DiracDelta(cond.lhs - cond.rhs) + else: + symbols = cond.free_symbols & set(self.symbols) + if len(symbols) != 1: # Can't handle x > y + raise NotImplementedError( + "Multivariate Inequalities not yet implemented") + # Can handle x > 0 + symbol = symbols.pop() + # Find the limit with x, such as (x, -oo, oo) + for i, limit in enumerate(limits): + if limit[0] == symbol: + # Make condition into an Interval like [0, oo] + cintvl = reduce_rational_inequalities_wrap( + cond, symbol) + # Make limit into an Interval like [-oo, oo] + lintvl = Interval(limit[1], limit[2]) + # Intersect them to get [0, oo] + intvl = cintvl.intersect(lintvl) + # Put back into limits list + limits[i] = (symbol, intvl.left, intvl.right) + else: + raise TypeError( + "Condition %s is not a relational or Boolean" % cond) + + return Integral(integrand, *limits, **kwargs) + + def as_boolean(self): + return And(self.fulldomain.as_boolean(), self.condition) + + @property + def set(self): + if len(self.symbols) == 1: + return (self.fulldomain.set & reduce_rational_inequalities_wrap( + self.condition, tuple(self.symbols)[0])) + else: + raise NotImplementedError( + "Set of Conditional Domain not Implemented") + + +class ContinuousDistribution(Distribution): + def __call__(self, *args): + return self.pdf(*args) + + +class SingleContinuousDistribution(ContinuousDistribution, NamedArgsMixin): + """ Continuous distribution of a single variable. + + Explanation + =========== + + Serves as superclass for Normal/Exponential/UniformDistribution etc.... + + Represented by parameters for each of the specific classes. E.g + NormalDistribution is represented by a mean and standard deviation. + + Provides methods for pdf, cdf, and sampling. + + See Also + ======== + + sympy.stats.crv_types.* + """ + + set = Interval(-oo, oo) + + def __new__(cls, *args): + args = list(map(sympify, args)) + return Basic.__new__(cls, *args) + + @staticmethod + def check(*args): + pass + + @cacheit + def compute_cdf(self, **kwargs): + """ Compute the CDF from the PDF. + + Returns a Lambda. + """ + x, z = symbols('x, z', real=True, cls=Dummy) + left_bound = self.set.start + + # CDF is integral of PDF from left bound to z + pdf = self.pdf(x) + cdf = integrate(pdf.doit(), (x, left_bound, z), **kwargs) + # CDF Ensure that CDF left of left_bound is zero + cdf = Piecewise((cdf, z >= left_bound), (0, True)) + return Lambda(z, cdf) + + def _cdf(self, x): + return None + + def cdf(self, x, **kwargs): + """ Cumulative density function """ + if len(kwargs) == 0: + cdf = self._cdf(x) + if cdf is not None: + return cdf + return self.compute_cdf(**kwargs)(x) + + @cacheit + def compute_characteristic_function(self, **kwargs): + """ Compute the characteristic function from the PDF. + + Returns a Lambda. + """ + x, t = symbols('x, t', real=True, cls=Dummy) + pdf = self.pdf(x) + cf = integrate(exp(I*t*x)*pdf, (x, self.set)) + return Lambda(t, cf) + + def _characteristic_function(self, t): + return None + + def characteristic_function(self, t, **kwargs): + """ Characteristic function """ + if len(kwargs) == 0: + cf = self._characteristic_function(t) + if cf is not None: + return cf + return self.compute_characteristic_function(**kwargs)(t) + + @cacheit + def compute_moment_generating_function(self, **kwargs): + """ Compute the moment generating function from the PDF. + + Returns a Lambda. + """ + x, t = symbols('x, t', real=True, cls=Dummy) + pdf = self.pdf(x) + mgf = integrate(exp(t * x) * pdf, (x, self.set)) + return Lambda(t, mgf) + + def _moment_generating_function(self, t): + return None + + def moment_generating_function(self, t, **kwargs): + """ Moment generating function """ + if not kwargs: + mgf = self._moment_generating_function(t) + if mgf is not None: + return mgf + return self.compute_moment_generating_function(**kwargs)(t) + + def expectation(self, expr, var, evaluate=True, **kwargs): + """ Expectation of expression over distribution """ + if evaluate: + try: + p = poly(expr, var) + if p.is_zero: + return S.Zero + t = Dummy('t', real=True) + mgf = self._moment_generating_function(t) + if mgf is None: + return integrate(expr * self.pdf(var), (var, self.set), **kwargs) + deg = p.degree() + taylor = poly(series(mgf, t, 0, deg + 1).removeO(), t) + result = 0 + for k in range(deg+1): + result += p.coeff_monomial(var ** k) * taylor.coeff_monomial(t ** k) * factorial(k) + return result + except PolynomialError: + return integrate(expr * self.pdf(var), (var, self.set), **kwargs) + else: + return Integral(expr * self.pdf(var), (var, self.set), **kwargs) + + @cacheit + def compute_quantile(self, **kwargs): + """ Compute the Quantile from the PDF. + + Returns a Lambda. + """ + x, p = symbols('x, p', real=True, cls=Dummy) + left_bound = self.set.start + + pdf = self.pdf(x) + cdf = integrate(pdf, (x, left_bound, x), **kwargs) + quantile = solveset(cdf - p, x, self.set) + return Lambda(p, Piecewise((quantile, (p >= 0) & (p <= 1) ), (nan, True))) + + def _quantile(self, x): + return None + + def quantile(self, x, **kwargs): + """ Cumulative density function """ + if len(kwargs) == 0: + quantile = self._quantile(x) + if quantile is not None: + return quantile + return self.compute_quantile(**kwargs)(x) + + +class ContinuousPSpace(PSpace): + """ Continuous Probability Space + + Represents the likelihood of an event space defined over a continuum. + + Represented with a ContinuousDomain and a PDF (Lambda-Like) + """ + + is_Continuous = True + is_real = True + + @property + def pdf(self): + return self.density(*self.domain.symbols) + + def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs): + if rvs is None: + rvs = self.values + else: + rvs = frozenset(rvs) + + expr = expr.xreplace({rv: rv.symbol for rv in rvs}) + + domain_symbols = frozenset(rv.symbol for rv in rvs) + + return self.domain.compute_expectation(self.pdf * expr, + domain_symbols, **kwargs) + + def compute_density(self, expr, **kwargs): + # Common case Density(X) where X in self.values + if expr in self.values: + # Marginalize all other random symbols out of the density + randomsymbols = tuple(set(self.values) - frozenset([expr])) + symbols = tuple(rs.symbol for rs in randomsymbols) + pdf = self.domain.compute_expectation(self.pdf, symbols, **kwargs) + return Lambda(expr.symbol, pdf) + + z = Dummy('z', real=True) + return Lambda(z, self.compute_expectation(DiracDelta(expr - z), **kwargs)) + + @cacheit + def compute_cdf(self, expr, **kwargs): + if not self.domain.set.is_Interval: + raise ValueError( + "CDF not well defined on multivariate expressions") + + d = self.compute_density(expr, **kwargs) + x, z = symbols('x, z', real=True, cls=Dummy) + left_bound = self.domain.set.start + + # CDF is integral of PDF from left bound to z + cdf = integrate(d(x), (x, left_bound, z), **kwargs) + # CDF Ensure that CDF left of left_bound is zero + cdf = Piecewise((cdf, z >= left_bound), (0, True)) + return Lambda(z, cdf) + + @cacheit + def compute_characteristic_function(self, expr, **kwargs): + if not self.domain.set.is_Interval: + raise NotImplementedError("Characteristic function of multivariate expressions not implemented") + + d = self.compute_density(expr, **kwargs) + x, t = symbols('x, t', real=True, cls=Dummy) + cf = integrate(exp(I*t*x)*d(x), (x, -oo, oo), **kwargs) + return Lambda(t, cf) + + @cacheit + def compute_moment_generating_function(self, expr, **kwargs): + if not self.domain.set.is_Interval: + raise NotImplementedError("Moment generating function of multivariate expressions not implemented") + + d = self.compute_density(expr, **kwargs) + x, t = symbols('x, t', real=True, cls=Dummy) + mgf = integrate(exp(t * x) * d(x), (x, -oo, oo), **kwargs) + return Lambda(t, mgf) + + @cacheit + def compute_quantile(self, expr, **kwargs): + if not self.domain.set.is_Interval: + raise ValueError( + "Quantile not well defined on multivariate expressions") + + d = self.compute_cdf(expr, **kwargs) + x = Dummy('x', real=True) + p = Dummy('p', positive=True) + + quantile = solveset(d(x) - p, x, self.set) + + return Lambda(p, quantile) + + def probability(self, condition, **kwargs): + z = Dummy('z', real=True) + cond_inv = False + if isinstance(condition, Ne): + condition = Eq(condition.args[0], condition.args[1]) + cond_inv = True + # Univariate case can be handled by where + try: + domain = self.where(condition) + rv = [rv for rv in self.values if rv.symbol == domain.symbol][0] + # Integrate out all other random variables + pdf = self.compute_density(rv, **kwargs) + # return S.Zero if `domain` is empty set + if domain.set is S.EmptySet or isinstance(domain.set, FiniteSet): + return S.Zero if not cond_inv else S.One + if isinstance(domain.set, Union): + return sum( + Integral(pdf(z), (z, subset), **kwargs) for subset in + domain.set.args if isinstance(subset, Interval)) + # Integrate out the last variable over the special domain + return Integral(pdf(z), (z, domain.set), **kwargs) + + # Other cases can be turned into univariate case + # by computing a density handled by density computation + except NotImplementedError: + from sympy.stats.rv import density + expr = condition.lhs - condition.rhs + if not is_random(expr): + dens = self.density + comp = condition.rhs + else: + dens = density(expr, **kwargs) + comp = 0 + if not isinstance(dens, ContinuousDistribution): + from sympy.stats.crv_types import ContinuousDistributionHandmade + dens = ContinuousDistributionHandmade(dens, set=self.domain.set) + # Turn problem into univariate case + space = SingleContinuousPSpace(z, dens) + result = space.probability(condition.__class__(space.value, comp)) + return result if not cond_inv else S.One - result + + def where(self, condition): + rvs = frozenset(random_symbols(condition)) + if not (len(rvs) == 1 and rvs.issubset(self.values)): + raise NotImplementedError( + "Multiple continuous random variables not supported") + rv = tuple(rvs)[0] + interval = reduce_rational_inequalities_wrap(condition, rv) + interval = interval.intersect(self.domain.set) + return SingleContinuousDomain(rv.symbol, interval) + + def conditional_space(self, condition, normalize=True, **kwargs): + condition = condition.xreplace({rv: rv.symbol for rv in self.values}) + domain = ConditionalContinuousDomain(self.domain, condition) + if normalize: + # create a clone of the variable to + # make sure that variables in nested integrals are different + # from the variables outside the integral + # this makes sure that they are evaluated separately + # and in the correct order + replacement = {rv: Dummy(str(rv)) for rv in self.symbols} + norm = domain.compute_expectation(self.pdf, **kwargs) + pdf = self.pdf / norm.xreplace(replacement) + # XXX: Converting set to tuple. The order matters to Lambda though + # so we shouldn't be starting with a set here... + density = Lambda(tuple(domain.symbols), pdf) + + return ContinuousPSpace(domain, density) + + +class SingleContinuousPSpace(ContinuousPSpace, SinglePSpace): + """ + A continuous probability space over a single univariate variable. + + These consist of a Symbol and a SingleContinuousDistribution + + This class is normally accessed through the various random variable + functions, Normal, Exponential, Uniform, etc.... + """ + + @property + def set(self): + return self.distribution.set + + @property + def domain(self): + return SingleContinuousDomain(sympify(self.symbol), self.set) + + def sample(self, size=(), library='scipy', seed=None): + """ + Internal sample method. + + Returns dictionary mapping RandomSymbol to realization value. + """ + return {self.value: self.distribution.sample(size, library=library, seed=seed)} + + def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs): + rvs = rvs or (self.value,) + if self.value not in rvs: + return expr + + expr = _sympify(expr) + expr = expr.xreplace({rv: rv.symbol for rv in rvs}) + + x = self.value.symbol + try: + return self.distribution.expectation(expr, x, evaluate=evaluate, **kwargs) + except PoleError: + return Integral(expr * self.pdf, (x, self.set), **kwargs) + + def compute_cdf(self, expr, **kwargs): + if expr == self.value: + z = Dummy("z", real=True) + return Lambda(z, self.distribution.cdf(z, **kwargs)) + else: + return ContinuousPSpace.compute_cdf(self, expr, **kwargs) + + def compute_characteristic_function(self, expr, **kwargs): + if expr == self.value: + t = Dummy("t", real=True) + return Lambda(t, self.distribution.characteristic_function(t, **kwargs)) + else: + return ContinuousPSpace.compute_characteristic_function(self, expr, **kwargs) + + def compute_moment_generating_function(self, expr, **kwargs): + if expr == self.value: + t = Dummy("t", real=True) + return Lambda(t, self.distribution.moment_generating_function(t, **kwargs)) + else: + return ContinuousPSpace.compute_moment_generating_function(self, expr, **kwargs) + + def compute_density(self, expr, **kwargs): + # https://en.wikipedia.org/wiki/Random_variable#Functions_of_random_variables + if expr == self.value: + return self.density + y = Dummy('y', real=True) + + gs = solveset(expr - y, self.value, S.Reals) + + if isinstance(gs, Intersection): + if len(gs.args) == 2 and gs.args[0] is S.Reals: + gs = gs.args[1] + if not gs.is_FiniteSet: + raise ValueError("Can not solve %s for %s" % (expr, self.value)) + fx = self.compute_density(self.value) + fy = sum(fx(g) * abs(g.diff(y)) for g in gs) + return Lambda(y, fy) + + def compute_quantile(self, expr, **kwargs): + + if expr == self.value: + p = Dummy("p", real=True) + return Lambda(p, self.distribution.quantile(p, **kwargs)) + else: + return ContinuousPSpace.compute_quantile(self, expr, **kwargs) + +def _reduce_inequalities(conditions, var, **kwargs): + try: + return reduce_rational_inequalities(conditions, var, **kwargs) + except PolynomialError: + raise ValueError("Reduction of condition failed %s\n" % conditions[0]) + + +def reduce_rational_inequalities_wrap(condition, var): + if condition.is_Relational: + return _reduce_inequalities([[condition]], var, relational=False) + if isinstance(condition, Or): + return Union(*[_reduce_inequalities([[arg]], var, relational=False) + for arg in condition.args]) + if isinstance(condition, And): + intervals = [_reduce_inequalities([[arg]], var, relational=False) + for arg in condition.args] + I = intervals[0] + for i in intervals: + I = I.intersect(i) + return I diff --git a/venv/lib/python3.10/site-packages/sympy/stats/crv_types.py b/venv/lib/python3.10/site-packages/sympy/stats/crv_types.py new file mode 100644 index 0000000000000000000000000000000000000000..073e7350fdf80aac39ecd1dd607488a8b76187e3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/crv_types.py @@ -0,0 +1,4732 @@ +""" +Continuous Random Variables - Prebuilt variables + +Contains +======== +Arcsin +Benini +Beta +BetaNoncentral +BetaPrime +BoundedPareto +Cauchy +Chi +ChiNoncentral +ChiSquared +Dagum +Davis +Erlang +ExGaussian +Exponential +ExponentialPower +FDistribution +FisherZ +Frechet +Gamma +GammaInverse +Gumbel +Gompertz +Kumaraswamy +Laplace +Levy +LogCauchy +Logistic +LogLogistic +LogitNormal +LogNormal +Lomax +Maxwell +Moyal +Nakagami +Normal +Pareto +PowerFunction +QuadraticU +RaisedCosine +Rayleigh +Reciprocal +ShiftedGompertz +StudentT +Trapezoidal +Triangular +Uniform +UniformSum +VonMises +Wald +Weibull +WignerSemicircle +""" + + + +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.trigonometric import (atan, cos, sin, tan) +from sympy.functions.special.bessel import (besseli, besselj, besselk) +from sympy.functions.special.beta_functions import beta as beta_fn +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.function import Lambda +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import (Eq, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import (binomial, factorial) +from sympy.functions.elementary.complexes import (Abs, sign) +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.hyperbolic import sinh +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt, Max, Min +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import asin +from sympy.functions.special.error_functions import (erf, erfc, erfi, erfinv, expint) +from sympy.functions.special.gamma_functions import (gamma, lowergamma, uppergamma) +from sympy.functions.special.zeta_functions import zeta +from sympy.functions.special.hyper import hyper +from sympy.integrals.integrals import integrate +from sympy.logic.boolalg import And +from sympy.sets.sets import Interval +from sympy.matrices import MatrixBase +from sympy.stats.crv import SingleContinuousPSpace, SingleContinuousDistribution +from sympy.stats.rv import _value_check, is_random + +oo = S.Infinity + +__all__ = ['ContinuousRV', +'Arcsin', +'Benini', +'Beta', +'BetaNoncentral', +'BetaPrime', +'BoundedPareto', +'Cauchy', +'Chi', +'ChiNoncentral', +'ChiSquared', +'Dagum', +'Davis', +'Erlang', +'ExGaussian', +'Exponential', +'ExponentialPower', +'FDistribution', +'FisherZ', +'Frechet', +'Gamma', +'GammaInverse', +'Gompertz', +'Gumbel', +'Kumaraswamy', +'Laplace', +'Levy', +'LogCauchy', +'Logistic', +'LogLogistic', +'LogitNormal', +'LogNormal', +'Lomax', +'Maxwell', +'Moyal', +'Nakagami', +'Normal', +'GaussianInverse', +'Pareto', +'PowerFunction', +'QuadraticU', +'RaisedCosine', +'Rayleigh', +'Reciprocal', +'StudentT', +'ShiftedGompertz', +'Trapezoidal', +'Triangular', +'Uniform', +'UniformSum', +'VonMises', +'Wald', +'Weibull', +'WignerSemicircle', +] + + +@is_random.register(MatrixBase) +def _(x): + return any(is_random(i) for i in x) + +def rv(symbol, cls, args, **kwargs): + args = list(map(sympify, args)) + dist = cls(*args) + if kwargs.pop('check', True): + dist.check(*args) + pspace = SingleContinuousPSpace(symbol, dist) + if any(is_random(arg) for arg in args): + from sympy.stats.compound_rv import CompoundPSpace, CompoundDistribution + pspace = CompoundPSpace(symbol, CompoundDistribution(dist)) + return pspace.value + + +class ContinuousDistributionHandmade(SingleContinuousDistribution): + _argnames = ('pdf',) + + def __new__(cls, pdf, set=Interval(-oo, oo)): + return Basic.__new__(cls, pdf, set) + + @property + def set(self): + return self.args[1] + + @staticmethod + def check(pdf, set): + x = Dummy('x') + val = integrate(pdf(x), (x, set)) + _value_check(Eq(val, 1) != S.false, "The pdf on the given set is incorrect.") + + +def ContinuousRV(symbol, density, set=Interval(-oo, oo), **kwargs): + """ + Create a Continuous Random Variable given the following: + + Parameters + ========== + + symbol : Symbol + Represents name of the random variable. + density : Expression containing symbol + Represents probability density function. + set : set/Interval + Represents the region where the pdf is valid, by default is real line. + check : bool + If True, it will check whether the given density + integrates to 1 over the given set. If False, it + will not perform this check. Default is False. + + + Returns + ======= + + RandomSymbol + + Many common continuous random variable types are already implemented. + This function should be necessary only very rarely. + + + Examples + ======== + + >>> from sympy import Symbol, sqrt, exp, pi + >>> from sympy.stats import ContinuousRV, P, E + + >>> x = Symbol("x") + + >>> pdf = sqrt(2)*exp(-x**2/2)/(2*sqrt(pi)) # Normal distribution + >>> X = ContinuousRV(x, pdf) + + >>> E(X) + 0 + >>> P(X>0) + 1/2 + """ + pdf = Piecewise((density, set.as_relational(symbol)), (0, True)) + pdf = Lambda(symbol, pdf) + # have a default of False while `rv` should have a default of True + kwargs['check'] = kwargs.pop('check', False) + return rv(symbol.name, ContinuousDistributionHandmade, (pdf, set), **kwargs) + +######################################## +# Continuous Probability Distributions # +######################################## + +#------------------------------------------------------------------------------- +# Arcsin distribution ---------------------------------------------------------- + + +class ArcsinDistribution(SingleContinuousDistribution): + _argnames = ('a', 'b') + + @property + def set(self): + return Interval(self.a, self.b) + + def pdf(self, x): + a, b = self.a, self.b + return 1/(pi*sqrt((x - a)*(b - x))) + + def _cdf(self, x): + a, b = self.a, self.b + return Piecewise( + (S.Zero, x < a), + (2*asin(sqrt((x - a)/(b - a)))/pi, x <= b), + (S.One, True)) + + +def Arcsin(name, a=0, b=1): + r""" + Create a Continuous Random Variable with an arcsin distribution. + + The density of the arcsin distribution is given by + + .. math:: + f(x) := \frac{1}{\pi\sqrt{(x-a)(b-x)}} + + with :math:`x \in (a,b)`. It must hold that :math:`-\infty < a < b < \infty`. + + Parameters + ========== + + a : Real number, the left interval boundary + b : Real number, the right interval boundary + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Arcsin, density, cdf + >>> from sympy import Symbol + + >>> a = Symbol("a", real=True) + >>> b = Symbol("b", real=True) + >>> z = Symbol("z") + + >>> X = Arcsin("x", a, b) + + >>> density(X)(z) + 1/(pi*sqrt((-a + z)*(b - z))) + + >>> cdf(X)(z) + Piecewise((0, a > z), + (2*asin(sqrt((-a + z)/(-a + b)))/pi, b >= z), + (1, True)) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Arcsine_distribution + + """ + + return rv(name, ArcsinDistribution, (a, b)) + +#------------------------------------------------------------------------------- +# Benini distribution ---------------------------------------------------------- + + +class BeniniDistribution(SingleContinuousDistribution): + _argnames = ('alpha', 'beta', 'sigma') + + @staticmethod + def check(alpha, beta, sigma): + _value_check(alpha > 0, "Shape parameter Alpha must be positive.") + _value_check(beta > 0, "Shape parameter Beta must be positive.") + _value_check(sigma > 0, "Scale parameter Sigma must be positive.") + + @property + def set(self): + return Interval(self.sigma, oo) + + def pdf(self, x): + alpha, beta, sigma = self.alpha, self.beta, self.sigma + return (exp(-alpha*log(x/sigma) - beta*log(x/sigma)**2) + *(alpha/x + 2*beta*log(x/sigma)/x)) + + def _moment_generating_function(self, t): + raise NotImplementedError('The moment generating function of the ' + 'Benini distribution does not exist.') + +def Benini(name, alpha, beta, sigma): + r""" + Create a Continuous Random Variable with a Benini distribution. + + The density of the Benini distribution is given by + + .. math:: + f(x) := e^{-\alpha\log{\frac{x}{\sigma}} + -\beta\log^2\left[{\frac{x}{\sigma}}\right]} + \left(\frac{\alpha}{x}+\frac{2\beta\log{\frac{x}{\sigma}}}{x}\right) + + This is a heavy-tailed distribution and is also known as the log-Rayleigh + distribution. + + Parameters + ========== + + alpha : Real number, `\alpha > 0`, a shape + beta : Real number, `\beta > 0`, a shape + sigma : Real number, `\sigma > 0`, a scale + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Benini, density, cdf + >>> from sympy import Symbol, pprint + + >>> alpha = Symbol("alpha", positive=True) + >>> beta = Symbol("beta", positive=True) + >>> sigma = Symbol("sigma", positive=True) + >>> z = Symbol("z") + + >>> X = Benini("x", alpha, beta, sigma) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + / / z \\ / z \ 2/ z \ + | 2*beta*log|-----|| - alpha*log|-----| - beta*log |-----| + |alpha \sigma/| \sigma/ \sigma/ + |----- + -----------------|*e + \ z z / + + >>> cdf(X)(z) + Piecewise((1 - exp(-alpha*log(z/sigma) - beta*log(z/sigma)**2), sigma <= z), + (0, True)) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Benini_distribution + .. [2] https://reference.wolfram.com/legacy/v8/ref/BeniniDistribution.html + + """ + + return rv(name, BeniniDistribution, (alpha, beta, sigma)) + +#------------------------------------------------------------------------------- +# Beta distribution ------------------------------------------------------------ + + +class BetaDistribution(SingleContinuousDistribution): + _argnames = ('alpha', 'beta') + + set = Interval(0, 1) + + @staticmethod + def check(alpha, beta): + _value_check(alpha > 0, "Shape parameter Alpha must be positive.") + _value_check(beta > 0, "Shape parameter Beta must be positive.") + + def pdf(self, x): + alpha, beta = self.alpha, self.beta + return x**(alpha - 1) * (1 - x)**(beta - 1) / beta_fn(alpha, beta) + + def _characteristic_function(self, t): + return hyper((self.alpha,), (self.alpha + self.beta,), I*t) + + def _moment_generating_function(self, t): + return hyper((self.alpha,), (self.alpha + self.beta,), t) + + +def Beta(name, alpha, beta): + r""" + Create a Continuous Random Variable with a Beta distribution. + + The density of the Beta distribution is given by + + .. math:: + f(x) := \frac{x^{\alpha-1}(1-x)^{\beta-1}} {\mathrm{B}(\alpha,\beta)} + + with :math:`x \in [0,1]`. + + Parameters + ========== + + alpha : Real number, `\alpha > 0`, a shape + beta : Real number, `\beta > 0`, a shape + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Beta, density, E, variance + >>> from sympy import Symbol, simplify, pprint, factor + + >>> alpha = Symbol("alpha", positive=True) + >>> beta = Symbol("beta", positive=True) + >>> z = Symbol("z") + + >>> X = Beta("x", alpha, beta) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + alpha - 1 beta - 1 + z *(1 - z) + -------------------------- + B(alpha, beta) + + >>> simplify(E(X)) + alpha/(alpha + beta) + + >>> factor(simplify(variance(X))) + alpha*beta/((alpha + beta)**2*(alpha + beta + 1)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Beta_distribution + .. [2] https://mathworld.wolfram.com/BetaDistribution.html + + """ + + return rv(name, BetaDistribution, (alpha, beta)) + +#------------------------------------------------------------------------------- +# Noncentral Beta distribution ------------------------------------------------------------ + + +class BetaNoncentralDistribution(SingleContinuousDistribution): + _argnames = ('alpha', 'beta', 'lamda') + + set = Interval(0, 1) + + @staticmethod + def check(alpha, beta, lamda): + _value_check(alpha > 0, "Shape parameter Alpha must be positive.") + _value_check(beta > 0, "Shape parameter Beta must be positive.") + _value_check(lamda >= 0, "Noncentrality parameter Lambda must be positive") + + def pdf(self, x): + alpha, beta, lamda = self.alpha, self.beta, self.lamda + k = Dummy("k") + return Sum(exp(-lamda / 2) * (lamda / 2)**k * x**(alpha + k - 1) *( + 1 - x)**(beta - 1) / (factorial(k) * beta_fn(alpha + k, beta)), (k, 0, oo)) + +def BetaNoncentral(name, alpha, beta, lamda): + r""" + Create a Continuous Random Variable with a Type I Noncentral Beta distribution. + + The density of the Noncentral Beta distribution is given by + + .. math:: + f(x) := \sum_{k=0}^\infty e^{-\lambda/2}\frac{(\lambda/2)^k}{k!} + \frac{x^{\alpha+k-1}(1-x)^{\beta-1}}{\mathrm{B}(\alpha+k,\beta)} + + with :math:`x \in [0,1]`. + + Parameters + ========== + + alpha : Real number, `\alpha > 0`, a shape + beta : Real number, `\beta > 0`, a shape + lamda : Real number, `\lambda \geq 0`, noncentrality parameter + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import BetaNoncentral, density, cdf + >>> from sympy import Symbol, pprint + + >>> alpha = Symbol("alpha", positive=True) + >>> beta = Symbol("beta", positive=True) + >>> lamda = Symbol("lamda", nonnegative=True) + >>> z = Symbol("z") + + >>> X = BetaNoncentral("x", alpha, beta, lamda) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + oo + _____ + \ ` + \ -lamda + \ k ------- + \ k + alpha - 1 /lamda\ beta - 1 2 + ) z *|-----| *(1 - z) *e + / \ 2 / + / ------------------------------------------------ + / B(k + alpha, beta)*k! + /____, + k = 0 + + Compute cdf with specific 'x', 'alpha', 'beta' and 'lamda' values as follows: + + >>> cdf(BetaNoncentral("x", 1, 1, 1), evaluate=False)(2).doit() + 2*exp(1/2) + + The argument evaluate=False prevents an attempt at evaluation + of the sum for general x, before the argument 2 is passed. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Noncentral_beta_distribution + .. [2] https://reference.wolfram.com/language/ref/NoncentralBetaDistribution.html + + """ + + return rv(name, BetaNoncentralDistribution, (alpha, beta, lamda)) + + +#------------------------------------------------------------------------------- +# Beta prime distribution ------------------------------------------------------ + + +class BetaPrimeDistribution(SingleContinuousDistribution): + _argnames = ('alpha', 'beta') + + @staticmethod + def check(alpha, beta): + _value_check(alpha > 0, "Shape parameter Alpha must be positive.") + _value_check(beta > 0, "Shape parameter Beta must be positive.") + + set = Interval(0, oo) + + def pdf(self, x): + alpha, beta = self.alpha, self.beta + return x**(alpha - 1)*(1 + x)**(-alpha - beta)/beta_fn(alpha, beta) + +def BetaPrime(name, alpha, beta): + r""" + Create a continuous random variable with a Beta prime distribution. + + The density of the Beta prime distribution is given by + + .. math:: + f(x) := \frac{x^{\alpha-1} (1+x)^{-\alpha -\beta}}{B(\alpha,\beta)} + + with :math:`x > 0`. + + Parameters + ========== + + alpha : Real number, `\alpha > 0`, a shape + beta : Real number, `\beta > 0`, a shape + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import BetaPrime, density + >>> from sympy import Symbol, pprint + + >>> alpha = Symbol("alpha", positive=True) + >>> beta = Symbol("beta", positive=True) + >>> z = Symbol("z") + + >>> X = BetaPrime("x", alpha, beta) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + alpha - 1 -alpha - beta + z *(z + 1) + ------------------------------- + B(alpha, beta) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Beta_prime_distribution + .. [2] https://mathworld.wolfram.com/BetaPrimeDistribution.html + + """ + + return rv(name, BetaPrimeDistribution, (alpha, beta)) + +#------------------------------------------------------------------------------- +# Bounded Pareto Distribution -------------------------------------------------- +class BoundedParetoDistribution(SingleContinuousDistribution): + _argnames = ('alpha', 'left', 'right') + + @property + def set(self): + return Interval(self.left, self.right) + + @staticmethod + def check(alpha, left, right): + _value_check (alpha.is_positive, "Shape must be positive.") + _value_check (left.is_positive, "Left value should be positive.") + _value_check (right > left, "Right should be greater than left.") + + def pdf(self, x): + alpha, left, right = self.alpha, self.left, self.right + num = alpha * (left**alpha) * x**(- alpha -1) + den = 1 - (left/right)**alpha + return num/den + +def BoundedPareto(name, alpha, left, right): + r""" + Create a continuous random variable with a Bounded Pareto distribution. + + The density of the Bounded Pareto distribution is given by + + .. math:: + f(x) := \frac{\alpha L^{\alpha}x^{-\alpha-1}}{1-(\frac{L}{H})^{\alpha}} + + Parameters + ========== + + alpha : Real Number, `\alpha > 0` + Shape parameter + left : Real Number, `left > 0` + Location parameter + right : Real Number, `right > left` + Location parameter + + Examples + ======== + + >>> from sympy.stats import BoundedPareto, density, cdf, E + >>> from sympy import symbols + >>> L, H = symbols('L, H', positive=True) + >>> X = BoundedPareto('X', 2, L, H) + >>> x = symbols('x') + >>> density(X)(x) + 2*L**2/(x**3*(1 - L**2/H**2)) + >>> cdf(X)(x) + Piecewise((-H**2*L**2/(x**2*(H**2 - L**2)) + H**2/(H**2 - L**2), L <= x), (0, True)) + >>> E(X).simplify() + 2*H*L/(H + L) + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Pareto_distribution#Bounded_Pareto_distribution + + """ + return rv (name, BoundedParetoDistribution, (alpha, left, right)) + +# ------------------------------------------------------------------------------ +# Cauchy distribution ---------------------------------------------------------- + + +class CauchyDistribution(SingleContinuousDistribution): + _argnames = ('x0', 'gamma') + + @staticmethod + def check(x0, gamma): + _value_check(gamma > 0, "Scale parameter Gamma must be positive.") + _value_check(x0.is_real, "Location parameter must be real.") + + def pdf(self, x): + return 1/(pi*self.gamma*(1 + ((x - self.x0)/self.gamma)**2)) + + def _cdf(self, x): + x0, gamma = self.x0, self.gamma + return (1/pi)*atan((x - x0)/gamma) + S.Half + + def _characteristic_function(self, t): + return exp(self.x0 * I * t - self.gamma * Abs(t)) + + def _moment_generating_function(self, t): + raise NotImplementedError("The moment generating function for the " + "Cauchy distribution does not exist.") + + def _quantile(self, p): + return self.x0 + self.gamma*tan(pi*(p - S.Half)) + + +def Cauchy(name, x0, gamma): + r""" + Create a continuous random variable with a Cauchy distribution. + + The density of the Cauchy distribution is given by + + .. math:: + f(x) := \frac{1}{\pi \gamma [1 + {(\frac{x-x_0}{\gamma})}^2]} + + Parameters + ========== + + x0 : Real number, the location + gamma : Real number, `\gamma > 0`, a scale + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Cauchy, density + >>> from sympy import Symbol + + >>> x0 = Symbol("x0") + >>> gamma = Symbol("gamma", positive=True) + >>> z = Symbol("z") + + >>> X = Cauchy("x", x0, gamma) + + >>> density(X)(z) + 1/(pi*gamma*(1 + (-x0 + z)**2/gamma**2)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Cauchy_distribution + .. [2] https://mathworld.wolfram.com/CauchyDistribution.html + + """ + + return rv(name, CauchyDistribution, (x0, gamma)) + +#------------------------------------------------------------------------------- +# Chi distribution ------------------------------------------------------------- + + +class ChiDistribution(SingleContinuousDistribution): + _argnames = ('k',) + + @staticmethod + def check(k): + _value_check(k > 0, "Number of degrees of freedom (k) must be positive.") + _value_check(k.is_integer, "Number of degrees of freedom (k) must be an integer.") + + set = Interval(0, oo) + + def pdf(self, x): + return 2**(1 - self.k/2)*x**(self.k - 1)*exp(-x**2/2)/gamma(self.k/2) + + def _characteristic_function(self, t): + k = self.k + + part_1 = hyper((k/2,), (S.Half,), -t**2/2) + part_2 = I*t*sqrt(2)*gamma((k+1)/2)/gamma(k/2) + part_3 = hyper(((k+1)/2,), (Rational(3, 2),), -t**2/2) + return part_1 + part_2*part_3 + + def _moment_generating_function(self, t): + k = self.k + + part_1 = hyper((k / 2,), (S.Half,), t ** 2 / 2) + part_2 = t * sqrt(2) * gamma((k + 1) / 2) / gamma(k / 2) + part_3 = hyper(((k + 1) / 2,), (S(3) / 2,), t ** 2 / 2) + return part_1 + part_2 * part_3 + +def Chi(name, k): + r""" + Create a continuous random variable with a Chi distribution. + + The density of the Chi distribution is given by + + .. math:: + f(x) := \frac{2^{1-k/2}x^{k-1}e^{-x^2/2}}{\Gamma(k/2)} + + with :math:`x \geq 0`. + + Parameters + ========== + + k : Positive integer, The number of degrees of freedom + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Chi, density, E + >>> from sympy import Symbol, simplify + + >>> k = Symbol("k", integer=True) + >>> z = Symbol("z") + + >>> X = Chi("x", k) + + >>> density(X)(z) + 2**(1 - k/2)*z**(k - 1)*exp(-z**2/2)/gamma(k/2) + + >>> simplify(E(X)) + sqrt(2)*gamma(k/2 + 1/2)/gamma(k/2) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Chi_distribution + .. [2] https://mathworld.wolfram.com/ChiDistribution.html + + """ + + return rv(name, ChiDistribution, (k,)) + +#------------------------------------------------------------------------------- +# Non-central Chi distribution ------------------------------------------------- + + +class ChiNoncentralDistribution(SingleContinuousDistribution): + _argnames = ('k', 'l') + + @staticmethod + def check(k, l): + _value_check(k > 0, "Number of degrees of freedom (k) must be positive.") + _value_check(k.is_integer, "Number of degrees of freedom (k) must be an integer.") + _value_check(l > 0, "Shift parameter Lambda must be positive.") + + set = Interval(0, oo) + + def pdf(self, x): + k, l = self.k, self.l + return exp(-(x**2+l**2)/2)*x**k*l / (l*x)**(k/2) * besseli(k/2-1, l*x) + +def ChiNoncentral(name, k, l): + r""" + Create a continuous random variable with a non-central Chi distribution. + + Explanation + =========== + + The density of the non-central Chi distribution is given by + + .. math:: + f(x) := \frac{e^{-(x^2+\lambda^2)/2} x^k\lambda} + {(\lambda x)^{k/2}} I_{k/2-1}(\lambda x) + + with `x \geq 0`. Here, `I_\nu (x)` is the + :ref:`modified Bessel function of the first kind `. + + Parameters + ========== + + k : A positive Integer, $k > 0$ + The number of degrees of freedom. + lambda : Real number, `\lambda > 0` + Shift parameter. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import ChiNoncentral, density + >>> from sympy import Symbol + + >>> k = Symbol("k", integer=True) + >>> l = Symbol("l") + >>> z = Symbol("z") + + >>> X = ChiNoncentral("x", k, l) + + >>> density(X)(z) + l*z**k*exp(-l**2/2 - z**2/2)*besseli(k/2 - 1, l*z)/(l*z)**(k/2) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Noncentral_chi_distribution + """ + + return rv(name, ChiNoncentralDistribution, (k, l)) + +#------------------------------------------------------------------------------- +# Chi squared distribution ----------------------------------------------------- + + +class ChiSquaredDistribution(SingleContinuousDistribution): + _argnames = ('k',) + + @staticmethod + def check(k): + _value_check(k > 0, "Number of degrees of freedom (k) must be positive.") + _value_check(k.is_integer, "Number of degrees of freedom (k) must be an integer.") + + set = Interval(0, oo) + + def pdf(self, x): + k = self.k + return 1/(2**(k/2)*gamma(k/2))*x**(k/2 - 1)*exp(-x/2) + + def _cdf(self, x): + k = self.k + return Piecewise( + (S.One/gamma(k/2)*lowergamma(k/2, x/2), x >= 0), + (0, True) + ) + + def _characteristic_function(self, t): + return (1 - 2*I*t)**(-self.k/2) + + def _moment_generating_function(self, t): + return (1 - 2*t)**(-self.k/2) + + +def ChiSquared(name, k): + r""" + Create a continuous random variable with a Chi-squared distribution. + + Explanation + =========== + + The density of the Chi-squared distribution is given by + + .. math:: + f(x) := \frac{1}{2^{\frac{k}{2}}\Gamma\left(\frac{k}{2}\right)} + x^{\frac{k}{2}-1} e^{-\frac{x}{2}} + + with :math:`x \geq 0`. + + Parameters + ========== + + k : Positive integer + The number of degrees of freedom. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import ChiSquared, density, E, variance, moment + >>> from sympy import Symbol + + >>> k = Symbol("k", integer=True, positive=True) + >>> z = Symbol("z") + + >>> X = ChiSquared("x", k) + + >>> density(X)(z) + z**(k/2 - 1)*exp(-z/2)/(2**(k/2)*gamma(k/2)) + + >>> E(X) + k + + >>> variance(X) + 2*k + + >>> moment(X, 3) + k**3 + 6*k**2 + 8*k + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Chi_squared_distribution + .. [2] https://mathworld.wolfram.com/Chi-SquaredDistribution.html + """ + + return rv(name, ChiSquaredDistribution, (k, )) + +#------------------------------------------------------------------------------- +# Dagum distribution ----------------------------------------------------------- + + +class DagumDistribution(SingleContinuousDistribution): + _argnames = ('p', 'a', 'b') + + set = Interval(0, oo) + + @staticmethod + def check(p, a, b): + _value_check(p > 0, "Shape parameter p must be positive.") + _value_check(a > 0, "Shape parameter a must be positive.") + _value_check(b > 0, "Scale parameter b must be positive.") + + def pdf(self, x): + p, a, b = self.p, self.a, self.b + return a*p/x*((x/b)**(a*p)/(((x/b)**a + 1)**(p + 1))) + + def _cdf(self, x): + p, a, b = self.p, self.a, self.b + return Piecewise(((S.One + (S(x)/b)**-a)**-p, x>=0), + (S.Zero, True)) + +def Dagum(name, p, a, b): + r""" + Create a continuous random variable with a Dagum distribution. + + Explanation + =========== + + The density of the Dagum distribution is given by + + .. math:: + f(x) := \frac{a p}{x} \left( \frac{\left(\tfrac{x}{b}\right)^{a p}} + {\left(\left(\tfrac{x}{b}\right)^a + 1 \right)^{p+1}} \right) + + with :math:`x > 0`. + + Parameters + ========== + + p : Real number + `p > 0`, a shape. + a : Real number + `a > 0`, a shape. + b : Real number + `b > 0`, a scale. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Dagum, density, cdf + >>> from sympy import Symbol + + >>> p = Symbol("p", positive=True) + >>> a = Symbol("a", positive=True) + >>> b = Symbol("b", positive=True) + >>> z = Symbol("z") + + >>> X = Dagum("x", p, a, b) + + >>> density(X)(z) + a*p*(z/b)**(a*p)*((z/b)**a + 1)**(-p - 1)/z + + >>> cdf(X)(z) + Piecewise(((1 + (z/b)**(-a))**(-p), z >= 0), (0, True)) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Dagum_distribution + + """ + + return rv(name, DagumDistribution, (p, a, b)) + +#------------------------------------------------------------------------------- +# Davis distribution ----------------------------------------------------------- + +class DavisDistribution(SingleContinuousDistribution): + _argnames = ('b', 'n', 'mu') + + set = Interval(0, oo) + + @staticmethod + def check(b, n, mu): + _value_check(b > 0, "Scale parameter b must be positive.") + _value_check(n > 1, "Shape parameter n must be above 1.") + _value_check(mu > 0, "Location parameter mu must be positive.") + + def pdf(self, x): + b, n, mu = self.b, self.n, self.mu + dividend = b**n*(x - mu)**(-1-n) + divisor = (exp(b/(x-mu))-1)*(gamma(n)*zeta(n)) + return dividend/divisor + + +def Davis(name, b, n, mu): + r""" Create a continuous random variable with Davis distribution. + + Explanation + =========== + + The density of Davis distribution is given by + + .. math:: + f(x; \mu; b, n) := \frac{b^{n}(x - \mu)^{1-n}}{ \left( e^{\frac{b}{x-\mu}} - 1 \right) \Gamma(n)\zeta(n)} + + with :math:`x \in [0,\infty]`. + + Davis distribution is a generalization of the Planck's law of radiation from statistical physics. It is used for modeling income distribution. + + Parameters + ========== + b : Real number + `p > 0`, a scale. + n : Real number + `n > 1`, a shape. + mu : Real number + `mu > 0`, a location. + + Returns + ======= + + RandomSymbol + + Examples + ======== + >>> from sympy.stats import Davis, density + >>> from sympy import Symbol + >>> b = Symbol("b", positive=True) + >>> n = Symbol("n", positive=True) + >>> mu = Symbol("mu", positive=True) + >>> z = Symbol("z") + >>> X = Davis("x", b, n, mu) + >>> density(X)(z) + b**n*(-mu + z)**(-n - 1)/((exp(b/(-mu + z)) - 1)*gamma(n)*zeta(n)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Davis_distribution + .. [2] https://reference.wolfram.com/language/ref/DavisDistribution.html + + """ + return rv(name, DavisDistribution, (b, n, mu)) + + +#------------------------------------------------------------------------------- +# Erlang distribution ---------------------------------------------------------- + + +def Erlang(name, k, l): + r""" + Create a continuous random variable with an Erlang distribution. + + Explanation + =========== + + The density of the Erlang distribution is given by + + .. math:: + f(x) := \frac{\lambda^k x^{k-1} e^{-\lambda x}}{(k-1)!} + + with :math:`x \in [0,\infty]`. + + Parameters + ========== + + k : Positive integer + l : Real number, `\lambda > 0`, the rate + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Erlang, density, cdf, E, variance + >>> from sympy import Symbol, simplify, pprint + + >>> k = Symbol("k", integer=True, positive=True) + >>> l = Symbol("l", positive=True) + >>> z = Symbol("z") + + >>> X = Erlang("x", k, l) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + k k - 1 -l*z + l *z *e + --------------- + Gamma(k) + + >>> C = cdf(X)(z) + >>> pprint(C, use_unicode=False) + /lowergamma(k, l*z) + |------------------ for z > 0 + < Gamma(k) + | + \ 0 otherwise + + + >>> E(X) + k/l + + >>> simplify(variance(X)) + k/l**2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Erlang_distribution + .. [2] https://mathworld.wolfram.com/ErlangDistribution.html + + """ + + return rv(name, GammaDistribution, (k, S.One/l)) + +# ------------------------------------------------------------------------------- +# ExGaussian distribution ----------------------------------------------------- + + +class ExGaussianDistribution(SingleContinuousDistribution): + _argnames = ('mean', 'std', 'rate') + + set = Interval(-oo, oo) + + @staticmethod + def check(mean, std, rate): + _value_check( + std > 0, "Standard deviation of ExGaussian must be positive.") + _value_check(rate > 0, "Rate of ExGaussian must be positive.") + + def pdf(self, x): + mean, std, rate = self.mean, self.std, self.rate + term1 = rate/2 + term2 = exp(rate * (2 * mean + rate * std**2 - 2*x)/2) + term3 = erfc((mean + rate*std**2 - x)/(sqrt(2)*std)) + return term1*term2*term3 + + def _cdf(self, x): + from sympy.stats import cdf + mean, std, rate = self.mean, self.std, self.rate + u = rate*(x - mean) + v = rate*std + GaussianCDF1 = cdf(Normal('x', 0, v))(u) + GaussianCDF2 = cdf(Normal('x', v**2, v))(u) + + return GaussianCDF1 - exp(-u + (v**2/2) + log(GaussianCDF2)) + + def _characteristic_function(self, t): + mean, std, rate = self.mean, self.std, self.rate + term1 = (1 - I*t/rate)**(-1) + term2 = exp(I*mean*t - std**2*t**2/2) + return term1 * term2 + + def _moment_generating_function(self, t): + mean, std, rate = self.mean, self.std, self.rate + term1 = (1 - t/rate)**(-1) + term2 = exp(mean*t + std**2*t**2/2) + return term1*term2 + + +def ExGaussian(name, mean, std, rate): + r""" + Create a continuous random variable with an Exponentially modified + Gaussian (EMG) distribution. + + Explanation + =========== + + The density of the exponentially modified Gaussian distribution is given by + + .. math:: + f(x) := \frac{\lambda}{2}e^{\frac{\lambda}{2}(2\mu+\lambda\sigma^2-2x)} + \text{erfc}(\frac{\mu + \lambda\sigma^2 - x}{\sqrt{2}\sigma}) + + with $x > 0$. Note that the expected value is `1/\lambda`. + + Parameters + ========== + + name : A string giving a name for this distribution + mean : A Real number, the mean of Gaussian component + std : A positive Real number, + :math: `\sigma^2 > 0` the variance of Gaussian component + rate : A positive Real number, + :math: `\lambda > 0` the rate of Exponential component + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import ExGaussian, density, cdf, E + >>> from sympy.stats import variance, skewness + >>> from sympy import Symbol, pprint, simplify + + >>> mean = Symbol("mu") + >>> std = Symbol("sigma", positive=True) + >>> rate = Symbol("lamda", positive=True) + >>> z = Symbol("z") + >>> X = ExGaussian("x", mean, std, rate) + + >>> pprint(density(X)(z), use_unicode=False) + / 2 \ + lamda*\lamda*sigma + 2*mu - 2*z/ + --------------------------------- / ___ / 2 \\ + 2 |\/ 2 *\lamda*sigma + mu - z/| + lamda*e *erfc|-----------------------------| + \ 2*sigma / + ---------------------------------------------------------------------------- + 2 + + >>> cdf(X)(z) + -(erf(sqrt(2)*(-lamda**2*sigma**2 + lamda*(-mu + z))/(2*lamda*sigma))/2 + 1/2)*exp(lamda**2*sigma**2/2 - lamda*(-mu + z)) + erf(sqrt(2)*(-mu + z)/(2*sigma))/2 + 1/2 + + >>> E(X) + (lamda*mu + 1)/lamda + + >>> simplify(variance(X)) + sigma**2 + lamda**(-2) + + >>> simplify(skewness(X)) + 2/(lamda**2*sigma**2 + 1)**(3/2) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Exponentially_modified_Gaussian_distribution + """ + return rv(name, ExGaussianDistribution, (mean, std, rate)) + +#------------------------------------------------------------------------------- +# Exponential distribution ----------------------------------------------------- + + +class ExponentialDistribution(SingleContinuousDistribution): + _argnames = ('rate',) + + set = Interval(0, oo) + + @staticmethod + def check(rate): + _value_check(rate > 0, "Rate must be positive.") + + def pdf(self, x): + return self.rate * exp(-self.rate*x) + + def _cdf(self, x): + return Piecewise( + (S.One - exp(-self.rate*x), x >= 0), + (0, True), + ) + + def _characteristic_function(self, t): + rate = self.rate + return rate / (rate - I*t) + + def _moment_generating_function(self, t): + rate = self.rate + return rate / (rate - t) + + def _quantile(self, p): + return -log(1-p)/self.rate + + +def Exponential(name, rate): + r""" + Create a continuous random variable with an Exponential distribution. + + Explanation + =========== + + The density of the exponential distribution is given by + + .. math:: + f(x) := \lambda \exp(-\lambda x) + + with $x > 0$. Note that the expected value is `1/\lambda`. + + Parameters + ========== + + rate : A positive Real number, `\lambda > 0`, the rate (or inverse scale/inverse mean) + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Exponential, density, cdf, E + >>> from sympy.stats import variance, std, skewness, quantile + >>> from sympy import Symbol + + >>> l = Symbol("lambda", positive=True) + >>> z = Symbol("z") + >>> p = Symbol("p") + >>> X = Exponential("x", l) + + >>> density(X)(z) + lambda*exp(-lambda*z) + + >>> cdf(X)(z) + Piecewise((1 - exp(-lambda*z), z >= 0), (0, True)) + + >>> quantile(X)(p) + -log(1 - p)/lambda + + >>> E(X) + 1/lambda + + >>> variance(X) + lambda**(-2) + + >>> skewness(X) + 2 + + >>> X = Exponential('x', 10) + + >>> density(X)(z) + 10*exp(-10*z) + + >>> E(X) + 1/10 + + >>> std(X) + 1/10 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Exponential_distribution + .. [2] https://mathworld.wolfram.com/ExponentialDistribution.html + + """ + + return rv(name, ExponentialDistribution, (rate, )) + + +# ------------------------------------------------------------------------------- +# Exponential Power distribution ----------------------------------------------------- + +class ExponentialPowerDistribution(SingleContinuousDistribution): + _argnames = ('mu', 'alpha', 'beta') + + set = Interval(-oo, oo) + + @staticmethod + def check(mu, alpha, beta): + _value_check(alpha > 0, "Scale parameter alpha must be positive.") + _value_check(beta > 0, "Shape parameter beta must be positive.") + + def pdf(self, x): + mu, alpha, beta = self.mu, self.alpha, self.beta + num = beta*exp(-(Abs(x - mu)/alpha)**beta) + den = 2*alpha*gamma(1/beta) + return num/den + + def _cdf(self, x): + mu, alpha, beta = self.mu, self.alpha, self.beta + num = lowergamma(1/beta, (Abs(x - mu) / alpha)**beta) + den = 2*gamma(1/beta) + return sign(x - mu)*num/den + S.Half + + +def ExponentialPower(name, mu, alpha, beta): + r""" + Create a Continuous Random Variable with Exponential Power distribution. + This distribution is known also as Generalized Normal + distribution version 1. + + Explanation + =========== + + The density of the Exponential Power distribution is given by + + .. math:: + f(x) := \frac{\beta}{2\alpha\Gamma(\frac{1}{\beta})} + e^{{-(\frac{|x - \mu|}{\alpha})^{\beta}}} + + with :math:`x \in [ - \infty, \infty ]`. + + Parameters + ========== + + mu : Real number + A location. + alpha : Real number,`\alpha > 0` + A scale. + beta : Real number, `\beta > 0` + A shape. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import ExponentialPower, density, cdf + >>> from sympy import Symbol, pprint + >>> z = Symbol("z") + >>> mu = Symbol("mu") + >>> alpha = Symbol("alpha", positive=True) + >>> beta = Symbol("beta", positive=True) + >>> X = ExponentialPower("x", mu, alpha, beta) + >>> pprint(density(X)(z), use_unicode=False) + beta + /|mu - z|\ + -|--------| + \ alpha / + beta*e + --------------------- + / 1 \ + 2*alpha*Gamma|----| + \beta/ + >>> cdf(X)(z) + 1/2 + lowergamma(1/beta, (Abs(mu - z)/alpha)**beta)*sign(-mu + z)/(2*gamma(1/beta)) + + References + ========== + + .. [1] https://reference.wolfram.com/language/ref/ExponentialPowerDistribution.html + .. [2] https://en.wikipedia.org/wiki/Generalized_normal_distribution#Version_1 + + """ + return rv(name, ExponentialPowerDistribution, (mu, alpha, beta)) + + +#------------------------------------------------------------------------------- +# F distribution --------------------------------------------------------------- + + +class FDistributionDistribution(SingleContinuousDistribution): + _argnames = ('d1', 'd2') + + set = Interval(0, oo) + + @staticmethod + def check(d1, d2): + _value_check((d1 > 0, d1.is_integer), + "Degrees of freedom d1 must be positive integer.") + _value_check((d2 > 0, d2.is_integer), + "Degrees of freedom d2 must be positive integer.") + + def pdf(self, x): + d1, d2 = self.d1, self.d2 + return (sqrt((d1*x)**d1*d2**d2 / (d1*x+d2)**(d1+d2)) + / (x * beta_fn(d1/2, d2/2))) + + def _moment_generating_function(self, t): + raise NotImplementedError('The moment generating function for the ' + 'F-distribution does not exist.') + +def FDistribution(name, d1, d2): + r""" + Create a continuous random variable with a F distribution. + + Explanation + =========== + + The density of the F distribution is given by + + .. math:: + f(x) := \frac{\sqrt{\frac{(d_1 x)^{d_1} d_2^{d_2}} + {(d_1 x + d_2)^{d_1 + d_2}}}} + {x \mathrm{B} \left(\frac{d_1}{2}, \frac{d_2}{2}\right)} + + with :math:`x > 0`. + + Parameters + ========== + + d1 : `d_1 > 0`, where `d_1` is the degrees of freedom (`n_1 - 1`) + d2 : `d_2 > 0`, where `d_2` is the degrees of freedom (`n_2 - 1`) + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import FDistribution, density + >>> from sympy import Symbol, pprint + + >>> d1 = Symbol("d1", positive=True) + >>> d2 = Symbol("d2", positive=True) + >>> z = Symbol("z") + + >>> X = FDistribution("x", d1, d2) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + d2 + -- ______________________________ + 2 / d1 -d1 - d2 + d2 *\/ (d1*z) *(d1*z + d2) + -------------------------------------- + /d1 d2\ + z*B|--, --| + \2 2 / + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/F-distribution + .. [2] https://mathworld.wolfram.com/F-Distribution.html + + """ + + return rv(name, FDistributionDistribution, (d1, d2)) + +#------------------------------------------------------------------------------- +# Fisher Z distribution -------------------------------------------------------- + +class FisherZDistribution(SingleContinuousDistribution): + _argnames = ('d1', 'd2') + + set = Interval(-oo, oo) + + @staticmethod + def check(d1, d2): + _value_check(d1 > 0, "Degree of freedom d1 must be positive.") + _value_check(d2 > 0, "Degree of freedom d2 must be positive.") + + def pdf(self, x): + d1, d2 = self.d1, self.d2 + return (2*d1**(d1/2)*d2**(d2/2) / beta_fn(d1/2, d2/2) * + exp(d1*x) / (d1*exp(2*x)+d2)**((d1+d2)/2)) + +def FisherZ(name, d1, d2): + r""" + Create a Continuous Random Variable with an Fisher's Z distribution. + + Explanation + =========== + + The density of the Fisher's Z distribution is given by + + .. math:: + f(x) := \frac{2d_1^{d_1/2} d_2^{d_2/2}} {\mathrm{B}(d_1/2, d_2/2)} + \frac{e^{d_1z}}{\left(d_1e^{2z}+d_2\right)^{\left(d_1+d_2\right)/2}} + + + .. TODO - What is the difference between these degrees of freedom? + + Parameters + ========== + + d1 : `d_1 > 0` + Degree of freedom. + d2 : `d_2 > 0` + Degree of freedom. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import FisherZ, density + >>> from sympy import Symbol, pprint + + >>> d1 = Symbol("d1", positive=True) + >>> d2 = Symbol("d2", positive=True) + >>> z = Symbol("z") + + >>> X = FisherZ("x", d1, d2) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + d1 d2 + d1 d2 - -- - -- + -- -- 2 2 + 2 2 / 2*z \ d1*z + 2*d1 *d2 *\d1*e + d2/ *e + ----------------------------------------- + /d1 d2\ + B|--, --| + \2 2 / + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Fisher%27s_z-distribution + .. [2] https://mathworld.wolfram.com/Fishersz-Distribution.html + + """ + + return rv(name, FisherZDistribution, (d1, d2)) + +#------------------------------------------------------------------------------- +# Frechet distribution --------------------------------------------------------- + +class FrechetDistribution(SingleContinuousDistribution): + _argnames = ('a', 's', 'm') + + set = Interval(0, oo) + + @staticmethod + def check(a, s, m): + _value_check(a > 0, "Shape parameter alpha must be positive.") + _value_check(s > 0, "Scale parameter s must be positive.") + + def __new__(cls, a, s=1, m=0): + a, s, m = list(map(sympify, (a, s, m))) + return Basic.__new__(cls, a, s, m) + + def pdf(self, x): + a, s, m = self.a, self.s, self.m + return a/s * ((x-m)/s)**(-1-a) * exp(-((x-m)/s)**(-a)) + + def _cdf(self, x): + a, s, m = self.a, self.s, self.m + return Piecewise((exp(-((x-m)/s)**(-a)), x >= m), + (S.Zero, True)) + +def Frechet(name, a, s=1, m=0): + r""" + Create a continuous random variable with a Frechet distribution. + + Explanation + =========== + + The density of the Frechet distribution is given by + + .. math:: + f(x) := \frac{\alpha}{s} \left(\frac{x-m}{s}\right)^{-1-\alpha} + e^{-(\frac{x-m}{s})^{-\alpha}} + + with :math:`x \geq m`. + + Parameters + ========== + + a : Real number, :math:`a \in \left(0, \infty\right)` the shape + s : Real number, :math:`s \in \left(0, \infty\right)` the scale + m : Real number, :math:`m \in \left(-\infty, \infty\right)` the minimum + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Frechet, density, cdf + >>> from sympy import Symbol + + >>> a = Symbol("a", positive=True) + >>> s = Symbol("s", positive=True) + >>> m = Symbol("m", real=True) + >>> z = Symbol("z") + + >>> X = Frechet("x", a, s, m) + + >>> density(X)(z) + a*((-m + z)/s)**(-a - 1)*exp(-1/((-m + z)/s)**a)/s + + >>> cdf(X)(z) + Piecewise((exp(-1/((-m + z)/s)**a), m <= z), (0, True)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Fr%C3%A9chet_distribution + + """ + + return rv(name, FrechetDistribution, (a, s, m)) + +#------------------------------------------------------------------------------- +# Gamma distribution ----------------------------------------------------------- + + +class GammaDistribution(SingleContinuousDistribution): + _argnames = ('k', 'theta') + + set = Interval(0, oo) + + @staticmethod + def check(k, theta): + _value_check(k > 0, "k must be positive") + _value_check(theta > 0, "Theta must be positive") + + def pdf(self, x): + k, theta = self.k, self.theta + return x**(k - 1) * exp(-x/theta) / (gamma(k)*theta**k) + + def _cdf(self, x): + k, theta = self.k, self.theta + return Piecewise( + (lowergamma(k, S(x)/theta)/gamma(k), x > 0), + (S.Zero, True)) + + def _characteristic_function(self, t): + return (1 - self.theta*I*t)**(-self.k) + + def _moment_generating_function(self, t): + return (1- self.theta*t)**(-self.k) + + +def Gamma(name, k, theta): + r""" + Create a continuous random variable with a Gamma distribution. + + Explanation + =========== + + The density of the Gamma distribution is given by + + .. math:: + f(x) := \frac{1}{\Gamma(k) \theta^k} x^{k - 1} e^{-\frac{x}{\theta}} + + with :math:`x \in [0,1]`. + + Parameters + ========== + + k : Real number, `k > 0`, a shape + theta : Real number, `\theta > 0`, a scale + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Gamma, density, cdf, E, variance + >>> from sympy import Symbol, pprint, simplify + + >>> k = Symbol("k", positive=True) + >>> theta = Symbol("theta", positive=True) + >>> z = Symbol("z") + + >>> X = Gamma("x", k, theta) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + -z + ----- + -k k - 1 theta + theta *z *e + --------------------- + Gamma(k) + + >>> C = cdf(X, meijerg=True)(z) + >>> pprint(C, use_unicode=False) + / / z \ + |k*lowergamma|k, -----| + | \ theta/ + <---------------------- for z >= 0 + | Gamma(k + 1) + | + \ 0 otherwise + + >>> E(X) + k*theta + + >>> V = simplify(variance(X)) + >>> pprint(V, use_unicode=False) + 2 + k*theta + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gamma_distribution + .. [2] https://mathworld.wolfram.com/GammaDistribution.html + + """ + + return rv(name, GammaDistribution, (k, theta)) + +#------------------------------------------------------------------------------- +# Inverse Gamma distribution --------------------------------------------------- + + +class GammaInverseDistribution(SingleContinuousDistribution): + _argnames = ('a', 'b') + + set = Interval(0, oo) + + @staticmethod + def check(a, b): + _value_check(a > 0, "alpha must be positive") + _value_check(b > 0, "beta must be positive") + + def pdf(self, x): + a, b = self.a, self.b + return b**a/gamma(a) * x**(-a-1) * exp(-b/x) + + def _cdf(self, x): + a, b = self.a, self.b + return Piecewise((uppergamma(a,b/x)/gamma(a), x > 0), + (S.Zero, True)) + + def _characteristic_function(self, t): + a, b = self.a, self.b + return 2 * (-I*b*t)**(a/2) * besselk(a, sqrt(-4*I*b*t)) / gamma(a) + + def _moment_generating_function(self, t): + raise NotImplementedError('The moment generating function for the ' + 'gamma inverse distribution does not exist.') + +def GammaInverse(name, a, b): + r""" + Create a continuous random variable with an inverse Gamma distribution. + + Explanation + =========== + + The density of the inverse Gamma distribution is given by + + .. math:: + f(x) := \frac{\beta^\alpha}{\Gamma(\alpha)} x^{-\alpha - 1} + \exp\left(\frac{-\beta}{x}\right) + + with :math:`x > 0`. + + Parameters + ========== + + a : Real number, `a > 0`, a shape + b : Real number, `b > 0`, a scale + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import GammaInverse, density, cdf + >>> from sympy import Symbol, pprint + + >>> a = Symbol("a", positive=True) + >>> b = Symbol("b", positive=True) + >>> z = Symbol("z") + + >>> X = GammaInverse("x", a, b) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + -b + --- + a -a - 1 z + b *z *e + --------------- + Gamma(a) + + >>> cdf(X)(z) + Piecewise((uppergamma(a, b/z)/gamma(a), z > 0), (0, True)) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Inverse-gamma_distribution + + """ + + return rv(name, GammaInverseDistribution, (a, b)) + + +#------------------------------------------------------------------------------- +# Gumbel distribution (Maximum and Minimum) -------------------------------------------------------- + + +class GumbelDistribution(SingleContinuousDistribution): + _argnames = ('beta', 'mu', 'minimum') + + set = Interval(-oo, oo) + + @staticmethod + def check(beta, mu, minimum): + _value_check(beta > 0, "Scale parameter beta must be positive.") + + def pdf(self, x): + beta, mu = self.beta, self.mu + z = (x - mu)/beta + f_max = (1/beta)*exp(-z - exp(-z)) + f_min = (1/beta)*exp(z - exp(z)) + return Piecewise((f_min, self.minimum), (f_max, not self.minimum)) + + def _cdf(self, x): + beta, mu = self.beta, self.mu + z = (x - mu)/beta + F_max = exp(-exp(-z)) + F_min = 1 - exp(-exp(z)) + return Piecewise((F_min, self.minimum), (F_max, not self.minimum)) + + def _characteristic_function(self, t): + cf_max = gamma(1 - I*self.beta*t) * exp(I*self.mu*t) + cf_min = gamma(1 + I*self.beta*t) * exp(I*self.mu*t) + return Piecewise((cf_min, self.minimum), (cf_max, not self.minimum)) + + def _moment_generating_function(self, t): + mgf_max = gamma(1 - self.beta*t) * exp(self.mu*t) + mgf_min = gamma(1 + self.beta*t) * exp(self.mu*t) + return Piecewise((mgf_min, self.minimum), (mgf_max, not self.minimum)) + +def Gumbel(name, beta, mu, minimum=False): + r""" + Create a Continuous Random Variable with Gumbel distribution. + + Explanation + =========== + + The density of the Gumbel distribution is given by + + For Maximum + + .. math:: + f(x) := \dfrac{1}{\beta} \exp \left( -\dfrac{x-\mu}{\beta} + - \exp \left( -\dfrac{x - \mu}{\beta} \right) \right) + + with :math:`x \in [ - \infty, \infty ]`. + + For Minimum + + .. math:: + f(x) := \frac{e^{- e^{\frac{- \mu + x}{\beta}} + \frac{- \mu + x}{\beta}}}{\beta} + + with :math:`x \in [ - \infty, \infty ]`. + + Parameters + ========== + + mu : Real number, `\mu`, a location + beta : Real number, `\beta > 0`, a scale + minimum : Boolean, by default ``False``, set to ``True`` for enabling minimum distribution + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Gumbel, density, cdf + >>> from sympy import Symbol + >>> x = Symbol("x") + >>> mu = Symbol("mu") + >>> beta = Symbol("beta", positive=True) + >>> X = Gumbel("x", beta, mu) + >>> density(X)(x) + exp(-exp(-(-mu + x)/beta) - (-mu + x)/beta)/beta + >>> cdf(X)(x) + exp(-exp(-(-mu + x)/beta)) + + References + ========== + + .. [1] https://mathworld.wolfram.com/GumbelDistribution.html + .. [2] https://en.wikipedia.org/wiki/Gumbel_distribution + .. [3] https://web.archive.org/web/20200628222206/http://www.mathwave.com/help/easyfit/html/analyses/distributions/gumbel_max.html + .. [4] https://web.archive.org/web/20200628222212/http://www.mathwave.com/help/easyfit/html/analyses/distributions/gumbel_min.html + + """ + return rv(name, GumbelDistribution, (beta, mu, minimum)) + +#------------------------------------------------------------------------------- +# Gompertz distribution -------------------------------------------------------- + +class GompertzDistribution(SingleContinuousDistribution): + _argnames = ('b', 'eta') + + set = Interval(0, oo) + + @staticmethod + def check(b, eta): + _value_check(b > 0, "b must be positive") + _value_check(eta > 0, "eta must be positive") + + def pdf(self, x): + eta, b = self.eta, self.b + return b*eta*exp(b*x)*exp(eta)*exp(-eta*exp(b*x)) + + def _cdf(self, x): + eta, b = self.eta, self.b + return 1 - exp(eta)*exp(-eta*exp(b*x)) + + def _moment_generating_function(self, t): + eta, b = self.eta, self.b + return eta * exp(eta) * expint(t/b, eta) + +def Gompertz(name, b, eta): + r""" + Create a Continuous Random Variable with Gompertz distribution. + + Explanation + =========== + + The density of the Gompertz distribution is given by + + .. math:: + f(x) := b \eta e^{b x} e^{\eta} \exp \left(-\eta e^{bx} \right) + + with :math:`x \in [0, \infty)`. + + Parameters + ========== + + b : Real number, `b > 0`, a scale + eta : Real number, `\eta > 0`, a shape + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Gompertz, density + >>> from sympy import Symbol + + >>> b = Symbol("b", positive=True) + >>> eta = Symbol("eta", positive=True) + >>> z = Symbol("z") + + >>> X = Gompertz("x", b, eta) + + >>> density(X)(z) + b*eta*exp(eta)*exp(b*z)*exp(-eta*exp(b*z)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gompertz_distribution + + """ + return rv(name, GompertzDistribution, (b, eta)) + +#------------------------------------------------------------------------------- +# Kumaraswamy distribution ----------------------------------------------------- + + +class KumaraswamyDistribution(SingleContinuousDistribution): + _argnames = ('a', 'b') + + set = Interval(0, oo) + + @staticmethod + def check(a, b): + _value_check(a > 0, "a must be positive") + _value_check(b > 0, "b must be positive") + + def pdf(self, x): + a, b = self.a, self.b + return a * b * x**(a-1) * (1-x**a)**(b-1) + + def _cdf(self, x): + a, b = self.a, self.b + return Piecewise( + (S.Zero, x < S.Zero), + (1 - (1 - x**a)**b, x <= S.One), + (S.One, True)) + +def Kumaraswamy(name, a, b): + r""" + Create a Continuous Random Variable with a Kumaraswamy distribution. + + Explanation + =========== + + The density of the Kumaraswamy distribution is given by + + .. math:: + f(x) := a b x^{a-1} (1-x^a)^{b-1} + + with :math:`x \in [0,1]`. + + Parameters + ========== + + a : Real number, `a > 0`, a shape + b : Real number, `b > 0`, a shape + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Kumaraswamy, density, cdf + >>> from sympy import Symbol, pprint + + >>> a = Symbol("a", positive=True) + >>> b = Symbol("b", positive=True) + >>> z = Symbol("z") + + >>> X = Kumaraswamy("x", a, b) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + b - 1 + a - 1 / a\ + a*b*z *\1 - z / + + >>> cdf(X)(z) + Piecewise((0, z < 0), (1 - (1 - z**a)**b, z <= 1), (1, True)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Kumaraswamy_distribution + + """ + + return rv(name, KumaraswamyDistribution, (a, b)) + +#------------------------------------------------------------------------------- +# Laplace distribution --------------------------------------------------------- + + +class LaplaceDistribution(SingleContinuousDistribution): + _argnames = ('mu', 'b') + + set = Interval(-oo, oo) + + @staticmethod + def check(mu, b): + _value_check(b > 0, "Scale parameter b must be positive.") + _value_check(mu.is_real, "Location parameter mu should be real") + + def pdf(self, x): + mu, b = self.mu, self.b + return 1/(2*b)*exp(-Abs(x - mu)/b) + + def _cdf(self, x): + mu, b = self.mu, self.b + return Piecewise( + (S.Half*exp((x - mu)/b), x < mu), + (S.One - S.Half*exp(-(x - mu)/b), x >= mu) + ) + + def _characteristic_function(self, t): + return exp(self.mu*I*t) / (1 + self.b**2*t**2) + + def _moment_generating_function(self, t): + return exp(self.mu*t) / (1 - self.b**2*t**2) + +def Laplace(name, mu, b): + r""" + Create a continuous random variable with a Laplace distribution. + + Explanation + =========== + + The density of the Laplace distribution is given by + + .. math:: + f(x) := \frac{1}{2 b} \exp \left(-\frac{|x-\mu|}b \right) + + Parameters + ========== + + mu : Real number or a list/matrix, the location (mean) or the + location vector + b : Real number or a positive definite matrix, representing a scale + or the covariance matrix. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Laplace, density, cdf + >>> from sympy import Symbol, pprint + + >>> mu = Symbol("mu") + >>> b = Symbol("b", positive=True) + >>> z = Symbol("z") + + >>> X = Laplace("x", mu, b) + + >>> density(X)(z) + exp(-Abs(mu - z)/b)/(2*b) + + >>> cdf(X)(z) + Piecewise((exp((-mu + z)/b)/2, mu > z), (1 - exp((mu - z)/b)/2, True)) + + >>> L = Laplace('L', [1, 2], [[1, 0], [0, 1]]) + >>> pprint(density(L)(1, 2), use_unicode=False) + 5 / ____\ + e *besselk\0, \/ 35 / + --------------------- + pi + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Laplace_distribution + .. [2] https://mathworld.wolfram.com/LaplaceDistribution.html + + """ + + if isinstance(mu, (list, MatrixBase)) and\ + isinstance(b, (list, MatrixBase)): + from sympy.stats.joint_rv_types import MultivariateLaplace + return MultivariateLaplace(name, mu, b) + + return rv(name, LaplaceDistribution, (mu, b)) + +#------------------------------------------------------------------------------- +# Levy distribution --------------------------------------------------------- + + +class LevyDistribution(SingleContinuousDistribution): + _argnames = ('mu', 'c') + + @property + def set(self): + return Interval(self.mu, oo) + + @staticmethod + def check(mu, c): + _value_check(c > 0, "c (scale parameter) must be positive") + _value_check(mu.is_real, "mu (location parameter) must be real") + + def pdf(self, x): + mu, c = self.mu, self.c + return sqrt(c/(2*pi))*exp(-c/(2*(x - mu)))/((x - mu)**(S.One + S.Half)) + + def _cdf(self, x): + mu, c = self.mu, self.c + return erfc(sqrt(c/(2*(x - mu)))) + + def _characteristic_function(self, t): + mu, c = self.mu, self.c + return exp(I * mu * t - sqrt(-2 * I * c * t)) + + def _moment_generating_function(self, t): + raise NotImplementedError('The moment generating function of Levy distribution does not exist.') + +def Levy(name, mu, c): + r""" + Create a continuous random variable with a Levy distribution. + + The density of the Levy distribution is given by + + .. math:: + f(x) := \sqrt(\frac{c}{2 \pi}) \frac{\exp -\frac{c}{2 (x - \mu)}}{(x - \mu)^{3/2}} + + Parameters + ========== + + mu : Real number + The location parameter. + c : Real number, `c > 0` + A scale parameter. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Levy, density, cdf + >>> from sympy import Symbol + + >>> mu = Symbol("mu", real=True) + >>> c = Symbol("c", positive=True) + >>> z = Symbol("z") + + >>> X = Levy("x", mu, c) + + >>> density(X)(z) + sqrt(2)*sqrt(c)*exp(-c/(-2*mu + 2*z))/(2*sqrt(pi)*(-mu + z)**(3/2)) + + >>> cdf(X)(z) + erfc(sqrt(c)*sqrt(1/(-2*mu + 2*z))) + + References + ========== + .. [1] https://en.wikipedia.org/wiki/L%C3%A9vy_distribution + .. [2] https://mathworld.wolfram.com/LevyDistribution.html + """ + + return rv(name, LevyDistribution, (mu, c)) + +#------------------------------------------------------------------------------- +# Log-Cauchy distribution -------------------------------------------------------- + + +class LogCauchyDistribution(SingleContinuousDistribution): + _argnames = ('mu', 'sigma') + + set = Interval.open(0, oo) + + @staticmethod + def check(mu, sigma): + _value_check((sigma > 0) != False, "Scale parameter Gamma must be positive.") + _value_check(mu.is_real != False, "Location parameter must be real.") + + def pdf(self, x): + mu, sigma = self.mu, self.sigma + return 1/(x*pi)*(sigma/((log(x) - mu)**2 + sigma**2)) + + def _cdf(self, x): + mu, sigma = self.mu, self.sigma + return (1/pi)*atan((log(x) - mu)/sigma) + S.Half + + def _characteristic_function(self, t): + raise NotImplementedError("The characteristic function for the " + "Log-Cauchy distribution does not exist.") + + def _moment_generating_function(self, t): + raise NotImplementedError("The moment generating function for the " + "Log-Cauchy distribution does not exist.") + +def LogCauchy(name, mu, sigma): + r""" + Create a continuous random variable with a Log-Cauchy distribution. + The density of the Log-Cauchy distribution is given by + + .. math:: + f(x) := \frac{1}{\pi x} \frac{\sigma}{(log(x)-\mu^2) + \sigma^2} + + Parameters + ========== + + mu : Real number, the location + + sigma : Real number, `\sigma > 0`, a scale + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import LogCauchy, density, cdf + >>> from sympy import Symbol, S + + >>> mu = 2 + >>> sigma = S.One / 5 + >>> z = Symbol("z") + + >>> X = LogCauchy("x", mu, sigma) + + >>> density(X)(z) + 1/(5*pi*z*((log(z) - 2)**2 + 1/25)) + + >>> cdf(X)(z) + atan(5*log(z) - 10)/pi + 1/2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Log-Cauchy_distribution + """ + + return rv(name, LogCauchyDistribution, (mu, sigma)) + + +#------------------------------------------------------------------------------- +# Logistic distribution -------------------------------------------------------- + + +class LogisticDistribution(SingleContinuousDistribution): + _argnames = ('mu', 's') + + set = Interval(-oo, oo) + + @staticmethod + def check(mu, s): + _value_check(s > 0, "Scale parameter s must be positive.") + + def pdf(self, x): + mu, s = self.mu, self.s + return exp(-(x - mu)/s)/(s*(1 + exp(-(x - mu)/s))**2) + + def _cdf(self, x): + mu, s = self.mu, self.s + return S.One/(1 + exp(-(x - mu)/s)) + + def _characteristic_function(self, t): + return Piecewise((exp(I*t*self.mu) * pi*self.s*t / sinh(pi*self.s*t), Ne(t, 0)), (S.One, True)) + + def _moment_generating_function(self, t): + return exp(self.mu*t) * beta_fn(1 - self.s*t, 1 + self.s*t) + + def _quantile(self, p): + return self.mu - self.s*log(-S.One + S.One/p) + +def Logistic(name, mu, s): + r""" + Create a continuous random variable with a logistic distribution. + + Explanation + =========== + + The density of the logistic distribution is given by + + .. math:: + f(x) := \frac{e^{-(x-\mu)/s}} {s\left(1+e^{-(x-\mu)/s}\right)^2} + + Parameters + ========== + + mu : Real number, the location (mean) + s : Real number, `s > 0`, a scale + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Logistic, density, cdf + >>> from sympy import Symbol + + >>> mu = Symbol("mu", real=True) + >>> s = Symbol("s", positive=True) + >>> z = Symbol("z") + + >>> X = Logistic("x", mu, s) + + >>> density(X)(z) + exp((mu - z)/s)/(s*(exp((mu - z)/s) + 1)**2) + + >>> cdf(X)(z) + 1/(exp((mu - z)/s) + 1) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Logistic_distribution + .. [2] https://mathworld.wolfram.com/LogisticDistribution.html + + """ + + return rv(name, LogisticDistribution, (mu, s)) + +#------------------------------------------------------------------------------- +# Log-logistic distribution -------------------------------------------------------- + + +class LogLogisticDistribution(SingleContinuousDistribution): + _argnames = ('alpha', 'beta') + + set = Interval(0, oo) + + @staticmethod + def check(alpha, beta): + _value_check(alpha > 0, "Scale parameter Alpha must be positive.") + _value_check(beta > 0, "Shape parameter Beta must be positive.") + + def pdf(self, x): + a, b = self.alpha, self.beta + return ((b/a)*(x/a)**(b - 1))/(1 + (x/a)**b)**2 + + def _cdf(self, x): + a, b = self.alpha, self.beta + return 1/(1 + (x/a)**(-b)) + + def _quantile(self, p): + a, b = self.alpha, self.beta + return a*((p/(1 - p))**(1/b)) + + def expectation(self, expr, var, **kwargs): + a, b = self.args + return Piecewise((S.NaN, b <= 1), (pi*a/(b*sin(pi/b)), True)) + +def LogLogistic(name, alpha, beta): + r""" + Create a continuous random variable with a log-logistic distribution. + The distribution is unimodal when ``beta > 1``. + + Explanation + =========== + + The density of the log-logistic distribution is given by + + .. math:: + f(x) := \frac{(\frac{\beta}{\alpha})(\frac{x}{\alpha})^{\beta - 1}} + {(1 + (\frac{x}{\alpha})^{\beta})^2} + + Parameters + ========== + + alpha : Real number, `\alpha > 0`, scale parameter and median of distribution + beta : Real number, `\beta > 0`, a shape parameter + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import LogLogistic, density, cdf, quantile + >>> from sympy import Symbol, pprint + + >>> alpha = Symbol("alpha", positive=True) + >>> beta = Symbol("beta", positive=True) + >>> p = Symbol("p") + >>> z = Symbol("z", positive=True) + + >>> X = LogLogistic("x", alpha, beta) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + beta - 1 + / z \ + beta*|-----| + \alpha/ + ------------------------ + 2 + / beta \ + |/ z \ | + alpha*||-----| + 1| + \\alpha/ / + + >>> cdf(X)(z) + 1/(1 + (z/alpha)**(-beta)) + + >>> quantile(X)(p) + alpha*(p/(1 - p))**(1/beta) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Log-logistic_distribution + + """ + + return rv(name, LogLogisticDistribution, (alpha, beta)) + +#------------------------------------------------------------------------------- +#Logit-Normal distribution------------------------------------------------------ + +class LogitNormalDistribution(SingleContinuousDistribution): + _argnames = ('mu', 's') + set = Interval.open(0, 1) + + @staticmethod + def check(mu, s): + _value_check((s ** 2).is_real is not False and s ** 2 > 0, "Squared scale parameter s must be positive.") + _value_check(mu.is_real is not False, "Location parameter must be real") + + def _logit(self, x): + return log(x / (1 - x)) + + def pdf(self, x): + mu, s = self.mu, self.s + return exp(-(self._logit(x) - mu)**2/(2*s**2))*(S.One/sqrt(2*pi*(s**2)))*(1/(x*(1 - x))) + + def _cdf(self, x): + mu, s = self.mu, self.s + return (S.One/2)*(1 + erf((self._logit(x) - mu)/(sqrt(2*s**2)))) + + +def LogitNormal(name, mu, s): + r""" + Create a continuous random variable with a Logit-Normal distribution. + + The density of the logistic distribution is given by + + .. math:: + f(x) := \frac{1}{s \sqrt{2 \pi}} \frac{1}{x(1 - x)} e^{- \frac{(logit(x) - \mu)^2}{s^2}} + where logit(x) = \log(\frac{x}{1 - x}) + Parameters + ========== + + mu : Real number, the location (mean) + s : Real number, `s > 0`, a scale + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import LogitNormal, density, cdf + >>> from sympy import Symbol,pprint + + >>> mu = Symbol("mu", real=True) + >>> s = Symbol("s", positive=True) + >>> z = Symbol("z") + >>> X = LogitNormal("x",mu,s) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + 2 + / / z \\ + -|-mu + log|-----|| + \ \1 - z// + --------------------- + 2 + ___ 2*s + \/ 2 *e + ---------------------------- + ____ + 2*\/ pi *s*z*(1 - z) + + >>> density(X)(z) + sqrt(2)*exp(-(-mu + log(z/(1 - z)))**2/(2*s**2))/(2*sqrt(pi)*s*z*(1 - z)) + + >>> cdf(X)(z) + erf(sqrt(2)*(-mu + log(z/(1 - z)))/(2*s))/2 + 1/2 + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Logit-normal_distribution + + """ + + return rv(name, LogitNormalDistribution, (mu, s)) + +#------------------------------------------------------------------------------- +# Log Normal distribution ------------------------------------------------------ + + +class LogNormalDistribution(SingleContinuousDistribution): + _argnames = ('mean', 'std') + + set = Interval(0, oo) + + @staticmethod + def check(mean, std): + _value_check(std > 0, "Parameter std must be positive.") + + def pdf(self, x): + mean, std = self.mean, self.std + return exp(-(log(x) - mean)**2 / (2*std**2)) / (x*sqrt(2*pi)*std) + + def _cdf(self, x): + mean, std = self.mean, self.std + return Piecewise( + (S.Half + S.Half*erf((log(x) - mean)/sqrt(2)/std), x > 0), + (S.Zero, True) + ) + + def _moment_generating_function(self, t): + raise NotImplementedError('Moment generating function of the log-normal distribution is not defined.') + + +def LogNormal(name, mean, std): + r""" + Create a continuous random variable with a log-normal distribution. + + Explanation + =========== + + The density of the log-normal distribution is given by + + .. math:: + f(x) := \frac{1}{x\sqrt{2\pi\sigma^2}} + e^{-\frac{\left(\ln x-\mu\right)^2}{2\sigma^2}} + + with :math:`x \geq 0`. + + Parameters + ========== + + mu : Real number + The log-scale. + sigma : Real number + A shape. ($\sigma^2 > 0$) + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import LogNormal, density + >>> from sympy import Symbol, pprint + + >>> mu = Symbol("mu", real=True) + >>> sigma = Symbol("sigma", positive=True) + >>> z = Symbol("z") + + >>> X = LogNormal("x", mu, sigma) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + 2 + -(-mu + log(z)) + ----------------- + 2 + ___ 2*sigma + \/ 2 *e + ------------------------ + ____ + 2*\/ pi *sigma*z + + + >>> X = LogNormal('x', 0, 1) # Mean 0, standard deviation 1 + + >>> density(X)(z) + sqrt(2)*exp(-log(z)**2/2)/(2*sqrt(pi)*z) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Lognormal + .. [2] https://mathworld.wolfram.com/LogNormalDistribution.html + + """ + + return rv(name, LogNormalDistribution, (mean, std)) + +#------------------------------------------------------------------------------- +# Lomax Distribution ----------------------------------------------------------- + +class LomaxDistribution(SingleContinuousDistribution): + _argnames = ('alpha', 'lamda',) + set = Interval(0, oo) + + @staticmethod + def check(alpha, lamda): + _value_check(alpha.is_real, "Shape parameter should be real.") + _value_check(lamda.is_real, "Scale parameter should be real.") + _value_check(alpha.is_positive, "Shape parameter should be positive.") + _value_check(lamda.is_positive, "Scale parameter should be positive.") + + def pdf(self, x): + lamba, alpha = self.lamda, self.alpha + return (alpha/lamba) * (S.One + x/lamba)**(-alpha-1) + +def Lomax(name, alpha, lamda): + r""" + Create a continuous random variable with a Lomax distribution. + + Explanation + =========== + + The density of the Lomax distribution is given by + + .. math:: + f(x) := \frac{\alpha}{\lambda}\left[1+\frac{x}{\lambda}\right]^{-(\alpha+1)} + + Parameters + ========== + + alpha : Real Number, `\alpha > 0` + Shape parameter + lamda : Real Number, `\lambda > 0` + Scale parameter + + Examples + ======== + + >>> from sympy.stats import Lomax, density, cdf, E + >>> from sympy import symbols + >>> a, l = symbols('a, l', positive=True) + >>> X = Lomax('X', a, l) + >>> x = symbols('x') + >>> density(X)(x) + a*(1 + x/l)**(-a - 1)/l + >>> cdf(X)(x) + Piecewise((1 - 1/(1 + x/l)**a, x >= 0), (0, True)) + >>> a = 2 + >>> X = Lomax('X', a, l) + >>> E(X) + l + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Lomax_distribution + + """ + return rv(name, LomaxDistribution, (alpha, lamda)) + +#------------------------------------------------------------------------------- +# Maxwell distribution --------------------------------------------------------- + + +class MaxwellDistribution(SingleContinuousDistribution): + _argnames = ('a',) + + set = Interval(0, oo) + + @staticmethod + def check(a): + _value_check(a > 0, "Parameter a must be positive.") + + def pdf(self, x): + a = self.a + return sqrt(2/pi)*x**2*exp(-x**2/(2*a**2))/a**3 + + def _cdf(self, x): + a = self.a + return erf(sqrt(2)*x/(2*a)) - sqrt(2)*x*exp(-x**2/(2*a**2))/(sqrt(pi)*a) + +def Maxwell(name, a): + r""" + Create a continuous random variable with a Maxwell distribution. + + Explanation + =========== + + The density of the Maxwell distribution is given by + + .. math:: + f(x) := \sqrt{\frac{2}{\pi}} \frac{x^2 e^{-x^2/(2a^2)}}{a^3} + + with :math:`x \geq 0`. + + .. TODO - what does the parameter mean? + + Parameters + ========== + + a : Real number, `a > 0` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Maxwell, density, E, variance + >>> from sympy import Symbol, simplify + + >>> a = Symbol("a", positive=True) + >>> z = Symbol("z") + + >>> X = Maxwell("x", a) + + >>> density(X)(z) + sqrt(2)*z**2*exp(-z**2/(2*a**2))/(sqrt(pi)*a**3) + + >>> E(X) + 2*sqrt(2)*a/sqrt(pi) + + >>> simplify(variance(X)) + a**2*(-8 + 3*pi)/pi + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Maxwell_distribution + .. [2] https://mathworld.wolfram.com/MaxwellDistribution.html + + """ + + return rv(name, MaxwellDistribution, (a, )) + +#------------------------------------------------------------------------------- +# Moyal Distribution ----------------------------------------------------------- +class MoyalDistribution(SingleContinuousDistribution): + _argnames = ('mu', 'sigma') + + @staticmethod + def check(mu, sigma): + _value_check(mu.is_real, "Location parameter must be real.") + _value_check(sigma.is_real and sigma > 0, "Scale parameter must be real\ + and positive.") + + def pdf(self, x): + mu, sigma = self.mu, self.sigma + num = exp(-(exp(-(x - mu)/sigma) + (x - mu)/(sigma))/2) + den = (sqrt(2*pi) * sigma) + return num/den + + def _characteristic_function(self, t): + mu, sigma = self.mu, self.sigma + term1 = exp(I*t*mu) + term2 = (2**(-I*sigma*t) * gamma(Rational(1, 2) - I*t*sigma)) + return (term1 * term2)/sqrt(pi) + + def _moment_generating_function(self, t): + mu, sigma = self.mu, self.sigma + term1 = exp(t*mu) + term2 = (2**(-1*sigma*t) * gamma(Rational(1, 2) - t*sigma)) + return (term1 * term2)/sqrt(pi) + +def Moyal(name, mu, sigma): + r""" + Create a continuous random variable with a Moyal distribution. + + Explanation + =========== + + The density of the Moyal distribution is given by + + .. math:: + f(x) := \frac{\exp-\frac{1}{2}\exp-\frac{x-\mu}{\sigma}-\frac{x-\mu}{2\sigma}}{\sqrt{2\pi}\sigma} + + with :math:`x \in \mathbb{R}`. + + Parameters + ========== + + mu : Real number + Location parameter + sigma : Real positive number + Scale parameter + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Moyal, density, cdf + >>> from sympy import Symbol, simplify + >>> mu = Symbol("mu", real=True) + >>> sigma = Symbol("sigma", positive=True, real=True) + >>> z = Symbol("z") + >>> X = Moyal("x", mu, sigma) + >>> density(X)(z) + sqrt(2)*exp(-exp((mu - z)/sigma)/2 - (-mu + z)/(2*sigma))/(2*sqrt(pi)*sigma) + >>> simplify(cdf(X)(z)) + 1 - erf(sqrt(2)*exp((mu - z)/(2*sigma))/2) + + References + ========== + + .. [1] https://reference.wolfram.com/language/ref/MoyalDistribution.html + .. [2] https://www.stat.rice.edu/~dobelman/textfiles/DistributionsHandbook.pdf + + """ + + return rv(name, MoyalDistribution, (mu, sigma)) + +#------------------------------------------------------------------------------- +# Nakagami distribution -------------------------------------------------------- + + +class NakagamiDistribution(SingleContinuousDistribution): + _argnames = ('mu', 'omega') + + set = Interval(0, oo) + + @staticmethod + def check(mu, omega): + _value_check(mu >= S.Half, "Shape parameter mu must be greater than equal to 1/2.") + _value_check(omega > 0, "Spread parameter omega must be positive.") + + def pdf(self, x): + mu, omega = self.mu, self.omega + return 2*mu**mu/(gamma(mu)*omega**mu)*x**(2*mu - 1)*exp(-mu/omega*x**2) + + def _cdf(self, x): + mu, omega = self.mu, self.omega + return Piecewise( + (lowergamma(mu, (mu/omega)*x**2)/gamma(mu), x > 0), + (S.Zero, True)) + +def Nakagami(name, mu, omega): + r""" + Create a continuous random variable with a Nakagami distribution. + + Explanation + =========== + + The density of the Nakagami distribution is given by + + .. math:: + f(x) := \frac{2\mu^\mu}{\Gamma(\mu)\omega^\mu} x^{2\mu-1} + \exp\left(-\frac{\mu}{\omega}x^2 \right) + + with :math:`x > 0`. + + Parameters + ========== + + mu : Real number, `\mu \geq \frac{1}{2}`, a shape + omega : Real number, `\omega > 0`, the spread + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Nakagami, density, E, variance, cdf + >>> from sympy import Symbol, simplify, pprint + + >>> mu = Symbol("mu", positive=True) + >>> omega = Symbol("omega", positive=True) + >>> z = Symbol("z") + + >>> X = Nakagami("x", mu, omega) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + 2 + -mu*z + ------- + mu -mu 2*mu - 1 omega + 2*mu *omega *z *e + ---------------------------------- + Gamma(mu) + + >>> simplify(E(X)) + sqrt(mu)*sqrt(omega)*gamma(mu + 1/2)/gamma(mu + 1) + + >>> V = simplify(variance(X)) + >>> pprint(V, use_unicode=False) + 2 + omega*Gamma (mu + 1/2) + omega - ----------------------- + Gamma(mu)*Gamma(mu + 1) + + >>> cdf(X)(z) + Piecewise((lowergamma(mu, mu*z**2/omega)/gamma(mu), z > 0), + (0, True)) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Nakagami_distribution + + """ + + return rv(name, NakagamiDistribution, (mu, omega)) + +#------------------------------------------------------------------------------- +# Normal distribution ---------------------------------------------------------- + + +class NormalDistribution(SingleContinuousDistribution): + _argnames = ('mean', 'std') + + @staticmethod + def check(mean, std): + _value_check(std > 0, "Standard deviation must be positive") + + def pdf(self, x): + return exp(-(x - self.mean)**2 / (2*self.std**2)) / (sqrt(2*pi)*self.std) + + def _cdf(self, x): + mean, std = self.mean, self.std + return erf(sqrt(2)*(-mean + x)/(2*std))/2 + S.Half + + def _characteristic_function(self, t): + mean, std = self.mean, self.std + return exp(I*mean*t - std**2*t**2/2) + + def _moment_generating_function(self, t): + mean, std = self.mean, self.std + return exp(mean*t + std**2*t**2/2) + + def _quantile(self, p): + mean, std = self.mean, self.std + return mean + std*sqrt(2)*erfinv(2*p - 1) + + +def Normal(name, mean, std): + r""" + Create a continuous random variable with a Normal distribution. + + Explanation + =========== + + The density of the Normal distribution is given by + + .. math:: + f(x) := \frac{1}{\sigma\sqrt{2\pi}} e^{ -\frac{(x-\mu)^2}{2\sigma^2} } + + Parameters + ========== + + mu : Real number or a list representing the mean or the mean vector + sigma : Real number or a positive definite square matrix, + :math:`\sigma^2 > 0`, the variance + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Normal, density, E, std, cdf, skewness, quantile, marginal_distribution + >>> from sympy import Symbol, simplify, pprint + + >>> mu = Symbol("mu") + >>> sigma = Symbol("sigma", positive=True) + >>> z = Symbol("z") + >>> y = Symbol("y") + >>> p = Symbol("p") + >>> X = Normal("x", mu, sigma) + + >>> density(X)(z) + sqrt(2)*exp(-(-mu + z)**2/(2*sigma**2))/(2*sqrt(pi)*sigma) + + >>> C = simplify(cdf(X))(z) # it needs a little more help... + >>> pprint(C, use_unicode=False) + / ___ \ + |\/ 2 *(-mu + z)| + erf|---------------| + \ 2*sigma / 1 + -------------------- + - + 2 2 + + >>> quantile(X)(p) + mu + sqrt(2)*sigma*erfinv(2*p - 1) + + >>> simplify(skewness(X)) + 0 + + >>> X = Normal("x", 0, 1) # Mean 0, standard deviation 1 + >>> density(X)(z) + sqrt(2)*exp(-z**2/2)/(2*sqrt(pi)) + + >>> E(2*X + 1) + 1 + + >>> simplify(std(2*X + 1)) + 2 + + >>> m = Normal('X', [1, 2], [[2, 1], [1, 2]]) + >>> pprint(density(m)(y, z), use_unicode=False) + 2 2 + y y*z z + - -- + --- - -- + z - 1 + ___ 3 3 3 + \/ 3 *e + ------------------------------ + 6*pi + + >>> marginal_distribution(m, m[0])(1) + 1/(2*sqrt(pi)) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Normal_distribution + .. [2] https://mathworld.wolfram.com/NormalDistributionFunction.html + + """ + + if isinstance(mean, list) or getattr(mean, 'is_Matrix', False) and\ + isinstance(std, list) or getattr(std, 'is_Matrix', False): + from sympy.stats.joint_rv_types import MultivariateNormal + return MultivariateNormal(name, mean, std) + return rv(name, NormalDistribution, (mean, std)) + + +#------------------------------------------------------------------------------- +# Inverse Gaussian distribution ---------------------------------------------------------- + + +class GaussianInverseDistribution(SingleContinuousDistribution): + _argnames = ('mean', 'shape') + + @property + def set(self): + return Interval(0, oo) + + @staticmethod + def check(mean, shape): + _value_check(shape > 0, "Shape parameter must be positive") + _value_check(mean > 0, "Mean must be positive") + + def pdf(self, x): + mu, s = self.mean, self.shape + return exp(-s*(x - mu)**2 / (2*x*mu**2)) * sqrt(s/(2*pi*x**3)) + + def _cdf(self, x): + from sympy.stats import cdf + mu, s = self.mean, self.shape + stdNormalcdf = cdf(Normal('x', 0, 1)) + + first_term = stdNormalcdf(sqrt(s/x) * ((x/mu) - S.One)) + second_term = exp(2*s/mu) * stdNormalcdf(-sqrt(s/x)*(x/mu + S.One)) + + return first_term + second_term + + def _characteristic_function(self, t): + mu, s = self.mean, self.shape + return exp((s/mu)*(1 - sqrt(1 - (2*mu**2*I*t)/s))) + + def _moment_generating_function(self, t): + mu, s = self.mean, self.shape + return exp((s/mu)*(1 - sqrt(1 - (2*mu**2*t)/s))) + + +def GaussianInverse(name, mean, shape): + r""" + Create a continuous random variable with an Inverse Gaussian distribution. + Inverse Gaussian distribution is also known as Wald distribution. + + Explanation + =========== + + The density of the Inverse Gaussian distribution is given by + + .. math:: + f(x) := \sqrt{\frac{\lambda}{2\pi x^3}} e^{-\frac{\lambda(x-\mu)^2}{2x\mu^2}} + + Parameters + ========== + + mu : + Positive number representing the mean. + lambda : + Positive number representing the shape parameter. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import GaussianInverse, density, E, std, skewness + >>> from sympy import Symbol, pprint + + >>> mu = Symbol("mu", positive=True) + >>> lamda = Symbol("lambda", positive=True) + >>> z = Symbol("z", positive=True) + >>> X = GaussianInverse("x", mu, lamda) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + 2 + -lambda*(-mu + z) + ------------------- + 2 + ___ ________ 2*mu *z + \/ 2 *\/ lambda *e + ------------------------------------- + ____ 3/2 + 2*\/ pi *z + + >>> E(X) + mu + + >>> std(X).expand() + mu**(3/2)/sqrt(lambda) + + >>> skewness(X).expand() + 3*sqrt(mu)/sqrt(lambda) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution + .. [2] https://mathworld.wolfram.com/InverseGaussianDistribution.html + + """ + + return rv(name, GaussianInverseDistribution, (mean, shape)) + +Wald = GaussianInverse + +#------------------------------------------------------------------------------- +# Pareto distribution ---------------------------------------------------------- + + +class ParetoDistribution(SingleContinuousDistribution): + _argnames = ('xm', 'alpha') + + @property + def set(self): + return Interval(self.xm, oo) + + @staticmethod + def check(xm, alpha): + _value_check(xm > 0, "Xm must be positive") + _value_check(alpha > 0, "Alpha must be positive") + + def pdf(self, x): + xm, alpha = self.xm, self.alpha + return alpha * xm**alpha / x**(alpha + 1) + + def _cdf(self, x): + xm, alpha = self.xm, self.alpha + return Piecewise( + (S.One - xm**alpha/x**alpha, x>=xm), + (0, True), + ) + + def _moment_generating_function(self, t): + xm, alpha = self.xm, self.alpha + return alpha * (-xm*t)**alpha * uppergamma(-alpha, -xm*t) + + def _characteristic_function(self, t): + xm, alpha = self.xm, self.alpha + return alpha * (-I * xm * t) ** alpha * uppergamma(-alpha, -I * xm * t) + + +def Pareto(name, xm, alpha): + r""" + Create a continuous random variable with the Pareto distribution. + + Explanation + =========== + + The density of the Pareto distribution is given by + + .. math:: + f(x) := \frac{\alpha\,x_m^\alpha}{x^{\alpha+1}} + + with :math:`x \in [x_m,\infty]`. + + Parameters + ========== + + xm : Real number, `x_m > 0`, a scale + alpha : Real number, `\alpha > 0`, a shape + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Pareto, density + >>> from sympy import Symbol + + >>> xm = Symbol("xm", positive=True) + >>> beta = Symbol("beta", positive=True) + >>> z = Symbol("z") + + >>> X = Pareto("x", xm, beta) + + >>> density(X)(z) + beta*xm**beta*z**(-beta - 1) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Pareto_distribution + .. [2] https://mathworld.wolfram.com/ParetoDistribution.html + + """ + + return rv(name, ParetoDistribution, (xm, alpha)) + +#------------------------------------------------------------------------------- +# PowerFunction distribution --------------------------------------------------- + + +class PowerFunctionDistribution(SingleContinuousDistribution): + _argnames=('alpha','a','b') + + @property + def set(self): + return Interval(self.a, self.b) + + @staticmethod + def check(alpha, a, b): + _value_check(a.is_real, "Continuous Boundary parameter should be real.") + _value_check(b.is_real, "Continuous Boundary parameter should be real.") + _value_check(a < b, " 'a' the left Boundary must be smaller than 'b' the right Boundary." ) + _value_check(alpha.is_positive, "Continuous Shape parameter should be positive.") + + def pdf(self, x): + alpha, a, b = self.alpha, self.a, self.b + num = alpha*(x - a)**(alpha - 1) + den = (b - a)**alpha + return num/den + +def PowerFunction(name, alpha, a, b): + r""" + Creates a continuous random variable with a Power Function Distribution. + + Explanation + =========== + + The density of PowerFunction distribution is given by + + .. math:: + f(x) := \frac{{\alpha}(x - a)^{\alpha - 1}}{(b - a)^{\alpha}} + + with :math:`x \in [a,b]`. + + Parameters + ========== + + alpha : Positive number, `0 < \alpha`, the shape parameter + a : Real number, :math:`-\infty < a`, the left boundary + b : Real number, :math:`a < b < \infty`, the right boundary + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import PowerFunction, density, cdf, E, variance + >>> from sympy import Symbol + >>> alpha = Symbol("alpha", positive=True) + >>> a = Symbol("a", real=True) + >>> b = Symbol("b", real=True) + >>> z = Symbol("z") + + >>> X = PowerFunction("X", 2, a, b) + + >>> density(X)(z) + (-2*a + 2*z)/(-a + b)**2 + + >>> cdf(X)(z) + Piecewise((a**2/(a**2 - 2*a*b + b**2) - 2*a*z/(a**2 - 2*a*b + b**2) + + z**2/(a**2 - 2*a*b + b**2), a <= z), (0, True)) + + >>> alpha = 2 + >>> a = 0 + >>> b = 1 + >>> Y = PowerFunction("Y", alpha, a, b) + + >>> E(Y) + 2/3 + + >>> variance(Y) + 1/18 + + References + ========== + + .. [1] https://web.archive.org/web/20200204081320/http://www.mathwave.com/help/easyfit/html/analyses/distributions/power_func.html + + """ + return rv(name, PowerFunctionDistribution, (alpha, a, b)) + +#------------------------------------------------------------------------------- +# QuadraticU distribution ------------------------------------------------------ + + +class QuadraticUDistribution(SingleContinuousDistribution): + _argnames = ('a', 'b') + + @property + def set(self): + return Interval(self.a, self.b) + + @staticmethod + def check(a, b): + _value_check(b > a, "Parameter b must be in range (%s, oo)."%(a)) + + def pdf(self, x): + a, b = self.a, self.b + alpha = 12 / (b-a)**3 + beta = (a+b) / 2 + return Piecewise( + (alpha * (x-beta)**2, And(a<=x, x<=b)), + (S.Zero, True)) + + def _moment_generating_function(self, t): + a, b = self.a, self.b + return -3 * (exp(a*t) * (4 + (a**2 + 2*a*(-2 + b) + b**2) * t) \ + - exp(b*t) * (4 + (-4*b + (a + b)**2) * t)) / ((a-b)**3 * t**2) + + def _characteristic_function(self, t): + a, b = self.a, self.b + return -3*I*(exp(I*a*t*exp(I*b*t)) * (4*I - (-4*b + (a+b)**2)*t)) \ + / ((a-b)**3 * t**2) + + +def QuadraticU(name, a, b): + r""" + Create a Continuous Random Variable with a U-quadratic distribution. + + Explanation + =========== + + The density of the U-quadratic distribution is given by + + .. math:: + f(x) := \alpha (x-\beta)^2 + + with :math:`x \in [a,b]`. + + Parameters + ========== + + a : Real number + b : Real number, :math:`a < b` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import QuadraticU, density + >>> from sympy import Symbol, pprint + + >>> a = Symbol("a", real=True) + >>> b = Symbol("b", real=True) + >>> z = Symbol("z") + + >>> X = QuadraticU("x", a, b) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + / 2 + | / a b \ + |12*|- - - - + z| + | \ 2 2 / + <----------------- for And(b >= z, a <= z) + | 3 + | (-a + b) + | + \ 0 otherwise + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/U-quadratic_distribution + + """ + + return rv(name, QuadraticUDistribution, (a, b)) + +#------------------------------------------------------------------------------- +# RaisedCosine distribution ---------------------------------------------------- + + +class RaisedCosineDistribution(SingleContinuousDistribution): + _argnames = ('mu', 's') + + @property + def set(self): + return Interval(self.mu - self.s, self.mu + self.s) + + @staticmethod + def check(mu, s): + _value_check(s > 0, "s must be positive") + + def pdf(self, x): + mu, s = self.mu, self.s + return Piecewise( + ((1+cos(pi*(x-mu)/s)) / (2*s), And(mu-s<=x, x<=mu+s)), + (S.Zero, True)) + + def _characteristic_function(self, t): + mu, s = self.mu, self.s + return Piecewise((exp(-I*pi*mu/s)/2, Eq(t, -pi/s)), + (exp(I*pi*mu/s)/2, Eq(t, pi/s)), + (pi**2*sin(s*t)*exp(I*mu*t) / (s*t*(pi**2 - s**2*t**2)), True)) + + def _moment_generating_function(self, t): + mu, s = self.mu, self.s + return pi**2 * sinh(s*t) * exp(mu*t) / (s*t*(pi**2 + s**2*t**2)) + +def RaisedCosine(name, mu, s): + r""" + Create a Continuous Random Variable with a raised cosine distribution. + + Explanation + =========== + + The density of the raised cosine distribution is given by + + .. math:: + f(x) := \frac{1}{2s}\left(1+\cos\left(\frac{x-\mu}{s}\pi\right)\right) + + with :math:`x \in [\mu-s,\mu+s]`. + + Parameters + ========== + + mu : Real number + s : Real number, `s > 0` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import RaisedCosine, density + >>> from sympy import Symbol, pprint + + >>> mu = Symbol("mu", real=True) + >>> s = Symbol("s", positive=True) + >>> z = Symbol("z") + + >>> X = RaisedCosine("x", mu, s) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + / /pi*(-mu + z)\ + |cos|------------| + 1 + | \ s / + <--------------------- for And(z >= mu - s, z <= mu + s) + | 2*s + | + \ 0 otherwise + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Raised_cosine_distribution + + """ + + return rv(name, RaisedCosineDistribution, (mu, s)) + +#------------------------------------------------------------------------------- +# Rayleigh distribution -------------------------------------------------------- + + +class RayleighDistribution(SingleContinuousDistribution): + _argnames = ('sigma',) + + set = Interval(0, oo) + + @staticmethod + def check(sigma): + _value_check(sigma > 0, "Scale parameter sigma must be positive.") + + def pdf(self, x): + sigma = self.sigma + return x/sigma**2*exp(-x**2/(2*sigma**2)) + + def _cdf(self, x): + sigma = self.sigma + return 1 - exp(-(x**2/(2*sigma**2))) + + def _characteristic_function(self, t): + sigma = self.sigma + return 1 - sigma*t*exp(-sigma**2*t**2/2) * sqrt(pi/2) * (erfi(sigma*t/sqrt(2)) - I) + + def _moment_generating_function(self, t): + sigma = self.sigma + return 1 + sigma*t*exp(sigma**2*t**2/2) * sqrt(pi/2) * (erf(sigma*t/sqrt(2)) + 1) + + +def Rayleigh(name, sigma): + r""" + Create a continuous random variable with a Rayleigh distribution. + + Explanation + =========== + + The density of the Rayleigh distribution is given by + + .. math :: + f(x) := \frac{x}{\sigma^2} e^{-x^2/2\sigma^2} + + with :math:`x > 0`. + + Parameters + ========== + + sigma : Real number, `\sigma > 0` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Rayleigh, density, E, variance + >>> from sympy import Symbol + + >>> sigma = Symbol("sigma", positive=True) + >>> z = Symbol("z") + + >>> X = Rayleigh("x", sigma) + + >>> density(X)(z) + z*exp(-z**2/(2*sigma**2))/sigma**2 + + >>> E(X) + sqrt(2)*sqrt(pi)*sigma/2 + + >>> variance(X) + -pi*sigma**2/2 + 2*sigma**2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Rayleigh_distribution + .. [2] https://mathworld.wolfram.com/RayleighDistribution.html + + """ + + return rv(name, RayleighDistribution, (sigma, )) + +#------------------------------------------------------------------------------- +# Reciprocal distribution -------------------------------------------------------- + +class ReciprocalDistribution(SingleContinuousDistribution): + _argnames = ('a', 'b') + + @property + def set(self): + return Interval(self.a, self.b) + + @staticmethod + def check(a, b): + _value_check(a > 0, "Parameter > 0. a = %s"%a) + _value_check((a < b), + "Parameter b must be in range (%s, +oo]. b = %s"%(a, b)) + + def pdf(self, x): + a, b = self.a, self.b + return 1/(x*(log(b) - log(a))) + + +def Reciprocal(name, a, b): + r"""Creates a continuous random variable with a reciprocal distribution. + + + Parameters + ========== + + a : Real number, :math:`0 < a` + b : Real number, :math:`a < b` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Reciprocal, density, cdf + >>> from sympy import symbols + >>> a, b, x = symbols('a, b, x', positive=True) + >>> R = Reciprocal('R', a, b) + + >>> density(R)(x) + 1/(x*(-log(a) + log(b))) + >>> cdf(R)(x) + Piecewise((log(a)/(log(a) - log(b)) - log(x)/(log(a) - log(b)), a <= x), (0, True)) + + Reference + ========= + + .. [1] https://en.wikipedia.org/wiki/Reciprocal_distribution + + """ + return rv(name, ReciprocalDistribution, (a, b)) + + +#------------------------------------------------------------------------------- +# Shifted Gompertz distribution ------------------------------------------------ + + +class ShiftedGompertzDistribution(SingleContinuousDistribution): + _argnames = ('b', 'eta') + + set = Interval(0, oo) + + @staticmethod + def check(b, eta): + _value_check(b > 0, "b must be positive") + _value_check(eta > 0, "eta must be positive") + + def pdf(self, x): + b, eta = self.b, self.eta + return b*exp(-b*x)*exp(-eta*exp(-b*x))*(1+eta*(1-exp(-b*x))) + +def ShiftedGompertz(name, b, eta): + r""" + Create a continuous random variable with a Shifted Gompertz distribution. + + Explanation + =========== + + The density of the Shifted Gompertz distribution is given by + + .. math:: + f(x) := b e^{-b x} e^{-\eta \exp(-b x)} \left[1 + \eta(1 - e^(-bx)) \right] + + with :math:`x \in [0, \infty)`. + + Parameters + ========== + + b : Real number, `b > 0`, a scale + eta : Real number, `\eta > 0`, a shape + + Returns + ======= + + RandomSymbol + + Examples + ======== + >>> from sympy.stats import ShiftedGompertz, density + >>> from sympy import Symbol + + >>> b = Symbol("b", positive=True) + >>> eta = Symbol("eta", positive=True) + >>> x = Symbol("x") + + >>> X = ShiftedGompertz("x", b, eta) + + >>> density(X)(x) + b*(eta*(1 - exp(-b*x)) + 1)*exp(-b*x)*exp(-eta*exp(-b*x)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Shifted_Gompertz_distribution + + """ + return rv(name, ShiftedGompertzDistribution, (b, eta)) + +#------------------------------------------------------------------------------- +# StudentT distribution -------------------------------------------------------- + + +class StudentTDistribution(SingleContinuousDistribution): + _argnames = ('nu',) + + set = Interval(-oo, oo) + + @staticmethod + def check(nu): + _value_check(nu > 0, "Degrees of freedom nu must be positive.") + + def pdf(self, x): + nu = self.nu + return 1/(sqrt(nu)*beta_fn(S.Half, nu/2))*(1 + x**2/nu)**(-(nu + 1)/2) + + def _cdf(self, x): + nu = self.nu + return S.Half + x*gamma((nu+1)/2)*hyper((S.Half, (nu+1)/2), + (Rational(3, 2),), -x**2/nu)/(sqrt(pi*nu)*gamma(nu/2)) + + def _moment_generating_function(self, t): + raise NotImplementedError('The moment generating function for the Student-T distribution is undefined.') + + +def StudentT(name, nu): + r""" + Create a continuous random variable with a student's t distribution. + + Explanation + =========== + + The density of the student's t distribution is given by + + .. math:: + f(x) := \frac{\Gamma \left(\frac{\nu+1}{2} \right)} + {\sqrt{\nu\pi}\Gamma \left(\frac{\nu}{2} \right)} + \left(1+\frac{x^2}{\nu} \right)^{-\frac{\nu+1}{2}} + + Parameters + ========== + + nu : Real number, `\nu > 0`, the degrees of freedom + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import StudentT, density, cdf + >>> from sympy import Symbol, pprint + + >>> nu = Symbol("nu", positive=True) + >>> z = Symbol("z") + + >>> X = StudentT("x", nu) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + nu 1 + - -- - - + 2 2 + / 2\ + | z | + |1 + --| + \ nu/ + ----------------- + ____ / nu\ + \/ nu *B|1/2, --| + \ 2 / + + >>> cdf(X)(z) + 1/2 + z*gamma(nu/2 + 1/2)*hyper((1/2, nu/2 + 1/2), (3/2,), + -z**2/nu)/(sqrt(pi)*sqrt(nu)*gamma(nu/2)) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Student_t-distribution + .. [2] https://mathworld.wolfram.com/Studentst-Distribution.html + + """ + + return rv(name, StudentTDistribution, (nu, )) + +#------------------------------------------------------------------------------- +# Trapezoidal distribution ------------------------------------------------------ + + +class TrapezoidalDistribution(SingleContinuousDistribution): + _argnames = ('a', 'b', 'c', 'd') + + @property + def set(self): + return Interval(self.a, self.d) + + @staticmethod + def check(a, b, c, d): + _value_check(a < d, "Lower bound parameter a < %s. a = %s"%(d, a)) + _value_check((a <= b, b < c), + "Level start parameter b must be in range [%s, %s). b = %s"%(a, c, b)) + _value_check((b < c, c <= d), + "Level end parameter c must be in range (%s, %s]. c = %s"%(b, d, c)) + _value_check(d >= c, "Upper bound parameter d > %s. d = %s"%(c, d)) + + def pdf(self, x): + a, b, c, d = self.a, self.b, self.c, self.d + return Piecewise( + (2*(x-a) / ((b-a)*(d+c-a-b)), And(a <= x, x < b)), + (2 / (d+c-a-b), And(b <= x, x < c)), + (2*(d-x) / ((d-c)*(d+c-a-b)), And(c <= x, x <= d)), + (S.Zero, True)) + +def Trapezoidal(name, a, b, c, d): + r""" + Create a continuous random variable with a trapezoidal distribution. + + Explanation + =========== + + The density of the trapezoidal distribution is given by + + .. math:: + f(x) := \begin{cases} + 0 & \mathrm{for\ } x < a, \\ + \frac{2(x-a)}{(b-a)(d+c-a-b)} & \mathrm{for\ } a \le x < b, \\ + \frac{2}{d+c-a-b} & \mathrm{for\ } b \le x < c, \\ + \frac{2(d-x)}{(d-c)(d+c-a-b)} & \mathrm{for\ } c \le x < d, \\ + 0 & \mathrm{for\ } d < x. + \end{cases} + + Parameters + ========== + + a : Real number, :math:`a < d` + b : Real number, :math:`a \le b < c` + c : Real number, :math:`b < c \le d` + d : Real number + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Trapezoidal, density + >>> from sympy import Symbol, pprint + + >>> a = Symbol("a") + >>> b = Symbol("b") + >>> c = Symbol("c") + >>> d = Symbol("d") + >>> z = Symbol("z") + + >>> X = Trapezoidal("x", a,b,c,d) + + >>> pprint(density(X)(z), use_unicode=False) + / -2*a + 2*z + |------------------------- for And(a <= z, b > z) + |(-a + b)*(-a - b + c + d) + | + | 2 + | -------------- for And(b <= z, c > z) + < -a - b + c + d + | + | 2*d - 2*z + |------------------------- for And(d >= z, c <= z) + |(-c + d)*(-a - b + c + d) + | + \ 0 otherwise + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trapezoidal_distribution + + """ + return rv(name, TrapezoidalDistribution, (a, b, c, d)) + +#------------------------------------------------------------------------------- +# Triangular distribution ------------------------------------------------------ + + +class TriangularDistribution(SingleContinuousDistribution): + _argnames = ('a', 'b', 'c') + + @property + def set(self): + return Interval(self.a, self.b) + + @staticmethod + def check(a, b, c): + _value_check(b > a, "Parameter b > %s. b = %s"%(a, b)) + _value_check((a <= c, c <= b), + "Parameter c must be in range [%s, %s]. c = %s"%(a, b, c)) + + def pdf(self, x): + a, b, c = self.a, self.b, self.c + return Piecewise( + (2*(x - a)/((b - a)*(c - a)), And(a <= x, x < c)), + (2/(b - a), Eq(x, c)), + (2*(b - x)/((b - a)*(b - c)), And(c < x, x <= b)), + (S.Zero, True)) + + def _characteristic_function(self, t): + a, b, c = self.a, self.b, self.c + return -2 *((b-c) * exp(I*a*t) - (b-a) * exp(I*c*t) + (c-a) * exp(I*b*t)) / ((b-a)*(c-a)*(b-c)*t**2) + + def _moment_generating_function(self, t): + a, b, c = self.a, self.b, self.c + return 2 * ((b - c) * exp(a * t) - (b - a) * exp(c * t) + (c - a) * exp(b * t)) / ( + (b - a) * (c - a) * (b - c) * t ** 2) + + +def Triangular(name, a, b, c): + r""" + Create a continuous random variable with a triangular distribution. + + Explanation + =========== + + The density of the triangular distribution is given by + + .. math:: + f(x) := \begin{cases} + 0 & \mathrm{for\ } x < a, \\ + \frac{2(x-a)}{(b-a)(c-a)} & \mathrm{for\ } a \le x < c, \\ + \frac{2}{b-a} & \mathrm{for\ } x = c, \\ + \frac{2(b-x)}{(b-a)(b-c)} & \mathrm{for\ } c < x \le b, \\ + 0 & \mathrm{for\ } b < x. + \end{cases} + + Parameters + ========== + + a : Real number, :math:`a \in \left(-\infty, \infty\right)` + b : Real number, :math:`a < b` + c : Real number, :math:`a \leq c \leq b` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Triangular, density + >>> from sympy import Symbol, pprint + + >>> a = Symbol("a") + >>> b = Symbol("b") + >>> c = Symbol("c") + >>> z = Symbol("z") + + >>> X = Triangular("x", a,b,c) + + >>> pprint(density(X)(z), use_unicode=False) + / -2*a + 2*z + |----------------- for And(a <= z, c > z) + |(-a + b)*(-a + c) + | + | 2 + | ------ for c = z + < -a + b + | + | 2*b - 2*z + |---------------- for And(b >= z, c < z) + |(-a + b)*(b - c) + | + \ 0 otherwise + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Triangular_distribution + .. [2] https://mathworld.wolfram.com/TriangularDistribution.html + + """ + + return rv(name, TriangularDistribution, (a, b, c)) + +#------------------------------------------------------------------------------- +# Uniform distribution --------------------------------------------------------- + + +class UniformDistribution(SingleContinuousDistribution): + _argnames = ('left', 'right') + + @property + def set(self): + return Interval(self.left, self.right) + + @staticmethod + def check(left, right): + _value_check(left < right, "Lower limit should be less than Upper limit.") + + def pdf(self, x): + left, right = self.left, self.right + return Piecewise( + (S.One/(right - left), And(left <= x, x <= right)), + (S.Zero, True) + ) + + def _cdf(self, x): + left, right = self.left, self.right + return Piecewise( + (S.Zero, x < left), + ((x - left)/(right - left), x <= right), + (S.One, True) + ) + + def _characteristic_function(self, t): + left, right = self.left, self.right + return Piecewise(((exp(I*t*right) - exp(I*t*left)) / (I*t*(right - left)), Ne(t, 0)), + (S.One, True)) + + def _moment_generating_function(self, t): + left, right = self.left, self.right + return Piecewise(((exp(t*right) - exp(t*left)) / (t * (right - left)), Ne(t, 0)), + (S.One, True)) + + def expectation(self, expr, var, **kwargs): + kwargs['evaluate'] = True + result = SingleContinuousDistribution.expectation(self, expr, var, **kwargs) + result = result.subs({Max(self.left, self.right): self.right, + Min(self.left, self.right): self.left}) + return result + + +def Uniform(name, left, right): + r""" + Create a continuous random variable with a uniform distribution. + + Explanation + =========== + + The density of the uniform distribution is given by + + .. math:: + f(x) := \begin{cases} + \frac{1}{b - a} & \text{for } x \in [a,b] \\ + 0 & \text{otherwise} + \end{cases} + + with :math:`x \in [a,b]`. + + Parameters + ========== + + a : Real number, :math:`-\infty < a`, the left boundary + b : Real number, :math:`a < b < \infty`, the right boundary + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Uniform, density, cdf, E, variance + >>> from sympy import Symbol, simplify + + >>> a = Symbol("a", negative=True) + >>> b = Symbol("b", positive=True) + >>> z = Symbol("z") + + >>> X = Uniform("x", a, b) + + >>> density(X)(z) + Piecewise((1/(-a + b), (b >= z) & (a <= z)), (0, True)) + + >>> cdf(X)(z) + Piecewise((0, a > z), ((-a + z)/(-a + b), b >= z), (1, True)) + + >>> E(X) + a/2 + b/2 + + >>> simplify(variance(X)) + a**2/12 - a*b/6 + b**2/12 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Uniform_distribution_%28continuous%29 + .. [2] https://mathworld.wolfram.com/UniformDistribution.html + + """ + + return rv(name, UniformDistribution, (left, right)) + +#------------------------------------------------------------------------------- +# UniformSum distribution ------------------------------------------------------ + + +class UniformSumDistribution(SingleContinuousDistribution): + _argnames = ('n',) + + @property + def set(self): + return Interval(0, self.n) + + @staticmethod + def check(n): + _value_check((n > 0, n.is_integer), + "Parameter n must be positive integer.") + + def pdf(self, x): + n = self.n + k = Dummy("k") + return 1/factorial( + n - 1)*Sum((-1)**k*binomial(n, k)*(x - k)**(n - 1), (k, 0, floor(x))) + + def _cdf(self, x): + n = self.n + k = Dummy("k") + return Piecewise((S.Zero, x < 0), + (1/factorial(n)*Sum((-1)**k*binomial(n, k)*(x - k)**(n), + (k, 0, floor(x))), x <= n), + (S.One, True)) + + def _characteristic_function(self, t): + return ((exp(I*t) - 1) / (I*t))**self.n + + def _moment_generating_function(self, t): + return ((exp(t) - 1) / t)**self.n + +def UniformSum(name, n): + r""" + Create a continuous random variable with an Irwin-Hall distribution. + + Explanation + =========== + + The probability distribution function depends on a single parameter + $n$ which is an integer. + + The density of the Irwin-Hall distribution is given by + + .. math :: + f(x) := \frac{1}{(n-1)!}\sum_{k=0}^{\left\lfloor x\right\rfloor}(-1)^k + \binom{n}{k}(x-k)^{n-1} + + Parameters + ========== + + n : A positive integer, `n > 0` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import UniformSum, density, cdf + >>> from sympy import Symbol, pprint + + >>> n = Symbol("n", integer=True) + >>> z = Symbol("z") + + >>> X = UniformSum("x", n) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + floor(z) + ___ + \ ` + \ k n - 1 /n\ + ) (-1) *(-k + z) *| | + / \k/ + /__, + k = 0 + -------------------------------- + (n - 1)! + + >>> cdf(X)(z) + Piecewise((0, z < 0), (Sum((-1)**_k*(-_k + z)**n*binomial(n, _k), + (_k, 0, floor(z)))/factorial(n), n >= z), (1, True)) + + + Compute cdf with specific 'x' and 'n' values as follows : + >>> cdf(UniformSum("x", 5), evaluate=False)(2).doit() + 9/40 + + The argument evaluate=False prevents an attempt at evaluation + of the sum for general n, before the argument 2 is passed. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Uniform_sum_distribution + .. [2] https://mathworld.wolfram.com/UniformSumDistribution.html + + """ + + return rv(name, UniformSumDistribution, (n, )) + +#------------------------------------------------------------------------------- +# VonMises distribution -------------------------------------------------------- + + +class VonMisesDistribution(SingleContinuousDistribution): + _argnames = ('mu', 'k') + + set = Interval(0, 2*pi) + + @staticmethod + def check(mu, k): + _value_check(k > 0, "k must be positive") + + def pdf(self, x): + mu, k = self.mu, self.k + return exp(k*cos(x-mu)) / (2*pi*besseli(0, k)) + +def VonMises(name, mu, k): + r""" + Create a Continuous Random Variable with a von Mises distribution. + + Explanation + =========== + + The density of the von Mises distribution is given by + + .. math:: + f(x) := \frac{e^{\kappa\cos(x-\mu)}}{2\pi I_0(\kappa)} + + with :math:`x \in [0,2\pi]`. + + Parameters + ========== + + mu : Real number + Measure of location. + k : Real number + Measure of concentration. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import VonMises, density + >>> from sympy import Symbol, pprint + + >>> mu = Symbol("mu") + >>> k = Symbol("k", positive=True) + >>> z = Symbol("z") + + >>> X = VonMises("x", mu, k) + + >>> D = density(X)(z) + >>> pprint(D, use_unicode=False) + k*cos(mu - z) + e + ------------------ + 2*pi*besseli(0, k) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Von_Mises_distribution + .. [2] https://mathworld.wolfram.com/vonMisesDistribution.html + + """ + + return rv(name, VonMisesDistribution, (mu, k)) + +#------------------------------------------------------------------------------- +# Weibull distribution --------------------------------------------------------- + + +class WeibullDistribution(SingleContinuousDistribution): + _argnames = ('alpha', 'beta') + + set = Interval(0, oo) + + @staticmethod + def check(alpha, beta): + _value_check(alpha > 0, "Alpha must be positive") + _value_check(beta > 0, "Beta must be positive") + + def pdf(self, x): + alpha, beta = self.alpha, self.beta + return beta * (x/alpha)**(beta - 1) * exp(-(x/alpha)**beta) / alpha + + +def Weibull(name, alpha, beta): + r""" + Create a continuous random variable with a Weibull distribution. + + Explanation + =========== + + The density of the Weibull distribution is given by + + .. math:: + f(x) := \begin{cases} + \frac{k}{\lambda}\left(\frac{x}{\lambda}\right)^{k-1} + e^{-(x/\lambda)^{k}} & x\geq0\\ + 0 & x<0 + \end{cases} + + Parameters + ========== + + lambda : Real number, $\lambda > 0$, a scale + k : Real number, $k > 0$, a shape + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Weibull, density, E, variance + >>> from sympy import Symbol, simplify + + >>> l = Symbol("lambda", positive=True) + >>> k = Symbol("k", positive=True) + >>> z = Symbol("z") + + >>> X = Weibull("x", l, k) + + >>> density(X)(z) + k*(z/lambda)**(k - 1)*exp(-(z/lambda)**k)/lambda + + >>> simplify(E(X)) + lambda*gamma(1 + 1/k) + + >>> simplify(variance(X)) + lambda**2*(-gamma(1 + 1/k)**2 + gamma(1 + 2/k)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Weibull_distribution + .. [2] https://mathworld.wolfram.com/WeibullDistribution.html + + """ + + return rv(name, WeibullDistribution, (alpha, beta)) + +#------------------------------------------------------------------------------- +# Wigner semicircle distribution ----------------------------------------------- + + +class WignerSemicircleDistribution(SingleContinuousDistribution): + _argnames = ('R',) + + @property + def set(self): + return Interval(-self.R, self.R) + + @staticmethod + def check(R): + _value_check(R > 0, "Radius R must be positive.") + + def pdf(self, x): + R = self.R + return 2/(pi*R**2)*sqrt(R**2 - x**2) + + def _characteristic_function(self, t): + return Piecewise((2 * besselj(1, self.R*t) / (self.R*t), Ne(t, 0)), + (S.One, True)) + + def _moment_generating_function(self, t): + return Piecewise((2 * besseli(1, self.R*t) / (self.R*t), Ne(t, 0)), + (S.One, True)) + +def WignerSemicircle(name, R): + r""" + Create a continuous random variable with a Wigner semicircle distribution. + + Explanation + =========== + + The density of the Wigner semicircle distribution is given by + + .. math:: + f(x) := \frac2{\pi R^2}\,\sqrt{R^2-x^2} + + with :math:`x \in [-R,R]`. + + Parameters + ========== + + R : Real number, `R > 0`, the radius + + Returns + ======= + + A RandomSymbol. + + Examples + ======== + + >>> from sympy.stats import WignerSemicircle, density, E + >>> from sympy import Symbol + + >>> R = Symbol("R", positive=True) + >>> z = Symbol("z") + + >>> X = WignerSemicircle("x", R) + + >>> density(X)(z) + 2*sqrt(R**2 - z**2)/(pi*R**2) + + >>> E(X) + 0 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Wigner_semicircle_distribution + .. [2] https://mathworld.wolfram.com/WignersSemicircleLaw.html + + """ + + return rv(name, WignerSemicircleDistribution, (R,)) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/drv.py b/venv/lib/python3.10/site-packages/sympy/stats/drv.py new file mode 100644 index 0000000000000000000000000000000000000000..dea14f2dfd1078c223c61bb5cd1373105e72ea28 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/drv.py @@ -0,0 +1,350 @@ +from sympy.concrete.summations import (Sum, summation) +from sympy.core.basic import Basic +from sympy.core.cache import cacheit +from sympy.core.function import Lambda +from sympy.core.numbers import I +from sympy.core.relational import (Eq, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.piecewise import Piecewise +from sympy.logic.boolalg import And +from sympy.polys.polytools import poly +from sympy.series.series import series + +from sympy.polys.polyerrors import PolynomialError +from sympy.stats.crv import reduce_rational_inequalities_wrap +from sympy.stats.rv import (NamedArgsMixin, SinglePSpace, SingleDomain, + random_symbols, PSpace, ConditionalDomain, RandomDomain, + ProductDomain, Distribution) +from sympy.stats.symbolic_probability import Probability +from sympy.sets.fancysets import Range, FiniteSet +from sympy.sets.sets import Union +from sympy.sets.contains import Contains +from sympy.utilities import filldedent +from sympy.core.sympify import _sympify + + +class DiscreteDistribution(Distribution): + def __call__(self, *args): + return self.pdf(*args) + + +class SingleDiscreteDistribution(DiscreteDistribution, NamedArgsMixin): + """ Discrete distribution of a single variable. + + Serves as superclass for PoissonDistribution etc.... + + Provides methods for pdf, cdf, and sampling + + See Also: + sympy.stats.crv_types.* + """ + + set = S.Integers + + def __new__(cls, *args): + args = list(map(sympify, args)) + return Basic.__new__(cls, *args) + + @staticmethod + def check(*args): + pass + + @cacheit + def compute_cdf(self, **kwargs): + """ Compute the CDF from the PDF. + + Returns a Lambda. + """ + x = symbols('x', integer=True, cls=Dummy) + z = symbols('z', real=True, cls=Dummy) + left_bound = self.set.inf + + # CDF is integral of PDF from left bound to z + pdf = self.pdf(x) + cdf = summation(pdf, (x, left_bound, floor(z)), **kwargs) + # CDF Ensure that CDF left of left_bound is zero + cdf = Piecewise((cdf, z >= left_bound), (0, True)) + return Lambda(z, cdf) + + def _cdf(self, x): + return None + + def cdf(self, x, **kwargs): + """ Cumulative density function """ + if not kwargs: + cdf = self._cdf(x) + if cdf is not None: + return cdf + return self.compute_cdf(**kwargs)(x) + + @cacheit + def compute_characteristic_function(self, **kwargs): + """ Compute the characteristic function from the PDF. + + Returns a Lambda. + """ + x, t = symbols('x, t', real=True, cls=Dummy) + pdf = self.pdf(x) + cf = summation(exp(I*t*x)*pdf, (x, self.set.inf, self.set.sup)) + return Lambda(t, cf) + + def _characteristic_function(self, t): + return None + + def characteristic_function(self, t, **kwargs): + """ Characteristic function """ + if not kwargs: + cf = self._characteristic_function(t) + if cf is not None: + return cf + return self.compute_characteristic_function(**kwargs)(t) + + @cacheit + def compute_moment_generating_function(self, **kwargs): + t = Dummy('t', real=True) + x = Dummy('x', integer=True) + pdf = self.pdf(x) + mgf = summation(exp(t*x)*pdf, (x, self.set.inf, self.set.sup)) + return Lambda(t, mgf) + + def _moment_generating_function(self, t): + return None + + def moment_generating_function(self, t, **kwargs): + if not kwargs: + mgf = self._moment_generating_function(t) + if mgf is not None: + return mgf + return self.compute_moment_generating_function(**kwargs)(t) + + @cacheit + def compute_quantile(self, **kwargs): + """ Compute the Quantile from the PDF. + + Returns a Lambda. + """ + x = Dummy('x', integer=True) + p = Dummy('p', real=True) + left_bound = self.set.inf + pdf = self.pdf(x) + cdf = summation(pdf, (x, left_bound, x), **kwargs) + set = ((x, p <= cdf), ) + return Lambda(p, Piecewise(*set)) + + def _quantile(self, x): + return None + + def quantile(self, x, **kwargs): + """ Cumulative density function """ + if not kwargs: + quantile = self._quantile(x) + if quantile is not None: + return quantile + return self.compute_quantile(**kwargs)(x) + + def expectation(self, expr, var, evaluate=True, **kwargs): + """ Expectation of expression over distribution """ + # TODO: support discrete sets with non integer stepsizes + + if evaluate: + try: + p = poly(expr, var) + + t = Dummy('t', real=True) + + mgf = self.moment_generating_function(t) + deg = p.degree() + taylor = poly(series(mgf, t, 0, deg + 1).removeO(), t) + result = 0 + for k in range(deg+1): + result += p.coeff_monomial(var ** k) * taylor.coeff_monomial(t ** k) * factorial(k) + + return result + + except PolynomialError: + return summation(expr * self.pdf(var), + (var, self.set.inf, self.set.sup), **kwargs) + + else: + return Sum(expr * self.pdf(var), + (var, self.set.inf, self.set.sup), **kwargs) + + def __call__(self, *args): + return self.pdf(*args) + + +class DiscreteDomain(RandomDomain): + """ + A domain with discrete support with step size one. + Represented using symbols and Range. + """ + is_Discrete = True + +class SingleDiscreteDomain(DiscreteDomain, SingleDomain): + def as_boolean(self): + return Contains(self.symbol, self.set) + + +class ConditionalDiscreteDomain(DiscreteDomain, ConditionalDomain): + """ + Domain with discrete support of step size one, that is restricted by + some condition. + """ + @property + def set(self): + rv = self.symbols + if len(self.symbols) > 1: + raise NotImplementedError(filldedent(''' + Multivariate conditional domains are not yet implemented.''')) + rv = list(rv)[0] + return reduce_rational_inequalities_wrap(self.condition, + rv).intersect(self.fulldomain.set) + + +class DiscretePSpace(PSpace): + is_real = True + is_Discrete = True + + @property + def pdf(self): + return self.density(*self.symbols) + + def where(self, condition): + rvs = random_symbols(condition) + assert all(r.symbol in self.symbols for r in rvs) + if len(rvs) > 1: + raise NotImplementedError(filldedent('''Multivariate discrete + random variables are not yet supported.''')) + conditional_domain = reduce_rational_inequalities_wrap(condition, + rvs[0]) + conditional_domain = conditional_domain.intersect(self.domain.set) + return SingleDiscreteDomain(rvs[0].symbol, conditional_domain) + + def probability(self, condition): + complement = isinstance(condition, Ne) + if complement: + condition = Eq(condition.args[0], condition.args[1]) + try: + _domain = self.where(condition).set + if condition == False or _domain is S.EmptySet: + return S.Zero + if condition == True or _domain == self.domain.set: + return S.One + prob = self.eval_prob(_domain) + except NotImplementedError: + from sympy.stats.rv import density + expr = condition.lhs - condition.rhs + dens = density(expr) + if not isinstance(dens, DiscreteDistribution): + from sympy.stats.drv_types import DiscreteDistributionHandmade + dens = DiscreteDistributionHandmade(dens) + z = Dummy('z', real=True) + space = SingleDiscretePSpace(z, dens) + prob = space.probability(condition.__class__(space.value, 0)) + if prob is None: + prob = Probability(condition) + return prob if not complement else S.One - prob + + def eval_prob(self, _domain): + sym = list(self.symbols)[0] + if isinstance(_domain, Range): + n = symbols('n', integer=True) + inf, sup, step = (r for r in _domain.args) + summand = ((self.pdf).replace( + sym, n*step)) + rv = summation(summand, + (n, inf/step, (sup)/step - 1)).doit() + return rv + elif isinstance(_domain, FiniteSet): + pdf = Lambda(sym, self.pdf) + rv = sum(pdf(x) for x in _domain) + return rv + elif isinstance(_domain, Union): + rv = sum(self.eval_prob(x) for x in _domain.args) + return rv + + def conditional_space(self, condition): + # XXX: Converting from set to tuple. The order matters to Lambda + # though so we should be starting with a set... + density = Lambda(tuple(self.symbols), self.pdf/self.probability(condition)) + condition = condition.xreplace({rv: rv.symbol for rv in self.values}) + domain = ConditionalDiscreteDomain(self.domain, condition) + return DiscretePSpace(domain, density) + +class ProductDiscreteDomain(ProductDomain, DiscreteDomain): + def as_boolean(self): + return And(*[domain.as_boolean for domain in self.domains]) + +class SingleDiscretePSpace(DiscretePSpace, SinglePSpace): + """ Discrete probability space over a single univariate variable """ + is_real = True + + @property + def set(self): + return self.distribution.set + + @property + def domain(self): + return SingleDiscreteDomain(self.symbol, self.set) + + def sample(self, size=(), library='scipy', seed=None): + """ + Internal sample method. + + Returns dictionary mapping RandomSymbol to realization value. + """ + return {self.value: self.distribution.sample(size, library=library, seed=seed)} + + def compute_expectation(self, expr, rvs=None, evaluate=True, **kwargs): + rvs = rvs or (self.value,) + if self.value not in rvs: + return expr + + expr = _sympify(expr) + expr = expr.xreplace({rv: rv.symbol for rv in rvs}) + + x = self.value.symbol + try: + return self.distribution.expectation(expr, x, evaluate=evaluate, + **kwargs) + except NotImplementedError: + return Sum(expr * self.pdf, (x, self.set.inf, self.set.sup), + **kwargs) + + def compute_cdf(self, expr, **kwargs): + if expr == self.value: + x = Dummy("x", real=True) + return Lambda(x, self.distribution.cdf(x, **kwargs)) + else: + raise NotImplementedError() + + def compute_density(self, expr, **kwargs): + if expr == self.value: + return self.distribution + raise NotImplementedError() + + def compute_characteristic_function(self, expr, **kwargs): + if expr == self.value: + t = Dummy("t", real=True) + return Lambda(t, self.distribution.characteristic_function(t, **kwargs)) + else: + raise NotImplementedError() + + def compute_moment_generating_function(self, expr, **kwargs): + if expr == self.value: + t = Dummy("t", real=True) + return Lambda(t, self.distribution.moment_generating_function(t, **kwargs)) + else: + raise NotImplementedError() + + def compute_quantile(self, expr, **kwargs): + if expr == self.value: + p = Dummy("p", real=True) + return Lambda(p, self.distribution.quantile(p, **kwargs)) + else: + raise NotImplementedError() diff --git a/venv/lib/python3.10/site-packages/sympy/stats/drv_types.py b/venv/lib/python3.10/site-packages/sympy/stats/drv_types.py new file mode 100644 index 0000000000000000000000000000000000000000..84920d31c0083828efc2cd3f752d2c48f5430102 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/drv_types.py @@ -0,0 +1,849 @@ +""" + +Contains +======== +FlorySchulz +Geometric +Hermite +Logarithmic +NegativeBinomial +Poisson +Skellam +YuleSimon +Zeta +""" + + + +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.function import Lambda +from sympy.core.numbers import I +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import (binomial, factorial, FallingFactorial) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.bessel import besseli +from sympy.functions.special.beta_functions import beta +from sympy.functions.special.hyper import hyper +from sympy.functions.special.zeta_functions import (polylog, zeta) +from sympy.stats.drv import SingleDiscreteDistribution, SingleDiscretePSpace +from sympy.stats.rv import _value_check, is_random + + +__all__ = ['FlorySchulz', +'Geometric', +'Hermite', +'Logarithmic', +'NegativeBinomial', +'Poisson', +'Skellam', +'YuleSimon', +'Zeta' +] + + +def rv(symbol, cls, *args, **kwargs): + args = list(map(sympify, args)) + dist = cls(*args) + if kwargs.pop('check', True): + dist.check(*args) + pspace = SingleDiscretePSpace(symbol, dist) + if any(is_random(arg) for arg in args): + from sympy.stats.compound_rv import CompoundPSpace, CompoundDistribution + pspace = CompoundPSpace(symbol, CompoundDistribution(dist)) + return pspace.value + + +class DiscreteDistributionHandmade(SingleDiscreteDistribution): + _argnames = ('pdf',) + + def __new__(cls, pdf, set=S.Integers): + return Basic.__new__(cls, pdf, set) + + @property + def set(self): + return self.args[1] + + @staticmethod + def check(pdf, set): + x = Dummy('x') + val = Sum(pdf(x), (x, set._inf, set._sup)).doit() + _value_check(Eq(val, 1) != S.false, "The pdf is incorrect on the given set.") + + + +def DiscreteRV(symbol, density, set=S.Integers, **kwargs): + """ + Create a Discrete Random Variable given the following: + + Parameters + ========== + + symbol : Symbol + Represents name of the random variable. + density : Expression containing symbol + Represents probability density function. + set : set + Represents the region where the pdf is valid, by default is real line. + check : bool + If True, it will check whether the given density + integrates to 1 over the given set. If False, it + will not perform this check. Default is False. + + Examples + ======== + + >>> from sympy.stats import DiscreteRV, P, E + >>> from sympy import Rational, Symbol + >>> x = Symbol('x') + >>> n = 10 + >>> density = Rational(1, 10) + >>> X = DiscreteRV(x, density, set=set(range(n))) + >>> E(X) + 9/2 + >>> P(X>3) + 3/5 + + Returns + ======= + + RandomSymbol + + """ + set = sympify(set) + pdf = Piecewise((density, set.as_relational(symbol)), (0, True)) + pdf = Lambda(symbol, pdf) + # have a default of False while `rv` should have a default of True + kwargs['check'] = kwargs.pop('check', False) + return rv(symbol.name, DiscreteDistributionHandmade, pdf, set, **kwargs) + + +#------------------------------------------------------------------------------- +# Flory-Schulz distribution ------------------------------------------------------------ + +class FlorySchulzDistribution(SingleDiscreteDistribution): + _argnames = ('a',) + set = S.Naturals + + @staticmethod + def check(a): + _value_check((0 < a, a < 1), "a must be between 0 and 1") + + def pdf(self, k): + a = self.a + return (a**2 * k * (1 - a)**(k - 1)) + + def _characteristic_function(self, t): + a = self.a + return a**2*exp(I*t)/((1 + (a - 1)*exp(I*t))**2) + + def _moment_generating_function(self, t): + a = self.a + return a**2*exp(t)/((1 + (a - 1)*exp(t))**2) + + +def FlorySchulz(name, a): + r""" + Create a discrete random variable with a FlorySchulz distribution. + + The density of the FlorySchulz distribution is given by + + .. math:: + f(k) := (a^2) k (1 - a)^{k-1} + + Parameters + ========== + + a : A real number between 0 and 1 + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density, E, variance, FlorySchulz + >>> from sympy import Symbol, S + + >>> a = S.One / 5 + >>> z = Symbol("z") + + >>> X = FlorySchulz("x", a) + + >>> density(X)(z) + (4/5)**(z - 1)*z/25 + + >>> E(X) + 9 + + >>> variance(X) + 40 + + References + ========== + + https://en.wikipedia.org/wiki/Flory%E2%80%93Schulz_distribution + """ + return rv(name, FlorySchulzDistribution, a) + + +#------------------------------------------------------------------------------- +# Geometric distribution ------------------------------------------------------------ + +class GeometricDistribution(SingleDiscreteDistribution): + _argnames = ('p',) + set = S.Naturals + + @staticmethod + def check(p): + _value_check((0 < p, p <= 1), "p must be between 0 and 1") + + def pdf(self, k): + return (1 - self.p)**(k - 1) * self.p + + def _characteristic_function(self, t): + p = self.p + return p * exp(I*t) / (1 - (1 - p)*exp(I*t)) + + def _moment_generating_function(self, t): + p = self.p + return p * exp(t) / (1 - (1 - p) * exp(t)) + + +def Geometric(name, p): + r""" + Create a discrete random variable with a Geometric distribution. + + Explanation + =========== + + The density of the Geometric distribution is given by + + .. math:: + f(k) := p (1 - p)^{k - 1} + + Parameters + ========== + + p : A probability between 0 and 1 + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Geometric, density, E, variance + >>> from sympy import Symbol, S + + >>> p = S.One / 5 + >>> z = Symbol("z") + + >>> X = Geometric("x", p) + + >>> density(X)(z) + (4/5)**(z - 1)/5 + + >>> E(X) + 5 + + >>> variance(X) + 20 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Geometric_distribution + .. [2] https://mathworld.wolfram.com/GeometricDistribution.html + + """ + return rv(name, GeometricDistribution, p) + + +#------------------------------------------------------------------------------- +# Hermite distribution --------------------------------------------------------- + + +class HermiteDistribution(SingleDiscreteDistribution): + _argnames = ('a1', 'a2') + set = S.Naturals0 + + @staticmethod + def check(a1, a2): + _value_check(a1.is_nonnegative, 'Parameter a1 must be >= 0.') + _value_check(a2.is_nonnegative, 'Parameter a2 must be >= 0.') + + def pdf(self, k): + a1, a2 = self.a1, self.a2 + term1 = exp(-(a1 + a2)) + j = Dummy("j", integer=True) + num = a1**(k - 2*j) * a2**j + den = factorial(k - 2*j) * factorial(j) + return term1 * Sum(num/den, (j, 0, k//2)).doit() + + def _moment_generating_function(self, t): + a1, a2 = self.a1, self.a2 + term1 = a1 * (exp(t) - 1) + term2 = a2 * (exp(2*t) - 1) + return exp(term1 + term2) + + def _characteristic_function(self, t): + a1, a2 = self.a1, self.a2 + term1 = a1 * (exp(I*t) - 1) + term2 = a2 * (exp(2*I*t) - 1) + return exp(term1 + term2) + +def Hermite(name, a1, a2): + r""" + Create a discrete random variable with a Hermite distribution. + + Explanation + =========== + + The density of the Hermite distribution is given by + + .. math:: + f(x):= e^{-a_1 -a_2}\sum_{j=0}^{\left \lfloor x/2 \right \rfloor} + \frac{a_{1}^{x-2j}a_{2}^{j}}{(x-2j)!j!} + + Parameters + ========== + + a1 : A Positive number greater than equal to 0. + a2 : A Positive number greater than equal to 0. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Hermite, density, E, variance + >>> from sympy import Symbol + + >>> a1 = Symbol("a1", positive=True) + >>> a2 = Symbol("a2", positive=True) + >>> x = Symbol("x") + + >>> H = Hermite("H", a1=5, a2=4) + + >>> density(H)(2) + 33*exp(-9)/2 + + >>> E(H) + 13 + + >>> variance(H) + 21 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hermite_distribution + + """ + + return rv(name, HermiteDistribution, a1, a2) + + +#------------------------------------------------------------------------------- +# Logarithmic distribution ------------------------------------------------------------ + +class LogarithmicDistribution(SingleDiscreteDistribution): + _argnames = ('p',) + + set = S.Naturals + + @staticmethod + def check(p): + _value_check((p > 0, p < 1), "p should be between 0 and 1") + + def pdf(self, k): + p = self.p + return (-1) * p**k / (k * log(1 - p)) + + def _characteristic_function(self, t): + p = self.p + return log(1 - p * exp(I*t)) / log(1 - p) + + def _moment_generating_function(self, t): + p = self.p + return log(1 - p * exp(t)) / log(1 - p) + + +def Logarithmic(name, p): + r""" + Create a discrete random variable with a Logarithmic distribution. + + Explanation + =========== + + The density of the Logarithmic distribution is given by + + .. math:: + f(k) := \frac{-p^k}{k \ln{(1 - p)}} + + Parameters + ========== + + p : A value between 0 and 1 + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Logarithmic, density, E, variance + >>> from sympy import Symbol, S + + >>> p = S.One / 5 + >>> z = Symbol("z") + + >>> X = Logarithmic("x", p) + + >>> density(X)(z) + -1/(5**z*z*log(4/5)) + + >>> E(X) + -1/(-4*log(5) + 8*log(2)) + + >>> variance(X) + -1/((-4*log(5) + 8*log(2))*(-2*log(5) + 4*log(2))) + 1/(-64*log(2)*log(5) + 64*log(2)**2 + 16*log(5)**2) - 10/(-32*log(5) + 64*log(2)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Logarithmic_distribution + .. [2] https://mathworld.wolfram.com/LogarithmicDistribution.html + + """ + return rv(name, LogarithmicDistribution, p) + + +#------------------------------------------------------------------------------- +# Negative binomial distribution ------------------------------------------------------------ + +class NegativeBinomialDistribution(SingleDiscreteDistribution): + _argnames = ('r', 'p') + set = S.Naturals0 + + @staticmethod + def check(r, p): + _value_check(r > 0, 'r should be positive') + _value_check((p > 0, p < 1), 'p should be between 0 and 1') + + def pdf(self, k): + r = self.r + p = self.p + + return binomial(k + r - 1, k) * (1 - p)**k * p**r + + def _characteristic_function(self, t): + r = self.r + p = self.p + + return (p / (1 - (1 - p) * exp(I*t)))**r + + def _moment_generating_function(self, t): + r = self.r + p = self.p + + return (p / (1 - (1 - p) * exp(t)))**r + +def NegativeBinomial(name, r, p): + r""" + Create a discrete random variable with a Negative Binomial distribution. + + Explanation + =========== + + The density of the Negative Binomial distribution is given by + + .. math:: + f(k) := \binom{k + r - 1}{k} (1 - p)^k p^r + + Parameters + ========== + + r : A positive value + Number of successes until the experiment is stopped. + p : A value between 0 and 1 + Probability of success. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import NegativeBinomial, density, E, variance + >>> from sympy import Symbol, S + + >>> r = 5 + >>> p = S.One / 3 + >>> z = Symbol("z") + + >>> X = NegativeBinomial("x", r, p) + + >>> density(X)(z) + (2/3)**z*binomial(z + 4, z)/243 + + >>> E(X) + 10 + + >>> variance(X) + 30 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Negative_binomial_distribution + .. [2] https://mathworld.wolfram.com/NegativeBinomialDistribution.html + + """ + return rv(name, NegativeBinomialDistribution, r, p) + + +#------------------------------------------------------------------------------- +# Poisson distribution ------------------------------------------------------------ + +class PoissonDistribution(SingleDiscreteDistribution): + _argnames = ('lamda',) + + set = S.Naturals0 + + @staticmethod + def check(lamda): + _value_check(lamda > 0, "Lambda must be positive") + + def pdf(self, k): + return self.lamda**k / factorial(k) * exp(-self.lamda) + + def _characteristic_function(self, t): + return exp(self.lamda * (exp(I*t) - 1)) + + def _moment_generating_function(self, t): + return exp(self.lamda * (exp(t) - 1)) + + def expectation(self, expr, var, evaluate=True, **kwargs): + if evaluate: + if expr == var: + return self.lamda + if ( + isinstance(expr, FallingFactorial) + and expr.args[1].is_integer + and expr.args[1].is_positive + and expr.args[0] == var + ): + return self.lamda ** expr.args[1] + return super().expectation(expr, var, evaluate, **kwargs) + +def Poisson(name, lamda): + r""" + Create a discrete random variable with a Poisson distribution. + + Explanation + =========== + + The density of the Poisson distribution is given by + + .. math:: + f(k) := \frac{\lambda^{k} e^{- \lambda}}{k!} + + Parameters + ========== + + lamda : Positive number, a rate + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Poisson, density, E, variance + >>> from sympy import Symbol, simplify + + >>> rate = Symbol("lambda", positive=True) + >>> z = Symbol("z") + + >>> X = Poisson("x", rate) + + >>> density(X)(z) + lambda**z*exp(-lambda)/factorial(z) + + >>> E(X) + lambda + + >>> simplify(variance(X)) + lambda + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Poisson_distribution + .. [2] https://mathworld.wolfram.com/PoissonDistribution.html + + """ + return rv(name, PoissonDistribution, lamda) + + +# ----------------------------------------------------------------------------- +# Skellam distribution -------------------------------------------------------- + + +class SkellamDistribution(SingleDiscreteDistribution): + _argnames = ('mu1', 'mu2') + set = S.Integers + + @staticmethod + def check(mu1, mu2): + _value_check(mu1 >= 0, 'Parameter mu1 must be >= 0') + _value_check(mu2 >= 0, 'Parameter mu2 must be >= 0') + + def pdf(self, k): + (mu1, mu2) = (self.mu1, self.mu2) + term1 = exp(-(mu1 + mu2)) * (mu1 / mu2) ** (k / 2) + term2 = besseli(k, 2 * sqrt(mu1 * mu2)) + return term1 * term2 + + def _cdf(self, x): + raise NotImplementedError( + "Skellam doesn't have closed form for the CDF.") + + def _characteristic_function(self, t): + (mu1, mu2) = (self.mu1, self.mu2) + return exp(-(mu1 + mu2) + mu1 * exp(I * t) + mu2 * exp(-I * t)) + + def _moment_generating_function(self, t): + (mu1, mu2) = (self.mu1, self.mu2) + return exp(-(mu1 + mu2) + mu1 * exp(t) + mu2 * exp(-t)) + + +def Skellam(name, mu1, mu2): + r""" + Create a discrete random variable with a Skellam distribution. + + Explanation + =========== + + The Skellam is the distribution of the difference N1 - N2 + of two statistically independent random variables N1 and N2 + each Poisson-distributed with respective expected values mu1 and mu2. + + The density of the Skellam distribution is given by + + .. math:: + f(k) := e^{-(\mu_1+\mu_2)}(\frac{\mu_1}{\mu_2})^{k/2}I_k(2\sqrt{\mu_1\mu_2}) + + Parameters + ========== + + mu1 : A non-negative value + mu2 : A non-negative value + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Skellam, density, E, variance + >>> from sympy import Symbol, pprint + + >>> z = Symbol("z", integer=True) + >>> mu1 = Symbol("mu1", positive=True) + >>> mu2 = Symbol("mu2", positive=True) + >>> X = Skellam("x", mu1, mu2) + + >>> pprint(density(X)(z), use_unicode=False) + z + - + 2 + /mu1\ -mu1 - mu2 / _____ _____\ + |---| *e *besseli\z, 2*\/ mu1 *\/ mu2 / + \mu2/ + >>> E(X) + mu1 - mu2 + >>> variance(X).expand() + mu1 + mu2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Skellam_distribution + + """ + return rv(name, SkellamDistribution, mu1, mu2) + + +#------------------------------------------------------------------------------- +# Yule-Simon distribution ------------------------------------------------------------ + +class YuleSimonDistribution(SingleDiscreteDistribution): + _argnames = ('rho',) + set = S.Naturals + + @staticmethod + def check(rho): + _value_check(rho > 0, 'rho should be positive') + + def pdf(self, k): + rho = self.rho + return rho * beta(k, rho + 1) + + def _cdf(self, x): + return Piecewise((1 - floor(x) * beta(floor(x), self.rho + 1), x >= 1), (0, True)) + + def _characteristic_function(self, t): + rho = self.rho + return rho * hyper((1, 1), (rho + 2,), exp(I*t)) * exp(I*t) / (rho + 1) + + def _moment_generating_function(self, t): + rho = self.rho + return rho * hyper((1, 1), (rho + 2,), exp(t)) * exp(t) / (rho + 1) + + +def YuleSimon(name, rho): + r""" + Create a discrete random variable with a Yule-Simon distribution. + + Explanation + =========== + + The density of the Yule-Simon distribution is given by + + .. math:: + f(k) := \rho B(k, \rho + 1) + + Parameters + ========== + + rho : A positive value + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import YuleSimon, density, E, variance + >>> from sympy import Symbol, simplify + + >>> p = 5 + >>> z = Symbol("z") + + >>> X = YuleSimon("x", p) + + >>> density(X)(z) + 5*beta(z, 6) + + >>> simplify(E(X)) + 5/4 + + >>> simplify(variance(X)) + 25/48 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Yule%E2%80%93Simon_distribution + + """ + return rv(name, YuleSimonDistribution, rho) + + +#------------------------------------------------------------------------------- +# Zeta distribution ------------------------------------------------------------ + +class ZetaDistribution(SingleDiscreteDistribution): + _argnames = ('s',) + set = S.Naturals + + @staticmethod + def check(s): + _value_check(s > 1, 's should be greater than 1') + + def pdf(self, k): + s = self.s + return 1 / (k**s * zeta(s)) + + def _characteristic_function(self, t): + return polylog(self.s, exp(I*t)) / zeta(self.s) + + def _moment_generating_function(self, t): + return polylog(self.s, exp(t)) / zeta(self.s) + + +def Zeta(name, s): + r""" + Create a discrete random variable with a Zeta distribution. + + Explanation + =========== + + The density of the Zeta distribution is given by + + .. math:: + f(k) := \frac{1}{k^s \zeta{(s)}} + + Parameters + ========== + + s : A value greater than 1 + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import Zeta, density, E, variance + >>> from sympy import Symbol + + >>> s = 5 + >>> z = Symbol("z") + + >>> X = Zeta("x", s) + + >>> density(X)(z) + 1/(z**5*zeta(5)) + + >>> E(X) + pi**4/(90*zeta(5)) + + >>> variance(X) + -pi**8/(8100*zeta(5)**2) + zeta(3)/zeta(5) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Zeta_distribution + + """ + return rv(name, ZetaDistribution, s) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/error_prop.py b/venv/lib/python3.10/site-packages/sympy/stats/error_prop.py new file mode 100644 index 0000000000000000000000000000000000000000..e6cacb894307fe60cbf096c7760e6ed57f385a91 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/error_prop.py @@ -0,0 +1,100 @@ +"""Tools for arithmetic error propagation.""" + +from itertools import repeat, combinations + +from sympy.core.add import Add +from sympy.core.mul import Mul +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.exponential import exp +from sympy.simplify.simplify import simplify +from sympy.stats.symbolic_probability import RandomSymbol, Variance, Covariance +from sympy.stats.rv import is_random + +_arg0_or_var = lambda var: var.args[0] if len(var.args) > 0 else var + + +def variance_prop(expr, consts=(), include_covar=False): + r"""Symbolically propagates variance (`\sigma^2`) for expressions. + This is computed as as seen in [1]_. + + Parameters + ========== + + expr : Expr + A SymPy expression to compute the variance for. + consts : sequence of Symbols, optional + Represents symbols that are known constants in the expr, + and thus have zero variance. All symbols not in consts are + assumed to be variant. + include_covar : bool, optional + Flag for whether or not to include covariances, default=False. + + Returns + ======= + + var_expr : Expr + An expression for the total variance of the expr. + The variance for the original symbols (e.g. x) are represented + via instance of the Variance symbol (e.g. Variance(x)). + + Examples + ======== + + >>> from sympy import symbols, exp + >>> from sympy.stats.error_prop import variance_prop + >>> x, y = symbols('x y') + + >>> variance_prop(x + y) + Variance(x) + Variance(y) + + >>> variance_prop(x * y) + x**2*Variance(y) + y**2*Variance(x) + + >>> variance_prop(exp(2*x)) + 4*exp(4*x)*Variance(x) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Propagation_of_uncertainty + + """ + args = expr.args + if len(args) == 0: + if expr in consts: + return S.Zero + elif is_random(expr): + return Variance(expr).doit() + elif isinstance(expr, Symbol): + return Variance(RandomSymbol(expr)).doit() + else: + return S.Zero + nargs = len(args) + var_args = list(map(variance_prop, args, repeat(consts, nargs), + repeat(include_covar, nargs))) + if isinstance(expr, Add): + var_expr = Add(*var_args) + if include_covar: + terms = [2 * Covariance(_arg0_or_var(x), _arg0_or_var(y)).expand() \ + for x, y in combinations(var_args, 2)] + var_expr += Add(*terms) + elif isinstance(expr, Mul): + terms = [v/a**2 for a, v in zip(args, var_args)] + var_expr = simplify(expr**2 * Add(*terms)) + if include_covar: + terms = [2*Covariance(_arg0_or_var(x), _arg0_or_var(y)).expand()/(a*b) \ + for (a, b), (x, y) in zip(combinations(args, 2), + combinations(var_args, 2))] + var_expr += Add(*terms) + elif isinstance(expr, Pow): + b = args[1] + v = var_args[0] * (expr * b / args[0])**2 + var_expr = simplify(v) + elif isinstance(expr, exp): + var_expr = simplify(var_args[0] * expr**2) + else: + # unknown how to proceed, return variance of whole expr. + var_expr = Variance(expr) + return var_expr diff --git a/venv/lib/python3.10/site-packages/sympy/stats/frv.py b/venv/lib/python3.10/site-packages/sympy/stats/frv.py new file mode 100644 index 0000000000000000000000000000000000000000..498d7e4006b2b8db306a0905ed67578021e220a8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/frv.py @@ -0,0 +1,512 @@ +""" +Finite Discrete Random Variables Module + +See Also +======== +sympy.stats.frv_types +sympy.stats.rv +sympy.stats.crv +""" +from itertools import product + +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.cache import cacheit +from sympy.core.function import Lambda +from sympy.core.mul import Mul +from sympy.core.numbers import (I, nan) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.core.sympify import sympify +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.piecewise import Piecewise +from sympy.logic.boolalg import (And, Or) +from sympy.sets.sets import Intersection +from sympy.core.containers import Dict +from sympy.core.logic import Logic +from sympy.core.relational import Relational +from sympy.core.sympify import _sympify +from sympy.sets.sets import FiniteSet +from sympy.stats.rv import (RandomDomain, ProductDomain, ConditionalDomain, + PSpace, IndependentProductPSpace, SinglePSpace, random_symbols, + sumsets, rv_subs, NamedArgsMixin, Density, Distribution) + + +class FiniteDensity(dict): + """ + A domain with Finite Density. + """ + def __call__(self, item): + """ + Make instance of a class callable. + + If item belongs to current instance of a class, return it. + + Otherwise, return 0. + """ + item = sympify(item) + if item in self: + return self[item] + else: + return 0 + + @property + def dict(self): + """ + Return item as dictionary. + """ + return dict(self) + +class FiniteDomain(RandomDomain): + """ + A domain with discrete finite support + + Represented using a FiniteSet. + """ + is_Finite = True + + @property + def symbols(self): + return FiniteSet(sym for sym, val in self.elements) + + @property + def elements(self): + return self.args[0] + + @property + def dict(self): + return FiniteSet(*[Dict(dict(el)) for el in self.elements]) + + def __contains__(self, other): + return other in self.elements + + def __iter__(self): + return self.elements.__iter__() + + def as_boolean(self): + return Or(*[And(*[Eq(sym, val) for sym, val in item]) for item in self]) + + +class SingleFiniteDomain(FiniteDomain): + """ + A FiniteDomain over a single symbol/set + + Example: The possibilities of a *single* die roll. + """ + + def __new__(cls, symbol, set): + if not isinstance(set, FiniteSet) and \ + not isinstance(set, Intersection): + set = FiniteSet(*set) + return Basic.__new__(cls, symbol, set) + + @property + def symbol(self): + return self.args[0] + + @property + def symbols(self): + return FiniteSet(self.symbol) + + @property + def set(self): + return self.args[1] + + @property + def elements(self): + return FiniteSet(*[frozenset(((self.symbol, elem), )) for elem in self.set]) + + def __iter__(self): + return (frozenset(((self.symbol, elem),)) for elem in self.set) + + def __contains__(self, other): + sym, val = tuple(other)[0] + return sym == self.symbol and val in self.set + + +class ProductFiniteDomain(ProductDomain, FiniteDomain): + """ + A Finite domain consisting of several other FiniteDomains + + Example: The possibilities of the rolls of three independent dice + """ + + def __iter__(self): + proditer = product(*self.domains) + return (sumsets(items) for items in proditer) + + @property + def elements(self): + return FiniteSet(*self) + + +class ConditionalFiniteDomain(ConditionalDomain, ProductFiniteDomain): + """ + A FiniteDomain that has been restricted by a condition + + Example: The possibilities of a die roll under the condition that the + roll is even. + """ + + def __new__(cls, domain, condition): + """ + Create a new instance of ConditionalFiniteDomain class + """ + if condition is True: + return domain + cond = rv_subs(condition) + return Basic.__new__(cls, domain, cond) + + def _test(self, elem): + """ + Test the value. If value is boolean, return it. If value is equality + relational (two objects are equal), return it with left-hand side + being equal to right-hand side. Otherwise, raise ValueError exception. + """ + val = self.condition.xreplace(dict(elem)) + if val in [True, False]: + return val + elif val.is_Equality: + return val.lhs == val.rhs + raise ValueError("Undecidable if %s" % str(val)) + + def __contains__(self, other): + return other in self.fulldomain and self._test(other) + + def __iter__(self): + return (elem for elem in self.fulldomain if self._test(elem)) + + @property + def set(self): + if isinstance(self.fulldomain, SingleFiniteDomain): + return FiniteSet(*[elem for elem in self.fulldomain.set + if frozenset(((self.fulldomain.symbol, elem),)) in self]) + else: + raise NotImplementedError( + "Not implemented on multi-dimensional conditional domain") + + def as_boolean(self): + return FiniteDomain.as_boolean(self) + + +class SingleFiniteDistribution(Distribution, NamedArgsMixin): + def __new__(cls, *args): + args = list(map(sympify, args)) + return Basic.__new__(cls, *args) + + @staticmethod + def check(*args): + pass + + @property # type: ignore + @cacheit + def dict(self): + if self.is_symbolic: + return Density(self) + return {k: self.pmf(k) for k in self.set} + + def pmf(self, *args): # to be overridden by specific distribution + raise NotImplementedError() + + @property + def set(self): # to be overridden by specific distribution + raise NotImplementedError() + + values = property(lambda self: self.dict.values) + items = property(lambda self: self.dict.items) + is_symbolic = property(lambda self: False) + __iter__ = property(lambda self: self.dict.__iter__) + __getitem__ = property(lambda self: self.dict.__getitem__) + + def __call__(self, *args): + return self.pmf(*args) + + def __contains__(self, other): + return other in self.set + + +#============================================= +#========= Probability Space =============== +#============================================= + + +class FinitePSpace(PSpace): + """ + A Finite Probability Space + + Represents the probabilities of a finite number of events. + """ + is_Finite = True + + def __new__(cls, domain, density): + density = {sympify(key): sympify(val) + for key, val in density.items()} + public_density = Dict(density) + + obj = PSpace.__new__(cls, domain, public_density) + obj._density = density + return obj + + def prob_of(self, elem): + elem = sympify(elem) + density = self._density + if isinstance(list(density.keys())[0], FiniteSet): + return density.get(elem, S.Zero) + return density.get(tuple(elem)[0][1], S.Zero) + + def where(self, condition): + assert all(r.symbol in self.symbols for r in random_symbols(condition)) + return ConditionalFiniteDomain(self.domain, condition) + + def compute_density(self, expr): + expr = rv_subs(expr, self.values) + d = FiniteDensity() + for elem in self.domain: + val = expr.xreplace(dict(elem)) + prob = self.prob_of(elem) + d[val] = d.get(val, S.Zero) + prob + return d + + @cacheit + def compute_cdf(self, expr): + d = self.compute_density(expr) + cum_prob = S.Zero + cdf = [] + for key in sorted(d): + prob = d[key] + cum_prob += prob + cdf.append((key, cum_prob)) + + return dict(cdf) + + @cacheit + def sorted_cdf(self, expr, python_float=False): + cdf = self.compute_cdf(expr) + items = list(cdf.items()) + sorted_items = sorted(items, key=lambda val_cumprob: val_cumprob[1]) + if python_float: + sorted_items = [(v, float(cum_prob)) + for v, cum_prob in sorted_items] + return sorted_items + + @cacheit + def compute_characteristic_function(self, expr): + d = self.compute_density(expr) + t = Dummy('t', real=True) + + return Lambda(t, sum(exp(I*k*t)*v for k,v in d.items())) + + @cacheit + def compute_moment_generating_function(self, expr): + d = self.compute_density(expr) + t = Dummy('t', real=True) + + return Lambda(t, sum(exp(k*t)*v for k,v in d.items())) + + def compute_expectation(self, expr, rvs=None, **kwargs): + rvs = rvs or self.values + expr = rv_subs(expr, rvs) + probs = [self.prob_of(elem) for elem in self.domain] + if isinstance(expr, (Logic, Relational)): + parse_domain = [tuple(elem)[0][1] for elem in self.domain] + bools = [expr.xreplace(dict(elem)) for elem in self.domain] + else: + parse_domain = [expr.xreplace(dict(elem)) for elem in self.domain] + bools = [True for elem in self.domain] + return sum(Piecewise((prob * elem, blv), (S.Zero, True)) + for prob, elem, blv in zip(probs, parse_domain, bools)) + + def compute_quantile(self, expr): + cdf = self.compute_cdf(expr) + p = Dummy('p', real=True) + set = ((nan, (p < 0) | (p > 1)),) + for key, value in cdf.items(): + set = set + ((key, p <= value), ) + return Lambda(p, Piecewise(*set)) + + def probability(self, condition): + cond_symbols = frozenset(rs.symbol for rs in random_symbols(condition)) + cond = rv_subs(condition) + if not cond_symbols.issubset(self.symbols): + raise ValueError("Cannot compare foreign random symbols, %s" + %(str(cond_symbols - self.symbols))) + if isinstance(condition, Relational) and \ + (not cond.free_symbols.issubset(self.domain.free_symbols)): + rv = condition.lhs if isinstance(condition.rhs, Symbol) else condition.rhs + return sum(Piecewise( + (self.prob_of(elem), condition.subs(rv, list(elem)[0][1])), + (S.Zero, True)) for elem in self.domain) + return sympify(sum(self.prob_of(elem) for elem in self.where(condition))) + + def conditional_space(self, condition): + domain = self.where(condition) + prob = self.probability(condition) + density = {key: val / prob + for key, val in self._density.items() if domain._test(key)} + return FinitePSpace(domain, density) + + def sample(self, size=(), library='scipy', seed=None): + """ + Internal sample method + + Returns dictionary mapping RandomSymbol to realization value. + """ + return {self.value: self.distribution.sample(size, library, seed)} + + +class SingleFinitePSpace(SinglePSpace, FinitePSpace): + """ + A single finite probability space + + Represents the probabilities of a set of random events that can be + attributed to a single variable/symbol. + + This class is implemented by many of the standard FiniteRV types such as + Die, Bernoulli, Coin, etc.... + """ + @property + def domain(self): + return SingleFiniteDomain(self.symbol, self.distribution.set) + + @property + def _is_symbolic(self): + """ + Helper property to check if the distribution + of the random variable is having symbolic + dimension. + """ + return self.distribution.is_symbolic + + @property + def distribution(self): + return self.args[1] + + def pmf(self, expr): + return self.distribution.pmf(expr) + + @property # type: ignore + @cacheit + def _density(self): + return {FiniteSet((self.symbol, val)): prob + for val, prob in self.distribution.dict.items()} + + @cacheit + def compute_characteristic_function(self, expr): + if self._is_symbolic: + d = self.compute_density(expr) + t = Dummy('t', real=True) + ki = Dummy('ki') + return Lambda(t, Sum(d(ki)*exp(I*ki*t), (ki, self.args[1].low, self.args[1].high))) + expr = rv_subs(expr, self.values) + return FinitePSpace(self.domain, self.distribution).compute_characteristic_function(expr) + + @cacheit + def compute_moment_generating_function(self, expr): + if self._is_symbolic: + d = self.compute_density(expr) + t = Dummy('t', real=True) + ki = Dummy('ki') + return Lambda(t, Sum(d(ki)*exp(ki*t), (ki, self.args[1].low, self.args[1].high))) + expr = rv_subs(expr, self.values) + return FinitePSpace(self.domain, self.distribution).compute_moment_generating_function(expr) + + def compute_quantile(self, expr): + if self._is_symbolic: + raise NotImplementedError("Computing quantile for random variables " + "with symbolic dimension because the bounds of searching the required " + "value is undetermined.") + expr = rv_subs(expr, self.values) + return FinitePSpace(self.domain, self.distribution).compute_quantile(expr) + + def compute_density(self, expr): + if self._is_symbolic: + rv = list(random_symbols(expr))[0] + k = Dummy('k', integer=True) + cond = True if not isinstance(expr, (Relational, Logic)) \ + else expr.subs(rv, k) + return Lambda(k, + Piecewise((self.pmf(k), And(k >= self.args[1].low, + k <= self.args[1].high, cond)), (S.Zero, True))) + expr = rv_subs(expr, self.values) + return FinitePSpace(self.domain, self.distribution).compute_density(expr) + + def compute_cdf(self, expr): + if self._is_symbolic: + d = self.compute_density(expr) + k = Dummy('k') + ki = Dummy('ki') + return Lambda(k, Sum(d(ki), (ki, self.args[1].low, k))) + expr = rv_subs(expr, self.values) + return FinitePSpace(self.domain, self.distribution).compute_cdf(expr) + + def compute_expectation(self, expr, rvs=None, **kwargs): + if self._is_symbolic: + rv = random_symbols(expr)[0] + k = Dummy('k', integer=True) + expr = expr.subs(rv, k) + cond = True if not isinstance(expr, (Relational, Logic)) \ + else expr + func = self.pmf(k) * k if cond != True else self.pmf(k) * expr + return Sum(Piecewise((func, cond), (S.Zero, True)), + (k, self.distribution.low, self.distribution.high)).doit() + + expr = _sympify(expr) + expr = rv_subs(expr, rvs) + return FinitePSpace(self.domain, self.distribution).compute_expectation(expr, rvs, **kwargs) + + def probability(self, condition): + if self._is_symbolic: + #TODO: Implement the mechanism for handling queries for symbolic sized distributions. + raise NotImplementedError("Currently, probability queries are not " + "supported for random variables with symbolic sized distributions.") + condition = rv_subs(condition) + return FinitePSpace(self.domain, self.distribution).probability(condition) + + def conditional_space(self, condition): + """ + This method is used for transferring the + computation to probability method because + conditional space of random variables with + symbolic dimensions is currently not possible. + """ + if self._is_symbolic: + self + domain = self.where(condition) + prob = self.probability(condition) + density = {key: val / prob + for key, val in self._density.items() if domain._test(key)} + return FinitePSpace(domain, density) + + +class ProductFinitePSpace(IndependentProductPSpace, FinitePSpace): + """ + A collection of several independent finite probability spaces + """ + @property + def domain(self): + return ProductFiniteDomain(*[space.domain for space in self.spaces]) + + @property # type: ignore + @cacheit + def _density(self): + proditer = product(*[iter(space._density.items()) + for space in self.spaces]) + d = {} + for items in proditer: + elems, probs = list(zip(*items)) + elem = sumsets(elems) + prob = Mul(*probs) + d[elem] = d.get(elem, S.Zero) + prob + return Dict(d) + + @property # type: ignore + @cacheit + def density(self): + return Dict(self._density) + + def probability(self, condition): + return FinitePSpace.probability(self, condition) + + def compute_density(self, expr): + return FinitePSpace.compute_density(self, expr) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/frv_types.py b/venv/lib/python3.10/site-packages/sympy/stats/frv_types.py new file mode 100644 index 0000000000000000000000000000000000000000..bde656c219791c287ff445d5d215e3759271e923 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/frv_types.py @@ -0,0 +1,873 @@ +""" +Finite Discrete Random Variables - Prebuilt variable types + +Contains +======== +FiniteRV +DiscreteUniform +Die +Bernoulli +Coin +Binomial +BetaBinomial +Hypergeometric +Rademacher +IdealSoliton +RobustSoliton +""" + + +from sympy.core.cache import cacheit +from sympy.core.function import Lambda +from sympy.core.numbers import (Integer, Rational) +from sympy.core.relational import (Eq, Ge, Gt, Le, Lt) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import binomial +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.piecewise import Piecewise +from sympy.logic.boolalg import Or +from sympy.sets.contains import Contains +from sympy.sets.fancysets import Range +from sympy.sets.sets import (Intersection, Interval) +from sympy.functions.special.beta_functions import beta as beta_fn +from sympy.stats.frv import (SingleFiniteDistribution, + SingleFinitePSpace) +from sympy.stats.rv import _value_check, Density, is_random +from sympy.utilities.iterables import multiset +from sympy.utilities.misc import filldedent + + +__all__ = ['FiniteRV', +'DiscreteUniform', +'Die', +'Bernoulli', +'Coin', +'Binomial', +'BetaBinomial', +'Hypergeometric', +'Rademacher', +'IdealSoliton', +'RobustSoliton', +] + +def rv(name, cls, *args, **kwargs): + args = list(map(sympify, args)) + dist = cls(*args) + if kwargs.pop('check', True): + dist.check(*args) + pspace = SingleFinitePSpace(name, dist) + if any(is_random(arg) for arg in args): + from sympy.stats.compound_rv import CompoundPSpace, CompoundDistribution + pspace = CompoundPSpace(name, CompoundDistribution(dist)) + return pspace.value + +class FiniteDistributionHandmade(SingleFiniteDistribution): + + @property + def dict(self): + return self.args[0] + + def pmf(self, x): + x = Symbol('x') + return Lambda(x, Piecewise(*( + [(v, Eq(k, x)) for k, v in self.dict.items()] + [(S.Zero, True)]))) + + @property + def set(self): + return set(self.dict.keys()) + + @staticmethod + def check(density): + for p in density.values(): + _value_check((p >= 0, p <= 1), + "Probability at a point must be between 0 and 1.") + val = sum(density.values()) + _value_check(Eq(val, 1) != S.false, "Total Probability must be 1.") + +def FiniteRV(name, density, **kwargs): + r""" + Create a Finite Random Variable given a dict representing the density. + + Parameters + ========== + + name : Symbol + Represents name of the random variable. + density : dict + Dictionary containing the pdf of finite distribution + check : bool + If True, it will check whether the given density + integrates to 1 over the given set. If False, it + will not perform this check. Default is False. + + Examples + ======== + + >>> from sympy.stats import FiniteRV, P, E + + >>> density = {0: .1, 1: .2, 2: .3, 3: .4} + >>> X = FiniteRV('X', density) + + >>> E(X) + 2.00000000000000 + >>> P(X >= 2) + 0.700000000000000 + + Returns + ======= + + RandomSymbol + + """ + # have a default of False while `rv` should have a default of True + kwargs['check'] = kwargs.pop('check', False) + return rv(name, FiniteDistributionHandmade, density, **kwargs) + +class DiscreteUniformDistribution(SingleFiniteDistribution): + + @staticmethod + def check(*args): + # not using _value_check since there is a + # suggestion for the user + if len(set(args)) != len(args): + weights = multiset(args) + n = Integer(len(args)) + for k in weights: + weights[k] /= n + raise ValueError(filldedent(""" + Repeated args detected but set expected. For a + distribution having different weights for each + item use the following:""") + ( + '\nS("FiniteRV(%s, %s)")' % ("'X'", weights))) + + @property + def p(self): + return Rational(1, len(self.args)) + + @property # type: ignore + @cacheit + def dict(self): + return dict.fromkeys(self.set, self.p) + + @property + def set(self): + return set(self.args) + + def pmf(self, x): + if x in self.args: + return self.p + else: + return S.Zero + + +def DiscreteUniform(name, items): + r""" + Create a Finite Random Variable representing a uniform distribution over + the input set. + + Parameters + ========== + + items : list/tuple + Items over which Uniform distribution is to be made + + Examples + ======== + + >>> from sympy.stats import DiscreteUniform, density + >>> from sympy import symbols + + >>> X = DiscreteUniform('X', symbols('a b c')) # equally likely over a, b, c + >>> density(X).dict + {a: 1/3, b: 1/3, c: 1/3} + + >>> Y = DiscreteUniform('Y', list(range(5))) # distribution over a range + >>> density(Y).dict + {0: 1/5, 1: 1/5, 2: 1/5, 3: 1/5, 4: 1/5} + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Discrete_uniform_distribution + .. [2] https://mathworld.wolfram.com/DiscreteUniformDistribution.html + + """ + return rv(name, DiscreteUniformDistribution, *items) + + +class DieDistribution(SingleFiniteDistribution): + _argnames = ('sides',) + + @staticmethod + def check(sides): + _value_check((sides.is_positive, sides.is_integer), + "number of sides must be a positive integer.") + + @property + def is_symbolic(self): + return not self.sides.is_number + + @property + def high(self): + return self.sides + + @property + def low(self): + return S.One + + @property + def set(self): + if self.is_symbolic: + return Intersection(S.Naturals0, Interval(0, self.sides)) + return set(map(Integer, range(1, self.sides + 1))) + + def pmf(self, x): + x = sympify(x) + if not (x.is_number or x.is_Symbol or is_random(x)): + raise ValueError("'x' expected as an argument of type 'number', 'Symbol', or " + "'RandomSymbol' not %s" % (type(x))) + cond = Ge(x, 1) & Le(x, self.sides) & Contains(x, S.Integers) + return Piecewise((S.One/self.sides, cond), (S.Zero, True)) + +def Die(name, sides=6): + r""" + Create a Finite Random Variable representing a fair die. + + Parameters + ========== + + sides : Integer + Represents the number of sides of the Die, by default is 6 + + Examples + ======== + + >>> from sympy.stats import Die, density + >>> from sympy import Symbol + + >>> D6 = Die('D6', 6) # Six sided Die + >>> density(D6).dict + {1: 1/6, 2: 1/6, 3: 1/6, 4: 1/6, 5: 1/6, 6: 1/6} + + >>> D4 = Die('D4', 4) # Four sided Die + >>> density(D4).dict + {1: 1/4, 2: 1/4, 3: 1/4, 4: 1/4} + + >>> n = Symbol('n', positive=True, integer=True) + >>> Dn = Die('Dn', n) # n sided Die + >>> density(Dn).dict + Density(DieDistribution(n)) + >>> density(Dn).dict.subs(n, 4).doit() + {1: 1/4, 2: 1/4, 3: 1/4, 4: 1/4} + + Returns + ======= + + RandomSymbol + """ + + return rv(name, DieDistribution, sides) + + +class BernoulliDistribution(SingleFiniteDistribution): + _argnames = ('p', 'succ', 'fail') + + @staticmethod + def check(p, succ, fail): + _value_check((p >= 0, p <= 1), + "p should be in range [0, 1].") + + @property + def set(self): + return {self.succ, self.fail} + + def pmf(self, x): + if isinstance(self.succ, Symbol) and isinstance(self.fail, Symbol): + return Piecewise((self.p, x == self.succ), + (1 - self.p, x == self.fail), + (S.Zero, True)) + return Piecewise((self.p, Eq(x, self.succ)), + (1 - self.p, Eq(x, self.fail)), + (S.Zero, True)) + + +def Bernoulli(name, p, succ=1, fail=0): + r""" + Create a Finite Random Variable representing a Bernoulli process. + + Parameters + ========== + + p : Rational number between 0 and 1 + Represents probability of success + succ : Integer/symbol/string + Represents event of success + fail : Integer/symbol/string + Represents event of failure + + Examples + ======== + + >>> from sympy.stats import Bernoulli, density + >>> from sympy import S + + >>> X = Bernoulli('X', S(3)/4) # 1-0 Bernoulli variable, probability = 3/4 + >>> density(X).dict + {0: 1/4, 1: 3/4} + + >>> X = Bernoulli('X', S.Half, 'Heads', 'Tails') # A fair coin toss + >>> density(X).dict + {Heads: 1/2, Tails: 1/2} + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Bernoulli_distribution + .. [2] https://mathworld.wolfram.com/BernoulliDistribution.html + + """ + + return rv(name, BernoulliDistribution, p, succ, fail) + + +def Coin(name, p=S.Half): + r""" + Create a Finite Random Variable representing a Coin toss. + + This is an equivalent of a Bernoulli random variable with + "H" and "T" as success and failure events respectively. + + Parameters + ========== + + p : Rational Number between 0 and 1 + Represents probability of getting "Heads", by default is Half + + Examples + ======== + + >>> from sympy.stats import Coin, density + >>> from sympy import Rational + + >>> C = Coin('C') # A fair coin toss + >>> density(C).dict + {H: 1/2, T: 1/2} + + >>> C2 = Coin('C2', Rational(3, 5)) # An unfair coin + >>> density(C2).dict + {H: 3/5, T: 2/5} + + Returns + ======= + + RandomSymbol + + See Also + ======== + + sympy.stats.Binomial + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Coin_flipping + + """ + return rv(name, BernoulliDistribution, p, 'H', 'T') + + +class BinomialDistribution(SingleFiniteDistribution): + _argnames = ('n', 'p', 'succ', 'fail') + + @staticmethod + def check(n, p, succ, fail): + _value_check((n.is_integer, n.is_nonnegative), + "'n' must be nonnegative integer.") + _value_check((p <= 1, p >= 0), + "p should be in range [0, 1].") + + @property + def high(self): + return self.n + + @property + def low(self): + return S.Zero + + @property + def is_symbolic(self): + return not self.n.is_number + + @property + def set(self): + if self.is_symbolic: + return Intersection(S.Naturals0, Interval(0, self.n)) + return set(self.dict.keys()) + + def pmf(self, x): + n, p = self.n, self.p + x = sympify(x) + if not (x.is_number or x.is_Symbol or is_random(x)): + raise ValueError("'x' expected as an argument of type 'number', 'Symbol', or " + "'RandomSymbol' not %s" % (type(x))) + cond = Ge(x, 0) & Le(x, n) & Contains(x, S.Integers) + return Piecewise((binomial(n, x) * p**x * (1 - p)**(n - x), cond), (S.Zero, True)) + + @property # type: ignore + @cacheit + def dict(self): + if self.is_symbolic: + return Density(self) + return {k*self.succ + (self.n-k)*self.fail: self.pmf(k) + for k in range(0, self.n + 1)} + + +def Binomial(name, n, p, succ=1, fail=0): + r""" + Create a Finite Random Variable representing a binomial distribution. + + Parameters + ========== + + n : Positive Integer + Represents number of trials + p : Rational Number between 0 and 1 + Represents probability of success + succ : Integer/symbol/string + Represents event of success, by default is 1 + fail : Integer/symbol/string + Represents event of failure, by default is 0 + + Examples + ======== + + >>> from sympy.stats import Binomial, density + >>> from sympy import S, Symbol + + >>> X = Binomial('X', 4, S.Half) # Four "coin flips" + >>> density(X).dict + {0: 1/16, 1: 1/4, 2: 3/8, 3: 1/4, 4: 1/16} + + >>> n = Symbol('n', positive=True, integer=True) + >>> p = Symbol('p', positive=True) + >>> X = Binomial('X', n, S.Half) # n "coin flips" + >>> density(X).dict + Density(BinomialDistribution(n, 1/2, 1, 0)) + >>> density(X).dict.subs(n, 4).doit() + {0: 1/16, 1: 1/4, 2: 3/8, 3: 1/4, 4: 1/16} + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Binomial_distribution + .. [2] https://mathworld.wolfram.com/BinomialDistribution.html + + """ + + return rv(name, BinomialDistribution, n, p, succ, fail) + +#------------------------------------------------------------------------------- +# Beta-binomial distribution ---------------------------------------------------------- + +class BetaBinomialDistribution(SingleFiniteDistribution): + _argnames = ('n', 'alpha', 'beta') + + @staticmethod + def check(n, alpha, beta): + _value_check((n.is_integer, n.is_nonnegative), + "'n' must be nonnegative integer. n = %s." % str(n)) + _value_check((alpha > 0), + "'alpha' must be: alpha > 0 . alpha = %s" % str(alpha)) + _value_check((beta > 0), + "'beta' must be: beta > 0 . beta = %s" % str(beta)) + + @property + def high(self): + return self.n + + @property + def low(self): + return S.Zero + + @property + def is_symbolic(self): + return not self.n.is_number + + @property + def set(self): + if self.is_symbolic: + return Intersection(S.Naturals0, Interval(0, self.n)) + return set(map(Integer, range(self.n + 1))) + + def pmf(self, k): + n, a, b = self.n, self.alpha, self.beta + return binomial(n, k) * beta_fn(k + a, n - k + b) / beta_fn(a, b) + + +def BetaBinomial(name, n, alpha, beta): + r""" + Create a Finite Random Variable representing a Beta-binomial distribution. + + Parameters + ========== + + n : Positive Integer + Represents number of trials + alpha : Real positive number + beta : Real positive number + + Examples + ======== + + >>> from sympy.stats import BetaBinomial, density + + >>> X = BetaBinomial('X', 2, 1, 1) + >>> density(X).dict + {0: 1/3, 1: 2*beta(2, 2), 2: 1/3} + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Beta-binomial_distribution + .. [2] https://mathworld.wolfram.com/BetaBinomialDistribution.html + + """ + + return rv(name, BetaBinomialDistribution, n, alpha, beta) + + +class HypergeometricDistribution(SingleFiniteDistribution): + _argnames = ('N', 'm', 'n') + + @staticmethod + def check(n, N, m): + _value_check((N.is_integer, N.is_nonnegative), + "'N' must be nonnegative integer. N = %s." % str(N)) + _value_check((n.is_integer, n.is_nonnegative), + "'n' must be nonnegative integer. n = %s." % str(n)) + _value_check((m.is_integer, m.is_nonnegative), + "'m' must be nonnegative integer. m = %s." % str(m)) + + @property + def is_symbolic(self): + return not all(x.is_number for x in (self.N, self.m, self.n)) + + @property + def high(self): + return Piecewise((self.n, Lt(self.n, self.m) != False), (self.m, True)) + + @property + def low(self): + return Piecewise((0, Gt(0, self.n + self.m - self.N) != False), (self.n + self.m - self.N, True)) + + @property + def set(self): + N, m, n = self.N, self.m, self.n + if self.is_symbolic: + return Intersection(S.Naturals0, Interval(self.low, self.high)) + return set(range(max(0, n + m - N), min(n, m) + 1)) + + def pmf(self, k): + N, m, n = self.N, self.m, self.n + return S(binomial(m, k) * binomial(N - m, n - k))/binomial(N, n) + + +def Hypergeometric(name, N, m, n): + r""" + Create a Finite Random Variable representing a hypergeometric distribution. + + Parameters + ========== + + N : Positive Integer + Represents finite population of size N. + m : Positive Integer + Represents number of trials with required feature. + n : Positive Integer + Represents numbers of draws. + + + Examples + ======== + + >>> from sympy.stats import Hypergeometric, density + + >>> X = Hypergeometric('X', 10, 5, 3) # 10 marbles, 5 white (success), 3 draws + >>> density(X).dict + {0: 1/12, 1: 5/12, 2: 5/12, 3: 1/12} + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hypergeometric_distribution + .. [2] https://mathworld.wolfram.com/HypergeometricDistribution.html + + """ + return rv(name, HypergeometricDistribution, N, m, n) + + +class RademacherDistribution(SingleFiniteDistribution): + + @property + def set(self): + return {-1, 1} + + @property + def pmf(self): + k = Dummy('k') + return Lambda(k, Piecewise((S.Half, Or(Eq(k, -1), Eq(k, 1))), (S.Zero, True))) + +def Rademacher(name): + r""" + Create a Finite Random Variable representing a Rademacher distribution. + + Examples + ======== + + >>> from sympy.stats import Rademacher, density + + >>> X = Rademacher('X') + >>> density(X).dict + {-1: 1/2, 1: 1/2} + + Returns + ======= + + RandomSymbol + + See Also + ======== + + sympy.stats.Bernoulli + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Rademacher_distribution + + """ + return rv(name, RademacherDistribution) + +class IdealSolitonDistribution(SingleFiniteDistribution): + _argnames = ('k',) + + @staticmethod + def check(k): + _value_check(k.is_integer and k.is_positive, + "'k' must be a positive integer.") + + @property + def low(self): + return S.One + + @property + def high(self): + return self.k + + @property + def set(self): + return set(map(Integer, range(1, self.k + 1))) + + @property # type: ignore + @cacheit + def dict(self): + if self.k.is_Symbol: + return Density(self) + d = {1: Rational(1, self.k)} + d.update({i: Rational(1, i*(i - 1)) for i in range(2, self.k + 1)}) + return d + + def pmf(self, x): + x = sympify(x) + if not (x.is_number or x.is_Symbol or is_random(x)): + raise ValueError("'x' expected as an argument of type 'number', 'Symbol', or " + "'RandomSymbol' not %s" % (type(x))) + cond1 = Eq(x, 1) & x.is_integer + cond2 = Ge(x, 1) & Le(x, self.k) & x.is_integer + return Piecewise((1/self.k, cond1), (1/(x*(x - 1)), cond2), (S.Zero, True)) + +def IdealSoliton(name, k): + r""" + Create a Finite Random Variable of Ideal Soliton Distribution + + Parameters + ========== + + k : Positive Integer + Represents the number of input symbols in an LT (Luby Transform) code. + + Examples + ======== + + >>> from sympy.stats import IdealSoliton, density, P, E + >>> sol = IdealSoliton('sol', 5) + >>> density(sol).dict + {1: 1/5, 2: 1/2, 3: 1/6, 4: 1/12, 5: 1/20} + >>> density(sol).set + {1, 2, 3, 4, 5} + + >>> from sympy import Symbol + >>> k = Symbol('k', positive=True, integer=True) + >>> sol = IdealSoliton('sol', k) + >>> density(sol).dict + Density(IdealSolitonDistribution(k)) + >>> density(sol).dict.subs(k, 10).doit() + {1: 1/10, 2: 1/2, 3: 1/6, 4: 1/12, 5: 1/20, 6: 1/30, 7: 1/42, 8: 1/56, 9: 1/72, 10: 1/90} + + >>> E(sol.subs(k, 10)) + 7381/2520 + + >>> P(sol.subs(k, 4) > 2) + 1/4 + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Soliton_distribution#Ideal_distribution + .. [2] https://pages.cs.wisc.edu/~suman/courses/740/papers/luby02lt.pdf + + """ + return rv(name, IdealSolitonDistribution, k) + +class RobustSolitonDistribution(SingleFiniteDistribution): + _argnames= ('k', 'delta', 'c') + + @staticmethod + def check(k, delta, c): + _value_check(k.is_integer and k.is_positive, + "'k' must be a positive integer") + _value_check(Gt(delta, 0) and Le(delta, 1), + "'delta' must be a real number in the interval (0,1)") + _value_check(c.is_positive, + "'c' must be a positive real number.") + + @property + def R(self): + return self.c * log(self.k/self.delta) * self.k**0.5 + + @property + def Z(self): + z = 0 + for i in Range(1, round(self.k/self.R)): + z += (1/i) + z += log(self.R/self.delta) + return 1 + z * self.R/self.k + + @property + def low(self): + return S.One + + @property + def high(self): + return self.k + + @property + def set(self): + return set(map(Integer, range(1, self.k + 1))) + + @property + def is_symbolic(self): + return not (self.k.is_number and self.c.is_number and self.delta.is_number) + + def pmf(self, x): + x = sympify(x) + if not (x.is_number or x.is_Symbol or is_random(x)): + raise ValueError("'x' expected as an argument of type 'number', 'Symbol', or " + "'RandomSymbol' not %s" % (type(x))) + + cond1 = Eq(x, 1) & x.is_integer + cond2 = Ge(x, 1) & Le(x, self.k) & x.is_integer + rho = Piecewise((Rational(1, self.k), cond1), (Rational(1, x*(x-1)), cond2), (S.Zero, True)) + + cond1 = Ge(x, 1) & Le(x, round(self.k/self.R)-1) + cond2 = Eq(x, round(self.k/self.R)) + tau = Piecewise((self.R/(self.k * x), cond1), (self.R * log(self.R/self.delta)/self.k, cond2), (S.Zero, True)) + + return (rho + tau)/self.Z + +def RobustSoliton(name, k, delta, c): + r''' + Create a Finite Random Variable of Robust Soliton Distribution + + Parameters + ========== + + k : Positive Integer + Represents the number of input symbols in an LT (Luby Transform) code. + delta : Positive Rational Number + Represents the failure probability. Must be in the interval (0,1). + c : Positive Rational Number + Constant of proportionality. Values close to 1 are recommended + + Examples + ======== + + >>> from sympy.stats import RobustSoliton, density, P, E + >>> robSol = RobustSoliton('robSol', 5, 0.5, 0.01) + >>> density(robSol).dict + {1: 0.204253668152708, 2: 0.490631107897393, 3: 0.165210624506162, 4: 0.0834387731899302, 5: 0.0505633404760675} + >>> density(robSol).set + {1, 2, 3, 4, 5} + + >>> from sympy import Symbol + >>> k = Symbol('k', positive=True, integer=True) + >>> c = Symbol('c', positive=True) + >>> robSol = RobustSoliton('robSol', k, 0.5, c) + >>> density(robSol).dict + Density(RobustSolitonDistribution(k, 0.5, c)) + >>> density(robSol).dict.subs(k, 10).subs(c, 0.03).doit() + {1: 0.116641095387194, 2: 0.467045731687165, 3: 0.159984123349381, 4: 0.0821431680681869, 5: 0.0505765646770100, + 6: 0.0345781523420719, 7: 0.0253132820710503, 8: 0.0194459129233227, 9: 0.0154831166726115, 10: 0.0126733075238887} + + >>> E(robSol.subs(k, 10).subs(c, 0.05)) + 2.91358846104106 + + >>> P(robSol.subs(k, 4).subs(c, 0.1) > 2) + 0.243650614389834 + + Returns + ======= + + RandomSymbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Soliton_distribution#Robust_distribution + .. [2] https://www.inference.org.uk/mackay/itprnn/ps/588.596.pdf + .. [3] https://pages.cs.wisc.edu/~suman/courses/740/papers/luby02lt.pdf + + ''' + return rv(name, RobustSolitonDistribution, k, delta, c) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/joint_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/joint_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..d147942f08b998e167b246628360fa27fc8ef348 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/joint_rv.py @@ -0,0 +1,426 @@ +""" +Joint Random Variables Module + +See Also +======== +sympy.stats.rv +sympy.stats.frv +sympy.stats.crv +sympy.stats.drv +""" +from math import prod + +from sympy.core.basic import Basic +from sympy.core.function import Lambda +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.core.sympify import sympify +from sympy.sets.sets import ProductSet +from sympy.tensor.indexed import Indexed +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum, summation +from sympy.core.containers import Tuple +from sympy.integrals.integrals import Integral, integrate +from sympy.matrices import ImmutableMatrix, matrix2numpy, list2numpy +from sympy.stats.crv import SingleContinuousDistribution, SingleContinuousPSpace +from sympy.stats.drv import SingleDiscreteDistribution, SingleDiscretePSpace +from sympy.stats.rv import (ProductPSpace, NamedArgsMixin, Distribution, + ProductDomain, RandomSymbol, random_symbols, + SingleDomain, _symbol_converter) +from sympy.utilities.iterables import iterable +from sympy.utilities.misc import filldedent +from sympy.external import import_module + +# __all__ = ['marginal_distribution'] + +class JointPSpace(ProductPSpace): + """ + Represents a joint probability space. Represented using symbols for + each component and a distribution. + """ + def __new__(cls, sym, dist): + if isinstance(dist, SingleContinuousDistribution): + return SingleContinuousPSpace(sym, dist) + if isinstance(dist, SingleDiscreteDistribution): + return SingleDiscretePSpace(sym, dist) + sym = _symbol_converter(sym) + return Basic.__new__(cls, sym, dist) + + @property + def set(self): + return self.domain.set + + @property + def symbol(self): + return self.args[0] + + @property + def distribution(self): + return self.args[1] + + @property + def value(self): + return JointRandomSymbol(self.symbol, self) + + @property + def component_count(self): + _set = self.distribution.set + if isinstance(_set, ProductSet): + return S(len(_set.args)) + elif isinstance(_set, Product): + return _set.limits[0][-1] + return S.One + + @property + def pdf(self): + sym = [Indexed(self.symbol, i) for i in range(self.component_count)] + return self.distribution(*sym) + + @property + def domain(self): + rvs = random_symbols(self.distribution) + if not rvs: + return SingleDomain(self.symbol, self.distribution.set) + return ProductDomain(*[rv.pspace.domain for rv in rvs]) + + def component_domain(self, index): + return self.set.args[index] + + def marginal_distribution(self, *indices): + count = self.component_count + if count.atoms(Symbol): + raise ValueError("Marginal distributions cannot be computed " + "for symbolic dimensions. It is a work under progress.") + orig = [Indexed(self.symbol, i) for i in range(count)] + all_syms = [Symbol(str(i)) for i in orig] + replace_dict = dict(zip(all_syms, orig)) + sym = tuple(Symbol(str(Indexed(self.symbol, i))) for i in indices) + limits = [[i,] for i in all_syms if i not in sym] + index = 0 + for i in range(count): + if i not in indices: + limits[index].append(self.distribution.set.args[i]) + limits[index] = tuple(limits[index]) + index += 1 + if self.distribution.is_Continuous: + f = Lambda(sym, integrate(self.distribution(*all_syms), *limits)) + elif self.distribution.is_Discrete: + f = Lambda(sym, summation(self.distribution(*all_syms), *limits)) + return f.xreplace(replace_dict) + + def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs): + syms = tuple(self.value[i] for i in range(self.component_count)) + rvs = rvs or syms + if not any(i in rvs for i in syms): + return expr + expr = expr*self.pdf + for rv in rvs: + if isinstance(rv, Indexed): + expr = expr.xreplace({rv: Indexed(str(rv.base), rv.args[1])}) + elif isinstance(rv, RandomSymbol): + expr = expr.xreplace({rv: rv.symbol}) + if self.value in random_symbols(expr): + raise NotImplementedError(filldedent(''' + Expectations of expression with unindexed joint random symbols + cannot be calculated yet.''')) + limits = tuple((Indexed(str(rv.base),rv.args[1]), + self.distribution.set.args[rv.args[1]]) for rv in syms) + return Integral(expr, *limits) + + def where(self, condition): + raise NotImplementedError() + + def compute_density(self, expr): + raise NotImplementedError() + + def sample(self, size=(), library='scipy', seed=None): + """ + Internal sample method + + Returns dictionary mapping RandomSymbol to realization value. + """ + return {RandomSymbol(self.symbol, self): self.distribution.sample(size, + library=library, seed=seed)} + + def probability(self, condition): + raise NotImplementedError() + + +class SampleJointScipy: + """Returns the sample from scipy of the given distribution""" + def __new__(cls, dist, size, seed=None): + return cls._sample_scipy(dist, size, seed) + + @classmethod + def _sample_scipy(cls, dist, size, seed): + """Sample from SciPy.""" + + import numpy + if seed is None or isinstance(seed, int): + rand_state = numpy.random.default_rng(seed=seed) + else: + rand_state = seed + from scipy import stats as scipy_stats + scipy_rv_map = { + 'MultivariateNormalDistribution': lambda dist, size: scipy_stats.multivariate_normal.rvs( + mean=matrix2numpy(dist.mu).flatten(), + cov=matrix2numpy(dist.sigma), size=size, random_state=rand_state), + 'MultivariateBetaDistribution': lambda dist, size: scipy_stats.dirichlet.rvs( + alpha=list2numpy(dist.alpha, float).flatten(), size=size, random_state=rand_state), + 'MultinomialDistribution': lambda dist, size: scipy_stats.multinomial.rvs( + n=int(dist.n), p=list2numpy(dist.p, float).flatten(), size=size, random_state=rand_state) + } + + sample_shape = { + 'MultivariateNormalDistribution': lambda dist: matrix2numpy(dist.mu).flatten().shape, + 'MultivariateBetaDistribution': lambda dist: list2numpy(dist.alpha).flatten().shape, + 'MultinomialDistribution': lambda dist: list2numpy(dist.p).flatten().shape + } + + dist_list = scipy_rv_map.keys() + + if dist.__class__.__name__ not in dist_list: + return None + + samples = scipy_rv_map[dist.__class__.__name__](dist, size) + return samples.reshape(size + sample_shape[dist.__class__.__name__](dist)) + +class SampleJointNumpy: + """Returns the sample from numpy of the given distribution""" + + def __new__(cls, dist, size, seed=None): + return cls._sample_numpy(dist, size, seed) + + @classmethod + def _sample_numpy(cls, dist, size, seed): + """Sample from NumPy.""" + + import numpy + if seed is None or isinstance(seed, int): + rand_state = numpy.random.default_rng(seed=seed) + else: + rand_state = seed + numpy_rv_map = { + 'MultivariateNormalDistribution': lambda dist, size: rand_state.multivariate_normal( + mean=matrix2numpy(dist.mu, float).flatten(), + cov=matrix2numpy(dist.sigma, float), size=size), + 'MultivariateBetaDistribution': lambda dist, size: rand_state.dirichlet( + alpha=list2numpy(dist.alpha, float).flatten(), size=size), + 'MultinomialDistribution': lambda dist, size: rand_state.multinomial( + n=int(dist.n), pvals=list2numpy(dist.p, float).flatten(), size=size) + } + + sample_shape = { + 'MultivariateNormalDistribution': lambda dist: matrix2numpy(dist.mu).flatten().shape, + 'MultivariateBetaDistribution': lambda dist: list2numpy(dist.alpha).flatten().shape, + 'MultinomialDistribution': lambda dist: list2numpy(dist.p).flatten().shape + } + + dist_list = numpy_rv_map.keys() + + if dist.__class__.__name__ not in dist_list: + return None + + samples = numpy_rv_map[dist.__class__.__name__](dist, prod(size)) + return samples.reshape(size + sample_shape[dist.__class__.__name__](dist)) + +class SampleJointPymc: + """Returns the sample from pymc of the given distribution""" + + def __new__(cls, dist, size, seed=None): + return cls._sample_pymc(dist, size, seed) + + @classmethod + def _sample_pymc(cls, dist, size, seed): + """Sample from PyMC.""" + + try: + import pymc + except ImportError: + import pymc3 as pymc + pymc_rv_map = { + 'MultivariateNormalDistribution': lambda dist: + pymc.MvNormal('X', mu=matrix2numpy(dist.mu, float).flatten(), + cov=matrix2numpy(dist.sigma, float), shape=(1, dist.mu.shape[0])), + 'MultivariateBetaDistribution': lambda dist: + pymc.Dirichlet('X', a=list2numpy(dist.alpha, float).flatten()), + 'MultinomialDistribution': lambda dist: + pymc.Multinomial('X', n=int(dist.n), + p=list2numpy(dist.p, float).flatten(), shape=(1, len(dist.p))) + } + + sample_shape = { + 'MultivariateNormalDistribution': lambda dist: matrix2numpy(dist.mu).flatten().shape, + 'MultivariateBetaDistribution': lambda dist: list2numpy(dist.alpha).flatten().shape, + 'MultinomialDistribution': lambda dist: list2numpy(dist.p).flatten().shape + } + + dist_list = pymc_rv_map.keys() + + if dist.__class__.__name__ not in dist_list: + return None + + import logging + logging.getLogger("pymc3").setLevel(logging.ERROR) + with pymc.Model(): + pymc_rv_map[dist.__class__.__name__](dist) + samples = pymc.sample(draws=prod(size), chains=1, progressbar=False, random_seed=seed, return_inferencedata=False, compute_convergence_checks=False)[:]['X'] + return samples.reshape(size + sample_shape[dist.__class__.__name__](dist)) + + +_get_sample_class_jrv = { + 'scipy': SampleJointScipy, + 'pymc3': SampleJointPymc, + 'pymc': SampleJointPymc, + 'numpy': SampleJointNumpy +} + +class JointDistribution(Distribution, NamedArgsMixin): + """ + Represented by the random variables part of the joint distribution. + Contains methods for PDF, CDF, sampling, marginal densities, etc. + """ + + _argnames = ('pdf', ) + + def __new__(cls, *args): + args = list(map(sympify, args)) + for i in range(len(args)): + if isinstance(args[i], list): + args[i] = ImmutableMatrix(args[i]) + return Basic.__new__(cls, *args) + + @property + def domain(self): + return ProductDomain(self.symbols) + + @property + def pdf(self): + return self.density.args[1] + + def cdf(self, other): + if not isinstance(other, dict): + raise ValueError("%s should be of type dict, got %s"%(other, type(other))) + rvs = other.keys() + _set = self.domain.set.sets + expr = self.pdf(tuple(i.args[0] for i in self.symbols)) + for i in range(len(other)): + if rvs[i].is_Continuous: + density = Integral(expr, (rvs[i], _set[i].inf, + other[rvs[i]])) + elif rvs[i].is_Discrete: + density = Sum(expr, (rvs[i], _set[i].inf, + other[rvs[i]])) + return density + + def sample(self, size=(), library='scipy', seed=None): + """ A random realization from the distribution """ + + libraries = ('scipy', 'numpy', 'pymc3', 'pymc') + if library not in libraries: + raise NotImplementedError("Sampling from %s is not supported yet." + % str(library)) + if not import_module(library): + raise ValueError("Failed to import %s" % library) + + samps = _get_sample_class_jrv[library](self, size, seed=seed) + + if samps is not None: + return samps + raise NotImplementedError( + "Sampling for %s is not currently implemented from %s" + % (self.__class__.__name__, library) + ) + + def __call__(self, *args): + return self.pdf(*args) + +class JointRandomSymbol(RandomSymbol): + """ + Representation of random symbols with joint probability distributions + to allow indexing." + """ + def __getitem__(self, key): + if isinstance(self.pspace, JointPSpace): + if (self.pspace.component_count <= key) == True: + raise ValueError("Index keys for %s can only up to %s." % + (self.name, self.pspace.component_count - 1)) + return Indexed(self, key) + + + +class MarginalDistribution(Distribution): + """ + Represents the marginal distribution of a joint probability space. + + Initialised using a probability distribution and random variables(or + their indexed components) which should be a part of the resultant + distribution. + """ + + def __new__(cls, dist, *rvs): + if len(rvs) == 1 and iterable(rvs[0]): + rvs = tuple(rvs[0]) + if not all(isinstance(rv, (Indexed, RandomSymbol)) for rv in rvs): + raise ValueError(filldedent('''Marginal distribution can be + intitialised only in terms of random variables or indexed random + variables''')) + rvs = Tuple.fromiter(rv for rv in rvs) + if not isinstance(dist, JointDistribution) and len(random_symbols(dist)) == 0: + return dist + return Basic.__new__(cls, dist, rvs) + + def check(self): + pass + + @property + def set(self): + rvs = [i for i in self.args[1] if isinstance(i, RandomSymbol)] + return ProductSet(*[rv.pspace.set for rv in rvs]) + + @property + def symbols(self): + rvs = self.args[1] + return {rv.pspace.symbol for rv in rvs} + + def pdf(self, *x): + expr, rvs = self.args[0], self.args[1] + marginalise_out = [i for i in random_symbols(expr) if i not in rvs] + if isinstance(expr, JointDistribution): + count = len(expr.domain.args) + x = Dummy('x', real=True) + syms = tuple(Indexed(x, i) for i in count) + expr = expr.pdf(syms) + else: + syms = tuple(rv.pspace.symbol if isinstance(rv, RandomSymbol) else rv.args[0] for rv in rvs) + return Lambda(syms, self.compute_pdf(expr, marginalise_out))(*x) + + def compute_pdf(self, expr, rvs): + for rv in rvs: + lpdf = 1 + if isinstance(rv, RandomSymbol): + lpdf = rv.pspace.pdf + expr = self.marginalise_out(expr*lpdf, rv) + return expr + + def marginalise_out(self, expr, rv): + from sympy.concrete.summations import Sum + if isinstance(rv, RandomSymbol): + dom = rv.pspace.set + elif isinstance(rv, Indexed): + dom = rv.base.component_domain( + rv.pspace.component_domain(rv.args[1])) + expr = expr.xreplace({rv: rv.pspace.symbol}) + if rv.pspace.is_Continuous: + #TODO: Modify to support integration + #for all kinds of sets. + expr = Integral(expr, (rv.pspace.symbol, dom)) + elif rv.pspace.is_Discrete: + #incorporate this into `Sum`/`summation` + if dom in (S.Integers, S.Naturals, S.Naturals0): + dom = (dom.inf, dom.sup) + expr = Sum(expr, (rv.pspace.symbol, dom)) + return expr + + def __call__(self, *args): + return self.pdf(*args) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/joint_rv_types.py b/venv/lib/python3.10/site-packages/sympy/stats/joint_rv_types.py new file mode 100644 index 0000000000000000000000000000000000000000..6cee9f9aa30897593ffb7c7b930a55a38f0c518a --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/joint_rv_types.py @@ -0,0 +1,945 @@ +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.function import Lambda +from sympy.core.mul import Mul +from sympy.core.numbers import (Integer, Rational, pi) +from sympy.core.power import Pow +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import (rf, factorial) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.bessel import besselk +from sympy.functions.special.gamma_functions import gamma +from sympy.matrices.dense import (Matrix, ones) +from sympy.sets.fancysets import Range +from sympy.sets.sets import (Intersection, Interval) +from sympy.tensor.indexed import (Indexed, IndexedBase) +from sympy.matrices import ImmutableMatrix, MatrixSymbol +from sympy.matrices.expressions.determinant import det +from sympy.matrices.expressions.matexpr import MatrixElement +from sympy.stats.joint_rv import JointDistribution, JointPSpace, MarginalDistribution +from sympy.stats.rv import _value_check, random_symbols + +__all__ = ['JointRV', +'MultivariateNormal', +'MultivariateLaplace', +'Dirichlet', +'GeneralizedMultivariateLogGamma', +'GeneralizedMultivariateLogGammaOmega', +'Multinomial', +'MultivariateBeta', +'MultivariateEwens', +'MultivariateT', +'NegativeMultinomial', +'NormalGamma' +] + +def multivariate_rv(cls, sym, *args): + args = list(map(sympify, args)) + dist = cls(*args) + args = dist.args + dist.check(*args) + return JointPSpace(sym, dist).value + + +def marginal_distribution(rv, *indices): + """ + Marginal distribution function of a joint random variable. + + Parameters + ========== + + rv : A random variable with a joint probability distribution. + indices : Component indices or the indexed random symbol + for which the joint distribution is to be calculated + + Returns + ======= + + A Lambda expression in `sym`. + + Examples + ======== + + >>> from sympy.stats import MultivariateNormal, marginal_distribution + >>> m = MultivariateNormal('X', [1, 2], [[2, 1], [1, 2]]) + >>> marginal_distribution(m, m[0])(1) + 1/(2*sqrt(pi)) + + """ + indices = list(indices) + for i in range(len(indices)): + if isinstance(indices[i], Indexed): + indices[i] = indices[i].args[1] + prob_space = rv.pspace + if not indices: + raise ValueError( + "At least one component for marginal density is needed.") + if hasattr(prob_space.distribution, '_marginal_distribution'): + return prob_space.distribution._marginal_distribution(indices, rv.symbol) + return prob_space.marginal_distribution(*indices) + + +class JointDistributionHandmade(JointDistribution): + + _argnames = ('pdf',) + is_Continuous = True + + @property + def set(self): + return self.args[1] + + +def JointRV(symbol, pdf, _set=None): + """ + Create a Joint Random Variable where each of its component is continuous, + given the following: + + Parameters + ========== + + symbol : Symbol + Represents name of the random variable. + pdf : A PDF in terms of indexed symbols of the symbol given + as the first argument + + NOTE + ==== + + As of now, the set for each component for a ``JointRV`` is + equal to the set of all integers, which cannot be changed. + + Examples + ======== + + >>> from sympy import exp, pi, Indexed, S + >>> from sympy.stats import density, JointRV + >>> x1, x2 = (Indexed('x', i) for i in (1, 2)) + >>> pdf = exp(-x1**2/2 + x1 - x2**2/2 - S(1)/2)/(2*pi) + >>> N1 = JointRV('x', pdf) #Multivariate Normal distribution + >>> density(N1)(1, 2) + exp(-2)/(2*pi) + + Returns + ======= + + RandomSymbol + + """ + #TODO: Add support for sets provided by the user + symbol = sympify(symbol) + syms = [i for i in pdf.free_symbols if isinstance(i, Indexed) + and i.base == IndexedBase(symbol)] + syms = tuple(sorted(syms, key = lambda index: index.args[1])) + _set = S.Reals**len(syms) + pdf = Lambda(syms, pdf) + dist = JointDistributionHandmade(pdf, _set) + jrv = JointPSpace(symbol, dist).value + rvs = random_symbols(pdf) + if len(rvs) != 0: + dist = MarginalDistribution(dist, (jrv,)) + return JointPSpace(symbol, dist).value + return jrv + +#------------------------------------------------------------------------------- +# Multivariate Normal distribution --------------------------------------------- + +class MultivariateNormalDistribution(JointDistribution): + _argnames = ('mu', 'sigma') + + is_Continuous=True + + @property + def set(self): + k = self.mu.shape[0] + return S.Reals**k + + @staticmethod + def check(mu, sigma): + _value_check(mu.shape[0] == sigma.shape[0], + "Size of the mean vector and covariance matrix are incorrect.") + #check if covariance matrix is positive semi definite or not. + if not isinstance(sigma, MatrixSymbol): + _value_check(sigma.is_positive_semidefinite, + "The covariance matrix must be positive semi definite. ") + + def pdf(self, *args): + mu, sigma = self.mu, self.sigma + k = mu.shape[0] + if len(args) == 1 and args[0].is_Matrix: + args = args[0] + else: + args = ImmutableMatrix(args) + x = args - mu + density = S.One/sqrt((2*pi)**(k)*det(sigma))*exp( + Rational(-1, 2)*x.transpose()*(sigma.inv()*x)) + return MatrixElement(density, 0, 0) + + def _marginal_distribution(self, indices, sym): + sym = ImmutableMatrix([Indexed(sym, i) for i in indices]) + _mu, _sigma = self.mu, self.sigma + k = self.mu.shape[0] + for i in range(k): + if i not in indices: + _mu = _mu.row_del(i) + _sigma = _sigma.col_del(i) + _sigma = _sigma.row_del(i) + return Lambda(tuple(sym), S.One/sqrt((2*pi)**(len(_mu))*det(_sigma))*exp( + Rational(-1, 2)*(_mu - sym).transpose()*(_sigma.inv()*\ + (_mu - sym)))[0]) + +def MultivariateNormal(name, mu, sigma): + r""" + Creates a continuous random variable with Multivariate Normal + Distribution. + + The density of the multivariate normal distribution can be found at [1]. + + Parameters + ========== + + mu : List representing the mean or the mean vector + sigma : Positive semidefinite square matrix + Represents covariance Matrix. + If `\sigma` is noninvertible then only sampling is supported currently + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import MultivariateNormal, density, marginal_distribution + >>> from sympy import symbols, MatrixSymbol + >>> X = MultivariateNormal('X', [3, 4], [[2, 1], [1, 2]]) + >>> y, z = symbols('y z') + >>> density(X)(y, z) + sqrt(3)*exp(-y**2/3 + y*z/3 + 2*y/3 - z**2/3 + 5*z/3 - 13/3)/(6*pi) + >>> density(X)(1, 2) + sqrt(3)*exp(-4/3)/(6*pi) + >>> marginal_distribution(X, X[1])(y) + exp(-(y - 4)**2/4)/(2*sqrt(pi)) + >>> marginal_distribution(X, X[0])(y) + exp(-(y - 3)**2/4)/(2*sqrt(pi)) + + The example below shows that it is also possible to use + symbolic parameters to define the MultivariateNormal class. + + >>> n = symbols('n', integer=True, positive=True) + >>> Sg = MatrixSymbol('Sg', n, n) + >>> mu = MatrixSymbol('mu', n, 1) + >>> obs = MatrixSymbol('obs', n, 1) + >>> X = MultivariateNormal('X', mu, Sg) + + The density of a multivariate normal can be + calculated using a matrix argument, as shown below. + + >>> density(X)(obs) + (exp(((1/2)*mu.T - (1/2)*obs.T)*Sg**(-1)*(-mu + obs))/sqrt((2*pi)**n*Determinant(Sg)))[0, 0] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Multivariate_normal_distribution + + """ + return multivariate_rv(MultivariateNormalDistribution, name, mu, sigma) + +#------------------------------------------------------------------------------- +# Multivariate Laplace distribution -------------------------------------------- + +class MultivariateLaplaceDistribution(JointDistribution): + _argnames = ('mu', 'sigma') + is_Continuous=True + + @property + def set(self): + k = self.mu.shape[0] + return S.Reals**k + + @staticmethod + def check(mu, sigma): + _value_check(mu.shape[0] == sigma.shape[0], + "Size of the mean vector and covariance matrix are incorrect.") + # check if covariance matrix is positive definite or not. + if not isinstance(sigma, MatrixSymbol): + _value_check(sigma.is_positive_definite, + "The covariance matrix must be positive definite. ") + + def pdf(self, *args): + mu, sigma = self.mu, self.sigma + mu_T = mu.transpose() + k = S(mu.shape[0]) + sigma_inv = sigma.inv() + args = ImmutableMatrix(args) + args_T = args.transpose() + x = (mu_T*sigma_inv*mu)[0] + y = (args_T*sigma_inv*args)[0] + v = 1 - k/2 + return (2 * (y/(2 + x))**(v/2) * besselk(v, sqrt((2 + x)*y)) * + exp((args_T * sigma_inv * mu)[0]) / + ((2 * pi)**(k/2) * sqrt(det(sigma)))) + + +def MultivariateLaplace(name, mu, sigma): + """ + Creates a continuous random variable with Multivariate Laplace + Distribution. + + The density of the multivariate Laplace distribution can be found at [1]. + + Parameters + ========== + + mu : List representing the mean or the mean vector + sigma : Positive definite square matrix + Represents covariance Matrix + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import MultivariateLaplace, density + >>> from sympy import symbols + >>> y, z = symbols('y z') + >>> X = MultivariateLaplace('X', [2, 4], [[3, 1], [1, 3]]) + >>> density(X)(y, z) + sqrt(2)*exp(y/4 + 5*z/4)*besselk(0, sqrt(15*y*(3*y/8 - z/8)/2 + 15*z*(-y/8 + 3*z/8)/2))/(4*pi) + >>> density(X)(1, 2) + sqrt(2)*exp(11/4)*besselk(0, sqrt(165)/4)/(4*pi) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Multivariate_Laplace_distribution + + """ + return multivariate_rv(MultivariateLaplaceDistribution, name, mu, sigma) + +#------------------------------------------------------------------------------- +# Multivariate StudentT distribution ------------------------------------------- + +class MultivariateTDistribution(JointDistribution): + _argnames = ('mu', 'shape_mat', 'dof') + is_Continuous=True + + @property + def set(self): + k = self.mu.shape[0] + return S.Reals**k + + @staticmethod + def check(mu, sigma, v): + _value_check(mu.shape[0] == sigma.shape[0], + "Size of the location vector and shape matrix are incorrect.") + # check if covariance matrix is positive definite or not. + if not isinstance(sigma, MatrixSymbol): + _value_check(sigma.is_positive_definite, + "The shape matrix must be positive definite. ") + + def pdf(self, *args): + mu, sigma = self.mu, self.shape_mat + v = S(self.dof) + k = S(mu.shape[0]) + sigma_inv = sigma.inv() + args = ImmutableMatrix(args) + x = args - mu + return gamma((k + v)/2)/(gamma(v/2)*(v*pi)**(k/2)*sqrt(det(sigma)))\ + *(1 + 1/v*(x.transpose()*sigma_inv*x)[0])**((-v - k)/2) + +def MultivariateT(syms, mu, sigma, v): + """ + Creates a joint random variable with multivariate T-distribution. + + Parameters + ========== + + syms : A symbol/str + For identifying the random variable. + mu : A list/matrix + Representing the location vector + sigma : The shape matrix for the distribution + + Examples + ======== + + >>> from sympy.stats import density, MultivariateT + >>> from sympy import Symbol + + >>> x = Symbol("x") + >>> X = MultivariateT("x", [1, 1], [[1, 0], [0, 1]], 2) + + >>> density(X)(1, 2) + 2/(9*pi) + + Returns + ======= + + RandomSymbol + + """ + return multivariate_rv(MultivariateTDistribution, syms, mu, sigma, v) + + +#------------------------------------------------------------------------------- +# Multivariate Normal Gamma distribution --------------------------------------- + +class NormalGammaDistribution(JointDistribution): + + _argnames = ('mu', 'lamda', 'alpha', 'beta') + is_Continuous=True + + @staticmethod + def check(mu, lamda, alpha, beta): + _value_check(mu.is_real, "Location must be real.") + _value_check(lamda > 0, "Lambda must be positive") + _value_check(alpha > 0, "alpha must be positive") + _value_check(beta > 0, "beta must be positive") + + @property + def set(self): + return S.Reals*Interval(0, S.Infinity) + + def pdf(self, x, tau): + beta, alpha, lamda = self.beta, self.alpha, self.lamda + mu = self.mu + + return beta**alpha*sqrt(lamda)/(gamma(alpha)*sqrt(2*pi))*\ + tau**(alpha - S.Half)*exp(-1*beta*tau)*\ + exp(-1*(lamda*tau*(x - mu)**2)/S(2)) + + def _marginal_distribution(self, indices, *sym): + if len(indices) == 2: + return self.pdf(*sym) + if indices[0] == 0: + #For marginal over `x`, return non-standardized Student-T's + #distribution + x = sym[0] + v, mu, sigma = self.alpha - S.Half, self.mu, \ + S(self.beta)/(self.lamda * self.alpha) + return Lambda(sym, gamma((v + 1)/2)/(gamma(v/2)*sqrt(pi*v)*sigma)*\ + (1 + 1/v*((x - mu)/sigma)**2)**((-v -1)/2)) + #For marginal over `tau`, return Gamma distribution as per construction + from sympy.stats.crv_types import GammaDistribution + return Lambda(sym, GammaDistribution(self.alpha, self.beta)(sym[0])) + +def NormalGamma(sym, mu, lamda, alpha, beta): + """ + Creates a bivariate joint random variable with multivariate Normal gamma + distribution. + + Parameters + ========== + + sym : A symbol/str + For identifying the random variable. + mu : A real number + The mean of the normal distribution + lamda : A positive integer + Parameter of joint distribution + alpha : A positive integer + Parameter of joint distribution + beta : A positive integer + Parameter of joint distribution + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density, NormalGamma + >>> from sympy import symbols + + >>> X = NormalGamma('x', 0, 1, 2, 3) + >>> y, z = symbols('y z') + + >>> density(X)(y, z) + 9*sqrt(2)*z**(3/2)*exp(-3*z)*exp(-y**2*z/2)/(2*sqrt(pi)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Normal-gamma_distribution + + """ + return multivariate_rv(NormalGammaDistribution, sym, mu, lamda, alpha, beta) + +#------------------------------------------------------------------------------- +# Multivariate Beta/Dirichlet distribution ------------------------------------- + +class MultivariateBetaDistribution(JointDistribution): + + _argnames = ('alpha',) + is_Continuous = True + + @staticmethod + def check(alpha): + _value_check(len(alpha) >= 2, "At least two categories should be passed.") + for a_k in alpha: + _value_check((a_k > 0) != False, "Each concentration parameter" + " should be positive.") + + @property + def set(self): + k = len(self.alpha) + return Interval(0, 1)**k + + def pdf(self, *syms): + alpha = self.alpha + B = Mul.fromiter(map(gamma, alpha))/gamma(Add(*alpha)) + return Mul.fromiter(sym**(a_k - 1) for a_k, sym in zip(alpha, syms))/B + +def MultivariateBeta(syms, *alpha): + """ + Creates a continuous random variable with Dirichlet/Multivariate Beta + Distribution. + + The density of the Dirichlet distribution can be found at [1]. + + Parameters + ========== + + alpha : Positive real numbers + Signifies concentration numbers. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density, MultivariateBeta, marginal_distribution + >>> from sympy import Symbol + >>> a1 = Symbol('a1', positive=True) + >>> a2 = Symbol('a2', positive=True) + >>> B = MultivariateBeta('B', [a1, a2]) + >>> C = MultivariateBeta('C', a1, a2) + >>> x = Symbol('x') + >>> y = Symbol('y') + >>> density(B)(x, y) + x**(a1 - 1)*y**(a2 - 1)*gamma(a1 + a2)/(gamma(a1)*gamma(a2)) + >>> marginal_distribution(C, C[0])(x) + x**(a1 - 1)*gamma(a1 + a2)/(a2*gamma(a1)*gamma(a2)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Dirichlet_distribution + .. [2] https://mathworld.wolfram.com/DirichletDistribution.html + + """ + if not isinstance(alpha[0], list): + alpha = (list(alpha),) + return multivariate_rv(MultivariateBetaDistribution, syms, alpha[0]) + +Dirichlet = MultivariateBeta + +#------------------------------------------------------------------------------- +# Multivariate Ewens distribution ---------------------------------------------- + +class MultivariateEwensDistribution(JointDistribution): + + _argnames = ('n', 'theta') + is_Discrete = True + is_Continuous = False + + @staticmethod + def check(n, theta): + _value_check((n > 0), + "sample size should be positive integer.") + _value_check(theta.is_positive, "mutation rate should be positive.") + + @property + def set(self): + if not isinstance(self.n, Integer): + i = Symbol('i', integer=True, positive=True) + return Product(Intersection(S.Naturals0, Interval(0, self.n//i)), + (i, 1, self.n)) + prod_set = Range(0, self.n + 1) + for i in range(2, self.n + 1): + prod_set *= Range(0, self.n//i + 1) + return prod_set.flatten() + + def pdf(self, *syms): + n, theta = self.n, self.theta + condi = isinstance(self.n, Integer) + if not (isinstance(syms[0], IndexedBase) or condi): + raise ValueError("Please use IndexedBase object for syms as " + "the dimension is symbolic") + term_1 = factorial(n)/rf(theta, n) + if condi: + term_2 = Mul.fromiter(theta**syms[j]/((j+1)**syms[j]*factorial(syms[j])) + for j in range(n)) + cond = Eq(sum((k + 1)*syms[k] for k in range(n)), n) + return Piecewise((term_1 * term_2, cond), (0, True)) + syms = syms[0] + j, k = symbols('j, k', positive=True, integer=True) + term_2 = Product(theta**syms[j]/((j+1)**syms[j]*factorial(syms[j])), + (j, 0, n - 1)) + cond = Eq(Sum((k + 1)*syms[k], (k, 0, n - 1)), n) + return Piecewise((term_1 * term_2, cond), (0, True)) + + +def MultivariateEwens(syms, n, theta): + """ + Creates a discrete random variable with Multivariate Ewens + Distribution. + + The density of the said distribution can be found at [1]. + + Parameters + ========== + + n : Positive integer + Size of the sample or the integer whose partitions are considered + theta : Positive real number + Denotes Mutation rate + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density, marginal_distribution, MultivariateEwens + >>> from sympy import Symbol + >>> a1 = Symbol('a1', positive=True) + >>> a2 = Symbol('a2', positive=True) + >>> ed = MultivariateEwens('E', 2, 1) + >>> density(ed)(a1, a2) + Piecewise((1/(2**a2*factorial(a1)*factorial(a2)), Eq(a1 + 2*a2, 2)), (0, True)) + >>> marginal_distribution(ed, ed[0])(a1) + Piecewise((1/factorial(a1), Eq(a1, 2)), (0, True)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Ewens%27s_sampling_formula + .. [2] https://www.jstor.org/stable/24780825 + """ + return multivariate_rv(MultivariateEwensDistribution, syms, n, theta) + +#------------------------------------------------------------------------------- +# Generalized Multivariate Log Gamma distribution ------------------------------ + +class GeneralizedMultivariateLogGammaDistribution(JointDistribution): + + _argnames = ('delta', 'v', 'lamda', 'mu') + is_Continuous=True + + def check(self, delta, v, l, mu): + _value_check((delta >= 0, delta <= 1), "delta must be in range [0, 1].") + _value_check((v > 0), "v must be positive") + for lk in l: + _value_check((lk > 0), "lamda must be a positive vector.") + for muk in mu: + _value_check((muk > 0), "mu must be a positive vector.") + _value_check(len(l) > 1,"the distribution should have at least" + " two random variables.") + + @property + def set(self): + return S.Reals**len(self.lamda) + + def pdf(self, *y): + d, v, l, mu = self.delta, self.v, self.lamda, self.mu + n = Symbol('n', negative=False, integer=True) + k = len(l) + sterm1 = Pow((1 - d), n)/\ + ((gamma(v + n)**(k - 1))*gamma(v)*gamma(n + 1)) + sterm2 = Mul.fromiter(mui*li**(-v - n) for mui, li in zip(mu, l)) + term1 = sterm1 * sterm2 + sterm3 = (v + n) * sum(mui * yi for mui, yi in zip(mu, y)) + sterm4 = sum(exp(mui * yi)/li for (mui, yi, li) in zip(mu, y, l)) + term2 = exp(sterm3 - sterm4) + return Pow(d, v) * Sum(term1 * term2, (n, 0, S.Infinity)) + +def GeneralizedMultivariateLogGamma(syms, delta, v, lamda, mu): + """ + Creates a joint random variable with generalized multivariate log gamma + distribution. + + The joint pdf can be found at [1]. + + Parameters + ========== + + syms : list/tuple/set of symbols for identifying each component + delta : A constant in range $[0, 1]$ + v : Positive real number + lamda : List of positive real numbers + mu : List of positive real numbers + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density + >>> from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGamma + >>> from sympy import symbols, S + >>> v = 1 + >>> l, mu = [1, 1, 1], [1, 1, 1] + >>> d = S.Half + >>> y = symbols('y_1:4', positive=True) + >>> Gd = GeneralizedMultivariateLogGamma('G', d, v, l, mu) + >>> density(Gd)(y[0], y[1], y[2]) + Sum(exp((n + 1)*(y_1 + y_2 + y_3) - exp(y_1) - exp(y_2) - + exp(y_3))/(2**n*gamma(n + 1)**3), (n, 0, oo))/2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Generalized_multivariate_log-gamma_distribution + .. [2] https://www.researchgate.net/publication/234137346_On_a_multivariate_log-gamma_distribution_and_the_use_of_the_distribution_in_the_Bayesian_analysis + + Note + ==== + + If the GeneralizedMultivariateLogGamma is too long to type use, + + >>> from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGamma as GMVLG + >>> Gd = GMVLG('G', d, v, l, mu) + + If you want to pass the matrix omega instead of the constant delta, then use + ``GeneralizedMultivariateLogGammaOmega``. + + """ + return multivariate_rv(GeneralizedMultivariateLogGammaDistribution, + syms, delta, v, lamda, mu) + +def GeneralizedMultivariateLogGammaOmega(syms, omega, v, lamda, mu): + """ + Extends GeneralizedMultivariateLogGamma. + + Parameters + ========== + + syms : list/tuple/set of symbols + For identifying each component + omega : A square matrix + Every element of square matrix must be absolute value of + square root of correlation coefficient + v : Positive real number + lamda : List of positive real numbers + mu : List of positive real numbers + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density + >>> from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGammaOmega + >>> from sympy import Matrix, symbols, S + >>> omega = Matrix([[1, S.Half, S.Half], [S.Half, 1, S.Half], [S.Half, S.Half, 1]]) + >>> v = 1 + >>> l, mu = [1, 1, 1], [1, 1, 1] + >>> G = GeneralizedMultivariateLogGammaOmega('G', omega, v, l, mu) + >>> y = symbols('y_1:4', positive=True) + >>> density(G)(y[0], y[1], y[2]) + sqrt(2)*Sum((1 - sqrt(2)/2)**n*exp((n + 1)*(y_1 + y_2 + y_3) - exp(y_1) - + exp(y_2) - exp(y_3))/gamma(n + 1)**3, (n, 0, oo))/2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Generalized_multivariate_log-gamma_distribution + .. [2] https://www.researchgate.net/publication/234137346_On_a_multivariate_log-gamma_distribution_and_the_use_of_the_distribution_in_the_Bayesian_analysis + + Notes + ===== + + If the GeneralizedMultivariateLogGammaOmega is too long to type use, + + >>> from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGammaOmega as GMVLGO + >>> G = GMVLGO('G', omega, v, l, mu) + + """ + _value_check((omega.is_square, isinstance(omega, Matrix)), "omega must be a" + " square matrix") + for val in omega.values(): + _value_check((val >= 0, val <= 1), + "all values in matrix must be between 0 and 1(both inclusive).") + _value_check(omega.diagonal().equals(ones(1, omega.shape[0])), + "all the elements of diagonal should be 1.") + _value_check((omega.shape[0] == len(lamda), len(lamda) == len(mu)), + "lamda, mu should be of same length and omega should " + " be of shape (length of lamda, length of mu)") + _value_check(len(lamda) > 1,"the distribution should have at least" + " two random variables.") + delta = Pow(Rational(omega.det()), Rational(1, len(lamda) - 1)) + return GeneralizedMultivariateLogGamma(syms, delta, v, lamda, mu) + + +#------------------------------------------------------------------------------- +# Multinomial distribution ----------------------------------------------------- + +class MultinomialDistribution(JointDistribution): + + _argnames = ('n', 'p') + is_Continuous=False + is_Discrete = True + + @staticmethod + def check(n, p): + _value_check(n > 0, + "number of trials must be a positive integer") + for p_k in p: + _value_check((p_k >= 0, p_k <= 1), + "probability must be in range [0, 1]") + _value_check(Eq(sum(p), 1), + "probabilities must sum to 1") + + @property + def set(self): + return Intersection(S.Naturals0, Interval(0, self.n))**len(self.p) + + def pdf(self, *x): + n, p = self.n, self.p + term_1 = factorial(n)/Mul.fromiter(factorial(x_k) for x_k in x) + term_2 = Mul.fromiter(p_k**x_k for p_k, x_k in zip(p, x)) + return Piecewise((term_1 * term_2, Eq(sum(x), n)), (0, True)) + +def Multinomial(syms, n, *p): + """ + Creates a discrete random variable with Multinomial Distribution. + + The density of the said distribution can be found at [1]. + + Parameters + ========== + + n : Positive integer + Represents number of trials + p : List of event probabilities + Must be in the range of $[0, 1]$. + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density, Multinomial, marginal_distribution + >>> from sympy import symbols + >>> x1, x2, x3 = symbols('x1, x2, x3', nonnegative=True, integer=True) + >>> p1, p2, p3 = symbols('p1, p2, p3', positive=True) + >>> M = Multinomial('M', 3, p1, p2, p3) + >>> density(M)(x1, x2, x3) + Piecewise((6*p1**x1*p2**x2*p3**x3/(factorial(x1)*factorial(x2)*factorial(x3)), + Eq(x1 + x2 + x3, 3)), (0, True)) + >>> marginal_distribution(M, M[0])(x1).subs(x1, 1) + 3*p1*p2**2 + 6*p1*p2*p3 + 3*p1*p3**2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Multinomial_distribution + .. [2] https://mathworld.wolfram.com/MultinomialDistribution.html + + """ + if not isinstance(p[0], list): + p = (list(p), ) + return multivariate_rv(MultinomialDistribution, syms, n, p[0]) + +#------------------------------------------------------------------------------- +# Negative Multinomial Distribution -------------------------------------------- + +class NegativeMultinomialDistribution(JointDistribution): + + _argnames = ('k0', 'p') + is_Continuous=False + is_Discrete = True + + @staticmethod + def check(k0, p): + _value_check(k0 > 0, + "number of failures must be a positive integer") + for p_k in p: + _value_check((p_k >= 0, p_k <= 1), + "probability must be in range [0, 1].") + _value_check(sum(p) <= 1, + "success probabilities must not be greater than 1.") + + @property + def set(self): + return Range(0, S.Infinity)**len(self.p) + + def pdf(self, *k): + k0, p = self.k0, self.p + term_1 = (gamma(k0 + sum(k))*(1 - sum(p))**k0)/gamma(k0) + term_2 = Mul.fromiter(pi**ki/factorial(ki) for pi, ki in zip(p, k)) + return term_1 * term_2 + +def NegativeMultinomial(syms, k0, *p): + """ + Creates a discrete random variable with Negative Multinomial Distribution. + + The density of the said distribution can be found at [1]. + + Parameters + ========== + + k0 : positive integer + Represents number of failures before the experiment is stopped + p : List of event probabilities + Must be in the range of $[0, 1]$ + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density, NegativeMultinomial, marginal_distribution + >>> from sympy import symbols + >>> x1, x2, x3 = symbols('x1, x2, x3', nonnegative=True, integer=True) + >>> p1, p2, p3 = symbols('p1, p2, p3', positive=True) + >>> N = NegativeMultinomial('M', 3, p1, p2, p3) + >>> N_c = NegativeMultinomial('M', 3, 0.1, 0.1, 0.1) + >>> density(N)(x1, x2, x3) + p1**x1*p2**x2*p3**x3*(-p1 - p2 - p3 + 1)**3*gamma(x1 + x2 + + x3 + 3)/(2*factorial(x1)*factorial(x2)*factorial(x3)) + >>> marginal_distribution(N_c, N_c[0])(1).evalf().round(2) + 0.25 + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Negative_multinomial_distribution + .. [2] https://mathworld.wolfram.com/NegativeBinomialDistribution.html + + """ + if not isinstance(p[0], list): + p = (list(p), ) + return multivariate_rv(NegativeMultinomialDistribution, syms, k0, p[0]) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/matrix_distributions.py b/venv/lib/python3.10/site-packages/sympy/stats/matrix_distributions.py new file mode 100644 index 0000000000000000000000000000000000000000..9a43c0226bc25702211a910ebbe30e280ad0cf50 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/matrix_distributions.py @@ -0,0 +1,610 @@ +from math import prod + +from sympy.core.basic import Basic +from sympy.core.numbers import pi +from sympy.core.singleton import S +from sympy.functions.elementary.exponential import exp +from sympy.functions.special.gamma_functions import multigamma +from sympy.core.sympify import sympify, _sympify +from sympy.matrices import (ImmutableMatrix, Inverse, Trace, Determinant, + MatrixSymbol, MatrixBase, Transpose, MatrixSet, + matrix2numpy) +from sympy.stats.rv import (_value_check, RandomMatrixSymbol, NamedArgsMixin, PSpace, + _symbol_converter, MatrixDomain, Distribution) +from sympy.external import import_module + + +################################################################################ +#------------------------Matrix Probability Space------------------------------# +################################################################################ +class MatrixPSpace(PSpace): + """ + Represents probability space for + Matrix Distributions. + """ + def __new__(cls, sym, distribution, dim_n, dim_m): + sym = _symbol_converter(sym) + dim_n, dim_m = _sympify(dim_n), _sympify(dim_m) + if not (dim_n.is_integer and dim_m.is_integer): + raise ValueError("Dimensions should be integers") + return Basic.__new__(cls, sym, distribution, dim_n, dim_m) + + distribution = property(lambda self: self.args[1]) + symbol = property(lambda self: self.args[0]) + + @property + def domain(self): + return MatrixDomain(self.symbol, self.distribution.set) + + @property + def value(self): + return RandomMatrixSymbol(self.symbol, self.args[2], self.args[3], self) + + @property + def values(self): + return {self.value} + + def compute_density(self, expr, *args): + rms = expr.atoms(RandomMatrixSymbol) + if len(rms) > 1 or (not isinstance(expr, RandomMatrixSymbol)): + raise NotImplementedError("Currently, no algorithm has been " + "implemented to handle general expressions containing " + "multiple matrix distributions.") + return self.distribution.pdf(expr) + + def sample(self, size=(), library='scipy', seed=None): + """ + Internal sample method + + Returns dictionary mapping RandomMatrixSymbol to realization value. + """ + return {self.value: self.distribution.sample(size, library=library, seed=seed)} + + +def rv(symbol, cls, args): + args = list(map(sympify, args)) + dist = cls(*args) + dist.check(*args) + dim = dist.dimension + pspace = MatrixPSpace(symbol, dist, dim[0], dim[1]) + return pspace.value + + +class SampleMatrixScipy: + """Returns the sample from scipy of the given distribution""" + def __new__(cls, dist, size, seed=None): + return cls._sample_scipy(dist, size, seed) + + @classmethod + def _sample_scipy(cls, dist, size, seed): + """Sample from SciPy.""" + + from scipy import stats as scipy_stats + import numpy + scipy_rv_map = { + 'WishartDistribution': lambda dist, size, rand_state: scipy_stats.wishart.rvs( + df=int(dist.n), scale=matrix2numpy(dist.scale_matrix, float), size=size), + 'MatrixNormalDistribution': lambda dist, size, rand_state: scipy_stats.matrix_normal.rvs( + mean=matrix2numpy(dist.location_matrix, float), + rowcov=matrix2numpy(dist.scale_matrix_1, float), + colcov=matrix2numpy(dist.scale_matrix_2, float), size=size, random_state=rand_state) + } + + sample_shape = { + 'WishartDistribution': lambda dist: dist.scale_matrix.shape, + 'MatrixNormalDistribution' : lambda dist: dist.location_matrix.shape + } + + dist_list = scipy_rv_map.keys() + + if dist.__class__.__name__ not in dist_list: + return None + + if seed is None or isinstance(seed, int): + rand_state = numpy.random.default_rng(seed=seed) + else: + rand_state = seed + samp = scipy_rv_map[dist.__class__.__name__](dist, prod(size), rand_state) + return samp.reshape(size + sample_shape[dist.__class__.__name__](dist)) + + +class SampleMatrixNumpy: + """Returns the sample from numpy of the given distribution""" + + ### TODO: Add tests after adding matrix distributions in numpy_rv_map + def __new__(cls, dist, size, seed=None): + return cls._sample_numpy(dist, size, seed) + + @classmethod + def _sample_numpy(cls, dist, size, seed): + """Sample from NumPy.""" + + numpy_rv_map = { + } + + sample_shape = { + } + + dist_list = numpy_rv_map.keys() + + if dist.__class__.__name__ not in dist_list: + return None + + import numpy + if seed is None or isinstance(seed, int): + rand_state = numpy.random.default_rng(seed=seed) + else: + rand_state = seed + samp = numpy_rv_map[dist.__class__.__name__](dist, prod(size), rand_state) + return samp.reshape(size + sample_shape[dist.__class__.__name__](dist)) + + +class SampleMatrixPymc: + """Returns the sample from pymc of the given distribution""" + + def __new__(cls, dist, size, seed=None): + return cls._sample_pymc(dist, size, seed) + + @classmethod + def _sample_pymc(cls, dist, size, seed): + """Sample from PyMC.""" + + try: + import pymc + except ImportError: + import pymc3 as pymc + pymc_rv_map = { + 'MatrixNormalDistribution': lambda dist: pymc.MatrixNormal('X', + mu=matrix2numpy(dist.location_matrix, float), + rowcov=matrix2numpy(dist.scale_matrix_1, float), + colcov=matrix2numpy(dist.scale_matrix_2, float), + shape=dist.location_matrix.shape), + 'WishartDistribution': lambda dist: pymc.WishartBartlett('X', + nu=int(dist.n), S=matrix2numpy(dist.scale_matrix, float)) + } + + sample_shape = { + 'WishartDistribution': lambda dist: dist.scale_matrix.shape, + 'MatrixNormalDistribution' : lambda dist: dist.location_matrix.shape + } + + dist_list = pymc_rv_map.keys() + + if dist.__class__.__name__ not in dist_list: + return None + import logging + logging.getLogger("pymc").setLevel(logging.ERROR) + with pymc.Model(): + pymc_rv_map[dist.__class__.__name__](dist) + samps = pymc.sample(draws=prod(size), chains=1, progressbar=False, random_seed=seed, return_inferencedata=False, compute_convergence_checks=False)['X'] + return samps.reshape(size + sample_shape[dist.__class__.__name__](dist)) + +_get_sample_class_matrixrv = { + 'scipy': SampleMatrixScipy, + 'pymc3': SampleMatrixPymc, + 'pymc': SampleMatrixPymc, + 'numpy': SampleMatrixNumpy +} + +################################################################################ +#-------------------------Matrix Distribution----------------------------------# +################################################################################ + +class MatrixDistribution(Distribution, NamedArgsMixin): + """ + Abstract class for Matrix Distribution. + """ + def __new__(cls, *args): + args = [ImmutableMatrix(arg) if isinstance(arg, list) + else _sympify(arg) for arg in args] + return Basic.__new__(cls, *args) + + @staticmethod + def check(*args): + pass + + def __call__(self, expr): + if isinstance(expr, list): + expr = ImmutableMatrix(expr) + return self.pdf(expr) + + def sample(self, size=(), library='scipy', seed=None): + """ + Internal sample method + + Returns dictionary mapping RandomSymbol to realization value. + """ + + libraries = ['scipy', 'numpy', 'pymc3', 'pymc'] + if library not in libraries: + raise NotImplementedError("Sampling from %s is not supported yet." + % str(library)) + if not import_module(library): + raise ValueError("Failed to import %s" % library) + + samps = _get_sample_class_matrixrv[library](self, size, seed) + + if samps is not None: + return samps + raise NotImplementedError( + "Sampling for %s is not currently implemented from %s" + % (self.__class__.__name__, library) + ) + +################################################################################ +#------------------------Matrix Distribution Types-----------------------------# +################################################################################ + +#------------------------------------------------------------------------------- +# Matrix Gamma distribution ---------------------------------------------------- + +class MatrixGammaDistribution(MatrixDistribution): + + _argnames = ('alpha', 'beta', 'scale_matrix') + + @staticmethod + def check(alpha, beta, scale_matrix): + if not isinstance(scale_matrix, MatrixSymbol): + _value_check(scale_matrix.is_positive_definite, "The shape " + "matrix must be positive definite.") + _value_check(scale_matrix.is_square, "Should " + "be square matrix") + _value_check(alpha.is_positive, "Shape parameter should be positive.") + _value_check(beta.is_positive, "Scale parameter should be positive.") + + @property + def set(self): + k = self.scale_matrix.shape[0] + return MatrixSet(k, k, S.Reals) + + @property + def dimension(self): + return self.scale_matrix.shape + + def pdf(self, x): + alpha, beta, scale_matrix = self.alpha, self.beta, self.scale_matrix + p = scale_matrix.shape[0] + if isinstance(x, list): + x = ImmutableMatrix(x) + if not isinstance(x, (MatrixBase, MatrixSymbol)): + raise ValueError("%s should be an isinstance of Matrix " + "or MatrixSymbol" % str(x)) + sigma_inv_x = - Inverse(scale_matrix)*x / beta + term1 = exp(Trace(sigma_inv_x))/((beta**(p*alpha)) * multigamma(alpha, p)) + term2 = (Determinant(scale_matrix))**(-alpha) + term3 = (Determinant(x))**(alpha - S(p + 1)/2) + return term1 * term2 * term3 + +def MatrixGamma(symbol, alpha, beta, scale_matrix): + """ + Creates a random variable with Matrix Gamma Distribution. + + The density of the said distribution can be found at [1]. + + Parameters + ========== + + alpha: Positive Real number + Shape Parameter + beta: Positive Real number + Scale Parameter + scale_matrix: Positive definite real square matrix + Scale Matrix + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density, MatrixGamma + >>> from sympy import MatrixSymbol, symbols + >>> a, b = symbols('a b', positive=True) + >>> M = MatrixGamma('M', a, b, [[2, 1], [1, 2]]) + >>> X = MatrixSymbol('X', 2, 2) + >>> density(M)(X).doit() + exp(Trace(Matrix([ + [-2/3, 1/3], + [ 1/3, -2/3]])*X)/b)*Determinant(X)**(a - 3/2)/(3**a*sqrt(pi)*b**(2*a)*gamma(a)*gamma(a - 1/2)) + >>> density(M)([[1, 0], [0, 1]]).doit() + exp(-4/(3*b))/(3**a*sqrt(pi)*b**(2*a)*gamma(a)*gamma(a - 1/2)) + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Matrix_gamma_distribution + + """ + if isinstance(scale_matrix, list): + scale_matrix = ImmutableMatrix(scale_matrix) + return rv(symbol, MatrixGammaDistribution, (alpha, beta, scale_matrix)) + +#------------------------------------------------------------------------------- +# Wishart Distribution --------------------------------------------------------- + +class WishartDistribution(MatrixDistribution): + + _argnames = ('n', 'scale_matrix') + + @staticmethod + def check(n, scale_matrix): + if not isinstance(scale_matrix, MatrixSymbol): + _value_check(scale_matrix.is_positive_definite, "The shape " + "matrix must be positive definite.") + _value_check(scale_matrix.is_square, "Should " + "be square matrix") + _value_check(n.is_positive, "Shape parameter should be positive.") + + @property + def set(self): + k = self.scale_matrix.shape[0] + return MatrixSet(k, k, S.Reals) + + @property + def dimension(self): + return self.scale_matrix.shape + + def pdf(self, x): + n, scale_matrix = self.n, self.scale_matrix + p = scale_matrix.shape[0] + if isinstance(x, list): + x = ImmutableMatrix(x) + if not isinstance(x, (MatrixBase, MatrixSymbol)): + raise ValueError("%s should be an isinstance of Matrix " + "or MatrixSymbol" % str(x)) + sigma_inv_x = - Inverse(scale_matrix)*x / S(2) + term1 = exp(Trace(sigma_inv_x))/((2**(p*n/S(2))) * multigamma(n/S(2), p)) + term2 = (Determinant(scale_matrix))**(-n/S(2)) + term3 = (Determinant(x))**(S(n - p - 1)/2) + return term1 * term2 * term3 + +def Wishart(symbol, n, scale_matrix): + """ + Creates a random variable with Wishart Distribution. + + The density of the said distribution can be found at [1]. + + Parameters + ========== + + n: Positive Real number + Represents degrees of freedom + scale_matrix: Positive definite real square matrix + Scale Matrix + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy.stats import density, Wishart + >>> from sympy import MatrixSymbol, symbols + >>> n = symbols('n', positive=True) + >>> W = Wishart('W', n, [[2, 1], [1, 2]]) + >>> X = MatrixSymbol('X', 2, 2) + >>> density(W)(X).doit() + exp(Trace(Matrix([ + [-1/3, 1/6], + [ 1/6, -1/3]])*X))*Determinant(X)**(n/2 - 3/2)/(2**n*3**(n/2)*sqrt(pi)*gamma(n/2)*gamma(n/2 - 1/2)) + >>> density(W)([[1, 0], [0, 1]]).doit() + exp(-2/3)/(2**n*3**(n/2)*sqrt(pi)*gamma(n/2)*gamma(n/2 - 1/2)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Wishart_distribution + + """ + if isinstance(scale_matrix, list): + scale_matrix = ImmutableMatrix(scale_matrix) + return rv(symbol, WishartDistribution, (n, scale_matrix)) + +#------------------------------------------------------------------------------- +# Matrix Normal distribution --------------------------------------------------- + +class MatrixNormalDistribution(MatrixDistribution): + + _argnames = ('location_matrix', 'scale_matrix_1', 'scale_matrix_2') + + @staticmethod + def check(location_matrix, scale_matrix_1, scale_matrix_2): + if not isinstance(scale_matrix_1, MatrixSymbol): + _value_check(scale_matrix_1.is_positive_definite, "The shape " + "matrix must be positive definite.") + if not isinstance(scale_matrix_2, MatrixSymbol): + _value_check(scale_matrix_2.is_positive_definite, "The shape " + "matrix must be positive definite.") + _value_check(scale_matrix_1.is_square, "Scale matrix 1 should be " + "be square matrix") + _value_check(scale_matrix_2.is_square, "Scale matrix 2 should be " + "be square matrix") + n = location_matrix.shape[0] + p = location_matrix.shape[1] + _value_check(scale_matrix_1.shape[0] == n, "Scale matrix 1 should be" + " of shape %s x %s"% (str(n), str(n))) + _value_check(scale_matrix_2.shape[0] == p, "Scale matrix 2 should be" + " of shape %s x %s"% (str(p), str(p))) + + @property + def set(self): + n, p = self.location_matrix.shape + return MatrixSet(n, p, S.Reals) + + @property + def dimension(self): + return self.location_matrix.shape + + def pdf(self, x): + M, U, V = self.location_matrix, self.scale_matrix_1, self.scale_matrix_2 + n, p = M.shape + if isinstance(x, list): + x = ImmutableMatrix(x) + if not isinstance(x, (MatrixBase, MatrixSymbol)): + raise ValueError("%s should be an isinstance of Matrix " + "or MatrixSymbol" % str(x)) + term1 = Inverse(V)*Transpose(x - M)*Inverse(U)*(x - M) + num = exp(-Trace(term1)/S(2)) + den = (2*pi)**(S(n*p)/2) * Determinant(U)**(S(p)/2) * Determinant(V)**(S(n)/2) + return num/den + +def MatrixNormal(symbol, location_matrix, scale_matrix_1, scale_matrix_2): + """ + Creates a random variable with Matrix Normal Distribution. + + The density of the said distribution can be found at [1]. + + Parameters + ========== + + location_matrix: Real ``n x p`` matrix + Represents degrees of freedom + scale_matrix_1: Positive definite matrix + Scale Matrix of shape ``n x n`` + scale_matrix_2: Positive definite matrix + Scale Matrix of shape ``p x p`` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy import MatrixSymbol + >>> from sympy.stats import density, MatrixNormal + >>> M = MatrixNormal('M', [[1, 2]], [1], [[1, 0], [0, 1]]) + >>> X = MatrixSymbol('X', 1, 2) + >>> density(M)(X).doit() + exp(-Trace((Matrix([ + [-1], + [-2]]) + X.T)*(Matrix([[-1, -2]]) + X))/2)/(2*pi) + >>> density(M)([[3, 4]]).doit() + exp(-4)/(2*pi) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Matrix_normal_distribution + + """ + if isinstance(location_matrix, list): + location_matrix = ImmutableMatrix(location_matrix) + if isinstance(scale_matrix_1, list): + scale_matrix_1 = ImmutableMatrix(scale_matrix_1) + if isinstance(scale_matrix_2, list): + scale_matrix_2 = ImmutableMatrix(scale_matrix_2) + args = (location_matrix, scale_matrix_1, scale_matrix_2) + return rv(symbol, MatrixNormalDistribution, args) + +#------------------------------------------------------------------------------- +# Matrix Student's T distribution --------------------------------------------------- + +class MatrixStudentTDistribution(MatrixDistribution): + + _argnames = ('nu', 'location_matrix', 'scale_matrix_1', 'scale_matrix_2') + + @staticmethod + def check(nu, location_matrix, scale_matrix_1, scale_matrix_2): + if not isinstance(scale_matrix_1, MatrixSymbol): + _value_check(scale_matrix_1.is_positive_definite != False, "The shape " + "matrix must be positive definite.") + if not isinstance(scale_matrix_2, MatrixSymbol): + _value_check(scale_matrix_2.is_positive_definite != False, "The shape " + "matrix must be positive definite.") + _value_check(scale_matrix_1.is_square != False, "Scale matrix 1 should be " + "be square matrix") + _value_check(scale_matrix_2.is_square != False, "Scale matrix 2 should be " + "be square matrix") + n = location_matrix.shape[0] + p = location_matrix.shape[1] + _value_check(scale_matrix_1.shape[0] == p, "Scale matrix 1 should be" + " of shape %s x %s" % (str(p), str(p))) + _value_check(scale_matrix_2.shape[0] == n, "Scale matrix 2 should be" + " of shape %s x %s" % (str(n), str(n))) + _value_check(nu.is_positive != False, "Degrees of freedom must be positive") + + @property + def set(self): + n, p = self.location_matrix.shape + return MatrixSet(n, p, S.Reals) + + @property + def dimension(self): + return self.location_matrix.shape + + def pdf(self, x): + from sympy.matrices.dense import eye + if isinstance(x, list): + x = ImmutableMatrix(x) + if not isinstance(x, (MatrixBase, MatrixSymbol)): + raise ValueError("%s should be an isinstance of Matrix " + "or MatrixSymbol" % str(x)) + nu, M, Omega, Sigma = self.nu, self.location_matrix, self.scale_matrix_1, self.scale_matrix_2 + n, p = M.shape + + K = multigamma((nu + n + p - 1)/2, p) * Determinant(Omega)**(-n/2) * Determinant(Sigma)**(-p/2) \ + / ((pi)**(n*p/2) * multigamma((nu + p - 1)/2, p)) + return K * (Determinant(eye(n) + Inverse(Sigma)*(x - M)*Inverse(Omega)*Transpose(x - M))) \ + **(-(nu + n + p -1)/2) + + + +def MatrixStudentT(symbol, nu, location_matrix, scale_matrix_1, scale_matrix_2): + """ + Creates a random variable with Matrix Gamma Distribution. + + The density of the said distribution can be found at [1]. + + Parameters + ========== + + nu: Positive Real number + degrees of freedom + location_matrix: Positive definite real square matrix + Location Matrix of shape ``n x p`` + scale_matrix_1: Positive definite real square matrix + Scale Matrix of shape ``p x p`` + scale_matrix_2: Positive definite real square matrix + Scale Matrix of shape ``n x n`` + + Returns + ======= + + RandomSymbol + + Examples + ======== + + >>> from sympy import MatrixSymbol,symbols + >>> from sympy.stats import density, MatrixStudentT + >>> v = symbols('v',positive=True) + >>> M = MatrixStudentT('M', v, [[1, 2]], [[1, 0], [0, 1]], [1]) + >>> X = MatrixSymbol('X', 1, 2) + >>> density(M)(X) + gamma(v/2 + 1)*Determinant((Matrix([[-1, -2]]) + X)*(Matrix([ + [-1], + [-2]]) + X.T) + Matrix([[1]]))**(-v/2 - 1)/(pi**1.0*gamma(v/2)*Determinant(Matrix([[1]]))**1.0*Determinant(Matrix([ + [1, 0], + [0, 1]]))**0.5) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Matrix_t-distribution + + """ + if isinstance(location_matrix, list): + location_matrix = ImmutableMatrix(location_matrix) + if isinstance(scale_matrix_1, list): + scale_matrix_1 = ImmutableMatrix(scale_matrix_1) + if isinstance(scale_matrix_2, list): + scale_matrix_2 = ImmutableMatrix(scale_matrix_2) + args = (nu, location_matrix, scale_matrix_1, scale_matrix_2) + return rv(symbol, MatrixStudentTDistribution, args) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/random_matrix.py b/venv/lib/python3.10/site-packages/sympy/stats/random_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..fdd25cb9ad23fed9d3a85982b24bef33d04928f0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/random_matrix.py @@ -0,0 +1,30 @@ +from sympy.core.basic import Basic +from sympy.stats.rv import PSpace, _symbol_converter, RandomMatrixSymbol + +class RandomMatrixPSpace(PSpace): + """ + Represents probability space for + random matrices. It contains the mechanics + for handling the API calls for random matrices. + """ + def __new__(cls, sym, model=None): + sym = _symbol_converter(sym) + if model: + return Basic.__new__(cls, sym, model) + else: + return Basic.__new__(cls, sym) + + @property + def model(self): + try: + return self.args[1] + except IndexError: + return None + + def compute_density(self, expr, *args): + rms = expr.atoms(RandomMatrixSymbol) + if len(rms) > 2 or (not isinstance(expr, RandomMatrixSymbol)): + raise NotImplementedError("Currently, no algorithm has been " + "implemented to handle general expressions containing " + "multiple random matrices.") + return self.model.density(expr) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/random_matrix_models.py b/venv/lib/python3.10/site-packages/sympy/stats/random_matrix_models.py new file mode 100644 index 0000000000000000000000000000000000000000..6327a248ea5919c0bbb0ffc2c984105e04fe20e9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/random_matrix_models.py @@ -0,0 +1,457 @@ +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.function import Lambda +from sympy.core.numbers import (I, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import Integral +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.trace import Trace +from sympy.tensor.indexed import IndexedBase +from sympy.core.sympify import _sympify +from sympy.stats.rv import _symbol_converter, Density, RandomMatrixSymbol, is_random +from sympy.stats.joint_rv_types import JointDistributionHandmade +from sympy.stats.random_matrix import RandomMatrixPSpace +from sympy.tensor.array import ArrayComprehension + +__all__ = [ + 'CircularEnsemble', + 'CircularUnitaryEnsemble', + 'CircularOrthogonalEnsemble', + 'CircularSymplecticEnsemble', + 'GaussianEnsemble', + 'GaussianUnitaryEnsemble', + 'GaussianOrthogonalEnsemble', + 'GaussianSymplecticEnsemble', + 'joint_eigen_distribution', + 'JointEigenDistribution', + 'level_spacing_distribution' +] + +@is_random.register(RandomMatrixSymbol) +def _(x): + return True + + +class RandomMatrixEnsembleModel(Basic): + """ + Base class for random matrix ensembles. + It acts as an umbrella and contains + the methods common to all the ensembles + defined in sympy.stats.random_matrix_models. + """ + def __new__(cls, sym, dim=None): + sym, dim = _symbol_converter(sym), _sympify(dim) + if dim.is_integer == False: + raise ValueError("Dimension of the random matrices must be " + "integers, received %s instead."%(dim)) + return Basic.__new__(cls, sym, dim) + + symbol = property(lambda self: self.args[0]) + dimension = property(lambda self: self.args[1]) + + def density(self, expr): + return Density(expr) + + def __call__(self, expr): + return self.density(expr) + +class GaussianEnsembleModel(RandomMatrixEnsembleModel): + """ + Abstract class for Gaussian ensembles. + Contains the properties common to all the + gaussian ensembles. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Random_matrix#Gaussian_ensembles + .. [2] https://arxiv.org/pdf/1712.07903.pdf + """ + def _compute_normalization_constant(self, beta, n): + """ + Helper function for computing normalization + constant for joint probability density of eigen + values of Gaussian ensembles. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Selberg_integral#Mehta's_integral + """ + n = S(n) + prod_term = lambda j: gamma(1 + beta*S(j)/2)/gamma(S.One + beta/S(2)) + j = Dummy('j', integer=True, positive=True) + term1 = Product(prod_term(j), (j, 1, n)).doit() + term2 = (2/(beta*n))**(beta*n*(n - 1)/4 + n/2) + term3 = (2*pi)**(n/2) + return term1 * term2 * term3 + + def _compute_joint_eigen_distribution(self, beta): + """ + Helper function for computing the joint + probability distribution of eigen values + of the random matrix. + """ + n = self.dimension + Zbn = self._compute_normalization_constant(beta, n) + l = IndexedBase('l') + i = Dummy('i', integer=True, positive=True) + j = Dummy('j', integer=True, positive=True) + k = Dummy('k', integer=True, positive=True) + term1 = exp((-S(n)/2) * Sum(l[k]**2, (k, 1, n)).doit()) + sub_term = Lambda(i, Product(Abs(l[j] - l[i])**beta, (j, i + 1, n))) + term2 = Product(sub_term(i).doit(), (i, 1, n - 1)).doit() + syms = ArrayComprehension(l[k], (k, 1, n)).doit() + return Lambda(tuple(syms), (term1 * term2)/Zbn) + +class GaussianUnitaryEnsembleModel(GaussianEnsembleModel): + @property + def normalization_constant(self): + n = self.dimension + return 2**(S(n)/2) * pi**(S(n**2)/2) + + def density(self, expr): + n, ZGUE = self.dimension, self.normalization_constant + h_pspace = RandomMatrixPSpace('P', model=self) + H = RandomMatrixSymbol('H', n, n, pspace=h_pspace) + return Lambda(H, exp(-S(n)/2 * Trace(H**2))/ZGUE)(expr) + + def joint_eigen_distribution(self): + return self._compute_joint_eigen_distribution(S(2)) + + def level_spacing_distribution(self): + s = Dummy('s') + f = (32/pi**2)*(s**2)*exp((-4/pi)*s**2) + return Lambda(s, f) + +class GaussianOrthogonalEnsembleModel(GaussianEnsembleModel): + @property + def normalization_constant(self): + n = self.dimension + _H = MatrixSymbol('_H', n, n) + return Integral(exp(-S(n)/4 * Trace(_H**2))) + + def density(self, expr): + n, ZGOE = self.dimension, self.normalization_constant + h_pspace = RandomMatrixPSpace('P', model=self) + H = RandomMatrixSymbol('H', n, n, pspace=h_pspace) + return Lambda(H, exp(-S(n)/4 * Trace(H**2))/ZGOE)(expr) + + def joint_eigen_distribution(self): + return self._compute_joint_eigen_distribution(S.One) + + def level_spacing_distribution(self): + s = Dummy('s') + f = (pi/2)*s*exp((-pi/4)*s**2) + return Lambda(s, f) + +class GaussianSymplecticEnsembleModel(GaussianEnsembleModel): + @property + def normalization_constant(self): + n = self.dimension + _H = MatrixSymbol('_H', n, n) + return Integral(exp(-S(n) * Trace(_H**2))) + + def density(self, expr): + n, ZGSE = self.dimension, self.normalization_constant + h_pspace = RandomMatrixPSpace('P', model=self) + H = RandomMatrixSymbol('H', n, n, pspace=h_pspace) + return Lambda(H, exp(-S(n) * Trace(H**2))/ZGSE)(expr) + + def joint_eigen_distribution(self): + return self._compute_joint_eigen_distribution(S(4)) + + def level_spacing_distribution(self): + s = Dummy('s') + f = ((S(2)**18)/((S(3)**6)*(pi**3)))*(s**4)*exp((-64/(9*pi))*s**2) + return Lambda(s, f) + +def GaussianEnsemble(sym, dim): + sym, dim = _symbol_converter(sym), _sympify(dim) + model = GaussianEnsembleModel(sym, dim) + rmp = RandomMatrixPSpace(sym, model=model) + return RandomMatrixSymbol(sym, dim, dim, pspace=rmp) + +def GaussianUnitaryEnsemble(sym, dim): + """ + Represents Gaussian Unitary Ensembles. + + Examples + ======== + + >>> from sympy.stats import GaussianUnitaryEnsemble as GUE, density + >>> from sympy import MatrixSymbol + >>> G = GUE('U', 2) + >>> X = MatrixSymbol('X', 2, 2) + >>> density(G)(X) + exp(-Trace(X**2))/(2*pi**2) + """ + sym, dim = _symbol_converter(sym), _sympify(dim) + model = GaussianUnitaryEnsembleModel(sym, dim) + rmp = RandomMatrixPSpace(sym, model=model) + return RandomMatrixSymbol(sym, dim, dim, pspace=rmp) + +def GaussianOrthogonalEnsemble(sym, dim): + """ + Represents Gaussian Orthogonal Ensembles. + + Examples + ======== + + >>> from sympy.stats import GaussianOrthogonalEnsemble as GOE, density + >>> from sympy import MatrixSymbol + >>> G = GOE('U', 2) + >>> X = MatrixSymbol('X', 2, 2) + >>> density(G)(X) + exp(-Trace(X**2)/2)/Integral(exp(-Trace(_H**2)/2), _H) + """ + sym, dim = _symbol_converter(sym), _sympify(dim) + model = GaussianOrthogonalEnsembleModel(sym, dim) + rmp = RandomMatrixPSpace(sym, model=model) + return RandomMatrixSymbol(sym, dim, dim, pspace=rmp) + +def GaussianSymplecticEnsemble(sym, dim): + """ + Represents Gaussian Symplectic Ensembles. + + Examples + ======== + + >>> from sympy.stats import GaussianSymplecticEnsemble as GSE, density + >>> from sympy import MatrixSymbol + >>> G = GSE('U', 2) + >>> X = MatrixSymbol('X', 2, 2) + >>> density(G)(X) + exp(-2*Trace(X**2))/Integral(exp(-2*Trace(_H**2)), _H) + """ + sym, dim = _symbol_converter(sym), _sympify(dim) + model = GaussianSymplecticEnsembleModel(sym, dim) + rmp = RandomMatrixPSpace(sym, model=model) + return RandomMatrixSymbol(sym, dim, dim, pspace=rmp) + +class CircularEnsembleModel(RandomMatrixEnsembleModel): + """ + Abstract class for Circular ensembles. + Contains the properties and methods + common to all the circular ensembles. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Circular_ensemble + """ + def density(self, expr): + # TODO : Add support for Lie groups(as extensions of sympy.diffgeom) + # and define measures on them + raise NotImplementedError("Support for Haar measure hasn't been " + "implemented yet, therefore the density of " + "%s cannot be computed."%(self)) + + def _compute_joint_eigen_distribution(self, beta): + """ + Helper function to compute the joint distribution of phases + of the complex eigen values of matrices belonging to any + circular ensembles. + """ + n = self.dimension + Zbn = ((2*pi)**n)*(gamma(beta*n/2 + 1)/S(gamma(beta/2 + 1))**n) + t = IndexedBase('t') + i, j, k = (Dummy('i', integer=True), Dummy('j', integer=True), + Dummy('k', integer=True)) + syms = ArrayComprehension(t[i], (i, 1, n)).doit() + f = Product(Product(Abs(exp(I*t[k]) - exp(I*t[j]))**beta, (j, k + 1, n)).doit(), + (k, 1, n - 1)).doit() + return Lambda(tuple(syms), f/Zbn) + +class CircularUnitaryEnsembleModel(CircularEnsembleModel): + def joint_eigen_distribution(self): + return self._compute_joint_eigen_distribution(S(2)) + +class CircularOrthogonalEnsembleModel(CircularEnsembleModel): + def joint_eigen_distribution(self): + return self._compute_joint_eigen_distribution(S.One) + +class CircularSymplecticEnsembleModel(CircularEnsembleModel): + def joint_eigen_distribution(self): + return self._compute_joint_eigen_distribution(S(4)) + +def CircularEnsemble(sym, dim): + sym, dim = _symbol_converter(sym), _sympify(dim) + model = CircularEnsembleModel(sym, dim) + rmp = RandomMatrixPSpace(sym, model=model) + return RandomMatrixSymbol(sym, dim, dim, pspace=rmp) + +def CircularUnitaryEnsemble(sym, dim): + """ + Represents Circular Unitary Ensembles. + + Examples + ======== + + >>> from sympy.stats import CircularUnitaryEnsemble as CUE + >>> from sympy.stats import joint_eigen_distribution + >>> C = CUE('U', 1) + >>> joint_eigen_distribution(C) + Lambda(t[1], Product(Abs(exp(I*t[_j]) - exp(I*t[_k]))**2, (_j, _k + 1, 1), (_k, 1, 0))/(2*pi)) + + Note + ==== + + As can be seen above in the example, density of CiruclarUnitaryEnsemble + is not evaluated because the exact definition is based on haar measure of + unitary group which is not unique. + """ + sym, dim = _symbol_converter(sym), _sympify(dim) + model = CircularUnitaryEnsembleModel(sym, dim) + rmp = RandomMatrixPSpace(sym, model=model) + return RandomMatrixSymbol(sym, dim, dim, pspace=rmp) + +def CircularOrthogonalEnsemble(sym, dim): + """ + Represents Circular Orthogonal Ensembles. + + Examples + ======== + + >>> from sympy.stats import CircularOrthogonalEnsemble as COE + >>> from sympy.stats import joint_eigen_distribution + >>> C = COE('O', 1) + >>> joint_eigen_distribution(C) + Lambda(t[1], Product(Abs(exp(I*t[_j]) - exp(I*t[_k])), (_j, _k + 1, 1), (_k, 1, 0))/(2*pi)) + + Note + ==== + + As can be seen above in the example, density of CiruclarOrthogonalEnsemble + is not evaluated because the exact definition is based on haar measure of + unitary group which is not unique. + """ + sym, dim = _symbol_converter(sym), _sympify(dim) + model = CircularOrthogonalEnsembleModel(sym, dim) + rmp = RandomMatrixPSpace(sym, model=model) + return RandomMatrixSymbol(sym, dim, dim, pspace=rmp) + +def CircularSymplecticEnsemble(sym, dim): + """ + Represents Circular Symplectic Ensembles. + + Examples + ======== + + >>> from sympy.stats import CircularSymplecticEnsemble as CSE + >>> from sympy.stats import joint_eigen_distribution + >>> C = CSE('S', 1) + >>> joint_eigen_distribution(C) + Lambda(t[1], Product(Abs(exp(I*t[_j]) - exp(I*t[_k]))**4, (_j, _k + 1, 1), (_k, 1, 0))/(2*pi)) + + Note + ==== + + As can be seen above in the example, density of CiruclarSymplecticEnsemble + is not evaluated because the exact definition is based on haar measure of + unitary group which is not unique. + """ + sym, dim = _symbol_converter(sym), _sympify(dim) + model = CircularSymplecticEnsembleModel(sym, dim) + rmp = RandomMatrixPSpace(sym, model=model) + return RandomMatrixSymbol(sym, dim, dim, pspace=rmp) + +def joint_eigen_distribution(mat): + """ + For obtaining joint probability distribution + of eigen values of random matrix. + + Parameters + ========== + + mat: RandomMatrixSymbol + The matrix symbol whose eigen values are to be considered. + + Returns + ======= + + Lambda + + Examples + ======== + + >>> from sympy.stats import GaussianUnitaryEnsemble as GUE + >>> from sympy.stats import joint_eigen_distribution + >>> U = GUE('U', 2) + >>> joint_eigen_distribution(U) + Lambda((l[1], l[2]), exp(-l[1]**2 - l[2]**2)*Product(Abs(l[_i] - l[_j])**2, (_j, _i + 1, 2), (_i, 1, 1))/pi) + """ + if not isinstance(mat, RandomMatrixSymbol): + raise ValueError("%s is not of type, RandomMatrixSymbol."%(mat)) + return mat.pspace.model.joint_eigen_distribution() + +def JointEigenDistribution(mat): + """ + Creates joint distribution of eigen values of matrices with random + expressions. + + Parameters + ========== + + mat: Matrix + The matrix under consideration. + + Returns + ======= + + JointDistributionHandmade + + Examples + ======== + + >>> from sympy.stats import Normal, JointEigenDistribution + >>> from sympy import Matrix + >>> A = [[Normal('A00', 0, 1), Normal('A01', 0, 1)], + ... [Normal('A10', 0, 1), Normal('A11', 0, 1)]] + >>> JointEigenDistribution(Matrix(A)) + JointDistributionHandmade(-sqrt(A00**2 - 2*A00*A11 + 4*A01*A10 + A11**2)/2 + + A00/2 + A11/2, sqrt(A00**2 - 2*A00*A11 + 4*A01*A10 + A11**2)/2 + A00/2 + A11/2) + + """ + eigenvals = mat.eigenvals(multiple=True) + if not all(is_random(eigenval) for eigenval in set(eigenvals)): + raise ValueError("Eigen values do not have any random expression, " + "joint distribution cannot be generated.") + return JointDistributionHandmade(*eigenvals) + +def level_spacing_distribution(mat): + """ + For obtaining distribution of level spacings. + + Parameters + ========== + + mat: RandomMatrixSymbol + The random matrix symbol whose eigen values are + to be considered for finding the level spacings. + + Returns + ======= + + Lambda + + Examples + ======== + + >>> from sympy.stats import GaussianUnitaryEnsemble as GUE + >>> from sympy.stats import level_spacing_distribution + >>> U = GUE('U', 2) + >>> level_spacing_distribution(U) + Lambda(_s, 32*_s**2*exp(-4*_s**2/pi)/pi**2) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Random_matrix#Distribution_of_level_spacings + """ + return mat.pspace.model.level_spacing_distribution() diff --git a/venv/lib/python3.10/site-packages/sympy/stats/rv.py b/venv/lib/python3.10/site-packages/sympy/stats/rv.py new file mode 100644 index 0000000000000000000000000000000000000000..75ab54deb551b7ff3d4d06f37482a1f16a789ba6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/rv.py @@ -0,0 +1,1798 @@ +""" +Main Random Variables Module + +Defines abstract random variable type. +Contains interfaces for probability space object (PSpace) as well as standard +operators, P, E, sample, density, where, quantile + +See Also +======== + +sympy.stats.crv +sympy.stats.frv +sympy.stats.rv_interface +""" + +from __future__ import annotations +from functools import singledispatch +from math import prod + +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.expr import Expr +from sympy.core.function import (Function, Lambda) +from sympy.core.logic import fuzzy_and +from sympy.core.mul import Mul +from sympy.core.relational import (Eq, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.core.sympify import sympify +from sympy.functions.special.delta_functions import DiracDelta +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.logic.boolalg import (And, Or) +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.tensor.indexed import Indexed +from sympy.utilities.lambdify import lambdify +from sympy.core.relational import Relational +from sympy.core.sympify import _sympify +from sympy.sets.sets import FiniteSet, ProductSet, Intersection +from sympy.solvers.solveset import solveset +from sympy.external import import_module +from sympy.utilities.decorator import doctest_depends_on +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import iterable + + +__doctest_requires__ = {('sample',): ['scipy']} + + +x = Symbol('x') + + +@singledispatch +def is_random(x): + return False + + +@is_random.register(Basic) +def _(x): + atoms = x.free_symbols + return any(is_random(i) for i in atoms) + + +class RandomDomain(Basic): + """ + Represents a set of variables and the values which they can take. + + See Also + ======== + + sympy.stats.crv.ContinuousDomain + sympy.stats.frv.FiniteDomain + """ + + is_ProductDomain = False + is_Finite = False + is_Continuous = False + is_Discrete = False + + def __new__(cls, symbols, *args): + symbols = FiniteSet(*symbols) + return Basic.__new__(cls, symbols, *args) + + @property + def symbols(self): + return self.args[0] + + @property + def set(self): + return self.args[1] + + def __contains__(self, other): + raise NotImplementedError() + + def compute_expectation(self, expr): + raise NotImplementedError() + + +class SingleDomain(RandomDomain): + """ + A single variable and its domain. + + See Also + ======== + + sympy.stats.crv.SingleContinuousDomain + sympy.stats.frv.SingleFiniteDomain + """ + def __new__(cls, symbol, set): + assert symbol.is_Symbol + return Basic.__new__(cls, symbol, set) + + @property + def symbol(self): + return self.args[0] + + @property + def symbols(self): + return FiniteSet(self.symbol) + + def __contains__(self, other): + if len(other) != 1: + return False + sym, val = tuple(other)[0] + return self.symbol == sym and val in self.set + + +class MatrixDomain(RandomDomain): + """ + A Random Matrix variable and its domain. + + """ + def __new__(cls, symbol, set): + symbol, set = _symbol_converter(symbol), _sympify(set) + return Basic.__new__(cls, symbol, set) + + @property + def symbol(self): + return self.args[0] + + @property + def symbols(self): + return FiniteSet(self.symbol) + + +class ConditionalDomain(RandomDomain): + """ + A RandomDomain with an attached condition. + + See Also + ======== + + sympy.stats.crv.ConditionalContinuousDomain + sympy.stats.frv.ConditionalFiniteDomain + """ + def __new__(cls, fulldomain, condition): + condition = condition.xreplace({rs: rs.symbol + for rs in random_symbols(condition)}) + return Basic.__new__(cls, fulldomain, condition) + + @property + def symbols(self): + return self.fulldomain.symbols + + @property + def fulldomain(self): + return self.args[0] + + @property + def condition(self): + return self.args[1] + + @property + def set(self): + raise NotImplementedError("Set of Conditional Domain not Implemented") + + def as_boolean(self): + return And(self.fulldomain.as_boolean(), self.condition) + + +class PSpace(Basic): + """ + A Probability Space. + + Explanation + =========== + + Probability Spaces encode processes that equal different values + probabilistically. These underly Random Symbols which occur in SymPy + expressions and contain the mechanics to evaluate statistical statements. + + See Also + ======== + + sympy.stats.crv.ContinuousPSpace + sympy.stats.frv.FinitePSpace + """ + + is_Finite: bool | None = None # Fails test if not set to None + is_Continuous: bool | None = None # Fails test if not set to None + is_Discrete: bool | None = None # Fails test if not set to None + is_real: bool | None + + @property + def domain(self): + return self.args[0] + + @property + def density(self): + return self.args[1] + + @property + def values(self): + return frozenset(RandomSymbol(sym, self) for sym in self.symbols) + + @property + def symbols(self): + return self.domain.symbols + + def where(self, condition): + raise NotImplementedError() + + def compute_density(self, expr): + raise NotImplementedError() + + def sample(self, size=(), library='scipy', seed=None): + raise NotImplementedError() + + def probability(self, condition): + raise NotImplementedError() + + def compute_expectation(self, expr): + raise NotImplementedError() + + +class SinglePSpace(PSpace): + """ + Represents the probabilities of a set of random events that can be + attributed to a single variable/symbol. + """ + def __new__(cls, s, distribution): + s = _symbol_converter(s) + return Basic.__new__(cls, s, distribution) + + @property + def value(self): + return RandomSymbol(self.symbol, self) + + @property + def symbol(self): + return self.args[0] + + @property + def distribution(self): + return self.args[1] + + @property + def pdf(self): + return self.distribution.pdf(self.symbol) + + +class RandomSymbol(Expr): + """ + Random Symbols represent ProbabilitySpaces in SymPy Expressions. + In principle they can take on any value that their symbol can take on + within the associated PSpace with probability determined by the PSpace + Density. + + Explanation + =========== + + Random Symbols contain pspace and symbol properties. + The pspace property points to the represented Probability Space + The symbol is a standard SymPy Symbol that is used in that probability space + for example in defining a density. + + You can form normal SymPy expressions using RandomSymbols and operate on + those expressions with the Functions + + E - Expectation of a random expression + P - Probability of a condition + density - Probability Density of an expression + given - A new random expression (with new random symbols) given a condition + + An object of the RandomSymbol type should almost never be created by the + user. They tend to be created instead by the PSpace class's value method. + Traditionally a user does not even do this but instead calls one of the + convenience functions Normal, Exponential, Coin, Die, FiniteRV, etc.... + """ + + def __new__(cls, symbol, pspace=None): + from sympy.stats.joint_rv import JointRandomSymbol + if pspace is None: + # Allow single arg, representing pspace == PSpace() + pspace = PSpace() + symbol = _symbol_converter(symbol) + if not isinstance(pspace, PSpace): + raise TypeError("pspace variable should be of type PSpace") + if cls == JointRandomSymbol and isinstance(pspace, SinglePSpace): + cls = RandomSymbol + return Basic.__new__(cls, symbol, pspace) + + is_finite = True + is_symbol = True + is_Atom = True + + _diff_wrt = True + + pspace = property(lambda self: self.args[1]) + symbol = property(lambda self: self.args[0]) + name = property(lambda self: self.symbol.name) + + def _eval_is_positive(self): + return self.symbol.is_positive + + def _eval_is_integer(self): + return self.symbol.is_integer + + def _eval_is_real(self): + return self.symbol.is_real or self.pspace.is_real + + @property + def is_commutative(self): + return self.symbol.is_commutative + + @property + def free_symbols(self): + return {self} + +class RandomIndexedSymbol(RandomSymbol): + + def __new__(cls, idx_obj, pspace=None): + if pspace is None: + # Allow single arg, representing pspace == PSpace() + pspace = PSpace() + if not isinstance(idx_obj, (Indexed, Function)): + raise TypeError("An Function or Indexed object is expected not %s"%(idx_obj)) + return Basic.__new__(cls, idx_obj, pspace) + + symbol = property(lambda self: self.args[0]) + name = property(lambda self: str(self.args[0])) + + @property + def key(self): + if isinstance(self.symbol, Indexed): + return self.symbol.args[1] + elif isinstance(self.symbol, Function): + return self.symbol.args[0] + + @property + def free_symbols(self): + if self.key.free_symbols: + free_syms = self.key.free_symbols + free_syms.add(self) + return free_syms + return {self} + + @property + def pspace(self): + return self.args[1] + +class RandomMatrixSymbol(RandomSymbol, MatrixSymbol): # type: ignore + def __new__(cls, symbol, n, m, pspace=None): + n, m = _sympify(n), _sympify(m) + symbol = _symbol_converter(symbol) + if pspace is None: + # Allow single arg, representing pspace == PSpace() + pspace = PSpace() + return Basic.__new__(cls, symbol, n, m, pspace) + + symbol = property(lambda self: self.args[0]) + pspace = property(lambda self: self.args[3]) + + +class ProductPSpace(PSpace): + """ + Abstract class for representing probability spaces with multiple random + variables. + + See Also + ======== + + sympy.stats.rv.IndependentProductPSpace + sympy.stats.joint_rv.JointPSpace + """ + pass + +class IndependentProductPSpace(ProductPSpace): + """ + A probability space resulting from the merger of two independent probability + spaces. + + Often created using the function, pspace. + """ + + def __new__(cls, *spaces): + rs_space_dict = {} + for space in spaces: + for value in space.values: + rs_space_dict[value] = space + + symbols = FiniteSet(*[val.symbol for val in rs_space_dict.keys()]) + + # Overlapping symbols + from sympy.stats.joint_rv import MarginalDistribution + from sympy.stats.compound_rv import CompoundDistribution + if len(symbols) < sum(len(space.symbols) for space in spaces if not + isinstance(space.distribution, ( + CompoundDistribution, MarginalDistribution))): + raise ValueError("Overlapping Random Variables") + + if all(space.is_Finite for space in spaces): + from sympy.stats.frv import ProductFinitePSpace + cls = ProductFinitePSpace + + obj = Basic.__new__(cls, *FiniteSet(*spaces)) + + return obj + + @property + def pdf(self): + p = Mul(*[space.pdf for space in self.spaces]) + return p.subs({rv: rv.symbol for rv in self.values}) + + @property + def rs_space_dict(self): + d = {} + for space in self.spaces: + for value in space.values: + d[value] = space + return d + + @property + def symbols(self): + return FiniteSet(*[val.symbol for val in self.rs_space_dict.keys()]) + + @property + def spaces(self): + return FiniteSet(*self.args) + + @property + def values(self): + return sumsets(space.values for space in self.spaces) + + def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs): + rvs = rvs or self.values + rvs = frozenset(rvs) + for space in self.spaces: + expr = space.compute_expectation(expr, rvs & space.values, evaluate=False, **kwargs) + if evaluate and hasattr(expr, 'doit'): + return expr.doit(**kwargs) + return expr + + @property + def domain(self): + return ProductDomain(*[space.domain for space in self.spaces]) + + @property + def density(self): + raise NotImplementedError("Density not available for ProductSpaces") + + def sample(self, size=(), library='scipy', seed=None): + return {k: v for space in self.spaces + for k, v in space.sample(size=size, library=library, seed=seed).items()} + + + def probability(self, condition, **kwargs): + cond_inv = False + if isinstance(condition, Ne): + condition = Eq(condition.args[0], condition.args[1]) + cond_inv = True + elif isinstance(condition, And): # they are independent + return Mul(*[self.probability(arg) for arg in condition.args]) + elif isinstance(condition, Or): # they are independent + return Add(*[self.probability(arg) for arg in condition.args]) + expr = condition.lhs - condition.rhs + rvs = random_symbols(expr) + dens = self.compute_density(expr) + if any(pspace(rv).is_Continuous for rv in rvs): + from sympy.stats.crv import SingleContinuousPSpace + from sympy.stats.crv_types import ContinuousDistributionHandmade + if expr in self.values: + # Marginalize all other random symbols out of the density + randomsymbols = tuple(set(self.values) - frozenset([expr])) + symbols = tuple(rs.symbol for rs in randomsymbols) + pdf = self.domain.integrate(self.pdf, symbols, **kwargs) + return Lambda(expr.symbol, pdf) + dens = ContinuousDistributionHandmade(dens) + z = Dummy('z', real=True) + space = SingleContinuousPSpace(z, dens) + result = space.probability(condition.__class__(space.value, 0)) + else: + from sympy.stats.drv import SingleDiscretePSpace + from sympy.stats.drv_types import DiscreteDistributionHandmade + dens = DiscreteDistributionHandmade(dens) + z = Dummy('z', integer=True) + space = SingleDiscretePSpace(z, dens) + result = space.probability(condition.__class__(space.value, 0)) + return result if not cond_inv else S.One - result + + def compute_density(self, expr, **kwargs): + rvs = random_symbols(expr) + if any(pspace(rv).is_Continuous for rv in rvs): + z = Dummy('z', real=True) + expr = self.compute_expectation(DiracDelta(expr - z), + **kwargs) + else: + z = Dummy('z', integer=True) + expr = self.compute_expectation(KroneckerDelta(expr, z), + **kwargs) + return Lambda(z, expr) + + def compute_cdf(self, expr, **kwargs): + raise ValueError("CDF not well defined on multivariate expressions") + + def conditional_space(self, condition, normalize=True, **kwargs): + rvs = random_symbols(condition) + condition = condition.xreplace({rv: rv.symbol for rv in self.values}) + pspaces = [pspace(rv) for rv in rvs] + if any(ps.is_Continuous for ps in pspaces): + from sympy.stats.crv import (ConditionalContinuousDomain, + ContinuousPSpace) + space = ContinuousPSpace + domain = ConditionalContinuousDomain(self.domain, condition) + elif any(ps.is_Discrete for ps in pspaces): + from sympy.stats.drv import (ConditionalDiscreteDomain, + DiscretePSpace) + space = DiscretePSpace + domain = ConditionalDiscreteDomain(self.domain, condition) + elif all(ps.is_Finite for ps in pspaces): + from sympy.stats.frv import FinitePSpace + return FinitePSpace.conditional_space(self, condition) + if normalize: + replacement = {rv: Dummy(str(rv)) for rv in self.symbols} + norm = domain.compute_expectation(self.pdf, **kwargs) + pdf = self.pdf / norm.xreplace(replacement) + # XXX: Converting symbols from set to tuple. The order matters to + # Lambda though so we shouldn't be starting with a set here... + density = Lambda(tuple(domain.symbols), pdf) + + return space(domain, density) + +class ProductDomain(RandomDomain): + """ + A domain resulting from the merger of two independent domains. + + See Also + ======== + sympy.stats.crv.ProductContinuousDomain + sympy.stats.frv.ProductFiniteDomain + """ + is_ProductDomain = True + + def __new__(cls, *domains): + # Flatten any product of products + domains2 = [] + for domain in domains: + if not domain.is_ProductDomain: + domains2.append(domain) + else: + domains2.extend(domain.domains) + domains2 = FiniteSet(*domains2) + + if all(domain.is_Finite for domain in domains2): + from sympy.stats.frv import ProductFiniteDomain + cls = ProductFiniteDomain + if all(domain.is_Continuous for domain in domains2): + from sympy.stats.crv import ProductContinuousDomain + cls = ProductContinuousDomain + if all(domain.is_Discrete for domain in domains2): + from sympy.stats.drv import ProductDiscreteDomain + cls = ProductDiscreteDomain + + return Basic.__new__(cls, *domains2) + + @property + def sym_domain_dict(self): + return {symbol: domain for domain in self.domains + for symbol in domain.symbols} + + @property + def symbols(self): + return FiniteSet(*[sym for domain in self.domains + for sym in domain.symbols]) + + @property + def domains(self): + return self.args + + @property + def set(self): + return ProductSet(*(domain.set for domain in self.domains)) + + def __contains__(self, other): + # Split event into each subdomain + for domain in self.domains: + # Collect the parts of this event which associate to this domain + elem = frozenset([item for item in other + if sympify(domain.symbols.contains(item[0])) + is S.true]) + # Test this sub-event + if elem not in domain: + return False + # All subevents passed + return True + + def as_boolean(self): + return And(*[domain.as_boolean() for domain in self.domains]) + + +def random_symbols(expr): + """ + Returns all RandomSymbols within a SymPy Expression. + """ + atoms = getattr(expr, 'atoms', None) + if atoms is not None: + comp = lambda rv: rv.symbol.name + l = list(atoms(RandomSymbol)) + return sorted(l, key=comp) + else: + return [] + + +def pspace(expr): + """ + Returns the underlying Probability Space of a random expression. + + For internal use. + + Examples + ======== + + >>> from sympy.stats import pspace, Normal + >>> X = Normal('X', 0, 1) + >>> pspace(2*X + 1) == X.pspace + True + """ + expr = sympify(expr) + if isinstance(expr, RandomSymbol) and expr.pspace is not None: + return expr.pspace + if expr.has(RandomMatrixSymbol): + rm = list(expr.atoms(RandomMatrixSymbol))[0] + return rm.pspace + + rvs = random_symbols(expr) + if not rvs: + raise ValueError("Expression containing Random Variable expected, not %s" % (expr)) + # If only one space present + if all(rv.pspace == rvs[0].pspace for rv in rvs): + return rvs[0].pspace + from sympy.stats.compound_rv import CompoundPSpace + from sympy.stats.stochastic_process import StochasticPSpace + for rv in rvs: + if isinstance(rv.pspace, (CompoundPSpace, StochasticPSpace)): + return rv.pspace + # Otherwise make a product space + return IndependentProductPSpace(*[rv.pspace for rv in rvs]) + + +def sumsets(sets): + """ + Union of sets + """ + return frozenset().union(*sets) + + +def rs_swap(a, b): + """ + Build a dictionary to swap RandomSymbols based on their underlying symbol. + + i.e. + if ``X = ('x', pspace1)`` + and ``Y = ('x', pspace2)`` + then ``X`` and ``Y`` match and the key, value pair + ``{X:Y}`` will appear in the result + + Inputs: collections a and b of random variables which share common symbols + Output: dict mapping RVs in a to RVs in b + """ + d = {} + for rsa in a: + d[rsa] = [rsb for rsb in b if rsa.symbol == rsb.symbol][0] + return d + + +def given(expr, condition=None, **kwargs): + r""" Conditional Random Expression. + + Explanation + =========== + + From a random expression and a condition on that expression creates a new + probability space from the condition and returns the same expression on that + conditional probability space. + + Examples + ======== + + >>> from sympy.stats import given, density, Die + >>> X = Die('X', 6) + >>> Y = given(X, X > 3) + >>> density(Y).dict + {4: 1/3, 5: 1/3, 6: 1/3} + + Following convention, if the condition is a random symbol then that symbol + is considered fixed. + + >>> from sympy.stats import Normal + >>> from sympy import pprint + >>> from sympy.abc import z + + >>> X = Normal('X', 0, 1) + >>> Y = Normal('Y', 0, 1) + >>> pprint(density(X + Y, Y)(z), use_unicode=False) + 2 + -(-Y + z) + ----------- + ___ 2 + \/ 2 *e + ------------------ + ____ + 2*\/ pi + """ + + if not is_random(condition) or pspace_independent(expr, condition): + return expr + + if isinstance(condition, RandomSymbol): + condition = Eq(condition, condition.symbol) + + condsymbols = random_symbols(condition) + if (isinstance(condition, Eq) and len(condsymbols) == 1 and + not isinstance(pspace(expr).domain, ConditionalDomain)): + rv = tuple(condsymbols)[0] + + results = solveset(condition, rv) + if isinstance(results, Intersection) and S.Reals in results.args: + results = list(results.args[1]) + + sums = 0 + for res in results: + temp = expr.subs(rv, res) + if temp == True: + return True + if temp != False: + # XXX: This seems nonsensical but preserves existing behaviour + # after the change that Relational is no longer a subclass of + # Expr. Here expr is sometimes Relational and sometimes Expr + # but we are trying to add them with +=. This needs to be + # fixed somehow. + if sums == 0 and isinstance(expr, Relational): + sums = expr.subs(rv, res) + else: + sums += expr.subs(rv, res) + if sums == 0: + return False + return sums + + # Get full probability space of both the expression and the condition + fullspace = pspace(Tuple(expr, condition)) + # Build new space given the condition + space = fullspace.conditional_space(condition, **kwargs) + # Dictionary to swap out RandomSymbols in expr with new RandomSymbols + # That point to the new conditional space + swapdict = rs_swap(fullspace.values, space.values) + # Swap random variables in the expression + expr = expr.xreplace(swapdict) + return expr + + +def expectation(expr, condition=None, numsamples=None, evaluate=True, **kwargs): + """ + Returns the expected value of a random expression. + + Parameters + ========== + + expr : Expr containing RandomSymbols + The expression of which you want to compute the expectation value + given : Expr containing RandomSymbols + A conditional expression. E(X, X>0) is expectation of X given X > 0 + numsamples : int + Enables sampling and approximates the expectation with this many samples + evalf : Bool (defaults to True) + If sampling return a number rather than a complex expression + evaluate : Bool (defaults to True) + In case of continuous systems return unevaluated integral + + Examples + ======== + + >>> from sympy.stats import E, Die + >>> X = Die('X', 6) + >>> E(X) + 7/2 + >>> E(2*X + 1) + 8 + + >>> E(X, X > 3) # Expectation of X given that it is above 3 + 5 + """ + + if not is_random(expr): # expr isn't random? + return expr + kwargs['numsamples'] = numsamples + from sympy.stats.symbolic_probability import Expectation + if evaluate: + return Expectation(expr, condition).doit(**kwargs) + return Expectation(expr, condition) + + +def probability(condition, given_condition=None, numsamples=None, + evaluate=True, **kwargs): + """ + Probability that a condition is true, optionally given a second condition. + + Parameters + ========== + + condition : Combination of Relationals containing RandomSymbols + The condition of which you want to compute the probability + given_condition : Combination of Relationals containing RandomSymbols + A conditional expression. P(X > 1, X > 0) is expectation of X > 1 + given X > 0 + numsamples : int + Enables sampling and approximates the probability with this many samples + evaluate : Bool (defaults to True) + In case of continuous systems return unevaluated integral + + Examples + ======== + + >>> from sympy.stats import P, Die + >>> from sympy import Eq + >>> X, Y = Die('X', 6), Die('Y', 6) + >>> P(X > 3) + 1/2 + >>> P(Eq(X, 5), X > 2) # Probability that X == 5 given that X > 2 + 1/4 + >>> P(X > Y) + 5/12 + """ + + kwargs['numsamples'] = numsamples + from sympy.stats.symbolic_probability import Probability + if evaluate: + return Probability(condition, given_condition).doit(**kwargs) + return Probability(condition, given_condition) + + +class Density(Basic): + expr = property(lambda self: self.args[0]) + + def __new__(cls, expr, condition = None): + expr = _sympify(expr) + if condition is None: + obj = Basic.__new__(cls, expr) + else: + condition = _sympify(condition) + obj = Basic.__new__(cls, expr, condition) + return obj + + @property + def condition(self): + if len(self.args) > 1: + return self.args[1] + else: + return None + + def doit(self, evaluate=True, **kwargs): + from sympy.stats.random_matrix import RandomMatrixPSpace + from sympy.stats.joint_rv import JointPSpace + from sympy.stats.matrix_distributions import MatrixPSpace + from sympy.stats.compound_rv import CompoundPSpace + from sympy.stats.frv import SingleFiniteDistribution + expr, condition = self.expr, self.condition + + if isinstance(expr, SingleFiniteDistribution): + return expr.dict + if condition is not None: + # Recompute on new conditional expr + expr = given(expr, condition, **kwargs) + if not random_symbols(expr): + return Lambda(x, DiracDelta(x - expr)) + if isinstance(expr, RandomSymbol): + if isinstance(expr.pspace, (SinglePSpace, JointPSpace, MatrixPSpace)) and \ + hasattr(expr.pspace, 'distribution'): + return expr.pspace.distribution + elif isinstance(expr.pspace, RandomMatrixPSpace): + return expr.pspace.model + if isinstance(pspace(expr), CompoundPSpace): + kwargs['compound_evaluate'] = evaluate + result = pspace(expr).compute_density(expr, **kwargs) + + if evaluate and hasattr(result, 'doit'): + return result.doit() + else: + return result + + +def density(expr, condition=None, evaluate=True, numsamples=None, **kwargs): + """ + Probability density of a random expression, optionally given a second + condition. + + Explanation + =========== + + This density will take on different forms for different types of + probability spaces. Discrete variables produce Dicts. Continuous + variables produce Lambdas. + + Parameters + ========== + + expr : Expr containing RandomSymbols + The expression of which you want to compute the density value + condition : Relational containing RandomSymbols + A conditional expression. density(X > 1, X > 0) is density of X > 1 + given X > 0 + numsamples : int + Enables sampling and approximates the density with this many samples + + Examples + ======== + + >>> from sympy.stats import density, Die, Normal + >>> from sympy import Symbol + + >>> x = Symbol('x') + >>> D = Die('D', 6) + >>> X = Normal(x, 0, 1) + + >>> density(D).dict + {1: 1/6, 2: 1/6, 3: 1/6, 4: 1/6, 5: 1/6, 6: 1/6} + >>> density(2*D).dict + {2: 1/6, 4: 1/6, 6: 1/6, 8: 1/6, 10: 1/6, 12: 1/6} + >>> density(X)(x) + sqrt(2)*exp(-x**2/2)/(2*sqrt(pi)) + """ + + if numsamples: + return sampling_density(expr, condition, numsamples=numsamples, + **kwargs) + + return Density(expr, condition).doit(evaluate=evaluate, **kwargs) + + +def cdf(expr, condition=None, evaluate=True, **kwargs): + """ + Cumulative Distribution Function of a random expression. + + optionally given a second condition. + + Explanation + =========== + + This density will take on different forms for different types of + probability spaces. + Discrete variables produce Dicts. + Continuous variables produce Lambdas. + + Examples + ======== + + >>> from sympy.stats import density, Die, Normal, cdf + + >>> D = Die('D', 6) + >>> X = Normal('X', 0, 1) + + >>> density(D).dict + {1: 1/6, 2: 1/6, 3: 1/6, 4: 1/6, 5: 1/6, 6: 1/6} + >>> cdf(D) + {1: 1/6, 2: 1/3, 3: 1/2, 4: 2/3, 5: 5/6, 6: 1} + >>> cdf(3*D, D > 2) + {9: 1/4, 12: 1/2, 15: 3/4, 18: 1} + + >>> cdf(X) + Lambda(_z, erf(sqrt(2)*_z/2)/2 + 1/2) + """ + if condition is not None: # If there is a condition + # Recompute on new conditional expr + return cdf(given(expr, condition, **kwargs), **kwargs) + + # Otherwise pass work off to the ProbabilitySpace + result = pspace(expr).compute_cdf(expr, **kwargs) + + if evaluate and hasattr(result, 'doit'): + return result.doit() + else: + return result + + +def characteristic_function(expr, condition=None, evaluate=True, **kwargs): + """ + Characteristic function of a random expression, optionally given a second condition. + + Returns a Lambda. + + Examples + ======== + + >>> from sympy.stats import Normal, DiscreteUniform, Poisson, characteristic_function + + >>> X = Normal('X', 0, 1) + >>> characteristic_function(X) + Lambda(_t, exp(-_t**2/2)) + + >>> Y = DiscreteUniform('Y', [1, 2, 7]) + >>> characteristic_function(Y) + Lambda(_t, exp(7*_t*I)/3 + exp(2*_t*I)/3 + exp(_t*I)/3) + + >>> Z = Poisson('Z', 2) + >>> characteristic_function(Z) + Lambda(_t, exp(2*exp(_t*I) - 2)) + """ + if condition is not None: + return characteristic_function(given(expr, condition, **kwargs), **kwargs) + + result = pspace(expr).compute_characteristic_function(expr, **kwargs) + + if evaluate and hasattr(result, 'doit'): + return result.doit() + else: + return result + +def moment_generating_function(expr, condition=None, evaluate=True, **kwargs): + if condition is not None: + return moment_generating_function(given(expr, condition, **kwargs), **kwargs) + + result = pspace(expr).compute_moment_generating_function(expr, **kwargs) + + if evaluate and hasattr(result, 'doit'): + return result.doit() + else: + return result + +def where(condition, given_condition=None, **kwargs): + """ + Returns the domain where a condition is True. + + Examples + ======== + + >>> from sympy.stats import where, Die, Normal + >>> from sympy import And + + >>> D1, D2 = Die('a', 6), Die('b', 6) + >>> a, b = D1.symbol, D2.symbol + >>> X = Normal('x', 0, 1) + + >>> where(X**2<1) + Domain: (-1 < x) & (x < 1) + + >>> where(X**2<1).set + Interval.open(-1, 1) + + >>> where(And(D1<=D2, D2<3)) + Domain: (Eq(a, 1) & Eq(b, 1)) | (Eq(a, 1) & Eq(b, 2)) | (Eq(a, 2) & Eq(b, 2)) + """ + if given_condition is not None: # If there is a condition + # Recompute on new conditional expr + return where(given(condition, given_condition, **kwargs), **kwargs) + + # Otherwise pass work off to the ProbabilitySpace + return pspace(condition).where(condition, **kwargs) + + +@doctest_depends_on(modules=('scipy',)) +def sample(expr, condition=None, size=(), library='scipy', + numsamples=1, seed=None, **kwargs): + """ + A realization of the random expression. + + Parameters + ========== + + expr : Expression of random variables + Expression from which sample is extracted + condition : Expr containing RandomSymbols + A conditional expression + size : int, tuple + Represents size of each sample in numsamples + library : str + - 'scipy' : Sample using scipy + - 'numpy' : Sample using numpy + - 'pymc' : Sample using PyMC + + Choose any of the available options to sample from as string, + by default is 'scipy' + numsamples : int + Number of samples, each with size as ``size``. + + .. deprecated:: 1.9 + + The ``numsamples`` parameter is deprecated and is only provided for + compatibility with v1.8. Use a list comprehension or an additional + dimension in ``size`` instead. See + :ref:`deprecated-sympy-stats-numsamples` for details. + + seed : + An object to be used as seed by the given external library for sampling `expr`. + Following is the list of possible types of object for the supported libraries, + + - 'scipy': int, numpy.random.RandomState, numpy.random.Generator + - 'numpy': int, numpy.random.RandomState, numpy.random.Generator + - 'pymc': int + + Optional, by default None, in which case seed settings + related to the given library will be used. + No modifications to environment's global seed settings + are done by this argument. + + Returns + ======= + + sample: float/list/numpy.ndarray + one sample or a collection of samples of the random expression. + + - sample(X) returns float/numpy.float64/numpy.int64 object. + - sample(X, size=int/tuple) returns numpy.ndarray object. + + Examples + ======== + + >>> from sympy.stats import Die, sample, Normal, Geometric + >>> X, Y, Z = Die('X', 6), Die('Y', 6), Die('Z', 6) # Finite Random Variable + >>> die_roll = sample(X + Y + Z) + >>> die_roll # doctest: +SKIP + 3 + >>> N = Normal('N', 3, 4) # Continuous Random Variable + >>> samp = sample(N) + >>> samp in N.pspace.domain.set + True + >>> samp = sample(N, N>0) + >>> samp > 0 + True + >>> samp_list = sample(N, size=4) + >>> [sam in N.pspace.domain.set for sam in samp_list] + [True, True, True, True] + >>> sample(N, size = (2,3)) # doctest: +SKIP + array([[5.42519758, 6.40207856, 4.94991743], + [1.85819627, 6.83403519, 1.9412172 ]]) + >>> G = Geometric('G', 0.5) # Discrete Random Variable + >>> samp_list = sample(G, size=3) + >>> samp_list # doctest: +SKIP + [1, 3, 2] + >>> [sam in G.pspace.domain.set for sam in samp_list] + [True, True, True] + >>> MN = Normal("MN", [3, 4], [[2, 1], [1, 2]]) # Joint Random Variable + >>> samp_list = sample(MN, size=4) + >>> samp_list # doctest: +SKIP + [array([2.85768055, 3.38954165]), + array([4.11163337, 4.3176591 ]), + array([0.79115232, 1.63232916]), + array([4.01747268, 3.96716083])] + >>> [tuple(sam) in MN.pspace.domain.set for sam in samp_list] + [True, True, True, True] + + .. versionchanged:: 1.7.0 + sample used to return an iterator containing the samples instead of value. + + .. versionchanged:: 1.9.0 + sample returns values or array of values instead of an iterator and numsamples is deprecated. + + """ + + iterator = sample_iter(expr, condition, size=size, library=library, + numsamples=numsamples, seed=seed) + + if numsamples != 1: + sympy_deprecation_warning( + f""" + The numsamples parameter to sympy.stats.sample() is deprecated. + Either use a list comprehension, like + + [sample(...) for i in range({numsamples})] + + or add a dimension to size, like + + sample(..., size={(numsamples,) + size}) + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-sympy-stats-numsamples", + ) + return [next(iterator) for i in range(numsamples)] + + return next(iterator) + + +def quantile(expr, evaluate=True, **kwargs): + r""" + Return the :math:`p^{th}` order quantile of a probability distribution. + + Explanation + =========== + + Quantile is defined as the value at which the probability of the random + variable is less than or equal to the given probability. + + .. math:: + Q(p) = \inf\{x \in (-\infty, \infty) : p \le F(x)\} + + Examples + ======== + + >>> from sympy.stats import quantile, Die, Exponential + >>> from sympy import Symbol, pprint + >>> p = Symbol("p") + + >>> l = Symbol("lambda", positive=True) + >>> X = Exponential("x", l) + >>> quantile(X)(p) + -log(1 - p)/lambda + + >>> D = Die("d", 6) + >>> pprint(quantile(D)(p), use_unicode=False) + /nan for Or(p > 1, p < 0) + | + | 1 for p <= 1/6 + | + | 2 for p <= 1/3 + | + < 3 for p <= 1/2 + | + | 4 for p <= 2/3 + | + | 5 for p <= 5/6 + | + \ 6 for p <= 1 + + """ + result = pspace(expr).compute_quantile(expr, **kwargs) + + if evaluate and hasattr(result, 'doit'): + return result.doit() + else: + return result + +def sample_iter(expr, condition=None, size=(), library='scipy', + numsamples=S.Infinity, seed=None, **kwargs): + + """ + Returns an iterator of realizations from the expression given a condition. + + Parameters + ========== + + expr: Expr + Random expression to be realized + condition: Expr, optional + A conditional expression + size : int, tuple + Represents size of each sample in numsamples + numsamples: integer, optional + Length of the iterator (defaults to infinity) + seed : + An object to be used as seed by the given external library for sampling `expr`. + Following is the list of possible types of object for the supported libraries, + + - 'scipy': int, numpy.random.RandomState, numpy.random.Generator + - 'numpy': int, numpy.random.RandomState, numpy.random.Generator + - 'pymc': int + + Optional, by default None, in which case seed settings + related to the given library will be used. + No modifications to environment's global seed settings + are done by this argument. + + Examples + ======== + + >>> from sympy.stats import Normal, sample_iter + >>> X = Normal('X', 0, 1) + >>> expr = X*X + 3 + >>> iterator = sample_iter(expr, numsamples=3) # doctest: +SKIP + >>> list(iterator) # doctest: +SKIP + [12, 4, 7] + + Returns + ======= + + sample_iter: iterator object + iterator object containing the sample/samples of given expr + + See Also + ======== + + sample + sampling_P + sampling_E + + """ + from sympy.stats.joint_rv import JointRandomSymbol + if not import_module(library): + raise ValueError("Failed to import %s" % library) + + if condition is not None: + ps = pspace(Tuple(expr, condition)) + else: + ps = pspace(expr) + + rvs = list(ps.values) + if isinstance(expr, JointRandomSymbol): + expr = expr.subs({expr: RandomSymbol(expr.symbol, expr.pspace)}) + else: + sub = {} + for arg in expr.args: + if isinstance(arg, JointRandomSymbol): + sub[arg] = RandomSymbol(arg.symbol, arg.pspace) + expr = expr.subs(sub) + + def fn_subs(*args): + return expr.subs(dict(zip(rvs, args))) + + def given_fn_subs(*args): + if condition is not None: + return condition.subs(dict(zip(rvs, args))) + return False + + if library in ('pymc', 'pymc3'): + # Currently unable to lambdify in pymc + # TODO : Remove when lambdify accepts 'pymc' as module + fn = lambdify(rvs, expr, **kwargs) + else: + fn = lambdify(rvs, expr, modules=library, **kwargs) + + + if condition is not None: + given_fn = lambdify(rvs, condition, **kwargs) + + def return_generator_infinite(): + count = 0 + _size = (1,)+((size,) if isinstance(size, int) else size) + while count < numsamples: + d = ps.sample(size=_size, library=library, seed=seed) # a dictionary that maps RVs to values + args = [d[rv][0] for rv in rvs] + + if condition is not None: # Check that these values satisfy the condition + # TODO: Replace the try-except block with only given_fn(*args) + # once lambdify works with unevaluated SymPy objects. + try: + gd = given_fn(*args) + except (NameError, TypeError): + gd = given_fn_subs(*args) + if gd != True and gd != False: + raise ValueError( + "Conditions must not contain free symbols") + if not gd: # If the values don't satisfy then try again + continue + + yield fn(*args) + count += 1 + + def return_generator_finite(): + faulty = True + while faulty: + d = ps.sample(size=(numsamples,) + ((size,) if isinstance(size, int) else size), + library=library, seed=seed) # a dictionary that maps RVs to values + + faulty = False + count = 0 + while count < numsamples and not faulty: + args = [d[rv][count] for rv in rvs] + if condition is not None: # Check that these values satisfy the condition + # TODO: Replace the try-except block with only given_fn(*args) + # once lambdify works with unevaluated SymPy objects. + try: + gd = given_fn(*args) + except (NameError, TypeError): + gd = given_fn_subs(*args) + if gd != True and gd != False: + raise ValueError( + "Conditions must not contain free symbols") + if not gd: # If the values don't satisfy then try again + faulty = True + + count += 1 + + count = 0 + while count < numsamples: + args = [d[rv][count] for rv in rvs] + # TODO: Replace the try-except block with only fn(*args) + # once lambdify works with unevaluated SymPy objects. + try: + yield fn(*args) + except (NameError, TypeError): + yield fn_subs(*args) + count += 1 + + if numsamples is S.Infinity: + return return_generator_infinite() + + return return_generator_finite() + +def sample_iter_lambdify(expr, condition=None, size=(), + numsamples=S.Infinity, seed=None, **kwargs): + + return sample_iter(expr, condition=condition, size=size, + numsamples=numsamples, seed=seed, **kwargs) + +def sample_iter_subs(expr, condition=None, size=(), + numsamples=S.Infinity, seed=None, **kwargs): + + return sample_iter(expr, condition=condition, size=size, + numsamples=numsamples, seed=seed, **kwargs) + + +def sampling_P(condition, given_condition=None, library='scipy', numsamples=1, + evalf=True, seed=None, **kwargs): + """ + Sampling version of P. + + See Also + ======== + + P + sampling_E + sampling_density + + """ + + count_true = 0 + count_false = 0 + samples = sample_iter(condition, given_condition, library=library, + numsamples=numsamples, seed=seed, **kwargs) + + for sample in samples: + if sample: + count_true += 1 + else: + count_false += 1 + + result = S(count_true) / numsamples + if evalf: + return result.evalf() + else: + return result + + +def sampling_E(expr, given_condition=None, library='scipy', numsamples=1, + evalf=True, seed=None, **kwargs): + """ + Sampling version of E. + + See Also + ======== + + P + sampling_P + sampling_density + """ + samples = list(sample_iter(expr, given_condition, library=library, + numsamples=numsamples, seed=seed, **kwargs)) + result = Add(*samples) / numsamples + + if evalf: + return result.evalf() + else: + return result + +def sampling_density(expr, given_condition=None, library='scipy', + numsamples=1, seed=None, **kwargs): + """ + Sampling version of density. + + See Also + ======== + density + sampling_P + sampling_E + """ + + results = {} + for result in sample_iter(expr, given_condition, library=library, + numsamples=numsamples, seed=seed, **kwargs): + results[result] = results.get(result, 0) + 1 + + return results + + +def dependent(a, b): + """ + Dependence of two random expressions. + + Two expressions are independent if knowledge of one does not change + computations on the other. + + Examples + ======== + + >>> from sympy.stats import Normal, dependent, given + >>> from sympy import Tuple, Eq + + >>> X, Y = Normal('X', 0, 1), Normal('Y', 0, 1) + >>> dependent(X, Y) + False + >>> dependent(2*X + Y, -Y) + True + >>> X, Y = given(Tuple(X, Y), Eq(X + Y, 3)) + >>> dependent(X, Y) + True + + See Also + ======== + + independent + """ + if pspace_independent(a, b): + return False + + z = Symbol('z', real=True) + # Dependent if density is unchanged when one is given information about + # the other + return (density(a, Eq(b, z)) != density(a) or + density(b, Eq(a, z)) != density(b)) + + +def independent(a, b): + """ + Independence of two random expressions. + + Two expressions are independent if knowledge of one does not change + computations on the other. + + Examples + ======== + + >>> from sympy.stats import Normal, independent, given + >>> from sympy import Tuple, Eq + + >>> X, Y = Normal('X', 0, 1), Normal('Y', 0, 1) + >>> independent(X, Y) + True + >>> independent(2*X + Y, -Y) + False + >>> X, Y = given(Tuple(X, Y), Eq(X + Y, 3)) + >>> independent(X, Y) + False + + See Also + ======== + + dependent + """ + return not dependent(a, b) + + +def pspace_independent(a, b): + """ + Tests for independence between a and b by checking if their PSpaces have + overlapping symbols. This is a sufficient but not necessary condition for + independence and is intended to be used internally. + + Notes + ===== + + pspace_independent(a, b) implies independent(a, b) + independent(a, b) does not imply pspace_independent(a, b) + """ + a_symbols = set(pspace(b).symbols) + b_symbols = set(pspace(a).symbols) + + if len(set(random_symbols(a)).intersection(random_symbols(b))) != 0: + return False + + if len(a_symbols.intersection(b_symbols)) == 0: + return True + return None + + +def rv_subs(expr, symbols=None): + """ + Given a random expression replace all random variables with their symbols. + + If symbols keyword is given restrict the swap to only the symbols listed. + """ + if symbols is None: + symbols = random_symbols(expr) + if not symbols: + return expr + swapdict = {rv: rv.symbol for rv in symbols} + return expr.subs(swapdict) + + +class NamedArgsMixin: + _argnames: tuple[str, ...] = () + + def __getattr__(self, attr): + try: + return self.args[self._argnames.index(attr)] + except ValueError: + raise AttributeError("'%s' object has no attribute '%s'" % ( + type(self).__name__, attr)) + + +class Distribution(Basic): + + def sample(self, size=(), library='scipy', seed=None): + """ A random realization from the distribution """ + + module = import_module(library) + if library in {'scipy', 'numpy', 'pymc3', 'pymc'} and module is None: + raise ValueError("Failed to import %s" % library) + + if library == 'scipy': + # scipy does not require map as it can handle using custom distributions. + # However, we will still use a map where we can. + + # TODO: do this for drv.py and frv.py if necessary. + # TODO: add more distributions here if there are more + # See links below referring to sections beginning with "A common parametrization..." + # I will remove all these comments if everything is ok. + + from sympy.stats.sampling.sample_scipy import do_sample_scipy + import numpy + if seed is None or isinstance(seed, int): + rand_state = numpy.random.default_rng(seed=seed) + else: + rand_state = seed + samps = do_sample_scipy(self, size, rand_state) + + elif library == 'numpy': + from sympy.stats.sampling.sample_numpy import do_sample_numpy + import numpy + if seed is None or isinstance(seed, int): + rand_state = numpy.random.default_rng(seed=seed) + else: + rand_state = seed + _size = None if size == () else size + samps = do_sample_numpy(self, _size, rand_state) + elif library in ('pymc', 'pymc3'): + from sympy.stats.sampling.sample_pymc import do_sample_pymc + import logging + logging.getLogger("pymc").setLevel(logging.ERROR) + try: + import pymc + except ImportError: + import pymc3 as pymc + + with pymc.Model(): + if do_sample_pymc(self) is not None: + samps = pymc.sample(draws=prod(size), chains=1, compute_convergence_checks=False, + progressbar=False, random_seed=seed, return_inferencedata=False)[:]['X'] + samps = samps.reshape(size) + else: + samps = None + + else: + raise NotImplementedError("Sampling from %s is not supported yet." + % str(library)) + + if samps is not None: + return samps + raise NotImplementedError( + "Sampling for %s is not currently implemented from %s" + % (self, library)) + + +def _value_check(condition, message): + """ + Raise a ValueError with message if condition is False, else + return True if all conditions were True, else False. + + Examples + ======== + + >>> from sympy.stats.rv import _value_check + >>> from sympy.abc import a, b, c + >>> from sympy import And, Dummy + + >>> _value_check(2 < 3, '') + True + + Here, the condition is not False, but it does not evaluate to True + so False is returned (but no error is raised). So checking if the + return value is True or False will tell you if all conditions were + evaluated. + + >>> _value_check(a < b, '') + False + + In this case the condition is False so an error is raised: + + >>> r = Dummy(real=True) + >>> _value_check(r < r - 1, 'condition is not true') + Traceback (most recent call last): + ... + ValueError: condition is not true + + If no condition of many conditions must be False, they can be + checked by passing them as an iterable: + + >>> _value_check((a < 0, b < 0, c < 0), '') + False + + The iterable can be a generator, too: + + >>> _value_check((i < 0 for i in (a, b, c)), '') + False + + The following are equivalent to the above but do not pass + an iterable: + + >>> all(_value_check(i < 0, '') for i in (a, b, c)) + False + >>> _value_check(And(a < 0, b < 0, c < 0), '') + False + """ + if not iterable(condition): + condition = [condition] + truth = fuzzy_and(condition) + if truth == False: + raise ValueError(message) + return truth == True + +def _symbol_converter(sym): + """ + Casts the parameter to Symbol if it is 'str' + otherwise no operation is performed on it. + + Parameters + ========== + + sym + The parameter to be converted. + + Returns + ======= + + Symbol + the parameter converted to Symbol. + + Raises + ====== + + TypeError + If the parameter is not an instance of both str and + Symbol. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.stats.rv import _symbol_converter + >>> s = _symbol_converter('s') + >>> isinstance(s, Symbol) + True + >>> _symbol_converter(1) + Traceback (most recent call last): + ... + TypeError: 1 is neither a Symbol nor a string + >>> r = Symbol('r') + >>> isinstance(r, Symbol) + True + """ + if isinstance(sym, str): + sym = Symbol(sym) + if not isinstance(sym, Symbol): + raise TypeError("%s is neither a Symbol nor a string"%(sym)) + return sym + +def sample_stochastic_process(process): + """ + This function is used to sample from stochastic process. + + Parameters + ========== + + process: StochasticProcess + Process used to extract the samples. It must be an instance of + StochasticProcess + + Examples + ======== + + >>> from sympy.stats import sample_stochastic_process, DiscreteMarkovChain + >>> from sympy import Matrix + >>> T = Matrix([[0.5, 0.2, 0.3],[0.2, 0.5, 0.3],[0.2, 0.3, 0.5]]) + >>> Y = DiscreteMarkovChain("Y", [0, 1, 2], T) + >>> next(sample_stochastic_process(Y)) in Y.state_space + True + >>> next(sample_stochastic_process(Y)) # doctest: +SKIP + 0 + >>> next(sample_stochastic_process(Y)) # doctest: +SKIP + 2 + + Returns + ======= + + sample: iterator object + iterator object containing the sample of given process + + """ + from sympy.stats.stochastic_process_types import StochasticProcess + if not isinstance(process, StochasticProcess): + raise ValueError("Process must be an instance of Stochastic Process") + return process.sample() diff --git a/venv/lib/python3.10/site-packages/sympy/stats/rv_interface.py b/venv/lib/python3.10/site-packages/sympy/stats/rv_interface.py new file mode 100644 index 0000000000000000000000000000000000000000..16d65b83634cdb04ef7e5046175848cdf380434b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/rv_interface.py @@ -0,0 +1,519 @@ +from sympy.sets import FiniteSet +from sympy.core.numbers import Rational +from sympy.core.relational import Eq +from sympy.core.symbol import Dummy +from sympy.functions.combinatorial.factorials import FallingFactorial +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import piecewise_fold +from sympy.integrals.integrals import Integral +from sympy.solvers.solveset import solveset +from .rv import (probability, expectation, density, where, given, pspace, cdf, PSpace, + characteristic_function, sample, sample_iter, random_symbols, independent, dependent, + sampling_density, moment_generating_function, quantile, is_random, + sample_stochastic_process) + + +__all__ = ['P', 'E', 'H', 'density', 'where', 'given', 'sample', 'cdf', + 'characteristic_function', 'pspace', 'sample_iter', 'variance', 'std', + 'skewness', 'kurtosis', 'covariance', 'dependent', 'entropy', 'median', + 'independent', 'random_symbols', 'correlation', 'factorial_moment', + 'moment', 'cmoment', 'sampling_density', 'moment_generating_function', + 'smoment', 'quantile', 'sample_stochastic_process'] + + + +def moment(X, n, c=0, condition=None, *, evaluate=True, **kwargs): + """ + Return the nth moment of a random expression about c. + + .. math:: + moment(X, c, n) = E((X-c)^{n}) + + Default value of c is 0. + + Examples + ======== + + >>> from sympy.stats import Die, moment, E + >>> X = Die('X', 6) + >>> moment(X, 1, 6) + -5/2 + >>> moment(X, 2) + 91/6 + >>> moment(X, 1) == E(X) + True + """ + from sympy.stats.symbolic_probability import Moment + if evaluate: + return Moment(X, n, c, condition).doit() + return Moment(X, n, c, condition).rewrite(Integral) + + +def variance(X, condition=None, **kwargs): + """ + Variance of a random expression. + + .. math:: + variance(X) = E((X-E(X))^{2}) + + Examples + ======== + + >>> from sympy.stats import Die, Bernoulli, variance + >>> from sympy import simplify, Symbol + + >>> X = Die('X', 6) + >>> p = Symbol('p') + >>> B = Bernoulli('B', p, 1, 0) + + >>> variance(2*X) + 35/3 + + >>> simplify(variance(B)) + p*(1 - p) + """ + if is_random(X) and pspace(X) == PSpace(): + from sympy.stats.symbolic_probability import Variance + return Variance(X, condition) + + return cmoment(X, 2, condition, **kwargs) + + +def standard_deviation(X, condition=None, **kwargs): + r""" + Standard Deviation of a random expression + + .. math:: + std(X) = \sqrt(E((X-E(X))^{2})) + + Examples + ======== + + >>> from sympy.stats import Bernoulli, std + >>> from sympy import Symbol, simplify + + >>> p = Symbol('p') + >>> B = Bernoulli('B', p, 1, 0) + + >>> simplify(std(B)) + sqrt(p*(1 - p)) + """ + return sqrt(variance(X, condition, **kwargs)) +std = standard_deviation + +def entropy(expr, condition=None, **kwargs): + """ + Calculates entropy of a probability distribution. + + Parameters + ========== + + expression : the random expression whose entropy is to be calculated + condition : optional, to specify conditions on random expression + b: base of the logarithm, optional + By default, it is taken as Euler's number + + Returns + ======= + + result : Entropy of the expression, a constant + + Examples + ======== + + >>> from sympy.stats import Normal, Die, entropy + >>> X = Normal('X', 0, 1) + >>> entropy(X) + log(2)/2 + 1/2 + log(pi)/2 + + >>> D = Die('D', 4) + >>> entropy(D) + log(4) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Entropy_%28information_theory%29 + .. [2] https://www.crmarsh.com/static/pdf/Charles_Marsh_Continuous_Entropy.pdf + .. [3] https://kconrad.math.uconn.edu/blurbs/analysis/entropypost.pdf + """ + pdf = density(expr, condition, **kwargs) + base = kwargs.get('b', exp(1)) + if isinstance(pdf, dict): + return sum(-prob*log(prob, base) for prob in pdf.values()) + return expectation(-log(pdf(expr), base)) + +def covariance(X, Y, condition=None, **kwargs): + """ + Covariance of two random expressions. + + Explanation + =========== + + The expectation that the two variables will rise and fall together + + .. math:: + covariance(X,Y) = E((X-E(X)) (Y-E(Y))) + + Examples + ======== + + >>> from sympy.stats import Exponential, covariance + >>> from sympy import Symbol + + >>> rate = Symbol('lambda', positive=True, real=True) + >>> X = Exponential('X', rate) + >>> Y = Exponential('Y', rate) + + >>> covariance(X, X) + lambda**(-2) + >>> covariance(X, Y) + 0 + >>> covariance(X, Y + rate*X) + 1/lambda + """ + if (is_random(X) and pspace(X) == PSpace()) or (is_random(Y) and pspace(Y) == PSpace()): + from sympy.stats.symbolic_probability import Covariance + return Covariance(X, Y, condition) + + return expectation( + (X - expectation(X, condition, **kwargs)) * + (Y - expectation(Y, condition, **kwargs)), + condition, **kwargs) + + +def correlation(X, Y, condition=None, **kwargs): + r""" + Correlation of two random expressions, also known as correlation + coefficient or Pearson's correlation. + + Explanation + =========== + + The normalized expectation that the two variables will rise + and fall together + + .. math:: + correlation(X,Y) = E((X-E(X))(Y-E(Y)) / (\sigma_x \sigma_y)) + + Examples + ======== + + >>> from sympy.stats import Exponential, correlation + >>> from sympy import Symbol + + >>> rate = Symbol('lambda', positive=True, real=True) + >>> X = Exponential('X', rate) + >>> Y = Exponential('Y', rate) + + >>> correlation(X, X) + 1 + >>> correlation(X, Y) + 0 + >>> correlation(X, Y + rate*X) + 1/sqrt(1 + lambda**(-2)) + """ + return covariance(X, Y, condition, **kwargs)/(std(X, condition, **kwargs) + * std(Y, condition, **kwargs)) + + +def cmoment(X, n, condition=None, *, evaluate=True, **kwargs): + """ + Return the nth central moment of a random expression about its mean. + + .. math:: + cmoment(X, n) = E((X - E(X))^{n}) + + Examples + ======== + + >>> from sympy.stats import Die, cmoment, variance + >>> X = Die('X', 6) + >>> cmoment(X, 3) + 0 + >>> cmoment(X, 2) + 35/12 + >>> cmoment(X, 2) == variance(X) + True + """ + from sympy.stats.symbolic_probability import CentralMoment + if evaluate: + return CentralMoment(X, n, condition).doit() + return CentralMoment(X, n, condition).rewrite(Integral) + + +def smoment(X, n, condition=None, **kwargs): + r""" + Return the nth Standardized moment of a random expression. + + .. math:: + smoment(X, n) = E(((X - \mu)/\sigma_X)^{n}) + + Examples + ======== + + >>> from sympy.stats import skewness, Exponential, smoment + >>> from sympy import Symbol + >>> rate = Symbol('lambda', positive=True, real=True) + >>> Y = Exponential('Y', rate) + >>> smoment(Y, 4) + 9 + >>> smoment(Y, 4) == smoment(3*Y, 4) + True + >>> smoment(Y, 3) == skewness(Y) + True + """ + sigma = std(X, condition, **kwargs) + return (1/sigma)**n*cmoment(X, n, condition, **kwargs) + +def skewness(X, condition=None, **kwargs): + r""" + Measure of the asymmetry of the probability distribution. + + Explanation + =========== + + Positive skew indicates that most of the values lie to the right of + the mean. + + .. math:: + skewness(X) = E(((X - E(X))/\sigma_X)^{3}) + + Parameters + ========== + + condition : Expr containing RandomSymbols + A conditional expression. skewness(X, X>0) is skewness of X given X > 0 + + Examples + ======== + + >>> from sympy.stats import skewness, Exponential, Normal + >>> from sympy import Symbol + >>> X = Normal('X', 0, 1) + >>> skewness(X) + 0 + >>> skewness(X, X > 0) # find skewness given X > 0 + (-sqrt(2)/sqrt(pi) + 4*sqrt(2)/pi**(3/2))/(1 - 2/pi)**(3/2) + + >>> rate = Symbol('lambda', positive=True, real=True) + >>> Y = Exponential('Y', rate) + >>> skewness(Y) + 2 + """ + return smoment(X, 3, condition=condition, **kwargs) + +def kurtosis(X, condition=None, **kwargs): + r""" + Characterizes the tails/outliers of a probability distribution. + + Explanation + =========== + + Kurtosis of any univariate normal distribution is 3. Kurtosis less than + 3 means that the distribution produces fewer and less extreme outliers + than the normal distribution. + + .. math:: + kurtosis(X) = E(((X - E(X))/\sigma_X)^{4}) + + Parameters + ========== + + condition : Expr containing RandomSymbols + A conditional expression. kurtosis(X, X>0) is kurtosis of X given X > 0 + + Examples + ======== + + >>> from sympy.stats import kurtosis, Exponential, Normal + >>> from sympy import Symbol + >>> X = Normal('X', 0, 1) + >>> kurtosis(X) + 3 + >>> kurtosis(X, X > 0) # find kurtosis given X > 0 + (-4/pi - 12/pi**2 + 3)/(1 - 2/pi)**2 + + >>> rate = Symbol('lamda', positive=True, real=True) + >>> Y = Exponential('Y', rate) + >>> kurtosis(Y) + 9 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Kurtosis + .. [2] https://mathworld.wolfram.com/Kurtosis.html + """ + return smoment(X, 4, condition=condition, **kwargs) + + +def factorial_moment(X, n, condition=None, **kwargs): + """ + The factorial moment is a mathematical quantity defined as the expectation + or average of the falling factorial of a random variable. + + .. math:: + factorial-moment(X, n) = E(X(X - 1)(X - 2)...(X - n + 1)) + + Parameters + ========== + + n: A natural number, n-th factorial moment. + + condition : Expr containing RandomSymbols + A conditional expression. + + Examples + ======== + + >>> from sympy.stats import factorial_moment, Poisson, Binomial + >>> from sympy import Symbol, S + >>> lamda = Symbol('lamda') + >>> X = Poisson('X', lamda) + >>> factorial_moment(X, 2) + lamda**2 + >>> Y = Binomial('Y', 2, S.Half) + >>> factorial_moment(Y, 2) + 1/2 + >>> factorial_moment(Y, 2, Y > 1) # find factorial moment for Y > 1 + 2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Factorial_moment + .. [2] https://mathworld.wolfram.com/FactorialMoment.html + """ + return expectation(FallingFactorial(X, n), condition=condition, **kwargs) + +def median(X, evaluate=True, **kwargs): + r""" + Calculates the median of the probability distribution. + + Explanation + =========== + + Mathematically, median of Probability distribution is defined as all those + values of `m` for which the following condition is satisfied + + .. math:: + P(X\leq m) \geq \frac{1}{2} \text{ and} \text{ } P(X\geq m)\geq \frac{1}{2} + + Parameters + ========== + + X: The random expression whose median is to be calculated. + + Returns + ======= + + The FiniteSet or an Interval which contains the median of the + random expression. + + Examples + ======== + + >>> from sympy.stats import Normal, Die, median + >>> N = Normal('N', 3, 1) + >>> median(N) + {3} + >>> D = Die('D') + >>> median(D) + {3, 4} + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Median#Probability_distributions + + """ + if not is_random(X): + return X + + from sympy.stats.crv import ContinuousPSpace + from sympy.stats.drv import DiscretePSpace + from sympy.stats.frv import FinitePSpace + + if isinstance(pspace(X), FinitePSpace): + cdf = pspace(X).compute_cdf(X) + result = [] + for key, value in cdf.items(): + if value>= Rational(1, 2) and (1 - value) + \ + pspace(X).probability(Eq(X, key)) >= Rational(1, 2): + result.append(key) + return FiniteSet(*result) + if isinstance(pspace(X), (ContinuousPSpace, DiscretePSpace)): + cdf = pspace(X).compute_cdf(X) + x = Dummy('x') + result = solveset(piecewise_fold(cdf(x) - Rational(1, 2)), x, pspace(X).set) + return result + raise NotImplementedError("The median of %s is not implemented."%str(pspace(X))) + + +def coskewness(X, Y, Z, condition=None, **kwargs): + r""" + Calculates the co-skewness of three random variables. + + Explanation + =========== + + Mathematically Coskewness is defined as + + .. math:: + coskewness(X,Y,Z)=\frac{E[(X-E[X]) * (Y-E[Y]) * (Z-E[Z])]} {\sigma_{X}\sigma_{Y}\sigma_{Z}} + + Parameters + ========== + + X : RandomSymbol + Random Variable used to calculate coskewness + Y : RandomSymbol + Random Variable used to calculate coskewness + Z : RandomSymbol + Random Variable used to calculate coskewness + condition : Expr containing RandomSymbols + A conditional expression + + Examples + ======== + + >>> from sympy.stats import coskewness, Exponential, skewness + >>> from sympy import symbols + >>> p = symbols('p', positive=True) + >>> X = Exponential('X', p) + >>> Y = Exponential('Y', 2*p) + >>> coskewness(X, Y, Y) + 0 + >>> coskewness(X, Y + X, Y + 2*X) + 16*sqrt(85)/85 + >>> coskewness(X + 2*Y, Y + X, Y + 2*X, X > 3) + 9*sqrt(170)/85 + >>> coskewness(Y, Y, Y) == skewness(Y) + True + >>> coskewness(X, Y + p*X, Y + 2*p*X) + 4/(sqrt(1 + 1/(4*p**2))*sqrt(4 + 1/(4*p**2))) + + Returns + ======= + + coskewness : The coskewness of the three random variables + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Coskewness + + """ + num = expectation((X - expectation(X, condition, **kwargs)) \ + * (Y - expectation(Y, condition, **kwargs)) \ + * (Z - expectation(Z, condition, **kwargs)), condition, **kwargs) + den = std(X, condition, **kwargs) * std(Y, condition, **kwargs) \ + * std(Z, condition, **kwargs) + return num/den + + +P = probability +E = expectation +H = entropy diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/__init__.py b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c1123b8371f35a208a8e390ae81ef84b24135d0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_numpy.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_numpy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eaefa8046469269dbf5312783431257a70eb8f7c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_numpy.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_pymc.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_pymc.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8937b51041b499576869da17190eaeed761abaa3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_pymc.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_scipy.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_scipy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebec8b85d3ba96e4dd6e85eb71859b9baa24af8d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/sampling/__pycache__/sample_scipy.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_numpy.py b/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_numpy.py new file mode 100644 index 0000000000000000000000000000000000000000..d65417945449ed8b62d2547215d8908f84820a9b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_numpy.py @@ -0,0 +1,105 @@ +from functools import singledispatch + +from sympy.external import import_module +from sympy.stats.crv_types import BetaDistribution, ChiSquaredDistribution, ExponentialDistribution, GammaDistribution, \ + LogNormalDistribution, NormalDistribution, ParetoDistribution, UniformDistribution, FDistributionDistribution, GumbelDistribution, LaplaceDistribution, \ + LogisticDistribution, RayleighDistribution, TriangularDistribution +from sympy.stats.drv_types import GeometricDistribution, PoissonDistribution, ZetaDistribution +from sympy.stats.frv_types import BinomialDistribution, HypergeometricDistribution + + +numpy = import_module('numpy') + + +@singledispatch +def do_sample_numpy(dist, size, rand_state): + return None + + +# CRV: + +@do_sample_numpy.register(BetaDistribution) +def _(dist: BetaDistribution, size, rand_state): + return rand_state.beta(a=float(dist.alpha), b=float(dist.beta), size=size) + + +@do_sample_numpy.register(ChiSquaredDistribution) +def _(dist: ChiSquaredDistribution, size, rand_state): + return rand_state.chisquare(df=float(dist.k), size=size) + + +@do_sample_numpy.register(ExponentialDistribution) +def _(dist: ExponentialDistribution, size, rand_state): + return rand_state.exponential(1 / float(dist.rate), size=size) + +@do_sample_numpy.register(FDistributionDistribution) +def _(dist: FDistributionDistribution, size, rand_state): + return rand_state.f(dfnum = float(dist.d1), dfden = float(dist.d2), size=size) + +@do_sample_numpy.register(GammaDistribution) +def _(dist: GammaDistribution, size, rand_state): + return rand_state.gamma(shape = float(dist.k), scale = float(dist.theta), size=size) + +@do_sample_numpy.register(GumbelDistribution) +def _(dist: GumbelDistribution, size, rand_state): + return rand_state.gumbel(loc = float(dist.mu), scale = float(dist.beta), size=size) + +@do_sample_numpy.register(LaplaceDistribution) +def _(dist: LaplaceDistribution, size, rand_state): + return rand_state.laplace(loc = float(dist.mu), scale = float(dist.b), size=size) + +@do_sample_numpy.register(LogisticDistribution) +def _(dist: LogisticDistribution, size, rand_state): + return rand_state.logistic(loc = float(dist.mu), scale = float(dist.s), size=size) + +@do_sample_numpy.register(LogNormalDistribution) +def _(dist: LogNormalDistribution, size, rand_state): + return rand_state.lognormal(mean = float(dist.mean), sigma = float(dist.std), size=size) + +@do_sample_numpy.register(NormalDistribution) +def _(dist: NormalDistribution, size, rand_state): + return rand_state.normal(loc = float(dist.mean), scale = float(dist.std), size=size) + +@do_sample_numpy.register(RayleighDistribution) +def _(dist: RayleighDistribution, size, rand_state): + return rand_state.rayleigh(scale = float(dist.sigma), size=size) + +@do_sample_numpy.register(ParetoDistribution) +def _(dist: ParetoDistribution, size, rand_state): + return (numpy.random.pareto(a=float(dist.alpha), size=size) + 1) * float(dist.xm) + +@do_sample_numpy.register(TriangularDistribution) +def _(dist: TriangularDistribution, size, rand_state): + return rand_state.triangular(left = float(dist.a), mode = float(dist.b), right = float(dist.c), size=size) + +@do_sample_numpy.register(UniformDistribution) +def _(dist: UniformDistribution, size, rand_state): + return rand_state.uniform(low=float(dist.left), high=float(dist.right), size=size) + + +# DRV: + +@do_sample_numpy.register(GeometricDistribution) +def _(dist: GeometricDistribution, size, rand_state): + return rand_state.geometric(p=float(dist.p), size=size) + + +@do_sample_numpy.register(PoissonDistribution) +def _(dist: PoissonDistribution, size, rand_state): + return rand_state.poisson(lam=float(dist.lamda), size=size) + + +@do_sample_numpy.register(ZetaDistribution) +def _(dist: ZetaDistribution, size, rand_state): + return rand_state.zipf(a=float(dist.s), size=size) + + +# FRV: + +@do_sample_numpy.register(BinomialDistribution) +def _(dist: BinomialDistribution, size, rand_state): + return rand_state.binomial(n=int(dist.n), p=float(dist.p), size=size) + +@do_sample_numpy.register(HypergeometricDistribution) +def _(dist: HypergeometricDistribution, size, rand_state): + return rand_state.hypergeometric(ngood = int(dist.N), nbad = int(dist.m), nsample = int(dist.n), size=size) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_pymc.py b/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_pymc.py new file mode 100644 index 0000000000000000000000000000000000000000..546f02a3092815af2e54b4a164463f62ece7a024 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_pymc.py @@ -0,0 +1,99 @@ +from functools import singledispatch +from sympy.external import import_module +from sympy.stats.crv_types import BetaDistribution, CauchyDistribution, ChiSquaredDistribution, ExponentialDistribution, \ + GammaDistribution, LogNormalDistribution, NormalDistribution, ParetoDistribution, UniformDistribution, \ + GaussianInverseDistribution +from sympy.stats.drv_types import PoissonDistribution, GeometricDistribution, NegativeBinomialDistribution +from sympy.stats.frv_types import BinomialDistribution, BernoulliDistribution + + +try: + import pymc +except ImportError: + pymc = import_module('pymc3') + +@singledispatch +def do_sample_pymc(dist): + return None + + +# CRV: + +@do_sample_pymc.register(BetaDistribution) +def _(dist: BetaDistribution): + return pymc.Beta('X', alpha=float(dist.alpha), beta=float(dist.beta)) + + +@do_sample_pymc.register(CauchyDistribution) +def _(dist: CauchyDistribution): + return pymc.Cauchy('X', alpha=float(dist.x0), beta=float(dist.gamma)) + + +@do_sample_pymc.register(ChiSquaredDistribution) +def _(dist: ChiSquaredDistribution): + return pymc.ChiSquared('X', nu=float(dist.k)) + + +@do_sample_pymc.register(ExponentialDistribution) +def _(dist: ExponentialDistribution): + return pymc.Exponential('X', lam=float(dist.rate)) + + +@do_sample_pymc.register(GammaDistribution) +def _(dist: GammaDistribution): + return pymc.Gamma('X', alpha=float(dist.k), beta=1 / float(dist.theta)) + + +@do_sample_pymc.register(LogNormalDistribution) +def _(dist: LogNormalDistribution): + return pymc.Lognormal('X', mu=float(dist.mean), sigma=float(dist.std)) + + +@do_sample_pymc.register(NormalDistribution) +def _(dist: NormalDistribution): + return pymc.Normal('X', float(dist.mean), float(dist.std)) + + +@do_sample_pymc.register(GaussianInverseDistribution) +def _(dist: GaussianInverseDistribution): + return pymc.Wald('X', mu=float(dist.mean), lam=float(dist.shape)) + + +@do_sample_pymc.register(ParetoDistribution) +def _(dist: ParetoDistribution): + return pymc.Pareto('X', alpha=float(dist.alpha), m=float(dist.xm)) + + +@do_sample_pymc.register(UniformDistribution) +def _(dist: UniformDistribution): + return pymc.Uniform('X', lower=float(dist.left), upper=float(dist.right)) + + +# DRV: + +@do_sample_pymc.register(GeometricDistribution) +def _(dist: GeometricDistribution): + return pymc.Geometric('X', p=float(dist.p)) + + +@do_sample_pymc.register(NegativeBinomialDistribution) +def _(dist: NegativeBinomialDistribution): + return pymc.NegativeBinomial('X', mu=float((dist.p * dist.r) / (1 - dist.p)), + alpha=float(dist.r)) + + +@do_sample_pymc.register(PoissonDistribution) +def _(dist: PoissonDistribution): + return pymc.Poisson('X', mu=float(dist.lamda)) + + +# FRV: + +@do_sample_pymc.register(BernoulliDistribution) +def _(dist: BernoulliDistribution): + return pymc.Bernoulli('X', p=float(dist.p)) + + +@do_sample_pymc.register(BinomialDistribution) +def _(dist: BinomialDistribution): + return pymc.Binomial('X', n=int(dist.n), p=float(dist.p)) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_scipy.py b/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_scipy.py new file mode 100644 index 0000000000000000000000000000000000000000..f12508f68844488e9a14b1476005164eb422796e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/sampling/sample_scipy.py @@ -0,0 +1,167 @@ +from functools import singledispatch + +from sympy.core.symbol import Dummy +from sympy.functions.elementary.exponential import exp +from sympy.utilities.lambdify import lambdify +from sympy.external import import_module +from sympy.stats import DiscreteDistributionHandmade +from sympy.stats.crv import SingleContinuousDistribution +from sympy.stats.crv_types import ChiSquaredDistribution, ExponentialDistribution, GammaDistribution, \ + LogNormalDistribution, NormalDistribution, ParetoDistribution, UniformDistribution, BetaDistribution, \ + StudentTDistribution, CauchyDistribution +from sympy.stats.drv_types import GeometricDistribution, LogarithmicDistribution, NegativeBinomialDistribution, \ + PoissonDistribution, SkellamDistribution, YuleSimonDistribution, ZetaDistribution +from sympy.stats.frv import SingleFiniteDistribution + + +scipy = import_module("scipy", import_kwargs={'fromlist':['stats']}) + + +@singledispatch +def do_sample_scipy(dist, size, seed): + return None + + +# CRV + +@do_sample_scipy.register(SingleContinuousDistribution) +def _(dist: SingleContinuousDistribution, size, seed): + # if we don't need to make a handmade pdf, we won't + import scipy.stats + + z = Dummy('z') + handmade_pdf = lambdify(z, dist.pdf(z), ['numpy', 'scipy']) + + class scipy_pdf(scipy.stats.rv_continuous): + def _pdf(dist, x): + return handmade_pdf(x) + + scipy_rv = scipy_pdf(a=float(dist.set._inf), + b=float(dist.set._sup), name='scipy_pdf') + return scipy_rv.rvs(size=size, random_state=seed) + + +@do_sample_scipy.register(ChiSquaredDistribution) +def _(dist: ChiSquaredDistribution, size, seed): + # same parametrisation + return scipy.stats.chi2.rvs(df=float(dist.k), size=size, random_state=seed) + + +@do_sample_scipy.register(ExponentialDistribution) +def _(dist: ExponentialDistribution, size, seed): + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.expon.html#scipy.stats.expon + return scipy.stats.expon.rvs(scale=1 / float(dist.rate), size=size, random_state=seed) + + +@do_sample_scipy.register(GammaDistribution) +def _(dist: GammaDistribution, size, seed): + # https://stackoverflow.com/questions/42150965/how-to-plot-gamma-distribution-with-alpha-and-beta-parameters-in-python + return scipy.stats.gamma.rvs(a=float(dist.k), scale=float(dist.theta), size=size, random_state=seed) + + +@do_sample_scipy.register(LogNormalDistribution) +def _(dist: LogNormalDistribution, size, seed): + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.lognorm.html + return scipy.stats.lognorm.rvs(scale=float(exp(dist.mean)), s=float(dist.std), size=size, random_state=seed) + + +@do_sample_scipy.register(NormalDistribution) +def _(dist: NormalDistribution, size, seed): + return scipy.stats.norm.rvs(loc=float(dist.mean), scale=float(dist.std), size=size, random_state=seed) + + +@do_sample_scipy.register(ParetoDistribution) +def _(dist: ParetoDistribution, size, seed): + # https://stackoverflow.com/questions/42260519/defining-pareto-distribution-in-python-scipy + return scipy.stats.pareto.rvs(b=float(dist.alpha), scale=float(dist.xm), size=size, random_state=seed) + + +@do_sample_scipy.register(StudentTDistribution) +def _(dist: StudentTDistribution, size, seed): + return scipy.stats.t.rvs(df=float(dist.nu), size=size, random_state=seed) + + +@do_sample_scipy.register(UniformDistribution) +def _(dist: UniformDistribution, size, seed): + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.uniform.html + return scipy.stats.uniform.rvs(loc=float(dist.left), scale=float(dist.right - dist.left), size=size, random_state=seed) + + +@do_sample_scipy.register(BetaDistribution) +def _(dist: BetaDistribution, size, seed): + # same parametrisation + return scipy.stats.beta.rvs(a=float(dist.alpha), b=float(dist.beta), size=size, random_state=seed) + + +@do_sample_scipy.register(CauchyDistribution) +def _(dist: CauchyDistribution, size, seed): + return scipy.stats.cauchy.rvs(loc=float(dist.x0), scale=float(dist.gamma), size=size, random_state=seed) + + +# DRV: + +@do_sample_scipy.register(DiscreteDistributionHandmade) +def _(dist: DiscreteDistributionHandmade, size, seed): + from scipy.stats import rv_discrete + + z = Dummy('z') + handmade_pmf = lambdify(z, dist.pdf(z), ['numpy', 'scipy']) + + class scipy_pmf(rv_discrete): + def _pmf(dist, x): + return handmade_pmf(x) + + scipy_rv = scipy_pmf(a=float(dist.set._inf), b=float(dist.set._sup), + name='scipy_pmf') + return scipy_rv.rvs(size=size, random_state=seed) + + +@do_sample_scipy.register(GeometricDistribution) +def _(dist: GeometricDistribution, size, seed): + return scipy.stats.geom.rvs(p=float(dist.p), size=size, random_state=seed) + + +@do_sample_scipy.register(LogarithmicDistribution) +def _(dist: LogarithmicDistribution, size, seed): + return scipy.stats.logser.rvs(p=float(dist.p), size=size, random_state=seed) + + +@do_sample_scipy.register(NegativeBinomialDistribution) +def _(dist: NegativeBinomialDistribution, size, seed): + return scipy.stats.nbinom.rvs(n=float(dist.r), p=float(dist.p), size=size, random_state=seed) + + +@do_sample_scipy.register(PoissonDistribution) +def _(dist: PoissonDistribution, size, seed): + return scipy.stats.poisson.rvs(mu=float(dist.lamda), size=size, random_state=seed) + + +@do_sample_scipy.register(SkellamDistribution) +def _(dist: SkellamDistribution, size, seed): + return scipy.stats.skellam.rvs(mu1=float(dist.mu1), mu2=float(dist.mu2), size=size, random_state=seed) + + +@do_sample_scipy.register(YuleSimonDistribution) +def _(dist: YuleSimonDistribution, size, seed): + return scipy.stats.yulesimon.rvs(alpha=float(dist.rho), size=size, random_state=seed) + + +@do_sample_scipy.register(ZetaDistribution) +def _(dist: ZetaDistribution, size, seed): + return scipy.stats.zipf.rvs(a=float(dist.s), size=size, random_state=seed) + + +# FRV: + +@do_sample_scipy.register(SingleFiniteDistribution) +def _(dist: SingleFiniteDistribution, size, seed): + # scipy can handle with custom distributions + + from scipy.stats import rv_discrete + density_ = dist.dict + x, y = [], [] + for k, v in density_.items(): + x.append(int(k)) + y.append(float(v)) + scipy_rv = rv_discrete(name='scipy_rv', values=(x, y)) + return scipy_rv.rvs(size=size, random_state=seed) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..730a58a1e63baf76f3464f4ce20c8b2e6f8bee69 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_continuous_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_continuous_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ec3c7e4cf022e21bb705c8041f24aa28525af63 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_continuous_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_discrete_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_discrete_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42f46458adb2bf8c805c00868237aaae09f569ed Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_discrete_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_finite_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_finite_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81e16303969b82b1c2ac0763dc633c66ec405c7e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/__pycache__/test_sample_finite_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_continuous_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_continuous_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..953bb602df5e63da2882ee118de9dbf24b6f7804 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_continuous_rv.py @@ -0,0 +1,181 @@ +from sympy.core.numbers import oo +from sympy.core.symbol import Symbol +from sympy.functions.elementary.exponential import exp +from sympy.sets.sets import Interval +from sympy.external import import_module +from sympy.stats import Beta, Chi, Normal, Gamma, Exponential, LogNormal, Pareto, ChiSquared, Uniform, sample, \ + BetaPrime, Cauchy, GammaInverse, GaussianInverse, StudentT, Weibull, density, ContinuousRV, FDistribution, \ + Gumbel, Laplace, Logistic, Rayleigh, Triangular +from sympy.testing.pytest import skip, raises + + +def test_sample_numpy(): + distribs_numpy = [ + Beta("B", 1, 1), + Normal("N", 0, 1), + Gamma("G", 2, 7), + Exponential("E", 2), + LogNormal("LN", 0, 1), + Pareto("P", 1, 1), + ChiSquared("CS", 2), + Uniform("U", 0, 1), + FDistribution("FD", 1, 2), + Gumbel("GB", 1, 2), + Laplace("L", 1, 2), + Logistic("LO", 1, 2), + Rayleigh("R", 1), + Triangular("T", 1, 2, 2), + ] + size = 3 + numpy = import_module('numpy') + if not numpy: + skip('Numpy is not installed. Abort tests for _sample_numpy.') + else: + for X in distribs_numpy: + samps = sample(X, size=size, library='numpy') + for sam in samps: + assert sam in X.pspace.domain.set + raises(NotImplementedError, + lambda: sample(Chi("C", 1), library='numpy')) + raises(NotImplementedError, + lambda: Chi("C", 1).pspace.distribution.sample(library='tensorflow')) + + +def test_sample_scipy(): + distribs_scipy = [ + Beta("B", 1, 1), + BetaPrime("BP", 1, 1), + Cauchy("C", 1, 1), + Chi("C", 1), + Normal("N", 0, 1), + Gamma("G", 2, 7), + GammaInverse("GI", 1, 1), + GaussianInverse("GUI", 1, 1), + Exponential("E", 2), + LogNormal("LN", 0, 1), + Pareto("P", 1, 1), + StudentT("S", 2), + ChiSquared("CS", 2), + Uniform("U", 0, 1) + ] + size = 3 + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests for _sample_scipy.') + else: + for X in distribs_scipy: + samps = sample(X, size=size, library='scipy') + samps2 = sample(X, size=(2, 2), library='scipy') + for sam in samps: + assert sam in X.pspace.domain.set + for i in range(2): + for j in range(2): + assert samps2[i][j] in X.pspace.domain.set + + +def test_sample_pymc(): + distribs_pymc = [ + Beta("B", 1, 1), + Cauchy("C", 1, 1), + Normal("N", 0, 1), + Gamma("G", 2, 7), + GaussianInverse("GI", 1, 1), + Exponential("E", 2), + LogNormal("LN", 0, 1), + Pareto("P", 1, 1), + ChiSquared("CS", 2), + Uniform("U", 0, 1) + ] + size = 3 + pymc = import_module('pymc') + if not pymc: + skip('PyMC is not installed. Abort tests for _sample_pymc.') + else: + for X in distribs_pymc: + samps = sample(X, size=size, library='pymc') + for sam in samps: + assert sam in X.pspace.domain.set + raises(NotImplementedError, + lambda: sample(Chi("C", 1), library='pymc')) + + +def test_sampling_gamma_inverse(): + scipy = import_module('scipy') + if not scipy: + skip('Scipy not installed. Abort tests for sampling of gamma inverse.') + X = GammaInverse("x", 1, 1) + assert sample(X) in X.pspace.domain.set + + +def test_lognormal_sampling(): + # Right now, only density function and sampling works + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests') + for i in range(3): + X = LogNormal('x', i, 1) + assert sample(X) in X.pspace.domain.set + + size = 5 + samps = sample(X, size=size) + for samp in samps: + assert samp in X.pspace.domain.set + + +def test_sampling_gaussian_inverse(): + scipy = import_module('scipy') + if not scipy: + skip('Scipy not installed. Abort tests for sampling of Gaussian inverse.') + X = GaussianInverse("x", 1, 1) + assert sample(X, library='scipy') in X.pspace.domain.set + + +def test_prefab_sampling(): + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests') + N = Normal('X', 0, 1) + L = LogNormal('L', 0, 1) + E = Exponential('Ex', 1) + P = Pareto('P', 1, 3) + W = Weibull('W', 1, 1) + U = Uniform('U', 0, 1) + B = Beta('B', 2, 5) + G = Gamma('G', 1, 3) + + variables = [N, L, E, P, W, U, B, G] + niter = 10 + size = 5 + for var in variables: + for _ in range(niter): + assert sample(var) in var.pspace.domain.set + samps = sample(var, size=size) + for samp in samps: + assert samp in var.pspace.domain.set + + +def test_sample_continuous(): + z = Symbol('z') + Z = ContinuousRV(z, exp(-z), set=Interval(0, oo)) + assert density(Z)(-1) == 0 + + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests') + assert sample(Z) in Z.pspace.domain.set + sym, val = list(Z.pspace.sample().items())[0] + assert sym == Z and val in Interval(0, oo) + + libraries = ['scipy', 'numpy', 'pymc'] + for lib in libraries: + try: + imported_lib = import_module(lib) + if imported_lib: + s0, s1, s2 = [], [], [] + s0 = sample(Z, size=10, library=lib, seed=0) + s1 = sample(Z, size=10, library=lib, seed=0) + s2 = sample(Z, size=10, library=lib, seed=1) + assert all(s0 == s1) + assert all(s1 != s2) + except NotImplementedError: + continue diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_discrete_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_discrete_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..90d385cd599222fd7da7c1559b619bafbeb01831 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_discrete_rv.py @@ -0,0 +1,109 @@ +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.external import import_module +from sympy.stats import ( + Geometric, + Poisson, + Zeta, + sample, + Skellam, + Logarithmic, + NegativeBinomial, + YuleSimon, + DiscreteRV, +) +from sympy.testing.pytest import skip, raises, slow + + +def test_sample_numpy(): + distribs_numpy = [ + Geometric('G', 0.5), + Poisson('P', 1), + Zeta('Z', 2) + ] + size = 3 + numpy = import_module('numpy') + if not numpy: + skip('Numpy is not installed. Abort tests for _sample_numpy.') + else: + for X in distribs_numpy: + samps = sample(X, size=size, library='numpy') + for sam in samps: + assert sam in X.pspace.domain.set + raises(NotImplementedError, + lambda: sample(Skellam('S', 1, 1), library='numpy')) + raises(NotImplementedError, + lambda: Skellam('S', 1, 1).pspace.distribution.sample(library='tensorflow')) + + +def test_sample_scipy(): + p = S(2)/3 + x = Symbol('x', integer=True, positive=True) + pdf = p*(1 - p)**(x - 1) # pdf of Geometric Distribution + distribs_scipy = [ + DiscreteRV(x, pdf, set=S.Naturals), + Geometric('G', 0.5), + Logarithmic('L', 0.5), + NegativeBinomial('N', 5, 0.4), + Poisson('P', 1), + Skellam('S', 1, 1), + YuleSimon('Y', 1), + Zeta('Z', 2) + ] + size = 3 + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests for _sample_scipy.') + else: + for X in distribs_scipy: + samps = sample(X, size=size, library='scipy') + samps2 = sample(X, size=(2, 2), library='scipy') + for sam in samps: + assert sam in X.pspace.domain.set + for i in range(2): + for j in range(2): + assert samps2[i][j] in X.pspace.domain.set + + +def test_sample_pymc(): + distribs_pymc = [ + Geometric('G', 0.5), + Poisson('P', 1), + NegativeBinomial('N', 5, 0.4) + ] + size = 3 + pymc = import_module('pymc') + if not pymc: + skip('PyMC is not installed. Abort tests for _sample_pymc.') + else: + for X in distribs_pymc: + samps = sample(X, size=size, library='pymc') + for sam in samps: + assert sam in X.pspace.domain.set + raises(NotImplementedError, + lambda: sample(Skellam('S', 1, 1), library='pymc')) + +@slow +def test_sample_discrete(): + X = Geometric('X', S.Half) + scipy = import_module('scipy') + if not scipy: + skip('Scipy not installed. Abort tests') + assert sample(X) in X.pspace.domain.set + samps = sample(X, size=2) # This takes long time if ran without scipy + for samp in samps: + assert samp in X.pspace.domain.set + + libraries = ['scipy', 'numpy', 'pymc'] + for lib in libraries: + try: + imported_lib = import_module(lib) + if imported_lib: + s0, s1, s2 = [], [], [] + s0 = sample(X, size=10, library=lib, seed=0) + s1 = sample(X, size=10, library=lib, seed=0) + s2 = sample(X, size=10, library=lib, seed=1) + assert all(s0 == s1) + assert not all(s1 == s2) + except NotImplementedError: + continue diff --git a/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_finite_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_finite_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..96cabe0ff4aaa5977e16600217fbbdeb08b962ae --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/sampling/tests/test_sample_finite_rv.py @@ -0,0 +1,94 @@ +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.external import import_module +from sympy.stats import Binomial, sample, Die, FiniteRV, DiscreteUniform, Bernoulli, BetaBinomial, Hypergeometric, \ + Rademacher +from sympy.testing.pytest import skip, raises + +def test_given_sample(): + X = Die('X', 6) + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests') + assert sample(X, X > 5) == 6 + +def test_sample_numpy(): + distribs_numpy = [ + Binomial("B", 5, 0.4), + Hypergeometric("H", 2, 1, 1) + ] + size = 3 + numpy = import_module('numpy') + if not numpy: + skip('Numpy is not installed. Abort tests for _sample_numpy.') + else: + for X in distribs_numpy: + samps = sample(X, size=size, library='numpy') + for sam in samps: + assert sam in X.pspace.domain.set + raises(NotImplementedError, + lambda: sample(Die("D"), library='numpy')) + raises(NotImplementedError, + lambda: Die("D").pspace.sample(library='tensorflow')) + + +def test_sample_scipy(): + distribs_scipy = [ + FiniteRV('F', {1: S.Half, 2: Rational(1, 4), 3: Rational(1, 4)}), + DiscreteUniform("Y", list(range(5))), + Die("D"), + Bernoulli("Be", 0.3), + Binomial("Bi", 5, 0.4), + BetaBinomial("Bb", 2, 1, 1), + Hypergeometric("H", 1, 1, 1), + Rademacher("R") + ] + + size = 3 + scipy = import_module('scipy') + if not scipy: + skip('Scipy not installed. Abort tests for _sample_scipy.') + else: + for X in distribs_scipy: + samps = sample(X, size=size) + samps2 = sample(X, size=(2, 2)) + for sam in samps: + assert sam in X.pspace.domain.set + for i in range(2): + for j in range(2): + assert samps2[i][j] in X.pspace.domain.set + + +def test_sample_pymc(): + distribs_pymc = [ + Bernoulli('B', 0.2), + Binomial('N', 5, 0.4) + ] + size = 3 + pymc = import_module('pymc') + if not pymc: + skip('PyMC is not installed. Abort tests for _sample_pymc.') + else: + for X in distribs_pymc: + samps = sample(X, size=size, library='pymc') + for sam in samps: + assert sam in X.pspace.domain.set + raises(NotImplementedError, + lambda: (sample(Die("D"), library='pymc'))) + + +def test_sample_seed(): + F = FiniteRV('F', {1: S.Half, 2: Rational(1, 4), 3: Rational(1, 4)}) + size = 10 + libraries = ['scipy', 'numpy', 'pymc'] + for lib in libraries: + try: + imported_lib = import_module(lib) + if imported_lib: + s0 = sample(F, size=size, library=lib, seed=0) + s1 = sample(F, size=size, library=lib, seed=0) + s2 = sample(F, size=size, library=lib, seed=1) + assert all(s0 == s1) + assert not all(s1 == s2) + except NotImplementedError: + continue diff --git a/venv/lib/python3.10/site-packages/sympy/stats/stochastic_process.py b/venv/lib/python3.10/site-packages/sympy/stats/stochastic_process.py new file mode 100644 index 0000000000000000000000000000000000000000..bfb0e759c66be892ae38ddda004dfe928f683fee --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/stochastic_process.py @@ -0,0 +1,66 @@ +from sympy.core.basic import Basic +from sympy.stats.joint_rv import ProductPSpace +from sympy.stats.rv import ProductDomain, _symbol_converter, Distribution + + +class StochasticPSpace(ProductPSpace): + """ + Represents probability space of stochastic processes + and their random variables. Contains mechanics to do + computations for queries of stochastic processes. + + Explanation + =========== + + Initialized by symbol, the specific process and + distribution(optional) if the random indexed symbols + of the process follows any specific distribution, like, + in Bernoulli Process, each random indexed symbol follows + Bernoulli distribution. For processes with memory, this + parameter should not be passed. + """ + + def __new__(cls, sym, process, distribution=None): + sym = _symbol_converter(sym) + from sympy.stats.stochastic_process_types import StochasticProcess + if not isinstance(process, StochasticProcess): + raise TypeError("`process` must be an instance of StochasticProcess.") + if distribution is None: + distribution = Distribution() + return Basic.__new__(cls, sym, process, distribution) + + @property + def process(self): + """ + The associated stochastic process. + """ + return self.args[1] + + @property + def domain(self): + return ProductDomain(self.process.index_set, + self.process.state_space) + + @property + def symbol(self): + return self.args[0] + + @property + def distribution(self): + return self.args[2] + + def probability(self, condition, given_condition=None, evaluate=True, **kwargs): + """ + Transfers the task of handling queries to the specific stochastic + process because every process has their own logic of handling such + queries. + """ + return self.process.probability(condition, given_condition, evaluate, **kwargs) + + def compute_expectation(self, expr, condition=None, evaluate=True, **kwargs): + """ + Transfers the task of handling queries to the specific stochastic + process because every process has their own logic of handling such + queries. + """ + return self.process.expectation(expr, condition, evaluate, **kwargs) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/stochastic_process_types.py b/venv/lib/python3.10/site-packages/sympy/stats/stochastic_process_types.py new file mode 100644 index 0000000000000000000000000000000000000000..7387cd3dbcf6defb3b7e475f542d04ecef6fecf6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/stochastic_process_types.py @@ -0,0 +1,2383 @@ +from __future__ import annotations +import random +import itertools +from typing import Sequence as tSequence +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.cache import cacheit +from sympy.core.containers import Tuple +from sympy.core.expr import Expr +from sympy.core.function import (Function, Lambda) +from sympy.core.mul import Mul +from sympy.core.intfunc import igcd +from sympy.core.numbers import (Integer, Rational, oo, pi) +from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.integers import ceiling +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.gamma_functions import gamma +from sympy.logic.boolalg import (And, Not, Or) +from sympy.matrices.exceptions import NonSquareMatrixError +from sympy.matrices.dense import (Matrix, eye, ones, zeros) +from sympy.matrices.expressions.blockmatrix import BlockMatrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import Identity +from sympy.matrices.immutable import ImmutableMatrix +from sympy.sets.conditionset import ConditionSet +from sympy.sets.contains import Contains +from sympy.sets.fancysets import Range +from sympy.sets.sets import (FiniteSet, Intersection, Interval, Set, Union) +from sympy.solvers.solveset import linsolve +from sympy.tensor.indexed import (Indexed, IndexedBase) +from sympy.core.relational import Relational +from sympy.logic.boolalg import Boolean +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import strongly_connected_components +from sympy.stats.joint_rv import JointDistribution +from sympy.stats.joint_rv_types import JointDistributionHandmade +from sympy.stats.rv import (RandomIndexedSymbol, random_symbols, RandomSymbol, + _symbol_converter, _value_check, pspace, given, + dependent, is_random, sample_iter, Distribution, + Density) +from sympy.stats.stochastic_process import StochasticPSpace +from sympy.stats.symbolic_probability import Probability, Expectation +from sympy.stats.frv_types import Bernoulli, BernoulliDistribution, FiniteRV +from sympy.stats.drv_types import Poisson, PoissonDistribution +from sympy.stats.crv_types import Normal, NormalDistribution, Gamma, GammaDistribution +from sympy.core.sympify import _sympify, sympify + +EmptySet = S.EmptySet + +__all__ = [ + 'StochasticProcess', + 'DiscreteTimeStochasticProcess', + 'DiscreteMarkovChain', + 'TransitionMatrixOf', + 'StochasticStateSpaceOf', + 'GeneratorMatrixOf', + 'ContinuousMarkovChain', + 'BernoulliProcess', + 'PoissonProcess', + 'WienerProcess', + 'GammaProcess' +] + + +@is_random.register(Indexed) +def _(x): + return is_random(x.base) + +@is_random.register(RandomIndexedSymbol) # type: ignore +def _(x): + return True + +def _set_converter(itr): + """ + Helper function for converting list/tuple/set to Set. + If parameter is not an instance of list/tuple/set then + no operation is performed. + + Returns + ======= + + Set + The argument converted to Set. + + + Raises + ====== + + TypeError + If the argument is not an instance of list/tuple/set. + """ + if isinstance(itr, (list, tuple, set)): + itr = FiniteSet(*itr) + if not isinstance(itr, Set): + raise TypeError("%s is not an instance of list/tuple/set."%(itr)) + return itr + +def _state_converter(itr: tSequence) -> Tuple | Range: + """ + Helper function for converting list/tuple/set/Range/Tuple/FiniteSet + to tuple/Range. + """ + itr_ret: Tuple | Range + + if isinstance(itr, (Tuple, set, FiniteSet)): + itr_ret = Tuple(*(sympify(i) if isinstance(i, str) else i for i in itr)) + + elif isinstance(itr, (list, tuple)): + # check if states are unique + if len(set(itr)) != len(itr): + raise ValueError('The state space must have unique elements.') + itr_ret = Tuple(*(sympify(i) if isinstance(i, str) else i for i in itr)) + + elif isinstance(itr, Range): + # the only ordered set in SymPy I know of + # try to convert to tuple + try: + itr_ret = Tuple(*(sympify(i) if isinstance(i, str) else i for i in itr)) + except (TypeError, ValueError): + itr_ret = itr + + else: + raise TypeError("%s is not an instance of list/tuple/set/Range/Tuple/FiniteSet." % (itr)) + return itr_ret + +def _sym_sympify(arg): + """ + Converts an arbitrary expression to a type that can be used inside SymPy. + As generally strings are unwise to use in the expressions, + it returns the Symbol of argument if the string type argument is passed. + + Parameters + ========= + + arg: The parameter to be converted to be used in SymPy. + + Returns + ======= + + The converted parameter. + + """ + if isinstance(arg, str): + return Symbol(arg) + else: + return _sympify(arg) + +def _matrix_checks(matrix): + if not isinstance(matrix, (Matrix, MatrixSymbol, ImmutableMatrix)): + raise TypeError("Transition probabilities either should " + "be a Matrix or a MatrixSymbol.") + if matrix.shape[0] != matrix.shape[1]: + raise NonSquareMatrixError("%s is not a square matrix"%(matrix)) + if isinstance(matrix, Matrix): + matrix = ImmutableMatrix(matrix.tolist()) + return matrix + +class StochasticProcess(Basic): + """ + Base class for all the stochastic processes whether + discrete or continuous. + + Parameters + ========== + + sym: Symbol or str + state_space: Set + The state space of the stochastic process, by default S.Reals. + For discrete sets it is zero indexed. + + See Also + ======== + + DiscreteTimeStochasticProcess + """ + + index_set = S.Reals + + def __new__(cls, sym, state_space=S.Reals, **kwargs): + sym = _symbol_converter(sym) + state_space = _set_converter(state_space) + return Basic.__new__(cls, sym, state_space) + + @property + def symbol(self): + return self.args[0] + + @property + def state_space(self) -> FiniteSet | Range: + if not isinstance(self.args[1], (FiniteSet, Range)): + assert isinstance(self.args[1], Tuple) + return FiniteSet(*self.args[1]) + return self.args[1] + + def _deprecation_warn_distribution(self): + sympy_deprecation_warning( + """ + Calling the distribution method with a RandomIndexedSymbol + argument, like X.distribution(X(t)) is deprecated. Instead, call + distribution() with the given timestamp, like + + X.distribution(t) + """, + deprecated_since_version="1.7.1", + active_deprecations_target="deprecated-distribution-randomindexedsymbol", + stacklevel=4, + ) + + def distribution(self, key=None): + if key is None: + self._deprecation_warn_distribution() + return Distribution() + + def density(self, x): + return Density() + + def __call__(self, time): + """ + Overridden in ContinuousTimeStochasticProcess. + """ + raise NotImplementedError("Use [] for indexing discrete time stochastic process.") + + def __getitem__(self, time): + """ + Overridden in DiscreteTimeStochasticProcess. + """ + raise NotImplementedError("Use () for indexing continuous time stochastic process.") + + def probability(self, condition): + raise NotImplementedError() + + def joint_distribution(self, *args): + """ + Computes the joint distribution of the random indexed variables. + + Parameters + ========== + + args: iterable + The finite list of random indexed variables/the key of a stochastic + process whose joint distribution has to be computed. + + Returns + ======= + + JointDistribution + The joint distribution of the list of random indexed variables. + An unevaluated object is returned if it is not possible to + compute the joint distribution. + + Raises + ====== + + ValueError: When the arguments passed are not of type RandomIndexSymbol + or Number. + """ + args = list(args) + for i, arg in enumerate(args): + if S(arg).is_Number: + if self.index_set.is_subset(S.Integers): + args[i] = self.__getitem__(arg) + else: + args[i] = self.__call__(arg) + elif not isinstance(arg, RandomIndexedSymbol): + raise ValueError("Expected a RandomIndexedSymbol or " + "key not %s"%(type(arg))) + + if args[0].pspace.distribution == Distribution(): + return JointDistribution(*args) + density = Lambda(tuple(args), + expr=Mul.fromiter(arg.pspace.process.density(arg) for arg in args)) + return JointDistributionHandmade(density) + + def expectation(self, condition, given_condition): + raise NotImplementedError("Abstract method for expectation queries.") + + def sample(self): + raise NotImplementedError("Abstract method for sampling queries.") + +class DiscreteTimeStochasticProcess(StochasticProcess): + """ + Base class for all discrete stochastic processes. + """ + def __getitem__(self, time): + """ + For indexing discrete time stochastic processes. + + Returns + ======= + + RandomIndexedSymbol + """ + time = sympify(time) + if not time.is_symbol and time not in self.index_set: + raise IndexError("%s is not in the index set of %s"%(time, self.symbol)) + idx_obj = Indexed(self.symbol, time) + pspace_obj = StochasticPSpace(self.symbol, self, self.distribution(time)) + return RandomIndexedSymbol(idx_obj, pspace_obj) + +class ContinuousTimeStochasticProcess(StochasticProcess): + """ + Base class for all continuous time stochastic process. + """ + def __call__(self, time): + """ + For indexing continuous time stochastic processes. + + Returns + ======= + + RandomIndexedSymbol + """ + time = sympify(time) + if not time.is_symbol and time not in self.index_set: + raise IndexError("%s is not in the index set of %s"%(time, self.symbol)) + func_obj = Function(self.symbol)(time) + pspace_obj = StochasticPSpace(self.symbol, self, self.distribution(time)) + return RandomIndexedSymbol(func_obj, pspace_obj) + +class TransitionMatrixOf(Boolean): + """ + Assumes that the matrix is the transition matrix + of the process. + """ + + def __new__(cls, process, matrix): + if not isinstance(process, DiscreteMarkovChain): + raise ValueError("Currently only DiscreteMarkovChain " + "support TransitionMatrixOf.") + matrix = _matrix_checks(matrix) + return Basic.__new__(cls, process, matrix) + + process = property(lambda self: self.args[0]) + matrix = property(lambda self: self.args[1]) + +class GeneratorMatrixOf(TransitionMatrixOf): + """ + Assumes that the matrix is the generator matrix + of the process. + """ + + def __new__(cls, process, matrix): + if not isinstance(process, ContinuousMarkovChain): + raise ValueError("Currently only ContinuousMarkovChain " + "support GeneratorMatrixOf.") + matrix = _matrix_checks(matrix) + return Basic.__new__(cls, process, matrix) + +class StochasticStateSpaceOf(Boolean): + + def __new__(cls, process, state_space): + if not isinstance(process, (DiscreteMarkovChain, ContinuousMarkovChain)): + raise ValueError("Currently only DiscreteMarkovChain and ContinuousMarkovChain " + "support StochasticStateSpaceOf.") + state_space = _state_converter(state_space) + if isinstance(state_space, Range): + ss_size = ceiling((state_space.stop - state_space.start) / state_space.step) + else: + ss_size = len(state_space) + state_index = Range(ss_size) + return Basic.__new__(cls, process, state_index) + + process = property(lambda self: self.args[0]) + state_index = property(lambda self: self.args[1]) + +class MarkovProcess(StochasticProcess): + """ + Contains methods that handle queries + common to Markov processes. + """ + + @property + def number_of_states(self) -> Integer | Symbol: + """ + The number of states in the Markov Chain. + """ + return _sympify(self.args[2].shape[0]) # type: ignore + + @property + def _state_index(self): + """ + Returns state index as Range. + """ + return self.args[1] + + @classmethod + def _sanity_checks(cls, state_space, trans_probs): + # Try to never have None as state_space or trans_probs. + # This helps a lot if we get it done at the start. + if (state_space is None) and (trans_probs is None): + _n = Dummy('n', integer=True, nonnegative=True) + state_space = _state_converter(Range(_n)) + trans_probs = _matrix_checks(MatrixSymbol('_T', _n, _n)) + + elif state_space is None: + trans_probs = _matrix_checks(trans_probs) + state_space = _state_converter(Range(trans_probs.shape[0])) + + elif trans_probs is None: + state_space = _state_converter(state_space) + if isinstance(state_space, Range): + _n = ceiling((state_space.stop - state_space.start) / state_space.step) + else: + _n = len(state_space) + trans_probs = MatrixSymbol('_T', _n, _n) + + else: + state_space = _state_converter(state_space) + trans_probs = _matrix_checks(trans_probs) + # Range object doesn't want to give a symbolic size + # so we do it ourselves. + if isinstance(state_space, Range): + ss_size = ceiling((state_space.stop - state_space.start) / state_space.step) + else: + ss_size = len(state_space) + if ss_size != trans_probs.shape[0]: + raise ValueError('The size of the state space and the number of ' + 'rows of the transition matrix must be the same.') + + return state_space, trans_probs + + def _extract_information(self, given_condition): + """ + Helper function to extract information, like, + transition matrix/generator matrix, state space, etc. + """ + if isinstance(self, DiscreteMarkovChain): + trans_probs = self.transition_probabilities + state_index = self._state_index + elif isinstance(self, ContinuousMarkovChain): + trans_probs = self.generator_matrix + state_index = self._state_index + if isinstance(given_condition, And): + gcs = given_condition.args + given_condition = S.true + for gc in gcs: + if isinstance(gc, TransitionMatrixOf): + trans_probs = gc.matrix + if isinstance(gc, StochasticStateSpaceOf): + state_index = gc.state_index + if isinstance(gc, Relational): + given_condition = given_condition & gc + if isinstance(given_condition, TransitionMatrixOf): + trans_probs = given_condition.matrix + given_condition = S.true + if isinstance(given_condition, StochasticStateSpaceOf): + state_index = given_condition.state_index + given_condition = S.true + return trans_probs, state_index, given_condition + + def _check_trans_probs(self, trans_probs, row_sum=1): + """ + Helper function for checking the validity of transition + probabilities. + """ + if not isinstance(trans_probs, MatrixSymbol): + rows = trans_probs.tolist() + for row in rows: + if (sum(row) - row_sum) != 0: + raise ValueError("Values in a row must sum to %s. " + "If you are using Float or floats then please use Rational."%(row_sum)) + + def _work_out_state_index(self, state_index, given_condition, trans_probs): + """ + Helper function to extract state space if there + is a random symbol in the given condition. + """ + # if given condition is None, then there is no need to work out + # state_space from random variables + if given_condition != None: + rand_var = list(given_condition.atoms(RandomSymbol) - + given_condition.atoms(RandomIndexedSymbol)) + if len(rand_var) == 1: + state_index = rand_var[0].pspace.set + + # `not None` is `True`. So the old test fails for symbolic sizes. + # Need to build the statement differently. + sym_cond = not self.number_of_states.is_Integer + cond1 = not sym_cond and len(state_index) != trans_probs.shape[0] + if cond1: + raise ValueError("state space is not compatible with the transition probabilities.") + if not isinstance(trans_probs.shape[0], Symbol): + state_index = FiniteSet(*range(trans_probs.shape[0])) + return state_index + + @cacheit + def _preprocess(self, given_condition, evaluate): + """ + Helper function for pre-processing the information. + """ + is_insufficient = False + + if not evaluate: # avoid pre-processing if the result is not to be evaluated + return (True, None, None, None) + + # extracting transition matrix and state space + trans_probs, state_index, given_condition = self._extract_information(given_condition) + + # given_condition does not have sufficient information + # for computations + if trans_probs is None or \ + given_condition is None: + is_insufficient = True + else: + # checking transition probabilities + if isinstance(self, DiscreteMarkovChain): + self._check_trans_probs(trans_probs, row_sum=1) + elif isinstance(self, ContinuousMarkovChain): + self._check_trans_probs(trans_probs, row_sum=0) + + # working out state space + state_index = self._work_out_state_index(state_index, given_condition, trans_probs) + + return is_insufficient, trans_probs, state_index, given_condition + + def replace_with_index(self, condition): + if isinstance(condition, Relational): + lhs, rhs = condition.lhs, condition.rhs + if not isinstance(lhs, RandomIndexedSymbol): + lhs, rhs = rhs, lhs + condition = type(condition)(self.index_of.get(lhs, lhs), + self.index_of.get(rhs, rhs)) + return condition + + def probability(self, condition, given_condition=None, evaluate=True, **kwargs): + """ + Handles probability queries for Markov process. + + Parameters + ========== + + condition: Relational + given_condition: Relational/And + + Returns + ======= + Probability + If the information is not sufficient. + Expr + In all other cases. + + Note + ==== + Any information passed at the time of query overrides + any information passed at the time of object creation like + transition probabilities, state space. + Pass the transition matrix using TransitionMatrixOf, + generator matrix using GeneratorMatrixOf and state space + using StochasticStateSpaceOf in given_condition using & or And. + """ + check, mat, state_index, new_given_condition = \ + self._preprocess(given_condition, evaluate) + + rv = list(condition.atoms(RandomIndexedSymbol)) + symbolic = False + for sym in rv: + if sym.key.is_symbol: + symbolic = True + break + + if check: + return Probability(condition, new_given_condition) + + if isinstance(self, ContinuousMarkovChain): + trans_probs = self.transition_probabilities(mat) + elif isinstance(self, DiscreteMarkovChain): + trans_probs = mat + condition = self.replace_with_index(condition) + given_condition = self.replace_with_index(given_condition) + new_given_condition = self.replace_with_index(new_given_condition) + + if isinstance(condition, Relational): + if isinstance(new_given_condition, And): + gcs = new_given_condition.args + else: + gcs = (new_given_condition, ) + min_key_rv = list(new_given_condition.atoms(RandomIndexedSymbol)) + + if len(min_key_rv): + min_key_rv = min_key_rv[0] + for r in rv: + if min_key_rv.key.is_symbol or r.key.is_symbol: + continue + if min_key_rv.key > r.key: + return Probability(condition) + else: + min_key_rv = None + return Probability(condition) + + if symbolic: + return self._symbolic_probability(condition, new_given_condition, rv, min_key_rv) + + if len(rv) > 1: + rv[0] = condition.lhs + rv[1] = condition.rhs + if rv[0].key < rv[1].key: + rv[0], rv[1] = rv[1], rv[0] + if isinstance(condition, Gt): + condition = Lt(condition.lhs, condition.rhs) + elif isinstance(condition, Lt): + condition = Gt(condition.lhs, condition.rhs) + elif isinstance(condition, Ge): + condition = Le(condition.lhs, condition.rhs) + elif isinstance(condition, Le): + condition = Ge(condition.lhs, condition.rhs) + s = Rational(0, 1) + n = len(self.state_space) + + if isinstance(condition, (Eq, Ne)): + for i in range(0, n): + s += self.probability(Eq(rv[0], i), Eq(rv[1], i)) * self.probability(Eq(rv[1], i), new_given_condition) + return s if isinstance(condition, Eq) else 1 - s + else: + upper = 0 + greater = False + if isinstance(condition, (Ge, Lt)): + upper = 1 + if isinstance(condition, (Ge, Gt)): + greater = True + + for i in range(0, n): + if i <= n//2: + for j in range(0, i + upper): + s += self.probability(Eq(rv[0], i), Eq(rv[1], j)) * self.probability(Eq(rv[1], j), new_given_condition) + else: + s += self.probability(Eq(rv[0], i), new_given_condition) + for j in range(i + upper, n): + s -= self.probability(Eq(rv[0], i), Eq(rv[1], j)) * self.probability(Eq(rv[1], j), new_given_condition) + return s if greater else 1 - s + + rv = rv[0] + states = condition.as_set() + prob, gstate = {}, None + for gc in gcs: + if gc.has(min_key_rv): + if gc.has(Probability): + p, gp = (gc.rhs, gc.lhs) if isinstance(gc.lhs, Probability) \ + else (gc.lhs, gc.rhs) + gr = gp.args[0] + gset = Intersection(gr.as_set(), state_index) + gstate = list(gset)[0] + prob[gset] = p + else: + _, gstate = (gc.lhs.key, gc.rhs) if isinstance(gc.lhs, RandomIndexedSymbol) \ + else (gc.rhs.key, gc.lhs) + + if not all(k in self.index_set for k in (rv.key, min_key_rv.key)): + raise IndexError("The timestamps of the process are not in it's index set.") + states = Intersection(states, state_index) if not isinstance(self.number_of_states, Symbol) else states + for state in Union(states, FiniteSet(gstate)): + if not state.is_Integer or Ge(state, mat.shape[0]) is True: + raise IndexError("No information is available for (%s, %s) in " + "transition probabilities of shape, (%s, %s). " + "State space is zero indexed." + %(gstate, state, mat.shape[0], mat.shape[1])) + if prob: + gstates = Union(*prob.keys()) + if len(gstates) == 1: + gstate = list(gstates)[0] + gprob = list(prob.values())[0] + prob[gstates] = gprob + elif len(gstates) == len(state_index) - 1: + gstate = list(state_index - gstates)[0] + gprob = S.One - sum(prob.values()) + prob[state_index - gstates] = gprob + else: + raise ValueError("Conflicting information.") + else: + gprob = S.One + + if min_key_rv == rv: + return sum(prob[FiniteSet(state)] for state in states) + if isinstance(self, ContinuousMarkovChain): + return gprob * sum(trans_probs(rv.key - min_key_rv.key).__getitem__((gstate, state)) + for state in states) + if isinstance(self, DiscreteMarkovChain): + return gprob * sum((trans_probs**(rv.key - min_key_rv.key)).__getitem__((gstate, state)) + for state in states) + + if isinstance(condition, Not): + expr = condition.args[0] + return S.One - self.probability(expr, given_condition, evaluate, **kwargs) + + if isinstance(condition, And): + compute_later, state2cond, conds = [], {}, condition.args + for expr in conds: + if isinstance(expr, Relational): + ris = list(expr.atoms(RandomIndexedSymbol))[0] + if state2cond.get(ris, None) is None: + state2cond[ris] = S.true + state2cond[ris] &= expr + else: + compute_later.append(expr) + ris = [] + for ri in state2cond: + ris.append(ri) + cset = Intersection(state2cond[ri].as_set(), state_index) + if len(cset) == 0: + return S.Zero + state2cond[ri] = cset.as_relational(ri) + sorted_ris = sorted(ris, key=lambda ri: ri.key) + prod = self.probability(state2cond[sorted_ris[0]], given_condition, evaluate, **kwargs) + for i in range(1, len(sorted_ris)): + ri, prev_ri = sorted_ris[i], sorted_ris[i-1] + if not isinstance(state2cond[ri], Eq): + raise ValueError("The process is in multiple states at %s, unable to determine the probability."%(ri)) + mat_of = TransitionMatrixOf(self, mat) if isinstance(self, DiscreteMarkovChain) else GeneratorMatrixOf(self, mat) + prod *= self.probability(state2cond[ri], state2cond[prev_ri] + & mat_of + & StochasticStateSpaceOf(self, state_index), + evaluate, **kwargs) + for expr in compute_later: + prod *= self.probability(expr, given_condition, evaluate, **kwargs) + return prod + + if isinstance(condition, Or): + return sum(self.probability(expr, given_condition, evaluate, **kwargs) + for expr in condition.args) + + raise NotImplementedError("Mechanism for handling (%s, %s) queries hasn't been " + "implemented yet."%(condition, given_condition)) + + def _symbolic_probability(self, condition, new_given_condition, rv, min_key_rv): + #Function to calculate probability for queries with symbols + if isinstance(condition, Relational): + curr_state = new_given_condition.rhs if isinstance(new_given_condition.lhs, RandomIndexedSymbol) \ + else new_given_condition.lhs + next_state = condition.rhs if isinstance(condition.lhs, RandomIndexedSymbol) \ + else condition.lhs + + if isinstance(condition, (Eq, Ne)): + if isinstance(self, DiscreteMarkovChain): + P = self.transition_probabilities**(rv[0].key - min_key_rv.key) + else: + P = exp(self.generator_matrix*(rv[0].key - min_key_rv.key)) + prob = P[curr_state, next_state] if isinstance(condition, Eq) else 1 - P[curr_state, next_state] + return Piecewise((prob, rv[0].key > min_key_rv.key), (Probability(condition), True)) + else: + upper = 1 + greater = False + if isinstance(condition, (Ge, Lt)): + upper = 0 + if isinstance(condition, (Ge, Gt)): + greater = True + k = Dummy('k') + condition = Eq(condition.lhs, k) if isinstance(condition.lhs, RandomIndexedSymbol)\ + else Eq(condition.rhs, k) + total = Sum(self.probability(condition, new_given_condition), (k, next_state + upper, self.state_space._sup)) + return Piecewise((total, rv[0].key > min_key_rv.key), (Probability(condition), True)) if greater\ + else Piecewise((1 - total, rv[0].key > min_key_rv.key), (Probability(condition), True)) + else: + return Probability(condition, new_given_condition) + + def expectation(self, expr, condition=None, evaluate=True, **kwargs): + """ + Handles expectation queries for markov process. + + Parameters + ========== + + expr: RandomIndexedSymbol, Relational, Logic + Condition for which expectation has to be computed. Must + contain a RandomIndexedSymbol of the process. + condition: Relational, Logic + The given conditions under which computations should be done. + + Returns + ======= + + Expectation + Unevaluated object if computations cannot be done due to + insufficient information. + Expr + In all other cases when the computations are successful. + + Note + ==== + + Any information passed at the time of query overrides + any information passed at the time of object creation like + transition probabilities, state space. + + Pass the transition matrix using TransitionMatrixOf, + generator matrix using GeneratorMatrixOf and state space + using StochasticStateSpaceOf in given_condition using & or And. + """ + + check, mat, state_index, condition = \ + self._preprocess(condition, evaluate) + + if check: + return Expectation(expr, condition) + + rvs = random_symbols(expr) + if isinstance(expr, Expr) and isinstance(condition, Eq) \ + and len(rvs) == 1: + # handle queries similar to E(f(X[i]), Eq(X[i-m], )) + condition=self.replace_with_index(condition) + state_index=self.replace_with_index(state_index) + rv = list(rvs)[0] + lhsg, rhsg = condition.lhs, condition.rhs + if not isinstance(lhsg, RandomIndexedSymbol): + lhsg, rhsg = (rhsg, lhsg) + if rhsg not in state_index: + raise ValueError("%s state is not in the state space."%(rhsg)) + if rv.key < lhsg.key: + raise ValueError("Incorrect given condition is given, expectation " + "time %s < time %s"%(rv.key, rv.key)) + mat_of = TransitionMatrixOf(self, mat) if isinstance(self, DiscreteMarkovChain) else GeneratorMatrixOf(self, mat) + cond = condition & mat_of & \ + StochasticStateSpaceOf(self, state_index) + func = lambda s: self.probability(Eq(rv, s), cond) * expr.subs(rv, self._state_index[s]) + return sum(func(s) for s in state_index) + + raise NotImplementedError("Mechanism for handling (%s, %s) queries hasn't been " + "implemented yet."%(expr, condition)) + +class DiscreteMarkovChain(DiscreteTimeStochasticProcess, MarkovProcess): + """ + Represents a finite discrete time-homogeneous Markov chain. + + This type of Markov Chain can be uniquely characterised by + its (ordered) state space and its one-step transition probability + matrix. + + Parameters + ========== + + sym: + The name given to the Markov Chain + state_space: + Optional, by default, Range(n) + trans_probs: + Optional, by default, MatrixSymbol('_T', n, n) + + Examples + ======== + + >>> from sympy.stats import DiscreteMarkovChain, TransitionMatrixOf, P, E + >>> from sympy import Matrix, MatrixSymbol, Eq, symbols + >>> T = Matrix([[0.5, 0.2, 0.3],[0.2, 0.5, 0.3],[0.2, 0.3, 0.5]]) + >>> Y = DiscreteMarkovChain("Y", [0, 1, 2], T) + >>> YS = DiscreteMarkovChain("Y") + + >>> Y.state_space + {0, 1, 2} + >>> Y.transition_probabilities + Matrix([ + [0.5, 0.2, 0.3], + [0.2, 0.5, 0.3], + [0.2, 0.3, 0.5]]) + >>> TS = MatrixSymbol('T', 3, 3) + >>> P(Eq(YS[3], 2), Eq(YS[1], 1) & TransitionMatrixOf(YS, TS)) + T[0, 2]*T[1, 0] + T[1, 1]*T[1, 2] + T[1, 2]*T[2, 2] + >>> P(Eq(Y[3], 2), Eq(Y[1], 1)).round(2) + 0.36 + + Probabilities will be calculated based on indexes rather + than state names. For example, with the Sunny-Cloudy-Rainy + model with string state names: + + >>> from sympy.core.symbol import Str + >>> Y = DiscreteMarkovChain("Y", [Str('Sunny'), Str('Cloudy'), Str('Rainy')], T) + >>> P(Eq(Y[3], 2), Eq(Y[1], 1)).round(2) + 0.36 + + This gives the same answer as the ``[0, 1, 2]`` state space. + Currently, there is no support for state names within probability + and expectation statements. Here is a work-around using ``Str``: + + >>> P(Eq(Str('Rainy'), Y[3]), Eq(Y[1], Str('Cloudy'))).round(2) + 0.36 + + Symbol state names can also be used: + + >>> sunny, cloudy, rainy = symbols('Sunny, Cloudy, Rainy') + >>> Y = DiscreteMarkovChain("Y", [sunny, cloudy, rainy], T) + >>> P(Eq(Y[3], rainy), Eq(Y[1], cloudy)).round(2) + 0.36 + + Expectations will be calculated as follows: + + >>> E(Y[3], Eq(Y[1], cloudy)) + 0.38*Cloudy + 0.36*Rainy + 0.26*Sunny + + Probability of expressions with multiple RandomIndexedSymbols + can also be calculated provided there is only 1 RandomIndexedSymbol + in the given condition. It is always better to use Rational instead + of floating point numbers for the probabilities in the + transition matrix to avoid errors. + + >>> from sympy import Gt, Le, Rational + >>> T = Matrix([[Rational(5, 10), Rational(3, 10), Rational(2, 10)], [Rational(2, 10), Rational(7, 10), Rational(1, 10)], [Rational(3, 10), Rational(3, 10), Rational(4, 10)]]) + >>> Y = DiscreteMarkovChain("Y", [0, 1, 2], T) + >>> P(Eq(Y[3], Y[1]), Eq(Y[0], 0)).round(3) + 0.409 + >>> P(Gt(Y[3], Y[1]), Eq(Y[0], 0)).round(2) + 0.36 + >>> P(Le(Y[15], Y[10]), Eq(Y[8], 2)).round(7) + 0.6963328 + + Symbolic probability queries are also supported + + >>> a, b, c, d = symbols('a b c d') + >>> T = Matrix([[Rational(1, 10), Rational(4, 10), Rational(5, 10)], [Rational(3, 10), Rational(4, 10), Rational(3, 10)], [Rational(7, 10), Rational(2, 10), Rational(1, 10)]]) + >>> Y = DiscreteMarkovChain("Y", [0, 1, 2], T) + >>> query = P(Eq(Y[a], b), Eq(Y[c], d)) + >>> query.subs({a:10, b:2, c:5, d:1}).round(4) + 0.3096 + >>> P(Eq(Y[10], 2), Eq(Y[5], 1)).evalf().round(4) + 0.3096 + >>> query_gt = P(Gt(Y[a], b), Eq(Y[c], d)) + >>> query_gt.subs({a:21, b:0, c:5, d:0}).evalf().round(5) + 0.64705 + >>> P(Gt(Y[21], 0), Eq(Y[5], 0)).round(5) + 0.64705 + + There is limited support for arbitrarily sized states: + + >>> n = symbols('n', nonnegative=True, integer=True) + >>> T = MatrixSymbol('T', n, n) + >>> Y = DiscreteMarkovChain("Y", trans_probs=T) + >>> Y.state_space + Range(0, n, 1) + >>> query = P(Eq(Y[a], b), Eq(Y[c], d)) + >>> query.subs({a:10, b:2, c:5, d:1}) + (T**5)[1, 2] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Markov_chain#Discrete-time_Markov_chain + .. [2] https://web.archive.org/web/20201230182007/https://www.dartmouth.edu/~chance/teaching_aids/books_articles/probability_book/Chapter11.pdf + """ + index_set = S.Naturals0 + + def __new__(cls, sym, state_space=None, trans_probs=None): + sym = _symbol_converter(sym) + + state_space, trans_probs = MarkovProcess._sanity_checks(state_space, trans_probs) + + obj = Basic.__new__(cls, sym, state_space, trans_probs) # type: ignore + indices = {} + if isinstance(obj.number_of_states, Integer): + for index, state in enumerate(obj._state_index): + indices[state] = index + obj.index_of = indices + return obj + + @property + def transition_probabilities(self): + """ + Transition probabilities of discrete Markov chain, + either an instance of Matrix or MatrixSymbol. + """ + return self.args[2] + + def communication_classes(self) -> list[tuple[list[Basic], Boolean, Integer]]: + """ + Returns the list of communication classes that partition + the states of the markov chain. + + A communication class is defined to be a set of states + such that every state in that set is reachable from + every other state in that set. Due to its properties + this forms a class in the mathematical sense. + Communication classes are also known as recurrence + classes. + + Returns + ======= + + classes + The ``classes`` are a list of tuples. Each + tuple represents a single communication class + with its properties. The first element in the + tuple is the list of states in the class, the + second element is whether the class is recurrent + and the third element is the period of the + communication class. + + Examples + ======== + + >>> from sympy.stats import DiscreteMarkovChain + >>> from sympy import Matrix + >>> T = Matrix([[0, 1, 0], + ... [1, 0, 0], + ... [1, 0, 0]]) + >>> X = DiscreteMarkovChain('X', [1, 2, 3], T) + >>> classes = X.communication_classes() + >>> for states, is_recurrent, period in classes: + ... states, is_recurrent, period + ([1, 2], True, 2) + ([3], False, 1) + + From this we can see that states ``1`` and ``2`` + communicate, are recurrent and have a period + of 2. We can also see state ``3`` is transient + with a period of 1. + + Notes + ===== + + The algorithm used is of order ``O(n**2)`` where + ``n`` is the number of states in the markov chain. + It uses Tarjan's algorithm to find the classes + themselves and then it uses a breadth-first search + algorithm to find each class's periodicity. + Most of the algorithm's components approach ``O(n)`` + as the matrix becomes more and more sparse. + + References + ========== + + .. [1] https://web.archive.org/web/20220207032113/https://www.columbia.edu/~ww2040/4701Sum07/4701-06-Notes-MCII.pdf + .. [2] https://cecas.clemson.edu/~shierd/Shier/markov.pdf + .. [3] https://www.proquest.com/openview/4adc6a51d8371be5b0e4c7dff287fc70/1?pq-origsite=gscholar&cbl=2026366&diss=y + .. [4] https://www.mathworks.com/help/econ/dtmc.classify.html + """ + n = self.number_of_states + T = self.transition_probabilities + + if isinstance(T, MatrixSymbol): + raise NotImplementedError("Cannot perform the operation with a symbolic matrix.") + + # begin Tarjan's algorithm + V = Range(n) + # don't use state names. Rather use state + # indexes since we use them for matrix + # indexing here and later onward + E = [(i, j) for i in V for j in V if T[i, j] != 0] + classes = strongly_connected_components((V, E)) + # end Tarjan's algorithm + + recurrence = [] + periods = [] + for class_ in classes: + # begin recurrent check (similar to self._check_trans_probs()) + submatrix = T[class_, class_] # get the submatrix with those states + is_recurrent = S.true + rows = submatrix.tolist() + for row in rows: + if (sum(row) - 1) != 0: + is_recurrent = S.false + break + recurrence.append(is_recurrent) + # end recurrent check + + # begin breadth-first search + non_tree_edge_values: set[int] = set() + visited = {class_[0]} + newly_visited = {class_[0]} + level = {class_[0]: 0} + current_level = 0 + done = False # imitate a do-while loop + while not done: # runs at most len(class_) times + done = len(visited) == len(class_) + current_level += 1 + + # this loop and the while loop above run a combined len(class_) number of times. + # so this triple nested loop runs through each of the n states once. + for i in newly_visited: + + # the loop below runs len(class_) number of times + # complexity is around about O(n * avg(len(class_))) + newly_visited = {j for j in class_ if T[i, j] != 0} + + new_tree_edges = newly_visited.difference(visited) + for j in new_tree_edges: + level[j] = current_level + + new_non_tree_edges = newly_visited.intersection(visited) + new_non_tree_edge_values = {level[i]-level[j]+1 for j in new_non_tree_edges} + + non_tree_edge_values = non_tree_edge_values.union(new_non_tree_edge_values) + visited = visited.union(new_tree_edges) + + # igcd needs at least 2 arguments + positive_ntev = {val_e for val_e in non_tree_edge_values if val_e > 0} + if len(positive_ntev) == 0: + periods.append(len(class_)) + elif len(positive_ntev) == 1: + periods.append(positive_ntev.pop()) + else: + periods.append(igcd(*positive_ntev)) + # end breadth-first search + + # convert back to the user's state names + classes = [[_sympify(self._state_index[i]) for i in class_] for class_ in classes] + return list(zip(classes, recurrence, map(Integer,periods))) + + def fundamental_matrix(self): + """ + Each entry fundamental matrix can be interpreted as + the expected number of times the chains is in state j + if it started in state i. + + References + ========== + + .. [1] https://lips.cs.princeton.edu/the-fundamental-matrix-of-a-finite-markov-chain/ + + """ + _, _, _, Q = self.decompose() + + if Q.shape[0] > 0: # if non-ergodic + I = eye(Q.shape[0]) + if (I - Q).det() == 0: + raise ValueError("The fundamental matrix doesn't exist.") + return (I - Q).inv().as_immutable() + else: # if ergodic + P = self.transition_probabilities + I = eye(P.shape[0]) + w = self.fixed_row_vector() + W = Matrix([list(w) for i in range(0, P.shape[0])]) + if (I - P + W).det() == 0: + raise ValueError("The fundamental matrix doesn't exist.") + return (I - P + W).inv().as_immutable() + + def absorbing_probabilities(self): + """ + Computes the absorbing probabilities, i.e. + the ij-th entry of the matrix denotes the + probability of Markov chain being absorbed + in state j starting from state i. + """ + _, _, R, _ = self.decompose() + N = self.fundamental_matrix() + if R is None or N is None: + return None + return N*R + + def absorbing_probabilites(self): + sympy_deprecation_warning( + """ + DiscreteMarkovChain.absorbing_probabilites() is deprecated. Use + absorbing_probabilities() instead (note the spelling difference). + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-absorbing_probabilites", + ) + return self.absorbing_probabilities() + + def is_regular(self): + tuples = self.communication_classes() + if len(tuples) == 0: + return S.false # not defined for a 0x0 matrix + classes, _, periods = list(zip(*tuples)) + return And(len(classes) == 1, periods[0] == 1) + + def is_ergodic(self): + tuples = self.communication_classes() + if len(tuples) == 0: + return S.false # not defined for a 0x0 matrix + classes, _, _ = list(zip(*tuples)) + return S(len(classes) == 1) + + def is_absorbing_state(self, state): + trans_probs = self.transition_probabilities + if isinstance(trans_probs, ImmutableMatrix) and \ + state < trans_probs.shape[0]: + return S(trans_probs[state, state]) is S.One + + def is_absorbing_chain(self): + states, A, B, C = self.decompose() + r = A.shape[0] + return And(r > 0, A == Identity(r).as_explicit()) + + def stationary_distribution(self, condition_set=False) -> ImmutableMatrix | ConditionSet | Lambda: + r""" + The stationary distribution is any row vector, p, that solves p = pP, + is row stochastic and each element in p must be nonnegative. + That means in matrix form: :math:`(P-I)^T p^T = 0` and + :math:`(1, \dots, 1) p = 1` + where ``P`` is the one-step transition matrix. + + All time-homogeneous Markov Chains with a finite state space + have at least one stationary distribution. In addition, if + a finite time-homogeneous Markov Chain is irreducible, the + stationary distribution is unique. + + Parameters + ========== + + condition_set : bool + If the chain has a symbolic size or transition matrix, + it will return a ``Lambda`` if ``False`` and return a + ``ConditionSet`` if ``True``. + + Examples + ======== + + >>> from sympy.stats import DiscreteMarkovChain + >>> from sympy import Matrix, S + + An irreducible Markov Chain + + >>> T = Matrix([[S(1)/2, S(1)/2, 0], + ... [S(4)/5, S(1)/5, 0], + ... [1, 0, 0]]) + >>> X = DiscreteMarkovChain('X', trans_probs=T) + >>> X.stationary_distribution() + Matrix([[8/13, 5/13, 0]]) + + A reducible Markov Chain + + >>> T = Matrix([[S(1)/2, S(1)/2, 0], + ... [S(4)/5, S(1)/5, 0], + ... [0, 0, 1]]) + >>> X = DiscreteMarkovChain('X', trans_probs=T) + >>> X.stationary_distribution() + Matrix([[8/13 - 8*tau0/13, 5/13 - 5*tau0/13, tau0]]) + + >>> Y = DiscreteMarkovChain('Y') + >>> Y.stationary_distribution() + Lambda((wm, _T), Eq(wm*_T, wm)) + + >>> Y.stationary_distribution(condition_set=True) + ConditionSet(wm, Eq(wm*_T, wm)) + + References + ========== + + .. [1] https://www.probabilitycourse.com/chapter11/11_2_6_stationary_and_limiting_distributions.php + .. [2] https://web.archive.org/web/20210508104430/https://galton.uchicago.edu/~yibi/teaching/stat317/2014/Lectures/Lecture4_6up.pdf + + See Also + ======== + + sympy.stats.DiscreteMarkovChain.limiting_distribution + """ + trans_probs = self.transition_probabilities + n = self.number_of_states + + if n == 0: + return ImmutableMatrix(Matrix([[]])) + + # symbolic matrix version + if isinstance(trans_probs, MatrixSymbol): + wm = MatrixSymbol('wm', 1, n) + if condition_set: + return ConditionSet(wm, Eq(wm * trans_probs, wm)) + else: + return Lambda((wm, trans_probs), Eq(wm * trans_probs, wm)) + + # numeric matrix version + a = Matrix(trans_probs - Identity(n)).T + a[0, 0:n] = ones(1, n) # type: ignore + b = zeros(n, 1) + b[0, 0] = 1 + + soln = list(linsolve((a, b)))[0] + return ImmutableMatrix([soln]) + + def fixed_row_vector(self): + """ + A wrapper for ``stationary_distribution()``. + """ + return self.stationary_distribution() + + @property + def limiting_distribution(self): + """ + The fixed row vector is the limiting + distribution of a discrete Markov chain. + """ + return self.fixed_row_vector() + + def decompose(self) -> tuple[list[Basic], ImmutableMatrix, ImmutableMatrix, ImmutableMatrix]: + """ + Decomposes the transition matrix into submatrices with + special properties. + + The transition matrix can be decomposed into 4 submatrices: + - A - the submatrix from recurrent states to recurrent states. + - B - the submatrix from transient to recurrent states. + - C - the submatrix from transient to transient states. + - O - the submatrix of zeros for recurrent to transient states. + + Returns + ======= + + states, A, B, C + ``states`` - a list of state names with the first being + the recurrent states and the last being + the transient states in the order + of the row names of A and then the row names of C. + ``A`` - the submatrix from recurrent states to recurrent states. + ``B`` - the submatrix from transient to recurrent states. + ``C`` - the submatrix from transient to transient states. + + Examples + ======== + + >>> from sympy.stats import DiscreteMarkovChain + >>> from sympy import Matrix, S + + One can decompose this chain for example: + + >>> T = Matrix([[S(1)/2, S(1)/2, 0, 0, 0], + ... [S(2)/5, S(1)/5, S(2)/5, 0, 0], + ... [0, 0, 1, 0, 0], + ... [0, 0, S(1)/2, S(1)/2, 0], + ... [S(1)/2, 0, 0, 0, S(1)/2]]) + >>> X = DiscreteMarkovChain('X', trans_probs=T) + >>> states, A, B, C = X.decompose() + >>> states + [2, 0, 1, 3, 4] + + >>> A # recurrent to recurrent + Matrix([[1]]) + + >>> B # transient to recurrent + Matrix([ + [ 0], + [2/5], + [1/2], + [ 0]]) + + >>> C # transient to transient + Matrix([ + [1/2, 1/2, 0, 0], + [2/5, 1/5, 0, 0], + [ 0, 0, 1/2, 0], + [1/2, 0, 0, 1/2]]) + + This means that state 2 is the only absorbing state + (since A is a 1x1 matrix). B is a 4x1 matrix since + the 4 remaining transient states all merge into recurrent + state 2. And C is the 4x4 matrix that shows how the + transient states 0, 1, 3, 4 all interact. + + See Also + ======== + + sympy.stats.DiscreteMarkovChain.communication_classes + sympy.stats.DiscreteMarkovChain.canonical_form + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Absorbing_Markov_chain + .. [2] https://people.brandeis.edu/~igusa/Math56aS08/Math56a_S08_notes015.pdf + """ + trans_probs = self.transition_probabilities + + classes = self.communication_classes() + r_states = [] + t_states = [] + + for states, recurrent, period in classes: + if recurrent: + r_states += states + else: + t_states += states + + states = r_states + t_states + indexes = [self.index_of[state] for state in states] # type: ignore + + A = Matrix(len(r_states), len(r_states), + lambda i, j: trans_probs[indexes[i], indexes[j]]) + + B = Matrix(len(t_states), len(r_states), + lambda i, j: trans_probs[indexes[len(r_states) + i], indexes[j]]) + + C = Matrix(len(t_states), len(t_states), + lambda i, j: trans_probs[indexes[len(r_states) + i], indexes[len(r_states) + j]]) + + return states, A.as_immutable(), B.as_immutable(), C.as_immutable() + + def canonical_form(self) -> tuple[list[Basic], ImmutableMatrix]: + """ + Reorders the one-step transition matrix + so that recurrent states appear first and transient + states appear last. Other representations include inserting + transient states first and recurrent states last. + + Returns + ======= + + states, P_new + ``states`` is the list that describes the order of the + new states in the matrix + so that the ith element in ``states`` is the state of the + ith row of A. + ``P_new`` is the new transition matrix in canonical form. + + Examples + ======== + + >>> from sympy.stats import DiscreteMarkovChain + >>> from sympy import Matrix, S + + You can convert your chain into canonical form: + + >>> T = Matrix([[S(1)/2, S(1)/2, 0, 0, 0], + ... [S(2)/5, S(1)/5, S(2)/5, 0, 0], + ... [0, 0, 1, 0, 0], + ... [0, 0, S(1)/2, S(1)/2, 0], + ... [S(1)/2, 0, 0, 0, S(1)/2]]) + >>> X = DiscreteMarkovChain('X', list(range(1, 6)), trans_probs=T) + >>> states, new_matrix = X.canonical_form() + >>> states + [3, 1, 2, 4, 5] + + >>> new_matrix + Matrix([ + [ 1, 0, 0, 0, 0], + [ 0, 1/2, 1/2, 0, 0], + [2/5, 2/5, 1/5, 0, 0], + [1/2, 0, 0, 1/2, 0], + [ 0, 1/2, 0, 0, 1/2]]) + + The new states are [3, 1, 2, 4, 5] and you can + create a new chain with this and its canonical + form will remain the same (since it is already + in canonical form). + + >>> X = DiscreteMarkovChain('X', states, new_matrix) + >>> states, new_matrix = X.canonical_form() + >>> states + [3, 1, 2, 4, 5] + + >>> new_matrix + Matrix([ + [ 1, 0, 0, 0, 0], + [ 0, 1/2, 1/2, 0, 0], + [2/5, 2/5, 1/5, 0, 0], + [1/2, 0, 0, 1/2, 0], + [ 0, 1/2, 0, 0, 1/2]]) + + This is not limited to absorbing chains: + + >>> T = Matrix([[0, 5, 5, 0, 0], + ... [0, 0, 0, 10, 0], + ... [5, 0, 5, 0, 0], + ... [0, 10, 0, 0, 0], + ... [0, 3, 0, 3, 4]])/10 + >>> X = DiscreteMarkovChain('X', trans_probs=T) + >>> states, new_matrix = X.canonical_form() + >>> states + [1, 3, 0, 2, 4] + + >>> new_matrix + Matrix([ + [ 0, 1, 0, 0, 0], + [ 1, 0, 0, 0, 0], + [ 1/2, 0, 0, 1/2, 0], + [ 0, 0, 1/2, 1/2, 0], + [3/10, 3/10, 0, 0, 2/5]]) + + See Also + ======== + + sympy.stats.DiscreteMarkovChain.communication_classes + sympy.stats.DiscreteMarkovChain.decompose + + References + ========== + + .. [1] https://onlinelibrary.wiley.com/doi/pdf/10.1002/9780470316887.app1 + .. [2] http://www.columbia.edu/~ww2040/6711F12/lect1023big.pdf + """ + states, A, B, C = self.decompose() + O = zeros(A.shape[0], C.shape[1]) + return states, BlockMatrix([[A, O], [B, C]]).as_explicit() + + def sample(self): + """ + Returns + ======= + + sample: iterator object + iterator object containing the sample + + """ + if not isinstance(self.transition_probabilities, (Matrix, ImmutableMatrix)): + raise ValueError("Transition Matrix must be provided for sampling") + Tlist = self.transition_probabilities.tolist() + samps = [random.choice(list(self.state_space))] + yield samps[0] + time = 1 + densities = {} + for state in self.state_space: + states = list(self.state_space) + densities[state] = {states[i]: Tlist[state][i] + for i in range(len(states))} + while time < S.Infinity: + samps.append((next(sample_iter(FiniteRV("_", densities[samps[time - 1]]))))) + yield samps[time] + time += 1 + +class ContinuousMarkovChain(ContinuousTimeStochasticProcess, MarkovProcess): + """ + Represents continuous time Markov chain. + + Parameters + ========== + + sym : Symbol/str + state_space : Set + Optional, by default, S.Reals + gen_mat : Matrix/ImmutableMatrix/MatrixSymbol + Optional, by default, None + + Examples + ======== + + >>> from sympy.stats import ContinuousMarkovChain, P + >>> from sympy import Matrix, S, Eq, Gt + >>> G = Matrix([[-S(1), S(1)], [S(1), -S(1)]]) + >>> C = ContinuousMarkovChain('C', state_space=[0, 1], gen_mat=G) + >>> C.limiting_distribution() + Matrix([[1/2, 1/2]]) + >>> C.state_space + {0, 1} + >>> C.generator_matrix + Matrix([ + [-1, 1], + [ 1, -1]]) + + Probability queries are supported + + >>> P(Eq(C(1.96), 0), Eq(C(0.78), 1)).round(5) + 0.45279 + >>> P(Gt(C(1.7), 0), Eq(C(0.82), 1)).round(5) + 0.58602 + + Probability of expressions with multiple RandomIndexedSymbols + can also be calculated provided there is only 1 RandomIndexedSymbol + in the given condition. It is always better to use Rational instead + of floating point numbers for the probabilities in the + generator matrix to avoid errors. + + >>> from sympy import Gt, Le, Rational + >>> G = Matrix([[-S(1), Rational(1, 10), Rational(9, 10)], [Rational(2, 5), -S(1), Rational(3, 5)], [Rational(1, 2), Rational(1, 2), -S(1)]]) + >>> C = ContinuousMarkovChain('C', state_space=[0, 1, 2], gen_mat=G) + >>> P(Eq(C(3.92), C(1.75)), Eq(C(0.46), 0)).round(5) + 0.37933 + >>> P(Gt(C(3.92), C(1.75)), Eq(C(0.46), 0)).round(5) + 0.34211 + >>> P(Le(C(1.57), C(3.14)), Eq(C(1.22), 1)).round(4) + 0.7143 + + Symbolic probability queries are also supported + + >>> from sympy import symbols + >>> a,b,c,d = symbols('a b c d') + >>> G = Matrix([[-S(1), Rational(1, 10), Rational(9, 10)], [Rational(2, 5), -S(1), Rational(3, 5)], [Rational(1, 2), Rational(1, 2), -S(1)]]) + >>> C = ContinuousMarkovChain('C', state_space=[0, 1, 2], gen_mat=G) + >>> query = P(Eq(C(a), b), Eq(C(c), d)) + >>> query.subs({a:3.65, b:2, c:1.78, d:1}).evalf().round(10) + 0.4002723175 + >>> P(Eq(C(3.65), 2), Eq(C(1.78), 1)).round(10) + 0.4002723175 + >>> query_gt = P(Gt(C(a), b), Eq(C(c), d)) + >>> query_gt.subs({a:43.2, b:0, c:3.29, d:2}).evalf().round(10) + 0.6832579186 + >>> P(Gt(C(43.2), 0), Eq(C(3.29), 2)).round(10) + 0.6832579186 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Markov_chain#Continuous-time_Markov_chain + .. [2] https://u.math.biu.ac.il/~amirgi/CTMCnotes.pdf + """ + index_set = S.Reals + + def __new__(cls, sym, state_space=None, gen_mat=None): + sym = _symbol_converter(sym) + state_space, gen_mat = MarkovProcess._sanity_checks(state_space, gen_mat) + obj = Basic.__new__(cls, sym, state_space, gen_mat) + indices = {} + if isinstance(obj.number_of_states, Integer): + for index, state in enumerate(obj.state_space): + indices[state] = index + obj.index_of = indices + return obj + + @property + def generator_matrix(self): + return self.args[2] + + @cacheit + def transition_probabilities(self, gen_mat=None): + t = Dummy('t') + if isinstance(gen_mat, (Matrix, ImmutableMatrix)) and \ + gen_mat.is_diagonalizable(): + # for faster computation use diagonalized generator matrix + Q, D = gen_mat.diagonalize() + return Lambda(t, Q*exp(t*D)*Q.inv()) + if gen_mat != None: + return Lambda(t, exp(t*gen_mat)) + + def limiting_distribution(self): + gen_mat = self.generator_matrix + if gen_mat is None: + return None + if isinstance(gen_mat, MatrixSymbol): + wm = MatrixSymbol('wm', 1, gen_mat.shape[0]) + return Lambda((wm, gen_mat), Eq(wm*gen_mat, wm)) + w = IndexedBase('w') + wi = [w[i] for i in range(gen_mat.shape[0])] + wm = Matrix([wi]) + eqs = (wm*gen_mat).tolist()[0] + eqs.append(sum(wi) - 1) + soln = list(linsolve(eqs, wi))[0] + return ImmutableMatrix([soln]) + + +class BernoulliProcess(DiscreteTimeStochasticProcess): + """ + The Bernoulli process consists of repeated + independent Bernoulli process trials with the same parameter `p`. + It's assumed that the probability `p` applies to every + trial and that the outcomes of each trial + are independent of all the rest. Therefore Bernoulli Process + is Discrete State and Discrete Time Stochastic Process. + + Parameters + ========== + + sym : Symbol/str + success : Integer/str + The event which is considered to be success. Default: 1. + failure: Integer/str + The event which is considered to be failure. Default: 0. + p : Real Number between 0 and 1 + Represents the probability of getting success. + + Examples + ======== + + >>> from sympy.stats import BernoulliProcess, P, E + >>> from sympy import Eq, Gt + >>> B = BernoulliProcess("B", p=0.7, success=1, failure=0) + >>> B.state_space + {0, 1} + >>> B.p.round(2) + 0.70 + >>> B.success + 1 + >>> B.failure + 0 + >>> X = B[1] + B[2] + B[3] + >>> P(Eq(X, 0)).round(2) + 0.03 + >>> P(Eq(X, 2)).round(2) + 0.44 + >>> P(Eq(X, 4)).round(2) + 0 + >>> P(Gt(X, 1)).round(2) + 0.78 + >>> P(Eq(B[1], 0) & Eq(B[2], 1) & Eq(B[3], 0) & Eq(B[4], 1)).round(2) + 0.04 + >>> B.joint_distribution(B[1], B[2]) + JointDistributionHandmade(Lambda((B[1], B[2]), Piecewise((0.7, Eq(B[1], 1)), + (0.3, Eq(B[1], 0)), (0, True))*Piecewise((0.7, Eq(B[2], 1)), (0.3, Eq(B[2], 0)), + (0, True)))) + >>> E(2*B[1] + B[2]).round(2) + 2.10 + >>> P(B[1] < 1).round(2) + 0.30 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Bernoulli_process + .. [2] https://mathcs.clarku.edu/~djoyce/ma217/bernoulli.pdf + + """ + + index_set = S.Naturals0 + + def __new__(cls, sym, p, success=1, failure=0): + _value_check(p >= 0 and p <= 1, 'Value of p must be between 0 and 1.') + sym = _symbol_converter(sym) + p = _sympify(p) + success = _sym_sympify(success) + failure = _sym_sympify(failure) + return Basic.__new__(cls, sym, p, success, failure) + + @property + def symbol(self): + return self.args[0] + + @property + def p(self): + return self.args[1] + + @property + def success(self): + return self.args[2] + + @property + def failure(self): + return self.args[3] + + @property + def state_space(self): + return _set_converter([self.success, self.failure]) + + def distribution(self, key=None): + if key is None: + self._deprecation_warn_distribution() + return BernoulliDistribution(self.p) + return BernoulliDistribution(self.p, self.success, self.failure) + + def simple_rv(self, rv): + return Bernoulli(rv.name, p=self.p, + succ=self.success, fail=self.failure) + + def expectation(self, expr, condition=None, evaluate=True, **kwargs): + """ + Computes expectation. + + Parameters + ========== + + expr : RandomIndexedSymbol, Relational, Logic + Condition for which expectation has to be computed. Must + contain a RandomIndexedSymbol of the process. + condition : Relational, Logic + The given conditions under which computations should be done. + + Returns + ======= + + Expectation of the RandomIndexedSymbol. + + """ + + return _SubstituteRV._expectation(expr, condition, evaluate, **kwargs) + + def probability(self, condition, given_condition=None, evaluate=True, **kwargs): + """ + Computes probability. + + Parameters + ========== + + condition : Relational + Condition for which probability has to be computed. Must + contain a RandomIndexedSymbol of the process. + given_condition : Relational, Logic + The given conditions under which computations should be done. + + Returns + ======= + + Probability of the condition. + + """ + + return _SubstituteRV._probability(condition, given_condition, evaluate, **kwargs) + + def density(self, x): + return Piecewise((self.p, Eq(x, self.success)), + (1 - self.p, Eq(x, self.failure)), + (S.Zero, True)) + +class _SubstituteRV: + """ + Internal class to handle the queries of expectation and probability + by substitution. + """ + + @staticmethod + def _rvindexed_subs(expr, condition=None): + """ + Substitutes the RandomIndexedSymbol with the RandomSymbol with + same name, distribution and probability as RandomIndexedSymbol. + + Parameters + ========== + + expr: RandomIndexedSymbol, Relational, Logic + Condition for which expectation has to be computed. Must + contain a RandomIndexedSymbol of the process. + condition: Relational, Logic + The given conditions under which computations should be done. + + """ + + rvs_expr = random_symbols(expr) + if len(rvs_expr) != 0: + swapdict_expr = {} + for rv in rvs_expr: + if isinstance(rv, RandomIndexedSymbol): + newrv = rv.pspace.process.simple_rv(rv) # substitute with equivalent simple rv + swapdict_expr[rv] = newrv + expr = expr.subs(swapdict_expr) + rvs_cond = random_symbols(condition) + if len(rvs_cond)!=0: + swapdict_cond = {} + for rv in rvs_cond: + if isinstance(rv, RandomIndexedSymbol): + newrv = rv.pspace.process.simple_rv(rv) + swapdict_cond[rv] = newrv + condition = condition.subs(swapdict_cond) + return expr, condition + + @classmethod + def _expectation(self, expr, condition=None, evaluate=True, **kwargs): + """ + Internal method for computing expectation of indexed RV. + + Parameters + ========== + + expr: RandomIndexedSymbol, Relational, Logic + Condition for which expectation has to be computed. Must + contain a RandomIndexedSymbol of the process. + condition: Relational, Logic + The given conditions under which computations should be done. + + Returns + ======= + + Expectation of the RandomIndexedSymbol. + + """ + new_expr, new_condition = self._rvindexed_subs(expr, condition) + + if not is_random(new_expr): + return new_expr + new_pspace = pspace(new_expr) + if new_condition is not None: + new_expr = given(new_expr, new_condition) + if new_expr.is_Add: # As E is Linear + return Add(*[new_pspace.compute_expectation( + expr=arg, evaluate=evaluate, **kwargs) + for arg in new_expr.args]) + return new_pspace.compute_expectation( + new_expr, evaluate=evaluate, **kwargs) + + @classmethod + def _probability(self, condition, given_condition=None, evaluate=True, **kwargs): + """ + Internal method for computing probability of indexed RV + + Parameters + ========== + + condition: Relational + Condition for which probability has to be computed. Must + contain a RandomIndexedSymbol of the process. + given_condition: Relational/And + The given conditions under which computations should be done. + + Returns + ======= + + Probability of the condition. + + """ + new_condition, new_givencondition = self._rvindexed_subs(condition, given_condition) + + if isinstance(new_givencondition, RandomSymbol): + condrv = random_symbols(new_condition) + if len(condrv) == 1 and condrv[0] == new_givencondition: + return BernoulliDistribution(self._probability(new_condition), 0, 1) + + if any(dependent(rv, new_givencondition) for rv in condrv): + return Probability(new_condition, new_givencondition) + else: + return self._probability(new_condition) + + if new_givencondition is not None and \ + not isinstance(new_givencondition, (Relational, Boolean)): + raise ValueError("%s is not a relational or combination of relationals" + % (new_givencondition)) + if new_givencondition == False or new_condition == False: + return S.Zero + if new_condition == True: + return S.One + if not isinstance(new_condition, (Relational, Boolean)): + raise ValueError("%s is not a relational or combination of relationals" + % (new_condition)) + + if new_givencondition is not None: # If there is a condition + # Recompute on new conditional expr + return self._probability(given(new_condition, new_givencondition, **kwargs), **kwargs) + result = pspace(new_condition).probability(new_condition, **kwargs) + if evaluate and hasattr(result, 'doit'): + return result.doit() + else: + return result + +def get_timerv_swaps(expr, condition): + """ + Finds the appropriate interval for each time stamp in expr by parsing + the given condition and returns intervals for each timestamp and + dictionary that maps variable time-stamped Random Indexed Symbol to its + corresponding Random Indexed variable with fixed time stamp. + + Parameters + ========== + + expr: SymPy Expression + Expression containing Random Indexed Symbols with variable time stamps + condition: Relational/Boolean Expression + Expression containing time bounds of variable time stamps in expr + + Examples + ======== + + >>> from sympy.stats.stochastic_process_types import get_timerv_swaps, PoissonProcess + >>> from sympy import symbols, Contains, Interval + >>> x, t, d = symbols('x t d', positive=True) + >>> X = PoissonProcess("X", 3) + >>> get_timerv_swaps(x*X(t), Contains(t, Interval.Lopen(0, 1))) + ([Interval.Lopen(0, 1)], {X(t): X(1)}) + >>> get_timerv_swaps((X(t)**2 + X(d)**2), Contains(t, Interval.Lopen(0, 1)) + ... & Contains(d, Interval.Ropen(1, 4))) # doctest: +SKIP + ([Interval.Ropen(1, 4), Interval.Lopen(0, 1)], {X(d): X(3), X(t): X(1)}) + + Returns + ======= + + intervals: list + List of Intervals/FiniteSet on which each time stamp is defined + rv_swap: dict + Dictionary mapping variable time Random Indexed Symbol to constant time + Random Indexed Variable + + """ + + if not isinstance(condition, (Relational, Boolean)): + raise ValueError("%s is not a relational or combination of relationals" + % (condition)) + expr_syms = list(expr.atoms(RandomIndexedSymbol)) + if isinstance(condition, (And, Or)): + given_cond_args = condition.args + else: # single condition + given_cond_args = (condition, ) + rv_swap = {} + intervals = [] + for expr_sym in expr_syms: + for arg in given_cond_args: + if arg.has(expr_sym.key) and isinstance(expr_sym.key, Symbol): + intv = _set_converter(arg.args[1]) + diff_key = intv._sup - intv._inf + if diff_key == oo: + raise ValueError("%s should have finite bounds" % str(expr_sym.name)) + elif diff_key == S.Zero: # has singleton set + diff_key = intv._sup + rv_swap[expr_sym] = expr_sym.subs({expr_sym.key: diff_key}) + intervals.append(intv) + return intervals, rv_swap + + +class CountingProcess(ContinuousTimeStochasticProcess): + """ + This class handles the common methods of the Counting Processes + such as Poisson, Wiener and Gamma Processes + """ + index_set = _set_converter(Interval(0, oo)) + + @property + def symbol(self): + return self.args[0] + + def expectation(self, expr, condition=None, evaluate=True, **kwargs): + """ + Computes expectation + + Parameters + ========== + + expr: RandomIndexedSymbol, Relational, Logic + Condition for which expectation has to be computed. Must + contain a RandomIndexedSymbol of the process. + condition: Relational, Boolean + The given conditions under which computations should be done, i.e, + the intervals on which each variable time stamp in expr is defined + + Returns + ======= + + Expectation of the given expr + + """ + if condition is not None: + intervals, rv_swap = get_timerv_swaps(expr, condition) + # they are independent when they have non-overlapping intervals + if len(intervals) == 1 or all(Intersection(*intv_comb) == EmptySet + for intv_comb in itertools.combinations(intervals, 2)): + if expr.is_Add: + return Add.fromiter(self.expectation(arg, condition) + for arg in expr.args) + expr = expr.subs(rv_swap) + else: + return Expectation(expr, condition) + + return _SubstituteRV._expectation(expr, evaluate=evaluate, **kwargs) + + def _solve_argwith_tworvs(self, arg): + if arg.args[0].key >= arg.args[1].key or isinstance(arg, Eq): + diff_key = abs(arg.args[0].key - arg.args[1].key) + rv = arg.args[0] + arg = arg.__class__(rv.pspace.process(diff_key), 0) + else: + diff_key = arg.args[1].key - arg.args[0].key + rv = arg.args[1] + arg = arg.__class__(rv.pspace.process(diff_key), 0) + return arg + + def _solve_numerical(self, condition, given_condition=None): + if isinstance(condition, And): + args_list = list(condition.args) + else: + args_list = [condition] + if given_condition is not None: + if isinstance(given_condition, And): + args_list.extend(list(given_condition.args)) + else: + args_list.extend([given_condition]) + # sort the args based on timestamp to get the independent increments in + # each segment using all the condition args as well as given_condition args + args_list = sorted(args_list, key=lambda x: x.args[0].key) + result = [] + cond_args = list(condition.args) if isinstance(condition, And) else [condition] + if args_list[0] in cond_args and not (is_random(args_list[0].args[0]) + and is_random(args_list[0].args[1])): + result.append(_SubstituteRV._probability(args_list[0])) + + if is_random(args_list[0].args[0]) and is_random(args_list[0].args[1]): + arg = self._solve_argwith_tworvs(args_list[0]) + result.append(_SubstituteRV._probability(arg)) + + for i in range(len(args_list) - 1): + curr, nex = args_list[i], args_list[i + 1] + diff_key = nex.args[0].key - curr.args[0].key + working_set = curr.args[0].pspace.process.state_space + if curr.args[1] > nex.args[1]: #impossible condition so return 0 + result.append(0) + break + if isinstance(curr, Eq): + working_set = Intersection(working_set, Interval.Lopen(curr.args[1], oo)) + else: + working_set = Intersection(working_set, curr.as_set()) + if isinstance(nex, Eq): + working_set = Intersection(working_set, Interval(-oo, nex.args[1])) + else: + working_set = Intersection(working_set, nex.as_set()) + if working_set == EmptySet: + rv = Eq(curr.args[0].pspace.process(diff_key), 0) + result.append(_SubstituteRV._probability(rv)) + else: + if working_set.is_finite_set: + if isinstance(curr, Eq) and isinstance(nex, Eq): + rv = Eq(curr.args[0].pspace.process(diff_key), len(working_set)) + result.append(_SubstituteRV._probability(rv)) + elif isinstance(curr, Eq) ^ isinstance(nex, Eq): + result.append(Add.fromiter(_SubstituteRV._probability(Eq( + curr.args[0].pspace.process(diff_key), x)) + for x in range(len(working_set)))) + else: + n = len(working_set) + result.append(Add.fromiter((n - x)*_SubstituteRV._probability(Eq( + curr.args[0].pspace.process(diff_key), x)) for x in range(n))) + else: + result.append(_SubstituteRV._probability( + curr.args[0].pspace.process(diff_key) <= working_set._sup - working_set._inf)) + return Mul.fromiter(result) + + + def probability(self, condition, given_condition=None, evaluate=True, **kwargs): + """ + Computes probability. + + Parameters + ========== + + condition: Relational + Condition for which probability has to be computed. Must + contain a RandomIndexedSymbol of the process. + given_condition: Relational, Boolean + The given conditions under which computations should be done, i.e, + the intervals on which each variable time stamp in expr is defined + + Returns + ======= + + Probability of the condition + + """ + check_numeric = True + if isinstance(condition, (And, Or)): + cond_args = condition.args + else: + cond_args = (condition, ) + # check that condition args are numeric or not + if not all(arg.args[0].key.is_number for arg in cond_args): + check_numeric = False + if given_condition is not None: + check_given_numeric = True + if isinstance(given_condition, (And, Or)): + given_cond_args = given_condition.args + else: + given_cond_args = (given_condition, ) + # check that given condition args are numeric or not + if given_condition.has(Contains): + check_given_numeric = False + # Handle numerical queries + if check_numeric and check_given_numeric: + res = [] + if isinstance(condition, Or): + res.append(Add.fromiter(self._solve_numerical(arg, given_condition) + for arg in condition.args)) + if isinstance(given_condition, Or): + res.append(Add.fromiter(self._solve_numerical(condition, arg) + for arg in given_condition.args)) + if res: + return Add.fromiter(res) + return self._solve_numerical(condition, given_condition) + + # No numeric queries, go by Contains?... then check that all the + # given condition are in form of `Contains` + if not all(arg.has(Contains) for arg in given_cond_args): + raise ValueError("If given condition is passed with `Contains`, then " + "please pass the evaluated condition with its corresponding information " + "in terms of intervals of each time stamp to be passed in given condition.") + + intervals, rv_swap = get_timerv_swaps(condition, given_condition) + # they are independent when they have non-overlapping intervals + if len(intervals) == 1 or all(Intersection(*intv_comb) == EmptySet + for intv_comb in itertools.combinations(intervals, 2)): + if isinstance(condition, And): + return Mul.fromiter(self.probability(arg, given_condition) + for arg in condition.args) + elif isinstance(condition, Or): + return Add.fromiter(self.probability(arg, given_condition) + for arg in condition.args) + condition = condition.subs(rv_swap) + else: + return Probability(condition, given_condition) + if check_numeric: + return self._solve_numerical(condition) + return _SubstituteRV._probability(condition, evaluate=evaluate, **kwargs) + +class PoissonProcess(CountingProcess): + """ + The Poisson process is a counting process. It is usually used in scenarios + where we are counting the occurrences of certain events that appear + to happen at a certain rate, but completely at random. + + Parameters + ========== + + sym : Symbol/str + lamda : Positive number + Rate of the process, ``lambda > 0`` + + Examples + ======== + + >>> from sympy.stats import PoissonProcess, P, E + >>> from sympy import symbols, Eq, Ne, Contains, Interval + >>> X = PoissonProcess("X", lamda=3) + >>> X.state_space + Naturals0 + >>> X.lamda + 3 + >>> t1, t2 = symbols('t1 t2', positive=True) + >>> P(X(t1) < 4) + (9*t1**3/2 + 9*t1**2/2 + 3*t1 + 1)*exp(-3*t1) + >>> P(Eq(X(t1), 2) | Ne(X(t1), 4), Contains(t1, Interval.Ropen(2, 4))) + 1 - 36*exp(-6) + >>> P(Eq(X(t1), 2) & Eq(X(t2), 3), Contains(t1, Interval.Lopen(0, 2)) + ... & Contains(t2, Interval.Lopen(2, 4))) + 648*exp(-12) + >>> E(X(t1)) + 3*t1 + >>> E(X(t1)**2 + 2*X(t2), Contains(t1, Interval.Lopen(0, 1)) + ... & Contains(t2, Interval.Lopen(1, 2))) + 18 + >>> P(X(3) < 1, Eq(X(1), 0)) + exp(-6) + >>> P(Eq(X(4), 3), Eq(X(2), 3)) + exp(-6) + >>> P(X(2) <= 3, X(1) > 1) + 5*exp(-3) + + Merging two Poisson Processes + + >>> Y = PoissonProcess("Y", lamda=4) + >>> Z = X + Y + >>> Z.lamda + 7 + + Splitting a Poisson Process into two independent Poisson Processes + + >>> N, M = Z.split(l1=2, l2=5) + >>> N.lamda, M.lamda + (2, 5) + + References + ========== + + .. [1] https://www.probabilitycourse.com/chapter11/11_0_0_intro.php + .. [2] https://en.wikipedia.org/wiki/Poisson_point_process + + """ + + def __new__(cls, sym, lamda): + _value_check(lamda > 0, 'lamda should be a positive number.') + sym = _symbol_converter(sym) + lamda = _sympify(lamda) + return Basic.__new__(cls, sym, lamda) + + @property + def lamda(self): + return self.args[1] + + @property + def state_space(self): + return S.Naturals0 + + def distribution(self, key): + if isinstance(key, RandomIndexedSymbol): + self._deprecation_warn_distribution() + return PoissonDistribution(self.lamda*key.key) + return PoissonDistribution(self.lamda*key) + + def density(self, x): + return (self.lamda*x.key)**x / factorial(x) * exp(-(self.lamda*x.key)) + + def simple_rv(self, rv): + return Poisson(rv.name, lamda=self.lamda*rv.key) + + def __add__(self, other): + if not isinstance(other, PoissonProcess): + raise ValueError("Only instances of Poisson Process can be merged") + return PoissonProcess(Dummy(self.symbol.name + other.symbol.name), + self.lamda + other.lamda) + + def split(self, l1, l2): + if _sympify(l1 + l2) != self.lamda: + raise ValueError("Sum of l1 and l2 should be %s" % str(self.lamda)) + return PoissonProcess(Dummy("l1"), l1), PoissonProcess(Dummy("l2"), l2) + +class WienerProcess(CountingProcess): + """ + The Wiener process is a real valued continuous-time stochastic process. + In physics it is used to study Brownian motion and it is often also called + Brownian motion due to its historical connection with physical process of the + same name originally observed by Scottish botanist Robert Brown. + + Parameters + ========== + + sym : Symbol/str + + Examples + ======== + + >>> from sympy.stats import WienerProcess, P, E + >>> from sympy import symbols, Contains, Interval + >>> X = WienerProcess("X") + >>> X.state_space + Reals + >>> t1, t2 = symbols('t1 t2', positive=True) + >>> P(X(t1) < 7).simplify() + erf(7*sqrt(2)/(2*sqrt(t1)))/2 + 1/2 + >>> P((X(t1) > 2) | (X(t1) < 4), Contains(t1, Interval.Ropen(2, 4))).simplify() + -erf(1)/2 + erf(2)/2 + 1 + >>> E(X(t1)) + 0 + >>> E(X(t1) + 2*X(t2), Contains(t1, Interval.Lopen(0, 1)) + ... & Contains(t2, Interval.Lopen(1, 2))) + 0 + + References + ========== + + .. [1] https://www.probabilitycourse.com/chapter11/11_4_0_brownian_motion_wiener_process.php + .. [2] https://en.wikipedia.org/wiki/Wiener_process + + """ + def __new__(cls, sym): + sym = _symbol_converter(sym) + return Basic.__new__(cls, sym) + + @property + def state_space(self): + return S.Reals + + def distribution(self, key): + if isinstance(key, RandomIndexedSymbol): + self._deprecation_warn_distribution() + return NormalDistribution(0, sqrt(key.key)) + return NormalDistribution(0, sqrt(key)) + + def density(self, x): + return exp(-x**2/(2*x.key)) / (sqrt(2*pi)*sqrt(x.key)) + + def simple_rv(self, rv): + return Normal(rv.name, 0, sqrt(rv.key)) + + +class GammaProcess(CountingProcess): + r""" + A Gamma process is a random process with independent gamma distributed + increments. It is a pure-jump increasing Levy process. + + Parameters + ========== + + sym : Symbol/str + lamda : Positive number + Jump size of the process, ``lamda > 0`` + gamma : Positive number + Rate of jump arrivals, `\gamma > 0` + + Examples + ======== + + >>> from sympy.stats import GammaProcess, E, P, variance + >>> from sympy import symbols, Contains, Interval, Not + >>> t, d, x, l, g = symbols('t d x l g', positive=True) + >>> X = GammaProcess("X", l, g) + >>> E(X(t)) + g*t/l + >>> variance(X(t)).simplify() + g*t/l**2 + >>> X = GammaProcess('X', 1, 2) + >>> P(X(t) < 1).simplify() + lowergamma(2*t, 1)/gamma(2*t) + >>> P(Not((X(t) < 5) & (X(d) > 3)), Contains(t, Interval.Ropen(2, 4)) & + ... Contains(d, Interval.Lopen(7, 8))).simplify() + -4*exp(-3) + 472*exp(-8)/3 + 1 + >>> E(X(2) + x*E(X(5))) + 10*x + 4 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gamma_process + + """ + def __new__(cls, sym, lamda, gamma): + _value_check(lamda > 0, 'lamda should be a positive number') + _value_check(gamma > 0, 'gamma should be a positive number') + sym = _symbol_converter(sym) + gamma = _sympify(gamma) + lamda = _sympify(lamda) + return Basic.__new__(cls, sym, lamda, gamma) + + @property + def lamda(self): + return self.args[1] + + @property + def gamma(self): + return self.args[2] + + @property + def state_space(self): + return _set_converter(Interval(0, oo)) + + def distribution(self, key): + if isinstance(key, RandomIndexedSymbol): + self._deprecation_warn_distribution() + return GammaDistribution(self.gamma*key.key, 1/self.lamda) + return GammaDistribution(self.gamma*key, 1/self.lamda) + + def density(self, x): + k = self.gamma*x.key + theta = 1/self.lamda + return x**(k - 1) * exp(-x/theta) / (gamma(k)*theta**k) + + def simple_rv(self, rv): + return Gamma(rv.name, self.gamma*rv.key, 1/self.lamda) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/symbolic_multivariate_probability.py b/venv/lib/python3.10/site-packages/sympy/stats/symbolic_multivariate_probability.py new file mode 100644 index 0000000000000000000000000000000000000000..bbe8776e58e82489e29734cea48c9138bc512f34 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/symbolic_multivariate_probability.py @@ -0,0 +1,308 @@ +import itertools + +from sympy.core.add import Add +from sympy.core.expr import Expr +from sympy.core.function import expand as _expand +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.matrices.exceptions import ShapeError +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.matrices.expressions.matmul import MatMul +from sympy.matrices.expressions.special import ZeroMatrix +from sympy.stats.rv import RandomSymbol, is_random +from sympy.core.sympify import _sympify +from sympy.stats.symbolic_probability import Variance, Covariance, Expectation + + +class ExpectationMatrix(Expectation, MatrixExpr): + """ + Expectation of a random matrix expression. + + Examples + ======== + + >>> from sympy.stats import ExpectationMatrix, Normal + >>> from sympy.stats.rv import RandomMatrixSymbol + >>> from sympy import symbols, MatrixSymbol, Matrix + >>> k = symbols("k") + >>> A, B = MatrixSymbol("A", k, k), MatrixSymbol("B", k, k) + >>> X, Y = RandomMatrixSymbol("X", k, 1), RandomMatrixSymbol("Y", k, 1) + >>> ExpectationMatrix(X) + ExpectationMatrix(X) + >>> ExpectationMatrix(A*X).shape + (k, 1) + + To expand the expectation in its expression, use ``expand()``: + + >>> ExpectationMatrix(A*X + B*Y).expand() + A*ExpectationMatrix(X) + B*ExpectationMatrix(Y) + >>> ExpectationMatrix((X + Y)*(X - Y).T).expand() + ExpectationMatrix(X*X.T) - ExpectationMatrix(X*Y.T) + ExpectationMatrix(Y*X.T) - ExpectationMatrix(Y*Y.T) + + To evaluate the ``ExpectationMatrix``, use ``doit()``: + + >>> N11, N12 = Normal('N11', 11, 1), Normal('N12', 12, 1) + >>> N21, N22 = Normal('N21', 21, 1), Normal('N22', 22, 1) + >>> M11, M12 = Normal('M11', 1, 1), Normal('M12', 2, 1) + >>> M21, M22 = Normal('M21', 3, 1), Normal('M22', 4, 1) + >>> x1 = Matrix([[N11, N12], [N21, N22]]) + >>> x2 = Matrix([[M11, M12], [M21, M22]]) + >>> ExpectationMatrix(x1 + x2).doit() + Matrix([ + [12, 14], + [24, 26]]) + + """ + def __new__(cls, expr, condition=None): + expr = _sympify(expr) + if condition is None: + if not is_random(expr): + return expr + obj = Expr.__new__(cls, expr) + else: + condition = _sympify(condition) + obj = Expr.__new__(cls, expr, condition) + + obj._shape = expr.shape + obj._condition = condition + return obj + + @property + def shape(self): + return self._shape + + def expand(self, **hints): + expr = self.args[0] + condition = self._condition + if not is_random(expr): + return expr + + if isinstance(expr, Add): + return Add.fromiter(Expectation(a, condition=condition).expand() + for a in expr.args) + + expand_expr = _expand(expr) + if isinstance(expand_expr, Add): + return Add.fromiter(Expectation(a, condition=condition).expand() + for a in expand_expr.args) + + elif isinstance(expr, (Mul, MatMul)): + rv = [] + nonrv = [] + postnon = [] + + for a in expr.args: + if is_random(a): + if rv: + rv.extend(postnon) + else: + nonrv.extend(postnon) + postnon = [] + rv.append(a) + elif a.is_Matrix: + postnon.append(a) + else: + nonrv.append(a) + + # In order to avoid infinite-looping (MatMul may call .doit() again), + # do not rebuild + if len(nonrv) == 0: + return self + return Mul.fromiter(nonrv)*Expectation(Mul.fromiter(rv), + condition=condition)*Mul.fromiter(postnon) + + return self + +class VarianceMatrix(Variance, MatrixExpr): + """ + Variance of a random matrix probability expression. Also known as + Covariance matrix, auto-covariance matrix, dispersion matrix, + or variance-covariance matrix. + + Examples + ======== + + >>> from sympy.stats import VarianceMatrix + >>> from sympy.stats.rv import RandomMatrixSymbol + >>> from sympy import symbols, MatrixSymbol + >>> k = symbols("k") + >>> A, B = MatrixSymbol("A", k, k), MatrixSymbol("B", k, k) + >>> X, Y = RandomMatrixSymbol("X", k, 1), RandomMatrixSymbol("Y", k, 1) + >>> VarianceMatrix(X) + VarianceMatrix(X) + >>> VarianceMatrix(X).shape + (k, k) + + To expand the variance in its expression, use ``expand()``: + + >>> VarianceMatrix(A*X).expand() + A*VarianceMatrix(X)*A.T + >>> VarianceMatrix(A*X + B*Y).expand() + 2*A*CrossCovarianceMatrix(X, Y)*B.T + A*VarianceMatrix(X)*A.T + B*VarianceMatrix(Y)*B.T + """ + def __new__(cls, arg, condition=None): + arg = _sympify(arg) + + if 1 not in arg.shape: + raise ShapeError("Expression is not a vector") + + shape = (arg.shape[0], arg.shape[0]) if arg.shape[1] == 1 else (arg.shape[1], arg.shape[1]) + + if condition: + obj = Expr.__new__(cls, arg, condition) + else: + obj = Expr.__new__(cls, arg) + + obj._shape = shape + obj._condition = condition + return obj + + @property + def shape(self): + return self._shape + + def expand(self, **hints): + arg = self.args[0] + condition = self._condition + + if not is_random(arg): + return ZeroMatrix(*self.shape) + + if isinstance(arg, RandomSymbol): + return self + elif isinstance(arg, Add): + rv = [] + for a in arg.args: + if is_random(a): + rv.append(a) + variances = Add(*(Variance(xv, condition).expand() for xv in rv)) + map_to_covar = lambda x: 2*Covariance(*x, condition=condition).expand() + covariances = Add(*map(map_to_covar, itertools.combinations(rv, 2))) + return variances + covariances + elif isinstance(arg, (Mul, MatMul)): + nonrv = [] + rv = [] + for a in arg.args: + if is_random(a): + rv.append(a) + else: + nonrv.append(a) + if len(rv) == 0: + return ZeroMatrix(*self.shape) + # Avoid possible infinite loops with MatMul: + if len(nonrv) == 0: + return self + # Variance of many multiple matrix products is not implemented: + if len(rv) > 1: + return self + return Mul.fromiter(nonrv)*Variance(Mul.fromiter(rv), + condition)*(Mul.fromiter(nonrv)).transpose() + + # this expression contains a RandomSymbol somehow: + return self + +class CrossCovarianceMatrix(Covariance, MatrixExpr): + """ + Covariance of a random matrix probability expression. + + Examples + ======== + + >>> from sympy.stats import CrossCovarianceMatrix + >>> from sympy.stats.rv import RandomMatrixSymbol + >>> from sympy import symbols, MatrixSymbol + >>> k = symbols("k") + >>> A, B = MatrixSymbol("A", k, k), MatrixSymbol("B", k, k) + >>> C, D = MatrixSymbol("C", k, k), MatrixSymbol("D", k, k) + >>> X, Y = RandomMatrixSymbol("X", k, 1), RandomMatrixSymbol("Y", k, 1) + >>> Z, W = RandomMatrixSymbol("Z", k, 1), RandomMatrixSymbol("W", k, 1) + >>> CrossCovarianceMatrix(X, Y) + CrossCovarianceMatrix(X, Y) + >>> CrossCovarianceMatrix(X, Y).shape + (k, k) + + To expand the covariance in its expression, use ``expand()``: + + >>> CrossCovarianceMatrix(X + Y, Z).expand() + CrossCovarianceMatrix(X, Z) + CrossCovarianceMatrix(Y, Z) + >>> CrossCovarianceMatrix(A*X, Y).expand() + A*CrossCovarianceMatrix(X, Y) + >>> CrossCovarianceMatrix(A*X, B.T*Y).expand() + A*CrossCovarianceMatrix(X, Y)*B + >>> CrossCovarianceMatrix(A*X + B*Y, C.T*Z + D.T*W).expand() + A*CrossCovarianceMatrix(X, W)*D + A*CrossCovarianceMatrix(X, Z)*C + B*CrossCovarianceMatrix(Y, W)*D + B*CrossCovarianceMatrix(Y, Z)*C + + """ + def __new__(cls, arg1, arg2, condition=None): + arg1 = _sympify(arg1) + arg2 = _sympify(arg2) + + if (1 not in arg1.shape) or (1 not in arg2.shape) or (arg1.shape[1] != arg2.shape[1]): + raise ShapeError("Expression is not a vector") + + shape = (arg1.shape[0], arg2.shape[0]) if arg1.shape[1] == 1 and arg2.shape[1] == 1 \ + else (1, 1) + + if condition: + obj = Expr.__new__(cls, arg1, arg2, condition) + else: + obj = Expr.__new__(cls, arg1, arg2) + + obj._shape = shape + obj._condition = condition + return obj + + @property + def shape(self): + return self._shape + + def expand(self, **hints): + arg1 = self.args[0] + arg2 = self.args[1] + condition = self._condition + + if arg1 == arg2: + return VarianceMatrix(arg1, condition).expand() + + if not is_random(arg1) or not is_random(arg2): + return ZeroMatrix(*self.shape) + + if isinstance(arg1, RandomSymbol) and isinstance(arg2, RandomSymbol): + return CrossCovarianceMatrix(arg1, arg2, condition) + + coeff_rv_list1 = self._expand_single_argument(arg1.expand()) + coeff_rv_list2 = self._expand_single_argument(arg2.expand()) + + addends = [a*CrossCovarianceMatrix(r1, r2, condition=condition)*b.transpose() + for (a, r1) in coeff_rv_list1 for (b, r2) in coeff_rv_list2] + return Add.fromiter(addends) + + @classmethod + def _expand_single_argument(cls, expr): + # return (coefficient, random_symbol) pairs: + if isinstance(expr, RandomSymbol): + return [(S.One, expr)] + elif isinstance(expr, Add): + outval = [] + for a in expr.args: + if isinstance(a, (Mul, MatMul)): + outval.append(cls._get_mul_nonrv_rv_tuple(a)) + elif is_random(a): + outval.append((S.One, a)) + + return outval + elif isinstance(expr, (Mul, MatMul)): + return [cls._get_mul_nonrv_rv_tuple(expr)] + elif is_random(expr): + return [(S.One, expr)] + + @classmethod + def _get_mul_nonrv_rv_tuple(cls, m): + rv = [] + nonrv = [] + for a in m.args: + if is_random(a): + rv.append(a) + else: + nonrv.append(a) + return (Mul.fromiter(nonrv), Mul.fromiter(rv)) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/symbolic_probability.py b/venv/lib/python3.10/site-packages/sympy/stats/symbolic_probability.py new file mode 100644 index 0000000000000000000000000000000000000000..5d0b971a8691f82de15258d4c460129059eaf436 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/symbolic_probability.py @@ -0,0 +1,698 @@ +import itertools +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.expr import Expr +from sympy.core.function import expand as _expand +from sympy.core.mul import Mul +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import Not +from sympy.core.parameters import global_parameters +from sympy.core.sorting import default_sort_key +from sympy.core.sympify import _sympify +from sympy.core.relational import Relational +from sympy.logic.boolalg import Boolean +from sympy.stats import variance, covariance +from sympy.stats.rv import (RandomSymbol, pspace, dependent, + given, sampling_E, RandomIndexedSymbol, is_random, + PSpace, sampling_P, random_symbols) + +__all__ = ['Probability', 'Expectation', 'Variance', 'Covariance'] + + +@is_random.register(Expr) +def _(x): + atoms = x.free_symbols + if len(atoms) == 1 and next(iter(atoms)) == x: + return False + return any(is_random(i) for i in atoms) + +@is_random.register(RandomSymbol) # type: ignore +def _(x): + return True + + +class Probability(Expr): + """ + Symbolic expression for the probability. + + Examples + ======== + + >>> from sympy.stats import Probability, Normal + >>> from sympy import Integral + >>> X = Normal("X", 0, 1) + >>> prob = Probability(X > 1) + >>> prob + Probability(X > 1) + + Integral representation: + + >>> prob.rewrite(Integral) + Integral(sqrt(2)*exp(-_z**2/2)/(2*sqrt(pi)), (_z, 1, oo)) + + Evaluation of the integral: + + >>> prob.evaluate_integral() + sqrt(2)*(-sqrt(2)*sqrt(pi)*erf(sqrt(2)/2) + sqrt(2)*sqrt(pi))/(4*sqrt(pi)) + """ + + is_commutative = True + + def __new__(cls, prob, condition=None, **kwargs): + prob = _sympify(prob) + if condition is None: + obj = Expr.__new__(cls, prob) + else: + condition = _sympify(condition) + obj = Expr.__new__(cls, prob, condition) + obj._condition = condition + return obj + + def doit(self, **hints): + condition = self.args[0] + given_condition = self._condition + numsamples = hints.get('numsamples', False) + evaluate = hints.get('evaluate', True) + + if isinstance(condition, Not): + return S.One - self.func(condition.args[0], given_condition, + evaluate=evaluate).doit(**hints) + + if condition.has(RandomIndexedSymbol): + return pspace(condition).probability(condition, given_condition, + evaluate=evaluate) + + if isinstance(given_condition, RandomSymbol): + condrv = random_symbols(condition) + if len(condrv) == 1 and condrv[0] == given_condition: + from sympy.stats.frv_types import BernoulliDistribution + return BernoulliDistribution(self.func(condition).doit(**hints), 0, 1) + if any(dependent(rv, given_condition) for rv in condrv): + return Probability(condition, given_condition) + else: + return Probability(condition).doit() + + if given_condition is not None and \ + not isinstance(given_condition, (Relational, Boolean)): + raise ValueError("%s is not a relational or combination of relationals" + % (given_condition)) + + if given_condition == False or condition is S.false: + return S.Zero + if not isinstance(condition, (Relational, Boolean)): + raise ValueError("%s is not a relational or combination of relationals" + % (condition)) + if condition is S.true: + return S.One + + if numsamples: + return sampling_P(condition, given_condition, numsamples=numsamples) + if given_condition is not None: # If there is a condition + # Recompute on new conditional expr + return Probability(given(condition, given_condition)).doit() + + # Otherwise pass work off to the ProbabilitySpace + if pspace(condition) == PSpace(): + return Probability(condition, given_condition) + + result = pspace(condition).probability(condition) + if hasattr(result, 'doit') and evaluate: + return result.doit() + else: + return result + + def _eval_rewrite_as_Integral(self, arg, condition=None, **kwargs): + return self.func(arg, condition=condition).doit(evaluate=False) + + _eval_rewrite_as_Sum = _eval_rewrite_as_Integral + + def evaluate_integral(self): + return self.rewrite(Integral).doit() + + +class Expectation(Expr): + """ + Symbolic expression for the expectation. + + Examples + ======== + + >>> from sympy.stats import Expectation, Normal, Probability, Poisson + >>> from sympy import symbols, Integral, Sum + >>> mu = symbols("mu") + >>> sigma = symbols("sigma", positive=True) + >>> X = Normal("X", mu, sigma) + >>> Expectation(X) + Expectation(X) + >>> Expectation(X).evaluate_integral().simplify() + mu + + To get the integral expression of the expectation: + + >>> Expectation(X).rewrite(Integral) + Integral(sqrt(2)*X*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo)) + + The same integral expression, in more abstract terms: + + >>> Expectation(X).rewrite(Probability) + Integral(x*Probability(Eq(X, x)), (x, -oo, oo)) + + To get the Summation expression of the expectation for discrete random variables: + + >>> lamda = symbols('lamda', positive=True) + >>> Z = Poisson('Z', lamda) + >>> Expectation(Z).rewrite(Sum) + Sum(Z*lamda**Z*exp(-lamda)/factorial(Z), (Z, 0, oo)) + + This class is aware of some properties of the expectation: + + >>> from sympy.abc import a + >>> Expectation(a*X) + Expectation(a*X) + >>> Y = Normal("Y", 1, 2) + >>> Expectation(X + Y) + Expectation(X + Y) + + To expand the ``Expectation`` into its expression, use ``expand()``: + + >>> Expectation(X + Y).expand() + Expectation(X) + Expectation(Y) + >>> Expectation(a*X + Y).expand() + a*Expectation(X) + Expectation(Y) + >>> Expectation(a*X + Y) + Expectation(a*X + Y) + >>> Expectation((X + Y)*(X - Y)).expand() + Expectation(X**2) - Expectation(Y**2) + + To evaluate the ``Expectation``, use ``doit()``: + + >>> Expectation(X + Y).doit() + mu + 1 + >>> Expectation(X + Expectation(Y + Expectation(2*X))).doit() + 3*mu + 1 + + To prevent evaluating nested ``Expectation``, use ``doit(deep=False)`` + + >>> Expectation(X + Expectation(Y)).doit(deep=False) + mu + Expectation(Expectation(Y)) + >>> Expectation(X + Expectation(Y + Expectation(2*X))).doit(deep=False) + mu + Expectation(Expectation(Expectation(2*X) + Y)) + + """ + + def __new__(cls, expr, condition=None, **kwargs): + expr = _sympify(expr) + if expr.is_Matrix: + from sympy.stats.symbolic_multivariate_probability import ExpectationMatrix + return ExpectationMatrix(expr, condition) + if condition is None: + if not is_random(expr): + return expr + obj = Expr.__new__(cls, expr) + else: + condition = _sympify(condition) + obj = Expr.__new__(cls, expr, condition) + obj._condition = condition + return obj + + def _eval_is_commutative(self): + return(self.args[0].is_commutative) + + def expand(self, **hints): + expr = self.args[0] + condition = self._condition + + if not is_random(expr): + return expr + + if isinstance(expr, Add): + return Add.fromiter(Expectation(a, condition=condition).expand() + for a in expr.args) + + expand_expr = _expand(expr) + if isinstance(expand_expr, Add): + return Add.fromiter(Expectation(a, condition=condition).expand() + for a in expand_expr.args) + + elif isinstance(expr, Mul): + rv = [] + nonrv = [] + for a in expr.args: + if is_random(a): + rv.append(a) + else: + nonrv.append(a) + return Mul.fromiter(nonrv)*Expectation(Mul.fromiter(rv), condition=condition) + + return self + + def doit(self, **hints): + deep = hints.get('deep', True) + condition = self._condition + expr = self.args[0] + numsamples = hints.get('numsamples', False) + evaluate = hints.get('evaluate', True) + + if deep: + expr = expr.doit(**hints) + + if not is_random(expr) or isinstance(expr, Expectation): # expr isn't random? + return expr + if numsamples: # Computing by monte carlo sampling? + evalf = hints.get('evalf', True) + return sampling_E(expr, condition, numsamples=numsamples, evalf=evalf) + + if expr.has(RandomIndexedSymbol): + return pspace(expr).compute_expectation(expr, condition) + + # Create new expr and recompute E + if condition is not None: # If there is a condition + return self.func(given(expr, condition)).doit(**hints) + + # A few known statements for efficiency + + if expr.is_Add: # We know that E is Linear + return Add(*[self.func(arg, condition).doit(**hints) + if not isinstance(arg, Expectation) else self.func(arg, condition) + for arg in expr.args]) + if expr.is_Mul: + if expr.atoms(Expectation): + return expr + + if pspace(expr) == PSpace(): + return self.func(expr) + # Otherwise case is simple, pass work off to the ProbabilitySpace + result = pspace(expr).compute_expectation(expr, evaluate=evaluate) + if hasattr(result, 'doit') and evaluate: + return result.doit(**hints) + else: + return result + + + def _eval_rewrite_as_Probability(self, arg, condition=None, **kwargs): + rvs = arg.atoms(RandomSymbol) + if len(rvs) > 1: + raise NotImplementedError() + if len(rvs) == 0: + return arg + + rv = rvs.pop() + if rv.pspace is None: + raise ValueError("Probability space not known") + + symbol = rv.symbol + if symbol.name[0].isupper(): + symbol = Symbol(symbol.name.lower()) + else : + symbol = Symbol(symbol.name + "_1") + + if rv.pspace.is_Continuous: + return Integral(arg.replace(rv, symbol)*Probability(Eq(rv, symbol), condition), (symbol, rv.pspace.domain.set.inf, rv.pspace.domain.set.sup)) + else: + if rv.pspace.is_Finite: + raise NotImplementedError + else: + return Sum(arg.replace(rv, symbol)*Probability(Eq(rv, symbol), condition), (symbol, rv.pspace.domain.set.inf, rv.pspace.set.sup)) + + def _eval_rewrite_as_Integral(self, arg, condition=None, evaluate=False, **kwargs): + return self.func(arg, condition=condition).doit(deep=False, evaluate=evaluate) + + _eval_rewrite_as_Sum = _eval_rewrite_as_Integral # For discrete this will be Sum + + def evaluate_integral(self): + return self.rewrite(Integral).doit() + + evaluate_sum = evaluate_integral + +class Variance(Expr): + """ + Symbolic expression for the variance. + + Examples + ======== + + >>> from sympy import symbols, Integral + >>> from sympy.stats import Normal, Expectation, Variance, Probability + >>> mu = symbols("mu", positive=True) + >>> sigma = symbols("sigma", positive=True) + >>> X = Normal("X", mu, sigma) + >>> Variance(X) + Variance(X) + >>> Variance(X).evaluate_integral() + sigma**2 + + Integral representation of the underlying calculations: + + >>> Variance(X).rewrite(Integral) + Integral(sqrt(2)*(X - Integral(sqrt(2)*X*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo)))**2*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo)) + + Integral representation, without expanding the PDF: + + >>> Variance(X).rewrite(Probability) + -Integral(x*Probability(Eq(X, x)), (x, -oo, oo))**2 + Integral(x**2*Probability(Eq(X, x)), (x, -oo, oo)) + + Rewrite the variance in terms of the expectation + + >>> Variance(X).rewrite(Expectation) + -Expectation(X)**2 + Expectation(X**2) + + Some transformations based on the properties of the variance may happen: + + >>> from sympy.abc import a + >>> Y = Normal("Y", 0, 1) + >>> Variance(a*X) + Variance(a*X) + + To expand the variance in its expression, use ``expand()``: + + >>> Variance(a*X).expand() + a**2*Variance(X) + >>> Variance(X + Y) + Variance(X + Y) + >>> Variance(X + Y).expand() + 2*Covariance(X, Y) + Variance(X) + Variance(Y) + + """ + def __new__(cls, arg, condition=None, **kwargs): + arg = _sympify(arg) + + if arg.is_Matrix: + from sympy.stats.symbolic_multivariate_probability import VarianceMatrix + return VarianceMatrix(arg, condition) + if condition is None: + obj = Expr.__new__(cls, arg) + else: + condition = _sympify(condition) + obj = Expr.__new__(cls, arg, condition) + obj._condition = condition + return obj + + def _eval_is_commutative(self): + return self.args[0].is_commutative + + def expand(self, **hints): + arg = self.args[0] + condition = self._condition + + if not is_random(arg): + return S.Zero + + if isinstance(arg, RandomSymbol): + return self + elif isinstance(arg, Add): + rv = [] + for a in arg.args: + if is_random(a): + rv.append(a) + variances = Add(*(Variance(xv, condition).expand() for xv in rv)) + map_to_covar = lambda x: 2*Covariance(*x, condition=condition).expand() + covariances = Add(*map(map_to_covar, itertools.combinations(rv, 2))) + return variances + covariances + elif isinstance(arg, Mul): + nonrv = [] + rv = [] + for a in arg.args: + if is_random(a): + rv.append(a) + else: + nonrv.append(a**2) + if len(rv) == 0: + return S.Zero + return Mul.fromiter(nonrv)*Variance(Mul.fromiter(rv), condition) + + # this expression contains a RandomSymbol somehow: + return self + + def _eval_rewrite_as_Expectation(self, arg, condition=None, **kwargs): + e1 = Expectation(arg**2, condition) + e2 = Expectation(arg, condition)**2 + return e1 - e2 + + def _eval_rewrite_as_Probability(self, arg, condition=None, **kwargs): + return self.rewrite(Expectation).rewrite(Probability) + + def _eval_rewrite_as_Integral(self, arg, condition=None, **kwargs): + return variance(self.args[0], self._condition, evaluate=False) + + _eval_rewrite_as_Sum = _eval_rewrite_as_Integral + + def evaluate_integral(self): + return self.rewrite(Integral).doit() + + +class Covariance(Expr): + """ + Symbolic expression for the covariance. + + Examples + ======== + + >>> from sympy.stats import Covariance + >>> from sympy.stats import Normal + >>> X = Normal("X", 3, 2) + >>> Y = Normal("Y", 0, 1) + >>> Z = Normal("Z", 0, 1) + >>> W = Normal("W", 0, 1) + >>> cexpr = Covariance(X, Y) + >>> cexpr + Covariance(X, Y) + + Evaluate the covariance, `X` and `Y` are independent, + therefore zero is the result: + + >>> cexpr.evaluate_integral() + 0 + + Rewrite the covariance expression in terms of expectations: + + >>> from sympy.stats import Expectation + >>> cexpr.rewrite(Expectation) + Expectation(X*Y) - Expectation(X)*Expectation(Y) + + In order to expand the argument, use ``expand()``: + + >>> from sympy.abc import a, b, c, d + >>> Covariance(a*X + b*Y, c*Z + d*W) + Covariance(a*X + b*Y, c*Z + d*W) + >>> Covariance(a*X + b*Y, c*Z + d*W).expand() + a*c*Covariance(X, Z) + a*d*Covariance(W, X) + b*c*Covariance(Y, Z) + b*d*Covariance(W, Y) + + This class is aware of some properties of the covariance: + + >>> Covariance(X, X).expand() + Variance(X) + >>> Covariance(a*X, b*Y).expand() + a*b*Covariance(X, Y) + """ + + def __new__(cls, arg1, arg2, condition=None, **kwargs): + arg1 = _sympify(arg1) + arg2 = _sympify(arg2) + + if arg1.is_Matrix or arg2.is_Matrix: + from sympy.stats.symbolic_multivariate_probability import CrossCovarianceMatrix + return CrossCovarianceMatrix(arg1, arg2, condition) + + if kwargs.pop('evaluate', global_parameters.evaluate): + arg1, arg2 = sorted([arg1, arg2], key=default_sort_key) + + if condition is None: + obj = Expr.__new__(cls, arg1, arg2) + else: + condition = _sympify(condition) + obj = Expr.__new__(cls, arg1, arg2, condition) + obj._condition = condition + return obj + + def _eval_is_commutative(self): + return self.args[0].is_commutative + + def expand(self, **hints): + arg1 = self.args[0] + arg2 = self.args[1] + condition = self._condition + + if arg1 == arg2: + return Variance(arg1, condition).expand() + + if not is_random(arg1): + return S.Zero + if not is_random(arg2): + return S.Zero + + arg1, arg2 = sorted([arg1, arg2], key=default_sort_key) + + if isinstance(arg1, RandomSymbol) and isinstance(arg2, RandomSymbol): + return Covariance(arg1, arg2, condition) + + coeff_rv_list1 = self._expand_single_argument(arg1.expand()) + coeff_rv_list2 = self._expand_single_argument(arg2.expand()) + + addends = [a*b*Covariance(*sorted([r1, r2], key=default_sort_key), condition=condition) + for (a, r1) in coeff_rv_list1 for (b, r2) in coeff_rv_list2] + return Add.fromiter(addends) + + @classmethod + def _expand_single_argument(cls, expr): + # return (coefficient, random_symbol) pairs: + if isinstance(expr, RandomSymbol): + return [(S.One, expr)] + elif isinstance(expr, Add): + outval = [] + for a in expr.args: + if isinstance(a, Mul): + outval.append(cls._get_mul_nonrv_rv_tuple(a)) + elif is_random(a): + outval.append((S.One, a)) + + return outval + elif isinstance(expr, Mul): + return [cls._get_mul_nonrv_rv_tuple(expr)] + elif is_random(expr): + return [(S.One, expr)] + + @classmethod + def _get_mul_nonrv_rv_tuple(cls, m): + rv = [] + nonrv = [] + for a in m.args: + if is_random(a): + rv.append(a) + else: + nonrv.append(a) + return (Mul.fromiter(nonrv), Mul.fromiter(rv)) + + def _eval_rewrite_as_Expectation(self, arg1, arg2, condition=None, **kwargs): + e1 = Expectation(arg1*arg2, condition) + e2 = Expectation(arg1, condition)*Expectation(arg2, condition) + return e1 - e2 + + def _eval_rewrite_as_Probability(self, arg1, arg2, condition=None, **kwargs): + return self.rewrite(Expectation).rewrite(Probability) + + def _eval_rewrite_as_Integral(self, arg1, arg2, condition=None, **kwargs): + return covariance(self.args[0], self.args[1], self._condition, evaluate=False) + + _eval_rewrite_as_Sum = _eval_rewrite_as_Integral + + def evaluate_integral(self): + return self.rewrite(Integral).doit() + + +class Moment(Expr): + """ + Symbolic class for Moment + + Examples + ======== + + >>> from sympy import Symbol, Integral + >>> from sympy.stats import Normal, Expectation, Probability, Moment + >>> mu = Symbol('mu', real=True) + >>> sigma = Symbol('sigma', positive=True) + >>> X = Normal('X', mu, sigma) + >>> M = Moment(X, 3, 1) + + To evaluate the result of Moment use `doit`: + + >>> M.doit() + mu**3 - 3*mu**2 + 3*mu*sigma**2 + 3*mu - 3*sigma**2 - 1 + + Rewrite the Moment expression in terms of Expectation: + + >>> M.rewrite(Expectation) + Expectation((X - 1)**3) + + Rewrite the Moment expression in terms of Probability: + + >>> M.rewrite(Probability) + Integral((x - 1)**3*Probability(Eq(X, x)), (x, -oo, oo)) + + Rewrite the Moment expression in terms of Integral: + + >>> M.rewrite(Integral) + Integral(sqrt(2)*(X - 1)**3*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo)) + + """ + def __new__(cls, X, n, c=0, condition=None, **kwargs): + X = _sympify(X) + n = _sympify(n) + c = _sympify(c) + if condition is not None: + condition = _sympify(condition) + return super().__new__(cls, X, n, c, condition) + else: + return super().__new__(cls, X, n, c) + + def doit(self, **hints): + return self.rewrite(Expectation).doit(**hints) + + def _eval_rewrite_as_Expectation(self, X, n, c=0, condition=None, **kwargs): + return Expectation((X - c)**n, condition) + + def _eval_rewrite_as_Probability(self, X, n, c=0, condition=None, **kwargs): + return self.rewrite(Expectation).rewrite(Probability) + + def _eval_rewrite_as_Integral(self, X, n, c=0, condition=None, **kwargs): + return self.rewrite(Expectation).rewrite(Integral) + + +class CentralMoment(Expr): + """ + Symbolic class Central Moment + + Examples + ======== + + >>> from sympy import Symbol, Integral + >>> from sympy.stats import Normal, Expectation, Probability, CentralMoment + >>> mu = Symbol('mu', real=True) + >>> sigma = Symbol('sigma', positive=True) + >>> X = Normal('X', mu, sigma) + >>> CM = CentralMoment(X, 4) + + To evaluate the result of CentralMoment use `doit`: + + >>> CM.doit().simplify() + 3*sigma**4 + + Rewrite the CentralMoment expression in terms of Expectation: + + >>> CM.rewrite(Expectation) + Expectation((-Expectation(X) + X)**4) + + Rewrite the CentralMoment expression in terms of Probability: + + >>> CM.rewrite(Probability) + Integral((x - Integral(x*Probability(True), (x, -oo, oo)))**4*Probability(Eq(X, x)), (x, -oo, oo)) + + Rewrite the CentralMoment expression in terms of Integral: + + >>> CM.rewrite(Integral) + Integral(sqrt(2)*(X - Integral(sqrt(2)*X*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo)))**4*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo)) + + """ + def __new__(cls, X, n, condition=None, **kwargs): + X = _sympify(X) + n = _sympify(n) + if condition is not None: + condition = _sympify(condition) + return super().__new__(cls, X, n, condition) + else: + return super().__new__(cls, X, n) + + def doit(self, **hints): + return self.rewrite(Expectation).doit(**hints) + + def _eval_rewrite_as_Expectation(self, X, n, condition=None, **kwargs): + mu = Expectation(X, condition, **kwargs) + return Moment(X, n, mu, condition, **kwargs).rewrite(Expectation) + + def _eval_rewrite_as_Probability(self, X, n, condition=None, **kwargs): + return self.rewrite(Expectation).rewrite(Probability) + + def _eval_rewrite_as_Integral(self, X, n, condition=None, **kwargs): + return self.rewrite(Expectation).rewrite(Integral) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74d1935c9d6c42b37f7da6c93604a2306e272aa8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_compound_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_compound_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57d2ca43579692a115119d6d207883df1720e4d0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_compound_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_continuous_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_continuous_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0414883de54aeb5cfbe82e4c8e2d9062ef19a0e0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_continuous_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_discrete_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_discrete_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..326131c4a4df8a62b66b5f9614b737b10b3860e8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_discrete_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_error_prop.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_error_prop.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19a9d9259eda8d3ac1a8fd0043dee28eacf2caea Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_error_prop.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_finite_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_finite_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..516dbd6b84c46f119b1dbdf0e60321cf7bf2c47c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_finite_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_joint_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_joint_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f357bc814c420fa21d3226c50e4e65228e28180c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_joint_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_matrix_distributions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_matrix_distributions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3af018efce93e1dad5e3409307e6ffd2a896b02e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_matrix_distributions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_mix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_mix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07064648162c2be58134764d91ff2fc887ad1d96 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_mix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_random_matrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_random_matrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fae8060b986904945a70685a24ce4d97f79e0f81 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_random_matrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_rv.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_rv.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23cf37f8af64234229c1f00f3b84e4db20313702 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_rv.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_stochastic_process.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_stochastic_process.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccee94d6e24999d48dadd47b584dfd7533d43b24 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_stochastic_process.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_symbolic_multivariate.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_symbolic_multivariate.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c490f38cee8a5eb153d4de23649bbf55ce33e01 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_symbolic_multivariate.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_symbolic_probability.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_symbolic_probability.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd2ab23e5a0051ba9546b52cb29e4777f3a4e51e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/stats/tests/__pycache__/test_symbolic_probability.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_compound_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_compound_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..573ba364b686738e56bb1c4615acd2a9bc8bf3ae --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_compound_rv.py @@ -0,0 +1,159 @@ +from sympy.concrete.summations import Sum +from sympy.core.numbers import (oo, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.beta_functions import beta +from sympy.functions.special.error_functions import erf +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import Integral +from sympy.sets.sets import Interval +from sympy.stats import (Normal, P, E, density, Gamma, Poisson, Rayleigh, + variance, Bernoulli, Beta, Uniform, cdf) +from sympy.stats.compound_rv import CompoundDistribution, CompoundPSpace +from sympy.stats.crv_types import NormalDistribution +from sympy.stats.drv_types import PoissonDistribution +from sympy.stats.frv_types import BernoulliDistribution +from sympy.testing.pytest import raises, ignore_warnings +from sympy.stats.joint_rv_types import MultivariateNormalDistribution + +from sympy.abc import x + + +# helpers for testing troublesome unevaluated expressions +flat = lambda s: ''.join(str(s).split()) +streq = lambda *a: len(set(map(flat, a))) == 1 +assert streq(x, x) +assert streq(x, 'x') +assert not streq(x, x + 1) + + +def test_normal_CompoundDist(): + X = Normal('X', 1, 2) + Y = Normal('X', X, 4) + assert density(Y)(x).simplify() == sqrt(10)*exp(-x**2/40 + x/20 - S(1)/40)/(20*sqrt(pi)) + assert E(Y) == 1 # it is always equal to mean of X + assert P(Y > 1) == S(1)/2 # as 1 is the mean + assert P(Y > 5).simplify() == S(1)/2 - erf(sqrt(10)/5)/2 + assert variance(Y) == variance(X) + 4**2 # 2**2 + 4**2 + # https://math.stackexchange.com/questions/1484451/ + # (Contains proof of E and variance computation) + + +def test_poisson_CompoundDist(): + k, t, y = symbols('k t y', positive=True, real=True) + G = Gamma('G', k, t) + D = Poisson('P', G) + assert density(D)(y).simplify() == t**y*(t + 1)**(-k - y)*gamma(k + y)/(gamma(k)*gamma(y + 1)) + # https://en.wikipedia.org/wiki/Negative_binomial_distribution#Gamma%E2%80%93Poisson_mixture + assert E(D).simplify() == k*t # mean of NegativeBinomialDistribution + + +def test_bernoulli_CompoundDist(): + X = Beta('X', 1, 2) + Y = Bernoulli('Y', X) + assert density(Y).dict == {0: S(2)/3, 1: S(1)/3} + assert E(Y) == P(Eq(Y, 1)) == S(1)/3 + assert variance(Y) == S(2)/9 + assert cdf(Y) == {0: S(2)/3, 1: 1} + + # test issue 8128 + a = Bernoulli('a', S(1)/2) + b = Bernoulli('b', a) + assert density(b).dict == {0: S(1)/2, 1: S(1)/2} + assert P(b > 0.5) == S(1)/2 + + X = Uniform('X', 0, 1) + Y = Bernoulli('Y', X) + assert E(Y) == S(1)/2 + assert P(Eq(Y, 1)) == E(Y) + + +def test_unevaluated_CompoundDist(): + # these tests need to be removed once they work with evaluation as they are currently not + # evaluated completely in sympy. + R = Rayleigh('R', 4) + X = Normal('X', 3, R) + ans = ''' + Piecewise(((-sqrt(pi)*sinh(x/4 - 3/4) + sqrt(pi)*cosh(x/4 - 3/4))/( + 8*sqrt(pi)), Abs(arg(x - 3)) <= pi/4), (Integral(sqrt(2)*exp(-(x - 3) + **2/(2*R**2))*exp(-R**2/32)/(32*sqrt(pi)), (R, 0, oo)), True))''' + assert streq(density(X)(x), ans) + + expre = ''' + Integral(X*Integral(sqrt(2)*exp(-(X-3)**2/(2*R**2))*exp(-R**2/32)/(32* + sqrt(pi)),(R,0,oo)),(X,-oo,oo))''' + with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed + assert streq(E(X, evaluate=False).rewrite(Integral), expre) + + X = Poisson('X', 1) + Y = Poisson('Y', X) + Z = Poisson('Z', Y) + exprd = Sum(exp(-Y)*Y**x*Sum(exp(-1)*exp(-X)*X**Y/(factorial(X)*factorial(Y) + ), (X, 0, oo))/factorial(x), (Y, 0, oo)) + assert density(Z)(x) == exprd + + N = Normal('N', 1, 2) + M = Normal('M', 3, 4) + D = Normal('D', M, N) + exprd = ''' + Integral(sqrt(2)*exp(-(N-1)**2/8)*Integral(exp(-(x-M)**2/(2*N**2))*exp + (-(M-3)**2/32)/(8*pi*N),(M,-oo,oo))/(4*sqrt(pi)),(N,-oo,oo))''' + assert streq(density(D, evaluate=False)(x), exprd) + + +def test_Compound_Distribution(): + X = Normal('X', 2, 4) + N = NormalDistribution(X, 4) + C = CompoundDistribution(N) + assert C.is_Continuous + assert C.set == Interval(-oo, oo) + assert C.pdf(x, evaluate=True).simplify() == exp(-x**2/64 + x/16 - S(1)/16)/(8*sqrt(pi)) + + assert not isinstance(CompoundDistribution(NormalDistribution(2, 3)), + CompoundDistribution) + M = MultivariateNormalDistribution([1, 2], [[2, 1], [1, 2]]) + raises(NotImplementedError, lambda: CompoundDistribution(M)) + + X = Beta('X', 2, 4) + B = BernoulliDistribution(X, 1, 0) + C = CompoundDistribution(B) + assert C.is_Finite + assert C.set == {0, 1} + y = symbols('y', negative=False, integer=True) + assert C.pdf(y, evaluate=True) == Piecewise((S(1)/(30*beta(2, 4)), Eq(y, 0)), + (S(1)/(60*beta(2, 4)), Eq(y, 1)), (0, True)) + + k, t, z = symbols('k t z', positive=True, real=True) + G = Gamma('G', k, t) + X = PoissonDistribution(G) + C = CompoundDistribution(X) + assert C.is_Discrete + assert C.set == S.Naturals0 + assert C.pdf(z, evaluate=True).simplify() == t**z*(t + 1)**(-k - z)*gamma(k \ + + z)/(gamma(k)*gamma(z + 1)) + + +def test_compound_pspace(): + X = Normal('X', 2, 4) + Y = Normal('Y', 3, 6) + assert not isinstance(Y.pspace, CompoundPSpace) + N = NormalDistribution(1, 2) + D = PoissonDistribution(3) + B = BernoulliDistribution(0.2, 1, 0) + pspace1 = CompoundPSpace('N', N) + pspace2 = CompoundPSpace('D', D) + pspace3 = CompoundPSpace('B', B) + assert not isinstance(pspace1, CompoundPSpace) + assert not isinstance(pspace2, CompoundPSpace) + assert not isinstance(pspace3, CompoundPSpace) + M = MultivariateNormalDistribution([1, 2], [[2, 1], [1, 2]]) + raises(ValueError, lambda: CompoundPSpace('M', M)) + Y = Normal('Y', X, 6) + assert isinstance(Y.pspace, CompoundPSpace) + assert Y.pspace.distribution == CompoundDistribution(NormalDistribution(X, 6)) + assert Y.pspace.domain.set == Interval(-oo, oo) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_continuous_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_continuous_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..b2c4206b5c29ffd3194d1ae05e57c51c9c1b6d78 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_continuous_rv.py @@ -0,0 +1,1583 @@ +from sympy.concrete.summations import Sum +from sympy.core.function import (Lambda, diff, expand_func) +from sympy.core.mul import Mul +from sympy.core import EulerGamma +from sympy.core.numbers import (E as e, I, Rational, pi) +from sympy.core.relational import (Eq, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.combinatorial.factorials import (binomial, factorial) +from sympy.functions.elementary.complexes import (Abs, im, re, sign) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (cosh, sinh) +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (asin, atan, cos, sin, tan) +from sympy.functions.special.bessel import (besseli, besselj, besselk) +from sympy.functions.special.beta_functions import beta +from sympy.functions.special.error_functions import (erf, erfc, erfi, expint) +from sympy.functions.special.gamma_functions import (gamma, lowergamma, uppergamma) +from sympy.functions.special.zeta_functions import zeta +from sympy.functions.special.hyper import hyper +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import (And, Or) +from sympy.sets.sets import Interval +from sympy.simplify.simplify import simplify +from sympy.utilities.lambdify import lambdify +from sympy.functions.special.error_functions import erfinv +from sympy.functions.special.hyper import meijerg +from sympy.sets.sets import FiniteSet, Complement, Intersection +from sympy.stats import (P, E, where, density, variance, covariance, skewness, kurtosis, median, + given, pspace, cdf, characteristic_function, moment_generating_function, + ContinuousRV, Arcsin, Benini, Beta, BetaNoncentral, BetaPrime, + Cauchy, Chi, ChiSquared, ChiNoncentral, Dagum, Davis, Erlang, ExGaussian, + Exponential, ExponentialPower, FDistribution, FisherZ, Frechet, Gamma, + GammaInverse, Gompertz, Gumbel, Kumaraswamy, Laplace, Levy, Logistic, LogCauchy, + LogLogistic, LogitNormal, LogNormal, Maxwell, Moyal, Nakagami, Normal, GaussianInverse, + Pareto, PowerFunction, QuadraticU, RaisedCosine, Rayleigh, Reciprocal, ShiftedGompertz, StudentT, + Trapezoidal, Triangular, Uniform, UniformSum, VonMises, Weibull, coskewness, + WignerSemicircle, Wald, correlation, moment, cmoment, smoment, quantile, + Lomax, BoundedPareto) + +from sympy.stats.crv_types import NormalDistribution, ExponentialDistribution, ContinuousDistributionHandmade +from sympy.stats.joint_rv_types import MultivariateLaplaceDistribution, MultivariateNormalDistribution +from sympy.stats.crv import SingleContinuousPSpace, SingleContinuousDomain +from sympy.stats.compound_rv import CompoundPSpace +from sympy.stats.symbolic_probability import Probability +from sympy.testing.pytest import raises, XFAIL, slow, ignore_warnings +from sympy.core.random import verify_numerically as tn + +oo = S.Infinity + +x, y, z = map(Symbol, 'xyz') + +def test_single_normal(): + mu = Symbol('mu', real=True) + sigma = Symbol('sigma', positive=True) + X = Normal('x', 0, 1) + Y = X*sigma + mu + + assert E(Y) == mu + assert variance(Y) == sigma**2 + pdf = density(Y) + x = Symbol('x', real=True) + assert (pdf(x) == + 2**S.Half*exp(-(x - mu)**2/(2*sigma**2))/(2*pi**S.Half*sigma)) + + assert P(X**2 < 1) == erf(2**S.Half/2) + ans = quantile(Y)(x) + assert ans == Complement(Intersection(FiniteSet( + sqrt(2)*sigma*(sqrt(2)*mu/(2*sigma)+ erfinv(2*x - 1))), + Interval(-oo, oo)), FiniteSet(mu)) + assert E(X, Eq(X, mu)) == mu + + assert median(X) == FiniteSet(0) + # issue 8248 + assert X.pspace.compute_expectation(1).doit() == 1 + + +def test_conditional_1d(): + X = Normal('x', 0, 1) + Y = given(X, X >= 0) + z = Symbol('z') + + assert density(Y)(z) == 2 * density(X)(z) + + assert Y.pspace.domain.set == Interval(0, oo) + assert E(Y) == sqrt(2) / sqrt(pi) + + assert E(X**2) == E(Y**2) + + +def test_ContinuousDomain(): + X = Normal('x', 0, 1) + assert where(X**2 <= 1).set == Interval(-1, 1) + assert where(X**2 <= 1).symbol == X.symbol + assert where(And(X**2 <= 1, X >= 0)).set == Interval(0, 1) + raises(ValueError, lambda: where(sin(X) > 1)) + + Y = given(X, X >= 0) + + assert Y.pspace.domain.set == Interval(0, oo) + + +def test_multiple_normal(): + X, Y = Normal('x', 0, 1), Normal('y', 0, 1) + p = Symbol("p", positive=True) + + assert E(X + Y) == 0 + assert variance(X + Y) == 2 + assert variance(X + X) == 4 + assert covariance(X, Y) == 0 + assert covariance(2*X + Y, -X) == -2*variance(X) + assert skewness(X) == 0 + assert skewness(X + Y) == 0 + assert kurtosis(X) == 3 + assert kurtosis(X+Y) == 3 + assert correlation(X, Y) == 0 + assert correlation(X, X + Y) == correlation(X, X - Y) + assert moment(X, 2) == 1 + assert cmoment(X, 3) == 0 + assert moment(X + Y, 4) == 12 + assert cmoment(X, 2) == variance(X) + assert smoment(X*X, 2) == 1 + assert smoment(X + Y, 3) == skewness(X + Y) + assert smoment(X + Y, 4) == kurtosis(X + Y) + assert E(X, Eq(X + Y, 0)) == 0 + assert variance(X, Eq(X + Y, 0)) == S.Half + assert quantile(X)(p) == sqrt(2)*erfinv(2*p - S.One) + + +def test_symbolic(): + mu1, mu2 = symbols('mu1 mu2', real=True) + s1, s2 = symbols('sigma1 sigma2', positive=True) + rate = Symbol('lambda', positive=True) + X = Normal('x', mu1, s1) + Y = Normal('y', mu2, s2) + Z = Exponential('z', rate) + a, b, c = symbols('a b c', real=True) + + assert E(X) == mu1 + assert E(X + Y) == mu1 + mu2 + assert E(a*X + b) == a*E(X) + b + assert variance(X) == s1**2 + assert variance(X + a*Y + b) == variance(X) + a**2*variance(Y) + + assert E(Z) == 1/rate + assert E(a*Z + b) == a*E(Z) + b + assert E(X + a*Z + b) == mu1 + a/rate + b + assert median(X) == FiniteSet(mu1) + + +def test_cdf(): + X = Normal('x', 0, 1) + + d = cdf(X) + assert P(X < 1) == d(1).rewrite(erfc) + assert d(0) == S.Half + + d = cdf(X, X > 0) # given X>0 + assert d(0) == 0 + + Y = Exponential('y', 10) + d = cdf(Y) + assert d(-5) == 0 + assert P(Y > 3) == 1 - d(3) + + raises(ValueError, lambda: cdf(X + Y)) + + Z = Exponential('z', 1) + f = cdf(Z) + assert f(z) == Piecewise((1 - exp(-z), z >= 0), (0, True)) + + +def test_characteristic_function(): + X = Uniform('x', 0, 1) + + cf = characteristic_function(X) + assert cf(1) == -I*(-1 + exp(I)) + + Y = Normal('y', 1, 1) + cf = characteristic_function(Y) + assert cf(0) == 1 + assert cf(1) == exp(I - S.Half) + + Z = Exponential('z', 5) + cf = characteristic_function(Z) + assert cf(0) == 1 + assert cf(1).expand() == Rational(25, 26) + I*5/26 + + X = GaussianInverse('x', 1, 1) + cf = characteristic_function(X) + assert cf(0) == 1 + assert cf(1) == exp(1 - sqrt(1 - 2*I)) + + X = ExGaussian('x', 0, 1, 1) + cf = characteristic_function(X) + assert cf(0) == 1 + assert cf(1) == (1 + I)*exp(Rational(-1, 2))/2 + + L = Levy('x', 0, 1) + cf = characteristic_function(L) + assert cf(0) == 1 + assert cf(1) == exp(-sqrt(2)*sqrt(-I)) + + +def test_moment_generating_function(): + t = symbols('t', positive=True) + + # Symbolic tests + a, b, c = symbols('a b c') + + mgf = moment_generating_function(Beta('x', a, b))(t) + assert mgf == hyper((a,), (a + b,), t) + + mgf = moment_generating_function(Chi('x', a))(t) + assert mgf == sqrt(2)*t*gamma(a/2 + S.Half)*\ + hyper((a/2 + S.Half,), (Rational(3, 2),), t**2/2)/gamma(a/2) +\ + hyper((a/2,), (S.Half,), t**2/2) + + mgf = moment_generating_function(ChiSquared('x', a))(t) + assert mgf == (1 - 2*t)**(-a/2) + + mgf = moment_generating_function(Erlang('x', a, b))(t) + assert mgf == (1 - t/b)**(-a) + + mgf = moment_generating_function(ExGaussian("x", a, b, c))(t) + assert mgf == exp(a*t + b**2*t**2/2)/(1 - t/c) + + mgf = moment_generating_function(Exponential('x', a))(t) + assert mgf == a/(a - t) + + mgf = moment_generating_function(Gamma('x', a, b))(t) + assert mgf == (-b*t + 1)**(-a) + + mgf = moment_generating_function(Gumbel('x', a, b))(t) + assert mgf == exp(b*t)*gamma(-a*t + 1) + + mgf = moment_generating_function(Gompertz('x', a, b))(t) + assert mgf == b*exp(b)*expint(t/a, b) + + mgf = moment_generating_function(Laplace('x', a, b))(t) + assert mgf == exp(a*t)/(-b**2*t**2 + 1) + + mgf = moment_generating_function(Logistic('x', a, b))(t) + assert mgf == exp(a*t)*beta(-b*t + 1, b*t + 1) + + mgf = moment_generating_function(Normal('x', a, b))(t) + assert mgf == exp(a*t + b**2*t**2/2) + + mgf = moment_generating_function(Pareto('x', a, b))(t) + assert mgf == b*(-a*t)**b*uppergamma(-b, -a*t) + + mgf = moment_generating_function(QuadraticU('x', a, b))(t) + assert str(mgf) == ("(3*(t*(-4*b + (a + b)**2) + 4)*exp(b*t) - " + "3*(t*(a**2 + 2*a*(b - 2) + b**2) + 4)*exp(a*t))/(t**2*(a - b)**3)") + + mgf = moment_generating_function(RaisedCosine('x', a, b))(t) + assert mgf == pi**2*exp(a*t)*sinh(b*t)/(b*t*(b**2*t**2 + pi**2)) + + mgf = moment_generating_function(Rayleigh('x', a))(t) + assert mgf == sqrt(2)*sqrt(pi)*a*t*(erf(sqrt(2)*a*t/2) + 1)\ + *exp(a**2*t**2/2)/2 + 1 + + mgf = moment_generating_function(Triangular('x', a, b, c))(t) + assert str(mgf) == ("(-2*(-a + b)*exp(c*t) + 2*(-a + c)*exp(b*t) + " + "2*(b - c)*exp(a*t))/(t**2*(-a + b)*(-a + c)*(b - c))") + + mgf = moment_generating_function(Uniform('x', a, b))(t) + assert mgf == (-exp(a*t) + exp(b*t))/(t*(-a + b)) + + mgf = moment_generating_function(UniformSum('x', a))(t) + assert mgf == ((exp(t) - 1)/t)**a + + mgf = moment_generating_function(WignerSemicircle('x', a))(t) + assert mgf == 2*besseli(1, a*t)/(a*t) + + # Numeric tests + + mgf = moment_generating_function(Beta('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 1) == hyper((2,), (3,), 1)/2 + + mgf = moment_generating_function(Chi('x', 1))(t) + assert mgf.diff(t).subs(t, 1) == sqrt(2)*hyper((1,), (Rational(3, 2),), S.Half + )/sqrt(pi) + hyper((Rational(3, 2),), (Rational(3, 2),), S.Half) + 2*sqrt(2)*hyper((2,), + (Rational(5, 2),), S.Half)/(3*sqrt(pi)) + + mgf = moment_generating_function(ChiSquared('x', 1))(t) + assert mgf.diff(t).subs(t, 1) == I + + mgf = moment_generating_function(Erlang('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 0) == 1 + + mgf = moment_generating_function(ExGaussian("x", 0, 1, 1))(t) + assert mgf.diff(t).subs(t, 2) == -exp(2) + + mgf = moment_generating_function(Exponential('x', 1))(t) + assert mgf.diff(t).subs(t, 0) == 1 + + mgf = moment_generating_function(Gamma('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 0) == 1 + + mgf = moment_generating_function(Gumbel('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 0) == EulerGamma + 1 + + mgf = moment_generating_function(Gompertz('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 1) == -e*meijerg(((), (1, 1)), + ((0, 0, 0), ()), 1) + + mgf = moment_generating_function(Laplace('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 0) == 1 + + mgf = moment_generating_function(Logistic('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 0) == beta(1, 1) + + mgf = moment_generating_function(Normal('x', 0, 1))(t) + assert mgf.diff(t).subs(t, 1) == exp(S.Half) + + mgf = moment_generating_function(Pareto('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 0) == expint(1, 0) + + mgf = moment_generating_function(QuadraticU('x', 1, 2))(t) + assert mgf.diff(t).subs(t, 1) == -12*e - 3*exp(2) + + mgf = moment_generating_function(RaisedCosine('x', 1, 1))(t) + assert mgf.diff(t).subs(t, 1) == -2*e*pi**2*sinh(1)/\ + (1 + pi**2)**2 + e*pi**2*cosh(1)/(1 + pi**2) + + mgf = moment_generating_function(Rayleigh('x', 1))(t) + assert mgf.diff(t).subs(t, 0) == sqrt(2)*sqrt(pi)/2 + + mgf = moment_generating_function(Triangular('x', 1, 3, 2))(t) + assert mgf.diff(t).subs(t, 1) == -e + exp(3) + + mgf = moment_generating_function(Uniform('x', 0, 1))(t) + assert mgf.diff(t).subs(t, 1) == 1 + + mgf = moment_generating_function(UniformSum('x', 1))(t) + assert mgf.diff(t).subs(t, 1) == 1 + + mgf = moment_generating_function(WignerSemicircle('x', 1))(t) + assert mgf.diff(t).subs(t, 1) == -2*besseli(1, 1) + besseli(2, 1) +\ + besseli(0, 1) + + +def test_ContinuousRV(): + pdf = sqrt(2)*exp(-x**2/2)/(2*sqrt(pi)) # Normal distribution + # X and Y should be equivalent + X = ContinuousRV(x, pdf, check=True) + Y = Normal('y', 0, 1) + + assert variance(X) == variance(Y) + assert P(X > 0) == P(Y > 0) + Z = ContinuousRV(z, exp(-z), set=Interval(0, oo)) + assert Z.pspace.domain.set == Interval(0, oo) + assert E(Z) == 1 + assert P(Z > 5) == exp(-5) + raises(ValueError, lambda: ContinuousRV(z, exp(-z), set=Interval(0, 10), check=True)) + + # the correct pdf for Gamma(k, theta) but the integral in `check` + # integrates to something equivalent to 1 and not to 1 exactly + _x, k, theta = symbols("x k theta", positive=True) + pdf = 1/(gamma(k)*theta**k)*_x**(k-1)*exp(-_x/theta) + X = ContinuousRV(_x, pdf, set=Interval(0, oo)) + Y = Gamma('y', k, theta) + assert (E(X) - E(Y)).simplify() == 0 + assert (variance(X) - variance(Y)).simplify() == 0 + + +def test_arcsin(): + + a = Symbol("a", real=True) + b = Symbol("b", real=True) + + X = Arcsin('x', a, b) + assert density(X)(x) == 1/(pi*sqrt((-x + b)*(x - a))) + assert cdf(X)(x) == Piecewise((0, a > x), + (2*asin(sqrt((-a + x)/(-a + b)))/pi, b >= x), + (1, True)) + assert pspace(X).domain.set == Interval(a, b) + +def test_benini(): + alpha = Symbol("alpha", positive=True) + beta = Symbol("beta", positive=True) + sigma = Symbol("sigma", positive=True) + X = Benini('x', alpha, beta, sigma) + + assert density(X)(x) == ((alpha/x + 2*beta*log(x/sigma)/x) + *exp(-alpha*log(x/sigma) - beta*log(x/sigma)**2)) + + assert pspace(X).domain.set == Interval(sigma, oo) + raises(NotImplementedError, lambda: moment_generating_function(X)) + alpha = Symbol("alpha", nonpositive=True) + raises(ValueError, lambda: Benini('x', alpha, beta, sigma)) + + beta = Symbol("beta", nonpositive=True) + raises(ValueError, lambda: Benini('x', alpha, beta, sigma)) + + alpha = Symbol("alpha", positive=True) + raises(ValueError, lambda: Benini('x', alpha, beta, sigma)) + + beta = Symbol("beta", positive=True) + sigma = Symbol("sigma", nonpositive=True) + raises(ValueError, lambda: Benini('x', alpha, beta, sigma)) + +def test_beta(): + a, b = symbols('alpha beta', positive=True) + B = Beta('x', a, b) + + assert pspace(B).domain.set == Interval(0, 1) + assert characteristic_function(B)(x) == hyper((a,), (a + b,), I*x) + assert density(B)(x) == x**(a - 1)*(1 - x)**(b - 1)/beta(a, b) + + assert simplify(E(B)) == a / (a + b) + assert simplify(variance(B)) == a*b / (a**3 + 3*a**2*b + a**2 + 3*a*b**2 + 2*a*b + b**3 + b**2) + + # Full symbolic solution is too much, test with numeric version + a, b = 1, 2 + B = Beta('x', a, b) + assert expand_func(E(B)) == a / S(a + b) + assert expand_func(variance(B)) == (a*b) / S((a + b)**2 * (a + b + 1)) + assert median(B) == FiniteSet(1 - 1/sqrt(2)) + +def test_beta_noncentral(): + a, b = symbols('a b', positive=True) + c = Symbol('c', nonnegative=True) + _k = Dummy('k') + + X = BetaNoncentral('x', a, b, c) + + assert pspace(X).domain.set == Interval(0, 1) + + dens = density(X) + z = Symbol('z') + + res = Sum( z**(_k + a - 1)*(c/2)**_k*(1 - z)**(b - 1)*exp(-c/2)/ + (beta(_k + a, b)*factorial(_k)), (_k, 0, oo)) + assert dens(z).dummy_eq(res) + + # BetaCentral should not raise if the assumptions + # on the symbols can not be determined + a, b, c = symbols('a b c') + assert BetaNoncentral('x', a, b, c) + + a = Symbol('a', positive=False, real=True) + raises(ValueError, lambda: BetaNoncentral('x', a, b, c)) + + a = Symbol('a', positive=True) + b = Symbol('b', positive=False, real=True) + raises(ValueError, lambda: BetaNoncentral('x', a, b, c)) + + a = Symbol('a', positive=True) + b = Symbol('b', positive=True) + c = Symbol('c', nonnegative=False, real=True) + raises(ValueError, lambda: BetaNoncentral('x', a, b, c)) + +def test_betaprime(): + alpha = Symbol("alpha", positive=True) + + betap = Symbol("beta", positive=True) + + X = BetaPrime('x', alpha, betap) + assert density(X)(x) == x**(alpha - 1)*(x + 1)**(-alpha - betap)/beta(alpha, betap) + + alpha = Symbol("alpha", nonpositive=True) + raises(ValueError, lambda: BetaPrime('x', alpha, betap)) + + alpha = Symbol("alpha", positive=True) + betap = Symbol("beta", nonpositive=True) + raises(ValueError, lambda: BetaPrime('x', alpha, betap)) + X = BetaPrime('x', 1, 1) + assert median(X) == FiniteSet(1) + + +def test_BoundedPareto(): + L, H = symbols('L, H', negative=True) + raises(ValueError, lambda: BoundedPareto('X', 1, L, H)) + L, H = symbols('L, H', real=False) + raises(ValueError, lambda: BoundedPareto('X', 1, L, H)) + L, H = symbols('L, H', positive=True) + raises(ValueError, lambda: BoundedPareto('X', -1, L, H)) + + X = BoundedPareto('X', 2, L, H) + assert X.pspace.domain.set == Interval(L, H) + assert density(X)(x) == 2*L**2/(x**3*(1 - L**2/H**2)) + assert cdf(X)(x) == Piecewise((-H**2*L**2/(x**2*(H**2 - L**2)) \ + + H**2/(H**2 - L**2), L <= x), (0, True)) + assert E(X).simplify() == 2*H*L/(H + L) + X = BoundedPareto('X', 1, 2, 4) + assert E(X).simplify() == log(16) + assert median(X) == FiniteSet(Rational(8, 3)) + assert variance(X).simplify() == 8 - 16*log(2)**2 + + +def test_cauchy(): + x0 = Symbol("x0", real=True) + gamma = Symbol("gamma", positive=True) + p = Symbol("p", positive=True) + + X = Cauchy('x', x0, gamma) + # Tests the characteristic function + assert characteristic_function(X)(x) == exp(-gamma*Abs(x) + I*x*x0) + raises(NotImplementedError, lambda: moment_generating_function(X)) + assert density(X)(x) == 1/(pi*gamma*(1 + (x - x0)**2/gamma**2)) + assert diff(cdf(X)(x), x) == density(X)(x) + assert quantile(X)(p) == gamma*tan(pi*(p - S.Half)) + x0 + + x1 = Symbol("x1", real=False) + raises(ValueError, lambda: Cauchy('x', x1, gamma)) + gamma = Symbol("gamma", nonpositive=True) + raises(ValueError, lambda: Cauchy('x', x0, gamma)) + assert median(X) == FiniteSet(x0) + +def test_chi(): + from sympy.core.numbers import I + k = Symbol("k", integer=True) + + X = Chi('x', k) + assert density(X)(x) == 2**(-k/2 + 1)*x**(k - 1)*exp(-x**2/2)/gamma(k/2) + + # Tests the characteristic function + assert characteristic_function(X)(x) == sqrt(2)*I*x*gamma(k/2 + S(1)/2)*hyper((k/2 + S(1)/2,), + (S(3)/2,), -x**2/2)/gamma(k/2) + hyper((k/2,), (S(1)/2,), -x**2/2) + + # Tests the moment generating function + assert moment_generating_function(X)(x) == sqrt(2)*x*gamma(k/2 + S(1)/2)*hyper((k/2 + S(1)/2,), + (S(3)/2,), x**2/2)/gamma(k/2) + hyper((k/2,), (S(1)/2,), x**2/2) + + k = Symbol("k", integer=True, positive=False) + raises(ValueError, lambda: Chi('x', k)) + + k = Symbol("k", integer=False, positive=True) + raises(ValueError, lambda: Chi('x', k)) + +def test_chi_noncentral(): + k = Symbol("k", integer=True) + l = Symbol("l") + + X = ChiNoncentral("x", k, l) + assert density(X)(x) == (x**k*l*(x*l)**(-k/2)* + exp(-x**2/2 - l**2/2)*besseli(k/2 - 1, x*l)) + + k = Symbol("k", integer=True, positive=False) + raises(ValueError, lambda: ChiNoncentral('x', k, l)) + + k = Symbol("k", integer=True, positive=True) + l = Symbol("l", nonpositive=True) + raises(ValueError, lambda: ChiNoncentral('x', k, l)) + + k = Symbol("k", integer=False) + l = Symbol("l", positive=True) + raises(ValueError, lambda: ChiNoncentral('x', k, l)) + + +def test_chi_squared(): + k = Symbol("k", integer=True) + X = ChiSquared('x', k) + + # Tests the characteristic function + assert characteristic_function(X)(x) == ((-2*I*x + 1)**(-k/2)) + + assert density(X)(x) == 2**(-k/2)*x**(k/2 - 1)*exp(-x/2)/gamma(k/2) + assert cdf(X)(x) == Piecewise((lowergamma(k/2, x/2)/gamma(k/2), x >= 0), (0, True)) + assert E(X) == k + assert variance(X) == 2*k + + X = ChiSquared('x', 15) + assert cdf(X)(3) == -14873*sqrt(6)*exp(Rational(-3, 2))/(5005*sqrt(pi)) + erf(sqrt(6)/2) + + k = Symbol("k", integer=True, positive=False) + raises(ValueError, lambda: ChiSquared('x', k)) + + k = Symbol("k", integer=False, positive=True) + raises(ValueError, lambda: ChiSquared('x', k)) + + +def test_dagum(): + p = Symbol("p", positive=True) + b = Symbol("b", positive=True) + a = Symbol("a", positive=True) + + X = Dagum('x', p, a, b) + assert density(X)(x) == a*p*(x/b)**(a*p)*((x/b)**a + 1)**(-p - 1)/x + assert cdf(X)(x) == Piecewise(((1 + (x/b)**(-a))**(-p), x >= 0), + (0, True)) + + p = Symbol("p", nonpositive=True) + raises(ValueError, lambda: Dagum('x', p, a, b)) + + p = Symbol("p", positive=True) + b = Symbol("b", nonpositive=True) + raises(ValueError, lambda: Dagum('x', p, a, b)) + + b = Symbol("b", positive=True) + a = Symbol("a", nonpositive=True) + raises(ValueError, lambda: Dagum('x', p, a, b)) + X = Dagum('x', 1, 1, 1) + assert median(X) == FiniteSet(1) + +def test_davis(): + b = Symbol("b", positive=True) + n = Symbol("n", positive=True) + mu = Symbol("mu", positive=True) + + X = Davis('x', b, n, mu) + dividend = b**n*(x - mu)**(-1-n) + divisor = (exp(b/(x-mu))-1)*(gamma(n)*zeta(n)) + assert density(X)(x) == dividend/divisor + + +def test_erlang(): + k = Symbol("k", integer=True, positive=True) + l = Symbol("l", positive=True) + + X = Erlang("x", k, l) + assert density(X)(x) == x**(k - 1)*l**k*exp(-x*l)/gamma(k) + assert cdf(X)(x) == Piecewise((lowergamma(k, l*x)/gamma(k), x > 0), + (0, True)) + + +def test_exgaussian(): + m, z = symbols("m, z") + s, l = symbols("s, l", positive=True) + X = ExGaussian("x", m, s, l) + + assert density(X)(z) == l*exp(l*(l*s**2 + 2*m - 2*z)/2) *\ + erfc(sqrt(2)*(l*s**2 + m - z)/(2*s))/2 + + # Note: actual_output simplifies to expected_output. + # Ideally cdf(X)(z) would return expected_output + # expected_output = (erf(sqrt(2)*(l*s**2 + m - z)/(2*s)) - 1)*exp(l*(l*s**2 + 2*m - 2*z)/2)/2 - erf(sqrt(2)*(m - z)/(2*s))/2 + S.Half + u = l*(z - m) + v = l*s + GaussianCDF1 = cdf(Normal('x', 0, v))(u) + GaussianCDF2 = cdf(Normal('x', v**2, v))(u) + actual_output = GaussianCDF1 - exp(-u + (v**2/2) + log(GaussianCDF2)) + assert cdf(X)(z) == actual_output + # assert simplify(actual_output) == expected_output + + assert variance(X).expand() == s**2 + l**(-2) + + assert skewness(X).expand() == 2/(l**3*s**2*sqrt(s**2 + l**(-2)) + l * + sqrt(s**2 + l**(-2))) + + +@slow +def test_exponential(): + rate = Symbol('lambda', positive=True) + X = Exponential('x', rate) + p = Symbol("p", positive=True, real=True) + + assert E(X) == 1/rate + assert variance(X) == 1/rate**2 + assert skewness(X) == 2 + assert skewness(X) == smoment(X, 3) + assert kurtosis(X) == 9 + assert kurtosis(X) == smoment(X, 4) + assert smoment(2*X, 4) == smoment(X, 4) + assert moment(X, 3) == 3*2*1/rate**3 + assert P(X > 0) is S.One + assert P(X > 1) == exp(-rate) + assert P(X > 10) == exp(-10*rate) + assert quantile(X)(p) == -log(1-p)/rate + + assert where(X <= 1).set == Interval(0, 1) + Y = Exponential('y', 1) + assert median(Y) == FiniteSet(log(2)) + #Test issue 9970 + z = Dummy('z') + assert P(X > z) == exp(-z*rate) + assert P(X < z) == 0 + #Test issue 10076 (Distribution with interval(0,oo)) + x = Symbol('x') + _z = Dummy('_z') + b = SingleContinuousPSpace(x, ExponentialDistribution(2)) + + with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed + expected1 = Integral(2*exp(-2*_z), (_z, 3, oo)) + assert b.probability(x > 3, evaluate=False).rewrite(Integral).dummy_eq(expected1) + + expected2 = Integral(2*exp(-2*_z), (_z, 0, 4)) + assert b.probability(x < 4, evaluate=False).rewrite(Integral).dummy_eq(expected2) + Y = Exponential('y', 2*rate) + assert coskewness(X, X, X) == skewness(X) + assert coskewness(X, Y + rate*X, Y + 2*rate*X) == \ + 4/(sqrt(1 + 1/(4*rate**2))*sqrt(4 + 1/(4*rate**2))) + assert coskewness(X + 2*Y, Y + X, Y + 2*X, X > 3) == \ + sqrt(170)*Rational(9, 85) + +def test_exponential_power(): + mu = Symbol('mu') + z = Symbol('z') + alpha = Symbol('alpha', positive=True) + beta = Symbol('beta', positive=True) + + X = ExponentialPower('x', mu, alpha, beta) + + assert density(X)(z) == beta*exp(-(Abs(mu - z)/alpha) + ** beta)/(2*alpha*gamma(1/beta)) + assert cdf(X)(z) == S.Half + lowergamma(1/beta, + (Abs(mu - z)/alpha)**beta)*sign(-mu + z)/\ + (2*gamma(1/beta)) + + +def test_f_distribution(): + d1 = Symbol("d1", positive=True) + d2 = Symbol("d2", positive=True) + + X = FDistribution("x", d1, d2) + + assert density(X)(x) == (d2**(d2/2)*sqrt((d1*x)**d1*(d1*x + d2)**(-d1 - d2)) + /(x*beta(d1/2, d2/2))) + + raises(NotImplementedError, lambda: moment_generating_function(X)) + d1 = Symbol("d1", nonpositive=True) + raises(ValueError, lambda: FDistribution('x', d1, d1)) + + d1 = Symbol("d1", positive=True, integer=False) + raises(ValueError, lambda: FDistribution('x', d1, d1)) + + d1 = Symbol("d1", positive=True) + d2 = Symbol("d2", nonpositive=True) + raises(ValueError, lambda: FDistribution('x', d1, d2)) + + d2 = Symbol("d2", positive=True, integer=False) + raises(ValueError, lambda: FDistribution('x', d1, d2)) + + +def test_fisher_z(): + d1 = Symbol("d1", positive=True) + d2 = Symbol("d2", positive=True) + + X = FisherZ("x", d1, d2) + assert density(X)(x) == (2*d1**(d1/2)*d2**(d2/2)*(d1*exp(2*x) + d2) + **(-d1/2 - d2/2)*exp(d1*x)/beta(d1/2, d2/2)) + +def test_frechet(): + a = Symbol("a", positive=True) + s = Symbol("s", positive=True) + m = Symbol("m", real=True) + + X = Frechet("x", a, s=s, m=m) + assert density(X)(x) == a*((x - m)/s)**(-a - 1)*exp(-((x - m)/s)**(-a))/s + assert cdf(X)(x) == Piecewise((exp(-((-m + x)/s)**(-a)), m <= x), (0, True)) + +@slow +def test_gamma(): + k = Symbol("k", positive=True) + theta = Symbol("theta", positive=True) + + X = Gamma('x', k, theta) + + # Tests characteristic function + assert characteristic_function(X)(x) == ((-I*theta*x + 1)**(-k)) + + assert density(X)(x) == x**(k - 1)*theta**(-k)*exp(-x/theta)/gamma(k) + assert cdf(X, meijerg=True)(z) == Piecewise( + (-k*lowergamma(k, 0)/gamma(k + 1) + + k*lowergamma(k, z/theta)/gamma(k + 1), z >= 0), + (0, True)) + + # assert simplify(variance(X)) == k*theta**2 # handled numerically below + assert E(X) == moment(X, 1) + + k, theta = symbols('k theta', positive=True) + X = Gamma('x', k, theta) + assert E(X) == k*theta + assert variance(X) == k*theta**2 + assert skewness(X).expand() == 2/sqrt(k) + assert kurtosis(X).expand() == 3 + 6/k + + Y = Gamma('y', 2*k, 3*theta) + assert coskewness(X, theta*X + Y, k*X + Y).simplify() == \ + 2*531441**(-k)*sqrt(k)*theta*(3*3**(12*k) - 2*531441**k) \ + /(sqrt(k**2 + 18)*sqrt(theta**2 + 18)) + +def test_gamma_inverse(): + a = Symbol("a", positive=True) + b = Symbol("b", positive=True) + X = GammaInverse("x", a, b) + assert density(X)(x) == x**(-a - 1)*b**a*exp(-b/x)/gamma(a) + assert cdf(X)(x) == Piecewise((uppergamma(a, b/x)/gamma(a), x > 0), (0, True)) + assert characteristic_function(X)(x) == 2 * (-I*b*x)**(a/2) \ + * besselk(a, 2*sqrt(b)*sqrt(-I*x))/gamma(a) + raises(NotImplementedError, lambda: moment_generating_function(X)) + +def test_gompertz(): + b = Symbol("b", positive=True) + eta = Symbol("eta", positive=True) + + X = Gompertz("x", b, eta) + + assert density(X)(x) == b*eta*exp(eta)*exp(b*x)*exp(-eta*exp(b*x)) + assert cdf(X)(x) == 1 - exp(eta)*exp(-eta*exp(b*x)) + assert diff(cdf(X)(x), x) == density(X)(x) + + +def test_gumbel(): + beta = Symbol("beta", positive=True) + mu = Symbol("mu") + x = Symbol("x") + y = Symbol("y") + X = Gumbel("x", beta, mu) + Y = Gumbel("y", beta, mu, minimum=True) + assert density(X)(x).expand() == \ + exp(mu/beta)*exp(-x/beta)*exp(-exp(mu/beta)*exp(-x/beta))/beta + assert density(Y)(y).expand() == \ + exp(-mu/beta)*exp(y/beta)*exp(-exp(-mu/beta)*exp(y/beta))/beta + assert cdf(X)(x).expand() == \ + exp(-exp(mu/beta)*exp(-x/beta)) + assert characteristic_function(X)(x) == exp(I*mu*x)*gamma(-I*beta*x + 1) + +def test_kumaraswamy(): + a = Symbol("a", positive=True) + b = Symbol("b", positive=True) + + X = Kumaraswamy("x", a, b) + assert density(X)(x) == x**(a - 1)*a*b*(-x**a + 1)**(b - 1) + assert cdf(X)(x) == Piecewise((0, x < 0), + (-(-x**a + 1)**b + 1, x <= 1), + (1, True)) + + +def test_laplace(): + mu = Symbol("mu") + b = Symbol("b", positive=True) + + X = Laplace('x', mu, b) + + #Tests characteristic_function + assert characteristic_function(X)(x) == (exp(I*mu*x)/(b**2*x**2 + 1)) + + assert density(X)(x) == exp(-Abs(x - mu)/b)/(2*b) + assert cdf(X)(x) == Piecewise((exp((-mu + x)/b)/2, mu > x), + (-exp((mu - x)/b)/2 + 1, True)) + X = Laplace('x', [1, 2], [[1, 0], [0, 1]]) + assert isinstance(pspace(X).distribution, MultivariateLaplaceDistribution) + +def test_levy(): + mu = Symbol("mu", real=True) + c = Symbol("c", positive=True) + + X = Levy('x', mu, c) + assert X.pspace.domain.set == Interval(mu, oo) + assert density(X)(x) == sqrt(c/(2*pi))*exp(-c/(2*(x - mu)))/((x - mu)**(S.One + S.Half)) + assert cdf(X)(x) == erfc(sqrt(c/(2*(x - mu)))) + + raises(NotImplementedError, lambda: moment_generating_function(X)) + mu = Symbol("mu", real=False) + raises(ValueError, lambda: Levy('x',mu,c)) + + c = Symbol("c", nonpositive=True) + raises(ValueError, lambda: Levy('x',mu,c)) + + mu = Symbol("mu", real=True) + raises(ValueError, lambda: Levy('x',mu,c)) + +def test_logcauchy(): + mu = Symbol("mu", positive=True) + sigma = Symbol("sigma", positive=True) + + X = LogCauchy("x", mu, sigma) + + assert density(X)(x) == sigma/(x*pi*(sigma**2 + (-mu + log(x))**2)) + assert cdf(X)(x) == atan((log(x) - mu)/sigma)/pi + S.Half + + +def test_logistic(): + mu = Symbol("mu", real=True) + s = Symbol("s", positive=True) + p = Symbol("p", positive=True) + + X = Logistic('x', mu, s) + + #Tests characteristics_function + assert characteristic_function(X)(x) == \ + (Piecewise((pi*s*x*exp(I*mu*x)/sinh(pi*s*x), Ne(x, 0)), (1, True))) + + assert density(X)(x) == exp((-x + mu)/s)/(s*(exp((-x + mu)/s) + 1)**2) + assert cdf(X)(x) == 1/(exp((mu - x)/s) + 1) + assert quantile(X)(p) == mu - s*log(-S.One + 1/p) + +def test_loglogistic(): + a, b = symbols('a b') + assert LogLogistic('x', a, b) + + a = Symbol('a', negative=True) + b = Symbol('b', positive=True) + raises(ValueError, lambda: LogLogistic('x', a, b)) + + a = Symbol('a', positive=True) + b = Symbol('b', negative=True) + raises(ValueError, lambda: LogLogistic('x', a, b)) + + a, b, z, p = symbols('a b z p', positive=True) + X = LogLogistic('x', a, b) + assert density(X)(z) == b*(z/a)**(b - 1)/(a*((z/a)**b + 1)**2) + assert cdf(X)(z) == 1/(1 + (z/a)**(-b)) + assert quantile(X)(p) == a*(p/(1 - p))**(1/b) + + # Expectation + assert E(X) == Piecewise((S.NaN, b <= 1), (pi*a/(b*sin(pi/b)), True)) + b = symbols('b', prime=True) # b > 1 + X = LogLogistic('x', a, b) + assert E(X) == pi*a/(b*sin(pi/b)) + X = LogLogistic('x', 1, 2) + assert median(X) == FiniteSet(1) + +def test_logitnormal(): + mu = Symbol('mu', real=True) + s = Symbol('s', positive=True) + X = LogitNormal('x', mu, s) + x = Symbol('x') + + assert density(X)(x) == sqrt(2)*exp(-(-mu + log(x/(1 - x)))**2/(2*s**2))/(2*sqrt(pi)*s*x*(1 - x)) + assert cdf(X)(x) == erf(sqrt(2)*(-mu + log(x/(1 - x)))/(2*s))/2 + S(1)/2 + +def test_lognormal(): + mean = Symbol('mu', real=True) + std = Symbol('sigma', positive=True) + X = LogNormal('x', mean, std) + # The sympy integrator can't do this too well + #assert E(X) == exp(mean+std**2/2) + #assert variance(X) == (exp(std**2)-1) * exp(2*mean + std**2) + + # The sympy integrator can't do this too well + #assert E(X) == + raises(NotImplementedError, lambda: moment_generating_function(X)) + mu = Symbol("mu", real=True) + sigma = Symbol("sigma", positive=True) + + X = LogNormal('x', mu, sigma) + assert density(X)(x) == (sqrt(2)*exp(-(-mu + log(x))**2 + /(2*sigma**2))/(2*x*sqrt(pi)*sigma)) + # Tests cdf + assert cdf(X)(x) == Piecewise( + (erf(sqrt(2)*(-mu + log(x))/(2*sigma))/2 + + S(1)/2, x > 0), (0, True)) + + X = LogNormal('x', 0, 1) # Mean 0, standard deviation 1 + assert density(X)(x) == sqrt(2)*exp(-log(x)**2/2)/(2*x*sqrt(pi)) + + +def test_Lomax(): + a, l = symbols('a, l', negative=True) + raises(ValueError, lambda: Lomax('X', a, l)) + a, l = symbols('a, l', real=False) + raises(ValueError, lambda: Lomax('X', a, l)) + + a, l = symbols('a, l', positive=True) + X = Lomax('X', a, l) + assert X.pspace.domain.set == Interval(0, oo) + assert density(X)(x) == a*(1 + x/l)**(-a - 1)/l + assert cdf(X)(x) == Piecewise((1 - (1 + x/l)**(-a), x >= 0), (0, True)) + a = 3 + X = Lomax('X', a, l) + assert E(X) == l/2 + assert median(X) == FiniteSet(l*(-1 + 2**Rational(1, 3))) + assert variance(X) == 3*l**2/4 + + +def test_maxwell(): + a = Symbol("a", positive=True) + + X = Maxwell('x', a) + + assert density(X)(x) == (sqrt(2)*x**2*exp(-x**2/(2*a**2))/ + (sqrt(pi)*a**3)) + assert E(X) == 2*sqrt(2)*a/sqrt(pi) + assert variance(X) == -8*a**2/pi + 3*a**2 + assert cdf(X)(x) == erf(sqrt(2)*x/(2*a)) - sqrt(2)*x*exp(-x**2/(2*a**2))/(sqrt(pi)*a) + assert diff(cdf(X)(x), x) == density(X)(x) + + +@slow +def test_Moyal(): + mu = Symbol('mu',real=False) + sigma = Symbol('sigma', positive=True) + raises(ValueError, lambda: Moyal('M',mu, sigma)) + + mu = Symbol('mu', real=True) + sigma = Symbol('sigma', negative=True) + raises(ValueError, lambda: Moyal('M',mu, sigma)) + + sigma = Symbol('sigma', positive=True) + M = Moyal('M', mu, sigma) + assert density(M)(z) == sqrt(2)*exp(-exp((mu - z)/sigma)/2 + - (-mu + z)/(2*sigma))/(2*sqrt(pi)*sigma) + assert cdf(M)(z).simplify() == 1 - erf(sqrt(2)*exp((mu - z)/(2*sigma))/2) + assert characteristic_function(M)(z) == 2**(-I*sigma*z)*exp(I*mu*z) \ + *gamma(-I*sigma*z + Rational(1, 2))/sqrt(pi) + assert E(M) == mu + EulerGamma*sigma + sigma*log(2) + assert moment_generating_function(M)(z) == 2**(-sigma*z)*exp(mu*z) \ + *gamma(-sigma*z + Rational(1, 2))/sqrt(pi) + + +def test_nakagami(): + mu = Symbol("mu", positive=True) + omega = Symbol("omega", positive=True) + + X = Nakagami('x', mu, omega) + assert density(X)(x) == (2*x**(2*mu - 1)*mu**mu*omega**(-mu) + *exp(-x**2*mu/omega)/gamma(mu)) + assert simplify(E(X)) == (sqrt(mu)*sqrt(omega) + *gamma(mu + S.Half)/gamma(mu + 1)) + assert simplify(variance(X)) == ( + omega - omega*gamma(mu + S.Half)**2/(gamma(mu)*gamma(mu + 1))) + assert cdf(X)(x) == Piecewise( + (lowergamma(mu, mu*x**2/omega)/gamma(mu), x > 0), + (0, True)) + X = Nakagami('x', 1, 1) + assert median(X) == FiniteSet(sqrt(log(2))) + +def test_gaussian_inverse(): + # test for symbolic parameters + a, b = symbols('a b') + assert GaussianInverse('x', a, b) + + # Inverse Gaussian distribution is also known as Wald distribution + # `GaussianInverse` can also be referred by the name `Wald` + a, b, z = symbols('a b z') + X = Wald('x', a, b) + assert density(X)(z) == sqrt(2)*sqrt(b/z**3)*exp(-b*(-a + z)**2/(2*a**2*z))/(2*sqrt(pi)) + + a, b = symbols('a b', positive=True) + z = Symbol('z', positive=True) + + X = GaussianInverse('x', a, b) + assert density(X)(z) == sqrt(2)*sqrt(b)*sqrt(z**(-3))*exp(-b*(-a + z)**2/(2*a**2*z))/(2*sqrt(pi)) + assert E(X) == a + assert variance(X).expand() == a**3/b + assert cdf(X)(z) == (S.Half - erf(sqrt(2)*sqrt(b)*(1 + z/a)/(2*sqrt(z)))/2)*exp(2*b/a) +\ + erf(sqrt(2)*sqrt(b)*(-1 + z/a)/(2*sqrt(z)))/2 + S.Half + + a = symbols('a', nonpositive=True) + raises(ValueError, lambda: GaussianInverse('x', a, b)) + + a = symbols('a', positive=True) + b = symbols('b', nonpositive=True) + raises(ValueError, lambda: GaussianInverse('x', a, b)) + +def test_pareto(): + xm, beta = symbols('xm beta', positive=True) + alpha = beta + 5 + X = Pareto('x', xm, alpha) + + dens = density(X) + + #Tests cdf function + assert cdf(X)(x) == \ + Piecewise((-x**(-beta - 5)*xm**(beta + 5) + 1, x >= xm), (0, True)) + + #Tests characteristic_function + assert characteristic_function(X)(x) == \ + ((-I*x*xm)**(beta + 5)*(beta + 5)*uppergamma(-beta - 5, -I*x*xm)) + + assert dens(x) == x**(-(alpha + 1))*xm**(alpha)*(alpha) + + assert simplify(E(X)) == alpha*xm/(alpha-1) + + # computation of taylor series for MGF still too slow + #assert simplify(variance(X)) == xm**2*alpha / ((alpha-1)**2*(alpha-2)) + + +def test_pareto_numeric(): + xm, beta = 3, 2 + alpha = beta + 5 + X = Pareto('x', xm, alpha) + + assert E(X) == alpha*xm/S(alpha - 1) + assert variance(X) == xm**2*alpha / S((alpha - 1)**2*(alpha - 2)) + assert median(X) == FiniteSet(3*2**Rational(1, 7)) + # Skewness tests too slow. Try shortcutting function? + + +def test_PowerFunction(): + alpha = Symbol("alpha", nonpositive=True) + a, b = symbols('a, b', real=True) + raises (ValueError, lambda: PowerFunction('x', alpha, a, b)) + + a, b = symbols('a, b', real=False) + raises (ValueError, lambda: PowerFunction('x', alpha, a, b)) + + alpha = Symbol("alpha", positive=True) + a, b = symbols('a, b', real=True) + raises (ValueError, lambda: PowerFunction('x', alpha, 5, 2)) + + X = PowerFunction('X', 2, a, b) + assert density(X)(z) == (-2*a + 2*z)/(-a + b)**2 + assert cdf(X)(z) == Piecewise((a**2/(a**2 - 2*a*b + b**2) - + 2*a*z/(a**2 - 2*a*b + b**2) + z**2/(a**2 - 2*a*b + b**2), a <= z), (0, True)) + + X = PowerFunction('X', 2, 0, 1) + assert density(X)(z) == 2*z + assert cdf(X)(z) == Piecewise((z**2, z >= 0), (0,True)) + assert E(X) == Rational(2,3) + assert P(X < 0) == 0 + assert P(X < 1) == 1 + assert median(X) == FiniteSet(1/sqrt(2)) + +def test_raised_cosine(): + mu = Symbol("mu", real=True) + s = Symbol("s", positive=True) + + X = RaisedCosine("x", mu, s) + + assert pspace(X).domain.set == Interval(mu - s, mu + s) + #Tests characteristics_function + assert characteristic_function(X)(x) == \ + Piecewise((exp(-I*pi*mu/s)/2, Eq(x, -pi/s)), (exp(I*pi*mu/s)/2, Eq(x, pi/s)), (pi**2*exp(I*mu*x)*sin(s*x)/(s*x*(-s**2*x**2 + pi**2)), True)) + + assert density(X)(x) == (Piecewise(((cos(pi*(x - mu)/s) + 1)/(2*s), + And(x <= mu + s, mu - s <= x)), (0, True))) + + +def test_rayleigh(): + sigma = Symbol("sigma", positive=True) + + X = Rayleigh('x', sigma) + + #Tests characteristic_function + assert characteristic_function(X)(x) == (-sqrt(2)*sqrt(pi)*sigma*x*(erfi(sqrt(2)*sigma*x/2) - I)*exp(-sigma**2*x**2/2)/2 + 1) + + assert density(X)(x) == x*exp(-x**2/(2*sigma**2))/sigma**2 + assert E(X) == sqrt(2)*sqrt(pi)*sigma/2 + assert variance(X) == -pi*sigma**2/2 + 2*sigma**2 + assert cdf(X)(x) == 1 - exp(-x**2/(2*sigma**2)) + assert diff(cdf(X)(x), x) == density(X)(x) + +def test_reciprocal(): + a = Symbol("a", real=True) + b = Symbol("b", real=True) + + X = Reciprocal('x', a, b) + assert density(X)(x) == 1/(x*(-log(a) + log(b))) + assert cdf(X)(x) == Piecewise((log(a)/(log(a) - log(b)) - log(x)/(log(a) - log(b)), a <= x), (0, True)) + X = Reciprocal('x', 5, 30) + + assert E(X) == 25/(log(30) - log(5)) + assert P(X < 4) == S.Zero + assert P(X < 20) == log(20) / (log(30) - log(5)) - log(5) / (log(30) - log(5)) + assert cdf(X)(10) == log(10) / (log(30) - log(5)) - log(5) / (log(30) - log(5)) + + a = symbols('a', nonpositive=True) + raises(ValueError, lambda: Reciprocal('x', a, b)) + + a = symbols('a', positive=True) + b = symbols('b', positive=True) + raises(ValueError, lambda: Reciprocal('x', a + b, a)) + +def test_shiftedgompertz(): + b = Symbol("b", positive=True) + eta = Symbol("eta", positive=True) + X = ShiftedGompertz("x", b, eta) + assert density(X)(x) == b*(eta*(1 - exp(-b*x)) + 1)*exp(-b*x)*exp(-eta*exp(-b*x)) + + +def test_studentt(): + nu = Symbol("nu", positive=True) + + X = StudentT('x', nu) + assert density(X)(x) == (1 + x**2/nu)**(-nu/2 - S.Half)/(sqrt(nu)*beta(S.Half, nu/2)) + assert cdf(X)(x) == S.Half + x*gamma(nu/2 + S.Half)*hyper((S.Half, nu/2 + S.Half), + (Rational(3, 2),), -x**2/nu)/(sqrt(pi)*sqrt(nu)*gamma(nu/2)) + raises(NotImplementedError, lambda: moment_generating_function(X)) + +def test_trapezoidal(): + a = Symbol("a", real=True) + b = Symbol("b", real=True) + c = Symbol("c", real=True) + d = Symbol("d", real=True) + + X = Trapezoidal('x', a, b, c, d) + assert density(X)(x) == Piecewise(((-2*a + 2*x)/((-a + b)*(-a - b + c + d)), (a <= x) & (x < b)), + (2/(-a - b + c + d), (b <= x) & (x < c)), + ((2*d - 2*x)/((-c + d)*(-a - b + c + d)), (c <= x) & (x <= d)), + (0, True)) + + X = Trapezoidal('x', 0, 1, 2, 3) + assert E(X) == Rational(3, 2) + assert variance(X) == Rational(5, 12) + assert P(X < 2) == Rational(3, 4) + assert median(X) == FiniteSet(Rational(3, 2)) + +def test_triangular(): + a = Symbol("a") + b = Symbol("b") + c = Symbol("c") + + X = Triangular('x', a, b, c) + assert pspace(X).domain.set == Interval(a, b) + assert str(density(X)(x)) == ("Piecewise(((-2*a + 2*x)/((-a + b)*(-a + c)), (a <= x) & (c > x)), " + "(2/(-a + b), Eq(c, x)), ((2*b - 2*x)/((-a + b)*(b - c)), (b >= x) & (c < x)), (0, True))") + + #Tests moment_generating_function + assert moment_generating_function(X)(x).expand() == \ + ((-2*(-a + b)*exp(c*x) + 2*(-a + c)*exp(b*x) + 2*(b - c)*exp(a*x))/(x**2*(-a + b)*(-a + c)*(b - c))).expand() + assert str(characteristic_function(X)(x)) == \ + '(2*(-a + b)*exp(I*c*x) - 2*(-a + c)*exp(I*b*x) - 2*(b - c)*exp(I*a*x))/(x**2*(-a + b)*(-a + c)*(b - c))' + +def test_quadratic_u(): + a = Symbol("a", real=True) + b = Symbol("b", real=True) + + X = QuadraticU("x", a, b) + Y = QuadraticU("x", 1, 2) + + assert pspace(X).domain.set == Interval(a, b) + # Tests _moment_generating_function + assert moment_generating_function(Y)(1) == -15*exp(2) + 27*exp(1) + assert moment_generating_function(Y)(2) == -9*exp(4)/2 + 21*exp(2)/2 + + assert characteristic_function(Y)(1) == 3*I*(-1 + 4*I)*exp(I*exp(2*I)) + assert density(X)(x) == (Piecewise((12*(x - a/2 - b/2)**2/(-a + b)**3, + And(x <= b, a <= x)), (0, True))) + + +def test_uniform(): + l = Symbol('l', real=True) + w = Symbol('w', positive=True) + X = Uniform('x', l, l + w) + + assert E(X) == l + w/2 + assert variance(X).expand() == w**2/12 + + # With numbers all is well + X = Uniform('x', 3, 5) + assert P(X < 3) == 0 and P(X > 5) == 0 + assert P(X < 4) == P(X > 4) == S.Half + assert median(X) == FiniteSet(4) + + z = Symbol('z') + p = density(X)(z) + assert p.subs(z, 3.7) == S.Half + assert p.subs(z, -1) == 0 + assert p.subs(z, 6) == 0 + + c = cdf(X) + assert c(2) == 0 and c(3) == 0 + assert c(Rational(7, 2)) == Rational(1, 4) + assert c(5) == 1 and c(6) == 1 + + +@XFAIL +@slow +def test_uniform_P(): + """ This stopped working because SingleContinuousPSpace.compute_density no + longer calls integrate on a DiracDelta but rather just solves directly. + integrate used to call UniformDistribution.expectation which special-cased + subsed out the Min and Max terms that Uniform produces + + I decided to regress on this class for general cleanliness (and I suspect + speed) of the algorithm. + """ + l = Symbol('l', real=True) + w = Symbol('w', positive=True) + X = Uniform('x', l, l + w) + assert P(X < l) == 0 and P(X > l + w) == 0 + + +def test_uniformsum(): + n = Symbol("n", integer=True) + _k = Dummy("k") + x = Symbol("x") + + X = UniformSum('x', n) + res = Sum((-1)**_k*(-_k + x)**(n - 1)*binomial(n, _k), (_k, 0, floor(x)))/factorial(n - 1) + assert density(X)(x).dummy_eq(res) + + #Tests set functions + assert X.pspace.domain.set == Interval(0, n) + + #Tests the characteristic_function + assert characteristic_function(X)(x) == (-I*(exp(I*x) - 1)/x)**n + + #Tests the moment_generating_function + assert moment_generating_function(X)(x) == ((exp(x) - 1)/x)**n + + +def test_von_mises(): + mu = Symbol("mu") + k = Symbol("k", positive=True) + + X = VonMises("x", mu, k) + assert density(X)(x) == exp(k*cos(x - mu))/(2*pi*besseli(0, k)) + + +def test_weibull(): + a, b = symbols('a b', positive=True) + # FIXME: simplify(E(X)) seems to hang without extended_positive=True + # On a Linux machine this had a rapid memory leak... + # a, b = symbols('a b', positive=True) + X = Weibull('x', a, b) + + assert E(X).expand() == a * gamma(1 + 1/b) + assert variance(X).expand() == (a**2 * gamma(1 + 2/b) - E(X)**2).expand() + assert simplify(skewness(X)) == (2*gamma(1 + 1/b)**3 - 3*gamma(1 + 1/b)*gamma(1 + 2/b) + gamma(1 + 3/b))/(-gamma(1 + 1/b)**2 + gamma(1 + 2/b))**Rational(3, 2) + assert simplify(kurtosis(X)) == (-3*gamma(1 + 1/b)**4 +\ + 6*gamma(1 + 1/b)**2*gamma(1 + 2/b) - 4*gamma(1 + 1/b)*gamma(1 + 3/b) + gamma(1 + 4/b))/(gamma(1 + 1/b)**2 - gamma(1 + 2/b))**2 + +def test_weibull_numeric(): + # Test for integers and rationals + a = 1 + bvals = [S.Half, 1, Rational(3, 2), 5] + for b in bvals: + X = Weibull('x', a, b) + assert simplify(E(X)) == expand_func(a * gamma(1 + 1/S(b))) + assert simplify(variance(X)) == simplify( + a**2 * gamma(1 + 2/S(b)) - E(X)**2) + # Not testing Skew... it's slow with int/frac values > 3/2 + + +def test_wignersemicircle(): + R = Symbol("R", positive=True) + + X = WignerSemicircle('x', R) + assert pspace(X).domain.set == Interval(-R, R) + assert density(X)(x) == 2*sqrt(-x**2 + R**2)/(pi*R**2) + assert E(X) == 0 + + + #Tests ChiNoncentralDistribution + assert characteristic_function(X)(x) == \ + Piecewise((2*besselj(1, R*x)/(R*x), Ne(x, 0)), (1, True)) + + +def test_input_value_assertions(): + a, b = symbols('a b') + p, q = symbols('p q', positive=True) + m, n = symbols('m n', positive=False, real=True) + + raises(ValueError, lambda: Normal('x', 3, 0)) + raises(ValueError, lambda: Normal('x', m, n)) + Normal('X', a, p) # No error raised + raises(ValueError, lambda: Exponential('x', m)) + Exponential('Ex', p) # No error raised + for fn in [Pareto, Weibull, Beta, Gamma]: + raises(ValueError, lambda: fn('x', m, p)) + raises(ValueError, lambda: fn('x', p, n)) + fn('x', p, q) # No error raised + + +def test_unevaluated(): + X = Normal('x', 0, 1) + k = Dummy('k') + expr1 = Integral(sqrt(2)*k*exp(-k**2/2)/(2*sqrt(pi)), (k, -oo, oo)) + expr2 = Integral(sqrt(2)*exp(-k**2/2)/(2*sqrt(pi)), (k, 0, oo)) + with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed + assert E(X, evaluate=False).rewrite(Integral).dummy_eq(expr1) + assert E(X + 1, evaluate=False).rewrite(Integral).dummy_eq(expr1 + 1) + assert P(X > 0, evaluate=False).rewrite(Integral).dummy_eq(expr2) + + assert P(X > 0, X**2 < 1) == S.Half + + +def test_probability_unevaluated(): + T = Normal('T', 30, 3) + with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed + assert type(P(T > 33, evaluate=False)) == Probability + + +def test_density_unevaluated(): + X = Normal('X', 0, 1) + Y = Normal('Y', 0, 2) + assert isinstance(density(X+Y, evaluate=False)(z), Integral) + + +def test_NormalDistribution(): + nd = NormalDistribution(0, 1) + x = Symbol('x') + assert nd.cdf(x) == erf(sqrt(2)*x/2)/2 + S.Half + assert nd.expectation(1, x) == 1 + assert nd.expectation(x, x) == 0 + assert nd.expectation(x**2, x) == 1 + #Test issue 10076 + a = SingleContinuousPSpace(x, NormalDistribution(2, 4)) + _z = Dummy('_z') + + expected1 = Integral(sqrt(2)*exp(-(_z - 2)**2/32)/(8*sqrt(pi)),(_z, -oo, 1)) + assert a.probability(x < 1, evaluate=False).dummy_eq(expected1) is True + + expected2 = Integral(sqrt(2)*exp(-(_z - 2)**2/32)/(8*sqrt(pi)),(_z, 1, oo)) + assert a.probability(x > 1, evaluate=False).dummy_eq(expected2) is True + + b = SingleContinuousPSpace(x, NormalDistribution(1, 9)) + + expected3 = Integral(sqrt(2)*exp(-(_z - 1)**2/162)/(18*sqrt(pi)),(_z, 6, oo)) + assert b.probability(x > 6, evaluate=False).dummy_eq(expected3) is True + + expected4 = Integral(sqrt(2)*exp(-(_z - 1)**2/162)/(18*sqrt(pi)),(_z, -oo, 6)) + assert b.probability(x < 6, evaluate=False).dummy_eq(expected4) is True + + +def test_random_parameters(): + mu = Normal('mu', 2, 3) + meas = Normal('T', mu, 1) + assert density(meas, evaluate=False)(z) + assert isinstance(pspace(meas), CompoundPSpace) + X = Normal('x', [1, 2], [[1, 0], [0, 1]]) + assert isinstance(pspace(X).distribution, MultivariateNormalDistribution) + assert density(meas)(z).simplify() == sqrt(5)*exp(-z**2/20 + z/5 - S(1)/5)/(10*sqrt(pi)) + + +def test_random_parameters_given(): + mu = Normal('mu', 2, 3) + meas = Normal('T', mu, 1) + assert given(meas, Eq(mu, 5)) == Normal('T', 5, 1) + + +def test_conjugate_priors(): + mu = Normal('mu', 2, 3) + x = Normal('x', mu, 1) + assert isinstance(simplify(density(mu, Eq(x, y), evaluate=False)(z)), + Mul) + + +def test_difficult_univariate(): + """ Since using solve in place of deltaintegrate we're able to perform + substantially more complex density computations on single continuous random + variables """ + x = Normal('x', 0, 1) + assert density(x**3) + assert density(exp(x**2)) + assert density(log(x)) + + +def test_issue_10003(): + X = Exponential('x', 3) + G = Gamma('g', 1, 2) + assert P(X < -1) is S.Zero + assert P(G < -1) is S.Zero + + +def test_precomputed_cdf(): + x = symbols("x", real=True) + mu = symbols("mu", real=True) + sigma, xm, alpha = symbols("sigma xm alpha", positive=True) + n = symbols("n", integer=True, positive=True) + distribs = [ + Normal("X", mu, sigma), + Pareto("P", xm, alpha), + ChiSquared("C", n), + Exponential("E", sigma), + # LogNormal("L", mu, sigma), + ] + for X in distribs: + compdiff = cdf(X)(x) - simplify(X.pspace.density.compute_cdf()(x)) + compdiff = simplify(compdiff.rewrite(erfc)) + assert compdiff == 0 + + +@slow +def test_precomputed_characteristic_functions(): + import mpmath + + def test_cf(dist, support_lower_limit, support_upper_limit): + pdf = density(dist) + t = Symbol('t') + + # first function is the hardcoded CF of the distribution + cf1 = lambdify([t], characteristic_function(dist)(t), 'mpmath') + + # second function is the Fourier transform of the density function + f = lambdify([x, t], pdf(x)*exp(I*x*t), 'mpmath') + cf2 = lambda t: mpmath.quad(lambda x: f(x, t), [support_lower_limit, support_upper_limit], maxdegree=10) + + # compare the two functions at various points + for test_point in [2, 5, 8, 11]: + n1 = cf1(test_point) + n2 = cf2(test_point) + + assert abs(re(n1) - re(n2)) < 1e-12 + assert abs(im(n1) - im(n2)) < 1e-12 + + test_cf(Beta('b', 1, 2), 0, 1) + test_cf(Chi('c', 3), 0, mpmath.inf) + test_cf(ChiSquared('c', 2), 0, mpmath.inf) + test_cf(Exponential('e', 6), 0, mpmath.inf) + test_cf(Logistic('l', 1, 2), -mpmath.inf, mpmath.inf) + test_cf(Normal('n', -1, 5), -mpmath.inf, mpmath.inf) + test_cf(RaisedCosine('r', 3, 1), 2, 4) + test_cf(Rayleigh('r', 0.5), 0, mpmath.inf) + test_cf(Uniform('u', -1, 1), -1, 1) + test_cf(WignerSemicircle('w', 3), -3, 3) + + +def test_long_precomputed_cdf(): + x = symbols("x", real=True) + distribs = [ + Arcsin("A", -5, 9), + Dagum("D", 4, 10, 3), + Erlang("E", 14, 5), + Frechet("F", 2, 6, -3), + Gamma("G", 2, 7), + GammaInverse("GI", 3, 5), + Kumaraswamy("K", 6, 8), + Laplace("LA", -5, 4), + Logistic("L", -6, 7), + Nakagami("N", 2, 7), + StudentT("S", 4) + ] + for distr in distribs: + for _ in range(5): + assert tn(diff(cdf(distr)(x), x), density(distr)(x), x, a=0, b=0, c=1, d=0) + + US = UniformSum("US", 5) + pdf01 = density(US)(x).subs(floor(x), 0).doit() # pdf on (0, 1) + cdf01 = cdf(US, evaluate=False)(x).subs(floor(x), 0).doit() # cdf on (0, 1) + assert tn(diff(cdf01, x), pdf01, x, a=0, b=0, c=1, d=0) + + +def test_issue_13324(): + X = Uniform('X', 0, 1) + assert E(X, X > S.Half) == Rational(3, 4) + assert E(X, X > 0) == S.Half + +def test_issue_20756(): + X = Uniform('X', -1, +1) + Y = Uniform('Y', -1, +1) + assert E(X * Y) == S.Zero + assert E(X * ((Y + 1) - 1)) == S.Zero + assert E(Y * (X*(X + 1) - X*X)) == S.Zero + +def test_FiniteSet_prob(): + E = Exponential('E', 3) + N = Normal('N', 5, 7) + assert P(Eq(E, 1)) is S.Zero + assert P(Eq(N, 2)) is S.Zero + assert P(Eq(N, x)) is S.Zero + +def test_prob_neq(): + E = Exponential('E', 4) + X = ChiSquared('X', 4) + assert P(Ne(E, 2)) == 1 + assert P(Ne(X, 4)) == 1 + assert P(Ne(X, 4)) == 1 + assert P(Ne(X, 5)) == 1 + assert P(Ne(E, x)) == 1 + +def test_union(): + N = Normal('N', 3, 2) + assert simplify(P(N**2 - N > 2)) == \ + -erf(sqrt(2))/2 - erfc(sqrt(2)/4)/2 + Rational(3, 2) + assert simplify(P(N**2 - 4 > 0)) == \ + -erf(5*sqrt(2)/4)/2 - erfc(sqrt(2)/4)/2 + Rational(3, 2) + +def test_Or(): + N = Normal('N', 0, 1) + assert simplify(P(Or(N > 2, N < 1))) == \ + -erf(sqrt(2))/2 - erfc(sqrt(2)/2)/2 + Rational(3, 2) + assert P(Or(N < 0, N < 1)) == P(N < 1) + assert P(Or(N > 0, N < 0)) == 1 + + +def test_conditional_eq(): + E = Exponential('E', 1) + assert P(Eq(E, 1), Eq(E, 1)) == 1 + assert P(Eq(E, 1), Eq(E, 2)) == 0 + assert P(E > 1, Eq(E, 2)) == 1 + assert P(E < 1, Eq(E, 2)) == 0 + +def test_ContinuousDistributionHandmade(): + x = Symbol('x') + z = Dummy('z') + dens = Lambda(x, Piecewise((S.Half, (0<=x)&(x<1)), (0, (x>=1)&(x<2)), + (S.Half, (x>=2)&(x<3)), (0, True))) + dens = ContinuousDistributionHandmade(dens, set=Interval(0, 3)) + space = SingleContinuousPSpace(z, dens) + assert dens.pdf == Lambda(x, Piecewise((S(1)/2, (x >= 0) & (x < 1)), + (0, (x >= 1) & (x < 2)), (S(1)/2, (x >= 2) & (x < 3)), (0, True))) + assert median(space.value) == Interval(1, 2) + assert E(space.value) == Rational(3, 2) + assert variance(space.value) == Rational(13, 12) + + +def test_issue_16318(): + # test compute_expectation function of the SingleContinuousDomain + N = SingleContinuousDomain(x, Interval(0, 1)) + raises(ValueError, lambda: SingleContinuousDomain.compute_expectation(N, x+1, {x, y})) + +def test_compute_density(): + X = Normal('X', 0, Symbol("sigma")**2) + raises(ValueError, lambda: density(X**5 + X)) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_discrete_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_discrete_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..39650a1a08e42840aaf8b06c1eb6e60c92a57f23 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_discrete_rv.py @@ -0,0 +1,312 @@ +from sympy.concrete.summations import Sum +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.bessel import besseli +from sympy.functions.special.beta_functions import beta +from sympy.functions.special.zeta_functions import zeta +from sympy.sets.sets import FiniteSet +from sympy.simplify.simplify import simplify +from sympy.utilities.lambdify import lambdify +from sympy.core.relational import Eq, Ne +from sympy.functions.elementary.exponential import exp +from sympy.logic.boolalg import Or +from sympy.sets.fancysets import Range +from sympy.stats import (P, E, variance, density, characteristic_function, + where, moment_generating_function, skewness, cdf, + kurtosis, coskewness) +from sympy.stats.drv_types import (PoissonDistribution, GeometricDistribution, + FlorySchulz, Poisson, Geometric, Hermite, Logarithmic, + NegativeBinomial, Skellam, YuleSimon, Zeta, + DiscreteRV) +from sympy.testing.pytest import slow, nocache_fail, raises, skip +from sympy.stats.symbolic_probability import Expectation +from sympy.functions.combinatorial.factorials import FallingFactorial + +x = Symbol('x') + + +def test_PoissonDistribution(): + l = 3 + p = PoissonDistribution(l) + assert abs(p.cdf(10).evalf() - 1) < .001 + assert abs(p.cdf(10.4).evalf() - 1) < .001 + assert p.expectation(x, x) == l + assert p.expectation(x**2, x) - p.expectation(x, x)**2 == l + + +def test_Poisson(): + l = 3 + x = Poisson('x', l) + assert E(x) == l + assert E(2*x) == 2*l + assert variance(x) == l + assert density(x) == PoissonDistribution(l) + assert isinstance(E(x, evaluate=False), Expectation) + assert isinstance(E(2*x, evaluate=False), Expectation) + # issue 8248 + assert x.pspace.compute_expectation(1) == 1 + # issue 27344 + try: + import numpy as np + except ImportError: + skip("numpy not installed") + y = Poisson('y', np.float64(4.72544290380919e-11)) + assert E(y) == 4.72544290380919e-11 + y = Poisson('y', np.float64(4.725442903809197e-11)) + assert E(y) == 4.725442903809197e-11 + l2 = 5 + z = Poisson('z', l2) + assert E(z) == l2 + assert E(FallingFactorial(z, 3)) == l2**3 + assert E(z**2) == l2 + l2**2 + + +def test_FlorySchulz(): + a = Symbol("a") + z = Symbol("z") + x = FlorySchulz('x', a) + assert E(x) == (2 - a)/a + assert (variance(x) - 2*(1 - a)/a**2).simplify() == S(0) + assert density(x)(z) == a**2*z*(1 - a)**(z - 1) + + +@slow +def test_GeometricDistribution(): + p = S.One / 5 + d = GeometricDistribution(p) + assert d.expectation(x, x) == 1/p + assert d.expectation(x**2, x) - d.expectation(x, x)**2 == (1-p)/p**2 + assert abs(d.cdf(20000).evalf() - 1) < .001 + assert abs(d.cdf(20000.8).evalf() - 1) < .001 + G = Geometric('G', p=S(1)/4) + assert cdf(G)(S(7)/2) == P(G <= S(7)/2) + + X = Geometric('X', Rational(1, 5)) + Y = Geometric('Y', Rational(3, 10)) + assert coskewness(X, X + Y, X + 2*Y).simplify() == sqrt(230)*Rational(81, 1150) + + +def test_Hermite(): + a1 = Symbol("a1", positive=True) + a2 = Symbol("a2", negative=True) + raises(ValueError, lambda: Hermite("H", a1, a2)) + + a1 = Symbol("a1", negative=True) + a2 = Symbol("a2", positive=True) + raises(ValueError, lambda: Hermite("H", a1, a2)) + + a1 = Symbol("a1", positive=True) + x = Symbol("x") + H = Hermite("H", a1, a2) + assert moment_generating_function(H)(x) == exp(a1*(exp(x) - 1) + + a2*(exp(2*x) - 1)) + assert characteristic_function(H)(x) == exp(a1*(exp(I*x) - 1) + + a2*(exp(2*I*x) - 1)) + assert E(H) == a1 + 2*a2 + + H = Hermite("H", a1=5, a2=4) + assert density(H)(2) == 33*exp(-9)/2 + assert E(H) == 13 + assert variance(H) == 21 + assert kurtosis(H) == Rational(464,147) + assert skewness(H) == 37*sqrt(21)/441 + +def test_Logarithmic(): + p = S.Half + x = Logarithmic('x', p) + assert E(x) == -p / ((1 - p) * log(1 - p)) + assert variance(x) == -1/log(2)**2 + 2/log(2) + assert E(2*x**2 + 3*x + 4) == 4 + 7 / log(2) + assert isinstance(E(x, evaluate=False), Expectation) + + +@nocache_fail +def test_negative_binomial(): + r = 5 + p = S.One / 3 + x = NegativeBinomial('x', r, p) + assert E(x) == r * (1 - p) / p + # This hangs when run with the cache disabled: + assert variance(x) == r * (1 - p) / p**2 + assert E(x**5 + 2*x + 3) == E(x**5) + 2*E(x) + 3 == Rational(796473, 1) + assert isinstance(E(x, evaluate=False), Expectation) + + +def test_skellam(): + mu1 = Symbol('mu1') + mu2 = Symbol('mu2') + z = Symbol('z') + X = Skellam('x', mu1, mu2) + + assert density(X)(z) == (mu1/mu2)**(z/2) * \ + exp(-mu1 - mu2)*besseli(z, 2*sqrt(mu1*mu2)) + assert skewness(X).expand() == mu1/(mu1*sqrt(mu1 + mu2) + mu2 * + sqrt(mu1 + mu2)) - mu2/(mu1*sqrt(mu1 + mu2) + mu2*sqrt(mu1 + mu2)) + assert variance(X).expand() == mu1 + mu2 + assert E(X) == mu1 - mu2 + assert characteristic_function(X)(z) == exp( + mu1*exp(I*z) - mu1 - mu2 + mu2*exp(-I*z)) + assert moment_generating_function(X)(z) == exp( + mu1*exp(z) - mu1 - mu2 + mu2*exp(-z)) + + +def test_yule_simon(): + from sympy.core.singleton import S + rho = S(3) + x = YuleSimon('x', rho) + assert simplify(E(x)) == rho / (rho - 1) + assert simplify(variance(x)) == rho**2 / ((rho - 1)**2 * (rho - 2)) + assert isinstance(E(x, evaluate=False), Expectation) + # To test the cdf function + assert cdf(x)(x) == Piecewise((-beta(floor(x), 4)*floor(x) + 1, x >= 1), (0, True)) + + +def test_zeta(): + s = S(5) + x = Zeta('x', s) + assert E(x) == zeta(s-1) / zeta(s) + assert simplify(variance(x)) == ( + zeta(s) * zeta(s-2) - zeta(s-1)**2) / zeta(s)**2 + + +def test_discrete_probability(): + X = Geometric('X', Rational(1, 5)) + Y = Poisson('Y', 4) + G = Geometric('e', x) + assert P(Eq(X, 3)) == Rational(16, 125) + assert P(X < 3) == Rational(9, 25) + assert P(X > 3) == Rational(64, 125) + assert P(X >= 3) == Rational(16, 25) + assert P(X <= 3) == Rational(61, 125) + assert P(Ne(X, 3)) == Rational(109, 125) + assert P(Eq(Y, 3)) == 32*exp(-4)/3 + assert P(Y < 3) == 13*exp(-4) + assert P(Y > 3).equals(32*(Rational(-71, 32) + 3*exp(4)/32)*exp(-4)/3) + assert P(Y >= 3).equals(32*(Rational(-39, 32) + 3*exp(4)/32)*exp(-4)/3) + assert P(Y <= 3) == 71*exp(-4)/3 + assert P(Ne(Y, 3)).equals( + 13*exp(-4) + 32*(Rational(-71, 32) + 3*exp(4)/32)*exp(-4)/3) + assert P(X < S.Infinity) is S.One + assert P(X > S.Infinity) is S.Zero + assert P(G < 3) == x*(2-x) + assert P(Eq(G, 3)) == x*(-x + 1)**2 + + +def test_DiscreteRV(): + p = S(1)/2 + x = Symbol('x', integer=True, positive=True) + pdf = p*(1 - p)**(x - 1) # pdf of Geometric Distribution + D = DiscreteRV(x, pdf, set=S.Naturals, check=True) + assert E(D) == E(Geometric('G', S(1)/2)) == 2 + assert P(D > 3) == S(1)/8 + assert D.pspace.domain.set == S.Naturals + raises(ValueError, lambda: DiscreteRV(x, x, FiniteSet(*range(4)), check=True)) + + # purposeful invalid pmf but it should not raise since check=False + # see test_drv_types.test_ContinuousRV for explanation + X = DiscreteRV(x, 1/x, S.Naturals) + assert P(X < 2) == 1 + assert E(X) == oo + +def test_precomputed_characteristic_functions(): + import mpmath + + def test_cf(dist, support_lower_limit, support_upper_limit): + pdf = density(dist) + t = S('t') + x = S('x') + + # first function is the hardcoded CF of the distribution + cf1 = lambdify([t], characteristic_function(dist)(t), 'mpmath') + + # second function is the Fourier transform of the density function + f = lambdify([x, t], pdf(x)*exp(I*x*t), 'mpmath') + cf2 = lambda t: mpmath.nsum(lambda x: f(x, t), [ + support_lower_limit, support_upper_limit], maxdegree=10) + + # compare the two functions at various points + for test_point in [2, 5, 8, 11]: + n1 = cf1(test_point) + n2 = cf2(test_point) + + assert abs(re(n1) - re(n2)) < 1e-12 + assert abs(im(n1) - im(n2)) < 1e-12 + + test_cf(Geometric('g', Rational(1, 3)), 1, mpmath.inf) + test_cf(Logarithmic('l', Rational(1, 5)), 1, mpmath.inf) + test_cf(NegativeBinomial('n', 5, Rational(1, 7)), 0, mpmath.inf) + test_cf(Poisson('p', 5), 0, mpmath.inf) + test_cf(YuleSimon('y', 5), 1, mpmath.inf) + test_cf(Zeta('z', 5), 1, mpmath.inf) + + +def test_moment_generating_functions(): + t = S('t') + + geometric_mgf = moment_generating_function(Geometric('g', S.Half))(t) + assert geometric_mgf.diff(t).subs(t, 0) == 2 + + logarithmic_mgf = moment_generating_function(Logarithmic('l', S.Half))(t) + assert logarithmic_mgf.diff(t).subs(t, 0) == 1/log(2) + + negative_binomial_mgf = moment_generating_function( + NegativeBinomial('n', 5, Rational(1, 3)))(t) + assert negative_binomial_mgf.diff(t).subs(t, 0) == Rational(10, 1) + + poisson_mgf = moment_generating_function(Poisson('p', 5))(t) + assert poisson_mgf.diff(t).subs(t, 0) == 5 + + skellam_mgf = moment_generating_function(Skellam('s', 1, 1))(t) + assert skellam_mgf.diff(t).subs( + t, 2) == (-exp(-2) + exp(2))*exp(-2 + exp(-2) + exp(2)) + + yule_simon_mgf = moment_generating_function(YuleSimon('y', 3))(t) + assert simplify(yule_simon_mgf.diff(t).subs(t, 0)) == Rational(3, 2) + + zeta_mgf = moment_generating_function(Zeta('z', 5))(t) + assert zeta_mgf.diff(t).subs(t, 0) == pi**4/(90*zeta(5)) + + +def test_Or(): + X = Geometric('X', S.Half) + assert P(Or(X < 3, X > 4)) == Rational(13, 16) + assert P(Or(X > 2, X > 1)) == P(X > 1) + assert P(Or(X >= 3, X < 3)) == 1 + + +def test_where(): + X = Geometric('X', Rational(1, 5)) + Y = Poisson('Y', 4) + assert where(X**2 > 4).set == Range(3, S.Infinity, 1) + assert where(X**2 >= 4).set == Range(2, S.Infinity, 1) + assert where(Y**2 < 9).set == Range(0, 3, 1) + assert where(Y**2 <= 9).set == Range(0, 4, 1) + + +def test_conditional(): + X = Geometric('X', Rational(2, 3)) + Y = Poisson('Y', 3) + assert P(X > 2, X > 3) == 1 + assert P(X > 3, X > 2) == Rational(1, 3) + assert P(Y > 2, Y < 2) == 0 + assert P(Eq(Y, 3), Y >= 0) == 9*exp(-3)/2 + assert P(Eq(Y, 3), Eq(Y, 2)) == 0 + assert P(X < 2, Eq(X, 2)) == 0 + assert P(X > 2, Eq(X, 3)) == 1 + + +def test_product_spaces(): + X1 = Geometric('X1', S.Half) + X2 = Geometric('X2', Rational(1, 3)) + assert str(P(X1 + X2 < 3).rewrite(Sum)) == ( + "Sum(Piecewise((1/(4*2**n), n >= -1), (0, True)), (n, -oo, -1))/3") + assert str(P(X1 + X2 > 3).rewrite(Sum)) == ( + 'Sum(Piecewise((2**(X2 - n - 2)*(2/3)**(X2 - 1)/6, ' + 'X2 - n <= 2), (0, True)), (X2, 1, oo), (n, 1, oo))') + assert P(Eq(X1 + X2, 3)) == Rational(1, 12) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_error_prop.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_error_prop.py new file mode 100644 index 0000000000000000000000000000000000000000..483fb4c36e202d744faeb355606ff9803a516873 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_error_prop.py @@ -0,0 +1,60 @@ +from sympy.core.function import Function +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import exp +from sympy.stats.error_prop import variance_prop +from sympy.stats.symbolic_probability import (RandomSymbol, Variance, + Covariance) + + +def test_variance_prop(): + x, y, z = symbols('x y z') + phi, t = consts = symbols('phi t') + a = RandomSymbol(x) + var_x = Variance(a) + var_y = Variance(RandomSymbol(y)) + var_z = Variance(RandomSymbol(z)) + f = Function('f')(x) + cases = { + x + y: var_x + var_y, + a + y: var_x + var_y, + x + y + z: var_x + var_y + var_z, + 2*x: 4*var_x, + x*y: var_x*y**2 + var_y*x**2, + 1/x: var_x/x**4, + x/y: (var_x*y**2 + var_y*x**2)/y**4, + exp(x): var_x*exp(2*x), + exp(2*x): 4*var_x*exp(4*x), + exp(-x*t): t**2*var_x*exp(-2*t*x), + f: Variance(f), + } + for inp, out in cases.items(): + obs = variance_prop(inp, consts=consts) + assert out == obs + +def test_variance_prop_with_covar(): + x, y, z = symbols('x y z') + phi, t = consts = symbols('phi t') + a = RandomSymbol(x) + var_x = Variance(a) + b = RandomSymbol(y) + var_y = Variance(b) + c = RandomSymbol(z) + var_z = Variance(c) + covar_x_y = Covariance(a, b) + covar_x_z = Covariance(a, c) + covar_y_z = Covariance(b, c) + cases = { + x + y: var_x + var_y + 2*covar_x_y, + a + y: var_x + var_y + 2*covar_x_y, + x + y + z: var_x + var_y + var_z + \ + 2*covar_x_y + 2*covar_x_z + 2*covar_y_z, + 2*x: 4*var_x, + x*y: var_x*y**2 + var_y*x**2 + 2*covar_x_y/(x*y), + 1/x: var_x/x**4, + exp(x): var_x*exp(2*x), + exp(2*x): 4*var_x*exp(4*x), + exp(-x*t): t**2*var_x*exp(-2*t*x), + } + for inp, out in cases.items(): + obs = variance_prop(inp, consts=consts, include_covar=True) + assert out == obs diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_finite_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_finite_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..93bf0211a26ecc32d7f18c7e2d8236859857e445 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_finite_rv.py @@ -0,0 +1,509 @@ +from sympy.concrete.summations import Sum +from sympy.core.containers import (Dict, Tuple) +from sympy.core.function import Function +from sympy.core.numbers import (I, Rational, nan) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import binomial +from sympy.functions.combinatorial.numbers import harmonic +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import cos +from sympy.functions.special.beta_functions import beta +from sympy.logic.boolalg import (And, Or) +from sympy.polys.polytools import cancel +from sympy.sets.sets import FiniteSet +from sympy.simplify.simplify import simplify +from sympy.matrices import Matrix +from sympy.stats import (DiscreteUniform, Die, Bernoulli, Coin, Binomial, BetaBinomial, + Hypergeometric, Rademacher, IdealSoliton, RobustSoliton, P, E, variance, + covariance, skewness, density, where, FiniteRV, pspace, cdf, + correlation, moment, cmoment, smoment, characteristic_function, + moment_generating_function, quantile, kurtosis, median, coskewness) +from sympy.stats.frv_types import DieDistribution, BinomialDistribution, \ + HypergeometricDistribution +from sympy.stats.rv import Density +from sympy.testing.pytest import raises + + +def BayesTest(A, B): + assert P(A, B) == P(And(A, B)) / P(B) + assert P(A, B) == P(B, A) * P(A) / P(B) + + +def test_discreteuniform(): + # Symbolic + a, b, c, t = symbols('a b c t') + X = DiscreteUniform('X', [a, b, c]) + + assert E(X) == (a + b + c)/3 + assert simplify(variance(X) + - ((a**2 + b**2 + c**2)/3 - (a/3 + b/3 + c/3)**2)) == 0 + assert P(Eq(X, a)) == P(Eq(X, b)) == P(Eq(X, c)) == S('1/3') + + Y = DiscreteUniform('Y', range(-5, 5)) + + # Numeric + assert E(Y) == S('-1/2') + assert variance(Y) == S('33/4') + assert median(Y) == FiniteSet(-1, 0) + + for x in range(-5, 5): + assert P(Eq(Y, x)) == S('1/10') + assert P(Y <= x) == S(x + 6)/10 + assert P(Y >= x) == S(5 - x)/10 + + assert dict(density(Die('D', 6)).items()) == \ + dict(density(DiscreteUniform('U', range(1, 7))).items()) + + assert characteristic_function(X)(t) == exp(I*a*t)/3 + exp(I*b*t)/3 + exp(I*c*t)/3 + assert moment_generating_function(X)(t) == exp(a*t)/3 + exp(b*t)/3 + exp(c*t)/3 + # issue 18611 + raises(ValueError, lambda: DiscreteUniform('Z', [a, a, a, b, b, c])) + +def test_dice(): + # TODO: Make iid method! + X, Y, Z = Die('X', 6), Die('Y', 6), Die('Z', 6) + a, b, t, p = symbols('a b t p') + + assert E(X) == 3 + S.Half + assert variance(X) == Rational(35, 12) + assert E(X + Y) == 7 + assert E(X + X) == 7 + assert E(a*X + b) == a*E(X) + b + assert variance(X + Y) == variance(X) + variance(Y) == cmoment(X + Y, 2) + assert variance(X + X) == 4 * variance(X) == cmoment(X + X, 2) + assert cmoment(X, 0) == 1 + assert cmoment(4*X, 3) == 64*cmoment(X, 3) + assert covariance(X, Y) is S.Zero + assert covariance(X, X + Y) == variance(X) + assert density(Eq(cos(X*S.Pi), 1))[True] == S.Half + assert correlation(X, Y) == 0 + assert correlation(X, Y) == correlation(Y, X) + assert smoment(X + Y, 3) == skewness(X + Y) + assert smoment(X + Y, 4) == kurtosis(X + Y) + assert smoment(X, 0) == 1 + assert P(X > 3) == S.Half + assert P(2*X > 6) == S.Half + assert P(X > Y) == Rational(5, 12) + assert P(Eq(X, Y)) == P(Eq(X, 1)) + + assert E(X, X > 3) == 5 == moment(X, 1, 0, X > 3) + assert E(X, Y > 3) == E(X) == moment(X, 1, 0, Y > 3) + assert E(X + Y, Eq(X, Y)) == E(2*X) + assert moment(X, 0) == 1 + assert moment(5*X, 2) == 25*moment(X, 2) + assert quantile(X)(p) == Piecewise((nan, (p > 1) | (p < 0)),\ + (S.One, p <= Rational(1, 6)), (S(2), p <= Rational(1, 3)), (S(3), p <= S.Half),\ + (S(4), p <= Rational(2, 3)), (S(5), p <= Rational(5, 6)), (S(6), p <= 1)) + + assert P(X > 3, X > 3) is S.One + assert P(X > Y, Eq(Y, 6)) is S.Zero + assert P(Eq(X + Y, 12)) == Rational(1, 36) + assert P(Eq(X + Y, 12), Eq(X, 6)) == Rational(1, 6) + + assert density(X + Y) == density(Y + Z) != density(X + X) + d = density(2*X + Y**Z) + assert d[S(22)] == Rational(1, 108) and d[S(4100)] == Rational(1, 216) and S(3130) not in d + + assert pspace(X).domain.as_boolean() == Or( + *[Eq(X.symbol, i) for i in [1, 2, 3, 4, 5, 6]]) + + assert where(X > 3).set == FiniteSet(4, 5, 6) + + assert characteristic_function(X)(t) == exp(6*I*t)/6 + exp(5*I*t)/6 + exp(4*I*t)/6 + exp(3*I*t)/6 + exp(2*I*t)/6 + exp(I*t)/6 + assert moment_generating_function(X)(t) == exp(6*t)/6 + exp(5*t)/6 + exp(4*t)/6 + exp(3*t)/6 + exp(2*t)/6 + exp(t)/6 + assert median(X) == FiniteSet(3, 4) + D = Die('D', 7) + assert median(D) == FiniteSet(4) + # Bayes test for die + BayesTest(X > 3, X + Y < 5) + BayesTest(Eq(X - Y, Z), Z > Y) + BayesTest(X > 3, X > 2) + + # arg test for die + raises(ValueError, lambda: Die('X', -1)) # issue 8105: negative sides. + raises(ValueError, lambda: Die('X', 0)) + raises(ValueError, lambda: Die('X', 1.5)) # issue 8103: non integer sides. + + # symbolic test for die + n, k = symbols('n, k', positive=True) + D = Die('D', n) + dens = density(D).dict + assert dens == Density(DieDistribution(n)) + assert set(dens.subs(n, 4).doit().keys()) == {1, 2, 3, 4} + assert set(dens.subs(n, 4).doit().values()) == {Rational(1, 4)} + k = Dummy('k', integer=True) + assert E(D).dummy_eq( + Sum(Piecewise((k/n, k <= n), (0, True)), (k, 1, n))) + assert variance(D).subs(n, 6).doit() == Rational(35, 12) + + ki = Dummy('ki') + cumuf = cdf(D)(k) + assert cumuf.dummy_eq( + Sum(Piecewise((1/n, (ki >= 1) & (ki <= n)), (0, True)), (ki, 1, k))) + assert cumuf.subs({n: 6, k: 2}).doit() == Rational(1, 3) + + t = Dummy('t') + cf = characteristic_function(D)(t) + assert cf.dummy_eq( + Sum(Piecewise((exp(ki*I*t)/n, (ki >= 1) & (ki <= n)), (0, True)), (ki, 1, n))) + assert cf.subs(n, 3).doit() == exp(3*I*t)/3 + exp(2*I*t)/3 + exp(I*t)/3 + mgf = moment_generating_function(D)(t) + assert mgf.dummy_eq( + Sum(Piecewise((exp(ki*t)/n, (ki >= 1) & (ki <= n)), (0, True)), (ki, 1, n))) + assert mgf.subs(n, 3).doit() == exp(3*t)/3 + exp(2*t)/3 + exp(t)/3 + +def test_given(): + X = Die('X', 6) + assert density(X, X > 5) == {S(6): S.One} + assert where(X > 2, X > 5).as_boolean() == Eq(X.symbol, 6) + + +def test_domains(): + X, Y = Die('x', 6), Die('y', 6) + x, y = X.symbol, Y.symbol + # Domains + d = where(X > Y) + assert d.condition == (x > y) + d = where(And(X > Y, Y > 3)) + assert d.as_boolean() == Or(And(Eq(x, 5), Eq(y, 4)), And(Eq(x, 6), + Eq(y, 5)), And(Eq(x, 6), Eq(y, 4))) + assert len(d.elements) == 3 + + assert len(pspace(X + Y).domain.elements) == 36 + + Z = Die('x', 4) + + raises(ValueError, lambda: P(X > Z)) # Two domains with same internal symbol + + assert pspace(X + Y).domain.set == FiniteSet(1, 2, 3, 4, 5, 6)**2 + + assert where(X > 3).set == FiniteSet(4, 5, 6) + assert X.pspace.domain.dict == FiniteSet( + *[Dict({X.symbol: i}) for i in range(1, 7)]) + + assert where(X > Y).dict == FiniteSet(*[Dict({X.symbol: i, Y.symbol: j}) + for i in range(1, 7) for j in range(1, 7) if i > j]) + +def test_bernoulli(): + p, a, b, t = symbols('p a b t') + X = Bernoulli('B', p, a, b) + + assert E(X) == a*p + b*(-p + 1) + assert density(X)[a] == p + assert density(X)[b] == 1 - p + assert characteristic_function(X)(t) == p * exp(I * a * t) + (-p + 1) * exp(I * b * t) + assert moment_generating_function(X)(t) == p * exp(a * t) + (-p + 1) * exp(b * t) + + X = Bernoulli('B', p, 1, 0) + z = Symbol("z") + + assert E(X) == p + assert simplify(variance(X)) == p*(1 - p) + assert E(a*X + b) == a*E(X) + b + assert simplify(variance(a*X + b)) == simplify(a**2 * variance(X)) + assert quantile(X)(z) == Piecewise((nan, (z > 1) | (z < 0)), (0, z <= 1 - p), (1, z <= 1)) + Y = Bernoulli('Y', Rational(1, 2)) + assert median(Y) == FiniteSet(0, 1) + Z = Bernoulli('Z', Rational(2, 3)) + assert median(Z) == FiniteSet(1) + raises(ValueError, lambda: Bernoulli('B', 1.5)) + raises(ValueError, lambda: Bernoulli('B', -0.5)) + + #issue 8248 + assert X.pspace.compute_expectation(1) == 1 + + p = Rational(1, 5) + X = Binomial('X', 5, p) + Y = Binomial('Y', 7, 2*p) + Z = Binomial('Z', 9, 3*p) + assert coskewness(Y + Z, X + Y, X + Z).simplify() == 0 + assert coskewness(Y + 2*X + Z, X + 2*Y + Z, X + 2*Z + Y).simplify() == \ + sqrt(1529)*Rational(12, 16819) + assert coskewness(Y + 2*X + Z, X + 2*Y + Z, X + 2*Z + Y, X < 2).simplify() \ + == -sqrt(357451121)*Rational(2812, 4646864573) + +def test_cdf(): + D = Die('D', 6) + o = S.One + + assert cdf( + D) == sympify({1: o/6, 2: o/3, 3: o/2, 4: 2*o/3, 5: 5*o/6, 6: o}) + + +def test_coins(): + C, D = Coin('C'), Coin('D') + H, T = symbols('H, T') + assert P(Eq(C, D)) == S.Half + assert density(Tuple(C, D)) == {(H, H): Rational(1, 4), (H, T): Rational(1, 4), + (T, H): Rational(1, 4), (T, T): Rational(1, 4)} + assert dict(density(C).items()) == {H: S.Half, T: S.Half} + + F = Coin('F', Rational(1, 10)) + assert P(Eq(F, H)) == Rational(1, 10) + + d = pspace(C).domain + + assert d.as_boolean() == Or(Eq(C.symbol, H), Eq(C.symbol, T)) + + raises(ValueError, lambda: P(C > D)) # Can't intelligently compare H to T + +def test_binomial_verify_parameters(): + raises(ValueError, lambda: Binomial('b', .2, .5)) + raises(ValueError, lambda: Binomial('b', 3, 1.5)) + +def test_binomial_numeric(): + nvals = range(5) + pvals = [0, Rational(1, 4), S.Half, Rational(3, 4), 1] + + for n in nvals: + for p in pvals: + X = Binomial('X', n, p) + assert E(X) == n*p + assert variance(X) == n*p*(1 - p) + if n > 0 and 0 < p < 1: + assert skewness(X) == (1 - 2*p)/sqrt(n*p*(1 - p)) + assert kurtosis(X) == 3 + (1 - 6*p*(1 - p))/(n*p*(1 - p)) + for k in range(n + 1): + assert P(Eq(X, k)) == binomial(n, k)*p**k*(1 - p)**(n - k) + +def test_binomial_quantile(): + X = Binomial('X', 50, S.Half) + assert quantile(X)(0.95) == S(31) + assert median(X) == FiniteSet(25) + + X = Binomial('X', 5, S.Half) + p = Symbol("p", positive=True) + assert quantile(X)(p) == Piecewise((nan, p > S.One), (S.Zero, p <= Rational(1, 32)),\ + (S.One, p <= Rational(3, 16)), (S(2), p <= S.Half), (S(3), p <= Rational(13, 16)),\ + (S(4), p <= Rational(31, 32)), (S(5), p <= S.One)) + assert median(X) == FiniteSet(2, 3) + + +def test_binomial_symbolic(): + n = 2 + p = symbols('p', positive=True) + X = Binomial('X', n, p) + t = Symbol('t') + + assert simplify(E(X)) == n*p == simplify(moment(X, 1)) + assert simplify(variance(X)) == n*p*(1 - p) == simplify(cmoment(X, 2)) + assert cancel(skewness(X) - (1 - 2*p)/sqrt(n*p*(1 - p))) == 0 + assert cancel((kurtosis(X)) - (3 + (1 - 6*p*(1 - p))/(n*p*(1 - p)))) == 0 + assert characteristic_function(X)(t) == p ** 2 * exp(2 * I * t) + 2 * p * (-p + 1) * exp(I * t) + (-p + 1) ** 2 + assert moment_generating_function(X)(t) == p ** 2 * exp(2 * t) + 2 * p * (-p + 1) * exp(t) + (-p + 1) ** 2 + + # Test ability to change success/failure winnings + H, T = symbols('H T') + Y = Binomial('Y', n, p, succ=H, fail=T) + assert simplify(E(Y) - (n*(H*p + T*(1 - p)))) == 0 + + # test symbolic dimensions + n = symbols('n') + B = Binomial('B', n, p) + raises(NotImplementedError, lambda: P(B > 2)) + assert density(B).dict == Density(BinomialDistribution(n, p, 1, 0)) + assert set(density(B).dict.subs(n, 4).doit().keys()) == \ + {S.Zero, S.One, S(2), S(3), S(4)} + assert set(density(B).dict.subs(n, 4).doit().values()) == \ + {(1 - p)**4, 4*p*(1 - p)**3, 6*p**2*(1 - p)**2, 4*p**3*(1 - p), p**4} + k = Dummy('k', integer=True) + assert E(B > 2).dummy_eq( + Sum(Piecewise((k*p**k*(1 - p)**(-k + n)*binomial(n, k), (k >= 0) + & (k <= n) & (k > 2)), (0, True)), (k, 0, n))) + +def test_beta_binomial(): + # verify parameters + raises(ValueError, lambda: BetaBinomial('b', .2, 1, 2)) + raises(ValueError, lambda: BetaBinomial('b', 2, -1, 2)) + raises(ValueError, lambda: BetaBinomial('b', 2, 1, -2)) + assert BetaBinomial('b', 2, 1, 1) + + # test numeric values + nvals = range(1,5) + alphavals = [Rational(1, 4), S.Half, Rational(3, 4), 1, 10] + betavals = [Rational(1, 4), S.Half, Rational(3, 4), 1, 10] + + for n in nvals: + for a in alphavals: + for b in betavals: + X = BetaBinomial('X', n, a, b) + assert E(X) == moment(X, 1) + assert variance(X) == cmoment(X, 2) + + # test symbolic + n, a, b = symbols('a b n') + assert BetaBinomial('x', n, a, b) + n = 2 # Because we're using for loops, can't do symbolic n + a, b = symbols('a b', positive=True) + X = BetaBinomial('X', n, a, b) + t = Symbol('t') + + assert E(X).expand() == moment(X, 1).expand() + assert variance(X).expand() == cmoment(X, 2).expand() + assert skewness(X) == smoment(X, 3) + assert characteristic_function(X)(t) == exp(2*I*t)*beta(a + 2, b)/beta(a, b) +\ + 2*exp(I*t)*beta(a + 1, b + 1)/beta(a, b) + beta(a, b + 2)/beta(a, b) + assert moment_generating_function(X)(t) == exp(2*t)*beta(a + 2, b)/beta(a, b) +\ + 2*exp(t)*beta(a + 1, b + 1)/beta(a, b) + beta(a, b + 2)/beta(a, b) + +def test_hypergeometric_numeric(): + for N in range(1, 5): + for m in range(0, N + 1): + for n in range(1, N + 1): + X = Hypergeometric('X', N, m, n) + N, m, n = map(sympify, (N, m, n)) + assert sum(density(X).values()) == 1 + assert E(X) == n * m / N + if N > 1: + assert variance(X) == n*(m/N)*(N - m)/N*(N - n)/(N - 1) + # Only test for skewness when defined + if N > 2 and 0 < m < N and n < N: + assert skewness(X) == simplify((N - 2*m)*sqrt(N - 1)*(N - 2*n) + / (sqrt(n*m*(N - m)*(N - n))*(N - 2))) + +def test_hypergeometric_symbolic(): + N, m, n = symbols('N, m, n') + H = Hypergeometric('H', N, m, n) + dens = density(H).dict + expec = E(H > 2) + assert dens == Density(HypergeometricDistribution(N, m, n)) + assert dens.subs(N, 5).doit() == Density(HypergeometricDistribution(5, m, n)) + assert set(dens.subs({N: 3, m: 2, n: 1}).doit().keys()) == {S.Zero, S.One} + assert set(dens.subs({N: 3, m: 2, n: 1}).doit().values()) == {Rational(1, 3), Rational(2, 3)} + k = Dummy('k', integer=True) + assert expec.dummy_eq( + Sum(Piecewise((k*binomial(m, k)*binomial(N - m, -k + n) + /binomial(N, n), k > 2), (0, True)), (k, 0, n))) + +def test_rademacher(): + X = Rademacher('X') + t = Symbol('t') + + assert E(X) == 0 + assert variance(X) == 1 + assert density(X)[-1] == S.Half + assert density(X)[1] == S.Half + assert characteristic_function(X)(t) == exp(I*t)/2 + exp(-I*t)/2 + assert moment_generating_function(X)(t) == exp(t) / 2 + exp(-t) / 2 + +def test_ideal_soliton(): + raises(ValueError, lambda : IdealSoliton('sol', -12)) + raises(ValueError, lambda : IdealSoliton('sol', 13.2)) + raises(ValueError, lambda : IdealSoliton('sol', 0)) + f = Function('f') + raises(ValueError, lambda : density(IdealSoliton('sol', 10)).pmf(f)) + + k = Symbol('k', integer=True, positive=True) + x = Symbol('x', integer=True, positive=True) + t = Symbol('t') + sol = IdealSoliton('sol', k) + assert density(sol).low == S.One + assert density(sol).high == k + assert density(sol).dict == Density(density(sol)) + assert density(sol).pmf(x) == Piecewise((1/k, Eq(x, 1)), (1/(x*(x - 1)), k >= x), (0, True)) + + k_vals = [5, 20, 50, 100, 1000] + for i in k_vals: + assert E(sol.subs(k, i)) == harmonic(i) == moment(sol.subs(k, i), 1) + assert variance(sol.subs(k, i)) == (i - 1) + harmonic(i) - harmonic(i)**2 == cmoment(sol.subs(k, i),2) + assert skewness(sol.subs(k, i)) == smoment(sol.subs(k, i), 3) + assert kurtosis(sol.subs(k, i)) == smoment(sol.subs(k, i), 4) + + assert exp(I*t)/10 + Sum(exp(I*t*x)/(x*x - x), (x, 2, k)).subs(k, 10).doit() == characteristic_function(sol.subs(k, 10))(t) + assert exp(t)/10 + Sum(exp(t*x)/(x*x - x), (x, 2, k)).subs(k, 10).doit() == moment_generating_function(sol.subs(k, 10))(t) + +def test_robust_soliton(): + raises(ValueError, lambda : RobustSoliton('robSol', -12, 0.1, 0.02)) + raises(ValueError, lambda : RobustSoliton('robSol', 13, 1.89, 0.1)) + raises(ValueError, lambda : RobustSoliton('robSol', 15, 0.6, -2.31)) + f = Function('f') + raises(ValueError, lambda : density(RobustSoliton('robSol', 15, 0.6, 0.1)).pmf(f)) + + k = Symbol('k', integer=True, positive=True) + delta = Symbol('delta', positive=True) + c = Symbol('c', positive=True) + robSol = RobustSoliton('robSol', k, delta, c) + assert density(robSol).low == 1 + assert density(robSol).high == k + + k_vals = [10, 20, 50] + delta_vals = [0.2, 0.4, 0.6] + c_vals = [0.01, 0.03, 0.05] + for x in k_vals: + for y in delta_vals: + for z in c_vals: + assert E(robSol.subs({k: x, delta: y, c: z})) == moment(robSol.subs({k: x, delta: y, c: z}), 1) + assert variance(robSol.subs({k: x, delta: y, c: z})) == cmoment(robSol.subs({k: x, delta: y, c: z}), 2) + assert skewness(robSol.subs({k: x, delta: y, c: z})) == smoment(robSol.subs({k: x, delta: y, c: z}), 3) + assert kurtosis(robSol.subs({k: x, delta: y, c: z})) == smoment(robSol.subs({k: x, delta: y, c: z}), 4) + +def test_FiniteRV(): + F = FiniteRV('F', {1: S.Half, 2: Rational(1, 4), 3: Rational(1, 4)}, check=True) + p = Symbol("p", positive=True) + + assert dict(density(F).items()) == {S.One: S.Half, S(2): Rational(1, 4), S(3): Rational(1, 4)} + assert P(F >= 2) == S.Half + assert quantile(F)(p) == Piecewise((nan, p > S.One), (S.One, p <= S.Half),\ + (S(2), p <= Rational(3, 4)),(S(3), True)) + + assert pspace(F).domain.as_boolean() == Or( + *[Eq(F.symbol, i) for i in [1, 2, 3]]) + + assert F.pspace.domain.set == FiniteSet(1, 2, 3) + raises(ValueError, lambda: FiniteRV('F', {1: S.Half, 2: S.Half, 3: S.Half}, check=True)) + raises(ValueError, lambda: FiniteRV('F', {1: S.Half, 2: Rational(-1, 2), 3: S.One}, check=True)) + raises(ValueError, lambda: FiniteRV('F', {1: S.One, 2: Rational(3, 2), 3: S.Zero,\ + 4: Rational(-1, 2), 5: Rational(-3, 4), 6: Rational(-1, 4)}, check=True)) + + # purposeful invalid pmf but it should not raise since check=False + # see test_drv_types.test_ContinuousRV for explanation + X = FiniteRV('X', {1: 1, 2: 2}) + assert E(X) == 5 + assert P(X <= 2) + P(X > 2) != 1 + +def test_density_call(): + from sympy.abc import p + x = Bernoulli('x', p) + d = density(x) + assert d(0) == 1 - p + assert d(S.Zero) == 1 - p + assert d(5) == 0 + + assert 0 in d + assert 5 not in d + assert d(S.Zero) == d[S.Zero] + + +def test_DieDistribution(): + from sympy.abc import x + X = DieDistribution(6) + assert X.pmf(S.Half) is S.Zero + assert X.pmf(x).subs({x: 1}).doit() == Rational(1, 6) + assert X.pmf(x).subs({x: 7}).doit() == 0 + assert X.pmf(x).subs({x: -1}).doit() == 0 + assert X.pmf(x).subs({x: Rational(1, 3)}).doit() == 0 + raises(ValueError, lambda: X.pmf(Matrix([0, 0]))) + raises(ValueError, lambda: X.pmf(x**2 - 1)) + +def test_FinitePSpace(): + X = Die('X', 6) + space = pspace(X) + assert space.density == DieDistribution(6) + +def test_symbolic_conditions(): + B = Bernoulli('B', Rational(1, 4)) + D = Die('D', 4) + b, n = symbols('b, n') + Y = P(Eq(B, b)) + Z = E(D > n) + assert Y == \ + Piecewise((Rational(1, 4), Eq(b, 1)), (0, True)) + \ + Piecewise((Rational(3, 4), Eq(b, 0)), (0, True)) + assert Z == \ + Piecewise((Rational(1, 4), n < 1), (0, True)) + Piecewise((S.Half, n < 2), (0, True)) + \ + Piecewise((Rational(3, 4), n < 3), (0, True)) + Piecewise((S.One, n < 4), (0, True)) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_joint_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_joint_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..057fc313dfbb31826b07fd1315205d22b86a7f96 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_joint_rv.py @@ -0,0 +1,436 @@ +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum +from sympy.core.numbers import (Rational, oo, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.combinatorial.factorials import (RisingFactorial, factorial) +from sympy.functions.elementary.complexes import polar_lift +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.bessel import besselk +from sympy.functions.special.gamma_functions import gamma +from sympy.matrices.dense import eye +from sympy.matrices.expressions.determinant import Determinant +from sympy.sets.fancysets import Range +from sympy.sets.sets import (Interval, ProductSet) +from sympy.simplify.simplify import simplify +from sympy.tensor.indexed import (Indexed, IndexedBase) +from sympy.core.numbers import comp +from sympy.integrals.integrals import integrate +from sympy.matrices import Matrix, MatrixSymbol +from sympy.matrices.expressions.matexpr import MatrixElement +from sympy.stats import density, median, marginal_distribution, Normal, Laplace, E, sample +from sympy.stats.joint_rv_types import (JointRV, MultivariateNormalDistribution, + JointDistributionHandmade, MultivariateT, NormalGamma, + GeneralizedMultivariateLogGammaOmega as GMVLGO, MultivariateBeta, + GeneralizedMultivariateLogGamma as GMVLG, MultivariateEwens, + Multinomial, NegativeMultinomial, MultivariateNormal, + MultivariateLaplace) +from sympy.testing.pytest import raises, XFAIL, skip, slow +from sympy.external import import_module + +from sympy.abc import x, y + + + +def test_Normal(): + m = Normal('A', [1, 2], [[1, 0], [0, 1]]) + A = MultivariateNormal('A', [1, 2], [[1, 0], [0, 1]]) + assert m == A + assert density(m)(1, 2) == 1/(2*pi) + assert m.pspace.distribution.set == ProductSet(S.Reals, S.Reals) + raises (ValueError, lambda:m[2]) + n = Normal('B', [1, 2, 3], [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + p = Normal('C', Matrix([1, 2]), Matrix([[1, 0], [0, 1]])) + assert density(m)(x, y) == density(p)(x, y) + assert marginal_distribution(n, 0, 1)(1, 2) == 1/(2*pi) + raises(ValueError, lambda: marginal_distribution(m)) + assert integrate(density(m)(x, y), (x, -oo, oo), (y, -oo, oo)).evalf() == 1.0 + N = Normal('N', [1, 2], [[x, 0], [0, y]]) + assert density(N)(0, 0) == exp(-((4*x + y)/(2*x*y)))/(2*pi*sqrt(x*y)) + + raises (ValueError, lambda: Normal('M', [1, 2], [[1, 1], [1, -1]])) + # symbolic + n = symbols('n', integer=True, positive=True) + mu = MatrixSymbol('mu', n, 1) + sigma = MatrixSymbol('sigma', n, n) + X = Normal('X', mu, sigma) + assert density(X) == MultivariateNormalDistribution(mu, sigma) + raises (NotImplementedError, lambda: median(m)) + # Below tests should work after issue #17267 is resolved + # assert E(X) == mu + # assert variance(X) == sigma + + # test symbolic multivariate normal densities + n = 3 + + Sg = MatrixSymbol('Sg', n, n) + mu = MatrixSymbol('mu', n, 1) + obs = MatrixSymbol('obs', n, 1) + + X = MultivariateNormal('X', mu, Sg) + density_X = density(X) + + eval_a = density_X(obs).subs({Sg: eye(3), + mu: Matrix([0, 0, 0]), obs: Matrix([0, 0, 0])}).doit() + eval_b = density_X(0, 0, 0).subs({Sg: eye(3), mu: Matrix([0, 0, 0])}).doit() + + assert eval_a == sqrt(2)/(4*pi**Rational(3/2)) + assert eval_b == sqrt(2)/(4*pi**Rational(3/2)) + + n = symbols('n', integer=True, positive=True) + + Sg = MatrixSymbol('Sg', n, n) + mu = MatrixSymbol('mu', n, 1) + obs = MatrixSymbol('obs', n, 1) + + X = MultivariateNormal('X', mu, Sg) + density_X_at_obs = density(X)(obs) + + expected_density = MatrixElement( + exp((S(1)/2) * (mu.T - obs.T) * Sg**(-1) * (-mu + obs)) / \ + sqrt((2*pi)**n * Determinant(Sg)), 0, 0) + + assert density_X_at_obs == expected_density + + +def test_MultivariateTDist(): + t1 = MultivariateT('T', [0, 0], [[1, 0], [0, 1]], 2) + assert(density(t1))(1, 1) == 1/(8*pi) + assert t1.pspace.distribution.set == ProductSet(S.Reals, S.Reals) + assert integrate(density(t1)(x, y), (x, -oo, oo), \ + (y, -oo, oo)).evalf() == 1.0 + raises(ValueError, lambda: MultivariateT('T', [1, 2], [[1, 1], [1, -1]], 1)) + t2 = MultivariateT('t2', [1, 2], [[x, 0], [0, y]], 1) + assert density(t2)(1, 2) == 1/(2*pi*sqrt(x*y)) + + +def test_multivariate_laplace(): + raises(ValueError, lambda: Laplace('T', [1, 2], [[1, 2], [2, 1]])) + L = Laplace('L', [1, 0], [[1, 0], [0, 1]]) + L2 = MultivariateLaplace('L2', [1, 0], [[1, 0], [0, 1]]) + assert density(L)(2, 3) == exp(2)*besselk(0, sqrt(39))/pi + L1 = Laplace('L1', [1, 2], [[x, 0], [0, y]]) + assert density(L1)(0, 1) == \ + exp(2/y)*besselk(0, sqrt((2 + 4/y + 1/x)/y))/(pi*sqrt(x*y)) + assert L.pspace.distribution.set == ProductSet(S.Reals, S.Reals) + assert L.pspace.distribution == L2.pspace.distribution + + +def test_NormalGamma(): + ng = NormalGamma('G', 1, 2, 3, 4) + assert density(ng)(1, 1) == 32*exp(-4)/sqrt(pi) + assert ng.pspace.distribution.set == ProductSet(S.Reals, Interval(0, oo)) + raises(ValueError, lambda:NormalGamma('G', 1, 2, 3, -1)) + assert marginal_distribution(ng, 0)(1) == \ + 3*sqrt(10)*gamma(Rational(7, 4))/(10*sqrt(pi)*gamma(Rational(5, 4))) + assert marginal_distribution(ng, y)(1) == exp(Rational(-1, 4))/128 + assert marginal_distribution(ng,[0,1])(x) == x**2*exp(-x/4)/128 + + +def test_GeneralizedMultivariateLogGammaDistribution(): + h = S.Half + omega = Matrix([[1, h, h, h], + [h, 1, h, h], + [h, h, 1, h], + [h, h, h, 1]]) + v, l, mu = (4, [1, 2, 3, 4], [1, 2, 3, 4]) + y_1, y_2, y_3, y_4 = symbols('y_1:5', real=True) + delta = symbols('d', positive=True) + G = GMVLGO('G', omega, v, l, mu) + Gd = GMVLG('Gd', delta, v, l, mu) + dend = ("d**4*Sum(4*24**(-n - 4)*(1 - d)**n*exp((n + 4)*(y_1 + 2*y_2 + 3*y_3 " + "+ 4*y_4) - exp(y_1) - exp(2*y_2)/2 - exp(3*y_3)/3 - exp(4*y_4)/4)/" + "(gamma(n + 1)*gamma(n + 4)**3), (n, 0, oo))") + assert str(density(Gd)(y_1, y_2, y_3, y_4)) == dend + den = ("5*2**(2/3)*5**(1/3)*Sum(4*24**(-n - 4)*(-2**(2/3)*5**(1/3)/4 + 1)**n*" + "exp((n + 4)*(y_1 + 2*y_2 + 3*y_3 + 4*y_4) - exp(y_1) - exp(2*y_2)/2 - " + "exp(3*y_3)/3 - exp(4*y_4)/4)/(gamma(n + 1)*gamma(n + 4)**3), (n, 0, oo))/64") + assert str(density(G)(y_1, y_2, y_3, y_4)) == den + marg = ("5*2**(2/3)*5**(1/3)*exp(4*y_1)*exp(-exp(y_1))*Integral(exp(-exp(4*G[3])" + "/4)*exp(16*G[3])*Integral(exp(-exp(3*G[2])/3)*exp(12*G[2])*Integral(exp(" + "-exp(2*G[1])/2)*exp(8*G[1])*Sum((-1/4)**n*(-4 + 2**(2/3)*5**(1/3" + "))**n*exp(n*y_1)*exp(2*n*G[1])*exp(3*n*G[2])*exp(4*n*G[3])/(24**n*gamma(n + 1)" + "*gamma(n + 4)**3), (n, 0, oo)), (G[1], -oo, oo)), (G[2], -oo, oo)), (G[3]" + ", -oo, oo))/5308416") + assert str(marginal_distribution(G, G[0])(y_1)) == marg + omega_f1 = Matrix([[1, h, h]]) + omega_f2 = Matrix([[1, h, h, h], + [h, 1, 2, h], + [h, h, 1, h], + [h, h, h, 1]]) + omega_f3 = Matrix([[6, h, h, h], + [h, 1, 2, h], + [h, h, 1, h], + [h, h, h, 1]]) + v_f = symbols("v_f", positive=False, real=True) + l_f = [1, 2, v_f, 4] + m_f = [v_f, 2, 3, 4] + omega_f4 = Matrix([[1, h, h, h, h], + [h, 1, h, h, h], + [h, h, 1, h, h], + [h, h, h, 1, h], + [h, h, h, h, 1]]) + l_f1 = [1, 2, 3, 4, 5] + omega_f5 = Matrix([[1]]) + mu_f5 = l_f5 = [1] + + raises(ValueError, lambda: GMVLGO('G', omega_f1, v, l, mu)) + raises(ValueError, lambda: GMVLGO('G', omega_f2, v, l, mu)) + raises(ValueError, lambda: GMVLGO('G', omega_f3, v, l, mu)) + raises(ValueError, lambda: GMVLGO('G', omega, v_f, l, mu)) + raises(ValueError, lambda: GMVLGO('G', omega, v, l_f, mu)) + raises(ValueError, lambda: GMVLGO('G', omega, v, l, m_f)) + raises(ValueError, lambda: GMVLGO('G', omega_f4, v, l, mu)) + raises(ValueError, lambda: GMVLGO('G', omega, v, l_f1, mu)) + raises(ValueError, lambda: GMVLGO('G', omega_f5, v, l_f5, mu_f5)) + raises(ValueError, lambda: GMVLG('G', Rational(3, 2), v, l, mu)) + + +def test_MultivariateBeta(): + a1, a2 = symbols('a1, a2', positive=True) + a1_f, a2_f = symbols('a1, a2', positive=False, real=True) + mb = MultivariateBeta('B', [a1, a2]) + mb_c = MultivariateBeta('C', a1, a2) + assert density(mb)(1, 2) == S(2)**(a2 - 1)*gamma(a1 + a2)/\ + (gamma(a1)*gamma(a2)) + assert marginal_distribution(mb_c, 0)(3) == S(3)**(a1 - 1)*gamma(a1 + a2)/\ + (a2*gamma(a1)*gamma(a2)) + raises(ValueError, lambda: MultivariateBeta('b1', [a1_f, a2])) + raises(ValueError, lambda: MultivariateBeta('b2', [a1, a2_f])) + raises(ValueError, lambda: MultivariateBeta('b3', [0, 0])) + raises(ValueError, lambda: MultivariateBeta('b4', [a1_f, a2_f])) + assert mb.pspace.distribution.set == ProductSet(Interval(0, 1), Interval(0, 1)) + + +def test_MultivariateEwens(): + n, theta, i = symbols('n theta i', positive=True) + + # tests for integer dimensions + theta_f = symbols('t_f', negative=True) + a = symbols('a_1:4', positive = True, integer = True) + ed = MultivariateEwens('E', 3, theta) + assert density(ed)(a[0], a[1], a[2]) == Piecewise((6*2**(-a[1])*3**(-a[2])* + theta**a[0]*theta**a[1]*theta**a[2]/ + (theta*(theta + 1)*(theta + 2)* + factorial(a[0])*factorial(a[1])* + factorial(a[2])), Eq(a[0] + 2*a[1] + + 3*a[2], 3)), (0, True)) + assert marginal_distribution(ed, ed[1])(a[1]) == Piecewise((6*2**(-a[1])* + theta**a[1]/((theta + 1)* + (theta + 2)*factorial(a[1])), + Eq(2*a[1] + 1, 3)), (0, True)) + raises(ValueError, lambda: MultivariateEwens('e1', 5, theta_f)) + assert ed.pspace.distribution.set == ProductSet(Range(0, 4, 1), + Range(0, 2, 1), Range(0, 2, 1)) + + # tests for symbolic dimensions + eds = MultivariateEwens('E', n, theta) + a = IndexedBase('a') + j, k = symbols('j, k') + den = Piecewise((factorial(n)*Product(theta**a[j]*(j + 1)**(-a[j])/ + factorial(a[j]), (j, 0, n - 1))/RisingFactorial(theta, n), + Eq(n, Sum((k + 1)*a[k], (k, 0, n - 1)))), (0, True)) + assert density(eds)(a).dummy_eq(den) + + +def test_Multinomial(): + n, x1, x2, x3, x4 = symbols('n, x1, x2, x3, x4', nonnegative=True, integer=True) + p1, p2, p3, p4 = symbols('p1, p2, p3, p4', positive=True) + p1_f, n_f = symbols('p1_f, n_f', negative=True) + M = Multinomial('M', n, [p1, p2, p3, p4]) + C = Multinomial('C', 3, p1, p2, p3) + f = factorial + assert density(M)(x1, x2, x3, x4) == Piecewise((p1**x1*p2**x2*p3**x3*p4**x4* + f(n)/(f(x1)*f(x2)*f(x3)*f(x4)), + Eq(n, x1 + x2 + x3 + x4)), (0, True)) + assert marginal_distribution(C, C[0])(x1).subs(x1, 1) ==\ + 3*p1*p2**2 +\ + 6*p1*p2*p3 +\ + 3*p1*p3**2 + raises(ValueError, lambda: Multinomial('b1', 5, [p1, p2, p3, p1_f])) + raises(ValueError, lambda: Multinomial('b2', n_f, [p1, p2, p3, p4])) + raises(ValueError, lambda: Multinomial('b3', n, 0.5, 0.4, 0.3, 0.1)) + + +def test_NegativeMultinomial(): + k0, x1, x2, x3, x4 = symbols('k0, x1, x2, x3, x4', nonnegative=True, integer=True) + p1, p2, p3, p4 = symbols('p1, p2, p3, p4', positive=True) + p1_f = symbols('p1_f', negative=True) + N = NegativeMultinomial('N', 4, [p1, p2, p3, p4]) + C = NegativeMultinomial('C', 4, 0.1, 0.2, 0.3) + g = gamma + f = factorial + assert simplify(density(N)(x1, x2, x3, x4) - + p1**x1*p2**x2*p3**x3*p4**x4*(-p1 - p2 - p3 - p4 + 1)**4*g(x1 + x2 + + x3 + x4 + 4)/(6*f(x1)*f(x2)*f(x3)*f(x4))) is S.Zero + assert comp(marginal_distribution(C, C[0])(1).evalf(), 0.33, .01) + raises(ValueError, lambda: NegativeMultinomial('b1', 5, [p1, p2, p3, p1_f])) + raises(ValueError, lambda: NegativeMultinomial('b2', k0, 0.5, 0.4, 0.3, 0.4)) + assert N.pspace.distribution.set == ProductSet(Range(0, oo, 1), + Range(0, oo, 1), Range(0, oo, 1), Range(0, oo, 1)) + + +@slow +def test_JointPSpace_marginal_distribution(): + T = MultivariateT('T', [0, 0], [[1, 0], [0, 1]], 2) + got = marginal_distribution(T, T[1])(x) + ans = sqrt(2)*(x**2/2 + 1)/(4*polar_lift(x**2/2 + 1)**(S(5)/2)) + assert got == ans, got + assert integrate(marginal_distribution(T, 1)(x), (x, -oo, oo)) == 1 + + t = MultivariateT('T', [0, 0, 0], [[1, 0, 0], [0, 1, 0], [0, 0, 1]], 3) + assert comp(marginal_distribution(t, 0)(1).evalf(), 0.2, .01) + + +def test_JointRV(): + x1, x2 = (Indexed('x', i) for i in (1, 2)) + pdf = exp(-x1**2/2 + x1 - x2**2/2 - S.Half)/(2*pi) + X = JointRV('x', pdf) + assert density(X)(1, 2) == exp(-2)/(2*pi) + assert isinstance(X.pspace.distribution, JointDistributionHandmade) + assert marginal_distribution(X, 0)(2) == sqrt(2)*exp(Rational(-1, 2))/(2*sqrt(pi)) + + +def test_expectation(): + m = Normal('A', [x, y], [[1, 0], [0, 1]]) + assert simplify(E(m[1])) == y + + +@XFAIL +def test_joint_vector_expectation(): + m = Normal('A', [x, y], [[1, 0], [0, 1]]) + assert E(m) == (x, y) + + +def test_sample_numpy(): + distribs_numpy = [ + MultivariateNormal("M", [3, 4], [[2, 1], [1, 2]]), + MultivariateBeta("B", [0.4, 5, 15, 50, 203]), + Multinomial("N", 50, [0.3, 0.2, 0.1, 0.25, 0.15]) + ] + size = 3 + numpy = import_module('numpy') + if not numpy: + skip('Numpy is not installed. Abort tests for _sample_numpy.') + else: + for X in distribs_numpy: + samps = sample(X, size=size, library='numpy') + for sam in samps: + assert tuple(sam) in X.pspace.distribution.set + N_c = NegativeMultinomial('N', 3, 0.1, 0.1, 0.1) + raises(NotImplementedError, lambda: sample(N_c, library='numpy')) + + +def test_sample_scipy(): + distribs_scipy = [ + MultivariateNormal("M", [0, 0], [[0.1, 0.025], [0.025, 0.1]]), + MultivariateBeta("B", [0.4, 5, 15]), + Multinomial("N", 8, [0.3, 0.2, 0.1, 0.4]) + ] + + size = 3 + scipy = import_module('scipy') + if not scipy: + skip('Scipy not installed. Abort tests for _sample_scipy.') + else: + for X in distribs_scipy: + samps = sample(X, size=size) + samps2 = sample(X, size=(2, 2)) + for sam in samps: + assert tuple(sam) in X.pspace.distribution.set + for i in range(2): + for j in range(2): + assert tuple(samps2[i][j]) in X.pspace.distribution.set + N_c = NegativeMultinomial('N', 3, 0.1, 0.1, 0.1) + raises(NotImplementedError, lambda: sample(N_c)) + + +def test_sample_pymc(): + distribs_pymc = [ + MultivariateNormal("M", [5, 2], [[1, 0], [0, 1]]), + MultivariateBeta("B", [0.4, 5, 15]), + Multinomial("N", 4, [0.3, 0.2, 0.1, 0.4]) + ] + size = 3 + pymc = import_module('pymc') + if not pymc: + skip('PyMC is not installed. Abort tests for _sample_pymc.') + else: + for X in distribs_pymc: + samps = sample(X, size=size, library='pymc') + for sam in samps: + assert tuple(sam.flatten()) in X.pspace.distribution.set + N_c = NegativeMultinomial('N', 3, 0.1, 0.1, 0.1) + raises(NotImplementedError, lambda: sample(N_c, library='pymc')) + + +def test_sample_seed(): + x1, x2 = (Indexed('x', i) for i in (1, 2)) + pdf = exp(-x1**2/2 + x1 - x2**2/2 - S.Half)/(2*pi) + X = JointRV('x', pdf) + + libraries = ['scipy', 'numpy', 'pymc'] + for lib in libraries: + try: + imported_lib = import_module(lib) + if imported_lib: + s0, s1, s2 = [], [], [] + s0 = sample(X, size=10, library=lib, seed=0) + s1 = sample(X, size=10, library=lib, seed=0) + s2 = sample(X, size=10, library=lib, seed=1) + assert all(s0 == s1) + assert all(s1 != s2) + except NotImplementedError: + continue + +# +# XXX: This fails for pymc. Previously the test appeared to pass but that is +# just because the library argument was not passed so the test always used +# scipy. +# +def test_issue_21057(): + m = Normal("x", [0, 0], [[0, 0], [0, 0]]) + n = MultivariateNormal("x", [0, 0], [[0, 0], [0, 0]]) + p = Normal("x", [0, 0], [[0, 0], [0, 1]]) + assert m == n + libraries = ('scipy', 'numpy') # , 'pymc') # <-- pymc fails + for library in libraries: + try: + imported_lib = import_module(library) + if imported_lib: + s1 = sample(m, size=8, library=library) + s2 = sample(n, size=8, library=library) + s3 = sample(p, size=8, library=library) + assert tuple(s1.flatten()) == tuple(s2.flatten()) + for s in s3: + assert tuple(s.flatten()) in p.pspace.distribution.set + except NotImplementedError: + continue + + +# +# When this passes the pymc part can be uncommented in test_issue_21057 above +# and this can be deleted. +# +@XFAIL +def test_issue_21057_pymc(): + m = Normal("x", [0, 0], [[0, 0], [0, 0]]) + n = MultivariateNormal("x", [0, 0], [[0, 0], [0, 0]]) + p = Normal("x", [0, 0], [[0, 0], [0, 1]]) + assert m == n + libraries = ('pymc',) + for library in libraries: + try: + imported_lib = import_module(library) + if imported_lib: + s1 = sample(m, size=8, library=library) + s2 = sample(n, size=8, library=library) + s3 = sample(p, size=8, library=library) + assert tuple(s1.flatten()) == tuple(s2.flatten()) + for s in s3: + assert tuple(s.flatten()) in p.pspace.distribution.set + except NotImplementedError: + continue diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_matrix_distributions.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_matrix_distributions.py new file mode 100644 index 0000000000000000000000000000000000000000..a2a2dcdd853793d9f77e1a88adf63158ed68e3ba --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_matrix_distributions.py @@ -0,0 +1,186 @@ +from sympy.concrete.products import Product +from sympy.core.numbers import pi +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.gamma_functions import gamma +from sympy.matrices import Determinant, Matrix, Trace, MatrixSymbol, MatrixSet +from sympy.stats import density, sample +from sympy.stats.matrix_distributions import (MatrixGammaDistribution, + MatrixGamma, MatrixPSpace, Wishart, MatrixNormal, MatrixStudentT) +from sympy.testing.pytest import raises, skip +from sympy.external import import_module + + +def test_MatrixPSpace(): + M = MatrixGammaDistribution(1, 2, [[2, 1], [1, 2]]) + MP = MatrixPSpace('M', M, 2, 2) + assert MP.distribution == M + raises(ValueError, lambda: MatrixPSpace('M', M, 1.2, 2)) + +def test_MatrixGamma(): + M = MatrixGamma('M', 1, 2, [[1, 0], [0, 1]]) + assert M.pspace.distribution.set == MatrixSet(2, 2, S.Reals) + assert isinstance(density(M), MatrixGammaDistribution) + X = MatrixSymbol('X', 2, 2) + num = exp(Trace(Matrix([[-S(1)/2, 0], [0, -S(1)/2]])*X)) + assert density(M)(X).doit() == num/(4*pi*sqrt(Determinant(X))) + assert density(M)([[2, 1], [1, 2]]).doit() == sqrt(3)*exp(-2)/(12*pi) + X = MatrixSymbol('X', 1, 2) + Y = MatrixSymbol('Y', 1, 2) + assert density(M)([X, Y]).doit() == exp(-X[0, 0]/2 - Y[0, 1]/2)/(4*pi*sqrt( + X[0, 0]*Y[0, 1] - X[0, 1]*Y[0, 0])) + # symbolic + a, b = symbols('a b', positive=True) + d = symbols('d', positive=True, integer=True) + Y = MatrixSymbol('Y', d, d) + Z = MatrixSymbol('Z', 2, 2) + SM = MatrixSymbol('SM', d, d) + M2 = MatrixGamma('M2', a, b, SM) + M3 = MatrixGamma('M3', 2, 3, [[2, 1], [1, 2]]) + k = Dummy('k') + exprd = pi**(-d*(d - 1)/4)*b**(-a*d)*exp(Trace((-1/b)*SM**(-1)*Y) + )*Determinant(SM)**(-a)*Determinant(Y)**(a - d/2 - S(1)/2)/Product( + gamma(-k/2 + a + S(1)/2), (k, 1, d)) + assert density(M2)(Y).dummy_eq(exprd) + raises(NotImplementedError, lambda: density(M3 + M)(Z)) + raises(ValueError, lambda: density(M)(1)) + raises(ValueError, lambda: MatrixGamma('M', -1, 2, [[1, 0], [0, 1]])) + raises(ValueError, lambda: MatrixGamma('M', -1, -2, [[1, 0], [0, 1]])) + raises(ValueError, lambda: MatrixGamma('M', -1, 2, [[1, 0], [2, 1]])) + raises(ValueError, lambda: MatrixGamma('M', -1, 2, [[1, 0], [0]])) + +def test_Wishart(): + W = Wishart('W', 5, [[1, 0], [0, 1]]) + assert W.pspace.distribution.set == MatrixSet(2, 2, S.Reals) + X = MatrixSymbol('X', 2, 2) + term1 = exp(Trace(Matrix([[-S(1)/2, 0], [0, -S(1)/2]])*X)) + assert density(W)(X).doit() == term1 * Determinant(X)/(24*pi) + assert density(W)([[2, 1], [1, 2]]).doit() == exp(-2)/(8*pi) + n = symbols('n', positive=True) + d = symbols('d', positive=True, integer=True) + Y = MatrixSymbol('Y', d, d) + SM = MatrixSymbol('SM', d, d) + W = Wishart('W', n, SM) + k = Dummy('k') + exprd = 2**(-d*n/2)*pi**(-d*(d - 1)/4)*exp(Trace(-(S(1)/2)*SM**(-1)*Y) + )*Determinant(SM)**(-n/2)*Determinant(Y)**( + -d/2 + n/2 - S(1)/2)/Product(gamma(-k/2 + n/2 + S(1)/2), (k, 1, d)) + assert density(W)(Y).dummy_eq(exprd) + raises(ValueError, lambda: density(W)(1)) + raises(ValueError, lambda: Wishart('W', -1, [[1, 0], [0, 1]])) + raises(ValueError, lambda: Wishart('W', -1, [[1, 0], [2, 1]])) + raises(ValueError, lambda: Wishart('W', 2, [[1, 0], [0]])) + +def test_MatrixNormal(): + M = MatrixNormal('M', [[5, 6]], [4], [[2, 1], [1, 2]]) + assert M.pspace.distribution.set == MatrixSet(1, 2, S.Reals) + X = MatrixSymbol('X', 1, 2) + term1 = exp(-Trace(Matrix([[ S(2)/3, -S(1)/3], [-S(1)/3, S(2)/3]])*( + Matrix([[-5], [-6]]) + X.T)*Matrix([[S(1)/4]])*(Matrix([[-5, -6]]) + X))/2) + assert density(M)(X).doit() == (sqrt(3)) * term1/(24*pi) + assert density(M)([[7, 8]]).doit() == sqrt(3)*exp(-S(1)/3)/(24*pi) + d, n = symbols('d n', positive=True, integer=True) + SM2 = MatrixSymbol('SM2', d, d) + SM1 = MatrixSymbol('SM1', n, n) + LM = MatrixSymbol('LM', n, d) + Y = MatrixSymbol('Y', n, d) + M = MatrixNormal('M', LM, SM1, SM2) + exprd = (2*pi)**(-d*n/2)*exp(-Trace(SM2**(-1)*(-LM.T + Y.T)*SM1**(-1)*(-LM + Y) + )/2)*Determinant(SM1)**(-d/2)*Determinant(SM2)**(-n/2) + assert density(M)(Y).doit() == exprd + raises(ValueError, lambda: density(M)(1)) + raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [0, 1]], [[1, 0], [2, 1]])) + raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [2, 1]], [[1, 0], [0, 1]])) + raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [0, 1]], [[1, 0], [0, 1]])) + raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [2]], [[1, 0], [0, 1]])) + raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [2, 1]], [[1, 0], [0]])) + raises(ValueError, lambda: MatrixNormal('M', [[1, 2]], [[1, 0], [0, 1]], [[1, 0]])) + raises(ValueError, lambda: MatrixNormal('M', [[1, 2]], [1], [[1, 0]])) + +def test_MatrixStudentT(): + M = MatrixStudentT('M', 2, [[5, 6]], [[2, 1], [1, 2]], [4]) + assert M.pspace.distribution.set == MatrixSet(1, 2, S.Reals) + X = MatrixSymbol('X', 1, 2) + D = pi ** (-1.0) * Determinant(Matrix([[4]])) ** (-1.0) * Determinant(Matrix([[2, 1], [1, 2]])) \ + ** (-0.5) / Determinant(Matrix([[S(1) / 4]]) * (Matrix([[-5, -6]]) + X) + * Matrix([[S(2) / 3, -S(1) / 3], [-S(1) / 3, S(2) / 3]]) * ( + Matrix([[-5], [-6]]) + X.T) + Matrix([[1]])) ** 2 + assert density(M)(X) == D + + v = symbols('v', positive=True) + n, p = 1, 2 + Omega = MatrixSymbol('Omega', p, p) + Sigma = MatrixSymbol('Sigma', n, n) + Location = MatrixSymbol('Location', n, p) + Y = MatrixSymbol('Y', n, p) + M = MatrixStudentT('M', v, Location, Omega, Sigma) + + exprd = gamma(v/2 + 1)*Determinant(Matrix([[1]]) + Sigma**(-1)*(-Location + Y)*Omega**(-1)*(-Location.T + Y.T))**(-v/2 - 1) / \ + (pi*gamma(v/2)*sqrt(Determinant(Omega))*Determinant(Sigma)) + + assert density(M)(Y) == exprd + raises(ValueError, lambda: density(M)(1)) + raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [0, 1]], [[1, 0], [2, 1]])) + raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [2, 1]], [[1, 0], [0, 1]])) + raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [0, 1]], [[1, 0], [0, 1]])) + raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [2]], [[1, 0], [0, 1]])) + raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [2, 1]], [[1], [2]])) + raises(ValueError, lambda: MatrixStudentT('M', 1, [[1, 2]], [[1, 0], [0, 1]], [[1, 0]])) + raises(ValueError, lambda: MatrixStudentT('M', 1, [[1, 2]], [1], [[1, 0]])) + raises(ValueError, lambda: MatrixStudentT('M', -1, [1, 2], [[1, 0], [0, 1]], [4])) + +def test_sample_scipy(): + distribs_scipy = [ + MatrixNormal('M', [[5, 6]], [4], [[2, 1], [1, 2]]), + Wishart('W', 5, [[1, 0], [0, 1]]) + ] + + size = 5 + scipy = import_module('scipy') + if not scipy: + skip('Scipy not installed. Abort tests for _sample_scipy.') + else: + for X in distribs_scipy: + samps = sample(X, size=size) + for sam in samps: + assert Matrix(sam) in X.pspace.distribution.set + M = MatrixGamma('M', 1, 2, [[1, 0], [0, 1]]) + raises(NotImplementedError, lambda: sample(M, size=3)) + +def test_sample_pymc(): + distribs_pymc = [ + MatrixNormal('M', [[5, 6], [3, 4]], [[1, 0], [0, 1]], [[2, 1], [1, 2]]), + Wishart('W', 7, [[2, 1], [1, 2]]) + ] + size = 3 + pymc = import_module('pymc') + if not pymc: + skip('PyMC is not installed. Abort tests for _sample_pymc.') + else: + for X in distribs_pymc: + samps = sample(X, size=size, library='pymc') + for sam in samps: + assert Matrix(sam) in X.pspace.distribution.set + M = MatrixGamma('M', 1, 2, [[1, 0], [0, 1]]) + raises(NotImplementedError, lambda: sample(M, size=3)) + +def test_sample_seed(): + X = MatrixNormal('M', [[5, 6], [3, 4]], [[1, 0], [0, 1]], [[2, 1], [1, 2]]) + + libraries = ['scipy', 'numpy', 'pymc'] + for lib in libraries: + try: + imported_lib = import_module(lib) + if imported_lib: + s0, s1, s2 = [], [], [] + s0 = sample(X, size=10, library=lib, seed=0) + s1 = sample(X, size=10, library=lib, seed=0) + s2 = sample(X, size=10, library=lib, seed=1) + for i in range(10): + assert (s0[i] == s1[i]).all() + assert (s1[i] != s2[i]).all() + + except NotImplementedError: + continue diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_mix.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_mix.py new file mode 100644 index 0000000000000000000000000000000000000000..4334d9b144a5ddaad938f195f0276e0e8993aa35 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_mix.py @@ -0,0 +1,82 @@ +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.mul import Mul +from sympy.core.numbers import (Integer, oo, pi) +from sympy.core.power import Pow +from sympy.core.relational import (Eq, Ne) +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.delta_functions import DiracDelta +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import Integral +from sympy.simplify.simplify import simplify +from sympy.tensor.indexed import (Indexed, IndexedBase) +from sympy.functions.elementary.piecewise import ExprCondPair +from sympy.stats import (Poisson, Beta, Exponential, P, + Multinomial, MultivariateBeta) +from sympy.stats.crv_types import Normal +from sympy.stats.drv_types import PoissonDistribution +from sympy.stats.compound_rv import CompoundPSpace, CompoundDistribution +from sympy.stats.joint_rv import MarginalDistribution +from sympy.stats.rv import pspace, density +from sympy.testing.pytest import ignore_warnings + +def test_density(): + x = Symbol('x') + l = Symbol('l', positive=True) + rate = Beta(l, 2, 3) + X = Poisson(x, rate) + assert isinstance(pspace(X), CompoundPSpace) + assert density(X, Eq(rate, rate.symbol)) == PoissonDistribution(l) + N1 = Normal('N1', 0, 1) + N2 = Normal('N2', N1, 2) + assert density(N2)(0).doit() == sqrt(10)/(10*sqrt(pi)) + assert simplify(density(N2, Eq(N1, 1))(x)) == \ + sqrt(2)*exp(-(x - 1)**2/8)/(4*sqrt(pi)) + assert simplify(density(N2)(x)) == sqrt(10)*exp(-x**2/10)/(10*sqrt(pi)) + +def test_MarginalDistribution(): + a1, p1, p2 = symbols('a1 p1 p2', positive=True) + C = Multinomial('C', 2, p1, p2) + B = MultivariateBeta('B', a1, C[0]) + MGR = MarginalDistribution(B, (C[0],)) + mgrc = Mul(Symbol('B'), Piecewise(ExprCondPair(Mul(Integer(2), + Pow(Symbol('p1', positive=True), Indexed(IndexedBase(Symbol('C')), + Integer(0))), Pow(Symbol('p2', positive=True), + Indexed(IndexedBase(Symbol('C')), Integer(1))), + Pow(factorial(Indexed(IndexedBase(Symbol('C')), Integer(0))), Integer(-1)), + Pow(factorial(Indexed(IndexedBase(Symbol('C')), Integer(1))), Integer(-1))), + Eq(Add(Indexed(IndexedBase(Symbol('C')), Integer(0)), + Indexed(IndexedBase(Symbol('C')), Integer(1))), Integer(2))), + ExprCondPair(Integer(0), True)), Pow(gamma(Symbol('a1', positive=True)), + Integer(-1)), gamma(Add(Symbol('a1', positive=True), + Indexed(IndexedBase(Symbol('C')), Integer(0)))), + Pow(gamma(Indexed(IndexedBase(Symbol('C')), Integer(0))), Integer(-1)), + Pow(Indexed(IndexedBase(Symbol('B')), Integer(0)), + Add(Symbol('a1', positive=True), Integer(-1))), + Pow(Indexed(IndexedBase(Symbol('B')), Integer(1)), + Add(Indexed(IndexedBase(Symbol('C')), Integer(0)), Integer(-1)))) + assert MGR(C) == mgrc + +def test_compound_distribution(): + Y = Poisson('Y', 1) + Z = Poisson('Z', Y) + assert isinstance(pspace(Z), CompoundPSpace) + assert isinstance(pspace(Z).distribution, CompoundDistribution) + assert Z.pspace.distribution.pdf(1).doit() == exp(-2)*exp(exp(-1)) + +def test_mix_expression(): + Y, E = Poisson('Y', 1), Exponential('E', 1) + k = Dummy('k') + expr1 = Integral(Sum(exp(-1)*Integral(exp(-k)*DiracDelta(k - 2), (k, 0, oo) + )/factorial(k), (k, 0, oo)), (k, -oo, 0)) + expr2 = Integral(Sum(exp(-1)*Integral(exp(-k)*DiracDelta(k - 2), (k, 0, oo) + )/factorial(k), (k, 0, oo)), (k, 0, oo)) + assert P(Eq(Y + E, 1)) == 0 + assert P(Ne(Y + E, 2)) == 1 + with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed + assert P(E + Y < 2, evaluate=False).rewrite(Integral).dummy_eq(expr1) + assert P(E + Y > 2, evaluate=False).rewrite(Integral).dummy_eq(expr2) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_random_matrix.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_random_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..ba570a16bc42620d53bce19be71e7d125965ede1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_random_matrix.py @@ -0,0 +1,135 @@ +from sympy.concrete.products import Product +from sympy.core.function import Lambda +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.integrals.integrals import Integral +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.trace import Trace +from sympy.tensor.indexed import IndexedBase +from sympy.stats import (GaussianUnitaryEnsemble as GUE, density, + GaussianOrthogonalEnsemble as GOE, + GaussianSymplecticEnsemble as GSE, + joint_eigen_distribution, + CircularUnitaryEnsemble as CUE, + CircularOrthogonalEnsemble as COE, + CircularSymplecticEnsemble as CSE, + JointEigenDistribution, + level_spacing_distribution, + Normal, Beta) +from sympy.stats.joint_rv_types import JointDistributionHandmade +from sympy.stats.rv import RandomMatrixSymbol +from sympy.stats.random_matrix_models import GaussianEnsemble, RandomMatrixPSpace +from sympy.testing.pytest import raises + +def test_GaussianEnsemble(): + G = GaussianEnsemble('G', 3) + assert density(G) == G.pspace.model + raises(ValueError, lambda: GaussianEnsemble('G', 3.5)) + +def test_GaussianUnitaryEnsemble(): + H = RandomMatrixSymbol('H', 3, 3) + G = GUE('U', 3) + assert density(G)(H) == sqrt(2)*exp(-3*Trace(H**2)/2)/(4*pi**Rational(9, 2)) + i, j = (Dummy('i', integer=True, positive=True), + Dummy('j', integer=True, positive=True)) + l = IndexedBase('l') + assert joint_eigen_distribution(G).dummy_eq( + Lambda((l[1], l[2], l[3]), + 27*sqrt(6)*exp(-3*(l[1]**2)/2 - 3*(l[2]**2)/2 - 3*(l[3]**2)/2)* + Product(Abs(l[i] - l[j])**2, (j, i + 1, 3), (i, 1, 2))/(16*pi**Rational(3, 2)))) + s = Dummy('s') + assert level_spacing_distribution(G).dummy_eq(Lambda(s, 32*s**2*exp(-4*s**2/pi)/pi**2)) + + +def test_GaussianOrthogonalEnsemble(): + H = RandomMatrixSymbol('H', 3, 3) + _H = MatrixSymbol('_H', 3, 3) + G = GOE('O', 3) + assert density(G)(H) == exp(-3*Trace(H**2)/4)/Integral(exp(-3*Trace(_H**2)/4), _H) + i, j = (Dummy('i', integer=True, positive=True), + Dummy('j', integer=True, positive=True)) + l = IndexedBase('l') + assert joint_eigen_distribution(G).dummy_eq( + Lambda((l[1], l[2], l[3]), + 9*sqrt(2)*exp(-3*l[1]**2/2 - 3*l[2]**2/2 - 3*l[3]**2/2)* + Product(Abs(l[i] - l[j]), (j, i + 1, 3), (i, 1, 2))/(32*pi))) + s = Dummy('s') + assert level_spacing_distribution(G).dummy_eq(Lambda(s, s*pi*exp(-s**2*pi/4)/2)) + +def test_GaussianSymplecticEnsemble(): + H = RandomMatrixSymbol('H', 3, 3) + _H = MatrixSymbol('_H', 3, 3) + G = GSE('O', 3) + assert density(G)(H) == exp(-3*Trace(H**2))/Integral(exp(-3*Trace(_H**2)), _H) + i, j = (Dummy('i', integer=True, positive=True), + Dummy('j', integer=True, positive=True)) + l = IndexedBase('l') + assert joint_eigen_distribution(G).dummy_eq( + Lambda((l[1], l[2], l[3]), + 162*sqrt(3)*exp(-3*l[1]**2/2 - 3*l[2]**2/2 - 3*l[3]**2/2)* + Product(Abs(l[i] - l[j])**4, (j, i + 1, 3), (i, 1, 2))/(5*pi**Rational(3, 2)))) + s = Dummy('s') + assert level_spacing_distribution(G).dummy_eq(Lambda(s, S(262144)*s**4*exp(-64*s**2/(9*pi))/(729*pi**3))) + +def test_CircularUnitaryEnsemble(): + CU = CUE('U', 3) + j, k = (Dummy('j', integer=True, positive=True), + Dummy('k', integer=True, positive=True)) + t = IndexedBase('t') + assert joint_eigen_distribution(CU).dummy_eq( + Lambda((t[1], t[2], t[3]), + Product(Abs(exp(I*t[j]) - exp(I*t[k]))**2, + (j, k + 1, 3), (k, 1, 2))/(48*pi**3)) + ) + +def test_CircularOrthogonalEnsemble(): + CO = COE('U', 3) + j, k = (Dummy('j', integer=True, positive=True), + Dummy('k', integer=True, positive=True)) + t = IndexedBase('t') + assert joint_eigen_distribution(CO).dummy_eq( + Lambda((t[1], t[2], t[3]), + Product(Abs(exp(I*t[j]) - exp(I*t[k])), + (j, k + 1, 3), (k, 1, 2))/(48*pi**2)) + ) + +def test_CircularSymplecticEnsemble(): + CS = CSE('U', 3) + j, k = (Dummy('j', integer=True, positive=True), + Dummy('k', integer=True, positive=True)) + t = IndexedBase('t') + assert joint_eigen_distribution(CS).dummy_eq( + Lambda((t[1], t[2], t[3]), + Product(Abs(exp(I*t[j]) - exp(I*t[k]))**4, + (j, k + 1, 3), (k, 1, 2))/(720*pi**3)) + ) + +def test_JointEigenDistribution(): + A = Matrix([[Normal('A00', 0, 1), Normal('A01', 1, 1)], + [Beta('A10', 1, 1), Beta('A11', 1, 1)]]) + assert JointEigenDistribution(A) == \ + JointDistributionHandmade(-sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2)/2 + + A[0, 0]/2 + A[1, 1]/2, sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2)/2 + A[0, 0]/2 + A[1, 1]/2) + raises(ValueError, lambda: JointEigenDistribution(Matrix([[1, 0], [2, 1]]))) + +def test_issue_19841(): + G1 = GUE('U', 2) + G2 = G1.xreplace({2: 2}) + assert G1.args == G2.args + + X = MatrixSymbol('X', 2, 2) + G = GSE('U', 2) + h_pspace = RandomMatrixPSpace('P', model=density(G)) + H = RandomMatrixSymbol('H', 2, 2, pspace=h_pspace) + H2 = RandomMatrixSymbol('H', 2, 2, pspace=None) + assert H.doit() == H + + assert (2*H).xreplace({H: X}) == 2*X + assert (2*H).xreplace({H2: X}) == 2*H + assert (2*H2).xreplace({H: X}) == 2*H2 + assert (2*H2).xreplace({H2: X}) == 2*X diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_rv.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_rv.py new file mode 100644 index 0000000000000000000000000000000000000000..185756300556f2fe70b76c402113ec2bb2501ef4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_rv.py @@ -0,0 +1,441 @@ +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.function import Lambda +from sympy.core.numbers import (Rational, nan, oo, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.combinatorial.factorials import (FallingFactorial, binomial) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.delta_functions import DiracDelta +from sympy.integrals.integrals import integrate +from sympy.logic.boolalg import (And, Or) +from sympy.matrices.dense import Matrix +from sympy.sets.sets import Interval +from sympy.tensor.indexed import Indexed +from sympy.stats import (Die, Normal, Exponential, FiniteRV, P, E, H, variance, + density, given, independent, dependent, where, pspace, GaussianUnitaryEnsemble, + random_symbols, sample, Geometric, factorial_moment, Binomial, Hypergeometric, + DiscreteUniform, Poisson, characteristic_function, moment_generating_function, + BernoulliProcess, Variance, Expectation, Probability, Covariance, covariance, cmoment, + moment, median) +from sympy.stats.rv import (IndependentProductPSpace, rs_swap, Density, NamedArgsMixin, + RandomSymbol, sample_iter, PSpace, is_random, RandomIndexedSymbol, RandomMatrixSymbol) +from sympy.testing.pytest import raises, skip, XFAIL, warns_deprecated_sympy +from sympy.external import import_module +from sympy.core.numbers import comp +from sympy.stats.frv_types import BernoulliDistribution +from sympy.core.symbol import Dummy +from sympy.functions.elementary.piecewise import Piecewise + +def test_where(): + X, Y = Die('X'), Die('Y') + Z = Normal('Z', 0, 1) + + assert where(Z**2 <= 1).set == Interval(-1, 1) + assert where(Z**2 <= 1).as_boolean() == Interval(-1, 1).as_relational(Z.symbol) + assert where(And(X > Y, Y > 4)).as_boolean() == And( + Eq(X.symbol, 6), Eq(Y.symbol, 5)) + + assert len(where(X < 3).set) == 2 + assert 1 in where(X < 3).set + + X, Y = Normal('X', 0, 1), Normal('Y', 0, 1) + assert where(And(X**2 <= 1, X >= 0)).set == Interval(0, 1) + XX = given(X, And(X**2 <= 1, X >= 0)) + assert XX.pspace.domain.set == Interval(0, 1) + assert XX.pspace.domain.as_boolean() == \ + And(0 <= X.symbol, X.symbol**2 <= 1, -oo < X.symbol, X.symbol < oo) + + with raises(TypeError): + XX = given(X, X + 3) + + +def test_random_symbols(): + X, Y = Normal('X', 0, 1), Normal('Y', 0, 1) + + assert set(random_symbols(2*X + 1)) == {X} + assert set(random_symbols(2*X + Y)) == {X, Y} + assert set(random_symbols(2*X + Y.symbol)) == {X} + assert set(random_symbols(2)) == set() + + +def test_characteristic_function(): + # Imports I from sympy + from sympy.core.numbers import I + X = Normal('X',0,1) + Y = DiscreteUniform('Y', [1,2,7]) + Z = Poisson('Z', 2) + t = symbols('_t') + P = Lambda(t, exp(-t**2/2)) + Q = Lambda(t, exp(7*t*I)/3 + exp(2*t*I)/3 + exp(t*I)/3) + R = Lambda(t, exp(2 * exp(t*I) - 2)) + + + assert characteristic_function(X).dummy_eq(P) + assert characteristic_function(Y).dummy_eq(Q) + assert characteristic_function(Z).dummy_eq(R) + + +def test_moment_generating_function(): + + X = Normal('X',0,1) + Y = DiscreteUniform('Y', [1,2,7]) + Z = Poisson('Z', 2) + t = symbols('_t') + P = Lambda(t, exp(t**2/2)) + Q = Lambda(t, (exp(7*t)/3 + exp(2*t)/3 + exp(t)/3)) + R = Lambda(t, exp(2 * exp(t) - 2)) + + + assert moment_generating_function(X).dummy_eq(P) + assert moment_generating_function(Y).dummy_eq(Q) + assert moment_generating_function(Z).dummy_eq(R) + +def test_sample_iter(): + + X = Normal('X',0,1) + Y = DiscreteUniform('Y', [1, 2, 7]) + Z = Poisson('Z', 2) + + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests') + expr = X**2 + 3 + iterator = sample_iter(expr) + + expr2 = Y**2 + 5*Y + 4 + iterator2 = sample_iter(expr2) + + expr3 = Z**3 + 4 + iterator3 = sample_iter(expr3) + + def is_iterator(obj): + if ( + hasattr(obj, '__iter__') and + (hasattr(obj, 'next') or + hasattr(obj, '__next__')) and + callable(obj.__iter__) and + obj.__iter__() is obj + ): + return True + else: + return False + assert is_iterator(iterator) + assert is_iterator(iterator2) + assert is_iterator(iterator3) + +def test_pspace(): + X, Y = Normal('X', 0, 1), Normal('Y', 0, 1) + x = Symbol('x') + + raises(ValueError, lambda: pspace(5 + 3)) + raises(ValueError, lambda: pspace(x < 1)) + assert pspace(X) == X.pspace + assert pspace(2*X + 1) == X.pspace + assert pspace(2*X + Y) == IndependentProductPSpace(Y.pspace, X.pspace) + +def test_rs_swap(): + X = Normal('x', 0, 1) + Y = Exponential('y', 1) + + XX = Normal('x', 0, 2) + YY = Normal('y', 0, 3) + + expr = 2*X + Y + assert expr.subs(rs_swap((X, Y), (YY, XX))) == 2*XX + YY + + +def test_RandomSymbol(): + + X = Normal('x', 0, 1) + Y = Normal('x', 0, 2) + assert X.symbol == Y.symbol + assert X != Y + + assert X.name == X.symbol.name + + X = Normal('lambda', 0, 1) # make sure we can use protected terms + X = Normal('Lambda', 0, 1) # make sure we can use SymPy terms + + +def test_RandomSymbol_diff(): + X = Normal('x', 0, 1) + assert (2*X).diff(X) + + +def test_random_symbol_no_pspace(): + x = RandomSymbol(Symbol('x')) + assert x.pspace == PSpace() + +def test_overlap(): + X = Normal('x', 0, 1) + Y = Normal('x', 0, 2) + + raises(ValueError, lambda: P(X > Y)) + + +def test_IndependentProductPSpace(): + X = Normal('X', 0, 1) + Y = Normal('Y', 0, 1) + px = X.pspace + py = Y.pspace + assert pspace(X + Y) == IndependentProductPSpace(px, py) + assert pspace(X + Y) == IndependentProductPSpace(py, px) + + +def test_E(): + assert E(5) == 5 + + +def test_H(): + X = Normal('X', 0, 1) + D = Die('D', sides = 4) + G = Geometric('G', 0.5) + assert H(X, X > 0) == -log(2)/2 + S.Half + log(pi)/2 + assert H(D, D > 2) == log(2) + assert comp(H(G).evalf().round(2), 1.39) + + +def test_Sample(): + X = Die('X', 6) + Y = Normal('Y', 0, 1) + z = Symbol('z', integer=True) + + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests') + assert sample(X) in [1, 2, 3, 4, 5, 6] + assert isinstance(sample(X + Y), float) + + assert P(X + Y > 0, Y < 0, numsamples=10).is_number + assert E(X + Y, numsamples=10).is_number + assert E(X**2 + Y, numsamples=10).is_number + assert E((X + Y)**2, numsamples=10).is_number + assert variance(X + Y, numsamples=10).is_number + + raises(TypeError, lambda: P(Y > z, numsamples=5)) + + assert P(sin(Y) <= 1, numsamples=10) == 1.0 + assert P(sin(Y) <= 1, cos(Y) < 1, numsamples=10) == 1.0 + + assert all(i in range(1, 7) for i in density(X, numsamples=10)) + assert all(i in range(4, 7) for i in density(X, X>3, numsamples=10)) + + numpy = import_module('numpy') + if not numpy: + skip('Numpy is not installed. Abort tests') + #Test Issue #21563: Output of sample must be a float or array + assert isinstance(sample(X), (numpy.int32, numpy.int64)) + assert isinstance(sample(Y), numpy.float64) + assert isinstance(sample(X, size=2), numpy.ndarray) + + with warns_deprecated_sympy(): + sample(X, numsamples=2) + +@XFAIL +def test_samplingE(): + scipy = import_module('scipy') + if not scipy: + skip('Scipy is not installed. Abort tests') + Y = Normal('Y', 0, 1) + z = Symbol('z', integer=True) + assert E(Sum(1/z**Y, (z, 1, oo)), Y > 2, numsamples=3).is_number + + +def test_given(): + X = Normal('X', 0, 1) + Y = Normal('Y', 0, 1) + A = given(X, True) + B = given(X, Y > 2) + + assert X == A == B + + +def test_factorial_moment(): + X = Poisson('X', 2) + Y = Binomial('Y', 2, S.Half) + Z = Hypergeometric('Z', 4, 2, 2) + assert factorial_moment(X, 2) == 4 + assert factorial_moment(Y, 2) == S.Half + assert factorial_moment(Z, 2) == Rational(1, 3) + + x, y, z, l = symbols('x y z l') + Y = Binomial('Y', 2, y) + Z = Hypergeometric('Z', 10, 2, 3) + assert factorial_moment(Y, l) == y**2*FallingFactorial( + 2, l) + 2*y*(1 - y)*FallingFactorial(1, l) + (1 - y)**2*\ + FallingFactorial(0, l) + assert factorial_moment(Z, l) == 7*FallingFactorial(0, l)/\ + 15 + 7*FallingFactorial(1, l)/15 + FallingFactorial(2, l)/15 + + +def test_dependence(): + X, Y = Die('X'), Die('Y') + assert independent(X, 2*Y) + assert not dependent(X, 2*Y) + + X, Y = Normal('X', 0, 1), Normal('Y', 0, 1) + assert independent(X, Y) + assert dependent(X, 2*X) + + # Create a dependency + XX, YY = given(Tuple(X, Y), Eq(X + Y, 3)) + assert dependent(XX, YY) + +def test_dependent_finite(): + X, Y = Die('X'), Die('Y') + # Dependence testing requires symbolic conditions which currently break + # finite random variables + assert dependent(X, Y + X) + + XX, YY = given(Tuple(X, Y), X + Y > 5) # Create a dependency + assert dependent(XX, YY) + + +def test_normality(): + X, Y = Normal('X', 0, 1), Normal('Y', 0, 1) + x = Symbol('x', real=True) + z = Symbol('z', real=True) + dens = density(X - Y, Eq(X + Y, z)) + + assert integrate(dens(x), (x, -oo, oo)) == 1 + + +def test_Density(): + X = Die('X', 6) + d = Density(X) + assert d.doit() == density(X) + +def test_NamedArgsMixin(): + class Foo(Basic, NamedArgsMixin): + _argnames = 'foo', 'bar' + + a = Foo(S(1), S(2)) + + assert a.foo == 1 + assert a.bar == 2 + + raises(AttributeError, lambda: a.baz) + + class Bar(Basic, NamedArgsMixin): + pass + + raises(AttributeError, lambda: Bar(S(1), S(2)).foo) + +def test_density_constant(): + assert density(3)(2) == 0 + assert density(3)(3) == DiracDelta(0) + +def test_cmoment_constant(): + assert variance(3) == 0 + assert cmoment(3, 3) == 0 + assert cmoment(3, 4) == 0 + x = Symbol('x') + assert variance(x) == 0 + assert cmoment(x, 15) == 0 + assert cmoment(x, 0) == 1 + +def test_moment_constant(): + assert moment(3, 0) == 1 + assert moment(3, 1) == 3 + assert moment(3, 2) == 9 + x = Symbol('x') + assert moment(x, 2) == x**2 + +def test_median_constant(): + assert median(3) == 3 + x = Symbol('x') + assert median(x) == x + +def test_real(): + x = Normal('x', 0, 1) + assert x.is_real + + +def test_issue_10052(): + X = Exponential('X', 3) + assert P(X < oo) == 1 + assert P(X > oo) == 0 + assert P(X < 2, X > oo) == 0 + assert P(X < oo, X > oo) == 0 + assert P(X < oo, X > 2) == 1 + assert P(X < 3, X == 2) == 0 + raises(ValueError, lambda: P(1)) + raises(ValueError, lambda: P(X < 1, 2)) + +def test_issue_11934(): + density = {0: .5, 1: .5} + X = FiniteRV('X', density) + assert E(X) == 0.5 + assert P( X>= 2) == 0 + +def test_issue_8129(): + X = Exponential('X', 4) + assert P(X >= X) == 1 + assert P(X > X) == 0 + assert P(X > X+1) == 0 + +def test_issue_12237(): + X = Normal('X', 0, 1) + Y = Normal('Y', 0, 1) + U = P(X > 0, X) + V = P(Y < 0, X) + W = P(X + Y > 0, X) + assert W == P(X + Y > 0, X) + assert U == BernoulliDistribution(S.Half, S.Zero, S.One) + assert V == S.Half + +def test_is_random(): + X = Normal('X', 0, 1) + Y = Normal('Y', 0, 1) + a, b = symbols('a, b') + G = GaussianUnitaryEnsemble('U', 2) + B = BernoulliProcess('B', 0.9) + assert not is_random(a) + assert not is_random(a + b) + assert not is_random(a * b) + assert not is_random(Matrix([a**2, b**2])) + assert is_random(X) + assert is_random(X**2 + Y) + assert is_random(Y + b**2) + assert is_random(Y > 5) + assert is_random(B[3] < 1) + assert is_random(G) + assert is_random(X * Y * B[1]) + assert is_random(Matrix([[X, B[2]], [G, Y]])) + assert is_random(Eq(X, 4)) + +def test_issue_12283(): + x = symbols('x') + X = RandomSymbol(x) + Y = RandomSymbol('Y') + Z = RandomMatrixSymbol('Z', 2, 1) + W = RandomMatrixSymbol('W', 2, 1) + RI = RandomIndexedSymbol(Indexed('RI', 3)) + assert pspace(Z) == PSpace() + assert pspace(RI) == PSpace() + assert pspace(X) == PSpace() + assert E(X) == Expectation(X) + assert P(Y > 3) == Probability(Y > 3) + assert variance(X) == Variance(X) + assert variance(RI) == Variance(RI) + assert covariance(X, Y) == Covariance(X, Y) + assert covariance(W, Z) == Covariance(W, Z) + +def test_issue_6810(): + X = Die('X', 6) + Y = Normal('Y', 0, 1) + assert P(Eq(X, 2)) == S(1)/6 + assert P(Eq(Y, 0)) == 0 + assert P(Or(X > 2, X < 3)) == 1 + assert P(And(X > 3, X > 2)) == S(1)/2 + +def test_issue_20286(): + n, p = symbols('n p') + B = Binomial('B', n, p) + k = Dummy('k', integer = True) + eq = Sum(Piecewise((-p**k*(1 - p)**(-k + n)*log(p**k*(1 - p)**(-k + n)*binomial(n, k))*binomial(n, k), (k >= 0) & (k <= n)), (nan, True)), (k, 0, n)) + assert eq.dummy_eq(H(B)) diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_stochastic_process.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_stochastic_process.py new file mode 100644 index 0000000000000000000000000000000000000000..3e42ffc8632240b1d85a774467a057e9857c567c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_stochastic_process.py @@ -0,0 +1,763 @@ +from sympy.concrete.summations import Sum +from sympy.core.containers import Tuple +from sympy.core.function import Lambda +from sympy.core.numbers import (Float, Rational, oo, pi) +from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.integers import ceiling +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.error_functions import erf +from sympy.functions.special.gamma_functions import (gamma, lowergamma) +from sympy.logic.boolalg import (And, Not) +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.immutable import ImmutableMatrix +from sympy.sets.contains import Contains +from sympy.sets.fancysets import Range +from sympy.sets.sets import (FiniteSet, Interval) +from sympy.stats import (DiscreteMarkovChain, P, TransitionMatrixOf, E, + StochasticStateSpaceOf, variance, ContinuousMarkovChain, + BernoulliProcess, PoissonProcess, WienerProcess, + GammaProcess, sample_stochastic_process) +from sympy.stats.joint_rv import JointDistribution +from sympy.stats.joint_rv_types import JointDistributionHandmade +from sympy.stats.rv import RandomIndexedSymbol +from sympy.stats.symbolic_probability import Probability, Expectation +from sympy.testing.pytest import (raises, skip, ignore_warnings, + warns_deprecated_sympy) +from sympy.external import import_module +from sympy.stats.frv_types import BernoulliDistribution +from sympy.stats.drv_types import PoissonDistribution +from sympy.stats.crv_types import NormalDistribution, GammaDistribution +from sympy.core.symbol import Str + + +def test_DiscreteMarkovChain(): + + # pass only the name + X = DiscreteMarkovChain("X") + assert isinstance(X.state_space, Range) + assert X.index_set == S.Naturals0 + assert isinstance(X.transition_probabilities, MatrixSymbol) + t = symbols('t', positive=True, integer=True) + assert isinstance(X[t], RandomIndexedSymbol) + assert E(X[0]) == Expectation(X[0]) + raises(TypeError, lambda: DiscreteMarkovChain(1)) + raises(NotImplementedError, lambda: X(t)) + raises(NotImplementedError, lambda: X.communication_classes()) + raises(NotImplementedError, lambda: X.canonical_form()) + raises(NotImplementedError, lambda: X.decompose()) + + nz = Symbol('n', integer=True) + TZ = MatrixSymbol('M', nz, nz) + SZ = Range(nz) + YZ = DiscreteMarkovChain('Y', SZ, TZ) + assert P(Eq(YZ[2], 1), Eq(YZ[1], 0)) == TZ[0, 1] + + raises(ValueError, lambda: sample_stochastic_process(t)) + raises(ValueError, lambda: next(sample_stochastic_process(X))) + # pass name and state_space + # any hashable object should be a valid state + # states should be valid as a tuple/set/list/Tuple/Range + sym, rainy, cloudy, sunny = symbols('a Rainy Cloudy Sunny', real=True) + state_spaces = [(1, 2, 3), [Str('Hello'), sym, DiscreteMarkovChain("Y", (1,2,3))], + Tuple(S(1), exp(sym), Str('World'), sympify=False), Range(-1, 5, 2), + [rainy, cloudy, sunny]] + chains = [DiscreteMarkovChain("Y", state_space) for state_space in state_spaces] + + for i, Y in enumerate(chains): + assert isinstance(Y.transition_probabilities, MatrixSymbol) + assert Y.state_space == state_spaces[i] or Y.state_space == FiniteSet(*state_spaces[i]) + assert Y.number_of_states == 3 + + with ignore_warnings(UserWarning): # TODO: Restore tests once warnings are removed + assert P(Eq(Y[2], 1), Eq(Y[0], 2), evaluate=False) == Probability(Eq(Y[2], 1), Eq(Y[0], 2)) + assert E(Y[0]) == Expectation(Y[0]) + + raises(ValueError, lambda: next(sample_stochastic_process(Y))) + + raises(TypeError, lambda: DiscreteMarkovChain("Y", {1: 1})) + Y = DiscreteMarkovChain("Y", Range(1, t, 2)) + assert Y.number_of_states == ceiling((t-1)/2) + + # pass name and transition_probabilities + chains = [DiscreteMarkovChain("Y", trans_probs=Matrix([])), + DiscreteMarkovChain("Y", trans_probs=Matrix([[0, 1], [1, 0]])), + DiscreteMarkovChain("Y", trans_probs=Matrix([[pi, 1-pi], [sym, 1-sym]]))] + for Z in chains: + assert Z.number_of_states == Z.transition_probabilities.shape[0] + assert isinstance(Z.transition_probabilities, ImmutableMatrix) + + # pass name, state_space and transition_probabilities + T = Matrix([[0.5, 0.2, 0.3],[0.2, 0.5, 0.3],[0.2, 0.3, 0.5]]) + TS = MatrixSymbol('T', 3, 3) + Y = DiscreteMarkovChain("Y", [0, 1, 2], T) + YS = DiscreteMarkovChain("Y", ['One', 'Two', 3], TS) + assert Y.joint_distribution(1, Y[2], 3) == JointDistribution(Y[1], Y[2], Y[3]) + raises(ValueError, lambda: Y.joint_distribution(Y[1].symbol, Y[2].symbol)) + assert P(Eq(Y[3], 2), Eq(Y[1], 1)).round(2) == Float(0.36, 2) + assert (P(Eq(YS[3], 2), Eq(YS[1], 1)) - + (TS[0, 2]*TS[1, 0] + TS[1, 1]*TS[1, 2] + TS[1, 2]*TS[2, 2])).simplify() == 0 + assert P(Eq(YS[1], 1), Eq(YS[2], 2)) == Probability(Eq(YS[1], 1)) + assert P(Eq(YS[3], 3), Eq(YS[1], 1)) == TS[0, 2]*TS[1, 0] + TS[1, 1]*TS[1, 2] + TS[1, 2]*TS[2, 2] + TO = Matrix([[0.25, 0.75, 0],[0, 0.25, 0.75],[0.75, 0, 0.25]]) + assert P(Eq(Y[3], 2), Eq(Y[1], 1) & TransitionMatrixOf(Y, TO)).round(3) == Float(0.375, 3) + with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed + assert E(Y[3], evaluate=False) == Expectation(Y[3]) + assert E(Y[3], Eq(Y[2], 1)).round(2) == Float(1.1, 3) + TSO = MatrixSymbol('T', 4, 4) + raises(ValueError, lambda: str(P(Eq(YS[3], 2), Eq(YS[1], 1) & TransitionMatrixOf(YS, TSO)))) + raises(TypeError, lambda: DiscreteMarkovChain("Z", [0, 1, 2], symbols('M'))) + raises(ValueError, lambda: DiscreteMarkovChain("Z", [0, 1, 2], MatrixSymbol('T', 3, 4))) + raises(ValueError, lambda: E(Y[3], Eq(Y[2], 6))) + raises(ValueError, lambda: E(Y[2], Eq(Y[3], 1))) + + + # extended tests for probability queries + TO1 = Matrix([[Rational(1, 4), Rational(3, 4), 0],[Rational(1, 3), Rational(1, 3), Rational(1, 3)],[0, Rational(1, 4), Rational(3, 4)]]) + assert P(And(Eq(Y[2], 1), Eq(Y[1], 1), Eq(Y[0], 0)), + Eq(Probability(Eq(Y[0], 0)), Rational(1, 4)) & TransitionMatrixOf(Y, TO1)) == Rational(1, 16) + assert P(And(Eq(Y[2], 1), Eq(Y[1], 1), Eq(Y[0], 0)), TransitionMatrixOf(Y, TO1)) == \ + Probability(Eq(Y[0], 0))/4 + assert P(Lt(X[1], 2) & Gt(X[1], 0), Eq(X[0], 2) & + StochasticStateSpaceOf(X, [0, 1, 2]) & TransitionMatrixOf(X, TO1)) == Rational(1, 4) + assert P(Lt(X[1], 2) & Gt(X[1], 0), Eq(X[0], 2) & + StochasticStateSpaceOf(X, [S(0), '0', 1]) & TransitionMatrixOf(X, TO1)) == Rational(1, 4) + assert P(Ne(X[1], 2) & Ne(X[1], 1), Eq(X[0], 2) & + StochasticStateSpaceOf(X, [0, 1, 2]) & TransitionMatrixOf(X, TO1)) is S.Zero + assert P(Ne(X[1], 2) & Ne(X[1], 1), Eq(X[0], 2) & + StochasticStateSpaceOf(X, [S(0), '0', 1]) & TransitionMatrixOf(X, TO1)) is S.Zero + assert P(And(Eq(Y[2], 1), Eq(Y[1], 1), Eq(Y[0], 0)), Eq(Y[1], 1)) == 0.1*Probability(Eq(Y[0], 0)) + + # testing properties of Markov chain + TO2 = Matrix([[S.One, 0, 0],[Rational(1, 3), Rational(1, 3), Rational(1, 3)],[0, Rational(1, 4), Rational(3, 4)]]) + TO3 = Matrix([[Rational(1, 4), Rational(3, 4), 0],[Rational(1, 3), Rational(1, 3), Rational(1, 3)], [0, Rational(1, 4), Rational(3, 4)]]) + Y2 = DiscreteMarkovChain('Y', trans_probs=TO2) + Y3 = DiscreteMarkovChain('Y', trans_probs=TO3) + assert Y3.fundamental_matrix() == ImmutableMatrix([[176, 81, -132], [36, 141, -52], [-44, -39, 208]])/125 + assert Y2.is_absorbing_chain() == True + assert Y3.is_absorbing_chain() == False + assert Y2.canonical_form() == ([0, 1, 2], TO2) + assert Y3.canonical_form() == ([0, 1, 2], TO3) + assert Y2.decompose() == ([0, 1, 2], TO2[0:1, 0:1], TO2[1:3, 0:1], TO2[1:3, 1:3]) + assert Y3.decompose() == ([0, 1, 2], TO3, Matrix(0, 3, []), Matrix(0, 0, [])) + TO4 = Matrix([[Rational(1, 5), Rational(2, 5), Rational(2, 5)], [Rational(1, 10), S.Half, Rational(2, 5)], [Rational(3, 5), Rational(3, 10), Rational(1, 10)]]) + Y4 = DiscreteMarkovChain('Y', trans_probs=TO4) + w = ImmutableMatrix([[Rational(11, 39), Rational(16, 39), Rational(4, 13)]]) + assert Y4.limiting_distribution == w + assert Y4.is_regular() == True + assert Y4.is_ergodic() == True + TS1 = MatrixSymbol('T', 3, 3) + Y5 = DiscreteMarkovChain('Y', trans_probs=TS1) + assert Y5.limiting_distribution(w, TO4).doit() == True + assert Y5.stationary_distribution(condition_set=True).subs(TS1, TO4).contains(w).doit() == S.true + TO6 = Matrix([[S.One, 0, 0, 0, 0],[S.Half, 0, S.Half, 0, 0],[0, S.Half, 0, S.Half, 0], [0, 0, S.Half, 0, S.Half], [0, 0, 0, 0, 1]]) + Y6 = DiscreteMarkovChain('Y', trans_probs=TO6) + assert Y6.fundamental_matrix() == ImmutableMatrix([[Rational(3, 2), S.One, S.Half], [S.One, S(2), S.One], [S.Half, S.One, Rational(3, 2)]]) + assert Y6.absorbing_probabilities() == ImmutableMatrix([[Rational(3, 4), Rational(1, 4)], [S.Half, S.Half], [Rational(1, 4), Rational(3, 4)]]) + with warns_deprecated_sympy(): + Y6.absorbing_probabilites() + TO7 = Matrix([[Rational(1, 2), Rational(1, 4), Rational(1, 4)], [Rational(1, 2), 0, Rational(1, 2)], [Rational(1, 4), Rational(1, 4), Rational(1, 2)]]) + Y7 = DiscreteMarkovChain('Y', trans_probs=TO7) + assert Y7.is_absorbing_chain() == False + assert Y7.fundamental_matrix() == ImmutableMatrix([[Rational(86, 75), Rational(1, 25), Rational(-14, 75)], + [Rational(2, 25), Rational(21, 25), Rational(2, 25)], + [Rational(-14, 75), Rational(1, 25), Rational(86, 75)]]) + + # test for zero-sized matrix functionality + X = DiscreteMarkovChain('X', trans_probs=Matrix([])) + assert X.number_of_states == 0 + assert X.stationary_distribution() == Matrix([[]]) + assert X.communication_classes() == [] + assert X.canonical_form() == ([], Matrix([])) + assert X.decompose() == ([], Matrix([]), Matrix([]), Matrix([])) + assert X.is_regular() == False + assert X.is_ergodic() == False + + # test communication_class + # see https://drive.google.com/drive/folders/1HbxLlwwn2b3U8Lj7eb_ASIUb5vYaNIjg?usp=sharing + # tutorial 2.pdf + TO7 = Matrix([[0, 5, 5, 0, 0], + [0, 0, 0, 10, 0], + [5, 0, 5, 0, 0], + [0, 10, 0, 0, 0], + [0, 3, 0, 3, 4]])/10 + Y7 = DiscreteMarkovChain('Y', trans_probs=TO7) + tuples = Y7.communication_classes() + classes, recurrence, periods = list(zip(*tuples)) + assert classes == ([1, 3], [0, 2], [4]) + assert recurrence == (True, False, False) + assert periods == (2, 1, 1) + + TO8 = Matrix([[0, 0, 0, 10, 0, 0], + [5, 0, 5, 0, 0, 0], + [0, 4, 0, 0, 0, 6], + [10, 0, 0, 0, 0, 0], + [0, 10, 0, 0, 0, 0], + [0, 0, 0, 5, 5, 0]])/10 + Y8 = DiscreteMarkovChain('Y', trans_probs=TO8) + tuples = Y8.communication_classes() + classes, recurrence, periods = list(zip(*tuples)) + assert classes == ([0, 3], [1, 2, 5, 4]) + assert recurrence == (True, False) + assert periods == (2, 2) + + TO9 = Matrix([[2, 0, 0, 3, 0, 0, 3, 2, 0, 0], + [0, 10, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 2, 0, 0, 0, 0, 0, 3, 3], + [0, 0, 0, 3, 0, 0, 6, 1, 0, 0], + [0, 0, 0, 0, 5, 5, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 10, 0, 0, 0, 0], + [4, 0, 0, 5, 0, 0, 1, 0, 0, 0], + [2, 0, 0, 4, 0, 0, 2, 2, 0, 0], + [3, 0, 1, 0, 0, 0, 0, 0, 4, 2], + [0, 0, 4, 0, 0, 0, 0, 0, 3, 3]])/10 + Y9 = DiscreteMarkovChain('Y', trans_probs=TO9) + tuples = Y9.communication_classes() + classes, recurrence, periods = list(zip(*tuples)) + assert classes == ([0, 3, 6, 7], [1], [2, 8, 9], [5], [4]) + assert recurrence == (True, True, False, True, False) + assert periods == (1, 1, 1, 1, 1) + + # test canonical form + # see https://web.archive.org/web/20201230182007/https://www.dartmouth.edu/~chance/teaching_aids/books_articles/probability_book/Chapter11.pdf + # example 11.13 + T = Matrix([[1, 0, 0, 0, 0], + [S(1) / 2, 0, S(1) / 2, 0, 0], + [0, S(1) / 2, 0, S(1) / 2, 0], + [0, 0, S(1) / 2, 0, S(1) / 2], + [0, 0, 0, 0, S(1)]]) + DW = DiscreteMarkovChain('DW', [0, 1, 2, 3, 4], T) + states, A, B, C = DW.decompose() + assert states == [0, 4, 1, 2, 3] + assert A == Matrix([[1, 0], [0, 1]]) + assert B == Matrix([[S(1)/2, 0], [0, 0], [0, S(1)/2]]) + assert C == Matrix([[0, S(1)/2, 0], [S(1)/2, 0, S(1)/2], [0, S(1)/2, 0]]) + states, new_matrix = DW.canonical_form() + assert states == [0, 4, 1, 2, 3] + assert new_matrix == Matrix([[1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], + [S(1)/2, 0, 0, S(1)/2, 0], + [0, 0, S(1)/2, 0, S(1)/2], + [0, S(1)/2, 0, S(1)/2, 0]]) + + # test regular and ergodic + # https://web.archive.org/web/20201230182007/https://www.dartmouth.edu/~chance/teaching_aids/books_articles/probability_book/Chapter11.pdf + T = Matrix([[0, 4, 0, 0, 0], + [1, 0, 3, 0, 0], + [0, 2, 0, 2, 0], + [0, 0, 3, 0, 1], + [0, 0, 0, 4, 0]])/4 + X = DiscreteMarkovChain('X', trans_probs=T) + assert not X.is_regular() + assert X.is_ergodic() + T = Matrix([[0, 1], [1, 0]]) + X = DiscreteMarkovChain('X', trans_probs=T) + assert not X.is_regular() + assert X.is_ergodic() + # http://www.math.wisc.edu/~valko/courses/331/MC2.pdf + T = Matrix([[2, 1, 1], + [2, 0, 2], + [1, 1, 2]])/4 + X = DiscreteMarkovChain('X', trans_probs=T) + assert X.is_regular() + assert X.is_ergodic() + # https://docs.ufpr.br/~lucambio/CE222/1S2014/Kemeny-Snell1976.pdf + T = Matrix([[1, 1], [1, 1]])/2 + X = DiscreteMarkovChain('X', trans_probs=T) + assert X.is_regular() + assert X.is_ergodic() + + # test is_absorbing_chain + T = Matrix([[0, 1, 0], + [1, 0, 0], + [0, 0, 1]]) + X = DiscreteMarkovChain('X', trans_probs=T) + assert not X.is_absorbing_chain() + # https://en.wikipedia.org/wiki/Absorbing_Markov_chain + T = Matrix([[1, 1, 0, 0], + [0, 1, 1, 0], + [1, 0, 0, 1], + [0, 0, 0, 2]])/2 + X = DiscreteMarkovChain('X', trans_probs=T) + assert X.is_absorbing_chain() + T = Matrix([[2, 0, 0, 0, 0], + [1, 0, 1, 0, 0], + [0, 1, 0, 1, 0], + [0, 0, 1, 0, 1], + [0, 0, 0, 0, 2]])/2 + X = DiscreteMarkovChain('X', trans_probs=T) + assert X.is_absorbing_chain() + + # test custom state space + Y10 = DiscreteMarkovChain('Y', [1, 2, 3], TO2) + tuples = Y10.communication_classes() + classes, recurrence, periods = list(zip(*tuples)) + assert classes == ([1], [2, 3]) + assert recurrence == (True, False) + assert periods == (1, 1) + assert Y10.canonical_form() == ([1, 2, 3], TO2) + assert Y10.decompose() == ([1, 2, 3], TO2[0:1, 0:1], TO2[1:3, 0:1], TO2[1:3, 1:3]) + + # testing miscellaneous queries + T = Matrix([[S.Half, Rational(1, 4), Rational(1, 4)], + [Rational(1, 3), 0, Rational(2, 3)], + [S.Half, S.Half, 0]]) + X = DiscreteMarkovChain('X', [0, 1, 2], T) + assert P(Eq(X[1], 2) & Eq(X[2], 1) & Eq(X[3], 0), + Eq(P(Eq(X[1], 0)), Rational(1, 4)) & Eq(P(Eq(X[1], 1)), Rational(1, 4))) == Rational(1, 12) + assert P(Eq(X[2], 1) | Eq(X[2], 2), Eq(X[1], 1)) == Rational(2, 3) + assert P(Eq(X[2], 1) & Eq(X[2], 2), Eq(X[1], 1)) is S.Zero + assert P(Ne(X[2], 2), Eq(X[1], 1)) == Rational(1, 3) + assert E(X[1]**2, Eq(X[0], 1)) == Rational(8, 3) + assert variance(X[1], Eq(X[0], 1)) == Rational(8, 9) + raises(ValueError, lambda: E(X[1], Eq(X[2], 1))) + raises(ValueError, lambda: DiscreteMarkovChain('X', [0, 1], T)) + + # testing miscellaneous queries with different state space + X = DiscreteMarkovChain('X', ['A', 'B', 'C'], T) + assert P(Eq(X[1], 2) & Eq(X[2], 1) & Eq(X[3], 0), + Eq(P(Eq(X[1], 0)), Rational(1, 4)) & Eq(P(Eq(X[1], 1)), Rational(1, 4))) == Rational(1, 12) + assert P(Eq(X[2], 1) | Eq(X[2], 2), Eq(X[1], 1)) == Rational(2, 3) + assert P(Eq(X[2], 1) & Eq(X[2], 2), Eq(X[1], 1)) is S.Zero + assert P(Ne(X[2], 2), Eq(X[1], 1)) == Rational(1, 3) + a = X.state_space.args[0] + c = X.state_space.args[2] + assert (E(X[1] ** 2, Eq(X[0], 1)) - (a**2/3 + 2*c**2/3)).simplify() == 0 + assert (variance(X[1], Eq(X[0], 1)) - (2*(-a/3 + c/3)**2/3 + (2*a/3 - 2*c/3)**2/3)).simplify() == 0 + raises(ValueError, lambda: E(X[1], Eq(X[2], 1))) + + #testing queries with multiple RandomIndexedSymbols + T = Matrix([[Rational(5, 10), Rational(3, 10), Rational(2, 10)], [Rational(2, 10), Rational(7, 10), Rational(1, 10)], [Rational(3, 10), Rational(3, 10), Rational(4, 10)]]) + Y = DiscreteMarkovChain("Y", [0, 1, 2], T) + assert P(Eq(Y[7], Y[5]), Eq(Y[2], 0)).round(5) == Float(0.44428, 5) + assert P(Gt(Y[3], Y[1]), Eq(Y[0], 0)).round(2) == Float(0.36, 2) + assert P(Le(Y[5], Y[10]), Eq(Y[4], 2)).round(6) == Float(0.583120, 6) + assert Float(P(Eq(Y[10], Y[5]), Eq(Y[4], 1)), 14) == Float(1 - P(Ne(Y[10], Y[5]), Eq(Y[4], 1)), 14) + assert Float(P(Gt(Y[8], Y[9]), Eq(Y[3], 2)), 14) == Float(1 - P(Le(Y[8], Y[9]), Eq(Y[3], 2)), 14) + assert Float(P(Lt(Y[1], Y[4]), Eq(Y[0], 0)), 14) == Float(1 - P(Ge(Y[1], Y[4]), Eq(Y[0], 0)), 14) + assert P(Eq(Y[5], Y[10]), Eq(Y[2], 1)) == P(Eq(Y[10], Y[5]), Eq(Y[2], 1)) + assert P(Gt(Y[1], Y[2]), Eq(Y[0], 1)) == P(Lt(Y[2], Y[1]), Eq(Y[0], 1)) + assert P(Ge(Y[7], Y[6]), Eq(Y[4], 1)) == P(Le(Y[6], Y[7]), Eq(Y[4], 1)) + + #test symbolic queries + a, b, c, d = symbols('a b c d') + T = Matrix([[Rational(1, 10), Rational(4, 10), Rational(5, 10)], [Rational(3, 10), Rational(4, 10), Rational(3, 10)], [Rational(7, 10), Rational(2, 10), Rational(1, 10)]]) + Y = DiscreteMarkovChain("Y", [0, 1, 2], T) + query = P(Eq(Y[a], b), Eq(Y[c], d)) + assert query.subs({a:10, b:2, c:5, d:1}).evalf().round(4) == P(Eq(Y[10], 2), Eq(Y[5], 1)).round(4) + assert query.subs({a:15, b:0, c:10, d:1}).evalf().round(4) == P(Eq(Y[15], 0), Eq(Y[10], 1)).round(4) + query_gt = P(Gt(Y[a], b), Eq(Y[c], d)) + query_le = P(Le(Y[a], b), Eq(Y[c], d)) + assert query_gt.subs({a:5, b:2, c:1, d:0}).evalf() + query_le.subs({a:5, b:2, c:1, d:0}).evalf() == 1.0 + query_ge = P(Ge(Y[a], b), Eq(Y[c], d)) + query_lt = P(Lt(Y[a], b), Eq(Y[c], d)) + assert query_ge.subs({a:4, b:1, c:0, d:2}).evalf() + query_lt.subs({a:4, b:1, c:0, d:2}).evalf() == 1.0 + + #test issue 20078 + assert (2*Y[1] + 3*Y[1]).simplify() == 5*Y[1] + assert (2*Y[1] - 3*Y[1]).simplify() == -Y[1] + assert (2*(0.25*Y[1])).simplify() == 0.5*Y[1] + assert ((2*Y[1]) * (0.25*Y[1])).simplify() == 0.5*Y[1]**2 + assert (Y[1]**2 + Y[1]**3).simplify() == (Y[1] + 1)*Y[1]**2 + +def test_sample_stochastic_process(): + if not import_module('scipy'): + skip('SciPy Not installed. Skip sampling tests') + import random + random.seed(0) + numpy = import_module('numpy') + if numpy: + numpy.random.seed(0) # scipy uses numpy to sample so to set its seed + T = Matrix([[0.5, 0.2, 0.3],[0.2, 0.5, 0.3],[0.2, 0.3, 0.5]]) + Y = DiscreteMarkovChain("Y", [0, 1, 2], T) + for samps in range(10): + assert next(sample_stochastic_process(Y)) in Y.state_space + Z = DiscreteMarkovChain("Z", ['1', 1, 0], T) + for samps in range(10): + assert next(sample_stochastic_process(Z)) in Z.state_space + + T = Matrix([[S.Half, Rational(1, 4), Rational(1, 4)], + [Rational(1, 3), 0, Rational(2, 3)], + [S.Half, S.Half, 0]]) + X = DiscreteMarkovChain('X', [0, 1, 2], T) + for samps in range(10): + assert next(sample_stochastic_process(X)) in X.state_space + W = DiscreteMarkovChain('W', [1, pi, oo], T) + for samps in range(10): + assert next(sample_stochastic_process(W)) in W.state_space + + +def test_ContinuousMarkovChain(): + T1 = Matrix([[S(-2), S(2), S.Zero], + [S.Zero, S.NegativeOne, S.One], + [Rational(3, 2), Rational(3, 2), S(-3)]]) + C1 = ContinuousMarkovChain('C', [0, 1, 2], T1) + assert C1.limiting_distribution() == ImmutableMatrix([[Rational(3, 19), Rational(12, 19), Rational(4, 19)]]) + + T2 = Matrix([[-S.One, S.One, S.Zero], [S.One, -S.One, S.Zero], [S.Zero, S.One, -S.One]]) + C2 = ContinuousMarkovChain('C', [0, 1, 2], T2) + A, t = C2.generator_matrix, symbols('t', positive=True) + assert C2.transition_probabilities(A)(t) == Matrix([[S.Half + exp(-2*t)/2, S.Half - exp(-2*t)/2, 0], + [S.Half - exp(-2*t)/2, S.Half + exp(-2*t)/2, 0], + [S.Half - exp(-t) + exp(-2*t)/2, S.Half - exp(-2*t)/2, exp(-t)]]) + with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed + assert P(Eq(C2(1), 1), Eq(C2(0), 1), evaluate=False) == Probability(Eq(C2(1), 1), Eq(C2(0), 1)) + assert P(Eq(C2(1), 1), Eq(C2(0), 1)) == exp(-2)/2 + S.Half + assert P(Eq(C2(1), 0) & Eq(C2(2), 1) & Eq(C2(3), 1), + Eq(P(Eq(C2(1), 0)), S.Half)) == (Rational(1, 4) - exp(-2)/4)*(exp(-2)/2 + S.Half) + assert P(Not(Eq(C2(1), 0) & Eq(C2(2), 1) & Eq(C2(3), 2)) | + (Eq(C2(1), 0) & Eq(C2(2), 1) & Eq(C2(3), 2)), + Eq(P(Eq(C2(1), 0)), Rational(1, 4)) & Eq(P(Eq(C2(1), 1)), Rational(1, 4))) is S.One + assert E(C2(Rational(3, 2)), Eq(C2(0), 2)) == -exp(-3)/2 + 2*exp(Rational(-3, 2)) + S.Half + assert variance(C2(Rational(3, 2)), Eq(C2(0), 1)) == ((S.Half - exp(-3)/2)**2*(exp(-3)/2 + S.Half) + + (Rational(-1, 2) - exp(-3)/2)**2*(S.Half - exp(-3)/2)) + raises(KeyError, lambda: P(Eq(C2(1), 0), Eq(P(Eq(C2(1), 1)), S.Half))) + assert P(Eq(C2(1), 0), Eq(P(Eq(C2(5), 1)), S.Half)) == Probability(Eq(C2(1), 0)) + TS1 = MatrixSymbol('G', 3, 3) + CS1 = ContinuousMarkovChain('C', [0, 1, 2], TS1) + A = CS1.generator_matrix + assert CS1.transition_probabilities(A)(t) == exp(t*A) + + C3 = ContinuousMarkovChain('C', [Symbol('0'), Symbol('1'), Symbol('2')], T2) + assert P(Eq(C3(1), 1), Eq(C3(0), 1)) == exp(-2)/2 + S.Half + assert P(Eq(C3(1), Symbol('1')), Eq(C3(0), Symbol('1'))) == exp(-2)/2 + S.Half + + #test probability queries + G = Matrix([[-S(1), Rational(1, 10), Rational(9, 10)], [Rational(2, 5), -S(1), Rational(3, 5)], [Rational(1, 2), Rational(1, 2), -S(1)]]) + C = ContinuousMarkovChain('C', state_space=[0, 1, 2], gen_mat=G) + assert P(Eq(C(7.385), C(3.19)), Eq(C(0.862), 0)).round(5) == Float(0.35469, 5) + assert P(Gt(C(98.715), C(19.807)), Eq(C(11.314), 2)).round(5) == Float(0.32452, 5) + assert P(Le(C(5.9), C(10.112)), Eq(C(4), 1)).round(6) == Float(0.675214, 6) + assert Float(P(Eq(C(7.32), C(2.91)), Eq(C(2.63), 1)), 14) == Float(1 - P(Ne(C(7.32), C(2.91)), Eq(C(2.63), 1)), 14) + assert Float(P(Gt(C(3.36), C(1.101)), Eq(C(0.8), 2)), 14) == Float(1 - P(Le(C(3.36), C(1.101)), Eq(C(0.8), 2)), 14) + assert Float(P(Lt(C(4.9), C(2.79)), Eq(C(1.61), 0)), 14) == Float(1 - P(Ge(C(4.9), C(2.79)), Eq(C(1.61), 0)), 14) + assert P(Eq(C(5.243), C(10.912)), Eq(C(2.174), 1)) == P(Eq(C(10.912), C(5.243)), Eq(C(2.174), 1)) + assert P(Gt(C(2.344), C(9.9)), Eq(C(1.102), 1)) == P(Lt(C(9.9), C(2.344)), Eq(C(1.102), 1)) + assert P(Ge(C(7.87), C(1.008)), Eq(C(0.153), 1)) == P(Le(C(1.008), C(7.87)), Eq(C(0.153), 1)) + + #test symbolic queries + a, b, c, d = symbols('a b c d') + query = P(Eq(C(a), b), Eq(C(c), d)) + assert query.subs({a:3.65, b:2, c:1.78, d:1}).evalf().round(10) == P(Eq(C(3.65), 2), Eq(C(1.78), 1)).round(10) + query_gt = P(Gt(C(a), b), Eq(C(c), d)) + query_le = P(Le(C(a), b), Eq(C(c), d)) + assert query_gt.subs({a:13.2, b:0, c:3.29, d:2}).evalf() + query_le.subs({a:13.2, b:0, c:3.29, d:2}).evalf() == 1.0 + query_ge = P(Ge(C(a), b), Eq(C(c), d)) + query_lt = P(Lt(C(a), b), Eq(C(c), d)) + assert query_ge.subs({a:7.43, b:1, c:1.45, d:0}).evalf() + query_lt.subs({a:7.43, b:1, c:1.45, d:0}).evalf() == 1.0 + + #test issue 20078 + assert (2*C(1) + 3*C(1)).simplify() == 5*C(1) + assert (2*C(1) - 3*C(1)).simplify() == -C(1) + assert (2*(0.25*C(1))).simplify() == 0.5*C(1) + assert (2*C(1) * 0.25*C(1)).simplify() == 0.5*C(1)**2 + assert (C(1)**2 + C(1)**3).simplify() == (C(1) + 1)*C(1)**2 + +def test_BernoulliProcess(): + + B = BernoulliProcess("B", p=0.6, success=1, failure=0) + assert B.state_space == FiniteSet(0, 1) + assert B.index_set == S.Naturals0 + assert B.success == 1 + assert B.failure == 0 + + X = BernoulliProcess("X", p=Rational(1,3), success='H', failure='T') + assert X.state_space == FiniteSet('H', 'T') + H, T = symbols("H,T") + assert E(X[1]+X[2]*X[3]) == H**2/9 + 4*H*T/9 + H/3 + 4*T**2/9 + 2*T/3 + + t, x = symbols('t, x', positive=True, integer=True) + assert isinstance(B[t], RandomIndexedSymbol) + + raises(ValueError, lambda: BernoulliProcess("X", p=1.1, success=1, failure=0)) + raises(NotImplementedError, lambda: B(t)) + + raises(IndexError, lambda: B[-3]) + assert B.joint_distribution(B[3], B[9]) == JointDistributionHandmade(Lambda((B[3], B[9]), + Piecewise((0.6, Eq(B[3], 1)), (0.4, Eq(B[3], 0)), (0, True)) + *Piecewise((0.6, Eq(B[9], 1)), (0.4, Eq(B[9], 0)), (0, True)))) + + assert B.joint_distribution(2, B[4]) == JointDistributionHandmade(Lambda((B[2], B[4]), + Piecewise((0.6, Eq(B[2], 1)), (0.4, Eq(B[2], 0)), (0, True)) + *Piecewise((0.6, Eq(B[4], 1)), (0.4, Eq(B[4], 0)), (0, True)))) + + # Test for the sum distribution of Bernoulli Process RVs + Y = B[1] + B[2] + B[3] + assert P(Eq(Y, 0)).round(2) == Float(0.06, 1) + assert P(Eq(Y, 2)).round(2) == Float(0.43, 2) + assert P(Eq(Y, 4)).round(2) == 0 + assert P(Gt(Y, 1)).round(2) == Float(0.65, 2) + # Test for independency of each Random Indexed variable + assert P(Eq(B[1], 0) & Eq(B[2], 1) & Eq(B[3], 0) & Eq(B[4], 1)).round(2) == Float(0.06, 1) + + assert E(2 * B[1] + B[2]).round(2) == Float(1.80, 3) + assert E(2 * B[1] + B[2] + 5).round(2) == Float(6.80, 3) + assert E(B[2] * B[4] + B[10]).round(2) == Float(0.96, 2) + assert E(B[2] > 0, Eq(B[1],1) & Eq(B[2],1)).round(2) == Float(0.60,2) + assert E(B[1]) == 0.6 + assert P(B[1] > 0).round(2) == Float(0.60, 2) + assert P(B[1] < 1).round(2) == Float(0.40, 2) + assert P(B[1] > 0, B[2] <= 1).round(2) == Float(0.60, 2) + assert P(B[12] * B[5] > 0).round(2) == Float(0.36, 2) + assert P(B[12] * B[5] > 0, B[4] < 1).round(2) == Float(0.36, 2) + assert P(Eq(B[2], 1), B[2] > 0) == 1.0 + assert P(Eq(B[5], 3)) == 0 + assert P(Eq(B[1], 1), B[1] < 0) == 0 + assert P(B[2] > 0, Eq(B[2], 1)) == 1 + assert P(B[2] < 0, Eq(B[2], 1)) == 0 + assert P(B[2] > 0, B[2]==7) == 0 + assert P(B[5] > 0, B[5]) == BernoulliDistribution(0.6, 0, 1) + raises(ValueError, lambda: P(3)) + raises(ValueError, lambda: P(B[3] > 0, 3)) + + # test issue 19456 + expr = Sum(B[t], (t, 0, 4)) + expr2 = Sum(B[t], (t, 1, 3)) + expr3 = Sum(B[t]**2, (t, 1, 3)) + assert expr.doit() == B[0] + B[1] + B[2] + B[3] + B[4] + assert expr2.doit() == Y + assert expr3.doit() == B[1]**2 + B[2]**2 + B[3]**2 + assert B[2*t].free_symbols == {B[2*t], t} + assert B[4].free_symbols == {B[4]} + assert B[x*t].free_symbols == {B[x*t], x, t} + + #test issue 20078 + assert (2*B[t] + 3*B[t]).simplify() == 5*B[t] + assert (2*B[t] - 3*B[t]).simplify() == -B[t] + assert (2*(0.25*B[t])).simplify() == 0.5*B[t] + assert (2*B[t] * 0.25*B[t]).simplify() == 0.5*B[t]**2 + assert (B[t]**2 + B[t]**3).simplify() == (B[t] + 1)*B[t]**2 + +def test_PoissonProcess(): + X = PoissonProcess("X", 3) + assert X.state_space == S.Naturals0 + assert X.index_set == Interval(0, oo) + assert X.lamda == 3 + + t, d, x, y = symbols('t d x y', positive=True) + assert isinstance(X(t), RandomIndexedSymbol) + assert X.distribution(t) == PoissonDistribution(3*t) + with warns_deprecated_sympy(): + X.distribution(X(t)) + raises(ValueError, lambda: PoissonProcess("X", -1)) + raises(NotImplementedError, lambda: X[t]) + raises(IndexError, lambda: X(-5)) + + assert X.joint_distribution(X(2), X(3)) == JointDistributionHandmade(Lambda((X(2), X(3)), + 6**X(2)*9**X(3)*exp(-15)/(factorial(X(2))*factorial(X(3))))) + + assert X.joint_distribution(4, 6) == JointDistributionHandmade(Lambda((X(4), X(6)), + 12**X(4)*18**X(6)*exp(-30)/(factorial(X(4))*factorial(X(6))))) + + assert P(X(t) < 1) == exp(-3*t) + assert P(Eq(X(t), 0), Contains(t, Interval.Lopen(3, 5))) == exp(-6) # exp(-2*lamda) + res = P(Eq(X(t), 1), Contains(t, Interval.Lopen(3, 4))) + assert res == 3*exp(-3) + + # Equivalent to P(Eq(X(t), 1))**4 because of non-overlapping intervals + assert P(Eq(X(t), 1) & Eq(X(d), 1) & Eq(X(x), 1) & Eq(X(y), 1), Contains(t, Interval.Lopen(0, 1)) + & Contains(d, Interval.Lopen(1, 2)) & Contains(x, Interval.Lopen(2, 3)) + & Contains(y, Interval.Lopen(3, 4))) == res**4 + + # Return Probability because of overlapping intervals + assert P(Eq(X(t), 2) & Eq(X(d), 3), Contains(t, Interval.Lopen(0, 2)) + & Contains(d, Interval.Ropen(2, 4))) == \ + Probability(Eq(X(d), 3) & Eq(X(t), 2), Contains(t, Interval.Lopen(0, 2)) + & Contains(d, Interval.Ropen(2, 4))) + + raises(ValueError, lambda: P(Eq(X(t), 2) & Eq(X(d), 3), + Contains(t, Interval.Lopen(0, 4)) & Contains(d, Interval.Lopen(3, oo)))) # no bound on d + assert P(Eq(X(3), 2)) == 81*exp(-9)/2 + assert P(Eq(X(t), 2), Contains(t, Interval.Lopen(0, 5))) == 225*exp(-15)/2 + + # Check that probability works correctly by adding it to 1 + res1 = P(X(t) <= 3, Contains(t, Interval.Lopen(0, 5))) + res2 = P(X(t) > 3, Contains(t, Interval.Lopen(0, 5))) + assert res1 == 691*exp(-15) + assert (res1 + res2).simplify() == 1 + + # Check Not and Or + assert P(Not(Eq(X(t), 2) & (X(d) > 3)), Contains(t, Interval.Ropen(2, 4)) & \ + Contains(d, Interval.Lopen(7, 8))).simplify() == -18*exp(-6) + 234*exp(-9) + 1 + assert P(Eq(X(t), 2) | Ne(X(t), 4), Contains(t, Interval.Ropen(2, 4))) == 1 - 36*exp(-6) + raises(ValueError, lambda: P(X(t) > 2, X(t) + X(d))) + assert E(X(t)) == 3*t # property of the distribution at a given timestamp + assert E(X(t)**2 + X(d)*2 + X(y)**3, Contains(t, Interval.Lopen(0, 1)) + & Contains(d, Interval.Lopen(1, 2)) & Contains(y, Interval.Ropen(3, 4))) == 75 + assert E(X(t)**2, Contains(t, Interval.Lopen(0, 1))) == 12 + assert E(x*(X(t) + X(d))*(X(t)**2+X(d)**2), Contains(t, Interval.Lopen(0, 1)) + & Contains(d, Interval.Ropen(1, 2))) == \ + Expectation(x*(X(d) + X(t))*(X(d)**2 + X(t)**2), Contains(t, Interval.Lopen(0, 1)) + & Contains(d, Interval.Ropen(1, 2))) + + # Value Error because of infinite time bound + raises(ValueError, lambda: E(X(t)**3, Contains(t, Interval.Lopen(1, oo)))) + + # Equivalent to E(X(t)**2) - E(X(d)**2) == E(X(1)**2) - E(X(1)**2) == 0 + assert E((X(t) + X(d))*(X(t) - X(d)), Contains(t, Interval.Lopen(0, 1)) + & Contains(d, Interval.Lopen(1, 2))) == 0 + assert E(X(2) + x*E(X(5))) == 15*x + 6 + assert E(x*X(1) + y) == 3*x + y + assert P(Eq(X(1), 2) & Eq(X(t), 3), Contains(t, Interval.Lopen(1, 2))) == 81*exp(-6)/4 + Y = PoissonProcess("Y", 6) + Z = X + Y + assert Z.lamda == X.lamda + Y.lamda == 9 + raises(ValueError, lambda: X + 5) # should be added be only PoissonProcess instance + N, M = Z.split(4, 5) + assert N.lamda == 4 + assert M.lamda == 5 + raises(ValueError, lambda: Z.split(3, 2)) # 2+3 != 9 + + raises(ValueError, lambda :P(Eq(X(t), 0), Contains(t, Interval.Lopen(1, 3)) & Eq(X(1), 0))) + # check if it handles queries with two random variables in one args + res1 = P(Eq(N(3), N(5))) + assert res1 == P(Eq(N(t), 0), Contains(t, Interval(3, 5))) + res2 = P(N(3) > N(1)) + assert res2 == P((N(t) > 0), Contains(t, Interval(1, 3))) + assert P(N(3) < N(1)) == 0 # condition is not possible + res3 = P(N(3) <= N(1)) # holds only for Eq(N(3), N(1)) + assert res3 == P(Eq(N(t), 0), Contains(t, Interval(1, 3))) + + # tests from https://www.probabilitycourse.com/chapter11/11_1_2_basic_concepts_of_the_poisson_process.php + X = PoissonProcess('X', 10) # 11.1 + assert P(Eq(X(S(1)/3), 3) & Eq(X(1), 10)) == exp(-10)*Rational(8000000000, 11160261) + assert P(Eq(X(1), 1), Eq(X(S(1)/3), 3)) == 0 + assert P(Eq(X(1), 10), Eq(X(S(1)/3), 3)) == P(Eq(X(S(2)/3), 7)) + + X = PoissonProcess('X', 2) # 11.2 + assert P(X(S(1)/2) < 1) == exp(-1) + assert P(X(3) < 1, Eq(X(1), 0)) == exp(-4) + assert P(Eq(X(4), 3), Eq(X(2), 3)) == exp(-4) + + X = PoissonProcess('X', 3) + assert P(Eq(X(2), 5) & Eq(X(1), 2)) == Rational(81, 4)*exp(-6) + + # check few properties + assert P(X(2) <= 3, X(1)>=1) == 3*P(Eq(X(1), 0)) + 2*P(Eq(X(1), 1)) + P(Eq(X(1), 2)) + assert P(X(2) <= 3, X(1) > 1) == 2*P(Eq(X(1), 0)) + 1*P(Eq(X(1), 1)) + assert P(Eq(X(2), 5) & Eq(X(1), 2)) == P(Eq(X(1), 3))*P(Eq(X(1), 2)) + assert P(Eq(X(3), 4), Eq(X(1), 3)) == P(Eq(X(2), 1)) + + #test issue 20078 + assert (2*X(t) + 3*X(t)).simplify() == 5*X(t) + assert (2*X(t) - 3*X(t)).simplify() == -X(t) + assert (2*(0.25*X(t))).simplify() == 0.5*X(t) + assert (2*X(t) * 0.25*X(t)).simplify() == 0.5*X(t)**2 + assert (X(t)**2 + X(t)**3).simplify() == (X(t) + 1)*X(t)**2 + +def test_WienerProcess(): + X = WienerProcess("X") + assert X.state_space == S.Reals + assert X.index_set == Interval(0, oo) + + t, d, x, y = symbols('t d x y', positive=True) + assert isinstance(X(t), RandomIndexedSymbol) + assert X.distribution(t) == NormalDistribution(0, sqrt(t)) + with warns_deprecated_sympy(): + X.distribution(X(t)) + raises(ValueError, lambda: PoissonProcess("X", -1)) + raises(NotImplementedError, lambda: X[t]) + raises(IndexError, lambda: X(-2)) + + assert X.joint_distribution(X(2), X(3)) == JointDistributionHandmade( + Lambda((X(2), X(3)), sqrt(6)*exp(-X(2)**2/4)*exp(-X(3)**2/6)/(12*pi))) + assert X.joint_distribution(4, 6) == JointDistributionHandmade( + Lambda((X(4), X(6)), sqrt(6)*exp(-X(4)**2/8)*exp(-X(6)**2/12)/(24*pi))) + + assert P(X(t) < 3).simplify() == erf(3*sqrt(2)/(2*sqrt(t)))/2 + S(1)/2 + assert P(X(t) > 2, Contains(t, Interval.Lopen(3, 7))).simplify() == S(1)/2 -\ + erf(sqrt(2)/2)/2 + + # Equivalent to P(X(1)>1)**4 + assert P((X(t) > 4) & (X(d) > 3) & (X(x) > 2) & (X(y) > 1), + Contains(t, Interval.Lopen(0, 1)) & Contains(d, Interval.Lopen(1, 2)) + & Contains(x, Interval.Lopen(2, 3)) & Contains(y, Interval.Lopen(3, 4))).simplify() ==\ + (1 - erf(sqrt(2)/2))*(1 - erf(sqrt(2)))*(1 - erf(3*sqrt(2)/2))*(1 - erf(2*sqrt(2)))/16 + + # Contains an overlapping interval so, return Probability + assert P((X(t)< 2) & (X(d)> 3), Contains(t, Interval.Lopen(0, 2)) + & Contains(d, Interval.Ropen(2, 4))) == Probability((X(d) > 3) & (X(t) < 2), + Contains(d, Interval.Ropen(2, 4)) & Contains(t, Interval.Lopen(0, 2))) + + assert str(P(Not((X(t) < 5) & (X(d) > 3)), Contains(t, Interval.Ropen(2, 4)) & + Contains(d, Interval.Lopen(7, 8))).simplify()) == \ + '-(1 - erf(3*sqrt(2)/2))*(2 - erfc(5/2))/4 + 1' + # Distribution has mean 0 at each timestamp + assert E(X(t)) == 0 + assert E(x*(X(t) + X(d))*(X(t)**2+X(d)**2), Contains(t, Interval.Lopen(0, 1)) + & Contains(d, Interval.Ropen(1, 2))) == Expectation(x*(X(d) + X(t))*(X(d)**2 + X(t)**2), + Contains(d, Interval.Ropen(1, 2)) & Contains(t, Interval.Lopen(0, 1))) + assert E(X(t) + x*E(X(3))) == 0 + + #test issue 20078 + assert (2*X(t) + 3*X(t)).simplify() == 5*X(t) + assert (2*X(t) - 3*X(t)).simplify() == -X(t) + assert (2*(0.25*X(t))).simplify() == 0.5*X(t) + assert (2*X(t) * 0.25*X(t)).simplify() == 0.5*X(t)**2 + assert (X(t)**2 + X(t)**3).simplify() == (X(t) + 1)*X(t)**2 + + +def test_GammaProcess_symbolic(): + t, d, x, y, g, l = symbols('t d x y g l', positive=True) + X = GammaProcess("X", l, g) + + raises(NotImplementedError, lambda: X[t]) + raises(IndexError, lambda: X(-1)) + assert isinstance(X(t), RandomIndexedSymbol) + assert X.state_space == Interval(0, oo) + assert X.distribution(t) == GammaDistribution(g*t, 1/l) + with warns_deprecated_sympy(): + X.distribution(X(t)) + assert X.joint_distribution(5, X(3)) == JointDistributionHandmade(Lambda( + (X(5), X(3)), l**(8*g)*exp(-l*X(3))*exp(-l*X(5))*X(3)**(3*g - 1)*X(5)**(5*g + - 1)/(gamma(3*g)*gamma(5*g)))) + # property of the gamma process at any given timestamp + assert E(X(t)) == g*t/l + assert variance(X(t)).simplify() == g*t/l**2 + + # Equivalent to E(2*X(1)) + E(X(1)**2) + E(X(1)**3), where E(X(1)) == g/l + assert E(X(t)**2 + X(d)*2 + X(y)**3, Contains(t, Interval.Lopen(0, 1)) + & Contains(d, Interval.Lopen(1, 2)) & Contains(y, Interval.Ropen(3, 4))) == \ + 2*g/l + (g**2 + g)/l**2 + (g**3 + 3*g**2 + 2*g)/l**3 + + assert P(X(t) > 3, Contains(t, Interval.Lopen(3, 4))).simplify() == \ + 1 - lowergamma(g, 3*l)/gamma(g) # equivalent to P(X(1)>3) + + + #test issue 20078 + assert (2*X(t) + 3*X(t)).simplify() == 5*X(t) + assert (2*X(t) - 3*X(t)).simplify() == -X(t) + assert (2*(0.25*X(t))).simplify() == 0.5*X(t) + assert (2*X(t) * 0.25*X(t)).simplify() == 0.5*X(t)**2 + assert (X(t)**2 + X(t)**3).simplify() == (X(t) + 1)*X(t)**2 +def test_GammaProcess_numeric(): + t, d, x, y = symbols('t d x y', positive=True) + X = GammaProcess("X", 1, 2) + assert X.state_space == Interval(0, oo) + assert X.index_set == Interval(0, oo) + assert X.lamda == 1 + assert X.gamma == 2 + + raises(ValueError, lambda: GammaProcess("X", -1, 2)) + raises(ValueError, lambda: GammaProcess("X", 0, -2)) + raises(ValueError, lambda: GammaProcess("X", -1, -2)) + + # all are independent because of non-overlapping intervals + assert P((X(t) > 4) & (X(d) > 3) & (X(x) > 2) & (X(y) > 1), Contains(t, + Interval.Lopen(0, 1)) & Contains(d, Interval.Lopen(1, 2)) & Contains(x, + Interval.Lopen(2, 3)) & Contains(y, Interval.Lopen(3, 4))).simplify() == \ + 120*exp(-10) + + # Check working with Not and Or + assert P(Not((X(t) < 5) & (X(d) > 3)), Contains(t, Interval.Ropen(2, 4)) & + Contains(d, Interval.Lopen(7, 8))).simplify() == -4*exp(-3) + 472*exp(-8)/3 + 1 + assert P((X(t) > 2) | (X(t) < 4), Contains(t, Interval.Ropen(1, 4))).simplify() == \ + -643*exp(-4)/15 + 109*exp(-2)/15 + 1 + + assert E(X(t)) == 2*t # E(X(t)) == gamma*t/l + assert E(X(2) + x*E(X(5))) == 10*x + 4 diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_symbolic_multivariate.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_symbolic_multivariate.py new file mode 100644 index 0000000000000000000000000000000000000000..79979e20a6f10d2a2ddfe85ce4c8df145e98c3fd --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_symbolic_multivariate.py @@ -0,0 +1,172 @@ +from sympy.stats import Expectation, Normal, Variance, Covariance +from sympy.testing.pytest import raises +from sympy.core.symbol import symbols +from sympy.matrices.exceptions import ShapeError +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import ZeroMatrix +from sympy.stats.rv import RandomMatrixSymbol +from sympy.stats.symbolic_multivariate_probability import (ExpectationMatrix, + VarianceMatrix, CrossCovarianceMatrix) + +j, k = symbols("j,k") + +A = MatrixSymbol("A", k, k) +B = MatrixSymbol("B", k, k) +C = MatrixSymbol("C", k, k) +D = MatrixSymbol("D", k, k) + +a = MatrixSymbol("a", k, 1) +b = MatrixSymbol("b", k, 1) + +A2 = MatrixSymbol("A2", 2, 2) +B2 = MatrixSymbol("B2", 2, 2) + +X = RandomMatrixSymbol("X", k, 1) +Y = RandomMatrixSymbol("Y", k, 1) +Z = RandomMatrixSymbol("Z", k, 1) +W = RandomMatrixSymbol("W", k, 1) + +R = RandomMatrixSymbol("R", k, k) + +X2 = RandomMatrixSymbol("X2", 2, 1) + +normal = Normal("normal", 0, 1) + +m1 = Matrix([ + [1, j*Normal("normal2", 2, 1)], + [normal, 0] +]) + +def test_multivariate_expectation(): + expr = Expectation(a) + assert expr == Expectation(a) == ExpectationMatrix(a) + assert expr.expand() == a + + expr = Expectation(X) + assert expr == Expectation(X) == ExpectationMatrix(X) + assert expr.shape == (k, 1) + assert expr.rows == k + assert expr.cols == 1 + assert isinstance(expr, ExpectationMatrix) + + expr = Expectation(A*X + b) + assert expr == ExpectationMatrix(A*X + b) + assert expr.expand() == A*ExpectationMatrix(X) + b + assert isinstance(expr, ExpectationMatrix) + assert expr.shape == (k, 1) + + expr = Expectation(m1*X2) + assert expr.expand() == expr + + expr = Expectation(A2*m1*B2*X2) + assert expr.args[0].args == (A2, m1, B2, X2) + assert expr.expand() == A2*ExpectationMatrix(m1*B2*X2) + + expr = Expectation((X + Y)*(X - Y).T) + assert expr.expand() == ExpectationMatrix(X*X.T) - ExpectationMatrix(X*Y.T) +\ + ExpectationMatrix(Y*X.T) - ExpectationMatrix(Y*Y.T) + + expr = Expectation(A*X + B*Y) + assert expr.expand() == A*ExpectationMatrix(X) + B*ExpectationMatrix(Y) + + assert Expectation(m1).doit() == Matrix([[1, 2*j], [0, 0]]) + + x1 = Matrix([ + [Normal('N11', 11, 1), Normal('N12', 12, 1)], + [Normal('N21', 21, 1), Normal('N22', 22, 1)] + ]) + x2 = Matrix([ + [Normal('M11', 1, 1), Normal('M12', 2, 1)], + [Normal('M21', 3, 1), Normal('M22', 4, 1)] + ]) + + assert Expectation(Expectation(x1 + x2)).doit(deep=False) == ExpectationMatrix(x1 + x2) + assert Expectation(Expectation(x1 + x2)).doit() == Matrix([[12, 14], [24, 26]]) + + +def test_multivariate_variance(): + raises(ShapeError, lambda: Variance(A)) + + expr = Variance(a) + assert expr == Variance(a) == VarianceMatrix(a) + assert expr.expand() == ZeroMatrix(k, k) + expr = Variance(a.T) + assert expr == Variance(a.T) == VarianceMatrix(a.T) + assert expr.expand() == ZeroMatrix(k, k) + + expr = Variance(X) + assert expr == Variance(X) == VarianceMatrix(X) + assert expr.shape == (k, k) + assert expr.rows == k + assert expr.cols == k + assert isinstance(expr, VarianceMatrix) + + expr = Variance(A*X) + assert expr == VarianceMatrix(A*X) + assert expr.expand() == A*VarianceMatrix(X)*A.T + assert isinstance(expr, VarianceMatrix) + assert expr.shape == (k, k) + + expr = Variance(A*B*X) + assert expr.expand() == A*B*VarianceMatrix(X)*B.T*A.T + + expr = Variance(m1*X2) + assert expr.expand() == expr + + expr = Variance(A2*m1*B2*X2) + assert expr.args[0].args == (A2, m1, B2, X2) + assert expr.expand() == expr + + expr = Variance(A*X + B*Y) + assert expr.expand() == 2*A*CrossCovarianceMatrix(X, Y)*B.T +\ + A*VarianceMatrix(X)*A.T + B*VarianceMatrix(Y)*B.T + +def test_multivariate_crosscovariance(): + raises(ShapeError, lambda: Covariance(X, Y.T)) + raises(ShapeError, lambda: Covariance(X, A)) + + + expr = Covariance(a.T, b.T) + assert expr.shape == (1, 1) + assert expr.expand() == ZeroMatrix(1, 1) + + expr = Covariance(a, b) + assert expr == Covariance(a, b) == CrossCovarianceMatrix(a, b) + assert expr.expand() == ZeroMatrix(k, k) + assert expr.shape == (k, k) + assert expr.rows == k + assert expr.cols == k + assert isinstance(expr, CrossCovarianceMatrix) + + expr = Covariance(A*X + a, b) + assert expr.expand() == ZeroMatrix(k, k) + + expr = Covariance(X, Y) + assert isinstance(expr, CrossCovarianceMatrix) + assert expr.expand() == expr + + expr = Covariance(X, X) + assert isinstance(expr, CrossCovarianceMatrix) + assert expr.expand() == VarianceMatrix(X) + + expr = Covariance(X + Y, Z) + assert isinstance(expr, CrossCovarianceMatrix) + assert expr.expand() == CrossCovarianceMatrix(X, Z) + CrossCovarianceMatrix(Y, Z) + + expr = Covariance(A*X, Y) + assert isinstance(expr, CrossCovarianceMatrix) + assert expr.expand() == A*CrossCovarianceMatrix(X, Y) + + expr = Covariance(X, B*Y) + assert isinstance(expr, CrossCovarianceMatrix) + assert expr.expand() == CrossCovarianceMatrix(X, Y)*B.T + + expr = Covariance(A*X + a, B.T*Y + b) + assert isinstance(expr, CrossCovarianceMatrix) + assert expr.expand() == A*CrossCovarianceMatrix(X, Y)*B + + expr = Covariance(A*X + B*Y + a, C.T*Z + D.T*W + b) + assert isinstance(expr, CrossCovarianceMatrix) + assert expr.expand() == A*CrossCovarianceMatrix(X, W)*D + A*CrossCovarianceMatrix(X, Z)*C \ + + B*CrossCovarianceMatrix(Y, W)*D + B*CrossCovarianceMatrix(Y, Z)*C diff --git a/venv/lib/python3.10/site-packages/sympy/stats/tests/test_symbolic_probability.py b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_symbolic_probability.py new file mode 100644 index 0000000000000000000000000000000000000000..edac942ac081c0d44cafd31761b77bc577b6a3fd --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/stats/tests/test_symbolic_probability.py @@ -0,0 +1,175 @@ +from sympy.concrete.summations import Sum +from sympy.core.mul import Mul +from sympy.core.numbers import (oo, pi) +from sympy.core.relational import Eq +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin +from sympy.integrals.integrals import Integral +from sympy.core.expr import unchanged +from sympy.stats import (Normal, Poisson, variance, Covariance, Variance, + Probability, Expectation, Moment, CentralMoment) +from sympy.stats.rv import probability, expectation + + +def test_literal_probability(): + X = Normal('X', 2, 3) + Y = Normal('Y', 3, 4) + Z = Poisson('Z', 4) + W = Poisson('W', 3) + x = symbols('x', real=True) + y, w, z = symbols('y, w, z') + + assert Probability(X > 0).evaluate_integral() == probability(X > 0) + assert Probability(X > x).evaluate_integral() == probability(X > x) + assert Probability(X > 0).rewrite(Integral).doit() == probability(X > 0) + assert Probability(X > x).rewrite(Integral).doit() == probability(X > x) + + assert Expectation(X).evaluate_integral() == expectation(X) + assert Expectation(X).rewrite(Integral).doit() == expectation(X) + assert Expectation(X**2).evaluate_integral() == expectation(X**2) + assert Expectation(x*X).args == (x*X,) + assert Expectation(x*X).expand() == x*Expectation(X) + assert Expectation(2*X + 3*Y + z*X*Y).expand() == 2*Expectation(X) + 3*Expectation(Y) + z*Expectation(X*Y) + assert Expectation(2*X + 3*Y + z*X*Y).args == (2*X + 3*Y + z*X*Y,) + assert Expectation(sin(X)) == Expectation(sin(X)).expand() + assert Expectation(2*x*sin(X)*Y + y*X**2 + z*X*Y).expand() == 2*x*Expectation(sin(X)*Y) \ + + y*Expectation(X**2) + z*Expectation(X*Y) + assert Expectation(X + Y).expand() == Expectation(X) + Expectation(Y) + assert Expectation((X + Y)*(X - Y)).expand() == Expectation(X**2) - Expectation(Y**2) + assert Expectation((X + Y)*(X - Y)).expand().doit() == -12 + assert Expectation(X + Y, evaluate=True).doit() == 5 + assert Expectation(X + Expectation(Y)).doit() == 5 + assert Expectation(X + Expectation(Y)).doit(deep=False) == 2 + Expectation(Expectation(Y)) + assert Expectation(X + Expectation(Y + Expectation(2*X))).doit(deep=False) == 2 \ + + Expectation(Expectation(Y + Expectation(2*X))) + assert Expectation(X + Expectation(Y + Expectation(2*X))).doit() == 9 + assert Expectation(Expectation(2*X)).doit() == 4 + assert Expectation(Expectation(2*X)).doit(deep=False) == Expectation(2*X) + assert Expectation(4*Expectation(2*X)).doit(deep=False) == 4*Expectation(2*X) + assert Expectation((X + Y)**3).expand() == 3*Expectation(X*Y**2) +\ + 3*Expectation(X**2*Y) + Expectation(X**3) + Expectation(Y**3) + assert Expectation((X - Y)**3).expand() == 3*Expectation(X*Y**2) -\ + 3*Expectation(X**2*Y) + Expectation(X**3) - Expectation(Y**3) + assert Expectation((X - Y)**2).expand() == -2*Expectation(X*Y) +\ + Expectation(X**2) + Expectation(Y**2) + + assert Variance(w).args == (w,) + assert Variance(w).expand() == 0 + assert Variance(X).evaluate_integral() == Variance(X).rewrite(Integral).doit() == variance(X) + assert Variance(X + z).args == (X + z,) + assert Variance(X + z).expand() == Variance(X) + assert Variance(X*Y).args == (Mul(X, Y),) + assert type(Variance(X*Y)) == Variance + assert Variance(z*X).expand() == z**2*Variance(X) + assert Variance(X + Y).expand() == Variance(X) + Variance(Y) + 2*Covariance(X, Y) + assert Variance(X + Y + Z + W).expand() == (Variance(X) + Variance(Y) + Variance(Z) + Variance(W) + + 2 * Covariance(X, Y) + 2 * Covariance(X, Z) + 2 * Covariance(X, W) + + 2 * Covariance(Y, Z) + 2 * Covariance(Y, W) + 2 * Covariance(W, Z)) + assert Variance(X**2).evaluate_integral() == variance(X**2) + assert unchanged(Variance, X**2) + assert Variance(x*X**2).expand() == x**2*Variance(X**2) + assert Variance(sin(X)).args == (sin(X),) + assert Variance(sin(X)).expand() == Variance(sin(X)) + assert Variance(x*sin(X)).expand() == x**2*Variance(sin(X)) + + assert Covariance(w, z).args == (w, z) + assert Covariance(w, z).expand() == 0 + assert Covariance(X, w).expand() == 0 + assert Covariance(w, X).expand() == 0 + assert Covariance(X, Y).args == (X, Y) + assert type(Covariance(X, Y)) == Covariance + assert Covariance(z*X + 3, Y).expand() == z*Covariance(X, Y) + assert Covariance(X, X).args == (X, X) + assert Covariance(X, X).expand() == Variance(X) + assert Covariance(z*X + 3, w*Y + 4).expand() == w*z*Covariance(X,Y) + assert Covariance(X, Y) == Covariance(Y, X) + assert Covariance(X + Y, Z + W).expand() == Covariance(W, X) + Covariance(W, Y) + Covariance(X, Z) + Covariance(Y, Z) + assert Covariance(x*X + y*Y, z*Z + w*W).expand() == (x*w*Covariance(W, X) + w*y*Covariance(W, Y) + + x*z*Covariance(X, Z) + y*z*Covariance(Y, Z)) + assert Covariance(x*X**2 + y*sin(Y), z*Y*Z**2 + w*W).expand() == (w*x*Covariance(W, X**2) + w*y*Covariance(sin(Y), W) + + x*z*Covariance(Y*Z**2, X**2) + y*z*Covariance(Y*Z**2, sin(Y))) + assert Covariance(X, X**2).expand() == Covariance(X, X**2) + assert Covariance(X, sin(X)).expand() == Covariance(sin(X), X) + assert Covariance(X**2, sin(X)*Y).expand() == Covariance(sin(X)*Y, X**2) + assert Covariance(w, X).evaluate_integral() == 0 + + +def test_probability_rewrite(): + X = Normal('X', 2, 3) + Y = Normal('Y', 3, 4) + Z = Poisson('Z', 4) + W = Poisson('W', 3) + x, y, w, z = symbols('x, y, w, z') + + assert Variance(w).rewrite(Expectation) == 0 + assert Variance(X).rewrite(Expectation) == Expectation(X ** 2) - Expectation(X) ** 2 + assert Variance(X, condition=Y).rewrite(Expectation) == Expectation(X ** 2, Y) - Expectation(X, Y) ** 2 + assert Variance(X, Y) != Expectation(X**2) - Expectation(X)**2 + assert Variance(X + z).rewrite(Expectation) == Expectation((X + z) ** 2) - Expectation(X + z) ** 2 + assert Variance(X * Y).rewrite(Expectation) == Expectation(X ** 2 * Y ** 2) - Expectation(X * Y) ** 2 + + assert Covariance(w, X).rewrite(Expectation) == -w*Expectation(X) + Expectation(w*X) + assert Covariance(X, Y).rewrite(Expectation) == Expectation(X*Y) - Expectation(X)*Expectation(Y) + assert Covariance(X, Y, condition=W).rewrite(Expectation) == Expectation(X * Y, W) - Expectation(X, W) * Expectation(Y, W) + + w, x, z = symbols("W, x, z") + px = Probability(Eq(X, x)) + pz = Probability(Eq(Z, z)) + + assert Expectation(X).rewrite(Probability) == Integral(x*px, (x, -oo, oo)) + assert Expectation(Z).rewrite(Probability) == Sum(z*pz, (z, 0, oo)) + assert Variance(X).rewrite(Probability) == Integral(x**2*px, (x, -oo, oo)) - Integral(x*px, (x, -oo, oo))**2 + assert Variance(Z).rewrite(Probability) == Sum(z**2*pz, (z, 0, oo)) - Sum(z*pz, (z, 0, oo))**2 + assert Covariance(w, X).rewrite(Probability) == \ + -w*Integral(x*Probability(Eq(X, x)), (x, -oo, oo)) + Integral(w*x*Probability(Eq(X, x)), (x, -oo, oo)) + + # To test rewrite as sum function + assert Variance(X).rewrite(Sum) == Variance(X).rewrite(Integral) + assert Expectation(X).rewrite(Sum) == Expectation(X).rewrite(Integral) + + assert Covariance(w, X).rewrite(Sum) == 0 + + assert Covariance(w, X).rewrite(Integral) == 0 + + assert Variance(X, condition=Y).rewrite(Probability) == Integral(x**2*Probability(Eq(X, x), Y), (x, -oo, oo)) - \ + Integral(x*Probability(Eq(X, x), Y), (x, -oo, oo))**2 + + +def test_symbolic_Moment(): + mu = symbols('mu', real=True) + sigma = symbols('sigma', positive=True) + x = symbols('x') + X = Normal('X', mu, sigma) + M = Moment(X, 4, 2) + assert M.rewrite(Expectation) == Expectation((X - 2)**4) + assert M.rewrite(Probability) == Integral((x - 2)**4*Probability(Eq(X, x)), + (x, -oo, oo)) + k = Dummy('k') + expri = Integral(sqrt(2)*(k - 2)**4*exp(-(k - \ + mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (k, -oo, oo)) + assert M.rewrite(Integral).dummy_eq(expri) + assert M.doit() == (mu**4 - 8*mu**3 + 6*mu**2*sigma**2 + \ + 24*mu**2 - 24*mu*sigma**2 - 32*mu + 3*sigma**4 + 24*sigma**2 + 16) + M = Moment(2, 5) + assert M.doit() == 2**5 + + +def test_symbolic_CentralMoment(): + mu = symbols('mu', real=True) + sigma = symbols('sigma', positive=True) + x = symbols('x') + X = Normal('X', mu, sigma) + CM = CentralMoment(X, 6) + assert CM.rewrite(Expectation) == Expectation((X - Expectation(X))**6) + assert CM.rewrite(Probability) == Integral((x - Integral(x*Probability(True), + (x, -oo, oo)))**6*Probability(Eq(X, x)), (x, -oo, oo)) + k = Dummy('k') + expri = Integral(sqrt(2)*(k - Integral(sqrt(2)*k*exp(-(k - \ + mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (k, -oo, oo)))**6*exp(-(k - \ + mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (k, -oo, oo)) + assert CM.rewrite(Integral).dummy_eq(expri) + assert CM.doit().simplify() == 15*sigma**6 + CM = Moment(5, 5) + assert CM.doit() == 5**5 diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/__init__.py b/venv/lib/python3.10/site-packages/sympy/tensor/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a832614b1d48e26bf01e16f040f34dd412e8e32b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/__init__.py @@ -0,0 +1,23 @@ +"""A module to manipulate symbolic objects with indices including tensors + +""" +from .indexed import IndexedBase, Idx, Indexed +from .index_methods import get_contraction_structure, get_indices +from .functions import shape +from .array import (MutableDenseNDimArray, ImmutableDenseNDimArray, + MutableSparseNDimArray, ImmutableSparseNDimArray, NDimArray, tensorproduct, + tensorcontraction, tensordiagonal, derive_by_array, permutedims, Array, + DenseNDimArray, SparseNDimArray,) + +__all__ = [ + 'IndexedBase', 'Idx', 'Indexed', + + 'get_contraction_structure', 'get_indices', + + 'shape', + + 'MutableDenseNDimArray', 'ImmutableDenseNDimArray', + 'MutableSparseNDimArray', 'ImmutableSparseNDimArray', 'NDimArray', + 'tensorproduct', 'tensorcontraction', 'tensordiagonal', 'derive_by_array', 'permutedims', + 'Array', 'DenseNDimArray', 'SparseNDimArray', +] diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b27e4e94aa935e882e0e4782f613e4fb08d8d860 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/functions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/functions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6adf2357c980abbaadcab3c1070475095f61a0bd Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/functions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/index_methods.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/index_methods.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f6254a65f1b56d68dc5c55c7335e227bdd3b13e Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/index_methods.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/indexed.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/indexed.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9e0e64615c2c0f2bd2d0c4e8c1d738aa4477da6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/indexed.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/toperators.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/toperators.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa6e082a4def96f96111c43a541397c8179cbbc6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/__pycache__/toperators.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__init__.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..eca2eb4c6c58cb113517b6e41737e9d97abbb84e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/__init__.py @@ -0,0 +1,271 @@ +r""" +N-dim array module for SymPy. + +Four classes are provided to handle N-dim arrays, given by the combinations +dense/sparse (i.e. whether to store all elements or only the non-zero ones in +memory) and mutable/immutable (immutable classes are SymPy objects, but cannot +change after they have been created). + +Examples +======== + +The following examples show the usage of ``Array``. This is an abbreviation for +``ImmutableDenseNDimArray``, that is an immutable and dense N-dim array, the +other classes are analogous. For mutable classes it is also possible to change +element values after the object has been constructed. + +Array construction can detect the shape of nested lists and tuples: + +>>> from sympy import Array +>>> a1 = Array([[1, 2], [3, 4], [5, 6]]) +>>> a1 +[[1, 2], [3, 4], [5, 6]] +>>> a1.shape +(3, 2) +>>> a1.rank() +2 +>>> from sympy.abc import x, y, z +>>> a2 = Array([[[x, y], [z, x*z]], [[1, x*y], [1/x, x/y]]]) +>>> a2 +[[[x, y], [z, x*z]], [[1, x*y], [1/x, x/y]]] +>>> a2.shape +(2, 2, 2) +>>> a2.rank() +3 + +Otherwise one could pass a 1-dim array followed by a shape tuple: + +>>> m1 = Array(range(12), (3, 4)) +>>> m1 +[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]] +>>> m2 = Array(range(12), (3, 2, 2)) +>>> m2 +[[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]] +>>> m2[1,1,1] +7 +>>> m2.reshape(4, 3) +[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]] + +Slice support: + +>>> m2[:, 1, 1] +[3, 7, 11] + +Elementwise derivative: + +>>> from sympy.abc import x, y, z +>>> m3 = Array([x**3, x*y, z]) +>>> m3.diff(x) +[3*x**2, y, 0] +>>> m3.diff(z) +[0, 0, 1] + +Multiplication with other SymPy expressions is applied elementwisely: + +>>> (1+x)*m3 +[x**3*(x + 1), x*y*(x + 1), z*(x + 1)] + +To apply a function to each element of the N-dim array, use ``applyfunc``: + +>>> m3.applyfunc(lambda x: x/2) +[x**3/2, x*y/2, z/2] + +N-dim arrays can be converted to nested lists by the ``tolist()`` method: + +>>> m2.tolist() +[[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]] +>>> isinstance(m2.tolist(), list) +True + +If the rank is 2, it is possible to convert them to matrices with ``tomatrix()``: + +>>> m1.tomatrix() +Matrix([ +[0, 1, 2, 3], +[4, 5, 6, 7], +[8, 9, 10, 11]]) + +Products and contractions +------------------------- + +Tensor product between arrays `A_{i_1,\ldots,i_n}` and `B_{j_1,\ldots,j_m}` +creates the combined array `P = A \otimes B` defined as + +`P_{i_1,\ldots,i_n,j_1,\ldots,j_m} := A_{i_1,\ldots,i_n}\cdot B_{j_1,\ldots,j_m}.` + +It is available through ``tensorproduct(...)``: + +>>> from sympy import Array, tensorproduct +>>> from sympy.abc import x,y,z,t +>>> A = Array([x, y, z, t]) +>>> B = Array([1, 2, 3, 4]) +>>> tensorproduct(A, B) +[[x, 2*x, 3*x, 4*x], [y, 2*y, 3*y, 4*y], [z, 2*z, 3*z, 4*z], [t, 2*t, 3*t, 4*t]] + +In case you don't want to evaluate the tensor product immediately, you can use +``ArrayTensorProduct``, which creates an unevaluated tensor product expression: + +>>> from sympy.tensor.array.expressions import ArrayTensorProduct +>>> ArrayTensorProduct(A, B) +ArrayTensorProduct([x, y, z, t], [1, 2, 3, 4]) + +Calling ``.as_explicit()`` on ``ArrayTensorProduct`` is equivalent to just calling +``tensorproduct(...)``: + +>>> ArrayTensorProduct(A, B).as_explicit() +[[x, 2*x, 3*x, 4*x], [y, 2*y, 3*y, 4*y], [z, 2*z, 3*z, 4*z], [t, 2*t, 3*t, 4*t]] + +Tensor product between a rank-1 array and a matrix creates a rank-3 array: + +>>> from sympy import eye +>>> p1 = tensorproduct(A, eye(4)) +>>> p1 +[[[x, 0, 0, 0], [0, x, 0, 0], [0, 0, x, 0], [0, 0, 0, x]], [[y, 0, 0, 0], [0, y, 0, 0], [0, 0, y, 0], [0, 0, 0, y]], [[z, 0, 0, 0], [0, z, 0, 0], [0, 0, z, 0], [0, 0, 0, z]], [[t, 0, 0, 0], [0, t, 0, 0], [0, 0, t, 0], [0, 0, 0, t]]] + +Now, to get back `A_0 \otimes \mathbf{1}` one can access `p_{0,m,n}` by slicing: + +>>> p1[0,:,:] +[[x, 0, 0, 0], [0, x, 0, 0], [0, 0, x, 0], [0, 0, 0, x]] + +Tensor contraction sums over the specified axes, for example contracting +positions `a` and `b` means + +`A_{i_1,\ldots,i_a,\ldots,i_b,\ldots,i_n} \implies \sum_k A_{i_1,\ldots,k,\ldots,k,\ldots,i_n}` + +Remember that Python indexing is zero starting, to contract the a-th and b-th +axes it is therefore necessary to specify `a-1` and `b-1` + +>>> from sympy import tensorcontraction +>>> C = Array([[x, y], [z, t]]) + +The matrix trace is equivalent to the contraction of a rank-2 array: + +`A_{m,n} \implies \sum_k A_{k,k}` + +>>> tensorcontraction(C, (0, 1)) +t + x + +To create an expression representing a tensor contraction that does not get +evaluated immediately, use ``ArrayContraction``, which is equivalent to +``tensorcontraction(...)`` if it is followed by ``.as_explicit()``: + +>>> from sympy.tensor.array.expressions import ArrayContraction +>>> ArrayContraction(C, (0, 1)) +ArrayContraction([[x, y], [z, t]], (0, 1)) +>>> ArrayContraction(C, (0, 1)).as_explicit() +t + x + +Matrix product is equivalent to a tensor product of two rank-2 arrays, followed +by a contraction of the 2nd and 3rd axes (in Python indexing axes number 1, 2). + +`A_{m,n}\cdot B_{i,j} \implies \sum_k A_{m, k}\cdot B_{k, j}` + +>>> D = Array([[2, 1], [0, -1]]) +>>> tensorcontraction(tensorproduct(C, D), (1, 2)) +[[2*x, x - y], [2*z, -t + z]] + +One may verify that the matrix product is equivalent: + +>>> from sympy import Matrix +>>> Matrix([[x, y], [z, t]])*Matrix([[2, 1], [0, -1]]) +Matrix([ +[2*x, x - y], +[2*z, -t + z]]) + +or equivalently + +>>> C.tomatrix()*D.tomatrix() +Matrix([ +[2*x, x - y], +[2*z, -t + z]]) + +Diagonal operator +----------------- + +The ``tensordiagonal`` function acts in a similar manner as ``tensorcontraction``, +but the joined indices are not summed over, for example diagonalizing +positions `a` and `b` means + +`A_{i_1,\ldots,i_a,\ldots,i_b,\ldots,i_n} \implies A_{i_1,\ldots,k,\ldots,k,\ldots,i_n} +\implies \tilde{A}_{i_1,\ldots,i_{a-1},i_{a+1},\ldots,i_{b-1},i_{b+1},\ldots,i_n,k}` + +where `\tilde{A}` is the array equivalent to the diagonal of `A` at positions +`a` and `b` moved to the last index slot. + +Compare the difference between contraction and diagonal operators: + +>>> from sympy import tensordiagonal +>>> from sympy.abc import a, b, c, d +>>> m = Matrix([[a, b], [c, d]]) +>>> tensorcontraction(m, [0, 1]) +a + d +>>> tensordiagonal(m, [0, 1]) +[a, d] + +In short, no summation occurs with ``tensordiagonal``. + + +Derivatives by array +-------------------- + +The usual derivative operation may be extended to support derivation with +respect to arrays, provided that all elements in the that array are symbols or +expressions suitable for derivations. + +The definition of a derivative by an array is as follows: given the array +`A_{i_1, \ldots, i_N}` and the array `X_{j_1, \ldots, j_M}` +the derivative of arrays will return a new array `B` defined by + +`B_{j_1,\ldots,j_M,i_1,\ldots,i_N} := \frac{\partial A_{i_1,\ldots,i_N}}{\partial X_{j_1,\ldots,j_M}}` + +The function ``derive_by_array`` performs such an operation: + +>>> from sympy import derive_by_array +>>> from sympy.abc import x, y, z, t +>>> from sympy import sin, exp + +With scalars, it behaves exactly as the ordinary derivative: + +>>> derive_by_array(sin(x*y), x) +y*cos(x*y) + +Scalar derived by an array basis: + +>>> derive_by_array(sin(x*y), [x, y, z]) +[y*cos(x*y), x*cos(x*y), 0] + +Deriving array by an array basis: `B^{nm} := \frac{\partial A^m}{\partial x^n}` + +>>> basis = [x, y, z] +>>> ax = derive_by_array([exp(x), sin(y*z), t], basis) +>>> ax +[[exp(x), 0, 0], [0, z*cos(y*z), 0], [0, y*cos(y*z), 0]] + +Contraction of the resulting array: `\sum_m \frac{\partial A^m}{\partial x^m}` + +>>> tensorcontraction(ax, (0, 1)) +z*cos(y*z) + exp(x) + +""" + +from .dense_ndim_array import MutableDenseNDimArray, ImmutableDenseNDimArray, DenseNDimArray +from .sparse_ndim_array import MutableSparseNDimArray, ImmutableSparseNDimArray, SparseNDimArray +from .ndim_array import NDimArray, ArrayKind +from .arrayop import tensorproduct, tensorcontraction, tensordiagonal, derive_by_array, permutedims +from .array_comprehension import ArrayComprehension, ArrayComprehensionMap + +Array = ImmutableDenseNDimArray + +__all__ = [ + 'MutableDenseNDimArray', 'ImmutableDenseNDimArray', 'DenseNDimArray', + + 'MutableSparseNDimArray', 'ImmutableSparseNDimArray', 'SparseNDimArray', + + 'NDimArray', 'ArrayKind', + + 'tensorproduct', 'tensorcontraction', 'tensordiagonal', 'derive_by_array', + + 'permutedims', 'ArrayComprehension', 'ArrayComprehensionMap', + + 'Array', +] diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64ff805b8b3167b7b003862e11abc525bfaef156 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/array_comprehension.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/array_comprehension.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..440cc24f444dd4d08e5008a50c2046c8b7a2ef1b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/array_comprehension.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/array_derivatives.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/array_derivatives.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec6a9a42de91f01ea079638b83b3aaa94124d71d Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/array_derivatives.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/arrayop.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/arrayop.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66cd111df8b21a850b9ecd70a1a53f12660e1235 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/arrayop.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/dense_ndim_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/dense_ndim_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71601d0740ab56f26021a35e17f5bda642e44df5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/dense_ndim_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/mutable_ndim_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/mutable_ndim_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb9f9776a0d56d8eb2c23602ba1575ce69845913 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/mutable_ndim_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/ndim_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/ndim_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0176f0834e953003a77810d1861a51d310227fc Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/ndim_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/sparse_ndim_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/sparse_ndim_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4104e6d0cd664348fb39781867d32b734ae177f3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/__pycache__/sparse_ndim_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/array_comprehension.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/array_comprehension.py new file mode 100644 index 0000000000000000000000000000000000000000..95702f499f3e40597fd0144929138ac1329962ee --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/array_comprehension.py @@ -0,0 +1,399 @@ +import functools, itertools +from sympy.core.sympify import _sympify, sympify +from sympy.core.expr import Expr +from sympy.core import Basic, Tuple +from sympy.tensor.array import ImmutableDenseNDimArray +from sympy.core.symbol import Symbol +from sympy.core.numbers import Integer + + +class ArrayComprehension(Basic): + """ + Generate a list comprehension. + + Explanation + =========== + + If there is a symbolic dimension, for example, say [i for i in range(1, N)] where + N is a Symbol, then the expression will not be expanded to an array. Otherwise, + calling the doit() function will launch the expansion. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j, k = symbols('i j k') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a + ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.doit() + [[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]] + >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k)) + >>> b.doit() + ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k)) + """ + def __new__(cls, function, *symbols, **assumptions): + if any(len(l) != 3 or None for l in symbols): + raise ValueError('ArrayComprehension requires values lower and upper bound' + ' for the expression') + arglist = [sympify(function)] + arglist.extend(cls._check_limits_validity(function, symbols)) + obj = Basic.__new__(cls, *arglist, **assumptions) + obj._limits = obj._args[1:] + obj._shape = cls._calculate_shape_from_limits(obj._limits) + obj._rank = len(obj._shape) + obj._loop_size = cls._calculate_loop_size(obj._shape) + return obj + + @property + def function(self): + """The function applied across limits. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j = symbols('i j') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.function + 10*i + j + """ + return self._args[0] + + @property + def limits(self): + """ + The list of limits that will be applied while expanding the array. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j = symbols('i j') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.limits + ((i, 1, 4), (j, 1, 3)) + """ + return self._limits + + @property + def free_symbols(self): + """ + The set of the free_symbols in the array. + Variables appeared in the bounds are supposed to be excluded + from the free symbol set. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j, k = symbols('i j k') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.free_symbols + set() + >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3)) + >>> b.free_symbols + {k} + """ + expr_free_sym = self.function.free_symbols + for var, inf, sup in self._limits: + expr_free_sym.discard(var) + curr_free_syms = inf.free_symbols.union(sup.free_symbols) + expr_free_sym = expr_free_sym.union(curr_free_syms) + return expr_free_sym + + @property + def variables(self): + """The tuples of the variables in the limits. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j, k = symbols('i j k') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.variables + [i, j] + """ + return [l[0] for l in self._limits] + + @property + def bound_symbols(self): + """The list of dummy variables. + + Note + ==== + + Note that all variables are dummy variables since a limit without + lower bound or upper bound is not accepted. + """ + return [l[0] for l in self._limits if len(l) != 1] + + @property + def shape(self): + """ + The shape of the expanded array, which may have symbols. + + Note + ==== + + Both the lower and the upper bounds are included while + calculating the shape. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j, k = symbols('i j k') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.shape + (4, 3) + >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3)) + >>> b.shape + (4, k + 3) + """ + return self._shape + + @property + def is_shape_numeric(self): + """ + Test if the array is shape-numeric which means there is no symbolic + dimension. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j, k = symbols('i j k') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.is_shape_numeric + True + >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3)) + >>> b.is_shape_numeric + False + """ + for _, inf, sup in self._limits: + if Basic(inf, sup).atoms(Symbol): + return False + return True + + def rank(self): + """The rank of the expanded array. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j, k = symbols('i j k') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.rank() + 2 + """ + return self._rank + + def __len__(self): + """ + The length of the expanded array which means the number + of elements in the array. + + Raises + ====== + + ValueError : When the length of the array is symbolic + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j = symbols('i j') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> len(a) + 12 + """ + if self._loop_size.free_symbols: + raise ValueError('Symbolic length is not supported') + return self._loop_size + + @classmethod + def _check_limits_validity(cls, function, limits): + #limits = sympify(limits) + new_limits = [] + for var, inf, sup in limits: + var = _sympify(var) + inf = _sympify(inf) + #since this is stored as an argument, it should be + #a Tuple + if isinstance(sup, list): + sup = Tuple(*sup) + else: + sup = _sympify(sup) + new_limits.append(Tuple(var, inf, sup)) + if any((not isinstance(i, Expr)) or i.atoms(Symbol, Integer) != i.atoms() + for i in [inf, sup]): + raise TypeError('Bounds should be an Expression(combination of Integer and Symbol)') + if (inf > sup) == True: + raise ValueError('Lower bound should be inferior to upper bound') + if var in inf.free_symbols or var in sup.free_symbols: + raise ValueError('Variable should not be part of its bounds') + return new_limits + + @classmethod + def _calculate_shape_from_limits(cls, limits): + return tuple([sup - inf + 1 for _, inf, sup in limits]) + + @classmethod + def _calculate_loop_size(cls, shape): + if not shape: + return 0 + loop_size = 1 + for l in shape: + loop_size = loop_size * l + + return loop_size + + def doit(self, **hints): + if not self.is_shape_numeric: + return self + + return self._expand_array() + + def _expand_array(self): + res = [] + for values in itertools.product(*[range(inf, sup+1) + for var, inf, sup + in self._limits]): + res.append(self._get_element(values)) + + return ImmutableDenseNDimArray(res, self.shape) + + def _get_element(self, values): + temp = self.function + for var, val in zip(self.variables, values): + temp = temp.subs(var, val) + return temp + + def tolist(self): + """Transform the expanded array to a list. + + Raises + ====== + + ValueError : When there is a symbolic dimension + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j = symbols('i j') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.tolist() + [[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]] + """ + if self.is_shape_numeric: + return self._expand_array().tolist() + + raise ValueError("A symbolic array cannot be expanded to a list") + + def tomatrix(self): + """Transform the expanded array to a matrix. + + Raises + ====== + + ValueError : When there is a symbolic dimension + ValueError : When the rank of the expanded array is not equal to 2 + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehension + >>> from sympy import symbols + >>> i, j = symbols('i j') + >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3)) + >>> a.tomatrix() + Matrix([ + [11, 12, 13], + [21, 22, 23], + [31, 32, 33], + [41, 42, 43]]) + """ + from sympy.matrices import Matrix + + if not self.is_shape_numeric: + raise ValueError("A symbolic array cannot be expanded to a matrix") + if self._rank != 2: + raise ValueError('Dimensions must be of size of 2') + + return Matrix(self._expand_array().tomatrix()) + + +def isLambda(v): + LAMBDA = lambda: 0 + return isinstance(v, type(LAMBDA)) and v.__name__ == LAMBDA.__name__ + +class ArrayComprehensionMap(ArrayComprehension): + ''' + A subclass of ArrayComprehension dedicated to map external function lambda. + + Notes + ===== + + Only the lambda function is considered. + At most one argument in lambda function is accepted in order to avoid ambiguity + in value assignment. + + Examples + ======== + + >>> from sympy.tensor.array import ArrayComprehensionMap + >>> from sympy import symbols + >>> i, j, k = symbols('i j k') + >>> a = ArrayComprehensionMap(lambda: 1, (i, 1, 4)) + >>> a.doit() + [1, 1, 1, 1] + >>> b = ArrayComprehensionMap(lambda a: a+1, (j, 1, 4)) + >>> b.doit() + [2, 3, 4, 5] + + ''' + def __new__(cls, function, *symbols, **assumptions): + if any(len(l) != 3 or None for l in symbols): + raise ValueError('ArrayComprehension requires values lower and upper bound' + ' for the expression') + + if not isLambda(function): + raise ValueError('Data type not supported') + + arglist = cls._check_limits_validity(function, symbols) + obj = Basic.__new__(cls, *arglist, **assumptions) + obj._limits = obj._args + obj._shape = cls._calculate_shape_from_limits(obj._limits) + obj._rank = len(obj._shape) + obj._loop_size = cls._calculate_loop_size(obj._shape) + obj._lambda = function + return obj + + @property + def func(self): + class _(ArrayComprehensionMap): + def __new__(cls, *args, **kwargs): + return ArrayComprehensionMap(self._lambda, *args, **kwargs) + return _ + + def _get_element(self, values): + temp = self._lambda + if self._lambda.__code__.co_argcount == 0: + temp = temp() + elif self._lambda.__code__.co_argcount == 1: + temp = temp(functools.reduce(lambda a, b: a*b, values)) + return temp diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/array_derivatives.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/array_derivatives.py new file mode 100644 index 0000000000000000000000000000000000000000..a38db6caefe256a8c7e1f3415b78351b3787fee9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/array_derivatives.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from sympy.core.expr import Expr +from sympy.core.function import Derivative +from sympy.core.numbers import Integer +from sympy.matrices.matrixbase import MatrixBase +from .ndim_array import NDimArray +from .arrayop import derive_by_array +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.matrices.expressions.special import ZeroMatrix +from sympy.matrices.expressions.matexpr import _matrix_derivative + + +class ArrayDerivative(Derivative): + + is_scalar = False + + def __new__(cls, expr, *variables, **kwargs): + obj = super().__new__(cls, expr, *variables, **kwargs) + if isinstance(obj, ArrayDerivative): + obj._shape = obj._get_shape() + return obj + + def _get_shape(self): + shape = () + for v, count in self.variable_count: + if hasattr(v, "shape"): + for i in range(count): + shape += v.shape + if hasattr(self.expr, "shape"): + shape += self.expr.shape + return shape + + @property + def shape(self): + return self._shape + + @classmethod + def _get_zero_with_shape_like(cls, expr): + if isinstance(expr, (MatrixBase, NDimArray)): + return expr.zeros(*expr.shape) + elif isinstance(expr, MatrixExpr): + return ZeroMatrix(*expr.shape) + else: + raise RuntimeError("Unable to determine shape of array-derivative.") + + @staticmethod + def _call_derive_scalar_by_matrix(expr: Expr, v: MatrixBase) -> Expr: + return v.applyfunc(lambda x: expr.diff(x)) + + @staticmethod + def _call_derive_scalar_by_matexpr(expr: Expr, v: MatrixExpr) -> Expr: + if expr.has(v): + return _matrix_derivative(expr, v) + else: + return ZeroMatrix(*v.shape) + + @staticmethod + def _call_derive_scalar_by_array(expr: Expr, v: NDimArray) -> Expr: + return v.applyfunc(lambda x: expr.diff(x)) + + @staticmethod + def _call_derive_matrix_by_scalar(expr: MatrixBase, v: Expr) -> Expr: + return _matrix_derivative(expr, v) + + @staticmethod + def _call_derive_matexpr_by_scalar(expr: MatrixExpr, v: Expr) -> Expr: + return expr._eval_derivative(v) + + @staticmethod + def _call_derive_array_by_scalar(expr: NDimArray, v: Expr) -> Expr: + return expr.applyfunc(lambda x: x.diff(v)) + + @staticmethod + def _call_derive_default(expr: Expr, v: Expr) -> Expr | None: + if expr.has(v): + return _matrix_derivative(expr, v) + else: + return None + + @classmethod + def _dispatch_eval_derivative_n_times(cls, expr, v, count): + # Evaluate the derivative `n` times. If + # `_eval_derivative_n_times` is not overridden by the current + # object, the default in `Basic` will call a loop over + # `_eval_derivative`: + + if not isinstance(count, (int, Integer)) or ((count <= 0) == True): + return None + + # TODO: this could be done with multiple-dispatching: + if expr.is_scalar: + if isinstance(v, MatrixBase): + result = cls._call_derive_scalar_by_matrix(expr, v) + elif isinstance(v, MatrixExpr): + result = cls._call_derive_scalar_by_matexpr(expr, v) + elif isinstance(v, NDimArray): + result = cls._call_derive_scalar_by_array(expr, v) + elif v.is_scalar: + # scalar by scalar has a special + return super()._dispatch_eval_derivative_n_times(expr, v, count) + else: + return None + elif v.is_scalar: + if isinstance(expr, MatrixBase): + result = cls._call_derive_matrix_by_scalar(expr, v) + elif isinstance(expr, MatrixExpr): + result = cls._call_derive_matexpr_by_scalar(expr, v) + elif isinstance(expr, NDimArray): + result = cls._call_derive_array_by_scalar(expr, v) + else: + return None + else: + # Both `expr` and `v` are some array/matrix type: + if isinstance(expr, MatrixBase) or isinstance(v, MatrixBase): + result = derive_by_array(expr, v) + elif isinstance(expr, MatrixExpr) and isinstance(v, MatrixExpr): + result = cls._call_derive_default(expr, v) + elif isinstance(expr, MatrixExpr) or isinstance(v, MatrixExpr): + # if one expression is a symbolic matrix expression while the other isn't, don't evaluate: + return None + else: + result = derive_by_array(expr, v) + if result is None: + return None + if count == 1: + return result + else: + return cls._dispatch_eval_derivative_n_times(result, v, count - 1) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/arrayop.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/arrayop.py new file mode 100644 index 0000000000000000000000000000000000000000..a81e6b381a8a93f0cd585278a4be0259b06406dd --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/arrayop.py @@ -0,0 +1,528 @@ +import itertools +from collections.abc import Iterable + +from sympy.core._print_helpers import Printable +from sympy.core.containers import Tuple +from sympy.core.function import diff +from sympy.core.singleton import S +from sympy.core.sympify import _sympify + +from sympy.tensor.array.ndim_array import NDimArray +from sympy.tensor.array.dense_ndim_array import DenseNDimArray, ImmutableDenseNDimArray +from sympy.tensor.array.sparse_ndim_array import SparseNDimArray + + +def _arrayfy(a): + from sympy.matrices import MatrixBase + + if isinstance(a, NDimArray): + return a + if isinstance(a, (MatrixBase, list, tuple, Tuple)): + return ImmutableDenseNDimArray(a) + return a + + +def tensorproduct(*args): + """ + Tensor product among scalars or array-like objects. + + The equivalent operator for array expressions is ``ArrayTensorProduct``, + which can be used to keep the expression unevaluated. + + Examples + ======== + + >>> from sympy.tensor.array import tensorproduct, Array + >>> from sympy.abc import x, y, z, t + >>> A = Array([[1, 2], [3, 4]]) + >>> B = Array([x, y]) + >>> tensorproduct(A, B) + [[[x, y], [2*x, 2*y]], [[3*x, 3*y], [4*x, 4*y]]] + >>> tensorproduct(A, x) + [[x, 2*x], [3*x, 4*x]] + >>> tensorproduct(A, B, B) + [[[[x**2, x*y], [x*y, y**2]], [[2*x**2, 2*x*y], [2*x*y, 2*y**2]]], [[[3*x**2, 3*x*y], [3*x*y, 3*y**2]], [[4*x**2, 4*x*y], [4*x*y, 4*y**2]]]] + + Applying this function on two matrices will result in a rank 4 array. + + >>> from sympy import Matrix, eye + >>> m = Matrix([[x, y], [z, t]]) + >>> p = tensorproduct(eye(3), m) + >>> p + [[[[x, y], [z, t]], [[0, 0], [0, 0]], [[0, 0], [0, 0]]], [[[0, 0], [0, 0]], [[x, y], [z, t]], [[0, 0], [0, 0]]], [[[0, 0], [0, 0]], [[0, 0], [0, 0]], [[x, y], [z, t]]]] + + See Also + ======== + + sympy.tensor.array.expressions.array_expressions.ArrayTensorProduct + + """ + from sympy.tensor.array import SparseNDimArray, ImmutableSparseNDimArray + + if len(args) == 0: + return S.One + if len(args) == 1: + return _arrayfy(args[0]) + from sympy.tensor.array.expressions.array_expressions import _CodegenArrayAbstract + from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct + from sympy.tensor.array.expressions.array_expressions import _ArrayExpr + from sympy.matrices.expressions.matexpr import MatrixSymbol + if any(isinstance(arg, (_ArrayExpr, _CodegenArrayAbstract, MatrixSymbol)) for arg in args): + return ArrayTensorProduct(*args) + if len(args) > 2: + return tensorproduct(tensorproduct(args[0], args[1]), *args[2:]) + + # length of args is 2: + a, b = map(_arrayfy, args) + + if not isinstance(a, NDimArray) or not isinstance(b, NDimArray): + return a*b + + if isinstance(a, SparseNDimArray) and isinstance(b, SparseNDimArray): + lp = len(b) + new_array = {k1*lp + k2: v1*v2 for k1, v1 in a._sparse_array.items() for k2, v2 in b._sparse_array.items()} + return ImmutableSparseNDimArray(new_array, a.shape + b.shape) + + product_list = [i*j for i in Flatten(a) for j in Flatten(b)] + return ImmutableDenseNDimArray(product_list, a.shape + b.shape) + + +def _util_contraction_diagonal(array, *contraction_or_diagonal_axes): + array = _arrayfy(array) + + # Verify contraction_axes: + taken_dims = set() + for axes_group in contraction_or_diagonal_axes: + if not isinstance(axes_group, Iterable): + raise ValueError("collections of contraction/diagonal axes expected") + + dim = array.shape[axes_group[0]] + + for d in axes_group: + if d in taken_dims: + raise ValueError("dimension specified more than once") + if dim != array.shape[d]: + raise ValueError("cannot contract or diagonalize between axes of different dimension") + taken_dims.add(d) + + rank = array.rank() + + remaining_shape = [dim for i, dim in enumerate(array.shape) if i not in taken_dims] + cum_shape = [0]*rank + _cumul = 1 + for i in range(rank): + cum_shape[rank - i - 1] = _cumul + _cumul *= int(array.shape[rank - i - 1]) + + # DEFINITION: by absolute position it is meant the position along the one + # dimensional array containing all the tensor components. + + # Possible future work on this module: move computation of absolute + # positions to a class method. + + # Determine absolute positions of the uncontracted indices: + remaining_indices = [[cum_shape[i]*j for j in range(array.shape[i])] + for i in range(rank) if i not in taken_dims] + + # Determine absolute positions of the contracted indices: + summed_deltas = [] + for axes_group in contraction_or_diagonal_axes: + lidx = [] + for js in range(array.shape[axes_group[0]]): + lidx.append(sum(cum_shape[ig] * js for ig in axes_group)) + summed_deltas.append(lidx) + + return array, remaining_indices, remaining_shape, summed_deltas + + +def tensorcontraction(array, *contraction_axes): + """ + Contraction of an array-like object on the specified axes. + + The equivalent operator for array expressions is ``ArrayContraction``, + which can be used to keep the expression unevaluated. + + Examples + ======== + + >>> from sympy import Array, tensorcontraction + >>> from sympy import Matrix, eye + >>> tensorcontraction(eye(3), (0, 1)) + 3 + >>> A = Array(range(18), (3, 2, 3)) + >>> A + [[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]], [[12, 13, 14], [15, 16, 17]]] + >>> tensorcontraction(A, (0, 2)) + [21, 30] + + Matrix multiplication may be emulated with a proper combination of + ``tensorcontraction`` and ``tensorproduct`` + + >>> from sympy import tensorproduct + >>> from sympy.abc import a,b,c,d,e,f,g,h + >>> m1 = Matrix([[a, b], [c, d]]) + >>> m2 = Matrix([[e, f], [g, h]]) + >>> p = tensorproduct(m1, m2) + >>> p + [[[[a*e, a*f], [a*g, a*h]], [[b*e, b*f], [b*g, b*h]]], [[[c*e, c*f], [c*g, c*h]], [[d*e, d*f], [d*g, d*h]]]] + >>> tensorcontraction(p, (1, 2)) + [[a*e + b*g, a*f + b*h], [c*e + d*g, c*f + d*h]] + >>> m1*m2 + Matrix([ + [a*e + b*g, a*f + b*h], + [c*e + d*g, c*f + d*h]]) + + See Also + ======== + + sympy.tensor.array.expressions.array_expressions.ArrayContraction + + """ + from sympy.tensor.array.expressions.array_expressions import _array_contraction + from sympy.tensor.array.expressions.array_expressions import _CodegenArrayAbstract + from sympy.tensor.array.expressions.array_expressions import _ArrayExpr + from sympy.matrices.expressions.matexpr import MatrixSymbol + if isinstance(array, (_ArrayExpr, _CodegenArrayAbstract, MatrixSymbol)): + return _array_contraction(array, *contraction_axes) + + array, remaining_indices, remaining_shape, summed_deltas = _util_contraction_diagonal(array, *contraction_axes) + + # Compute the contracted array: + # + # 1. external for loops on all uncontracted indices. + # Uncontracted indices are determined by the combinatorial product of + # the absolute positions of the remaining indices. + # 2. internal loop on all contracted indices. + # It sums the values of the absolute contracted index and the absolute + # uncontracted index for the external loop. + contracted_array = [] + for icontrib in itertools.product(*remaining_indices): + index_base_position = sum(icontrib) + isum = S.Zero + for sum_to_index in itertools.product(*summed_deltas): + idx = array._get_tuple_index(index_base_position + sum(sum_to_index)) + isum += array[idx] + + contracted_array.append(isum) + + if len(remaining_indices) == 0: + assert len(contracted_array) == 1 + return contracted_array[0] + + return type(array)(contracted_array, remaining_shape) + + +def tensordiagonal(array, *diagonal_axes): + """ + Diagonalization of an array-like object on the specified axes. + + This is equivalent to multiplying the expression by Kronecker deltas + uniting the axes. + + The diagonal indices are put at the end of the axes. + + The equivalent operator for array expressions is ``ArrayDiagonal``, which + can be used to keep the expression unevaluated. + + Examples + ======== + + ``tensordiagonal`` acting on a 2-dimensional array by axes 0 and 1 is + equivalent to the diagonal of the matrix: + + >>> from sympy import Array, tensordiagonal + >>> from sympy import Matrix, eye + >>> tensordiagonal(eye(3), (0, 1)) + [1, 1, 1] + + >>> from sympy.abc import a,b,c,d + >>> m1 = Matrix([[a, b], [c, d]]) + >>> tensordiagonal(m1, [0, 1]) + [a, d] + + In case of higher dimensional arrays, the diagonalized out dimensions + are appended removed and appended as a single dimension at the end: + + >>> A = Array(range(18), (3, 2, 3)) + >>> A + [[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]], [[12, 13, 14], [15, 16, 17]]] + >>> tensordiagonal(A, (0, 2)) + [[0, 7, 14], [3, 10, 17]] + >>> from sympy import permutedims + >>> tensordiagonal(A, (0, 2)) == permutedims(Array([A[0, :, 0], A[1, :, 1], A[2, :, 2]]), [1, 0]) + True + + See Also + ======== + + sympy.tensor.array.expressions.array_expressions.ArrayDiagonal + + """ + if any(len(i) <= 1 for i in diagonal_axes): + raise ValueError("need at least two axes to diagonalize") + + from sympy.tensor.array.expressions.array_expressions import _ArrayExpr + from sympy.tensor.array.expressions.array_expressions import _CodegenArrayAbstract + from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal, _array_diagonal + from sympy.matrices.expressions.matexpr import MatrixSymbol + if isinstance(array, (_ArrayExpr, _CodegenArrayAbstract, MatrixSymbol)): + return _array_diagonal(array, *diagonal_axes) + + ArrayDiagonal._validate(array, *diagonal_axes) + + array, remaining_indices, remaining_shape, diagonal_deltas = _util_contraction_diagonal(array, *diagonal_axes) + + # Compute the diagonalized array: + # + # 1. external for loops on all undiagonalized indices. + # Undiagonalized indices are determined by the combinatorial product of + # the absolute positions of the remaining indices. + # 2. internal loop on all diagonal indices. + # It appends the values of the absolute diagonalized index and the absolute + # undiagonalized index for the external loop. + diagonalized_array = [] + diagonal_shape = [len(i) for i in diagonal_deltas] + for icontrib in itertools.product(*remaining_indices): + index_base_position = sum(icontrib) + isum = [] + for sum_to_index in itertools.product(*diagonal_deltas): + idx = array._get_tuple_index(index_base_position + sum(sum_to_index)) + isum.append(array[idx]) + + isum = type(array)(isum).reshape(*diagonal_shape) + diagonalized_array.append(isum) + + return type(array)(diagonalized_array, remaining_shape + diagonal_shape) + + +def derive_by_array(expr, dx): + r""" + Derivative by arrays. Supports both arrays and scalars. + + The equivalent operator for array expressions is ``array_derive``. + + Explanation + =========== + + Given the array `A_{i_1, \ldots, i_N}` and the array `X_{j_1, \ldots, j_M}` + this function will return a new array `B` defined by + + `B_{j_1,\ldots,j_M,i_1,\ldots,i_N} := \frac{\partial A_{i_1,\ldots,i_N}}{\partial X_{j_1,\ldots,j_M}}` + + Examples + ======== + + >>> from sympy import derive_by_array + >>> from sympy.abc import x, y, z, t + >>> from sympy import cos + >>> derive_by_array(cos(x*t), x) + -t*sin(t*x) + >>> derive_by_array(cos(x*t), [x, y, z, t]) + [-t*sin(t*x), 0, 0, -x*sin(t*x)] + >>> derive_by_array([x, y**2*z], [[x, y], [z, t]]) + [[[1, 0], [0, 2*y*z]], [[0, y**2], [0, 0]]] + + """ + from sympy.matrices import MatrixBase + from sympy.tensor.array import SparseNDimArray + array_types = (Iterable, MatrixBase, NDimArray) + + if isinstance(dx, array_types): + dx = ImmutableDenseNDimArray(dx) + for i in dx: + if not i._diff_wrt: + raise ValueError("cannot derive by this array") + + if isinstance(expr, array_types): + if isinstance(expr, NDimArray): + expr = expr.as_immutable() + else: + expr = ImmutableDenseNDimArray(expr) + + if isinstance(dx, array_types): + if isinstance(expr, SparseNDimArray): + lp = len(expr) + new_array = {k + i*lp: v + for i, x in enumerate(Flatten(dx)) + for k, v in expr.diff(x)._sparse_array.items()} + else: + new_array = [[y.diff(x) for y in Flatten(expr)] for x in Flatten(dx)] + return type(expr)(new_array, dx.shape + expr.shape) + else: + return expr.diff(dx) + else: + expr = _sympify(expr) + if isinstance(dx, array_types): + return ImmutableDenseNDimArray([expr.diff(i) for i in Flatten(dx)], dx.shape) + else: + dx = _sympify(dx) + return diff(expr, dx) + + +def permutedims(expr, perm=None, index_order_old=None, index_order_new=None): + """ + Permutes the indices of an array. + + Parameter specifies the permutation of the indices. + + The equivalent operator for array expressions is ``PermuteDims``, which can + be used to keep the expression unevaluated. + + Examples + ======== + + >>> from sympy.abc import x, y, z, t + >>> from sympy import sin + >>> from sympy import Array, permutedims + >>> a = Array([[x, y, z], [t, sin(x), 0]]) + >>> a + [[x, y, z], [t, sin(x), 0]] + >>> permutedims(a, (1, 0)) + [[x, t], [y, sin(x)], [z, 0]] + + If the array is of second order, ``transpose`` can be used: + + >>> from sympy import transpose + >>> transpose(a) + [[x, t], [y, sin(x)], [z, 0]] + + Examples on higher dimensions: + + >>> b = Array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) + >>> permutedims(b, (2, 1, 0)) + [[[1, 5], [3, 7]], [[2, 6], [4, 8]]] + >>> permutedims(b, (1, 2, 0)) + [[[1, 5], [2, 6]], [[3, 7], [4, 8]]] + + An alternative way to specify the same permutations as in the previous + lines involves passing the *old* and *new* indices, either as a list or as + a string: + + >>> permutedims(b, index_order_old="cba", index_order_new="abc") + [[[1, 5], [3, 7]], [[2, 6], [4, 8]]] + >>> permutedims(b, index_order_old="cab", index_order_new="abc") + [[[1, 5], [2, 6]], [[3, 7], [4, 8]]] + + ``Permutation`` objects are also allowed: + + >>> from sympy.combinatorics import Permutation + >>> permutedims(b, Permutation([1, 2, 0])) + [[[1, 5], [2, 6]], [[3, 7], [4, 8]]] + + See Also + ======== + + sympy.tensor.array.expressions.array_expressions.PermuteDims + + """ + from sympy.tensor.array import SparseNDimArray + + from sympy.tensor.array.expressions.array_expressions import _ArrayExpr + from sympy.tensor.array.expressions.array_expressions import _CodegenArrayAbstract + from sympy.tensor.array.expressions.array_expressions import _permute_dims + from sympy.matrices.expressions.matexpr import MatrixSymbol + from sympy.tensor.array.expressions import PermuteDims + from sympy.tensor.array.expressions.array_expressions import get_rank + perm = PermuteDims._get_permutation_from_arguments(perm, index_order_old, index_order_new, get_rank(expr)) + if isinstance(expr, (_ArrayExpr, _CodegenArrayAbstract, MatrixSymbol)): + return _permute_dims(expr, perm) + + if not isinstance(expr, NDimArray): + expr = ImmutableDenseNDimArray(expr) + + from sympy.combinatorics import Permutation + if not isinstance(perm, Permutation): + perm = Permutation(list(perm)) + + if perm.size != expr.rank(): + raise ValueError("wrong permutation size") + + # Get the inverse permutation: + iperm = ~perm + new_shape = perm(expr.shape) + + if isinstance(expr, SparseNDimArray): + return type(expr)({tuple(perm(expr._get_tuple_index(k))): v + for k, v in expr._sparse_array.items()}, new_shape) + + indices_span = perm([range(i) for i in expr.shape]) + + new_array = [None]*len(expr) + for i, idx in enumerate(itertools.product(*indices_span)): + t = iperm(idx) + new_array[i] = expr[t] + + return type(expr)(new_array, new_shape) + + +class Flatten(Printable): + """ + Flatten an iterable object to a list in a lazy-evaluation way. + + Notes + ===== + + This class is an iterator with which the memory cost can be economised. + Optimisation has been considered to ameliorate the performance for some + specific data types like DenseNDimArray and SparseNDimArray. + + Examples + ======== + + >>> from sympy.tensor.array.arrayop import Flatten + >>> from sympy.tensor.array import Array + >>> A = Array(range(6)).reshape(2, 3) + >>> Flatten(A) + Flatten([[0, 1, 2], [3, 4, 5]]) + >>> [i for i in Flatten(A)] + [0, 1, 2, 3, 4, 5] + """ + def __init__(self, iterable): + from sympy.matrices.matrixbase import MatrixBase + from sympy.tensor.array import NDimArray + + if not isinstance(iterable, (Iterable, MatrixBase)): + raise NotImplementedError("Data type not yet supported") + + if isinstance(iterable, list): + iterable = NDimArray(iterable) + + self._iter = iterable + self._idx = 0 + + def __iter__(self): + return self + + def __next__(self): + from sympy.matrices.matrixbase import MatrixBase + + if len(self._iter) > self._idx: + if isinstance(self._iter, DenseNDimArray): + result = self._iter._array[self._idx] + + elif isinstance(self._iter, SparseNDimArray): + if self._idx in self._iter._sparse_array: + result = self._iter._sparse_array[self._idx] + else: + result = 0 + + elif isinstance(self._iter, MatrixBase): + result = self._iter[self._idx] + + elif hasattr(self._iter, '__next__'): + result = next(self._iter) + + else: + result = self._iter[self._idx] + + else: + raise StopIteration + + self._idx += 1 + return result + + def next(self): + return self.__next__() + + def _sympystr(self, printer): + return type(self).__name__ + '(' + printer._print(self._iter) + ')' diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/dense_ndim_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/dense_ndim_array.py new file mode 100644 index 0000000000000000000000000000000000000000..576e452c55d8d374ca1f72c553f3a64de7227d43 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/dense_ndim_array.py @@ -0,0 +1,206 @@ +import functools +from typing import List + +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.singleton import S +from sympy.core.sympify import _sympify +from sympy.tensor.array.mutable_ndim_array import MutableNDimArray +from sympy.tensor.array.ndim_array import NDimArray, ImmutableNDimArray, ArrayKind +from sympy.utilities.iterables import flatten + + +class DenseNDimArray(NDimArray): + + _array: List[Basic] + + def __new__(self, *args, **kwargs): + return ImmutableDenseNDimArray(*args, **kwargs) + + @property + def kind(self) -> ArrayKind: + return ArrayKind._union(self._array) + + def __getitem__(self, index): + """ + Allows to get items from N-dim array. + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray([0, 1, 2, 3], (2, 2)) + >>> a + [[0, 1], [2, 3]] + >>> a[0, 0] + 0 + >>> a[1, 1] + 3 + >>> a[0] + [0, 1] + >>> a[1] + [2, 3] + + + Symbolic index: + + >>> from sympy.abc import i, j + >>> a[i, j] + [[0, 1], [2, 3]][i, j] + + Replace `i` and `j` to get element `(1, 1)`: + + >>> a[i, j].subs({i: 1, j: 1}) + 3 + + """ + syindex = self._check_symbolic_index(index) + if syindex is not None: + return syindex + + index = self._check_index_for_getitem(index) + + if isinstance(index, tuple) and any(isinstance(i, slice) for i in index): + sl_factors, eindices = self._get_slice_data_for_array_access(index) + array = [self._array[self._parse_index(i)] for i in eindices] + nshape = [len(el) for i, el in enumerate(sl_factors) if isinstance(index[i], slice)] + return type(self)(array, nshape) + else: + index = self._parse_index(index) + return self._array[index] + + @classmethod + def zeros(cls, *shape): + list_length = functools.reduce(lambda x, y: x*y, shape, S.One) + return cls._new(([0]*list_length,), shape) + + def tomatrix(self): + """ + Converts MutableDenseNDimArray to Matrix. Can convert only 2-dim array, else will raise error. + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray([1 for i in range(9)], (3, 3)) + >>> b = a.tomatrix() + >>> b + Matrix([ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1]]) + + """ + from sympy.matrices import Matrix + + if self.rank() != 2: + raise ValueError('Dimensions must be of size of 2') + + return Matrix(self.shape[0], self.shape[1], self._array) + + def reshape(self, *newshape): + """ + Returns MutableDenseNDimArray instance with new shape. Elements number + must be suitable to new shape. The only argument of method sets + new shape. + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray([1, 2, 3, 4, 5, 6], (2, 3)) + >>> a.shape + (2, 3) + >>> a + [[1, 2, 3], [4, 5, 6]] + >>> b = a.reshape(3, 2) + >>> b.shape + (3, 2) + >>> b + [[1, 2], [3, 4], [5, 6]] + + """ + new_total_size = functools.reduce(lambda x,y: x*y, newshape) + if new_total_size != self._loop_size: + raise ValueError('Expecting reshape size to %d but got prod(%s) = %d' % ( + self._loop_size, str(newshape), new_total_size)) + + # there is no `.func` as this class does not subtype `Basic`: + return type(self)(self._array, newshape) + + +class ImmutableDenseNDimArray(DenseNDimArray, ImmutableNDimArray): # type: ignore + def __new__(cls, iterable, shape=None, **kwargs): + return cls._new(iterable, shape, **kwargs) + + @classmethod + def _new(cls, iterable, shape, **kwargs): + shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) + shape = Tuple(*map(_sympify, shape)) + cls._check_special_bounds(flat_list, shape) + flat_list = flatten(flat_list) + flat_list = Tuple(*flat_list) + self = Basic.__new__(cls, flat_list, shape, **kwargs) + self._shape = shape + self._array = list(flat_list) + self._rank = len(shape) + self._loop_size = functools.reduce(lambda x,y: x*y, shape, 1) + return self + + def __setitem__(self, index, value): + raise TypeError('immutable N-dim array') + + def as_mutable(self): + return MutableDenseNDimArray(self) + + def _eval_simplify(self, **kwargs): + from sympy.simplify.simplify import simplify + return self.applyfunc(simplify) + +class MutableDenseNDimArray(DenseNDimArray, MutableNDimArray): + + def __new__(cls, iterable=None, shape=None, **kwargs): + return cls._new(iterable, shape, **kwargs) + + @classmethod + def _new(cls, iterable, shape, **kwargs): + shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) + flat_list = flatten(flat_list) + self = object.__new__(cls) + self._shape = shape + self._array = list(flat_list) + self._rank = len(shape) + self._loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else len(flat_list) + return self + + def __setitem__(self, index, value): + """Allows to set items to MutableDenseNDimArray. + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray.zeros(2, 2) + >>> a[0,0] = 1 + >>> a[1,1] = 1 + >>> a + [[1, 0], [0, 1]] + + """ + if isinstance(index, tuple) and any(isinstance(i, slice) for i in index): + value, eindices, slice_offsets = self._get_slice_data_for_array_assignment(index, value) + for i in eindices: + other_i = [ind - j for ind, j in zip(i, slice_offsets) if j is not None] + self._array[self._parse_index(i)] = value[other_i] + else: + index = self._parse_index(index) + self._setter_iterable_check(value) + value = _sympify(value) + self._array[index] = value + + def as_immutable(self): + return ImmutableDenseNDimArray(self) + + @property + def free_symbols(self): + return {i for j in self._array for i in j.free_symbols} diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__init__.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f1658241782cdf0e38a30c43a6d67f9811297f4c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__init__.py @@ -0,0 +1,178 @@ +r""" +Array expressions are expressions representing N-dimensional arrays, without +evaluating them. These expressions represent in a certain way abstract syntax +trees of operations on N-dimensional arrays. + +Every N-dimensional array operator has a corresponding array expression object. + +Table of correspondences: + +=============================== ============================= + Array operator Array expression operator +=============================== ============================= + tensorproduct ArrayTensorProduct + tensorcontraction ArrayContraction + tensordiagonal ArrayDiagonal + permutedims PermuteDims +=============================== ============================= + +Examples +======== + +``ArraySymbol`` objects are the N-dimensional equivalent of ``MatrixSymbol`` +objects in the matrix module: + +>>> from sympy.tensor.array.expressions import ArraySymbol +>>> from sympy.abc import i, j, k +>>> A = ArraySymbol("A", (3, 2, 4)) +>>> A.shape +(3, 2, 4) +>>> A[i, j, k] +A[i, j, k] +>>> A.as_explicit() +[[[A[0, 0, 0], A[0, 0, 1], A[0, 0, 2], A[0, 0, 3]], + [A[0, 1, 0], A[0, 1, 1], A[0, 1, 2], A[0, 1, 3]]], + [[A[1, 0, 0], A[1, 0, 1], A[1, 0, 2], A[1, 0, 3]], + [A[1, 1, 0], A[1, 1, 1], A[1, 1, 2], A[1, 1, 3]]], + [[A[2, 0, 0], A[2, 0, 1], A[2, 0, 2], A[2, 0, 3]], + [A[2, 1, 0], A[2, 1, 1], A[2, 1, 2], A[2, 1, 3]]]] + +Component-explicit arrays can be added inside array expressions: + +>>> from sympy import Array +>>> from sympy import tensorproduct +>>> from sympy.tensor.array.expressions import ArrayTensorProduct +>>> a = Array([1, 2, 3]) +>>> b = Array([i, j, k]) +>>> expr = ArrayTensorProduct(a, b, b) +>>> expr +ArrayTensorProduct([1, 2, 3], [i, j, k], [i, j, k]) +>>> expr.as_explicit() == tensorproduct(a, b, b) +True + +Constructing array expressions from index-explicit forms +-------------------------------------------------------- + +Array expressions are index-implicit. This means they do not use any indices to +represent array operations. The function ``convert_indexed_to_array( ... )`` +may be used to convert index-explicit expressions to array expressions. +It takes as input two parameters: the index-explicit expression and the order +of the indices: + +>>> from sympy.tensor.array.expressions import convert_indexed_to_array +>>> from sympy import Sum +>>> A = ArraySymbol("A", (3, 3)) +>>> B = ArraySymbol("B", (3, 3)) +>>> convert_indexed_to_array(A[i, j], [i, j]) +A +>>> convert_indexed_to_array(A[i, j], [j, i]) +PermuteDims(A, (0 1)) +>>> convert_indexed_to_array(A[i, j] + B[j, i], [i, j]) +ArrayAdd(A, PermuteDims(B, (0 1))) +>>> convert_indexed_to_array(Sum(A[i, j]*B[j, k], (j, 0, 2)), [i, k]) +ArrayContraction(ArrayTensorProduct(A, B), (1, 2)) + +The diagonal of a matrix in the array expression form: + +>>> convert_indexed_to_array(A[i, i], [i]) +ArrayDiagonal(A, (0, 1)) + +The trace of a matrix in the array expression form: + +>>> convert_indexed_to_array(Sum(A[i, i], (i, 0, 2)), [i]) +ArrayContraction(A, (0, 1)) + +Compatibility with matrices +--------------------------- + +Array expressions can be mixed with objects from the matrix module: + +>>> from sympy import MatrixSymbol +>>> from sympy.tensor.array.expressions import ArrayContraction +>>> M = MatrixSymbol("M", 3, 3) +>>> N = MatrixSymbol("N", 3, 3) + +Express the matrix product in the array expression form: + +>>> from sympy.tensor.array.expressions import convert_matrix_to_array +>>> expr = convert_matrix_to_array(M*N) +>>> expr +ArrayContraction(ArrayTensorProduct(M, N), (1, 2)) + +The expression can be converted back to matrix form: + +>>> from sympy.tensor.array.expressions import convert_array_to_matrix +>>> convert_array_to_matrix(expr) +M*N + +Add a second contraction on the remaining axes in order to get the trace of `M \cdot N`: + +>>> expr_tr = ArrayContraction(expr, (0, 1)) +>>> expr_tr +ArrayContraction(ArrayContraction(ArrayTensorProduct(M, N), (1, 2)), (0, 1)) + +Flatten the expression by calling ``.doit()`` and remove the nested array contraction operations: + +>>> expr_tr.doit() +ArrayContraction(ArrayTensorProduct(M, N), (0, 3), (1, 2)) + +Get the explicit form of the array expression: + +>>> expr.as_explicit() +[[M[0, 0]*N[0, 0] + M[0, 1]*N[1, 0] + M[0, 2]*N[2, 0], M[0, 0]*N[0, 1] + M[0, 1]*N[1, 1] + M[0, 2]*N[2, 1], M[0, 0]*N[0, 2] + M[0, 1]*N[1, 2] + M[0, 2]*N[2, 2]], + [M[1, 0]*N[0, 0] + M[1, 1]*N[1, 0] + M[1, 2]*N[2, 0], M[1, 0]*N[0, 1] + M[1, 1]*N[1, 1] + M[1, 2]*N[2, 1], M[1, 0]*N[0, 2] + M[1, 1]*N[1, 2] + M[1, 2]*N[2, 2]], + [M[2, 0]*N[0, 0] + M[2, 1]*N[1, 0] + M[2, 2]*N[2, 0], M[2, 0]*N[0, 1] + M[2, 1]*N[1, 1] + M[2, 2]*N[2, 1], M[2, 0]*N[0, 2] + M[2, 1]*N[1, 2] + M[2, 2]*N[2, 2]]] + +Express the trace of a matrix: + +>>> from sympy import Trace +>>> convert_matrix_to_array(Trace(M)) +ArrayContraction(M, (0, 1)) +>>> convert_matrix_to_array(Trace(M*N)) +ArrayContraction(ArrayTensorProduct(M, N), (0, 3), (1, 2)) + +Express the transposition of a matrix (will be expressed as a permutation of the axes: + +>>> convert_matrix_to_array(M.T) +PermuteDims(M, (0 1)) + +Compute the derivative array expressions: + +>>> from sympy.tensor.array.expressions import array_derive +>>> d = array_derive(M, M) +>>> d +PermuteDims(ArrayTensorProduct(I, I), (3)(1 2)) + +Verify that the derivative corresponds to the form computed with explicit matrices: + +>>> d.as_explicit() +[[[[1, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 1, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 0], [0, 0, 0]]], [[[0, 0, 0], [1, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 1], [0, 0, 0]]], [[[0, 0, 0], [0, 0, 0], [1, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 1, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 1]]]] +>>> Me = M.as_explicit() +>>> Me.diff(Me) +[[[[1, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 1, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 0], [0, 0, 0]]], [[[0, 0, 0], [1, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 1], [0, 0, 0]]], [[[0, 0, 0], [0, 0, 0], [1, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 1, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 1]]]] + +""" + +__all__ = [ + "ArraySymbol", "ArrayElement", "ZeroArray", "OneArray", + "ArrayTensorProduct", + "ArrayContraction", + "ArrayDiagonal", + "PermuteDims", + "ArrayAdd", + "ArrayElementwiseApplyFunc", + "Reshape", + "convert_array_to_matrix", + "convert_matrix_to_array", + "convert_array_to_indexed", + "convert_indexed_to_array", + "array_derive", +] + +from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayAdd, PermuteDims, ArrayDiagonal, \ + ArrayContraction, Reshape, ArraySymbol, ArrayElement, ZeroArray, OneArray, ArrayElementwiseApplyFunc +from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive +from sympy.tensor.array.expressions.from_array_to_indexed import convert_array_to_indexed +from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix +from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array +from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..966cb1b1f83523e80ad5b58897c6dd9102b024c7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/array_expressions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/array_expressions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be449077c4f4b8efe3c4647937cd103de459c5ae Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/array_expressions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/arrayexpr_derivatives.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/arrayexpr_derivatives.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..621e624218f3578223aee81eb48e1a477b48d2cc Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/arrayexpr_derivatives.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_array_to_indexed.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_array_to_indexed.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91865526ca0fcf79b05ec60d365bf917d76040cc Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_array_to_indexed.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_array_to_matrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_array_to_matrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0478addf48490a818c8cb1c4c0584d37cf1f1af Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_array_to_matrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_indexed_to_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_indexed_to_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96715ba58230ed0aad8b8eec50d9e964055cff72 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_indexed_to_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_matrix_to_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_matrix_to_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5789ae0be8d30df68afffe88614588c41974f188 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/conv_matrix_to_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_array_to_indexed.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_array_to_indexed.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..301a4808ddd8d3e2b4b0000c0cd7055447c704df Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_array_to_indexed.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_array_to_matrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_array_to_matrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc9d1850984bef8688ad409ec62040f89ccd9083 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_array_to_matrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_indexed_to_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_indexed_to_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..450a227245f6c17bc27f4e3b6183b4a28f200b58 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_indexed_to_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_matrix_to_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_matrix_to_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..585f297356f4953e44b2da10c780de2cde174898 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/from_matrix_to_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/utils.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e31b218871555a5c122a3dc2bda4936cc61a5eb Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/__pycache__/utils.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/array_expressions.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/array_expressions.py new file mode 100644 index 0000000000000000000000000000000000000000..f062e3de4c24987d62ba0b3a19fe474fb4687940 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/array_expressions.py @@ -0,0 +1,1969 @@ +from __future__ import annotations +import collections.abc +import operator +from collections import defaultdict, Counter +from functools import reduce +import itertools +from itertools import accumulate + +import typing + +from sympy.core.numbers import Integer +from sympy.core.relational import Equality +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.expr import Expr +from sympy.core.function import (Function, Lambda) +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import (Dummy, Symbol) +from sympy.matrices.matrixbase import MatrixBase +from sympy.matrices.expressions.diagonal import diagonalize_vector +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.matrices.expressions.special import ZeroMatrix +from sympy.tensor.array.arrayop import (permutedims, tensorcontraction, tensordiagonal, tensorproduct) +from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray +from sympy.tensor.array.ndim_array import NDimArray +from sympy.tensor.indexed import (Indexed, IndexedBase) +from sympy.matrices.expressions.matexpr import MatrixElement +from sympy.tensor.array.expressions.utils import _apply_recursively_over_nested_lists, _sort_contraction_indices, \ + _get_mapping_from_subranks, _build_push_indices_up_func_transformation, _get_contraction_links, \ + _build_push_indices_down_func_transformation +from sympy.combinatorics import Permutation +from sympy.combinatorics.permutations import _af_invert +from sympy.core.sympify import _sympify + + +class _ArrayExpr(Expr): + shape: tuple[Expr, ...] + + def __getitem__(self, item): + if not isinstance(item, collections.abc.Iterable): + item = (item,) + ArrayElement._check_shape(self, item) + return self._get(item) + + def _get(self, item): + return _get_array_element_or_slice(self, item) + + +class ArraySymbol(_ArrayExpr): + """ + Symbol representing an array expression + """ + + _iterable = False + + def __new__(cls, symbol, shape: typing.Iterable) -> "ArraySymbol": + if isinstance(symbol, str): + symbol = Symbol(symbol) + # symbol = _sympify(symbol) + shape = Tuple(*map(_sympify, shape)) + obj = Expr.__new__(cls, symbol, shape) + return obj + + @property + def name(self): + return self._args[0] + + @property + def shape(self): + return self._args[1] + + def as_explicit(self): + if not all(i.is_Integer for i in self.shape): + raise ValueError("cannot express explicit array with symbolic shape") + data = [self[i] for i in itertools.product(*[range(j) for j in self.shape])] + return ImmutableDenseNDimArray(data).reshape(*self.shape) + + +class ArrayElement(Expr): + """ + An element of an array. + """ + + _diff_wrt = True + is_symbol = True + is_commutative = True + + def __new__(cls, name, indices): + if isinstance(name, str): + name = Symbol(name) + name = _sympify(name) + if not isinstance(indices, collections.abc.Iterable): + indices = (indices,) + indices = _sympify(tuple(indices)) + cls._check_shape(name, indices) + obj = Expr.__new__(cls, name, indices) + return obj + + @classmethod + def _check_shape(cls, name, indices): + indices = tuple(indices) + if hasattr(name, "shape"): + index_error = IndexError("number of indices does not match shape of the array") + if len(indices) != len(name.shape): + raise index_error + if any((i >= s) == True for i, s in zip(indices, name.shape)): + raise ValueError("shape is out of bounds") + if any((i < 0) == True for i in indices): + raise ValueError("shape contains negative values") + + @property + def name(self): + return self._args[0] + + @property + def indices(self): + return self._args[1] + + def _eval_derivative(self, s): + if not isinstance(s, ArrayElement): + return S.Zero + + if s == self: + return S.One + + if s.name != self.name: + return S.Zero + + return Mul.fromiter(KroneckerDelta(i, j) for i, j in zip(self.indices, s.indices)) + + +class ZeroArray(_ArrayExpr): + """ + Symbolic array of zeros. Equivalent to ``ZeroMatrix`` for matrices. + """ + + def __new__(cls, *shape): + if len(shape) == 0: + return S.Zero + shape = map(_sympify, shape) + obj = Expr.__new__(cls, *shape) + return obj + + @property + def shape(self): + return self._args + + def as_explicit(self): + if not all(i.is_Integer for i in self.shape): + raise ValueError("Cannot return explicit form for symbolic shape.") + return ImmutableDenseNDimArray.zeros(*self.shape) + + def _get(self, item): + return S.Zero + + +class OneArray(_ArrayExpr): + """ + Symbolic array of ones. + """ + + def __new__(cls, *shape): + if len(shape) == 0: + return S.One + shape = map(_sympify, shape) + obj = Expr.__new__(cls, *shape) + return obj + + @property + def shape(self): + return self._args + + def as_explicit(self): + if not all(i.is_Integer for i in self.shape): + raise ValueError("Cannot return explicit form for symbolic shape.") + return ImmutableDenseNDimArray([S.One for i in range(reduce(operator.mul, self.shape))]).reshape(*self.shape) + + def _get(self, item): + return S.One + + +class _CodegenArrayAbstract(Basic): + + @property + def subranks(self): + """ + Returns the ranks of the objects in the uppermost tensor product inside + the current object. In case no tensor products are contained, return + the atomic ranks. + + Examples + ======== + + >>> from sympy.tensor.array import tensorproduct, tensorcontraction + >>> from sympy import MatrixSymbol + >>> M = MatrixSymbol("M", 3, 3) + >>> N = MatrixSymbol("N", 3, 3) + >>> P = MatrixSymbol("P", 3, 3) + + Important: do not confuse the rank of the matrix with the rank of an array. + + >>> tp = tensorproduct(M, N, P) + >>> tp.subranks + [2, 2, 2] + + >>> co = tensorcontraction(tp, (1, 2), (3, 4)) + >>> co.subranks + [2, 2, 2] + """ + return self._subranks[:] + + def subrank(self): + """ + The sum of ``subranks``. + """ + return sum(self.subranks) + + @property + def shape(self): + return self._shape + + def doit(self, **hints): + deep = hints.get("deep", True) + if deep: + return self.func(*[arg.doit(**hints) for arg in self.args])._canonicalize() + else: + return self._canonicalize() + +class ArrayTensorProduct(_CodegenArrayAbstract): + r""" + Class to represent the tensor product of array-like objects. + """ + + def __new__(cls, *args, **kwargs): + args = [_sympify(arg) for arg in args] + + canonicalize = kwargs.pop("canonicalize", False) + + ranks = [get_rank(arg) for arg in args] + + obj = Basic.__new__(cls, *args) + obj._subranks = ranks + shapes = [get_shape(i) for i in args] + + if any(i is None for i in shapes): + obj._shape = None + else: + obj._shape = tuple(j for i in shapes for j in i) + if canonicalize: + return obj._canonicalize() + return obj + + def _canonicalize(self): + args = self.args + args = self._flatten(args) + + ranks = [get_rank(arg) for arg in args] + + # Check if there are nested permutation and lift them up: + permutation_cycles = [] + for i, arg in enumerate(args): + if not isinstance(arg, PermuteDims): + continue + permutation_cycles.extend([[k + sum(ranks[:i]) for k in j] for j in arg.permutation.cyclic_form]) + args[i] = arg.expr + if permutation_cycles: + return _permute_dims(_array_tensor_product(*args), Permutation(sum(ranks)-1)*Permutation(permutation_cycles)) + + if len(args) == 1: + return args[0] + + # If any object is a ZeroArray, return a ZeroArray: + if any(isinstance(arg, (ZeroArray, ZeroMatrix)) for arg in args): + shapes = reduce(operator.add, [get_shape(i) for i in args], ()) + return ZeroArray(*shapes) + + # If there are contraction objects inside, transform the whole + # expression into `ArrayContraction`: + contractions = {i: arg for i, arg in enumerate(args) if isinstance(arg, ArrayContraction)} + if contractions: + ranks = [_get_subrank(arg) if isinstance(arg, ArrayContraction) else get_rank(arg) for arg in args] + cumulative_ranks = list(accumulate([0] + ranks))[:-1] + tp = _array_tensor_product(*[arg.expr if isinstance(arg, ArrayContraction) else arg for arg in args]) + contraction_indices = [tuple(cumulative_ranks[i] + k for k in j) for i, arg in contractions.items() for j in arg.contraction_indices] + return _array_contraction(tp, *contraction_indices) + + diagonals = {i: arg for i, arg in enumerate(args) if isinstance(arg, ArrayDiagonal)} + if diagonals: + inverse_permutation = [] + last_perm = [] + ranks = [get_rank(arg) for arg in args] + cumulative_ranks = list(accumulate([0] + ranks))[:-1] + for i, arg in enumerate(args): + if isinstance(arg, ArrayDiagonal): + i1 = get_rank(arg) - len(arg.diagonal_indices) + i2 = len(arg.diagonal_indices) + inverse_permutation.extend([cumulative_ranks[i] + j for j in range(i1)]) + last_perm.extend([cumulative_ranks[i] + j for j in range(i1, i1 + i2)]) + else: + inverse_permutation.extend([cumulative_ranks[i] + j for j in range(get_rank(arg))]) + inverse_permutation.extend(last_perm) + tp = _array_tensor_product(*[arg.expr if isinstance(arg, ArrayDiagonal) else arg for arg in args]) + ranks2 = [_get_subrank(arg) if isinstance(arg, ArrayDiagonal) else get_rank(arg) for arg in args] + cumulative_ranks2 = list(accumulate([0] + ranks2))[:-1] + diagonal_indices = [tuple(cumulative_ranks2[i] + k for k in j) for i, arg in diagonals.items() for j in arg.diagonal_indices] + return _permute_dims(_array_diagonal(tp, *diagonal_indices), _af_invert(inverse_permutation)) + + return self.func(*args, canonicalize=False) + + @classmethod + def _flatten(cls, args): + args = [i for arg in args for i in (arg.args if isinstance(arg, cls) else [arg])] + return args + + def as_explicit(self): + return tensorproduct(*[arg.as_explicit() if hasattr(arg, "as_explicit") else arg for arg in self.args]) + + +class ArrayAdd(_CodegenArrayAbstract): + r""" + Class for elementwise array additions. + """ + + def __new__(cls, *args, **kwargs): + args = [_sympify(arg) for arg in args] + ranks = [get_rank(arg) for arg in args] + ranks = list(set(ranks)) + if len(ranks) != 1: + raise ValueError("summing arrays of different ranks") + shapes = [arg.shape for arg in args] + if len({i for i in shapes if i is not None}) > 1: + raise ValueError("mismatching shapes in addition") + + canonicalize = kwargs.pop("canonicalize", False) + + obj = Basic.__new__(cls, *args) + obj._subranks = ranks + if any(i is None for i in shapes): + obj._shape = None + else: + obj._shape = shapes[0] + if canonicalize: + return obj._canonicalize() + return obj + + def _canonicalize(self): + args = self.args + + # Flatten: + args = self._flatten_args(args) + + shapes = [get_shape(arg) for arg in args] + args = [arg for arg in args if not isinstance(arg, (ZeroArray, ZeroMatrix))] + if len(args) == 0: + if any(i for i in shapes if i is None): + raise NotImplementedError("cannot handle addition of ZeroMatrix/ZeroArray and undefined shape object") + return ZeroArray(*shapes[0]) + elif len(args) == 1: + return args[0] + return self.func(*args, canonicalize=False) + + @classmethod + def _flatten_args(cls, args): + new_args = [] + for arg in args: + if isinstance(arg, ArrayAdd): + new_args.extend(arg.args) + else: + new_args.append(arg) + return new_args + + def as_explicit(self): + return reduce( + operator.add, + [arg.as_explicit() if hasattr(arg, "as_explicit") else arg for arg in self.args]) + + +class PermuteDims(_CodegenArrayAbstract): + r""" + Class to represent permutation of axes of arrays. + + Examples + ======== + + >>> from sympy.tensor.array import permutedims + >>> from sympy import MatrixSymbol + >>> M = MatrixSymbol("M", 3, 3) + >>> cg = permutedims(M, [1, 0]) + + The object ``cg`` represents the transposition of ``M``, as the permutation + ``[1, 0]`` will act on its indices by switching them: + + `M_{ij} \Rightarrow M_{ji}` + + This is evident when transforming back to matrix form: + + >>> from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix + >>> convert_array_to_matrix(cg) + M.T + + >>> N = MatrixSymbol("N", 3, 2) + >>> cg = permutedims(N, [1, 0]) + >>> cg.shape + (2, 3) + + There are optional parameters that can be used as alternative to the permutation: + + >>> from sympy.tensor.array.expressions import ArraySymbol, PermuteDims + >>> M = ArraySymbol("M", (1, 2, 3, 4, 5)) + >>> expr = PermuteDims(M, index_order_old="ijklm", index_order_new="kijml") + >>> expr + PermuteDims(M, (0 2 1)(3 4)) + >>> expr.shape + (3, 1, 2, 5, 4) + + Permutations of tensor products are simplified in order to achieve a + standard form: + + >>> from sympy.tensor.array import tensorproduct + >>> M = MatrixSymbol("M", 4, 5) + >>> tp = tensorproduct(M, N) + >>> tp.shape + (4, 5, 3, 2) + >>> perm1 = permutedims(tp, [2, 3, 1, 0]) + + The args ``(M, N)`` have been sorted and the permutation has been + simplified, the expression is equivalent: + + >>> perm1.expr.args + (N, M) + >>> perm1.shape + (3, 2, 5, 4) + >>> perm1.permutation + (2 3) + + The permutation in its array form has been simplified from + ``[2, 3, 1, 0]`` to ``[0, 1, 3, 2]``, as the arguments of the tensor + product `M` and `N` have been switched: + + >>> perm1.permutation.array_form + [0, 1, 3, 2] + + We can nest a second permutation: + + >>> perm2 = permutedims(perm1, [1, 0, 2, 3]) + >>> perm2.shape + (2, 3, 5, 4) + >>> perm2.permutation.array_form + [1, 0, 3, 2] + """ + + def __new__(cls, expr, permutation=None, index_order_old=None, index_order_new=None, **kwargs): + from sympy.combinatorics import Permutation + expr = _sympify(expr) + expr_rank = get_rank(expr) + permutation = cls._get_permutation_from_arguments(permutation, index_order_old, index_order_new, expr_rank) + permutation = Permutation(permutation) + permutation_size = permutation.size + if permutation_size != expr_rank: + raise ValueError("Permutation size must be the length of the shape of expr") + + canonicalize = kwargs.pop("canonicalize", False) + + obj = Basic.__new__(cls, expr, permutation) + obj._subranks = [get_rank(expr)] + shape = get_shape(expr) + if shape is None: + obj._shape = None + else: + obj._shape = tuple(shape[permutation(i)] for i in range(len(shape))) + if canonicalize: + return obj._canonicalize() + return obj + + def _canonicalize(self): + expr = self.expr + permutation = self.permutation + if isinstance(expr, PermuteDims): + subexpr = expr.expr + subperm = expr.permutation + permutation = permutation * subperm + expr = subexpr + if isinstance(expr, ArrayContraction): + expr, permutation = self._PermuteDims_denestarg_ArrayContraction(expr, permutation) + if isinstance(expr, ArrayTensorProduct): + expr, permutation = self._PermuteDims_denestarg_ArrayTensorProduct(expr, permutation) + if isinstance(expr, (ZeroArray, ZeroMatrix)): + return ZeroArray(*[expr.shape[i] for i in permutation.array_form]) + plist = permutation.array_form + if plist == sorted(plist): + return expr + return self.func(expr, permutation, canonicalize=False) + + @property + def expr(self): + return self.args[0] + + @property + def permutation(self): + return self.args[1] + + @classmethod + def _PermuteDims_denestarg_ArrayTensorProduct(cls, expr, permutation): + # Get the permutation in its image-form: + perm_image_form = _af_invert(permutation.array_form) + args = list(expr.args) + # Starting index global position for every arg: + cumul = list(accumulate([0] + expr.subranks)) + # Split `perm_image_form` into a list of list corresponding to the indices + # of every argument: + perm_image_form_in_components = [perm_image_form[cumul[i]:cumul[i+1]] for i in range(len(args))] + # Create an index, target-position-key array: + ps = [(i, sorted(comp)) for i, comp in enumerate(perm_image_form_in_components)] + # Sort the array according to the target-position-key: + # In this way, we define a canonical way to sort the arguments according + # to the permutation. + ps.sort(key=lambda x: x[1]) + # Read the inverse-permutation (i.e. image-form) of the args: + perm_args_image_form = [i[0] for i in ps] + # Apply the args-permutation to the `args`: + args_sorted = [args[i] for i in perm_args_image_form] + # Apply the args-permutation to the array-form of the permutation of the axes (of `expr`): + perm_image_form_sorted_args = [perm_image_form_in_components[i] for i in perm_args_image_form] + new_permutation = Permutation(_af_invert([j for i in perm_image_form_sorted_args for j in i])) + return _array_tensor_product(*args_sorted), new_permutation + + @classmethod + def _PermuteDims_denestarg_ArrayContraction(cls, expr, permutation): + if not isinstance(expr, ArrayContraction): + return expr, permutation + if not isinstance(expr.expr, ArrayTensorProduct): + return expr, permutation + args = expr.expr.args + subranks = [get_rank(arg) for arg in expr.expr.args] + + contraction_indices = expr.contraction_indices + contraction_indices_flat = [j for i in contraction_indices for j in i] + cumul = list(accumulate([0] + subranks)) + + # Spread the permutation in its array form across the args in the corresponding + # tensor-product arguments with free indices: + permutation_array_blocks_up = [] + image_form = _af_invert(permutation.array_form) + counter = 0 + for i in range(len(subranks)): + current = [] + for j in range(cumul[i], cumul[i+1]): + if j in contraction_indices_flat: + continue + current.append(image_form[counter]) + counter += 1 + permutation_array_blocks_up.append(current) + + # Get the map of axis repositioning for every argument of tensor-product: + index_blocks = [list(range(cumul[i], cumul[i+1])) for i, e in enumerate(expr.subranks)] + index_blocks_up = expr._push_indices_up(expr.contraction_indices, index_blocks) + inverse_permutation = permutation**(-1) + index_blocks_up_permuted = [[inverse_permutation(j) for j in i if j is not None] for i in index_blocks_up] + + # Sorting key is a list of tuple, first element is the index of `args`, second element of + # the tuple is the sorting key to sort `args` of the tensor product: + sorting_keys = list(enumerate(index_blocks_up_permuted)) + sorting_keys.sort(key=lambda x: x[1]) + + # Now we can get the permutation acting on the args in its image-form: + new_perm_image_form = [i[0] for i in sorting_keys] + # Apply the args-level permutation to various elements: + new_index_blocks = [index_blocks[i] for i in new_perm_image_form] + new_index_perm_array_form = _af_invert([j for i in new_index_blocks for j in i]) + new_args = [args[i] for i in new_perm_image_form] + new_contraction_indices = [tuple(new_index_perm_array_form[j] for j in i) for i in contraction_indices] + new_expr = _array_contraction(_array_tensor_product(*new_args), *new_contraction_indices) + new_permutation = Permutation(_af_invert([j for i in [permutation_array_blocks_up[k] for k in new_perm_image_form] for j in i])) + return new_expr, new_permutation + + @classmethod + def _check_permutation_mapping(cls, expr, permutation): + subranks = expr.subranks + index2arg = [i for i, arg in enumerate(expr.args) for j in range(expr.subranks[i])] + permuted_indices = [permutation(i) for i in range(expr.subrank())] + new_args = list(expr.args) + arg_candidate_index = index2arg[permuted_indices[0]] + current_indices = [] + new_permutation = [] + inserted_arg_cand_indices = set() + for i, idx in enumerate(permuted_indices): + if index2arg[idx] != arg_candidate_index: + new_permutation.extend(current_indices) + current_indices = [] + arg_candidate_index = index2arg[idx] + current_indices.append(idx) + arg_candidate_rank = subranks[arg_candidate_index] + if len(current_indices) == arg_candidate_rank: + new_permutation.extend(sorted(current_indices)) + local_current_indices = [j - min(current_indices) for j in current_indices] + i1 = index2arg[i] + new_args[i1] = _permute_dims(new_args[i1], Permutation(local_current_indices)) + inserted_arg_cand_indices.add(arg_candidate_index) + current_indices = [] + new_permutation.extend(current_indices) + + # TODO: swap args positions in order to simplify the expression: + # TODO: this should be in a function + args_positions = list(range(len(new_args))) + # Get possible shifts: + maps = {} + cumulative_subranks = [0] + list(accumulate(subranks)) + for i in range(len(subranks)): + s = {index2arg[new_permutation[j]] for j in range(cumulative_subranks[i], cumulative_subranks[i+1])} + if len(s) != 1: + continue + elem = next(iter(s)) + if i != elem: + maps[i] = elem + + # Find cycles in the map: + lines = [] + current_line = [] + while maps: + if len(current_line) == 0: + k, v = maps.popitem() + current_line.append(k) + else: + k = current_line[-1] + if k not in maps: + current_line = [] + continue + v = maps.pop(k) + if v in current_line: + lines.append(current_line) + current_line = [] + continue + current_line.append(v) + for line in lines: + for i, e in enumerate(line): + args_positions[line[(i + 1) % len(line)]] = e + + # TODO: function in order to permute the args: + permutation_blocks = [[new_permutation[cumulative_subranks[i] + j] for j in range(e)] for i, e in enumerate(subranks)] + new_args = [new_args[i] for i in args_positions] + new_permutation_blocks = [permutation_blocks[i] for i in args_positions] + new_permutation2 = [j for i in new_permutation_blocks for j in i] + return _array_tensor_product(*new_args), Permutation(new_permutation2) # **(-1) + + @classmethod + def _check_if_there_are_closed_cycles(cls, expr, permutation): + args = list(expr.args) + subranks = expr.subranks + cyclic_form = permutation.cyclic_form + cumulative_subranks = [0] + list(accumulate(subranks)) + cyclic_min = [min(i) for i in cyclic_form] + cyclic_max = [max(i) for i in cyclic_form] + cyclic_keep = [] + for i, cycle in enumerate(cyclic_form): + flag = True + for j in range(len(cumulative_subranks) - 1): + if cyclic_min[i] >= cumulative_subranks[j] and cyclic_max[i] < cumulative_subranks[j+1]: + # Found a sinkable cycle. + args[j] = _permute_dims(args[j], Permutation([[k - cumulative_subranks[j] for k in cycle]])) + flag = False + break + if flag: + cyclic_keep.append(cycle) + return _array_tensor_product(*args), Permutation(cyclic_keep, size=permutation.size) + + def nest_permutation(self): + r""" + DEPRECATED. + """ + ret = self._nest_permutation(self.expr, self.permutation) + if ret is None: + return self + return ret + + @classmethod + def _nest_permutation(cls, expr, permutation): + if isinstance(expr, ArrayTensorProduct): + return _permute_dims(*cls._check_if_there_are_closed_cycles(expr, permutation)) + elif isinstance(expr, ArrayContraction): + # Invert tree hierarchy: put the contraction above. + cycles = permutation.cyclic_form + newcycles = ArrayContraction._convert_outer_indices_to_inner_indices(expr, *cycles) + newpermutation = Permutation(newcycles) + new_contr_indices = [tuple(newpermutation(j) for j in i) for i in expr.contraction_indices] + return _array_contraction(PermuteDims(expr.expr, newpermutation), *new_contr_indices) + elif isinstance(expr, ArrayAdd): + return _array_add(*[PermuteDims(arg, permutation) for arg in expr.args]) + return None + + def as_explicit(self): + expr = self.expr + if hasattr(expr, "as_explicit"): + expr = expr.as_explicit() + return permutedims(expr, self.permutation) + + @classmethod + def _get_permutation_from_arguments(cls, permutation, index_order_old, index_order_new, dim): + if permutation is None: + if index_order_new is None or index_order_old is None: + raise ValueError("Permutation not defined") + return PermuteDims._get_permutation_from_index_orders(index_order_old, index_order_new, dim) + else: + if index_order_new is not None: + raise ValueError("index_order_new cannot be defined with permutation") + if index_order_old is not None: + raise ValueError("index_order_old cannot be defined with permutation") + return permutation + + @classmethod + def _get_permutation_from_index_orders(cls, index_order_old, index_order_new, dim): + if len(set(index_order_new)) != dim: + raise ValueError("wrong number of indices in index_order_new") + if len(set(index_order_old)) != dim: + raise ValueError("wrong number of indices in index_order_old") + if len(set.symmetric_difference(set(index_order_new), set(index_order_old))) > 0: + raise ValueError("index_order_new and index_order_old must have the same indices") + permutation = [index_order_old.index(i) for i in index_order_new] + return permutation + + +class ArrayDiagonal(_CodegenArrayAbstract): + r""" + Class to represent the diagonal operator. + + Explanation + =========== + + In a 2-dimensional array it returns the diagonal, this looks like the + operation: + + `A_{ij} \rightarrow A_{ii}` + + The diagonal over axes 1 and 2 (the second and third) of the tensor product + of two 2-dimensional arrays `A \otimes B` is + + `\Big[ A_{ab} B_{cd} \Big]_{abcd} \rightarrow \Big[ A_{ai} B_{id} \Big]_{adi}` + + In this last example the array expression has been reduced from + 4-dimensional to 3-dimensional. Notice that no contraction has occurred, + rather there is a new index `i` for the diagonal, contraction would have + reduced the array to 2 dimensions. + + Notice that the diagonalized out dimensions are added as new dimensions at + the end of the indices. + """ + + def __new__(cls, expr, *diagonal_indices, **kwargs): + expr = _sympify(expr) + diagonal_indices = [Tuple(*sorted(i)) for i in diagonal_indices] + canonicalize = kwargs.get("canonicalize", False) + + shape = get_shape(expr) + if shape is not None: + cls._validate(expr, *diagonal_indices, **kwargs) + # Get new shape: + positions, shape = cls._get_positions_shape(shape, diagonal_indices) + else: + positions = None + if len(diagonal_indices) == 0: + return expr + obj = Basic.__new__(cls, expr, *diagonal_indices) + obj._positions = positions + obj._subranks = _get_subranks(expr) + obj._shape = shape + if canonicalize: + return obj._canonicalize() + return obj + + def _canonicalize(self): + expr = self.expr + diagonal_indices = self.diagonal_indices + trivial_diags = [i for i in diagonal_indices if len(i) == 1] + if len(trivial_diags) > 0: + trivial_pos = {e[0]: i for i, e in enumerate(diagonal_indices) if len(e) == 1} + diag_pos = {e: i for i, e in enumerate(diagonal_indices) if len(e) > 1} + diagonal_indices_short = [i for i in diagonal_indices if len(i) > 1] + rank1 = get_rank(self) + rank2 = len(diagonal_indices) + rank3 = rank1 - rank2 + inv_permutation = [] + counter1 = 0 + indices_down = ArrayDiagonal._push_indices_down(diagonal_indices_short, list(range(rank1)), get_rank(expr)) + for i in indices_down: + if i in trivial_pos: + inv_permutation.append(rank3 + trivial_pos[i]) + elif isinstance(i, (Integer, int)): + inv_permutation.append(counter1) + counter1 += 1 + else: + inv_permutation.append(rank3 + diag_pos[i]) + permutation = _af_invert(inv_permutation) + if len(diagonal_indices_short) > 0: + return _permute_dims(_array_diagonal(expr, *diagonal_indices_short), permutation) + else: + return _permute_dims(expr, permutation) + if isinstance(expr, ArrayAdd): + return self._ArrayDiagonal_denest_ArrayAdd(expr, *diagonal_indices) + if isinstance(expr, ArrayDiagonal): + return self._ArrayDiagonal_denest_ArrayDiagonal(expr, *diagonal_indices) + if isinstance(expr, PermuteDims): + return self._ArrayDiagonal_denest_PermuteDims(expr, *diagonal_indices) + if isinstance(expr, (ZeroArray, ZeroMatrix)): + positions, shape = self._get_positions_shape(expr.shape, diagonal_indices) + return ZeroArray(*shape) + return self.func(expr, *diagonal_indices, canonicalize=False) + + @staticmethod + def _validate(expr, *diagonal_indices, **kwargs): + # Check that no diagonalization happens on indices with mismatched + # dimensions: + shape = get_shape(expr) + for i in diagonal_indices: + if any(j >= len(shape) for j in i): + raise ValueError("index is larger than expression shape") + if len({shape[j] for j in i}) != 1: + raise ValueError("diagonalizing indices of different dimensions") + if not kwargs.get("allow_trivial_diags", False) and len(i) <= 1: + raise ValueError("need at least two axes to diagonalize") + if len(set(i)) != len(i): + raise ValueError("axis index cannot be repeated") + + @staticmethod + def _remove_trivial_dimensions(shape, *diagonal_indices): + return [tuple(j for j in i) for i in diagonal_indices if shape[i[0]] != 1] + + @property + def expr(self): + return self.args[0] + + @property + def diagonal_indices(self): + return self.args[1:] + + @staticmethod + def _flatten(expr, *outer_diagonal_indices): + inner_diagonal_indices = expr.diagonal_indices + all_inner = [j for i in inner_diagonal_indices for j in i] + all_inner.sort() + # TODO: add API for total rank and cumulative rank: + total_rank = _get_subrank(expr) + inner_rank = len(all_inner) + outer_rank = total_rank - inner_rank + shifts = [0 for i in range(outer_rank)] + counter = 0 + pointer = 0 + for i in range(outer_rank): + while pointer < inner_rank and counter >= all_inner[pointer]: + counter += 1 + pointer += 1 + shifts[i] += pointer + counter += 1 + outer_diagonal_indices = tuple(tuple(shifts[j] + j for j in i) for i in outer_diagonal_indices) + diagonal_indices = inner_diagonal_indices + outer_diagonal_indices + return _array_diagonal(expr.expr, *diagonal_indices) + + @classmethod + def _ArrayDiagonal_denest_ArrayAdd(cls, expr, *diagonal_indices): + return _array_add(*[_array_diagonal(arg, *diagonal_indices) for arg in expr.args]) + + @classmethod + def _ArrayDiagonal_denest_ArrayDiagonal(cls, expr, *diagonal_indices): + return cls._flatten(expr, *diagonal_indices) + + @classmethod + def _ArrayDiagonal_denest_PermuteDims(cls, expr: PermuteDims, *diagonal_indices): + back_diagonal_indices = [[expr.permutation(j) for j in i] for i in diagonal_indices] + nondiag = [i for i in range(get_rank(expr)) if not any(i in j for j in diagonal_indices)] + back_nondiag = [expr.permutation(i) for i in nondiag] + remap = {e: i for i, e in enumerate(sorted(back_nondiag))} + new_permutation1 = [remap[i] for i in back_nondiag] + shift = len(new_permutation1) + diag_block_perm = [i + shift for i in range(len(back_diagonal_indices))] + new_permutation = new_permutation1 + diag_block_perm + return _permute_dims( + _array_diagonal( + expr.expr, + *back_diagonal_indices + ), + new_permutation + ) + + def _push_indices_down_nonstatic(self, indices): + transform = lambda x: self._positions[x] if x < len(self._positions) else None + return _apply_recursively_over_nested_lists(transform, indices) + + def _push_indices_up_nonstatic(self, indices): + + def transform(x): + for i, e in enumerate(self._positions): + if (isinstance(e, int) and x == e) or (isinstance(e, tuple) and x in e): + return i + + return _apply_recursively_over_nested_lists(transform, indices) + + @classmethod + def _push_indices_down(cls, diagonal_indices, indices, rank): + positions, shape = cls._get_positions_shape(range(rank), diagonal_indices) + transform = lambda x: positions[x] if x < len(positions) else None + return _apply_recursively_over_nested_lists(transform, indices) + + @classmethod + def _push_indices_up(cls, diagonal_indices, indices, rank): + positions, shape = cls._get_positions_shape(range(rank), diagonal_indices) + + def transform(x): + for i, e in enumerate(positions): + if (isinstance(e, int) and x == e) or (isinstance(e, (tuple, Tuple)) and (x in e)): + return i + + return _apply_recursively_over_nested_lists(transform, indices) + + @classmethod + def _get_positions_shape(cls, shape, diagonal_indices): + data1 = tuple((i, shp) for i, shp in enumerate(shape) if not any(i in j for j in diagonal_indices)) + pos1, shp1 = zip(*data1) if data1 else ((), ()) + data2 = tuple((i, shape[i[0]]) for i in diagonal_indices) + pos2, shp2 = zip(*data2) if data2 else ((), ()) + positions = pos1 + pos2 + shape = shp1 + shp2 + return positions, shape + + def as_explicit(self): + expr = self.expr + if hasattr(expr, "as_explicit"): + expr = expr.as_explicit() + return tensordiagonal(expr, *self.diagonal_indices) + + +class ArrayElementwiseApplyFunc(_CodegenArrayAbstract): + + def __new__(cls, function, element): + + if not isinstance(function, Lambda): + d = Dummy('d') + function = Lambda(d, function(d)) + + obj = _CodegenArrayAbstract.__new__(cls, function, element) + obj._subranks = _get_subranks(element) + return obj + + @property + def function(self): + return self.args[0] + + @property + def expr(self): + return self.args[1] + + @property + def shape(self): + return self.expr.shape + + def _get_function_fdiff(self): + d = Dummy("d") + function = self.function(d) + fdiff = function.diff(d) + if isinstance(fdiff, Function): + fdiff = type(fdiff) + else: + fdiff = Lambda(d, fdiff) + return fdiff + + def as_explicit(self): + expr = self.expr + if hasattr(expr, "as_explicit"): + expr = expr.as_explicit() + return expr.applyfunc(self.function) + + +class ArrayContraction(_CodegenArrayAbstract): + r""" + This class is meant to represent contractions of arrays in a form easily + processable by the code printers. + """ + + def __new__(cls, expr, *contraction_indices, **kwargs): + contraction_indices = _sort_contraction_indices(contraction_indices) + expr = _sympify(expr) + + canonicalize = kwargs.get("canonicalize", False) + + obj = Basic.__new__(cls, expr, *contraction_indices) + obj._subranks = _get_subranks(expr) + obj._mapping = _get_mapping_from_subranks(obj._subranks) + + free_indices_to_position = {i: i for i in range(sum(obj._subranks)) if all(i not in cind for cind in contraction_indices)} + obj._free_indices_to_position = free_indices_to_position + + shape = get_shape(expr) + cls._validate(expr, *contraction_indices) + if shape: + shape = tuple(shp for i, shp in enumerate(shape) if not any(i in j for j in contraction_indices)) + obj._shape = shape + if canonicalize: + return obj._canonicalize() + return obj + + def _canonicalize(self): + expr = self.expr + contraction_indices = self.contraction_indices + + if len(contraction_indices) == 0: + return expr + + if isinstance(expr, ArrayContraction): + return self._ArrayContraction_denest_ArrayContraction(expr, *contraction_indices) + + if isinstance(expr, (ZeroArray, ZeroMatrix)): + return self._ArrayContraction_denest_ZeroArray(expr, *contraction_indices) + + if isinstance(expr, PermuteDims): + return self._ArrayContraction_denest_PermuteDims(expr, *contraction_indices) + + if isinstance(expr, ArrayTensorProduct): + expr, contraction_indices = self._sort_fully_contracted_args(expr, contraction_indices) + expr, contraction_indices = self._lower_contraction_to_addends(expr, contraction_indices) + if len(contraction_indices) == 0: + return expr + + if isinstance(expr, ArrayDiagonal): + return self._ArrayContraction_denest_ArrayDiagonal(expr, *contraction_indices) + + if isinstance(expr, ArrayAdd): + return self._ArrayContraction_denest_ArrayAdd(expr, *contraction_indices) + + # Check single index contractions on 1-dimensional axes: + contraction_indices = [i for i in contraction_indices if len(i) > 1 or get_shape(expr)[i[0]] != 1] + if len(contraction_indices) == 0: + return expr + + return self.func(expr, *contraction_indices, canonicalize=False) + + def __mul__(self, other): + if other == 1: + return self + else: + raise NotImplementedError("Product of N-dim arrays is not uniquely defined. Use another method.") + + def __rmul__(self, other): + if other == 1: + return self + else: + raise NotImplementedError("Product of N-dim arrays is not uniquely defined. Use another method.") + + @staticmethod + def _validate(expr, *contraction_indices): + shape = get_shape(expr) + if shape is None: + return + + # Check that no contraction happens when the shape is mismatched: + for i in contraction_indices: + if len({shape[j] for j in i if shape[j] != -1}) != 1: + raise ValueError("contracting indices of different dimensions") + + @classmethod + def _push_indices_down(cls, contraction_indices, indices): + flattened_contraction_indices = [j for i in contraction_indices for j in i] + flattened_contraction_indices.sort() + transform = _build_push_indices_down_func_transformation(flattened_contraction_indices) + return _apply_recursively_over_nested_lists(transform, indices) + + @classmethod + def _push_indices_up(cls, contraction_indices, indices): + flattened_contraction_indices = [j for i in contraction_indices for j in i] + flattened_contraction_indices.sort() + transform = _build_push_indices_up_func_transformation(flattened_contraction_indices) + return _apply_recursively_over_nested_lists(transform, indices) + + @classmethod + def _lower_contraction_to_addends(cls, expr, contraction_indices): + if isinstance(expr, ArrayAdd): + raise NotImplementedError() + if not isinstance(expr, ArrayTensorProduct): + return expr, contraction_indices + subranks = expr.subranks + cumranks = list(accumulate([0] + subranks)) + contraction_indices_remaining = [] + contraction_indices_args = [[] for i in expr.args] + backshift = set() + for contraction_group in contraction_indices: + for j in range(len(expr.args)): + if not isinstance(expr.args[j], ArrayAdd): + continue + if all(cumranks[j] <= k < cumranks[j+1] for k in contraction_group): + contraction_indices_args[j].append([k - cumranks[j] for k in contraction_group]) + backshift.update(contraction_group) + break + else: + contraction_indices_remaining.append(contraction_group) + if len(contraction_indices_remaining) == len(contraction_indices): + return expr, contraction_indices + total_rank = get_rank(expr) + shifts = list(accumulate([1 if i in backshift else 0 for i in range(total_rank)])) + contraction_indices_remaining = [Tuple.fromiter(j - shifts[j] for j in i) for i in contraction_indices_remaining] + ret = _array_tensor_product(*[ + _array_contraction(arg, *contr) for arg, contr in zip(expr.args, contraction_indices_args) + ]) + return ret, contraction_indices_remaining + + def split_multiple_contractions(self): + """ + Recognize multiple contractions and attempt at rewriting them as paired-contractions. + + This allows some contractions involving more than two indices to be + rewritten as multiple contractions involving two indices, thus allowing + the expression to be rewritten as a matrix multiplication line. + + Examples: + + * `A_ij b_j0 C_jk` ===> `A*DiagMatrix(b)*C` + + Care for: + - matrix being diagonalized (i.e. `A_ii`) + - vectors being diagonalized (i.e. `a_i0`) + + Multiple contractions can be split into matrix multiplications if + not more than two arguments are non-diagonals or non-vectors. + Vectors get diagonalized while diagonal matrices remain diagonal. + The non-diagonal matrices can be at the beginning or at the end + of the final matrix multiplication line. + """ + + editor = _EditArrayContraction(self) + + contraction_indices = self.contraction_indices + + onearray_insert = [] + + for indl, links in enumerate(contraction_indices): + if len(links) <= 2: + continue + + # Check multiple contractions: + # + # Examples: + # + # * `A_ij b_j0 C_jk` ===> `A*DiagMatrix(b)*C \otimes OneArray(1)` with permutation (1 2) + # + # Care for: + # - matrix being diagonalized (i.e. `A_ii`) + # - vectors being diagonalized (i.e. `a_i0`) + + # Multiple contractions can be split into matrix multiplications if + # not more than three arguments are non-diagonals or non-vectors. + # + # Vectors get diagonalized while diagonal matrices remain diagonal. + # The non-diagonal matrices can be at the beginning or at the end + # of the final matrix multiplication line. + + positions = editor.get_mapping_for_index(indl) + + # Also consider the case of diagonal matrices being contracted: + current_dimension = self.expr.shape[links[0]] + + not_vectors = [] + vectors = [] + for arg_ind, rel_ind in positions: + arg = editor.args_with_ind[arg_ind] + mat = arg.element + abs_arg_start, abs_arg_end = editor.get_absolute_range(arg) + other_arg_pos = 1-rel_ind + other_arg_abs = abs_arg_start + other_arg_pos + if ((1 not in mat.shape) or + ((current_dimension == 1) is True and mat.shape != (1, 1)) or + any(other_arg_abs in l for li, l in enumerate(contraction_indices) if li != indl) + ): + not_vectors.append((arg, rel_ind)) + else: + vectors.append((arg, rel_ind)) + if len(not_vectors) > 2: + # If more than two arguments in the multiple contraction are + # non-vectors and non-diagonal matrices, we cannot find a way + # to split this contraction into a matrix multiplication line: + continue + # Three cases to handle: + # - zero non-vectors + # - one non-vector + # - two non-vectors + for v, rel_ind in vectors: + v.element = diagonalize_vector(v.element) + vectors_to_loop = not_vectors[:1] + vectors + not_vectors[1:] + first_not_vector, rel_ind = vectors_to_loop[0] + new_index = first_not_vector.indices[rel_ind] + + for v, rel_ind in vectors_to_loop[1:-1]: + v.indices[rel_ind] = new_index + new_index = editor.get_new_contraction_index() + assert v.indices.index(None) == 1 - rel_ind + v.indices[v.indices.index(None)] = new_index + onearray_insert.append(v) + + last_vec, rel_ind = vectors_to_loop[-1] + last_vec.indices[rel_ind] = new_index + + for v in onearray_insert: + editor.insert_after(v, _ArgE(OneArray(1), [None])) + + return editor.to_array_contraction() + + def flatten_contraction_of_diagonal(self): + if not isinstance(self.expr, ArrayDiagonal): + return self + contraction_down = self.expr._push_indices_down(self.expr.diagonal_indices, self.contraction_indices) + new_contraction_indices = [] + diagonal_indices = self.expr.diagonal_indices[:] + for i in contraction_down: + contraction_group = list(i) + for j in i: + diagonal_with = [k for k in diagonal_indices if j in k] + contraction_group.extend([l for k in diagonal_with for l in k]) + diagonal_indices = [k for k in diagonal_indices if k not in diagonal_with] + new_contraction_indices.append(sorted(set(contraction_group))) + + new_contraction_indices = ArrayDiagonal._push_indices_up(diagonal_indices, new_contraction_indices) + return _array_contraction( + _array_diagonal( + self.expr.expr, + *diagonal_indices + ), + *new_contraction_indices + ) + + @staticmethod + def _get_free_indices_to_position_map(free_indices, contraction_indices): + free_indices_to_position = {} + flattened_contraction_indices = [j for i in contraction_indices for j in i] + counter = 0 + for ind in free_indices: + while counter in flattened_contraction_indices: + counter += 1 + free_indices_to_position[ind] = counter + counter += 1 + return free_indices_to_position + + @staticmethod + def _get_index_shifts(expr): + """ + Get the mapping of indices at the positions before the contraction + occurs. + + Examples + ======== + + >>> from sympy.tensor.array import tensorproduct, tensorcontraction + >>> from sympy import MatrixSymbol + >>> M = MatrixSymbol("M", 3, 3) + >>> N = MatrixSymbol("N", 3, 3) + >>> cg = tensorcontraction(tensorproduct(M, N), [1, 2]) + >>> cg._get_index_shifts(cg) + [0, 2] + + Indeed, ``cg`` after the contraction has two dimensions, 0 and 1. They + need to be shifted by 0 and 2 to get the corresponding positions before + the contraction (that is, 0 and 3). + """ + inner_contraction_indices = expr.contraction_indices + all_inner = [j for i in inner_contraction_indices for j in i] + all_inner.sort() + # TODO: add API for total rank and cumulative rank: + total_rank = _get_subrank(expr) + inner_rank = len(all_inner) + outer_rank = total_rank - inner_rank + shifts = [0 for i in range(outer_rank)] + counter = 0 + pointer = 0 + for i in range(outer_rank): + while pointer < inner_rank and counter >= all_inner[pointer]: + counter += 1 + pointer += 1 + shifts[i] += pointer + counter += 1 + return shifts + + @staticmethod + def _convert_outer_indices_to_inner_indices(expr, *outer_contraction_indices): + shifts = ArrayContraction._get_index_shifts(expr) + outer_contraction_indices = tuple(tuple(shifts[j] + j for j in i) for i in outer_contraction_indices) + return outer_contraction_indices + + @staticmethod + def _flatten(expr, *outer_contraction_indices): + inner_contraction_indices = expr.contraction_indices + outer_contraction_indices = ArrayContraction._convert_outer_indices_to_inner_indices(expr, *outer_contraction_indices) + contraction_indices = inner_contraction_indices + outer_contraction_indices + return _array_contraction(expr.expr, *contraction_indices) + + @classmethod + def _ArrayContraction_denest_ArrayContraction(cls, expr, *contraction_indices): + return cls._flatten(expr, *contraction_indices) + + @classmethod + def _ArrayContraction_denest_ZeroArray(cls, expr, *contraction_indices): + contraction_indices_flat = [j for i in contraction_indices for j in i] + shape = [e for i, e in enumerate(expr.shape) if i not in contraction_indices_flat] + return ZeroArray(*shape) + + @classmethod + def _ArrayContraction_denest_ArrayAdd(cls, expr, *contraction_indices): + return _array_add(*[_array_contraction(i, *contraction_indices) for i in expr.args]) + + @classmethod + def _ArrayContraction_denest_PermuteDims(cls, expr, *contraction_indices): + permutation = expr.permutation + plist = permutation.array_form + new_contraction_indices = [tuple(permutation(j) for j in i) for i in contraction_indices] + new_plist = [i for i in plist if not any(i in j for j in new_contraction_indices)] + new_plist = cls._push_indices_up(new_contraction_indices, new_plist) + return _permute_dims( + _array_contraction(expr.expr, *new_contraction_indices), + Permutation(new_plist) + ) + + @classmethod + def _ArrayContraction_denest_ArrayDiagonal(cls, expr: 'ArrayDiagonal', *contraction_indices): + diagonal_indices = list(expr.diagonal_indices) + down_contraction_indices = expr._push_indices_down(expr.diagonal_indices, contraction_indices, get_rank(expr.expr)) + # Flatten diagonally contracted indices: + down_contraction_indices = [[k for j in i for k in (j if isinstance(j, (tuple, Tuple)) else [j])] for i in down_contraction_indices] + new_contraction_indices = [] + for contr_indgrp in down_contraction_indices: + ind = contr_indgrp[:] + for j, diag_indgrp in enumerate(diagonal_indices): + if diag_indgrp is None: + continue + if any(i in diag_indgrp for i in contr_indgrp): + ind.extend(diag_indgrp) + diagonal_indices[j] = None + new_contraction_indices.append(sorted(set(ind))) + + new_diagonal_indices_down = [i for i in diagonal_indices if i is not None] + new_diagonal_indices = ArrayContraction._push_indices_up(new_contraction_indices, new_diagonal_indices_down) + return _array_diagonal( + _array_contraction(expr.expr, *new_contraction_indices), + *new_diagonal_indices + ) + + @classmethod + def _sort_fully_contracted_args(cls, expr, contraction_indices): + if expr.shape is None: + return expr, contraction_indices + cumul = list(accumulate([0] + expr.subranks)) + index_blocks = [list(range(cumul[i], cumul[i+1])) for i in range(len(expr.args))] + contraction_indices_flat = {j for i in contraction_indices for j in i} + fully_contracted = [all(j in contraction_indices_flat for j in range(cumul[i], cumul[i+1])) for i, arg in enumerate(expr.args)] + new_pos = sorted(range(len(expr.args)), key=lambda x: (0, default_sort_key(expr.args[x])) if fully_contracted[x] else (1,)) + new_args = [expr.args[i] for i in new_pos] + new_index_blocks_flat = [j for i in new_pos for j in index_blocks[i]] + index_permutation_array_form = _af_invert(new_index_blocks_flat) + new_contraction_indices = [tuple(index_permutation_array_form[j] for j in i) for i in contraction_indices] + new_contraction_indices = _sort_contraction_indices(new_contraction_indices) + return _array_tensor_product(*new_args), new_contraction_indices + + def _get_contraction_tuples(self): + r""" + Return tuples containing the argument index and position within the + argument of the index position. + + Examples + ======== + + >>> from sympy import MatrixSymbol + >>> from sympy.abc import N + >>> from sympy.tensor.array import tensorproduct, tensorcontraction + >>> A = MatrixSymbol("A", N, N) + >>> B = MatrixSymbol("B", N, N) + + >>> cg = tensorcontraction(tensorproduct(A, B), (1, 2)) + >>> cg._get_contraction_tuples() + [[(0, 1), (1, 0)]] + + Notes + ===== + + Here the contraction pair `(1, 2)` meaning that the 2nd and 3rd indices + of the tensor product `A\otimes B` are contracted, has been transformed + into `(0, 1)` and `(1, 0)`, identifying the same indices in a different + notation. `(0, 1)` is the second index (1) of the first argument (i.e. + 0 or `A`). `(1, 0)` is the first index (i.e. 0) of the second + argument (i.e. 1 or `B`). + """ + mapping = self._mapping + return [[mapping[j] for j in i] for i in self.contraction_indices] + + @staticmethod + def _contraction_tuples_to_contraction_indices(expr, contraction_tuples): + # TODO: check that `expr` has `.subranks`: + ranks = expr.subranks + cumulative_ranks = [0] + list(accumulate(ranks)) + return [tuple(cumulative_ranks[j]+k for j, k in i) for i in contraction_tuples] + + @property + def free_indices(self): + return self._free_indices[:] + + @property + def free_indices_to_position(self): + return dict(self._free_indices_to_position) + + @property + def expr(self): + return self.args[0] + + @property + def contraction_indices(self): + return self.args[1:] + + def _contraction_indices_to_components(self): + expr = self.expr + if not isinstance(expr, ArrayTensorProduct): + raise NotImplementedError("only for contractions of tensor products") + ranks = expr.subranks + mapping = {} + counter = 0 + for i, rank in enumerate(ranks): + for j in range(rank): + mapping[counter] = (i, j) + counter += 1 + return mapping + + def sort_args_by_name(self): + """ + Sort arguments in the tensor product so that their order is lexicographical. + + Examples + ======== + + >>> from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array + >>> from sympy import MatrixSymbol + >>> from sympy.abc import N + >>> A = MatrixSymbol("A", N, N) + >>> B = MatrixSymbol("B", N, N) + >>> C = MatrixSymbol("C", N, N) + >>> D = MatrixSymbol("D", N, N) + + >>> cg = convert_matrix_to_array(C*D*A*B) + >>> cg + ArrayContraction(ArrayTensorProduct(A, D, C, B), (0, 3), (1, 6), (2, 5)) + >>> cg.sort_args_by_name() + ArrayContraction(ArrayTensorProduct(A, D, B, C), (0, 3), (1, 4), (2, 7)) + """ + expr = self.expr + if not isinstance(expr, ArrayTensorProduct): + return self + args = expr.args + sorted_data = sorted(enumerate(args), key=lambda x: default_sort_key(x[1])) + pos_sorted, args_sorted = zip(*sorted_data) + reordering_map = {i: pos_sorted.index(i) for i, arg in enumerate(args)} + contraction_tuples = self._get_contraction_tuples() + contraction_tuples = [[(reordering_map[j], k) for j, k in i] for i in contraction_tuples] + c_tp = _array_tensor_product(*args_sorted) + new_contr_indices = self._contraction_tuples_to_contraction_indices( + c_tp, + contraction_tuples + ) + return _array_contraction(c_tp, *new_contr_indices) + + def _get_contraction_links(self): + r""" + Returns a dictionary of links between arguments in the tensor product + being contracted. + + See the example for an explanation of the values. + + Examples + ======== + + >>> from sympy import MatrixSymbol + >>> from sympy.abc import N + >>> from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array + >>> A = MatrixSymbol("A", N, N) + >>> B = MatrixSymbol("B", N, N) + >>> C = MatrixSymbol("C", N, N) + >>> D = MatrixSymbol("D", N, N) + + Matrix multiplications are pairwise contractions between neighboring + matrices: + + `A_{ij} B_{jk} C_{kl} D_{lm}` + + >>> cg = convert_matrix_to_array(A*B*C*D) + >>> cg + ArrayContraction(ArrayTensorProduct(B, C, A, D), (0, 5), (1, 2), (3, 6)) + + >>> cg._get_contraction_links() + {0: {0: (2, 1), 1: (1, 0)}, 1: {0: (0, 1), 1: (3, 0)}, 2: {1: (0, 0)}, 3: {0: (1, 1)}} + + This dictionary is interpreted as follows: argument in position 0 (i.e. + matrix `A`) has its second index (i.e. 1) contracted to `(1, 0)`, that + is argument in position 1 (matrix `B`) on the first index slot of `B`, + this is the contraction provided by the index `j` from `A`. + + The argument in position 1 (that is, matrix `B`) has two contractions, + the ones provided by the indices `j` and `k`, respectively the first + and second indices (0 and 1 in the sub-dict). The link `(0, 1)` and + `(2, 0)` respectively. `(0, 1)` is the index slot 1 (the 2nd) of + argument in position 0 (that is, `A_{\ldot j}`), and so on. + """ + args, dlinks = _get_contraction_links([self], self.subranks, *self.contraction_indices) + return dlinks + + def as_explicit(self): + expr = self.expr + if hasattr(expr, "as_explicit"): + expr = expr.as_explicit() + return tensorcontraction(expr, *self.contraction_indices) + + +class Reshape(_CodegenArrayAbstract): + """ + Reshape the dimensions of an array expression. + + Examples + ======== + + >>> from sympy.tensor.array.expressions import ArraySymbol, Reshape + >>> A = ArraySymbol("A", (6,)) + >>> A.shape + (6,) + >>> Reshape(A, (3, 2)).shape + (3, 2) + + Check the component-explicit forms: + + >>> A.as_explicit() + [A[0], A[1], A[2], A[3], A[4], A[5]] + >>> Reshape(A, (3, 2)).as_explicit() + [[A[0], A[1]], [A[2], A[3]], [A[4], A[5]]] + + """ + + def __new__(cls, expr, shape): + expr = _sympify(expr) + if not isinstance(shape, Tuple): + shape = Tuple(*shape) + if Equality(Mul.fromiter(expr.shape), Mul.fromiter(shape)) == False: + raise ValueError("shape mismatch") + obj = Expr.__new__(cls, expr, shape) + obj._shape = tuple(shape) + obj._expr = expr + return obj + + @property + def shape(self): + return self._shape + + @property + def expr(self): + return self._expr + + def doit(self, *args, **kwargs): + if kwargs.get("deep", True): + expr = self.expr.doit(*args, **kwargs) + else: + expr = self.expr + if isinstance(expr, (MatrixBase, NDimArray)): + return expr.reshape(*self.shape) + return Reshape(expr, self.shape) + + def as_explicit(self): + ee = self.expr + if hasattr(ee, "as_explicit"): + ee = ee.as_explicit() + if isinstance(ee, MatrixBase): + from sympy import Array + ee = Array(ee) + elif isinstance(ee, MatrixExpr): + return self + return ee.reshape(*self.shape) + + +class _ArgE: + """ + The ``_ArgE`` object contains references to the array expression + (``.element``) and a list containing the information about index + contractions (``.indices``). + + Index contractions are numbered and contracted indices show the number of + the contraction. Uncontracted indices have ``None`` value. + + For example: + ``_ArgE(M, [None, 3])`` + This object means that expression ``M`` is part of an array contraction + and has two indices, the first is not contracted (value ``None``), + the second index is contracted to the 4th (i.e. number ``3``) group of the + array contraction object. + """ + indices: list[int | None] + + def __init__(self, element, indices: list[int | None] | None = None): + self.element = element + if indices is None: + self.indices = [None for i in range(get_rank(element))] + else: + self.indices = indices + + def __str__(self): + return "_ArgE(%s, %s)" % (self.element, self.indices) + + __repr__ = __str__ + + +class _IndPos: + """ + Index position, requiring two integers in the constructor: + + - arg: the position of the argument in the tensor product, + - rel: the relative position of the index inside the argument. + """ + def __init__(self, arg: int, rel: int): + self.arg = arg + self.rel = rel + + def __str__(self): + return "_IndPos(%i, %i)" % (self.arg, self.rel) + + __repr__ = __str__ + + def __iter__(self): + yield from [self.arg, self.rel] + + +class _EditArrayContraction: + """ + Utility class to help manipulate array contraction objects. + + This class takes as input an ``ArrayContraction`` object and turns it into + an editable object. + + The field ``args_with_ind`` of this class is a list of ``_ArgE`` objects + which can be used to easily edit the contraction structure of the + expression. + + Once editing is finished, the ``ArrayContraction`` object may be recreated + by calling the ``.to_array_contraction()`` method. + """ + + def __init__(self, base_array: typing.Union[ArrayContraction, ArrayDiagonal, ArrayTensorProduct]): + + expr: Basic + diagonalized: tuple[tuple[int, ...], ...] + contraction_indices: list[tuple[int]] + if isinstance(base_array, ArrayContraction): + mapping = _get_mapping_from_subranks(base_array.subranks) + expr = base_array.expr + contraction_indices = base_array.contraction_indices + diagonalized = () + elif isinstance(base_array, ArrayDiagonal): + + if isinstance(base_array.expr, ArrayContraction): + mapping = _get_mapping_from_subranks(base_array.expr.subranks) + expr = base_array.expr.expr + diagonalized = ArrayContraction._push_indices_down(base_array.expr.contraction_indices, base_array.diagonal_indices) + contraction_indices = base_array.expr.contraction_indices + elif isinstance(base_array.expr, ArrayTensorProduct): + mapping = {} + expr = base_array.expr + diagonalized = base_array.diagonal_indices + contraction_indices = [] + else: + mapping = {} + expr = base_array.expr + diagonalized = base_array.diagonal_indices + contraction_indices = [] + + elif isinstance(base_array, ArrayTensorProduct): + expr = base_array + contraction_indices = [] + diagonalized = () + else: + raise NotImplementedError() + + if isinstance(expr, ArrayTensorProduct): + args = list(expr.args) + else: + args = [expr] + + args_with_ind: list[_ArgE] = [_ArgE(arg) for arg in args] + for i, contraction_tuple in enumerate(contraction_indices): + for j in contraction_tuple: + arg_pos, rel_pos = mapping[j] + args_with_ind[arg_pos].indices[rel_pos] = i + self.args_with_ind: list[_ArgE] = args_with_ind + self.number_of_contraction_indices: int = len(contraction_indices) + self._track_permutation: list[list[int]] | None = None + + mapping = _get_mapping_from_subranks(base_array.subranks) + + # Trick: add diagonalized indices as negative indices into the editor object: + for i, e in enumerate(diagonalized): + for j in e: + arg_pos, rel_pos = mapping[j] + self.args_with_ind[arg_pos].indices[rel_pos] = -1 - i + + def insert_after(self, arg: _ArgE, new_arg: _ArgE): + pos = self.args_with_ind.index(arg) + self.args_with_ind.insert(pos + 1, new_arg) + + def get_new_contraction_index(self): + self.number_of_contraction_indices += 1 + return self.number_of_contraction_indices - 1 + + def refresh_indices(self): + updates = {} + for arg_with_ind in self.args_with_ind: + updates.update({i: -1 for i in arg_with_ind.indices if i is not None}) + for i, e in enumerate(sorted(updates)): + updates[e] = i + self.number_of_contraction_indices = len(updates) + for arg_with_ind in self.args_with_ind: + arg_with_ind.indices = [updates.get(i, None) for i in arg_with_ind.indices] + + def merge_scalars(self): + scalars = [] + for arg_with_ind in self.args_with_ind: + if len(arg_with_ind.indices) == 0: + scalars.append(arg_with_ind) + for i in scalars: + self.args_with_ind.remove(i) + scalar = Mul.fromiter([i.element for i in scalars]) + if len(self.args_with_ind) == 0: + self.args_with_ind.append(_ArgE(scalar)) + else: + from sympy.tensor.array.expressions.from_array_to_matrix import _a2m_tensor_product + self.args_with_ind[0].element = _a2m_tensor_product(scalar, self.args_with_ind[0].element) + + def to_array_contraction(self): + + # Count the ranks of the arguments: + counter = 0 + # Create a collector for the new diagonal indices: + diag_indices = defaultdict(list) + + count_index_freq = Counter() + for arg_with_ind in self.args_with_ind: + count_index_freq.update(Counter(arg_with_ind.indices)) + + free_index_count = count_index_freq[None] + + # Construct the inverse permutation: + inv_perm1 = [] + inv_perm2 = [] + # Keep track of which diagonal indices have already been processed: + done = set() + + # Counter for the diagonal indices: + counter4 = 0 + + for arg_with_ind in self.args_with_ind: + # If some diagonalization axes have been removed, they should be + # permuted in order to keep the permutation. + # Add permutation here + counter2 = 0 # counter for the indices + for i in arg_with_ind.indices: + if i is None: + inv_perm1.append(counter4) + counter2 += 1 + counter4 += 1 + continue + if i >= 0: + continue + # Reconstruct the diagonal indices: + diag_indices[-1 - i].append(counter + counter2) + if count_index_freq[i] == 1 and i not in done: + inv_perm1.append(free_index_count - 1 - i) + done.add(i) + elif i not in done: + inv_perm2.append(free_index_count - 1 - i) + done.add(i) + counter2 += 1 + # Remove negative indices to restore a proper editor object: + arg_with_ind.indices = [i if i is not None and i >= 0 else None for i in arg_with_ind.indices] + counter += len([i for i in arg_with_ind.indices if i is None or i < 0]) + + inverse_permutation = inv_perm1 + inv_perm2 + permutation = _af_invert(inverse_permutation) + + # Get the diagonal indices after the detection of HadamardProduct in the expression: + diag_indices_filtered = [tuple(v) for v in diag_indices.values() if len(v) > 1] + + self.merge_scalars() + self.refresh_indices() + args = [arg.element for arg in self.args_with_ind] + contraction_indices = self.get_contraction_indices() + expr = _array_contraction(_array_tensor_product(*args), *contraction_indices) + expr2 = _array_diagonal(expr, *diag_indices_filtered) + if self._track_permutation is not None: + permutation2 = _af_invert([j for i in self._track_permutation for j in i]) + expr2 = _permute_dims(expr2, permutation2) + + expr3 = _permute_dims(expr2, permutation) + return expr3 + + def get_contraction_indices(self) -> list[list[int]]: + contraction_indices: list[list[int]] = [[] for i in range(self.number_of_contraction_indices)] + current_position: int = 0 + for arg_with_ind in self.args_with_ind: + for j in arg_with_ind.indices: + if j is not None: + contraction_indices[j].append(current_position) + current_position += 1 + return contraction_indices + + def get_mapping_for_index(self, ind) -> list[_IndPos]: + if ind >= self.number_of_contraction_indices: + raise ValueError("index value exceeding the index range") + positions: list[_IndPos] = [] + for i, arg_with_ind in enumerate(self.args_with_ind): + for j, arg_ind in enumerate(arg_with_ind.indices): + if ind == arg_ind: + positions.append(_IndPos(i, j)) + return positions + + def get_contraction_indices_to_ind_rel_pos(self) -> list[list[_IndPos]]: + contraction_indices: list[list[_IndPos]] = [[] for i in range(self.number_of_contraction_indices)] + for i, arg_with_ind in enumerate(self.args_with_ind): + for j, ind in enumerate(arg_with_ind.indices): + if ind is not None: + contraction_indices[ind].append(_IndPos(i, j)) + return contraction_indices + + def count_args_with_index(self, index: int) -> int: + """ + Count the number of arguments that have the given index. + """ + counter: int = 0 + for arg_with_ind in self.args_with_ind: + if index in arg_with_ind.indices: + counter += 1 + return counter + + def get_args_with_index(self, index: int) -> list[_ArgE]: + """ + Get a list of arguments having the given index. + """ + ret: list[_ArgE] = [i for i in self.args_with_ind if index in i.indices] + return ret + + @property + def number_of_diagonal_indices(self): + data = set() + for arg in self.args_with_ind: + data.update({i for i in arg.indices if i is not None and i < 0}) + return len(data) + + def track_permutation_start(self): + permutation = [] + perm_diag = [] + counter = 0 + counter2 = -1 + for arg_with_ind in self.args_with_ind: + perm = [] + for i in arg_with_ind.indices: + if i is not None: + if i < 0: + perm_diag.append(counter2) + counter2 -= 1 + continue + perm.append(counter) + counter += 1 + permutation.append(perm) + max_ind = max(max(i) if i else -1 for i in permutation) if permutation else -1 + perm_diag = [max_ind - i for i in perm_diag] + self._track_permutation = permutation + [perm_diag] + + def track_permutation_merge(self, destination: _ArgE, from_element: _ArgE): + index_destination = self.args_with_ind.index(destination) + index_element = self.args_with_ind.index(from_element) + self._track_permutation[index_destination].extend(self._track_permutation[index_element]) # type: ignore + self._track_permutation.pop(index_element) # type: ignore + + def get_absolute_free_range(self, arg: _ArgE) -> typing.Tuple[int, int]: + """ + Return the range of the free indices of the arg as absolute positions + among all free indices. + """ + counter = 0 + for arg_with_ind in self.args_with_ind: + number_free_indices = len([i for i in arg_with_ind.indices if i is None]) + if arg_with_ind == arg: + return counter, counter + number_free_indices + counter += number_free_indices + raise IndexError("argument not found") + + def get_absolute_range(self, arg: _ArgE) -> typing.Tuple[int, int]: + """ + Return the absolute range of indices for arg, disregarding dummy + indices. + """ + counter = 0 + for arg_with_ind in self.args_with_ind: + number_indices = len(arg_with_ind.indices) + if arg_with_ind == arg: + return counter, counter + number_indices + counter += number_indices + raise IndexError("argument not found") + + +def get_rank(expr): + if isinstance(expr, (MatrixExpr, MatrixElement)): + return 2 + if isinstance(expr, _CodegenArrayAbstract): + return len(expr.shape) + if isinstance(expr, NDimArray): + return expr.rank() + if isinstance(expr, Indexed): + return expr.rank + if isinstance(expr, IndexedBase): + shape = expr.shape + if shape is None: + return -1 + else: + return len(shape) + if hasattr(expr, "shape"): + return len(expr.shape) + return 0 + + +def _get_subrank(expr): + if isinstance(expr, _CodegenArrayAbstract): + return expr.subrank() + return get_rank(expr) + + +def _get_subranks(expr): + if isinstance(expr, _CodegenArrayAbstract): + return expr.subranks + else: + return [get_rank(expr)] + + +def get_shape(expr): + if hasattr(expr, "shape"): + return expr.shape + return () + + +def nest_permutation(expr): + if isinstance(expr, PermuteDims): + return expr.nest_permutation() + else: + return expr + + +def _array_tensor_product(*args, **kwargs): + return ArrayTensorProduct(*args, canonicalize=True, **kwargs) + + +def _array_contraction(expr, *contraction_indices, **kwargs): + return ArrayContraction(expr, *contraction_indices, canonicalize=True, **kwargs) + + +def _array_diagonal(expr, *diagonal_indices, **kwargs): + return ArrayDiagonal(expr, *diagonal_indices, canonicalize=True, **kwargs) + + +def _permute_dims(expr, permutation, **kwargs): + return PermuteDims(expr, permutation, canonicalize=True, **kwargs) + + +def _array_add(*args, **kwargs): + return ArrayAdd(*args, canonicalize=True, **kwargs) + + +def _get_array_element_or_slice(expr, indices): + return ArrayElement(expr, indices) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/arrayexpr_derivatives.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/arrayexpr_derivatives.py new file mode 100644 index 0000000000000000000000000000000000000000..ab44a6fbf715ac7f2b8c287dcc84a49289f2dd76 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/arrayexpr_derivatives.py @@ -0,0 +1,194 @@ +import operator +from functools import reduce, singledispatch + +from sympy.core.expr import Expr +from sympy.core.singleton import S +from sympy.matrices.expressions.hadamard import HadamardProduct +from sympy.matrices.expressions.inverse import Inverse +from sympy.matrices.expressions.matexpr import (MatrixExpr, MatrixSymbol) +from sympy.matrices.expressions.special import Identity, OneMatrix +from sympy.matrices.expressions.transpose import Transpose +from sympy.combinatorics.permutations import _af_invert +from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction +from sympy.tensor.array.expressions.array_expressions import ( + _ArrayExpr, ZeroArray, ArraySymbol, ArrayTensorProduct, ArrayAdd, + PermuteDims, ArrayDiagonal, ArrayElementwiseApplyFunc, get_rank, + get_shape, ArrayContraction, _array_tensor_product, _array_contraction, + _array_diagonal, _array_add, _permute_dims, Reshape) +from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array + + +@singledispatch +def array_derive(expr, x): + """ + Derivatives (gradients) for array expressions. + """ + raise NotImplementedError(f"not implemented for type {type(expr)}") + + +@array_derive.register(Expr) +def _(expr: Expr, x: _ArrayExpr): + return ZeroArray(*x.shape) + + +@array_derive.register(ArrayTensorProduct) +def _(expr: ArrayTensorProduct, x: Expr): + args = expr.args + addend_list = [] + for i, arg in enumerate(expr.args): + darg = array_derive(arg, x) + if darg == 0: + continue + args_prev = args[:i] + args_succ = args[i+1:] + shape_prev = reduce(operator.add, map(get_shape, args_prev), ()) + shape_succ = reduce(operator.add, map(get_shape, args_succ), ()) + addend = _array_tensor_product(*args_prev, darg, *args_succ) + tot1 = len(get_shape(x)) + tot2 = tot1 + len(shape_prev) + tot3 = tot2 + len(get_shape(arg)) + tot4 = tot3 + len(shape_succ) + perm = list(range(tot1, tot2)) + \ + list(range(tot1)) + list(range(tot2, tot3)) + \ + list(range(tot3, tot4)) + addend = _permute_dims(addend, _af_invert(perm)) + addend_list.append(addend) + if len(addend_list) == 1: + return addend_list[0] + elif len(addend_list) == 0: + return S.Zero + else: + return _array_add(*addend_list) + + +@array_derive.register(ArraySymbol) +def _(expr: ArraySymbol, x: _ArrayExpr): + if expr == x: + return _permute_dims( + ArrayTensorProduct.fromiter(Identity(i) for i in expr.shape), + [2*i for i in range(len(expr.shape))] + [2*i+1 for i in range(len(expr.shape))] + ) + return ZeroArray(*(x.shape + expr.shape)) + + +@array_derive.register(MatrixSymbol) +def _(expr: MatrixSymbol, x: _ArrayExpr): + m, n = expr.shape + if expr == x: + return _permute_dims( + _array_tensor_product(Identity(m), Identity(n)), + [0, 2, 1, 3] + ) + return ZeroArray(*(x.shape + expr.shape)) + + +@array_derive.register(Identity) +def _(expr: Identity, x: _ArrayExpr): + return ZeroArray(*(x.shape + expr.shape)) + + +@array_derive.register(OneMatrix) +def _(expr: OneMatrix, x: _ArrayExpr): + return ZeroArray(*(x.shape + expr.shape)) + + +@array_derive.register(Transpose) +def _(expr: Transpose, x: Expr): + # D(A.T, A) ==> (m,n,i,j) ==> D(A_ji, A_mn) = d_mj d_ni + # D(B.T, A) ==> (m,n,i,j) ==> D(B_ji, A_mn) + fd = array_derive(expr.arg, x) + return _permute_dims(fd, [0, 1, 3, 2]) + + +@array_derive.register(Inverse) +def _(expr: Inverse, x: Expr): + mat = expr.I + dexpr = array_derive(mat, x) + tp = _array_tensor_product(-expr, dexpr, expr) + mp = _array_contraction(tp, (1, 4), (5, 6)) + pp = _permute_dims(mp, [1, 2, 0, 3]) + return pp + + +@array_derive.register(ElementwiseApplyFunction) +def _(expr: ElementwiseApplyFunction, x: Expr): + assert get_rank(expr) == 2 + assert get_rank(x) == 2 + fdiff = expr._get_function_fdiff() + dexpr = array_derive(expr.expr, x) + tp = _array_tensor_product( + ElementwiseApplyFunction(fdiff, expr.expr), + dexpr + ) + td = _array_diagonal( + tp, (0, 4), (1, 5) + ) + return td + + +@array_derive.register(ArrayElementwiseApplyFunc) +def _(expr: ArrayElementwiseApplyFunc, x: Expr): + fdiff = expr._get_function_fdiff() + subexpr = expr.expr + dsubexpr = array_derive(subexpr, x) + tp = _array_tensor_product( + dsubexpr, + ArrayElementwiseApplyFunc(fdiff, subexpr) + ) + b = get_rank(x) + c = get_rank(expr) + diag_indices = [(b + i, b + c + i) for i in range(c)] + return _array_diagonal(tp, *diag_indices) + + +@array_derive.register(MatrixExpr) +def _(expr: MatrixExpr, x: Expr): + cg = convert_matrix_to_array(expr) + return array_derive(cg, x) + + +@array_derive.register(HadamardProduct) +def _(expr: HadamardProduct, x: Expr): + raise NotImplementedError() + + +@array_derive.register(ArrayContraction) +def _(expr: ArrayContraction, x: Expr): + fd = array_derive(expr.expr, x) + rank_x = len(get_shape(x)) + contraction_indices = expr.contraction_indices + new_contraction_indices = [tuple(j + rank_x for j in i) for i in contraction_indices] + return _array_contraction(fd, *new_contraction_indices) + + +@array_derive.register(ArrayDiagonal) +def _(expr: ArrayDiagonal, x: Expr): + dsubexpr = array_derive(expr.expr, x) + rank_x = len(get_shape(x)) + diag_indices = [[j + rank_x for j in i] for i in expr.diagonal_indices] + return _array_diagonal(dsubexpr, *diag_indices) + + +@array_derive.register(ArrayAdd) +def _(expr: ArrayAdd, x: Expr): + return _array_add(*[array_derive(arg, x) for arg in expr.args]) + + +@array_derive.register(PermuteDims) +def _(expr: PermuteDims, x: Expr): + de = array_derive(expr.expr, x) + perm = [0, 1] + [i + 2 for i in expr.permutation.array_form] + return _permute_dims(de, perm) + + +@array_derive.register(Reshape) +def _(expr: Reshape, x: Expr): + de = array_derive(expr.expr, x) + return Reshape(de, get_shape(x) + expr.shape) + + +def matrix_derive(expr, x): + from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix + ce = convert_matrix_to_array(expr) + dce = array_derive(ce, x) + return convert_array_to_matrix(dce).doit() diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_array_to_indexed.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_array_to_indexed.py new file mode 100644 index 0000000000000000000000000000000000000000..1929c3401e131cca0a83080131ead9198b37bcbb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_array_to_indexed.py @@ -0,0 +1,12 @@ +from sympy.tensor.array.expressions import from_array_to_indexed +from sympy.utilities.decorator import deprecated + + +_conv_to_from_decorator = deprecated( + "module has been renamed by replacing 'conv_' with 'from_' in its name", + deprecated_since_version="1.11", + active_deprecations_target="deprecated-conv-array-expr-module-names", +) + + +convert_array_to_indexed = _conv_to_from_decorator(from_array_to_indexed.convert_array_to_indexed) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_array_to_matrix.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_array_to_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..2708e74aaa98d6ee38eae46d97d4483a546e0776 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_array_to_matrix.py @@ -0,0 +1,6 @@ +from sympy.tensor.array.expressions import from_array_to_matrix +from sympy.tensor.array.expressions.conv_array_to_indexed import _conv_to_from_decorator + +convert_array_to_matrix = _conv_to_from_decorator(from_array_to_matrix.convert_array_to_matrix) +_array2matrix = _conv_to_from_decorator(from_array_to_matrix._array2matrix) +_remove_trivial_dims = _conv_to_from_decorator(from_array_to_matrix._remove_trivial_dims) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_indexed_to_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_indexed_to_array.py new file mode 100644 index 0000000000000000000000000000000000000000..6058b31f20778834ea23a01553d594b7965eb6bb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_indexed_to_array.py @@ -0,0 +1,4 @@ +from sympy.tensor.array.expressions import from_indexed_to_array +from sympy.tensor.array.expressions.conv_array_to_indexed import _conv_to_from_decorator + +convert_indexed_to_array = _conv_to_from_decorator(from_indexed_to_array.convert_indexed_to_array) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_matrix_to_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_matrix_to_array.py new file mode 100644 index 0000000000000000000000000000000000000000..46469df60703c237527c0b2834235309640afe7c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/conv_matrix_to_array.py @@ -0,0 +1,4 @@ +from sympy.tensor.array.expressions import from_matrix_to_array +from sympy.tensor.array.expressions.conv_array_to_indexed import _conv_to_from_decorator + +convert_matrix_to_array = _conv_to_from_decorator(from_matrix_to_array.convert_matrix_to_array) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_array_to_indexed.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_array_to_indexed.py new file mode 100644 index 0000000000000000000000000000000000000000..9eb86e7cfbe31ebfe7c9649803d9cb5e34b98276 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_array_to_indexed.py @@ -0,0 +1,84 @@ +import collections.abc +import operator +from itertools import accumulate + +from sympy import Mul, Sum, Dummy, Add +from sympy.tensor.array.expressions import PermuteDims, ArrayAdd, ArrayElementwiseApplyFunc, Reshape +from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, get_rank, ArrayContraction, \ + ArrayDiagonal, get_shape, _get_array_element_or_slice, _ArrayExpr +from sympy.tensor.array.expressions.utils import _apply_permutation_to_list + + +def convert_array_to_indexed(expr, indices): + return _ConvertArrayToIndexed().do_convert(expr, indices) + + +class _ConvertArrayToIndexed: + + def __init__(self): + self.count_dummies = 0 + + def do_convert(self, expr, indices): + if isinstance(expr, ArrayTensorProduct): + cumul = list(accumulate([0] + [get_rank(arg) for arg in expr.args])) + indices_grp = [indices[cumul[i]:cumul[i+1]] for i in range(len(expr.args))] + return Mul.fromiter(self.do_convert(arg, ind) for arg, ind in zip(expr.args, indices_grp)) + if isinstance(expr, ArrayContraction): + new_indices = [None for i in range(get_rank(expr.expr))] + limits = [] + bottom_shape = get_shape(expr.expr) + for contraction_index_grp in expr.contraction_indices: + d = Dummy(f"d{self.count_dummies}") + self.count_dummies += 1 + dim = bottom_shape[contraction_index_grp[0]] + limits.append((d, 0, dim-1)) + for i in contraction_index_grp: + new_indices[i] = d + j = 0 + for i in range(len(new_indices)): + if new_indices[i] is None: + new_indices[i] = indices[j] + j += 1 + newexpr = self.do_convert(expr.expr, new_indices) + return Sum(newexpr, *limits) + if isinstance(expr, ArrayDiagonal): + new_indices = [None for i in range(get_rank(expr.expr))] + ind_pos = expr._push_indices_down(expr.diagonal_indices, list(range(len(indices))), get_rank(expr)) + for i, index in zip(ind_pos, indices): + if isinstance(i, collections.abc.Iterable): + for j in i: + new_indices[j] = index + else: + new_indices[i] = index + newexpr = self.do_convert(expr.expr, new_indices) + return newexpr + if isinstance(expr, PermuteDims): + permuted_indices = _apply_permutation_to_list(expr.permutation, indices) + return self.do_convert(expr.expr, permuted_indices) + if isinstance(expr, ArrayAdd): + return Add.fromiter(self.do_convert(arg, indices) for arg in expr.args) + if isinstance(expr, _ArrayExpr): + return expr.__getitem__(tuple(indices)) + if isinstance(expr, ArrayElementwiseApplyFunc): + return expr.function(self.do_convert(expr.expr, indices)) + if isinstance(expr, Reshape): + shape_up = expr.shape + shape_down = get_shape(expr.expr) + cumul = list(accumulate([1] + list(reversed(shape_up)), operator.mul)) + one_index = Add.fromiter(i*s for i, s in zip(reversed(indices), cumul)) + dest_indices = [None for _ in shape_down] + c = 1 + for i, e in enumerate(reversed(shape_down)): + if c == 1: + if i == len(shape_down) - 1: + dest_indices[i] = one_index + else: + dest_indices[i] = one_index % e + elif i == len(shape_down) - 1: + dest_indices[i] = one_index // c + else: + dest_indices[i] = one_index // c % e + c *= e + dest_indices.reverse() + return self.do_convert(expr.expr, dest_indices) + return _get_array_element_or_slice(expr, indices) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_array_to_matrix.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_array_to_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..debfdd7eb5c4533996b3d72b55d679be3daf3afe --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_array_to_matrix.py @@ -0,0 +1,1004 @@ +from __future__ import annotations +import itertools +from collections import defaultdict +from typing import FrozenSet +from functools import singledispatch +from itertools import accumulate + +from sympy import MatMul, Basic, Wild, KroneckerProduct +from sympy.assumptions.ask import (Q, ask) +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.matrices.expressions.diagonal import DiagMatrix +from sympy.matrices.expressions.hadamard import hadamard_product, HadamardPower +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.matrices.expressions.special import (Identity, ZeroMatrix, OneMatrix) +from sympy.matrices.expressions.trace import Trace +from sympy.matrices.expressions.transpose import Transpose +from sympy.combinatorics.permutations import _af_invert, Permutation +from sympy.matrices.matrixbase import MatrixBase +from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction +from sympy.matrices.expressions.matexpr import MatrixElement +from sympy.tensor.array.expressions.array_expressions import PermuteDims, ArrayDiagonal, \ + ArrayTensorProduct, OneArray, get_rank, _get_subrank, ZeroArray, ArrayContraction, \ + ArrayAdd, _CodegenArrayAbstract, get_shape, ArrayElementwiseApplyFunc, _ArrayExpr, _EditArrayContraction, _ArgE, \ + ArrayElement, _array_tensor_product, _array_contraction, _array_diagonal, _array_add, _permute_dims +from sympy.tensor.array.expressions.utils import _get_mapping_from_subranks + + +def _get_candidate_for_matmul_from_contraction(scan_indices: list[int | None], remaining_args: list[_ArgE]) -> tuple[_ArgE | None, bool, int]: + + scan_indices_int: list[int] = [i for i in scan_indices if i is not None] + if len(scan_indices_int) == 0: + return None, False, -1 + + transpose: bool = False + candidate: _ArgE | None = None + candidate_index: int = -1 + for arg_with_ind2 in remaining_args: + if not isinstance(arg_with_ind2.element, MatrixExpr): + continue + for index in scan_indices_int: + if candidate_index != -1 and candidate_index != index: + # A candidate index has already been selected, check + # repetitions only for that index: + continue + if index in arg_with_ind2.indices: + if set(arg_with_ind2.indices) == {index}: + # Index repeated twice in arg_with_ind2 + candidate = None + break + if candidate is None: + candidate = arg_with_ind2 + candidate_index = index + transpose = (index == arg_with_ind2.indices[1]) + else: + # Index repeated more than twice, break + candidate = None + break + return candidate, transpose, candidate_index + + +def _insert_candidate_into_editor(editor: _EditArrayContraction, arg_with_ind: _ArgE, candidate: _ArgE, transpose1: bool, transpose2: bool): + other = candidate.element + other_index: int | None + if transpose2: + other = Transpose(other) + other_index = candidate.indices[0] + else: + other_index = candidate.indices[1] + new_element = (Transpose(arg_with_ind.element) if transpose1 else arg_with_ind.element) * other + editor.args_with_ind.remove(candidate) + new_arge = _ArgE(new_element) + return new_arge, other_index + + +def _support_function_tp1_recognize(contraction_indices, args): + if len(contraction_indices) == 0: + return _a2m_tensor_product(*args) + + ac = _array_contraction(_array_tensor_product(*args), *contraction_indices) + editor = _EditArrayContraction(ac) + editor.track_permutation_start() + + while True: + flag_stop = True + for i, arg_with_ind in enumerate(editor.args_with_ind): + if not isinstance(arg_with_ind.element, MatrixExpr): + continue + + first_index = arg_with_ind.indices[0] + second_index = arg_with_ind.indices[1] + + first_frequency = editor.count_args_with_index(first_index) + second_frequency = editor.count_args_with_index(second_index) + + if first_index is not None and first_frequency == 1 and first_index == second_index: + flag_stop = False + arg_with_ind.element = Trace(arg_with_ind.element)._normalize() + arg_with_ind.indices = [] + break + + scan_indices = [] + if first_frequency == 2: + scan_indices.append(first_index) + if second_frequency == 2: + scan_indices.append(second_index) + + candidate, transpose, found_index = _get_candidate_for_matmul_from_contraction(scan_indices, editor.args_with_ind[i+1:]) + if candidate is not None: + flag_stop = False + editor.track_permutation_merge(arg_with_ind, candidate) + transpose1 = found_index == first_index + new_arge, other_index = _insert_candidate_into_editor(editor, arg_with_ind, candidate, transpose1, transpose) + if found_index == first_index: + new_arge.indices = [second_index, other_index] + else: + new_arge.indices = [first_index, other_index] + set_indices = set(new_arge.indices) + if len(set_indices) == 1 and set_indices != {None}: + # This is a trace: + new_arge.element = Trace(new_arge.element)._normalize() + new_arge.indices = [] + editor.args_with_ind[i] = new_arge + # TODO: is this break necessary? + break + + if flag_stop: + break + + editor.refresh_indices() + return editor.to_array_contraction() + + +def _find_trivial_matrices_rewrite(expr: ArrayTensorProduct): + # If there are matrices of trivial shape in the tensor product (i.e. shape + # (1, 1)), try to check if there is a suitable non-trivial MatMul where the + # expression can be inserted. + + # For example, if "a" has shape (1, 1) and "b" has shape (k, 1), the + # expressions "_array_tensor_product(a, b*b.T)" can be rewritten as + # "b*a*b.T" + + trivial_matrices = [] + pos: int | None = None # must be initialized else causes UnboundLocalError + first: MatrixExpr | None = None # may cause UnboundLocalError if not initialized + second: MatrixExpr | None = None # may cause UnboundLocalError if not initialized + removed: list[int] = [] + counter: int = 0 + args: list[Basic | None] = list(expr.args) + for i, arg in enumerate(expr.args): + if isinstance(arg, MatrixExpr): + if arg.shape == (1, 1): + trivial_matrices.append(arg) + args[i] = None + removed.extend([counter, counter+1]) + elif pos is None and isinstance(arg, MatMul): + margs = arg.args + for j, e in enumerate(margs): + if isinstance(e, MatrixExpr) and e.shape[1] == 1: + pos = i + first = MatMul.fromiter(margs[:j+1]) + second = MatMul.fromiter(margs[j+1:]) + break + counter += get_rank(arg) + if pos is None: + return expr, [] + args[pos] = (first*MatMul.fromiter(i for i in trivial_matrices)*second).doit() + return _array_tensor_product(*[i for i in args if i is not None]), removed + + +def _find_trivial_kronecker_products_broadcast(expr: ArrayTensorProduct): + newargs: list[Basic] = [] + removed = [] + count_dims = 0 + for arg in expr.args: + count_dims += get_rank(arg) + shape = get_shape(arg) + current_range = [count_dims-i for i in range(len(shape), 0, -1)] + if (shape == (1, 1) and len(newargs) > 0 and 1 not in get_shape(newargs[-1]) and + isinstance(newargs[-1], MatrixExpr) and isinstance(arg, MatrixExpr)): + # KroneckerProduct object allows the trick of broadcasting: + newargs[-1] = KroneckerProduct(newargs[-1], arg) + removed.extend(current_range) + elif 1 not in shape and len(newargs) > 0 and get_shape(newargs[-1]) == (1, 1): + # Broadcast: + newargs[-1] = KroneckerProduct(newargs[-1], arg) + prev_range = [i for i in range(min(current_range)) if i not in removed] + removed.extend(prev_range[-2:]) + else: + newargs.append(arg) + return _array_tensor_product(*newargs), removed + + +@singledispatch +def _array2matrix(expr): + return expr + + +@_array2matrix.register(ZeroArray) +def _(expr: ZeroArray): + if get_rank(expr) == 2: + return ZeroMatrix(*expr.shape) + else: + return expr + + +@_array2matrix.register(ArrayTensorProduct) +def _(expr: ArrayTensorProduct): + return _a2m_tensor_product(*[_array2matrix(arg) for arg in expr.args]) + + +@_array2matrix.register(ArrayContraction) +def _(expr: ArrayContraction): + expr = expr.flatten_contraction_of_diagonal() + expr = identify_removable_identity_matrices(expr) + expr = expr.split_multiple_contractions() + expr = identify_hadamard_products(expr) + if not isinstance(expr, ArrayContraction): + return _array2matrix(expr) + subexpr = expr.expr + contraction_indices: tuple[tuple[int]] = expr.contraction_indices + if contraction_indices == ((0,), (1,)) or ( + contraction_indices == ((0,),) and subexpr.shape[1] == 1 + ) or ( + contraction_indices == ((1,),) and subexpr.shape[0] == 1 + ): + shape = subexpr.shape + subexpr = _array2matrix(subexpr) + if isinstance(subexpr, MatrixExpr): + return OneMatrix(1, shape[0])*subexpr*OneMatrix(shape[1], 1) + if isinstance(subexpr, ArrayTensorProduct): + newexpr = _array_contraction(_array2matrix(subexpr), *contraction_indices) + contraction_indices = newexpr.contraction_indices + if any(i > 2 for i in newexpr.subranks): + addends = _array_add(*[_a2m_tensor_product(*j) for j in itertools.product(*[i.args if isinstance(i, + ArrayAdd) else [i] for i in expr.expr.args])]) + newexpr = _array_contraction(addends, *contraction_indices) + if isinstance(newexpr, ArrayAdd): + ret = _array2matrix(newexpr) + return ret + assert isinstance(newexpr, ArrayContraction) + ret = _support_function_tp1_recognize(contraction_indices, list(newexpr.expr.args)) + return ret + elif not isinstance(subexpr, _CodegenArrayAbstract): + ret = _array2matrix(subexpr) + if isinstance(ret, MatrixExpr): + assert expr.contraction_indices == ((0, 1),) + return _a2m_trace(ret) + else: + return _array_contraction(ret, *expr.contraction_indices) + + +@_array2matrix.register(ArrayDiagonal) +def _(expr: ArrayDiagonal): + pexpr = _array_diagonal(_array2matrix(expr.expr), *expr.diagonal_indices) + pexpr = identify_hadamard_products(pexpr) + if isinstance(pexpr, ArrayDiagonal): + pexpr = _array_diag2contr_diagmatrix(pexpr) + if expr == pexpr: + return expr + return _array2matrix(pexpr) + + +@_array2matrix.register(PermuteDims) +def _(expr: PermuteDims): + if expr.permutation.array_form == [1, 0]: + return _a2m_transpose(_array2matrix(expr.expr)) + elif isinstance(expr.expr, ArrayTensorProduct): + ranks = expr.expr.subranks + inv_permutation = expr.permutation**(-1) + newrange = [inv_permutation(i) for i in range(sum(ranks))] + newpos = [] + counter = 0 + for rank in ranks: + newpos.append(newrange[counter:counter+rank]) + counter += rank + newargs = [] + newperm = [] + scalars = [] + for pos, arg in zip(newpos, expr.expr.args): + if len(pos) == 0: + scalars.append(_array2matrix(arg)) + elif pos == sorted(pos): + newargs.append((_array2matrix(arg), pos[0])) + newperm.extend(pos) + elif len(pos) == 2: + newargs.append((_a2m_transpose(_array2matrix(arg)), pos[0])) + newperm.extend(reversed(pos)) + else: + raise NotImplementedError() + newargs = [i[0] for i in newargs] + return _permute_dims(_a2m_tensor_product(*scalars, *newargs), _af_invert(newperm)) + elif isinstance(expr.expr, ArrayContraction): + mat_mul_lines = _array2matrix(expr.expr) + if not isinstance(mat_mul_lines, ArrayTensorProduct): + return _permute_dims(mat_mul_lines, expr.permutation) + # TODO: this assumes that all arguments are matrices, it may not be the case: + permutation = Permutation(2*len(mat_mul_lines.args)-1)*expr.permutation + permuted = [permutation(i) for i in range(2*len(mat_mul_lines.args))] + args_array = [None for i in mat_mul_lines.args] + for i in range(len(mat_mul_lines.args)): + p1 = permuted[2*i] + p2 = permuted[2*i+1] + if p1 // 2 != p2 // 2: + return _permute_dims(mat_mul_lines, permutation) + if p1 > p2: + args_array[i] = _a2m_transpose(mat_mul_lines.args[p1 // 2]) + else: + args_array[i] = mat_mul_lines.args[p1 // 2] + return _a2m_tensor_product(*args_array) + else: + return expr + + +@_array2matrix.register(ArrayAdd) +def _(expr: ArrayAdd): + addends = [_array2matrix(arg) for arg in expr.args] + return _a2m_add(*addends) + + +@_array2matrix.register(ArrayElementwiseApplyFunc) +def _(expr: ArrayElementwiseApplyFunc): + subexpr = _array2matrix(expr.expr) + if isinstance(subexpr, MatrixExpr): + if subexpr.shape != (1, 1): + d = expr.function.bound_symbols[0] + w = Wild("w", exclude=[d]) + p = Wild("p", exclude=[d]) + m = expr.function.expr.match(w*d**p) + if m is not None: + return m[w]*HadamardPower(subexpr, m[p]) + return ElementwiseApplyFunction(expr.function, subexpr) + else: + return ArrayElementwiseApplyFunc(expr.function, subexpr) + + +@_array2matrix.register(ArrayElement) +def _(expr: ArrayElement): + ret = _array2matrix(expr.name) + if isinstance(ret, MatrixExpr): + return MatrixElement(ret, *expr.indices) + return ArrayElement(ret, expr.indices) + + +@singledispatch +def _remove_trivial_dims(expr): + return expr, [] + + +@_remove_trivial_dims.register(ArrayTensorProduct) +def _(expr: ArrayTensorProduct): + # Recognize expressions like [x, y] with shape (k, 1, k, 1) as `x*y.T`. + # The matrix expression has to be equivalent to the tensor product of the + # matrices, with trivial dimensions (i.e. dim=1) dropped. + # That is, add contractions over trivial dimensions: + + removed = [] + newargs = [] + cumul = list(accumulate([0] + [get_rank(arg) for arg in expr.args])) + pending = None + prev_i = None + for i, arg in enumerate(expr.args): + current_range = list(range(cumul[i], cumul[i+1])) + if isinstance(arg, OneArray): + removed.extend(current_range) + continue + if not isinstance(arg, (MatrixExpr, MatrixBase)): + rarg, rem = _remove_trivial_dims(arg) + removed.extend(rem) + newargs.append(rarg) + continue + elif getattr(arg, "is_Identity", False) and arg.shape == (1, 1): + if arg.shape == (1, 1): + # Ignore identity matrices of shape (1, 1) - they are equivalent to scalar 1. + removed.extend(current_range) + continue + elif arg.shape == (1, 1): + arg, _ = _remove_trivial_dims(arg) + # Matrix is equivalent to scalar: + if len(newargs) == 0: + newargs.append(arg) + elif 1 in get_shape(newargs[-1]): + if newargs[-1].shape[1] == 1: + newargs[-1] = newargs[-1]*arg + else: + newargs[-1] = arg*newargs[-1] + removed.extend(current_range) + else: + newargs.append(arg) + elif 1 in arg.shape: + k = [i for i in arg.shape if i != 1][0] + if pending is None: + pending = k + prev_i = i + newargs.append(arg) + elif pending == k: + prev = newargs[-1] + if prev.shape[0] == 1: + d1 = cumul[prev_i] # type: ignore + prev = _a2m_transpose(prev) + else: + d1 = cumul[prev_i] + 1 # type: ignore + if arg.shape[1] == 1: + d2 = cumul[i] + 1 + arg = _a2m_transpose(arg) + else: + d2 = cumul[i] + newargs[-1] = prev*arg + pending = None + removed.extend([d1, d2]) + else: + newargs.append(arg) + pending = k + prev_i = i + else: + newargs.append(arg) + pending = None + newexpr, newremoved = _a2m_tensor_product(*newargs), sorted(removed) + if isinstance(newexpr, ArrayTensorProduct): + newexpr, newremoved2 = _find_trivial_matrices_rewrite(newexpr) + newremoved = _combine_removed(-1, newremoved, newremoved2) + if isinstance(newexpr, ArrayTensorProduct): + newexpr, newremoved2 = _find_trivial_kronecker_products_broadcast(newexpr) + newremoved = _combine_removed(-1, newremoved, newremoved2) + return newexpr, newremoved + + +@_remove_trivial_dims.register(ArrayAdd) +def _(expr: ArrayAdd): + rec = [_remove_trivial_dims(arg) for arg in expr.args] + newargs, removed = zip(*rec) + if len({get_shape(i) for i in newargs}) > 1: + return expr, [] + if len(removed) == 0: + return expr, removed + removed1 = removed[0] + return _a2m_add(*newargs), removed1 + + +@_remove_trivial_dims.register(PermuteDims) +def _(expr: PermuteDims): + subexpr, subremoved = _remove_trivial_dims(expr.expr) + p = expr.permutation.array_form + pinv = _af_invert(expr.permutation.array_form) + shift = list(accumulate([1 if i in subremoved else 0 for i in range(len(p))])) + premoved = [pinv[i] for i in subremoved] + p2 = [e - shift[e] for e in p if e not in subremoved] + # TODO: check if subremoved should be permuted as well... + newexpr = _permute_dims(subexpr, p2) + premoved = sorted(premoved) + if newexpr != expr: + newexpr, removed2 = _remove_trivial_dims(_array2matrix(newexpr)) + premoved = _combine_removed(-1, premoved, removed2) + return newexpr, premoved + + +@_remove_trivial_dims.register(ArrayContraction) +def _(expr: ArrayContraction): + new_expr, removed0 = _array_contraction_to_diagonal_multiple_identity(expr) + if new_expr != expr: + new_expr2, removed1 = _remove_trivial_dims(_array2matrix(new_expr)) + removed = _combine_removed(-1, removed0, removed1) + return new_expr2, removed + rank1 = get_rank(expr) + expr, removed1 = remove_identity_matrices(expr) + if not isinstance(expr, ArrayContraction): + expr2, removed2 = _remove_trivial_dims(expr) + return expr2, _combine_removed(rank1, removed1, removed2) + newexpr, removed2 = _remove_trivial_dims(expr.expr) + shifts = list(accumulate([1 if i in removed2 else 0 for i in range(get_rank(expr.expr))])) + new_contraction_indices = [tuple(j for j in i if j not in removed2) for i in expr.contraction_indices] + # Remove possible empty tuples "()": + new_contraction_indices = [i for i in new_contraction_indices if len(i) > 0] + contraction_indices_flat = [j for i in expr.contraction_indices for j in i] + removed2 = [i for i in removed2 if i not in contraction_indices_flat] + new_contraction_indices = [tuple(j - shifts[j] for j in i) for i in new_contraction_indices] + # Shift removed2: + removed2 = ArrayContraction._push_indices_up(expr.contraction_indices, removed2) + removed = _combine_removed(rank1, removed1, removed2) + return _array_contraction(newexpr, *new_contraction_indices), list(removed) + + +def _remove_diagonalized_identity_matrices(expr: ArrayDiagonal): + assert isinstance(expr, ArrayDiagonal) + editor = _EditArrayContraction(expr) + mapping = {i: {j for j in editor.args_with_ind if i in j.indices} for i in range(-1, -1-editor.number_of_diagonal_indices, -1)} + removed = [] + counter: int = 0 + for i, arg_with_ind in enumerate(editor.args_with_ind): + counter += len(arg_with_ind.indices) + if isinstance(arg_with_ind.element, Identity): + if None in arg_with_ind.indices and any(i is not None and (i < 0) == True for i in arg_with_ind.indices): + diag_ind = [j for j in arg_with_ind.indices if j is not None][0] + other = [j for j in mapping[diag_ind] if j != arg_with_ind][0] + if not isinstance(other.element, MatrixExpr): + continue + if 1 not in other.element.shape: + continue + if None not in other.indices: + continue + editor.args_with_ind[i].element = None + none_index = other.indices.index(None) + other.element = DiagMatrix(other.element) + other_range = editor.get_absolute_range(other) + removed.extend([other_range[0] + none_index]) + editor.args_with_ind = [i for i in editor.args_with_ind if i.element is not None] + removed = ArrayDiagonal._push_indices_up(expr.diagonal_indices, removed, get_rank(expr.expr)) + return editor.to_array_contraction(), removed + + +@_remove_trivial_dims.register(ArrayDiagonal) +def _(expr: ArrayDiagonal): + newexpr, removed = _remove_trivial_dims(expr.expr) + shifts = list(accumulate([0] + [1 if i in removed else 0 for i in range(get_rank(expr.expr))])) + new_diag_indices_map = {i: tuple(j for j in i if j not in removed) for i in expr.diagonal_indices} + for old_diag_tuple, new_diag_tuple in new_diag_indices_map.items(): + if len(new_diag_tuple) == 1: + removed = [i for i in removed if i not in old_diag_tuple] + new_diag_indices = [tuple(j - shifts[j] for j in i) for i in new_diag_indices_map.values()] + rank = get_rank(expr.expr) + removed = ArrayDiagonal._push_indices_up(expr.diagonal_indices, removed, rank) + removed = sorted(set(removed)) + # If there are single axes to diagonalize remaining, it means that their + # corresponding dimension has been removed, they no longer need diagonalization: + new_diag_indices = [i for i in new_diag_indices if len(i) > 0] + if len(new_diag_indices) > 0: + newexpr2 = _array_diagonal(newexpr, *new_diag_indices, allow_trivial_diags=True) + else: + newexpr2 = newexpr + if isinstance(newexpr2, ArrayDiagonal): + newexpr3, removed2 = _remove_diagonalized_identity_matrices(newexpr2) + removed = _combine_removed(-1, removed, removed2) + return newexpr3, removed + else: + return newexpr2, removed + + +@_remove_trivial_dims.register(ElementwiseApplyFunction) +def _(expr: ElementwiseApplyFunction): + subexpr, removed = _remove_trivial_dims(expr.expr) + if subexpr.shape == (1, 1): + # TODO: move this to ElementwiseApplyFunction + return expr.function(subexpr), removed + [0, 1] + return ElementwiseApplyFunction(expr.function, subexpr), [] + + +@_remove_trivial_dims.register(ArrayElementwiseApplyFunc) +def _(expr: ArrayElementwiseApplyFunc): + subexpr, removed = _remove_trivial_dims(expr.expr) + return ArrayElementwiseApplyFunc(expr.function, subexpr), removed + + +def convert_array_to_matrix(expr): + r""" + Recognize matrix expressions in codegen objects. + + If more than one matrix multiplication line have been detected, return a + list with the matrix expressions. + + Examples + ======== + + >>> from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array + >>> from sympy.tensor.array import tensorcontraction, tensorproduct + >>> from sympy import MatrixSymbol, Sum + >>> from sympy.abc import i, j, k, l, N + >>> from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array + >>> from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix + >>> A = MatrixSymbol("A", N, N) + >>> B = MatrixSymbol("B", N, N) + >>> C = MatrixSymbol("C", N, N) + >>> D = MatrixSymbol("D", N, N) + + >>> expr = Sum(A[i, j]*B[j, k], (j, 0, N-1)) + >>> cg = convert_indexed_to_array(expr) + >>> convert_array_to_matrix(cg) + A*B + >>> cg = convert_indexed_to_array(expr, first_indices=[k]) + >>> convert_array_to_matrix(cg) + B.T*A.T + + Transposition is detected: + + >>> expr = Sum(A[j, i]*B[j, k], (j, 0, N-1)) + >>> cg = convert_indexed_to_array(expr) + >>> convert_array_to_matrix(cg) + A.T*B + >>> cg = convert_indexed_to_array(expr, first_indices=[k]) + >>> convert_array_to_matrix(cg) + B.T*A + + Detect the trace: + + >>> expr = Sum(A[i, i], (i, 0, N-1)) + >>> cg = convert_indexed_to_array(expr) + >>> convert_array_to_matrix(cg) + Trace(A) + + Recognize some more complex traces: + + >>> expr = Sum(A[i, j]*B[j, i], (i, 0, N-1), (j, 0, N-1)) + >>> cg = convert_indexed_to_array(expr) + >>> convert_array_to_matrix(cg) + Trace(A*B) + + More complicated expressions: + + >>> expr = Sum(A[i, j]*B[k, j]*A[l, k], (j, 0, N-1), (k, 0, N-1)) + >>> cg = convert_indexed_to_array(expr) + >>> convert_array_to_matrix(cg) + A*B.T*A.T + + Expressions constructed from matrix expressions do not contain literal + indices, the positions of free indices are returned instead: + + >>> expr = A*B + >>> cg = convert_matrix_to_array(expr) + >>> convert_array_to_matrix(cg) + A*B + + If more than one line of matrix multiplications is detected, return + separate matrix multiplication factors embedded in a tensor product object: + + >>> cg = tensorcontraction(tensorproduct(A, B, C, D), (1, 2), (5, 6)) + >>> convert_array_to_matrix(cg) + ArrayTensorProduct(A*B, C*D) + + The two lines have free indices at axes 0, 3 and 4, 7, respectively. + """ + rec = _array2matrix(expr) + rec, removed = _remove_trivial_dims(rec) + return rec + + +def _array_diag2contr_diagmatrix(expr: ArrayDiagonal): + if isinstance(expr.expr, ArrayTensorProduct): + args = list(expr.expr.args) + diag_indices = list(expr.diagonal_indices) + mapping = _get_mapping_from_subranks([_get_subrank(arg) for arg in args]) + tuple_links = [[mapping[j] for j in i] for i in diag_indices] + contr_indices = [] + total_rank = get_rank(expr) + replaced = [False for arg in args] + for i, (abs_pos, rel_pos) in enumerate(zip(diag_indices, tuple_links)): + if len(abs_pos) != 2: + continue + (pos1_outer, pos1_inner), (pos2_outer, pos2_inner) = rel_pos + arg1 = args[pos1_outer] + arg2 = args[pos2_outer] + if get_rank(arg1) != 2 or get_rank(arg2) != 2: + if replaced[pos1_outer]: + diag_indices[i] = None + if replaced[pos2_outer]: + diag_indices[i] = None + continue + pos1_in2 = 1 - pos1_inner + pos2_in2 = 1 - pos2_inner + if arg1.shape[pos1_in2] == 1: + if arg1.shape[pos1_inner] != 1: + darg1 = DiagMatrix(arg1) + else: + darg1 = arg1 + args.append(darg1) + contr_indices.append(((pos2_outer, pos2_inner), (len(args)-1, pos1_inner))) + total_rank += 1 + diag_indices[i] = None + args[pos1_outer] = OneArray(arg1.shape[pos1_in2]) + replaced[pos1_outer] = True + elif arg2.shape[pos2_in2] == 1: + if arg2.shape[pos2_inner] != 1: + darg2 = DiagMatrix(arg2) + else: + darg2 = arg2 + args.append(darg2) + contr_indices.append(((pos1_outer, pos1_inner), (len(args)-1, pos2_inner))) + total_rank += 1 + diag_indices[i] = None + args[pos2_outer] = OneArray(arg2.shape[pos2_in2]) + replaced[pos2_outer] = True + diag_indices_new = [i for i in diag_indices if i is not None] + cumul = list(accumulate([0] + [get_rank(arg) for arg in args])) + contr_indices2 = [tuple(cumul[a] + b for a, b in i) for i in contr_indices] + tc = _array_contraction( + _array_tensor_product(*args), *contr_indices2 + ) + td = _array_diagonal(tc, *diag_indices_new) + return td + return expr + + +def _a2m_mul(*args): + if not any(isinstance(i, _CodegenArrayAbstract) for i in args): + from sympy.matrices.expressions.matmul import MatMul + return MatMul(*args).doit() + else: + return _array_contraction( + _array_tensor_product(*args), + *[(2*i-1, 2*i) for i in range(1, len(args))] + ) + + +def _a2m_tensor_product(*args): + scalars = [] + arrays = [] + for arg in args: + if isinstance(arg, (MatrixExpr, _ArrayExpr, _CodegenArrayAbstract)): + arrays.append(arg) + else: + scalars.append(arg) + scalar = Mul.fromiter(scalars) + if len(arrays) == 0: + return scalar + if scalar != 1: + if isinstance(arrays[0], _CodegenArrayAbstract): + arrays = [scalar] + arrays + else: + arrays[0] *= scalar + return _array_tensor_product(*arrays) + + +def _a2m_add(*args): + if not any(isinstance(i, _CodegenArrayAbstract) for i in args): + from sympy.matrices.expressions.matadd import MatAdd + return MatAdd(*args).doit() + else: + return _array_add(*args) + + +def _a2m_trace(arg): + if isinstance(arg, _CodegenArrayAbstract): + return _array_contraction(arg, (0, 1)) + else: + from sympy.matrices.expressions.trace import Trace + return Trace(arg) + + +def _a2m_transpose(arg): + if isinstance(arg, _CodegenArrayAbstract): + return _permute_dims(arg, [1, 0]) + else: + from sympy.matrices.expressions.transpose import Transpose + return Transpose(arg).doit() + + +def identify_hadamard_products(expr: ArrayContraction | ArrayDiagonal): + + editor: _EditArrayContraction = _EditArrayContraction(expr) + + map_contr_to_args: dict[FrozenSet, list[_ArgE]] = defaultdict(list) + map_ind_to_inds: dict[int | None, int] = defaultdict(int) + for arg_with_ind in editor.args_with_ind: + for ind in arg_with_ind.indices: + map_ind_to_inds[ind] += 1 + if None in arg_with_ind.indices: + continue + map_contr_to_args[frozenset(arg_with_ind.indices)].append(arg_with_ind) + + k: FrozenSet[int] + v: list[_ArgE] + for k, v in map_contr_to_args.items(): + make_trace: bool = False + if len(k) == 1 and next(iter(k)) >= 0 and sum(next(iter(k)) in i for i in map_contr_to_args) == 1: + # This is a trace: the arguments are fully contracted with only one + # index, and the index isn't used anywhere else: + make_trace = True + first_element = S.One + elif len(k) != 2: + # Hadamard product only defined for matrices: + continue + if len(v) == 1: + # Hadamard product with a single argument makes no sense: + continue + for ind in k: + if map_ind_to_inds[ind] <= 2: + # There is no other contraction, skip: + continue + + def check_transpose(x): + x = [i if i >= 0 else -1-i for i in x] + return x == sorted(x) + + # Check if expression is a trace: + if all(map_ind_to_inds[j] == len(v) and j >= 0 for j in k) and all(j >= 0 for j in k): + # This is a trace + make_trace = True + first_element = v[0].element + if not check_transpose(v[0].indices): + first_element = first_element.T # type: ignore + hadamard_factors = v[1:] + else: + hadamard_factors = v + + # This is a Hadamard product: + + hp = hadamard_product(*[i.element if check_transpose(i.indices) else Transpose(i.element) for i in hadamard_factors]) + hp_indices = v[0].indices + if not check_transpose(hadamard_factors[0].indices): + hp_indices = list(reversed(hp_indices)) + if make_trace: + hp = Trace(first_element*hp.T)._normalize() + hp_indices = [] + editor.insert_after(v[0], _ArgE(hp, hp_indices)) + for i in v: + editor.args_with_ind.remove(i) + + return editor.to_array_contraction() + + +def identify_removable_identity_matrices(expr): + editor = _EditArrayContraction(expr) + + flag = True + while flag: + flag = False + for arg_with_ind in editor.args_with_ind: + if isinstance(arg_with_ind.element, Identity): + k = arg_with_ind.element.shape[0] + # Candidate for removal: + if arg_with_ind.indices == [None, None]: + # Free identity matrix, will be cleared by _remove_trivial_dims: + continue + elif None in arg_with_ind.indices: + ind = [j for j in arg_with_ind.indices if j is not None][0] + counted = editor.count_args_with_index(ind) + if counted == 1: + # Identity matrix contracted only on one index with itself, + # transform to a OneArray(k) element: + editor.insert_after(arg_with_ind, OneArray(k)) + editor.args_with_ind.remove(arg_with_ind) + flag = True + break + elif counted > 2: + # Case counted = 2 is a matrix multiplication by identity matrix, skip it. + # Case counted > 2 is a multiple contraction, + # this is a case where the contraction becomes a diagonalization if the + # identity matrix is dropped. + continue + elif arg_with_ind.indices[0] == arg_with_ind.indices[1]: + ind = arg_with_ind.indices[0] + counted = editor.count_args_with_index(ind) + if counted > 1: + editor.args_with_ind.remove(arg_with_ind) + flag = True + break + else: + # This is a trace, skip it as it will be recognized somewhere else: + pass + elif ask(Q.diagonal(arg_with_ind.element)): + if arg_with_ind.indices == [None, None]: + continue + elif None in arg_with_ind.indices: + pass + elif arg_with_ind.indices[0] == arg_with_ind.indices[1]: + ind = arg_with_ind.indices[0] + counted = editor.count_args_with_index(ind) + if counted == 3: + # A_ai B_bi D_ii ==> A_ai D_ij B_bj + ind_new = editor.get_new_contraction_index() + other_args = [j for j in editor.args_with_ind if j != arg_with_ind] + other_args[1].indices = [ind_new if j == ind else j for j in other_args[1].indices] + arg_with_ind.indices = [ind, ind_new] + flag = True + break + + return editor.to_array_contraction() + + +def remove_identity_matrices(expr: ArrayContraction): + editor = _EditArrayContraction(expr) + removed: list[int] = [] + + permutation_map = {} + + free_indices = list(accumulate([0] + [sum(i is None for i in arg.indices) for arg in editor.args_with_ind])) + free_map = dict(zip(editor.args_with_ind, free_indices[:-1])) + + update_pairs = {} + + for ind in range(editor.number_of_contraction_indices): + args = editor.get_args_with_index(ind) + identity_matrices = [i for i in args if isinstance(i.element, Identity)] + number_identity_matrices = len(identity_matrices) + # If the contraction involves a non-identity matrix and multiple identity matrices: + if number_identity_matrices != len(args) - 1 or number_identity_matrices == 0: + continue + # Get the non-identity element: + non_identity = [i for i in args if not isinstance(i.element, Identity)][0] + # Check that all identity matrices have at least one free index + # (otherwise they would be contractions to some other elements) + if any(None not in i.indices for i in identity_matrices): + continue + # Mark the identity matrices for removal: + for i in identity_matrices: + i.element = None + removed.extend(range(free_map[i], free_map[i] + len([j for j in i.indices if j is None]))) + last_removed = removed.pop(-1) + update_pairs[last_removed, ind] = non_identity.indices[:] + # Remove the indices from the non-identity matrix, as the contraction + # no longer exists: + non_identity.indices = [None if i == ind else i for i in non_identity.indices] + + removed.sort() + + shifts = list(accumulate([1 if i in removed else 0 for i in range(get_rank(expr))])) + for (last_removed, ind), non_identity_indices in update_pairs.items(): + pos = [free_map[non_identity] + i for i, e in enumerate(non_identity_indices) if e == ind] + assert len(pos) == 1 + for j in pos: + permutation_map[j] = last_removed + + editor.args_with_ind = [i for i in editor.args_with_ind if i.element is not None] + ret_expr = editor.to_array_contraction() + permutation = [] + counter = 0 + counter2 = 0 + for j in range(get_rank(expr)): + if j in removed: + continue + if counter2 in permutation_map: + target = permutation_map[counter2] + permutation.append(target - shifts[target]) + counter2 += 1 + else: + while counter in permutation_map.values(): + counter += 1 + permutation.append(counter) + counter += 1 + counter2 += 1 + ret_expr2 = _permute_dims(ret_expr, _af_invert(permutation)) + return ret_expr2, removed + + +def _combine_removed(dim: int, removed1: list[int], removed2: list[int]) -> list[int]: + # Concatenate two axis removal operations as performed by + # _remove_trivial_dims, + removed1 = sorted(removed1) + removed2 = sorted(removed2) + i = 0 + j = 0 + removed = [] + while True: + if j >= len(removed2): + while i < len(removed1): + removed.append(removed1[i]) + i += 1 + break + elif i < len(removed1) and removed1[i] <= i + removed2[j]: + removed.append(removed1[i]) + i += 1 + else: + removed.append(i + removed2[j]) + j += 1 + return removed + + +def _array_contraction_to_diagonal_multiple_identity(expr: ArrayContraction): + editor = _EditArrayContraction(expr) + editor.track_permutation_start() + removed: list[int] = [] + diag_index_counter: int = 0 + for i in range(editor.number_of_contraction_indices): + identities = [] + args = [] + for j, arg in enumerate(editor.args_with_ind): + if i not in arg.indices: + continue + if isinstance(arg.element, Identity): + identities.append(arg) + else: + args.append(arg) + if len(identities) == 0: + continue + if len(args) + len(identities) < 3: + continue + new_diag_ind = -1 - diag_index_counter + diag_index_counter += 1 + # Variable "flag" to control whether to skip this contraction set: + flag: bool = True + for i1, id1 in enumerate(identities): + if None not in id1.indices: + flag = True + break + free_pos = list(range(*editor.get_absolute_free_range(id1)))[0] + editor._track_permutation[-1].append(free_pos) # type: ignore + id1.element = None + flag = False + break + if flag: + continue + for arg in identities[:i1] + identities[i1+1:]: + arg.element = None + removed.extend(range(*editor.get_absolute_free_range(arg))) + for arg in args: + arg.indices = [new_diag_ind if j == i else j for j in arg.indices] + for j, e in enumerate(editor.args_with_ind): + if e.element is None: + editor._track_permutation[j] = None # type: ignore + editor._track_permutation = [i for i in editor._track_permutation if i is not None] # type: ignore + # Renumber permutation array form in order to deal with deleted positions: + remap = {e: i for i, e in enumerate(sorted({k for j in editor._track_permutation for k in j}))} + editor._track_permutation = [[remap[j] for j in i] for i in editor._track_permutation] + editor.args_with_ind = [i for i in editor.args_with_ind if i.element is not None] + new_expr = editor.to_array_contraction() + return new_expr, removed diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_indexed_to_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_indexed_to_array.py new file mode 100644 index 0000000000000000000000000000000000000000..c219a205c4305bd7070e5117978146224521c58c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_indexed_to_array.py @@ -0,0 +1,257 @@ +from collections import defaultdict + +from sympy import Function +from sympy.combinatorics.permutations import _af_invert +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.mul import Mul +from sympy.core.numbers import Integer +from sympy.core.power import Pow +from sympy.core.sorting import default_sort_key +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.tensor.array.expressions import ArrayElementwiseApplyFunc +from sympy.tensor.indexed import (Indexed, IndexedBase) +from sympy.combinatorics import Permutation +from sympy.matrices.expressions.matexpr import MatrixElement +from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal, \ + get_shape, ArrayElement, _array_tensor_product, _array_diagonal, _array_contraction, _array_add, \ + _permute_dims, OneArray, ArrayAdd +from sympy.tensor.array.expressions.utils import _get_argindex, _get_diagonal_indices + + +def convert_indexed_to_array(expr, first_indices=None): + r""" + Parse indexed expression into a form useful for code generation. + + Examples + ======== + + >>> from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array + >>> from sympy import MatrixSymbol, Sum, symbols + + >>> i, j, k, d = symbols("i j k d") + >>> M = MatrixSymbol("M", d, d) + >>> N = MatrixSymbol("N", d, d) + + Recognize the trace in summation form: + + >>> expr = Sum(M[i, i], (i, 0, d-1)) + >>> convert_indexed_to_array(expr) + ArrayContraction(M, (0, 1)) + + Recognize the extraction of the diagonal by using the same index `i` on + both axes of the matrix: + + >>> expr = M[i, i] + >>> convert_indexed_to_array(expr) + ArrayDiagonal(M, (0, 1)) + + This function can help perform the transformation expressed in two + different mathematical notations as: + + `\sum_{j=0}^{N-1} A_{i,j} B_{j,k} \Longrightarrow \mathbf{A}\cdot \mathbf{B}` + + Recognize the matrix multiplication in summation form: + + >>> expr = Sum(M[i, j]*N[j, k], (j, 0, d-1)) + >>> convert_indexed_to_array(expr) + ArrayContraction(ArrayTensorProduct(M, N), (1, 2)) + + Specify that ``k`` has to be the starting index: + + >>> convert_indexed_to_array(expr, first_indices=[k]) + ArrayContraction(ArrayTensorProduct(N, M), (0, 3)) + """ + + result, indices = _convert_indexed_to_array(expr) + + if any(isinstance(i, (int, Integer)) for i in indices): + result = ArrayElement(result, indices) + indices = [] + + if not first_indices: + return result + + def _check_is_in(elem, indices): + if elem in indices: + return True + if any(elem in i for i in indices if isinstance(i, frozenset)): + return True + return False + + repl = {j: i for i in indices if isinstance(i, frozenset) for j in i} + first_indices = [repl.get(i, i) for i in first_indices] + for i in first_indices: + if not _check_is_in(i, indices): + first_indices.remove(i) + first_indices.extend([i for i in indices if not _check_is_in(i, first_indices)]) + + def _get_pos(elem, indices): + if elem in indices: + return indices.index(elem) + for i, e in enumerate(indices): + if not isinstance(e, frozenset): + continue + if elem in e: + return i + raise ValueError("not found") + + permutation = _af_invert([_get_pos(i, first_indices) for i in indices]) + if isinstance(result, ArrayAdd): + return _array_add(*[_permute_dims(arg, permutation) for arg in result.args]) + else: + return _permute_dims(result, permutation) + + +def _convert_indexed_to_array(expr): + if isinstance(expr, Sum): + function = expr.function + summation_indices = expr.variables + subexpr, subindices = _convert_indexed_to_array(function) + subindicessets = {j: i for i in subindices if isinstance(i, frozenset) for j in i} + summation_indices = sorted({subindicessets.get(i, i) for i in summation_indices}, key=default_sort_key) + # TODO: check that Kronecker delta is only contracted to one other element: + kronecker_indices = set() + if isinstance(function, Mul): + for arg in function.args: + if not isinstance(arg, KroneckerDelta): + continue + arg_indices = sorted(set(arg.indices), key=default_sort_key) + if len(arg_indices) == 2: + kronecker_indices.update(arg_indices) + kronecker_indices = sorted(kronecker_indices, key=default_sort_key) + # Check dimensional consistency: + shape = get_shape(subexpr) + if shape: + for ind, istart, iend in expr.limits: + i = _get_argindex(subindices, ind) + if istart != 0 or iend+1 != shape[i]: + raise ValueError("summation index and array dimension mismatch: %s" % ind) + contraction_indices = [] + subindices = list(subindices) + if isinstance(subexpr, ArrayDiagonal): + diagonal_indices = list(subexpr.diagonal_indices) + dindices = subindices[-len(diagonal_indices):] + subindices = subindices[:-len(diagonal_indices)] + for index in summation_indices: + if index in dindices: + position = dindices.index(index) + contraction_indices.append(diagonal_indices[position]) + diagonal_indices[position] = None + diagonal_indices = [i for i in diagonal_indices if i is not None] + for i, ind in enumerate(subindices): + if ind in summation_indices: + pass + if diagonal_indices: + subexpr = _array_diagonal(subexpr.expr, *diagonal_indices) + else: + subexpr = subexpr.expr + + axes_contraction = defaultdict(list) + for i, ind in enumerate(subindices): + include = all(j not in kronecker_indices for j in ind) if isinstance(ind, frozenset) else ind not in kronecker_indices + if ind in summation_indices and include: + axes_contraction[ind].append(i) + subindices[i] = None + for k, v in axes_contraction.items(): + if any(i in kronecker_indices for i in k) if isinstance(k, frozenset) else k in kronecker_indices: + continue + contraction_indices.append(tuple(v)) + free_indices = [i for i in subindices if i is not None] + indices_ret = list(free_indices) + indices_ret.sort(key=lambda x: free_indices.index(x)) + return _array_contraction( + subexpr, + *contraction_indices, + free_indices=free_indices + ), tuple(indices_ret) + if isinstance(expr, Mul): + args, indices = zip(*[_convert_indexed_to_array(arg) for arg in expr.args]) + # Check if there are KroneckerDelta objects: + kronecker_delta_repl = {} + for arg in args: + if not isinstance(arg, KroneckerDelta): + continue + # Diagonalize two indices: + i, j = arg.indices + kindices = set(arg.indices) + if i in kronecker_delta_repl: + kindices.update(kronecker_delta_repl[i]) + if j in kronecker_delta_repl: + kindices.update(kronecker_delta_repl[j]) + kindices = frozenset(kindices) + for index in kindices: + kronecker_delta_repl[index] = kindices + # Remove KroneckerDelta objects, their relations should be handled by + # ArrayDiagonal: + newargs = [] + newindices = [] + for arg, loc_indices in zip(args, indices): + if isinstance(arg, KroneckerDelta): + continue + newargs.append(arg) + newindices.append(loc_indices) + flattened_indices = [kronecker_delta_repl.get(j, j) for i in newindices for j in i] + diagonal_indices, ret_indices = _get_diagonal_indices(flattened_indices) + tp = _array_tensor_product(*newargs) + if diagonal_indices: + return _array_diagonal(tp, *diagonal_indices), ret_indices + else: + return tp, ret_indices + if isinstance(expr, MatrixElement): + indices = expr.args[1:] + diagonal_indices, ret_indices = _get_diagonal_indices(indices) + if diagonal_indices: + return _array_diagonal(expr.args[0], *diagonal_indices), ret_indices + else: + return expr.args[0], ret_indices + if isinstance(expr, ArrayElement): + indices = expr.indices + diagonal_indices, ret_indices = _get_diagonal_indices(indices) + if diagonal_indices: + return _array_diagonal(expr.name, *diagonal_indices), ret_indices + else: + return expr.name, ret_indices + if isinstance(expr, Indexed): + indices = expr.indices + diagonal_indices, ret_indices = _get_diagonal_indices(indices) + if diagonal_indices: + return _array_diagonal(expr.base, *diagonal_indices), ret_indices + else: + return expr.args[0], ret_indices + if isinstance(expr, IndexedBase): + raise NotImplementedError + if isinstance(expr, KroneckerDelta): + return expr, expr.indices + if isinstance(expr, Add): + args, indices = zip(*[_convert_indexed_to_array(arg) for arg in expr.args]) + args = list(args) + # Check if all indices are compatible. Otherwise expand the dimensions: + index0 = [] + shape0 = [] + for arg, arg_indices in zip(args, indices): + arg_indices_set = set(arg_indices) + arg_indices_missing = arg_indices_set.difference(index0) + index0.extend([i for i in arg_indices if i in arg_indices_missing]) + arg_shape = get_shape(arg) + shape0.extend([arg_shape[i] for i, e in enumerate(arg_indices) if e in arg_indices_missing]) + for i, (arg, arg_indices) in enumerate(zip(args, indices)): + if len(arg_indices) < len(index0): + missing_indices_pos = [i for i, e in enumerate(index0) if e not in arg_indices] + missing_shape = [shape0[i] for i in missing_indices_pos] + arg_indices = tuple(index0[j] for j in missing_indices_pos) + arg_indices + args[i] = _array_tensor_product(OneArray(*missing_shape), args[i]) + permutation = Permutation([arg_indices.index(j) for j in index0]) + # Perform index permutations: + args[i] = _permute_dims(args[i], permutation) + return _array_add(*args), tuple(index0) + if isinstance(expr, Pow): + subexpr, subindices = _convert_indexed_to_array(expr.base) + if isinstance(expr.exp, (int, Integer)): + diags = zip(*[(2*i, 2*i + 1) for i in range(expr.exp)]) + arr = _array_diagonal(_array_tensor_product(*[subexpr for i in range(expr.exp)]), *diags) + return arr, subindices + if isinstance(expr, Function): + subexpr, subindices = _convert_indexed_to_array(expr.args[0]) + return ArrayElementwiseApplyFunc(type(expr), subexpr), subindices + return expr, () diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_matrix_to_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_matrix_to_array.py new file mode 100644 index 0000000000000000000000000000000000000000..8f66961727f6338318d65876a7768802773e4f2d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/from_matrix_to_array.py @@ -0,0 +1,87 @@ +from sympy import KroneckerProduct +from sympy.core.basic import Basic +from sympy.core.function import Lambda +from sympy.core.mul import Mul +from sympy.core.numbers import Integer +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.matrices.expressions.hadamard import (HadamardPower, HadamardProduct) +from sympy.matrices.expressions.matadd import MatAdd +from sympy.matrices.expressions.matmul import MatMul +from sympy.matrices.expressions.matpow import MatPow +from sympy.matrices.expressions.trace import Trace +from sympy.matrices.expressions.transpose import Transpose +from sympy.matrices.expressions.matexpr import MatrixExpr +from sympy.tensor.array.expressions.array_expressions import \ + ArrayElementwiseApplyFunc, _array_tensor_product, _array_contraction, \ + _array_diagonal, _array_add, _permute_dims, Reshape + + +def convert_matrix_to_array(expr: Basic) -> Basic: + if isinstance(expr, MatMul): + args_nonmat = [] + args = [] + for arg in expr.args: + if isinstance(arg, MatrixExpr): + args.append(arg) + else: + args_nonmat.append(convert_matrix_to_array(arg)) + contractions = [(2*i+1, 2*i+2) for i in range(len(args)-1)] + scalar = _array_tensor_product(*args_nonmat) if args_nonmat else S.One + if scalar == 1: + tprod = _array_tensor_product( + *[convert_matrix_to_array(arg) for arg in args]) + else: + tprod = _array_tensor_product( + scalar, + *[convert_matrix_to_array(arg) for arg in args]) + return _array_contraction( + tprod, + *contractions + ) + elif isinstance(expr, MatAdd): + return _array_add( + *[convert_matrix_to_array(arg) for arg in expr.args] + ) + elif isinstance(expr, Transpose): + return _permute_dims( + convert_matrix_to_array(expr.args[0]), [1, 0] + ) + elif isinstance(expr, Trace): + inner_expr: MatrixExpr = convert_matrix_to_array(expr.arg) # type: ignore + return _array_contraction(inner_expr, (0, len(inner_expr.shape) - 1)) + elif isinstance(expr, Mul): + return _array_tensor_product(*[convert_matrix_to_array(i) for i in expr.args]) + elif isinstance(expr, Pow): + base = convert_matrix_to_array(expr.base) + if (expr.exp > 0) == True: + return _array_tensor_product(*[base for i in range(expr.exp)]) + else: + return expr + elif isinstance(expr, MatPow): + base = convert_matrix_to_array(expr.base) + if expr.exp.is_Integer != True: + b = symbols("b", cls=Dummy) + return ArrayElementwiseApplyFunc(Lambda(b, b**expr.exp), convert_matrix_to_array(base)) + elif (expr.exp > 0) == True: + return convert_matrix_to_array(MatMul.fromiter(base for i in range(expr.exp))) + else: + return expr + elif isinstance(expr, HadamardProduct): + tp = _array_tensor_product(*[convert_matrix_to_array(arg) for arg in expr.args]) + diag = [[2*i for i in range(len(expr.args))], [2*i+1 for i in range(len(expr.args))]] + return _array_diagonal(tp, *diag) + elif isinstance(expr, HadamardPower): + base, exp = expr.args + if isinstance(exp, Integer) and exp > 0: + return convert_matrix_to_array(HadamardProduct.fromiter(base for i in range(exp))) + else: + d = Dummy("d") + return ArrayElementwiseApplyFunc(Lambda(d, d**exp), base) + elif isinstance(expr, KroneckerProduct): + kp_args = [convert_matrix_to_array(arg) for arg in expr.args] + permutation = [2*i for i in range(len(kp_args))] + [2*i + 1 for i in range(len(kp_args))] + return Reshape(_permute_dims(_array_tensor_product(*kp_args), permutation), expr.shape) + else: + return expr diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce5fa21f85c3f481e46c01e0233f26c397f7f137 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_array_expressions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_array_expressions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7663e84c5f033378e4e36c6e8dc4cc76a6aa670a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_array_expressions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_arrayexpr_derivatives.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_arrayexpr_derivatives.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f6ec190b447b42048d6e68b147b8a4c948459b7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_arrayexpr_derivatives.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_as_explicit.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_as_explicit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83cb144193d47c1e4f009c83d77a9bb129aa8cb9 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_as_explicit.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_array_to_indexed.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_array_to_indexed.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef11931e6676d08c17b97105d3fff96de6d8360b Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_array_to_indexed.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_array_to_matrix.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_array_to_matrix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee7b2aace37ea43efb48831531437b337a85a4d7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_array_to_matrix.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_indexed_to_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_indexed_to_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..564615c5fbccbfa2313cbf73143883d8b01edecb Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_indexed_to_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_matrix_to_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_matrix_to_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f76b6c4e7160549e8790a48c9325ae5d82733239 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_convert_matrix_to_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_deprecated_conv_modules.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_deprecated_conv_modules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99ad616f6fed844b485ac0bbdef3a0eeeb7a39b4 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/__pycache__/test_deprecated_conv_modules.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_array_expressions.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_array_expressions.py new file mode 100644 index 0000000000000000000000000000000000000000..63fb79ab7ced7bff5ecb55b1764f43e29f98609d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_array_expressions.py @@ -0,0 +1,808 @@ +import random + +from sympy import tensordiagonal, eye, KroneckerDelta, Array +from sympy.core.symbol import symbols +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.matrices.expressions.diagonal import DiagMatrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import ZeroMatrix +from sympy.tensor.array.arrayop import (permutedims, tensorcontraction, tensorproduct) +from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray +from sympy.combinatorics import Permutation +from sympy.tensor.array.expressions.array_expressions import ZeroArray, OneArray, ArraySymbol, ArrayElement, \ + PermuteDims, ArrayContraction, ArrayTensorProduct, ArrayDiagonal, \ + ArrayAdd, nest_permutation, ArrayElementwiseApplyFunc, _EditArrayContraction, _ArgE, _array_tensor_product, \ + _array_contraction, _array_diagonal, _array_add, _permute_dims, Reshape +from sympy.testing.pytest import raises + +i, j, k, l, m, n = symbols("i j k l m n") + + +M = ArraySymbol("M", (k, k)) +N = ArraySymbol("N", (k, k)) +P = ArraySymbol("P", (k, k)) +Q = ArraySymbol("Q", (k, k)) + +A = ArraySymbol("A", (k, k)) +B = ArraySymbol("B", (k, k)) +C = ArraySymbol("C", (k, k)) +D = ArraySymbol("D", (k, k)) + +X = ArraySymbol("X", (k, k)) +Y = ArraySymbol("Y", (k, k)) + +a = ArraySymbol("a", (k, 1)) +b = ArraySymbol("b", (k, 1)) +c = ArraySymbol("c", (k, 1)) +d = ArraySymbol("d", (k, 1)) + + +def test_array_symbol_and_element(): + A = ArraySymbol("A", (2,)) + A0 = ArrayElement(A, (0,)) + A1 = ArrayElement(A, (1,)) + assert A[0] == A0 + assert A[1] != A0 + assert A.as_explicit() == ImmutableDenseNDimArray([A0, A1]) + + A2 = tensorproduct(A, A) + assert A2.shape == (2, 2) + # TODO: not yet supported: + # assert A2.as_explicit() == Array([[A[0]*A[0], A[1]*A[0]], [A[0]*A[1], A[1]*A[1]]]) + A3 = tensorcontraction(A2, (0, 1)) + assert A3.shape == () + # TODO: not yet supported: + # assert A3.as_explicit() == Array([]) + + A = ArraySymbol("A", (2, 3, 4)) + Ae = A.as_explicit() + assert Ae == ImmutableDenseNDimArray( + [[[ArrayElement(A, (i, j, k)) for k in range(4)] for j in range(3)] for i in range(2)]) + + p = _permute_dims(A, Permutation(0, 2, 1)) + assert isinstance(p, PermuteDims) + + A = ArraySymbol("A", (2,)) + raises(IndexError, lambda: A[()]) + raises(IndexError, lambda: A[0, 1]) + raises(ValueError, lambda: A[-1]) + raises(ValueError, lambda: A[2]) + + O = OneArray(3, 4) + Z = ZeroArray(m, n) + + raises(IndexError, lambda: O[()]) + raises(IndexError, lambda: O[1, 2, 3]) + raises(ValueError, lambda: O[3, 0]) + raises(ValueError, lambda: O[0, 4]) + + assert O[1, 2] == 1 + assert Z[1, 2] == 0 + + +def test_zero_array(): + assert ZeroArray() == 0 + assert ZeroArray().is_Integer + + za = ZeroArray(3, 2, 4) + assert za.shape == (3, 2, 4) + za_e = za.as_explicit() + assert za_e.shape == (3, 2, 4) + + m, n, k = symbols("m n k") + za = ZeroArray(m, n, k, 2) + assert za.shape == (m, n, k, 2) + raises(ValueError, lambda: za.as_explicit()) + + +def test_one_array(): + assert OneArray() == 1 + assert OneArray().is_Integer + + oa = OneArray(3, 2, 4) + assert oa.shape == (3, 2, 4) + oa_e = oa.as_explicit() + assert oa_e.shape == (3, 2, 4) + + m, n, k = symbols("m n k") + oa = OneArray(m, n, k, 2) + assert oa.shape == (m, n, k, 2) + raises(ValueError, lambda: oa.as_explicit()) + + +def test_arrayexpr_contraction_construction(): + + cg = _array_contraction(A) + assert cg == A + + cg = _array_contraction(_array_tensor_product(A, B), (1, 0)) + assert cg == _array_contraction(_array_tensor_product(A, B), (0, 1)) + + cg = _array_contraction(_array_tensor_product(M, N), (0, 1)) + indtup = cg._get_contraction_tuples() + assert indtup == [[(0, 0), (0, 1)]] + assert cg._contraction_tuples_to_contraction_indices(cg.expr, indtup) == [(0, 1)] + + cg = _array_contraction(_array_tensor_product(M, N), (1, 2)) + indtup = cg._get_contraction_tuples() + assert indtup == [[(0, 1), (1, 0)]] + assert cg._contraction_tuples_to_contraction_indices(cg.expr, indtup) == [(1, 2)] + + cg = _array_contraction(_array_tensor_product(M, M, N), (1, 4), (2, 5)) + indtup = cg._get_contraction_tuples() + assert indtup == [[(0, 0), (1, 1)], [(0, 1), (2, 0)]] + assert cg._contraction_tuples_to_contraction_indices(cg.expr, indtup) == [(0, 3), (1, 4)] + + # Test removal of trivial contraction: + assert _array_contraction(a, (1,)) == a + assert _array_contraction( + _array_tensor_product(a, b), (0, 2), (1,), (3,)) == _array_contraction( + _array_tensor_product(a, b), (0, 2)) + + +def test_arrayexpr_array_flatten(): + + # Flatten nested ArrayTensorProduct objects: + expr1 = _array_tensor_product(M, N) + expr2 = _array_tensor_product(P, Q) + expr = _array_tensor_product(expr1, expr2) + assert expr == _array_tensor_product(M, N, P, Q) + assert expr.args == (M, N, P, Q) + + # Flatten mixed ArrayTensorProduct and ArrayContraction objects: + cg1 = _array_contraction(expr1, (1, 2)) + cg2 = _array_contraction(expr2, (0, 3)) + + expr = _array_tensor_product(cg1, cg2) + assert expr == _array_contraction(_array_tensor_product(M, N, P, Q), (1, 2), (4, 7)) + + expr = _array_tensor_product(M, cg1) + assert expr == _array_contraction(_array_tensor_product(M, M, N), (3, 4)) + + # Flatten nested ArrayContraction objects: + cgnested = _array_contraction(cg1, (0, 1)) + assert cgnested == _array_contraction(_array_tensor_product(M, N), (0, 3), (1, 2)) + + cgnested = _array_contraction(_array_tensor_product(cg1, cg2), (0, 3)) + assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 6), (1, 2), (4, 7)) + + cg3 = _array_contraction(_array_tensor_product(M, N, P, Q), (1, 3), (2, 4)) + cgnested = _array_contraction(cg3, (0, 1)) + assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 5), (1, 3), (2, 4)) + + cgnested = _array_contraction(cg3, (0, 3), (1, 2)) + assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 7), (1, 3), (2, 4), (5, 6)) + + cg4 = _array_contraction(_array_tensor_product(M, N, P, Q), (1, 5), (3, 7)) + cgnested = _array_contraction(cg4, (0, 1)) + assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 2), (1, 5), (3, 7)) + + cgnested = _array_contraction(cg4, (0, 1), (2, 3)) + assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 2), (1, 5), (3, 7), (4, 6)) + + cg = _array_diagonal(cg4) + assert cg == cg4 + assert isinstance(cg, type(cg4)) + + # Flatten nested ArrayDiagonal objects: + cg1 = _array_diagonal(expr1, (1, 2)) + cg2 = _array_diagonal(expr2, (0, 3)) + cg3 = _array_diagonal(_array_tensor_product(M, N, P, Q), (1, 3), (2, 4)) + cg4 = _array_diagonal(_array_tensor_product(M, N, P, Q), (1, 5), (3, 7)) + + cgnested = _array_diagonal(cg1, (0, 1)) + assert cgnested == _array_diagonal(_array_tensor_product(M, N), (1, 2), (0, 3)) + + cgnested = _array_diagonal(cg3, (1, 2)) + assert cgnested == _array_diagonal(_array_tensor_product(M, N, P, Q), (1, 3), (2, 4), (5, 6)) + + cgnested = _array_diagonal(cg4, (1, 2)) + assert cgnested == _array_diagonal(_array_tensor_product(M, N, P, Q), (1, 5), (3, 7), (2, 4)) + + cg = _array_add(M, N) + cg2 = _array_add(cg, P) + assert isinstance(cg2, ArrayAdd) + assert cg2.args == (M, N, P) + assert cg2.shape == (k, k) + + expr = _array_tensor_product(_array_diagonal(X, (0, 1)), _array_diagonal(A, (0, 1))) + assert expr == _array_diagonal(_array_tensor_product(X, A), (0, 1), (2, 3)) + + expr1 = _array_diagonal(_array_tensor_product(X, A), (1, 2)) + expr2 = _array_tensor_product(expr1, a) + assert expr2 == _permute_dims(_array_diagonal(_array_tensor_product(X, A, a), (1, 2)), [0, 1, 4, 2, 3]) + + expr1 = _array_contraction(_array_tensor_product(X, A), (1, 2)) + expr2 = _array_tensor_product(expr1, a) + assert isinstance(expr2, ArrayContraction) + assert isinstance(expr2.expr, ArrayTensorProduct) + + cg = _array_tensor_product(_array_diagonal(_array_tensor_product(A, X, Y), (0, 3), (1, 5)), a, b) + assert cg == _permute_dims(_array_diagonal(_array_tensor_product(A, X, Y, a, b), (0, 3), (1, 5)), [0, 1, 6, 7, 2, 3, 4, 5]) + + +def test_arrayexpr_array_diagonal(): + cg = _array_diagonal(M, (1, 0)) + assert cg == _array_diagonal(M, (0, 1)) + + cg = _array_diagonal(_array_tensor_product(M, N, P), (4, 1), (2, 0)) + assert cg == _array_diagonal(_array_tensor_product(M, N, P), (1, 4), (0, 2)) + + cg = _array_diagonal(_array_tensor_product(M, N), (1, 2), (3,), allow_trivial_diags=True) + assert cg == _permute_dims(_array_diagonal(_array_tensor_product(M, N), (1, 2)), [0, 2, 1]) + + Ax = ArraySymbol("Ax", shape=(1, 2, 3, 4, 3, 5, 6, 2, 7)) + cg = _array_diagonal(Ax, (1, 7), (3,), (2, 4), (6,), allow_trivial_diags=True) + assert cg == _permute_dims(_array_diagonal(Ax, (1, 7), (2, 4)), [0, 2, 4, 5, 1, 6, 3]) + + cg = _array_diagonal(M, (0,), allow_trivial_diags=True) + assert cg == _permute_dims(M, [1, 0]) + + raises(ValueError, lambda: _array_diagonal(M, (0, 0))) + + +def test_arrayexpr_array_shape(): + expr = _array_tensor_product(M, N, P, Q) + assert expr.shape == (k, k, k, k, k, k, k, k) + Z = MatrixSymbol("Z", m, n) + expr = _array_tensor_product(M, Z) + assert expr.shape == (k, k, m, n) + expr2 = _array_contraction(expr, (0, 1)) + assert expr2.shape == (m, n) + expr2 = _array_diagonal(expr, (0, 1)) + assert expr2.shape == (m, n, k) + exprp = _permute_dims(expr, [2, 1, 3, 0]) + assert exprp.shape == (m, k, n, k) + expr3 = _array_tensor_product(N, Z) + expr2 = _array_add(expr, expr3) + assert expr2.shape == (k, k, m, n) + + # Contraction along axes with discordant dimensions: + raises(ValueError, lambda: _array_contraction(expr, (1, 2))) + # Also diagonal needs the same dimensions: + raises(ValueError, lambda: _array_diagonal(expr, (1, 2))) + # Diagonal requires at least to axes to compute the diagonal: + raises(ValueError, lambda: _array_diagonal(expr, (1,))) + + +def test_arrayexpr_permutedims_sink(): + + cg = _permute_dims(_array_tensor_product(M, N), [0, 1, 3, 2], nest_permutation=False) + sunk = nest_permutation(cg) + assert sunk == _array_tensor_product(M, _permute_dims(N, [1, 0])) + + cg = _permute_dims(_array_tensor_product(M, N), [1, 0, 3, 2], nest_permutation=False) + sunk = nest_permutation(cg) + assert sunk == _array_tensor_product(_permute_dims(M, [1, 0]), _permute_dims(N, [1, 0])) + + cg = _permute_dims(_array_tensor_product(M, N), [3, 2, 1, 0], nest_permutation=False) + sunk = nest_permutation(cg) + assert sunk == _array_tensor_product(_permute_dims(N, [1, 0]), _permute_dims(M, [1, 0])) + + cg = _permute_dims(_array_contraction(_array_tensor_product(M, N), (1, 2)), [1, 0], nest_permutation=False) + sunk = nest_permutation(cg) + assert sunk == _array_contraction(_permute_dims(_array_tensor_product(M, N), [[0, 3]]), (1, 2)) + + cg = _permute_dims(_array_tensor_product(M, N), [1, 0, 3, 2], nest_permutation=False) + sunk = nest_permutation(cg) + assert sunk == _array_tensor_product(_permute_dims(M, [1, 0]), _permute_dims(N, [1, 0])) + + cg = _permute_dims(_array_contraction(_array_tensor_product(M, N, P), (1, 2), (3, 4)), [1, 0], nest_permutation=False) + sunk = nest_permutation(cg) + assert sunk == _array_contraction(_permute_dims(_array_tensor_product(M, N, P), [[0, 5]]), (1, 2), (3, 4)) + + +def test_arrayexpr_push_indices_up_and_down(): + + indices = list(range(12)) + + contr_diag_indices = [(0, 6), (2, 8)] + assert ArrayContraction._push_indices_down(contr_diag_indices, indices) == (1, 3, 4, 5, 7, 9, 10, 11, 12, 13, 14, 15) + assert ArrayContraction._push_indices_up(contr_diag_indices, indices) == (None, 0, None, 1, 2, 3, None, 4, None, 5, 6, 7) + + assert ArrayDiagonal._push_indices_down(contr_diag_indices, indices, 10) == (1, 3, 4, 5, 7, 9, (0, 6), (2, 8), None, None, None, None) + assert ArrayDiagonal._push_indices_up(contr_diag_indices, indices, 10) == (6, 0, 7, 1, 2, 3, 6, 4, 7, 5, None, None) + + contr_diag_indices = [(1, 2), (7, 8)] + assert ArrayContraction._push_indices_down(contr_diag_indices, indices) == (0, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15) + assert ArrayContraction._push_indices_up(contr_diag_indices, indices) == (0, None, None, 1, 2, 3, 4, None, None, 5, 6, 7) + + assert ArrayDiagonal._push_indices_down(contr_diag_indices, indices, 10) == (0, 3, 4, 5, 6, 9, (1, 2), (7, 8), None, None, None, None) + assert ArrayDiagonal._push_indices_up(contr_diag_indices, indices, 10) == (0, 6, 6, 1, 2, 3, 4, 7, 7, 5, None, None) + + +def test_arrayexpr_split_multiple_contractions(): + a = MatrixSymbol("a", k, 1) + b = MatrixSymbol("b", k, 1) + A = MatrixSymbol("A", k, k) + B = MatrixSymbol("B", k, k) + C = MatrixSymbol("C", k, k) + X = MatrixSymbol("X", k, k) + + cg = _array_contraction(_array_tensor_product(A.T, a, b, b.T, (A*X*b).applyfunc(cos)), (1, 2, 8), (5, 6, 9)) + expected = _array_contraction(_array_tensor_product(A.T, DiagMatrix(a), OneArray(1), b, b.T, (A*X*b).applyfunc(cos)), (1, 3), (2, 9), (6, 7, 10)) + assert cg.split_multiple_contractions().dummy_eq(expected) + + # Check no overlap of lines: + + cg = _array_contraction(_array_tensor_product(A, a, C, a, B), (1, 2, 4), (5, 6, 8), (3, 7)) + assert cg.split_multiple_contractions() == cg + + cg = _array_contraction(_array_tensor_product(a, b, A), (0, 2, 4), (1, 3)) + assert cg.split_multiple_contractions() == cg + + +def test_arrayexpr_nested_permutations(): + + cg = _permute_dims(_permute_dims(M, (1, 0)), (1, 0)) + assert cg == M + + times = 3 + plist1 = [list(range(6)) for i in range(times)] + plist2 = [list(range(6)) for i in range(times)] + + for i in range(times): + random.shuffle(plist1[i]) + random.shuffle(plist2[i]) + + plist1.append([2, 5, 4, 1, 0, 3]) + plist2.append([3, 5, 0, 4, 1, 2]) + + plist1.append([2, 5, 4, 0, 3, 1]) + plist2.append([3, 0, 5, 1, 2, 4]) + + plist1.append([5, 4, 2, 0, 3, 1]) + plist2.append([4, 5, 0, 2, 3, 1]) + + Me = M.subs(k, 3).as_explicit() + Ne = N.subs(k, 3).as_explicit() + Pe = P.subs(k, 3).as_explicit() + cge = tensorproduct(Me, Ne, Pe) + + for permutation_array1, permutation_array2 in zip(plist1, plist2): + p1 = Permutation(permutation_array1) + p2 = Permutation(permutation_array2) + + cg = _permute_dims( + _permute_dims( + _array_tensor_product(M, N, P), + p1), + p2 + ) + result = _permute_dims( + _array_tensor_product(M, N, P), + p2*p1 + ) + assert cg == result + + # Check that `permutedims` behaves the same way with explicit-component arrays: + result1 = _permute_dims(_permute_dims(cge, p1), p2) + result2 = _permute_dims(cge, p2*p1) + assert result1 == result2 + + +def test_arrayexpr_contraction_permutation_mix(): + + Me = M.subs(k, 3).as_explicit() + Ne = N.subs(k, 3).as_explicit() + + cg1 = _array_contraction(PermuteDims(_array_tensor_product(M, N), Permutation([0, 2, 1, 3])), (2, 3)) + cg2 = _array_contraction(_array_tensor_product(M, N), (1, 3)) + assert cg1 == cg2 + cge1 = tensorcontraction(permutedims(tensorproduct(Me, Ne), Permutation([0, 2, 1, 3])), (2, 3)) + cge2 = tensorcontraction(tensorproduct(Me, Ne), (1, 3)) + assert cge1 == cge2 + + cg1 = _permute_dims(_array_tensor_product(M, N), Permutation([0, 1, 3, 2])) + cg2 = _array_tensor_product(M, _permute_dims(N, Permutation([1, 0]))) + assert cg1 == cg2 + + cg1 = _array_contraction( + _permute_dims( + _array_tensor_product(M, N, P, Q), Permutation([0, 2, 3, 1, 4, 5, 7, 6])), + (1, 2), (3, 5) + ) + cg2 = _array_contraction( + _array_tensor_product(M, N, P, _permute_dims(Q, Permutation([1, 0]))), + (1, 5), (2, 3) + ) + assert cg1 == cg2 + + cg1 = _array_contraction( + _permute_dims( + _array_tensor_product(M, N, P, Q), Permutation([1, 0, 4, 6, 2, 7, 5, 3])), + (0, 1), (2, 6), (3, 7) + ) + cg2 = _permute_dims( + _array_contraction( + _array_tensor_product(M, P, Q, N), + (0, 1), (2, 3), (4, 7)), + [1, 0] + ) + assert cg1 == cg2 + + cg1 = _array_contraction( + _permute_dims( + _array_tensor_product(M, N, P, Q), Permutation([1, 0, 4, 6, 7, 2, 5, 3])), + (0, 1), (2, 6), (3, 7) + ) + cg2 = _permute_dims( + _array_contraction( + _array_tensor_product(_permute_dims(M, [1, 0]), N, P, Q), + (0, 1), (3, 6), (4, 5) + ), + Permutation([1, 0]) + ) + assert cg1 == cg2 + + +def test_arrayexpr_permute_tensor_product(): + cg1 = _permute_dims(_array_tensor_product(M, N, P, Q), Permutation([2, 3, 1, 0, 5, 4, 6, 7])) + cg2 = _array_tensor_product(N, _permute_dims(M, [1, 0]), + _permute_dims(P, [1, 0]), Q) + assert cg1 == cg2 + + # TODO: reverse operation starting with `PermuteDims` and getting down to `bb`... + cg1 = _permute_dims(_array_tensor_product(M, N, P, Q), Permutation([2, 3, 4, 5, 0, 1, 6, 7])) + cg2 = _array_tensor_product(N, P, M, Q) + assert cg1 == cg2 + + cg1 = _permute_dims(_array_tensor_product(M, N, P, Q), Permutation([2, 3, 4, 6, 5, 7, 0, 1])) + assert cg1.expr == _array_tensor_product(N, P, Q, M) + assert cg1.permutation == Permutation([0, 1, 2, 4, 3, 5, 6, 7]) + + cg1 = _array_contraction( + _permute_dims( + _array_tensor_product(N, Q, Q, M), + [2, 1, 5, 4, 0, 3, 6, 7]), + [1, 2, 6]) + cg2 = _permute_dims(_array_contraction(_array_tensor_product(Q, Q, N, M), (3, 5, 6)), [0, 2, 3, 1, 4]) + assert cg1 == cg2 + + cg1 = _array_contraction( + _array_contraction( + _array_contraction( + _array_contraction( + _permute_dims( + _array_tensor_product(N, Q, Q, M), + [2, 1, 5, 4, 0, 3, 6, 7]), + [1, 2, 6]), + [1, 3, 4]), + [1]), + [0]) + cg2 = _array_contraction(_array_tensor_product(M, N, Q, Q), (0, 3, 5), (1, 4, 7), (2,), (6,)) + assert cg1 == cg2 + + +def test_arrayexpr_canonicalize_diagonal__permute_dims(): + tp = _array_tensor_product(M, Q, N, P) + expr = _array_diagonal( + _permute_dims(tp, [0, 1, 2, 4, 7, 6, 3, 5]), (2, 4, 5), (6, 7), + (0, 3)) + result = _array_diagonal(tp, (2, 6, 7), (3, 5), (0, 4)) + assert expr == result + + tp = _array_tensor_product(M, N, P, Q) + expr = _array_diagonal(_permute_dims(tp, [0, 5, 2, 4, 1, 6, 3, 7]), (1, 2, 6), (3, 4)) + result = _array_diagonal(_array_tensor_product(M, P, N, Q), (3, 4, 5), (1, 2)) + assert expr == result + + +def test_arrayexpr_canonicalize_diagonal_contraction(): + tp = _array_tensor_product(M, N, P, Q) + expr = _array_contraction(_array_diagonal(tp, (1, 3, 4)), (0, 3)) + result = _array_diagonal(_array_contraction(_array_tensor_product(M, N, P, Q), (0, 6)), (0, 2, 3)) + assert expr == result + + expr = _array_contraction(_array_diagonal(tp, (0, 1, 2, 3, 7)), (1, 2, 3)) + result = _array_contraction(_array_tensor_product(M, N, P, Q), (0, 1, 2, 3, 5, 6, 7)) + assert expr == result + + expr = _array_contraction(_array_diagonal(tp, (0, 2, 6, 7)), (1, 2, 3)) + result = _array_diagonal(_array_contraction(tp, (3, 4, 5)), (0, 2, 3, 4)) + assert expr == result + + td = _array_diagonal(_array_tensor_product(M, N, P, Q), (0, 3)) + expr = _array_contraction(td, (2, 1), (0, 4, 6, 5, 3)) + result = _array_contraction(_array_tensor_product(M, N, P, Q), (0, 1, 3, 5, 6, 7), (2, 4)) + assert expr == result + + +def test_arrayexpr_array_wrong_permutation_size(): + cg = _array_tensor_product(M, N) + raises(ValueError, lambda: _permute_dims(cg, [1, 0])) + raises(ValueError, lambda: _permute_dims(cg, [1, 0, 2, 3, 5, 4])) + + +def test_arrayexpr_nested_array_elementwise_add(): + cg = _array_contraction(_array_add( + _array_tensor_product(M, N), + _array_tensor_product(N, M) + ), (1, 2)) + result = _array_add( + _array_contraction(_array_tensor_product(M, N), (1, 2)), + _array_contraction(_array_tensor_product(N, M), (1, 2)) + ) + assert cg == result + + cg = _array_diagonal(_array_add( + _array_tensor_product(M, N), + _array_tensor_product(N, M) + ), (1, 2)) + result = _array_add( + _array_diagonal(_array_tensor_product(M, N), (1, 2)), + _array_diagonal(_array_tensor_product(N, M), (1, 2)) + ) + assert cg == result + + +def test_arrayexpr_array_expr_zero_array(): + za1 = ZeroArray(k, l, m, n) + zm1 = ZeroMatrix(m, n) + + za2 = ZeroArray(k, m, m, n) + zm2 = ZeroMatrix(m, m) + zm3 = ZeroMatrix(k, k) + + assert _array_tensor_product(M, N, za1) == ZeroArray(k, k, k, k, k, l, m, n) + assert _array_tensor_product(M, N, zm1) == ZeroArray(k, k, k, k, m, n) + + assert _array_contraction(za1, (3,)) == ZeroArray(k, l, m) + assert _array_contraction(zm1, (1,)) == ZeroArray(m) + assert _array_contraction(za2, (1, 2)) == ZeroArray(k, n) + assert _array_contraction(zm2, (0, 1)) == 0 + + assert _array_diagonal(za2, (1, 2)) == ZeroArray(k, n, m) + assert _array_diagonal(zm2, (0, 1)) == ZeroArray(m) + + assert _permute_dims(za1, [2, 1, 3, 0]) == ZeroArray(m, l, n, k) + assert _permute_dims(zm1, [1, 0]) == ZeroArray(n, m) + + assert _array_add(za1) == za1 + assert _array_add(zm1) == ZeroArray(m, n) + tp1 = _array_tensor_product(MatrixSymbol("A", k, l), MatrixSymbol("B", m, n)) + assert _array_add(tp1, za1) == tp1 + tp2 = _array_tensor_product(MatrixSymbol("C", k, l), MatrixSymbol("D", m, n)) + assert _array_add(tp1, za1, tp2) == _array_add(tp1, tp2) + assert _array_add(M, zm3) == M + assert _array_add(M, N, zm3) == _array_add(M, N) + + +def test_arrayexpr_array_expr_applyfunc(): + + A = ArraySymbol("A", (3, k, 2)) + aaf = ArrayElementwiseApplyFunc(sin, A) + assert aaf.shape == (3, k, 2) + + +def test_edit_array_contraction(): + cg = _array_contraction(_array_tensor_product(A, B, C, D), (1, 2, 5)) + ecg = _EditArrayContraction(cg) + assert ecg.to_array_contraction() == cg + + ecg.args_with_ind[1], ecg.args_with_ind[2] = ecg.args_with_ind[2], ecg.args_with_ind[1] + assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, C, B, D), (1, 3, 4)) + + ci = ecg.get_new_contraction_index() + new_arg = _ArgE(X) + new_arg.indices = [ci, ci] + ecg.args_with_ind.insert(2, new_arg) + assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, C, X, B, D), (1, 3, 6), (4, 5)) + + assert ecg.get_contraction_indices() == [[1, 3, 6], [4, 5]] + assert [[tuple(j) for j in i] for i in ecg.get_contraction_indices_to_ind_rel_pos()] == [[(0, 1), (1, 1), (3, 0)], [(2, 0), (2, 1)]] + assert [list(i) for i in ecg.get_mapping_for_index(0)] == [[0, 1], [1, 1], [3, 0]] + assert [list(i) for i in ecg.get_mapping_for_index(1)] == [[2, 0], [2, 1]] + raises(ValueError, lambda: ecg.get_mapping_for_index(2)) + + ecg.args_with_ind.pop(1) + assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, B, D), (1, 4), (2, 3)) + + ecg.args_with_ind[0].indices[1] = ecg.args_with_ind[1].indices[0] + ecg.args_with_ind[1].indices[1] = ecg.args_with_ind[2].indices[0] + assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, B, D), (1, 2), (3, 4)) + + ecg.insert_after(ecg.args_with_ind[1], _ArgE(C)) + assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, C, B, D), (1, 2), (3, 6)) + + +def test_array_expressions_no_canonicalization(): + + tp = _array_tensor_product(M, N, P) + + # ArrayTensorProduct: + + expr = ArrayTensorProduct(tp, N) + assert str(expr) == "ArrayTensorProduct(ArrayTensorProduct(M, N, P), N)" + assert expr.doit() == ArrayTensorProduct(M, N, P, N) + + expr = ArrayTensorProduct(ArrayContraction(M, (0, 1)), N) + assert str(expr) == "ArrayTensorProduct(ArrayContraction(M, (0, 1)), N)" + assert expr.doit() == ArrayContraction(ArrayTensorProduct(M, N), (0, 1)) + + expr = ArrayTensorProduct(ArrayDiagonal(M, (0, 1)), N) + assert str(expr) == "ArrayTensorProduct(ArrayDiagonal(M, (0, 1)), N)" + assert expr.doit() == PermuteDims(ArrayDiagonal(ArrayTensorProduct(M, N), (0, 1)), [2, 0, 1]) + + expr = ArrayTensorProduct(PermuteDims(M, [1, 0]), N) + assert str(expr) == "ArrayTensorProduct(PermuteDims(M, (0 1)), N)" + assert expr.doit() == PermuteDims(ArrayTensorProduct(M, N), [1, 0, 2, 3]) + + # ArrayContraction: + + expr = ArrayContraction(_array_contraction(tp, (0, 2)), (0, 1)) + assert isinstance(expr, ArrayContraction) + assert isinstance(expr.expr, ArrayContraction) + assert str(expr) == "ArrayContraction(ArrayContraction(ArrayTensorProduct(M, N, P), (0, 2)), (0, 1))" + assert expr.doit() == ArrayContraction(tp, (0, 2), (1, 3)) + + expr = ArrayContraction(ArrayContraction(ArrayContraction(tp, (0, 1)), (0, 1)), (0, 1)) + assert expr.doit() == ArrayContraction(tp, (0, 1), (2, 3), (4, 5)) + # assert expr._canonicalize() == ArrayContraction(ArrayContraction(tp, (0, 1)), (0, 1), (2, 3)) + + expr = ArrayContraction(ArrayDiagonal(tp, (0, 1)), (0, 1)) + assert str(expr) == "ArrayContraction(ArrayDiagonal(ArrayTensorProduct(M, N, P), (0, 1)), (0, 1))" + assert expr.doit() == ArrayDiagonal(ArrayContraction(ArrayTensorProduct(N, M, P), (0, 1)), (0, 1)) + + expr = ArrayContraction(PermuteDims(M, [1, 0]), (0, 1)) + assert str(expr) == "ArrayContraction(PermuteDims(M, (0 1)), (0, 1))" + assert expr.doit() == ArrayContraction(M, (0, 1)) + + # ArrayDiagonal: + + expr = ArrayDiagonal(ArrayDiagonal(tp, (0, 2)), (0, 1)) + assert str(expr) == "ArrayDiagonal(ArrayDiagonal(ArrayTensorProduct(M, N, P), (0, 2)), (0, 1))" + assert expr.doit() == ArrayDiagonal(tp, (0, 2), (1, 3)) + + expr = ArrayDiagonal(ArrayDiagonal(ArrayDiagonal(tp, (0, 1)), (0, 1)), (0, 1)) + assert expr.doit() == ArrayDiagonal(tp, (0, 1), (2, 3), (4, 5)) + assert expr._canonicalize() == expr.doit() + + expr = ArrayDiagonal(ArrayContraction(tp, (0, 1)), (0, 1)) + assert str(expr) == "ArrayDiagonal(ArrayContraction(ArrayTensorProduct(M, N, P), (0, 1)), (0, 1))" + assert expr.doit() == expr + + expr = ArrayDiagonal(PermuteDims(M, [1, 0]), (0, 1)) + assert str(expr) == "ArrayDiagonal(PermuteDims(M, (0 1)), (0, 1))" + assert expr.doit() == ArrayDiagonal(M, (0, 1)) + + # ArrayAdd: + + expr = ArrayAdd(M) + assert isinstance(expr, ArrayAdd) + assert expr.doit() == M + + expr = ArrayAdd(ArrayAdd(M, N), P) + assert str(expr) == "ArrayAdd(ArrayAdd(M, N), P)" + assert expr.doit() == ArrayAdd(M, N, P) + + expr = ArrayAdd(M, ArrayAdd(N, ArrayAdd(P, M))) + assert expr.doit() == ArrayAdd(M, N, P, M) + assert expr._canonicalize() == ArrayAdd(M, N, ArrayAdd(P, M)) + + expr = ArrayAdd(M, ZeroArray(k, k), N) + assert str(expr) == "ArrayAdd(M, ZeroArray(k, k), N)" + assert expr.doit() == ArrayAdd(M, N) + + # PermuteDims: + + expr = PermuteDims(PermuteDims(M, [1, 0]), [1, 0]) + assert str(expr) == "PermuteDims(PermuteDims(M, (0 1)), (0 1))" + assert expr.doit() == M + + expr = PermuteDims(PermuteDims(PermuteDims(M, [1, 0]), [1, 0]), [1, 0]) + assert expr.doit() == PermuteDims(M, [1, 0]) + assert expr._canonicalize() == expr.doit() + + # Reshape + + expr = Reshape(A, (k**2,)) + assert expr.shape == (k**2,) + assert isinstance(expr, Reshape) + + +def test_array_expr_construction_with_functions(): + + tp = tensorproduct(M, N) + assert tp == ArrayTensorProduct(M, N) + + expr = tensorproduct(A, eye(2)) + assert expr == ArrayTensorProduct(A, eye(2)) + + # Contraction: + + expr = tensorcontraction(M, (0, 1)) + assert expr == ArrayContraction(M, (0, 1)) + + expr = tensorcontraction(tp, (1, 2)) + assert expr == ArrayContraction(tp, (1, 2)) + + expr = tensorcontraction(tensorcontraction(tp, (1, 2)), (0, 1)) + assert expr == ArrayContraction(tp, (0, 3), (1, 2)) + + # Diagonalization: + + expr = tensordiagonal(M, (0, 1)) + assert expr == ArrayDiagonal(M, (0, 1)) + + expr = tensordiagonal(tensordiagonal(tp, (0, 1)), (0, 1)) + assert expr == ArrayDiagonal(tp, (0, 1), (2, 3)) + + # Permutation of dimensions: + + expr = permutedims(M, [1, 0]) + assert expr == PermuteDims(M, [1, 0]) + + expr = permutedims(PermuteDims(tp, [1, 0, 2, 3]), [0, 1, 3, 2]) + assert expr == PermuteDims(tp, [1, 0, 3, 2]) + + expr = PermuteDims(tp, index_order_new=["a", "b", "c", "d"], index_order_old=["d", "c", "b", "a"]) + assert expr == PermuteDims(tp, [3, 2, 1, 0]) + + arr = Array(range(32)).reshape(2, 2, 2, 2, 2) + expr = PermuteDims(arr, index_order_new=["a", "b", "c", "d", "e"], index_order_old=['b', 'e', 'a', 'd', 'c']) + assert expr == PermuteDims(arr, [2, 0, 4, 3, 1]) + assert expr.as_explicit() == permutedims(arr, index_order_new=["a", "b", "c", "d", "e"], index_order_old=['b', 'e', 'a', 'd', 'c']) + + +def test_array_element_expressions(): + # Check commutative property: + assert M[0, 0]*N[0, 0] == N[0, 0]*M[0, 0] + + # Check derivatives: + assert M[0, 0].diff(M[0, 0]) == 1 + assert M[0, 0].diff(M[1, 0]) == 0 + assert M[0, 0].diff(N[0, 0]) == 0 + assert M[0, 1].diff(M[i, j]) == KroneckerDelta(i, 0)*KroneckerDelta(j, 1) + assert M[0, 1].diff(N[i, j]) == 0 + + K4 = ArraySymbol("K4", shape=(k, k, k, k)) + + assert K4[i, j, k, l].diff(K4[1, 2, 3, 4]) == ( + KroneckerDelta(i, 1)*KroneckerDelta(j, 2)*KroneckerDelta(k, 3)*KroneckerDelta(l, 4) + ) + + +def test_array_expr_reshape(): + + A = MatrixSymbol("A", 2, 2) + B = ArraySymbol("B", (2, 2, 2)) + C = Array([1, 2, 3, 4]) + + expr = Reshape(A, (4,)) + assert expr.expr == A + assert expr.shape == (4,) + assert expr.as_explicit() == Array([A[0, 0], A[0, 1], A[1, 0], A[1, 1]]) + + expr = Reshape(B, (2, 4)) + assert expr.expr == B + assert expr.shape == (2, 4) + ee = expr.as_explicit() + assert isinstance(ee, ImmutableDenseNDimArray) + assert ee.shape == (2, 4) + assert ee == Array([[B[0, 0, 0], B[0, 0, 1], B[0, 1, 0], B[0, 1, 1]], [B[1, 0, 0], B[1, 0, 1], B[1, 1, 0], B[1, 1, 1]]]) + + expr = Reshape(A, (k, 2)) + assert expr.shape == (k, 2) + + raises(ValueError, lambda: Reshape(A, (2, 3))) + raises(ValueError, lambda: Reshape(A, (3,))) + + expr = Reshape(C, (2, 2)) + assert expr.expr == C + assert expr.shape == (2, 2) + assert expr.doit() == Array([[1, 2], [3, 4]]) + + +def test_array_expr_as_explicit_with_explicit_component_arrays(): + # Test if .as_explicit() works with explicit-component arrays + # nested in array expressions: + from sympy.abc import x, y, z, t + A = Array([[x, y], [z, t]]) + assert ArrayTensorProduct(A, A).as_explicit() == tensorproduct(A, A) + assert ArrayDiagonal(A, (0, 1)).as_explicit() == tensordiagonal(A, (0, 1)) + assert ArrayContraction(A, (0, 1)).as_explicit() == tensorcontraction(A, (0, 1)) + assert ArrayAdd(A, A).as_explicit() == A + A + assert ArrayElementwiseApplyFunc(sin, A).as_explicit() == A.applyfunc(sin) + assert PermuteDims(A, [1, 0]).as_explicit() == permutedims(A, [1, 0]) + assert Reshape(A, [4]).as_explicit() == A.reshape(4) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_arrayexpr_derivatives.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_arrayexpr_derivatives.py new file mode 100644 index 0000000000000000000000000000000000000000..bc0fcf63f2607b23feb38758e4f0994de4f0384b --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_arrayexpr_derivatives.py @@ -0,0 +1,78 @@ +from sympy.core.symbol import symbols +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import Identity +from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction +from sympy.tensor.array.expressions.array_expressions import ArraySymbol, ArrayTensorProduct, \ + PermuteDims, ArrayDiagonal, ArrayElementwiseApplyFunc, ArrayContraction, _permute_dims, Reshape +from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive + +k = symbols("k") + +I = Identity(k) +X = MatrixSymbol("X", k, k) +x = MatrixSymbol("x", k, 1) + +A = MatrixSymbol("A", k, k) +B = MatrixSymbol("B", k, k) +C = MatrixSymbol("C", k, k) +D = MatrixSymbol("D", k, k) + +A1 = ArraySymbol("A", (3, 2, k)) + + +def test_arrayexpr_derivatives1(): + + res = array_derive(X, X) + assert res == PermuteDims(ArrayTensorProduct(I, I), [0, 2, 1, 3]) + + cg = ArrayTensorProduct(A, X, B) + res = array_derive(cg, X) + assert res == _permute_dims( + ArrayTensorProduct(I, A, I, B), + [0, 4, 2, 3, 1, 5, 6, 7]) + + cg = ArrayContraction(X, (0, 1)) + res = array_derive(cg, X) + assert res == ArrayContraction(ArrayTensorProduct(I, I), (1, 3)) + + cg = ArrayDiagonal(X, (0, 1)) + res = array_derive(cg, X) + assert res == ArrayDiagonal(ArrayTensorProduct(I, I), (1, 3)) + + cg = ElementwiseApplyFunction(sin, X) + res = array_derive(cg, X) + assert res.dummy_eq(ArrayDiagonal( + ArrayTensorProduct( + ElementwiseApplyFunction(cos, X), + I, + I + ), (0, 3), (1, 5))) + + cg = ArrayElementwiseApplyFunc(sin, X) + res = array_derive(cg, X) + assert res.dummy_eq(ArrayDiagonal( + ArrayTensorProduct( + I, + I, + ArrayElementwiseApplyFunc(cos, X) + ), (1, 4), (3, 5))) + + res = array_derive(A1, A1) + assert res == PermuteDims( + ArrayTensorProduct(Identity(3), Identity(2), Identity(k)), + [0, 2, 4, 1, 3, 5] + ) + + cg = ArrayElementwiseApplyFunc(sin, A1) + res = array_derive(cg, A1) + assert res.dummy_eq(ArrayDiagonal( + ArrayTensorProduct( + Identity(3), Identity(2), Identity(k), + ArrayElementwiseApplyFunc(cos, A1) + ), (1, 6), (3, 7), (5, 8) + )) + + cg = Reshape(A, (k**2,)) + res = array_derive(cg, A) + assert res == Reshape(PermuteDims(ArrayTensorProduct(I, I), [0, 2, 1, 3]), (k, k, k**2)) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_as_explicit.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_as_explicit.py new file mode 100644 index 0000000000000000000000000000000000000000..30cc61b1ee651ca032e165cd67926fa33c71354f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_as_explicit.py @@ -0,0 +1,63 @@ +from sympy.core.symbol import Symbol +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.tensor.array.arrayop import (permutedims, tensorcontraction, tensordiagonal, tensorproduct) +from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray +from sympy.tensor.array.expressions.array_expressions import ZeroArray, OneArray, ArraySymbol, \ + ArrayTensorProduct, PermuteDims, ArrayDiagonal, ArrayContraction, ArrayAdd +from sympy.testing.pytest import raises + + +def test_array_as_explicit_call(): + + assert ZeroArray(3, 2, 4).as_explicit() == ImmutableDenseNDimArray.zeros(3, 2, 4) + assert OneArray(3, 2, 4).as_explicit() == ImmutableDenseNDimArray([1 for i in range(3*2*4)]).reshape(3, 2, 4) + + k = Symbol("k") + X = ArraySymbol("X", (k, 3, 2)) + raises(ValueError, lambda: X.as_explicit()) + raises(ValueError, lambda: ZeroArray(k, 2, 3).as_explicit()) + raises(ValueError, lambda: OneArray(2, k, 2).as_explicit()) + + A = ArraySymbol("A", (3, 3)) + B = ArraySymbol("B", (3, 3)) + + texpr = tensorproduct(A, B) + assert isinstance(texpr, ArrayTensorProduct) + assert texpr.as_explicit() == tensorproduct(A.as_explicit(), B.as_explicit()) + + texpr = tensorcontraction(A, (0, 1)) + assert isinstance(texpr, ArrayContraction) + assert texpr.as_explicit() == A[0, 0] + A[1, 1] + A[2, 2] + + texpr = tensordiagonal(A, (0, 1)) + assert isinstance(texpr, ArrayDiagonal) + assert texpr.as_explicit() == ImmutableDenseNDimArray([A[0, 0], A[1, 1], A[2, 2]]) + + texpr = permutedims(A, [1, 0]) + assert isinstance(texpr, PermuteDims) + assert texpr.as_explicit() == permutedims(A.as_explicit(), [1, 0]) + + +def test_array_as_explicit_matrix_symbol(): + + A = MatrixSymbol("A", 3, 3) + B = MatrixSymbol("B", 3, 3) + + texpr = tensorproduct(A, B) + assert isinstance(texpr, ArrayTensorProduct) + assert texpr.as_explicit() == tensorproduct(A.as_explicit(), B.as_explicit()) + + texpr = tensorcontraction(A, (0, 1)) + assert isinstance(texpr, ArrayContraction) + assert texpr.as_explicit() == A[0, 0] + A[1, 1] + A[2, 2] + + texpr = tensordiagonal(A, (0, 1)) + assert isinstance(texpr, ArrayDiagonal) + assert texpr.as_explicit() == ImmutableDenseNDimArray([A[0, 0], A[1, 1], A[2, 2]]) + + texpr = permutedims(A, [1, 0]) + assert isinstance(texpr, PermuteDims) + assert texpr.as_explicit() == permutedims(A.as_explicit(), [1, 0]) + + expr = ArrayAdd(ArrayTensorProduct(A, B), ArrayTensorProduct(B, A)) + assert expr.as_explicit() == expr.args[0].as_explicit() + expr.args[1].as_explicit() diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_array_to_indexed.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_array_to_indexed.py new file mode 100644 index 0000000000000000000000000000000000000000..a6b713fbec94ab7808c5a8a778b3313402d9d0c7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_array_to_indexed.py @@ -0,0 +1,61 @@ +from sympy import Sum, Dummy, sin +from sympy.tensor.array.expressions import ArraySymbol, ArrayTensorProduct, ArrayContraction, PermuteDims, \ + ArrayDiagonal, ArrayAdd, OneArray, ZeroArray, convert_indexed_to_array, ArrayElementwiseApplyFunc, Reshape +from sympy.tensor.array.expressions.from_array_to_indexed import convert_array_to_indexed + +from sympy.abc import i, j, k, l, m, n, o + + +def test_convert_array_to_indexed_main(): + A = ArraySymbol("A", (3, 3, 3)) + B = ArraySymbol("B", (3, 3)) + C = ArraySymbol("C", (3, 3)) + + d_ = Dummy("d_") + + assert convert_array_to_indexed(A, [i, j, k]) == A[i, j, k] + + expr = ArrayTensorProduct(A, B, C) + conv = convert_array_to_indexed(expr, [i,j,k,l,m,n,o]) + assert conv == A[i,j,k]*B[l,m]*C[n,o] + assert convert_indexed_to_array(conv, [i,j,k,l,m,n,o]) == expr + + expr = ArrayContraction(A, (0, 2)) + assert convert_array_to_indexed(expr, [i]).dummy_eq(Sum(A[d_, i, d_], (d_, 0, 2))) + + expr = ArrayDiagonal(A, (0, 2)) + assert convert_array_to_indexed(expr, [i, j]) == A[j, i, j] + + expr = PermuteDims(A, [1, 2, 0]) + conv = convert_array_to_indexed(expr, [i, j, k]) + assert conv == A[k, i, j] + assert convert_indexed_to_array(conv, [i, j, k]) == expr + + expr = ArrayAdd(B, C, PermuteDims(C, [1, 0])) + conv = convert_array_to_indexed(expr, [i, j]) + assert conv == B[i, j] + C[i, j] + C[j, i] + assert convert_indexed_to_array(conv, [i, j]) == expr + + expr = ArrayElementwiseApplyFunc(sin, A) + conv = convert_array_to_indexed(expr, [i, j, k]) + assert conv == sin(A[i, j, k]) + assert convert_indexed_to_array(conv, [i, j, k]).dummy_eq(expr) + + assert convert_array_to_indexed(OneArray(3, 3), [i, j]) == 1 + assert convert_array_to_indexed(ZeroArray(3, 3), [i, j]) == 0 + + expr = Reshape(A, (27,)) + assert convert_array_to_indexed(expr, [i]) == A[i // 9, i // 3 % 3, i % 3] + + X = ArraySymbol("X", (2, 3, 4, 5, 6)) + expr = Reshape(X, (2*3*4*5*6,)) + assert convert_array_to_indexed(expr, [i]) == X[i // 360, i // 120 % 3, i // 30 % 4, i // 6 % 5, i % 6] + + expr = Reshape(X, (4, 9, 2, 2, 5)) + one_index = 180*i + 20*j + 10*k + 5*l + m + expected = X[one_index // (3*4*5*6), one_index // (4*5*6) % 3, one_index // (5*6) % 4, one_index // 6 % 5, one_index % 6] + assert convert_array_to_indexed(expr, [i, j, k, l, m]) == expected + + X = ArraySymbol("X", (2*3*5,)) + expr = Reshape(X, (2, 3, 5)) + assert convert_array_to_indexed(expr, [i, j, k]) == X[15*i + 5*j + k] diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_array_to_matrix.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_array_to_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..26839d5e7cec0554948c6b726482f9d8ca250b1c --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_array_to_matrix.py @@ -0,0 +1,689 @@ +from sympy import Lambda, S, Dummy, KroneckerProduct +from sympy.core.symbol import symbols +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.matrices.expressions.hadamard import HadamardProduct, HadamardPower +from sympy.matrices.expressions.special import (Identity, OneMatrix, ZeroMatrix) +from sympy.matrices.expressions.matexpr import MatrixElement +from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array +from sympy.tensor.array.expressions.from_array_to_matrix import _support_function_tp1_recognize, \ + _array_diag2contr_diagmatrix, convert_array_to_matrix, _remove_trivial_dims, _array2matrix, \ + _combine_removed, identify_removable_identity_matrices, _array_contraction_to_diagonal_multiple_identity +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.combinatorics import Permutation +from sympy.matrices.expressions.diagonal import DiagMatrix, DiagonalMatrix +from sympy.matrices import Trace, MatMul, Transpose +from sympy.tensor.array.expressions.array_expressions import ZeroArray, OneArray, \ + ArrayElement, ArraySymbol, ArrayElementwiseApplyFunc, _array_tensor_product, _array_contraction, \ + _array_diagonal, _permute_dims, PermuteDims, ArrayAdd, ArrayDiagonal, ArrayContraction, ArrayTensorProduct +from sympy.testing.pytest import raises + + +i, j, k, l, m, n = symbols("i j k l m n") + +I = Identity(k) +I1 = Identity(1) + +M = MatrixSymbol("M", k, k) +N = MatrixSymbol("N", k, k) +P = MatrixSymbol("P", k, k) +Q = MatrixSymbol("Q", k, k) + +A = MatrixSymbol("A", k, k) +B = MatrixSymbol("B", k, k) +C = MatrixSymbol("C", k, k) +D = MatrixSymbol("D", k, k) + +X = MatrixSymbol("X", k, k) +Y = MatrixSymbol("Y", k, k) + +a = MatrixSymbol("a", k, 1) +b = MatrixSymbol("b", k, 1) +c = MatrixSymbol("c", k, 1) +d = MatrixSymbol("d", k, 1) + +x = MatrixSymbol("x", k, 1) +y = MatrixSymbol("y", k, 1) + + +def test_arrayexpr_convert_array_to_matrix(): + + cg = _array_contraction(_array_tensor_product(M), (0, 1)) + assert convert_array_to_matrix(cg) == Trace(M) + + cg = _array_contraction(_array_tensor_product(M, N), (0, 1), (2, 3)) + assert convert_array_to_matrix(cg) == Trace(M) * Trace(N) + + cg = _array_contraction(_array_tensor_product(M, N), (0, 3), (1, 2)) + assert convert_array_to_matrix(cg) == Trace(M * N) + + cg = _array_contraction(_array_tensor_product(M, N), (0, 2), (1, 3)) + assert convert_array_to_matrix(cg) == Trace(M * N.T) + + cg = convert_matrix_to_array(M * N * P) + assert convert_array_to_matrix(cg) == M * N * P + + cg = convert_matrix_to_array(M * N.T * P) + assert convert_array_to_matrix(cg) == M * N.T * P + + cg = _array_contraction(_array_tensor_product(M,N,P,Q), (1, 2), (5, 6)) + assert convert_array_to_matrix(cg) == _array_tensor_product(M * N, P * Q) + + cg = _array_contraction(_array_tensor_product(-2, M, N), (1, 2)) + assert convert_array_to_matrix(cg) == -2 * M * N + + a = MatrixSymbol("a", k, 1) + b = MatrixSymbol("b", k, 1) + c = MatrixSymbol("c", k, 1) + cg = PermuteDims( + _array_contraction( + _array_tensor_product( + a, + ArrayAdd( + _array_tensor_product(b, c), + _array_tensor_product(c, b), + ) + ), (2, 4)), [0, 1, 3, 2]) + assert convert_array_to_matrix(cg) == a * (b.T * c + c.T * b) + + za = ZeroArray(m, n) + assert convert_array_to_matrix(za) == ZeroMatrix(m, n) + + cg = _array_tensor_product(3, M) + assert convert_array_to_matrix(cg) == 3 * M + + # Partial conversion to matrix multiplication: + expr = _array_contraction(_array_tensor_product(M, N, P, Q), (0, 2), (1, 4, 6)) + assert convert_array_to_matrix(expr) == _array_contraction(_array_tensor_product(M.T*N, P, Q), (0, 2, 4)) + + x = MatrixSymbol("x", k, 1) + cg = PermuteDims( + _array_contraction(_array_tensor_product(OneArray(1), x, OneArray(1), DiagMatrix(Identity(1))), + (0, 5)), Permutation(1, 2, 3)) + assert convert_array_to_matrix(cg) == x + + expr = ArrayAdd(M, PermuteDims(M, [1, 0])) + assert convert_array_to_matrix(expr) == M + Transpose(M) + + +def test_arrayexpr_convert_array_to_matrix2(): + cg = _array_contraction(_array_tensor_product(M, N), (1, 3)) + assert convert_array_to_matrix(cg) == M * N.T + + cg = PermuteDims(_array_tensor_product(M, N), Permutation([0, 1, 3, 2])) + assert convert_array_to_matrix(cg) == _array_tensor_product(M, N.T) + + cg = _array_tensor_product(M, PermuteDims(N, Permutation([1, 0]))) + assert convert_array_to_matrix(cg) == _array_tensor_product(M, N.T) + + cg = _array_contraction( + PermuteDims( + _array_tensor_product(M, N, P, Q), Permutation([0, 2, 3, 1, 4, 5, 7, 6])), + (1, 2), (3, 5) + ) + assert convert_array_to_matrix(cg) == _array_tensor_product(M * P.T * Trace(N), Q.T) + + cg = _array_contraction( + _array_tensor_product(M, N, P, PermuteDims(Q, Permutation([1, 0]))), + (1, 5), (2, 3) + ) + assert convert_array_to_matrix(cg) == _array_tensor_product(M * P.T * Trace(N), Q.T) + + cg = _array_tensor_product(M, PermuteDims(N, [1, 0])) + assert convert_array_to_matrix(cg) == _array_tensor_product(M, N.T) + + cg = _array_tensor_product(PermuteDims(M, [1, 0]), PermuteDims(N, [1, 0])) + assert convert_array_to_matrix(cg) == _array_tensor_product(M.T, N.T) + + cg = _array_tensor_product(PermuteDims(N, [1, 0]), PermuteDims(M, [1, 0])) + assert convert_array_to_matrix(cg) == _array_tensor_product(N.T, M.T) + + cg = _array_contraction(M, (0,), (1,)) + assert convert_array_to_matrix(cg) == OneMatrix(1, k)*M*OneMatrix(k, 1) + + cg = _array_contraction(x, (0,), (1,)) + assert convert_array_to_matrix(cg) == OneMatrix(1, k)*x + + Xm = MatrixSymbol("Xm", m, n) + cg = _array_contraction(Xm, (0,), (1,)) + assert convert_array_to_matrix(cg) == OneMatrix(1, m)*Xm*OneMatrix(n, 1) + + +def test_arrayexpr_convert_array_to_diagonalized_vector(): + + # Check matrix recognition over trivial dimensions: + + cg = _array_tensor_product(a, b) + assert convert_array_to_matrix(cg) == a * b.T + + cg = _array_tensor_product(I1, a, b) + assert convert_array_to_matrix(cg) == a * b.T + + # Recognize trace inside a tensor product: + + cg = _array_contraction(_array_tensor_product(A, B, C), (0, 3), (1, 2)) + assert convert_array_to_matrix(cg) == Trace(A * B) * C + + # Transform diagonal operator to contraction: + + cg = _array_diagonal(_array_tensor_product(A, a), (1, 2)) + assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(A, OneArray(1), DiagMatrix(a)), (1, 3)) + assert convert_array_to_matrix(cg) == A * DiagMatrix(a) + + cg = _array_diagonal(_array_tensor_product(a, b), (0, 2)) + assert _array_diag2contr_diagmatrix(cg) == _permute_dims( + _array_contraction(_array_tensor_product(DiagMatrix(a), OneArray(1), b), (0, 3)), [1, 2, 0] + ) + assert convert_array_to_matrix(cg) == b.T * DiagMatrix(a) + + cg = _array_diagonal(_array_tensor_product(A, a), (0, 2)) + assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(A, OneArray(1), DiagMatrix(a)), (0, 3)) + assert convert_array_to_matrix(cg) == A.T * DiagMatrix(a) + + cg = _array_diagonal(_array_tensor_product(I, x, I1), (0, 2), (3, 5)) + assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(I, OneArray(1), I1, DiagMatrix(x)), (0, 5)) + assert convert_array_to_matrix(cg) == DiagMatrix(x) + + cg = _array_diagonal(_array_tensor_product(I, x, A, B), (1, 2), (5, 6)) + assert _array_diag2contr_diagmatrix(cg) == _array_diagonal(_array_contraction(_array_tensor_product(I, OneArray(1), A, B, DiagMatrix(x)), (1, 7)), (5, 6)) + # TODO: this is returning a wrong result: + # convert_array_to_matrix(cg) + + cg = _array_diagonal(_array_tensor_product(I1, a, b), (1, 3, 5)) + assert convert_array_to_matrix(cg) == a*b.T + + cg = _array_diagonal(_array_tensor_product(I1, a, b), (1, 3)) + assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(OneArray(1), a, b, I1), (2, 6)) + assert convert_array_to_matrix(cg) == a*b.T + + cg = _array_diagonal(_array_tensor_product(x, I1), (1, 2)) + assert isinstance(cg, ArrayDiagonal) + assert cg.diagonal_indices == ((1, 2),) + assert convert_array_to_matrix(cg) == x + + cg = _array_diagonal(_array_tensor_product(x, I), (0, 2)) + assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(OneArray(1), I, DiagMatrix(x)), (1, 3)) + assert convert_array_to_matrix(cg).doit() == DiagMatrix(x) + + raises(ValueError, lambda: _array_diagonal(x, (1,))) + + # Ignore identity matrices with contractions: + + cg = _array_contraction(_array_tensor_product(I, A, I, I), (0, 2), (1, 3), (5, 7)) + assert cg.split_multiple_contractions() == cg + assert convert_array_to_matrix(cg) == Trace(A) * I + + cg = _array_contraction(_array_tensor_product(Trace(A) * I, I, I), (1, 5), (3, 4)) + assert cg.split_multiple_contractions() == cg + assert convert_array_to_matrix(cg).doit() == Trace(A) * I + + # Add DiagMatrix when required: + + cg = _array_contraction(_array_tensor_product(A, a), (1, 2)) + assert cg.split_multiple_contractions() == cg + assert convert_array_to_matrix(cg) == A * a + + cg = _array_contraction(_array_tensor_product(A, a, B), (1, 2, 4)) + assert cg.split_multiple_contractions() == _array_contraction(_array_tensor_product(A, DiagMatrix(a), OneArray(1), B), (1, 2), (3, 5)) + assert convert_array_to_matrix(cg) == A * DiagMatrix(a) * B + + cg = _array_contraction(_array_tensor_product(A, a, B), (0, 2, 4)) + assert cg.split_multiple_contractions() == _array_contraction(_array_tensor_product(A, DiagMatrix(a), OneArray(1), B), (0, 2), (3, 5)) + assert convert_array_to_matrix(cg) == A.T * DiagMatrix(a) * B + + cg = _array_contraction(_array_tensor_product(A, a, b, a.T, B), (0, 2, 4, 7, 9)) + assert cg.split_multiple_contractions() == _array_contraction(_array_tensor_product(A, DiagMatrix(a), OneArray(1), + DiagMatrix(b), OneArray(1), DiagMatrix(a), OneArray(1), B), + (0, 2), (3, 5), (6, 9), (8, 12)) + assert convert_array_to_matrix(cg) == A.T * DiagMatrix(a) * DiagMatrix(b) * DiagMatrix(a) * B.T + + cg = _array_contraction(_array_tensor_product(I1, I1, I1), (1, 2, 4)) + assert cg.split_multiple_contractions() == _array_contraction(_array_tensor_product(I1, I1, OneArray(1), I1), (1, 2), (3, 5)) + assert convert_array_to_matrix(cg) == 1 + + cg = _array_contraction(_array_tensor_product(I, I, I, I, A), (1, 2, 8), (5, 6, 9)) + assert convert_array_to_matrix(cg.split_multiple_contractions()).doit() == A + + cg = _array_contraction(_array_tensor_product(A, a, C, a, B), (1, 2, 4), (5, 6, 8)) + expected = _array_contraction(_array_tensor_product(A, DiagMatrix(a), OneArray(1), C, DiagMatrix(a), OneArray(1), B), (1, 3), (2, 5), (6, 7), (8, 10)) + assert cg.split_multiple_contractions() == expected + assert convert_array_to_matrix(cg) == A * DiagMatrix(a) * C * DiagMatrix(a) * B + + cg = _array_contraction(_array_tensor_product(a, I1, b, I1, (a.T*b).applyfunc(cos)), (1, 2, 8), (5, 6, 9)) + expected = _array_contraction(_array_tensor_product(a, I1, OneArray(1), b, I1, OneArray(1), (a.T*b).applyfunc(cos)), + (1, 3), (2, 10), (6, 8), (7, 11)) + assert cg.split_multiple_contractions().dummy_eq(expected) + assert convert_array_to_matrix(cg).doit().dummy_eq(MatMul(a, (a.T * b).applyfunc(cos), b.T)) + + +def test_arrayexpr_convert_array_contraction_tp_additions(): + a = ArrayAdd( + _array_tensor_product(M, N), + _array_tensor_product(N, M) + ) + tp = _array_tensor_product(P, a, Q) + expr = _array_contraction(tp, (3, 4)) + expected = _array_tensor_product( + P, + ArrayAdd( + _array_contraction(_array_tensor_product(M, N), (1, 2)), + _array_contraction(_array_tensor_product(N, M), (1, 2)), + ), + Q + ) + assert expr == expected + assert convert_array_to_matrix(expr) == _array_tensor_product(P, M * N + N * M, Q) + + expr = _array_contraction(tp, (1, 2), (3, 4), (5, 6)) + result = _array_contraction( + _array_tensor_product( + P, + ArrayAdd( + _array_contraction(_array_tensor_product(M, N), (1, 2)), + _array_contraction(_array_tensor_product(N, M), (1, 2)), + ), + Q + ), (1, 2), (3, 4)) + assert expr == result + assert convert_array_to_matrix(expr) == P * (M * N + N * M) * Q + + +def test_arrayexpr_convert_array_to_implicit_matmul(): + # Trivial dimensions are suppressed, so the result can be expressed in matrix form: + + cg = _array_tensor_product(a, b) + assert convert_array_to_matrix(cg) == a * b.T + + cg = _array_tensor_product(a, b, I) + assert convert_array_to_matrix(cg) == _array_tensor_product(a*b.T, I) + + cg = _array_tensor_product(I, a, b) + assert convert_array_to_matrix(cg) == _array_tensor_product(I, a*b.T) + + cg = _array_tensor_product(a, I, b) + assert convert_array_to_matrix(cg) == _array_tensor_product(a, I, b) + + cg = _array_contraction(_array_tensor_product(I, I), (1, 2)) + assert convert_array_to_matrix(cg) == I + + cg = PermuteDims(_array_tensor_product(I, Identity(1)), [0, 2, 1, 3]) + assert convert_array_to_matrix(cg) == I + + +def test_arrayexpr_convert_array_to_matrix_remove_trivial_dims(): + + # Tensor Product: + assert _remove_trivial_dims(_array_tensor_product(a, b)) == (a * b.T, [1, 3]) + assert _remove_trivial_dims(_array_tensor_product(a.T, b)) == (a * b.T, [0, 3]) + assert _remove_trivial_dims(_array_tensor_product(a, b.T)) == (a * b.T, [1, 2]) + assert _remove_trivial_dims(_array_tensor_product(a.T, b.T)) == (a * b.T, [0, 2]) + + assert _remove_trivial_dims(_array_tensor_product(I, a.T, b.T)) == (_array_tensor_product(I, a * b.T), [2, 4]) + assert _remove_trivial_dims(_array_tensor_product(a.T, I, b.T)) == (_array_tensor_product(a.T, I, b.T), []) + + assert _remove_trivial_dims(_array_tensor_product(a, I)) == (_array_tensor_product(a, I), []) + assert _remove_trivial_dims(_array_tensor_product(I, a)) == (_array_tensor_product(I, a), []) + + assert _remove_trivial_dims(_array_tensor_product(a.T, b.T, c, d)) == ( + _array_tensor_product(a * b.T, c * d.T), [0, 2, 5, 7]) + assert _remove_trivial_dims(_array_tensor_product(a.T, I, b.T, c, d, I)) == ( + _array_tensor_product(a.T, I, b*c.T, d, I), [4, 7]) + + # Addition: + + cg = ArrayAdd(_array_tensor_product(a, b), _array_tensor_product(c, d)) + assert _remove_trivial_dims(cg) == (a * b.T + c * d.T, [1, 3]) + + # Permute Dims: + + cg = PermuteDims(_array_tensor_product(a, b), Permutation(3)(1, 2)) + assert _remove_trivial_dims(cg) == (a * b.T, [2, 3]) + + cg = PermuteDims(_array_tensor_product(a, I, b), Permutation(5)(1, 2, 3, 4)) + assert _remove_trivial_dims(cg) == (cg, []) + + cg = PermuteDims(_array_tensor_product(I, b, a), Permutation(5)(1, 2, 4, 5, 3)) + assert _remove_trivial_dims(cg) == (PermuteDims(_array_tensor_product(I, b * a.T), [0, 2, 3, 1]), [4, 5]) + + # Diagonal: + + cg = _array_diagonal(_array_tensor_product(M, a), (1, 2)) + assert _remove_trivial_dims(cg) == (cg, []) + + # Contraction: + + cg = _array_contraction(_array_tensor_product(M, a), (1, 2)) + assert _remove_trivial_dims(cg) == (cg, []) + + # A few more cases to test the removal and shift of nested removed axes + # with array contractions and array diagonals: + tp = _array_tensor_product( + OneMatrix(1, 1), + M, + x, + OneMatrix(1, 1), + Identity(1), + ) + + expr = _array_contraction(tp, (1, 8)) + rexpr, removed = _remove_trivial_dims(expr) + assert removed == [0, 5, 6, 7] + + expr = _array_contraction(tp, (1, 8), (3, 4)) + rexpr, removed = _remove_trivial_dims(expr) + assert removed == [0, 3, 4, 5] + + expr = _array_diagonal(tp, (1, 8)) + rexpr, removed = _remove_trivial_dims(expr) + assert removed == [0, 5, 6, 7, 8] + + expr = _array_diagonal(tp, (1, 8), (3, 4)) + rexpr, removed = _remove_trivial_dims(expr) + assert removed == [0, 3, 4, 5, 6] + + expr = _array_diagonal(_array_contraction(_array_tensor_product(A, x, I, I1), (1, 2, 5)), (1, 4)) + rexpr, removed = _remove_trivial_dims(expr) + assert removed == [2, 3] + + cg = _array_diagonal(_array_tensor_product(PermuteDims(_array_tensor_product(x, I1), Permutation(1, 2, 3)), (x.T*x).applyfunc(sqrt)), (2, 4), (3, 5)) + rexpr, removed = _remove_trivial_dims(cg) + assert removed == [1, 2] + + # Contractions with identity matrices need to be followed by a permutation + # in order + cg = _array_contraction(_array_tensor_product(A, B, C, M, I), (1, 8)) + ret, removed = _remove_trivial_dims(cg) + assert ret == PermuteDims(_array_tensor_product(A, B, C, M), [0, 2, 3, 4, 5, 6, 7, 1]) + assert removed == [] + + cg = _array_contraction(_array_tensor_product(A, B, C, M, I), (1, 8), (3, 4)) + ret, removed = _remove_trivial_dims(cg) + assert ret == PermuteDims(_array_contraction(_array_tensor_product(A, B, C, M), (3, 4)), [0, 2, 3, 4, 5, 1]) + assert removed == [] + + # Trivial matrices are sometimes inserted into MatMul expressions: + + cg = _array_tensor_product(b*b.T, a.T*a) + ret, removed = _remove_trivial_dims(cg) + assert ret == b*a.T*a*b.T + assert removed == [2, 3] + + Xs = ArraySymbol("X", (3, 2, k)) + cg = _array_tensor_product(M, Xs, b.T*c, a*a.T, b*b.T, c.T*d) + ret, removed = _remove_trivial_dims(cg) + assert ret == _array_tensor_product(M, Xs, a*b.T*c*c.T*d*a.T, b*b.T) + assert removed == [5, 6, 11, 12] + + cg = _array_diagonal(_array_tensor_product(I, I1, x), (1, 4), (3, 5)) + assert _remove_trivial_dims(cg) == (PermuteDims(_array_diagonal(_array_tensor_product(I, x), (1, 2)), Permutation(1, 2)), [1]) + + expr = _array_diagonal(_array_tensor_product(x, I, y), (0, 2)) + assert _remove_trivial_dims(expr) == (PermuteDims(_array_tensor_product(DiagMatrix(x), y), [1, 2, 3, 0]), [0]) + + expr = _array_diagonal(_array_tensor_product(x, I, y), (0, 2), (3, 4)) + assert _remove_trivial_dims(expr) == (expr, []) + + +def test_arrayexpr_convert_array_to_matrix_diag2contraction_diagmatrix(): + cg = _array_diagonal(_array_tensor_product(M, a), (1, 2)) + res = _array_diag2contr_diagmatrix(cg) + assert res.shape == cg.shape + assert res == _array_contraction(_array_tensor_product(M, OneArray(1), DiagMatrix(a)), (1, 3)) + + raises(ValueError, lambda: _array_diagonal(_array_tensor_product(a, M), (1, 2))) + + cg = _array_diagonal(_array_tensor_product(a.T, M), (1, 2)) + res = _array_diag2contr_diagmatrix(cg) + assert res.shape == cg.shape + assert res == _array_contraction(_array_tensor_product(OneArray(1), M, DiagMatrix(a.T)), (1, 4)) + + cg = _array_diagonal(_array_tensor_product(a.T, M, N, b.T), (1, 2), (4, 7)) + res = _array_diag2contr_diagmatrix(cg) + assert res.shape == cg.shape + assert res == _array_contraction( + _array_tensor_product(OneArray(1), M, N, OneArray(1), DiagMatrix(a.T), DiagMatrix(b.T)), (1, 7), (3, 9)) + + cg = _array_diagonal(_array_tensor_product(a, M, N, b.T), (0, 2), (4, 7)) + res = _array_diag2contr_diagmatrix(cg) + assert res.shape == cg.shape + assert res == _array_contraction( + _array_tensor_product(OneArray(1), M, N, OneArray(1), DiagMatrix(a), DiagMatrix(b.T)), (1, 6), (3, 9)) + + cg = _array_diagonal(_array_tensor_product(a, M, N, b.T), (0, 4), (3, 7)) + res = _array_diag2contr_diagmatrix(cg) + assert res.shape == cg.shape + assert res == _array_contraction( + _array_tensor_product(OneArray(1), M, N, OneArray(1), DiagMatrix(a), DiagMatrix(b.T)), (3, 6), (2, 9)) + + I1 = Identity(1) + x = MatrixSymbol("x", k, 1) + A = MatrixSymbol("A", k, k) + cg = _array_diagonal(_array_tensor_product(x, A.T, I1), (0, 2)) + assert _array_diag2contr_diagmatrix(cg).shape == cg.shape + assert _array2matrix(cg).shape == cg.shape + + +def test_arrayexpr_convert_array_to_matrix_support_function(): + + assert _support_function_tp1_recognize([], [2 * k]) == 2 * k + + assert _support_function_tp1_recognize([(1, 2)], [A, 2 * k, B, 3]) == 6 * k * A * B + + assert _support_function_tp1_recognize([(0, 3), (1, 2)], [A, B]) == Trace(A * B) + + assert _support_function_tp1_recognize([(1, 2)], [A, B]) == A * B + assert _support_function_tp1_recognize([(0, 2)], [A, B]) == A.T * B + assert _support_function_tp1_recognize([(1, 3)], [A, B]) == A * B.T + assert _support_function_tp1_recognize([(0, 3)], [A, B]) == A.T * B.T + + assert _support_function_tp1_recognize([(1, 2), (5, 6)], [A, B, C, D]) == _array_tensor_product(A * B, C * D) + assert _support_function_tp1_recognize([(1, 4), (3, 6)], [A, B, C, D]) == PermuteDims( + _array_tensor_product(A * C, B * D), [0, 2, 1, 3]) + + assert _support_function_tp1_recognize([(0, 3), (1, 4)], [A, B, C]) == B * A * C + + assert _support_function_tp1_recognize([(9, 10), (1, 2), (5, 6), (3, 4), (7, 8)], + [X, Y, A, B, C, D]) == X * Y * A * B * C * D + + assert _support_function_tp1_recognize([(9, 10), (1, 2), (5, 6), (3, 4)], + [X, Y, A, B, C, D]) == _array_tensor_product(X * Y * A * B, C * D) + + assert _support_function_tp1_recognize([(1, 7), (3, 8), (4, 11)], [X, Y, A, B, C, D]) == PermuteDims( + _array_tensor_product(X * B.T, Y * C, A.T * D.T), [0, 2, 4, 1, 3, 5] + ) + + assert _support_function_tp1_recognize([(0, 1), (3, 6), (5, 8)], [X, A, B, C, D]) == PermuteDims( + _array_tensor_product(Trace(X) * A * C, B * D), [0, 2, 1, 3]) + + assert _support_function_tp1_recognize([(1, 2), (3, 4), (5, 6), (7, 8)], [A, A, B, C, D]) == A ** 2 * B * C * D + assert _support_function_tp1_recognize([(1, 2), (3, 4), (5, 6), (7, 8)], [X, A, B, C, D]) == X * A * B * C * D + + assert _support_function_tp1_recognize([(1, 6), (3, 8), (5, 10)], [X, Y, A, B, C, D]) == PermuteDims( + _array_tensor_product(X * B, Y * C, A * D), [0, 2, 4, 1, 3, 5] + ) + + assert _support_function_tp1_recognize([(1, 4), (3, 6)], [A, B, C, D]) == PermuteDims( + _array_tensor_product(A * C, B * D), [0, 2, 1, 3]) + + assert _support_function_tp1_recognize([(0, 4), (1, 7), (2, 5), (3, 8)], [X, A, B, C, D]) == C*X.T*B*A*D + + assert _support_function_tp1_recognize([(0, 4), (1, 7), (2, 5), (3, 8)], [X, A, B, C, D]) == C*X.T*B*A*D + + +def test_convert_array_to_hadamard_products(): + + expr = HadamardProduct(M, N) + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert ret == expr + + expr = HadamardProduct(M, N)*P + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert ret == expr + + expr = Q*HadamardProduct(M, N)*P + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert ret == expr + + expr = Q*HadamardProduct(M, N.T)*P + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert ret == expr + + expr = HadamardProduct(M, N)*HadamardProduct(Q, P) + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert expr == ret + + expr = P.T*HadamardProduct(M, N)*HadamardProduct(Q, P) + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert expr == ret + + # ArrayDiagonal should be converted + cg = _array_diagonal(_array_tensor_product(M, N, Q), (1, 3), (0, 2, 4)) + ret = convert_array_to_matrix(cg) + expected = PermuteDims(_array_diagonal(_array_tensor_product(HadamardProduct(M.T, N.T), Q), (1, 2)), [1, 0, 2]) + assert expected == ret + + # Special case that should return the same expression: + cg = _array_diagonal(_array_tensor_product(HadamardProduct(M, N), Q), (0, 2)) + ret = convert_array_to_matrix(cg) + assert ret == cg + + # Hadamard products with traces: + + expr = Trace(HadamardProduct(M, N)) + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert ret == Trace(HadamardProduct(M.T, N.T)) + + expr = Trace(A*HadamardProduct(M, N)) + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert ret == Trace(HadamardProduct(M, N)*A) + + expr = Trace(HadamardProduct(A, M)*N) + cg = convert_matrix_to_array(expr) + ret = convert_array_to_matrix(cg) + assert ret == Trace(HadamardProduct(M.T, N)*A) + + # These should not be converted into Hadamard products: + + cg = _array_diagonal(_array_tensor_product(M, N), (0, 1, 2, 3)) + ret = convert_array_to_matrix(cg) + assert ret == cg + + cg = _array_diagonal(_array_tensor_product(A), (0, 1)) + ret = convert_array_to_matrix(cg) + assert ret == cg + + cg = _array_diagonal(_array_tensor_product(M, N, P), (0, 2, 4), (1, 3, 5)) + assert convert_array_to_matrix(cg) == HadamardProduct(M, N, P) + + cg = _array_diagonal(_array_tensor_product(M, N, P), (0, 3, 4), (1, 2, 5)) + assert convert_array_to_matrix(cg) == HadamardProduct(M, P, N.T) + + cg = _array_diagonal(_array_tensor_product(I, I1, x), (1, 4), (3, 5)) + assert convert_array_to_matrix(cg) == DiagMatrix(x) + + +def test_identify_removable_identity_matrices(): + + D = DiagonalMatrix(MatrixSymbol("D", k, k)) + + cg = _array_contraction(_array_tensor_product(A, B, I), (1, 2, 4, 5)) + expected = _array_contraction(_array_tensor_product(A, B), (1, 2)) + assert identify_removable_identity_matrices(cg) == expected + + cg = _array_contraction(_array_tensor_product(A, B, C, I), (1, 3, 5, 6, 7)) + expected = _array_contraction(_array_tensor_product(A, B, C), (1, 3, 5)) + assert identify_removable_identity_matrices(cg) == expected + + # Tests with diagonal matrices: + + cg = _array_contraction(_array_tensor_product(A, B, D), (1, 2, 4, 5)) + ret = identify_removable_identity_matrices(cg) + expected = _array_contraction(_array_tensor_product(A, B, D), (1, 4), (2, 5)) + assert ret == expected + + cg = _array_contraction(_array_tensor_product(A, B, D, M, N), (1, 2, 4, 5, 6, 8)) + ret = identify_removable_identity_matrices(cg) + assert ret == cg + + +def test_combine_removed(): + + assert _combine_removed(6, [0, 1, 2], [0, 1, 2]) == [0, 1, 2, 3, 4, 5] + assert _combine_removed(8, [2, 5], [1, 3, 4]) == [1, 2, 4, 5, 6] + assert _combine_removed(8, [7], []) == [7] + + +def test_array_contraction_to_diagonal_multiple_identities(): + + expr = _array_contraction(_array_tensor_product(A, B, I, C), (1, 2, 4), (5, 6)) + assert _array_contraction_to_diagonal_multiple_identity(expr) == (expr, []) + assert convert_array_to_matrix(expr) == _array_contraction(_array_tensor_product(A, B, C), (1, 2, 4)) + + expr = _array_contraction(_array_tensor_product(A, I, I), (1, 2, 4)) + assert _array_contraction_to_diagonal_multiple_identity(expr) == (A, [2]) + assert convert_array_to_matrix(expr) == A + + expr = _array_contraction(_array_tensor_product(A, I, I, B), (1, 2, 4), (3, 6)) + assert _array_contraction_to_diagonal_multiple_identity(expr) == (expr, []) + + expr = _array_contraction(_array_tensor_product(A, I, I, B), (1, 2, 3, 4, 6)) + assert _array_contraction_to_diagonal_multiple_identity(expr) == (expr, []) + + +def test_convert_array_element_to_matrix(): + + expr = ArrayElement(M, (i, j)) + assert convert_array_to_matrix(expr) == MatrixElement(M, i, j) + + expr = ArrayElement(_array_contraction(_array_tensor_product(M, N), (1, 3)), (i, j)) + assert convert_array_to_matrix(expr) == MatrixElement(M*N.T, i, j) + + expr = ArrayElement(_array_tensor_product(M, N), (i, j, m, n)) + assert convert_array_to_matrix(expr) == expr + + +def test_convert_array_elementwise_function_to_matrix(): + + d = Dummy("d") + + expr = ArrayElementwiseApplyFunc(Lambda(d, sin(d)), x.T*y) + assert convert_array_to_matrix(expr) == sin(x.T*y) + + expr = ArrayElementwiseApplyFunc(Lambda(d, d**2), x.T*y) + assert convert_array_to_matrix(expr) == (x.T*y)**2 + + expr = ArrayElementwiseApplyFunc(Lambda(d, sin(d)), x) + assert convert_array_to_matrix(expr).dummy_eq(x.applyfunc(sin)) + + expr = ArrayElementwiseApplyFunc(Lambda(d, 1 / (2 * sqrt(d))), x) + assert convert_array_to_matrix(expr) == S.Half * HadamardPower(x, -S.Half) + + +def test_array2matrix(): + # See issue https://github.com/sympy/sympy/pull/22877 + expr = PermuteDims(ArrayContraction(ArrayTensorProduct(x, I, I1, x), (0, 3), (1, 7)), Permutation(2, 3)) + expected = PermuteDims(ArrayTensorProduct(x*x.T, I1), Permutation(3)(1, 2)) + assert _array2matrix(expr) == expected + + +def test_recognize_broadcasting(): + expr = ArrayTensorProduct(x.T*x, A) + assert _remove_trivial_dims(expr) == (KroneckerProduct(x.T*x, A), [0, 1]) + + expr = ArrayTensorProduct(A, x.T*x) + assert _remove_trivial_dims(expr) == (KroneckerProduct(A, x.T*x), [2, 3]) + + expr = ArrayTensorProduct(A, B, x.T*x, C) + assert _remove_trivial_dims(expr) == (ArrayTensorProduct(A, KroneckerProduct(B, x.T*x), C), [4, 5]) + + # Always prefer matrix multiplication to Kronecker product, if possible: + expr = ArrayTensorProduct(a, b, x.T*x) + assert _remove_trivial_dims(expr) == (a*x.T*x*b.T, [1, 3, 4, 5]) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_indexed_to_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_indexed_to_array.py new file mode 100644 index 0000000000000000000000000000000000000000..258062eadeca041ae3c864dabeefd5165f1cef11 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_indexed_to_array.py @@ -0,0 +1,205 @@ +from sympy import tanh +from sympy.concrete.summations import Sum +from sympy.core.symbol import symbols +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import Identity +from sympy.tensor.array.expressions import ArrayElementwiseApplyFunc +from sympy.tensor.indexed import IndexedBase +from sympy.combinatorics import Permutation +from sympy.tensor.array.expressions.array_expressions import ArrayContraction, ArrayTensorProduct, \ + ArrayDiagonal, ArrayAdd, PermuteDims, ArrayElement, _array_tensor_product, _array_contraction, _array_diagonal, \ + _array_add, _permute_dims, ArraySymbol, OneArray +from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix +from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array, _convert_indexed_to_array +from sympy.testing.pytest import raises + + +A, B = symbols("A B", cls=IndexedBase) +i, j, k, l, m, n = symbols("i j k l m n") +d0, d1, d2, d3 = symbols("d0:4") + +I = Identity(k) + +M = MatrixSymbol("M", k, k) +N = MatrixSymbol("N", k, k) +P = MatrixSymbol("P", k, k) +Q = MatrixSymbol("Q", k, k) + +a = MatrixSymbol("a", k, 1) +b = MatrixSymbol("b", k, 1) +c = MatrixSymbol("c", k, 1) +d = MatrixSymbol("d", k, 1) + + +def test_arrayexpr_convert_index_to_array_support_function(): + expr = M[i, j] + assert _convert_indexed_to_array(expr) == (M, (i, j)) + expr = M[i, j]*N[k, l] + assert _convert_indexed_to_array(expr) == (ArrayTensorProduct(M, N), (i, j, k, l)) + expr = M[i, j]*N[j, k] + assert _convert_indexed_to_array(expr) == (ArrayDiagonal(ArrayTensorProduct(M, N), (1, 2)), (i, k, j)) + expr = Sum(M[i, j]*N[j, k], (j, 0, k-1)) + assert _convert_indexed_to_array(expr) == (ArrayContraction(ArrayTensorProduct(M, N), (1, 2)), (i, k)) + expr = M[i, j] + N[i, j] + assert _convert_indexed_to_array(expr) == (ArrayAdd(M, N), (i, j)) + expr = M[i, j] + N[j, i] + assert _convert_indexed_to_array(expr) == (ArrayAdd(M, PermuteDims(N, Permutation([1, 0]))), (i, j)) + expr = M[i, j] + M[j, i] + assert _convert_indexed_to_array(expr) == (ArrayAdd(M, PermuteDims(M, Permutation([1, 0]))), (i, j)) + expr = (M*N*P)[i, j] + assert _convert_indexed_to_array(expr) == (_array_contraction(ArrayTensorProduct(M, N, P), (1, 2), (3, 4)), (i, j)) + expr = expr.function # Disregard summation in previous expression + ret1, ret2 = _convert_indexed_to_array(expr) + assert ret1 == ArrayDiagonal(ArrayTensorProduct(M, N, P), (1, 2), (3, 4)) + assert str(ret2) == "(i, j, _i_1, _i_2)" + expr = KroneckerDelta(i, j)*M[i, k] + assert _convert_indexed_to_array(expr) == (M, ({i, j}, k)) + expr = KroneckerDelta(i, j)*KroneckerDelta(j, k)*M[i, l] + assert _convert_indexed_to_array(expr) == (M, ({i, j, k}, l)) + expr = KroneckerDelta(j, k)*(M[i, j]*N[k, l] + N[i, j]*M[k, l]) + assert _convert_indexed_to_array(expr) == (_array_diagonal(_array_add( + ArrayTensorProduct(M, N), + _permute_dims(ArrayTensorProduct(M, N), Permutation(0, 2)(1, 3)) + ), (1, 2)), (i, l, frozenset({j, k}))) + expr = KroneckerDelta(j, m)*KroneckerDelta(m, k)*(M[i, j]*N[k, l] + N[i, j]*M[k, l]) + assert _convert_indexed_to_array(expr) == (_array_diagonal(_array_add( + ArrayTensorProduct(M, N), + _permute_dims(ArrayTensorProduct(M, N), Permutation(0, 2)(1, 3)) + ), (1, 2)), (i, l, frozenset({j, m, k}))) + expr = KroneckerDelta(i, j)*KroneckerDelta(j, k)*KroneckerDelta(k,m)*M[i, 0]*KroneckerDelta(m, n) + assert _convert_indexed_to_array(expr) == (M, ({i, j, k, m, n}, 0)) + expr = M[i, i] + assert _convert_indexed_to_array(expr) == (ArrayDiagonal(M, (0, 1)), (i,)) + + +def test_arrayexpr_convert_indexed_to_array_expression(): + + s = Sum(A[i]*B[i], (i, 0, 3)) + cg = convert_indexed_to_array(s) + assert cg == ArrayContraction(ArrayTensorProduct(A, B), (0, 1)) + + expr = M*N + result = ArrayContraction(ArrayTensorProduct(M, N), (1, 2)) + elem = expr[i, j] + assert convert_indexed_to_array(elem) == result + + expr = M*N*M + elem = expr[i, j] + result = _array_contraction(_array_tensor_product(M, M, N), (1, 4), (2, 5)) + cg = convert_indexed_to_array(elem) + assert cg == result + + cg = convert_indexed_to_array((M * N * P)[i, j]) + assert cg == _array_contraction(ArrayTensorProduct(M, N, P), (1, 2), (3, 4)) + + cg = convert_indexed_to_array((M * N.T * P)[i, j]) + assert cg == _array_contraction(ArrayTensorProduct(M, N, P), (1, 3), (2, 4)) + + expr = -2*M*N + elem = expr[i, j] + cg = convert_indexed_to_array(elem) + assert cg == ArrayContraction(ArrayTensorProduct(-2, M, N), (1, 2)) + + +def test_arrayexpr_convert_array_element_to_array_expression(): + A = ArraySymbol("A", (k,)) + B = ArraySymbol("B", (k,)) + + s = Sum(A[i]*B[i], (i, 0, k-1)) + cg = convert_indexed_to_array(s) + assert cg == ArrayContraction(ArrayTensorProduct(A, B), (0, 1)) + + s = A[i]*B[i] + cg = convert_indexed_to_array(s) + assert cg == ArrayDiagonal(ArrayTensorProduct(A, B), (0, 1)) + + s = A[i]*B[j] + cg = convert_indexed_to_array(s, [i, j]) + assert cg == ArrayTensorProduct(A, B) + cg = convert_indexed_to_array(s, [j, i]) + assert cg == ArrayTensorProduct(B, A) + + s = tanh(A[i]*B[j]) + cg = convert_indexed_to_array(s, [i, j]) + assert cg.dummy_eq(ArrayElementwiseApplyFunc(tanh, ArrayTensorProduct(A, B))) + + +def test_arrayexpr_convert_indexed_to_array_and_back_to_matrix(): + + expr = a.T*b + elem = expr[0, 0] + cg = convert_indexed_to_array(elem) + assert cg == ArrayElement(ArrayContraction(ArrayTensorProduct(a, b), (0, 2)), [0, 0]) + + expr = M[i,j] + N[i,j] + p1, p2 = _convert_indexed_to_array(expr) + assert convert_array_to_matrix(p1) == M + N + + expr = M[i,j] + N[j,i] + p1, p2 = _convert_indexed_to_array(expr) + assert convert_array_to_matrix(p1) == M + N.T + + expr = M[i,j]*N[k,l] + N[i,j]*M[k,l] + p1, p2 = _convert_indexed_to_array(expr) + assert convert_array_to_matrix(p1) == ArrayAdd( + ArrayTensorProduct(M, N), + ArrayTensorProduct(N, M)) + + expr = (M*N*P)[i, j] + p1, p2 = _convert_indexed_to_array(expr) + assert convert_array_to_matrix(p1) == M * N * P + + expr = Sum(M[i,j]*(N*P)[j,m], (j, 0, k-1)) + p1, p2 = _convert_indexed_to_array(expr) + assert convert_array_to_matrix(p1) == M * N * P + + expr = Sum((P[j, m] + P[m, j])*(M[i,j]*N[m,n] + N[i,j]*M[m,n]), (j, 0, k-1), (m, 0, k-1)) + p1, p2 = _convert_indexed_to_array(expr) + assert convert_array_to_matrix(p1) == M * P * N + M * P.T * N + N * P * M + N * P.T * M + + +def test_arrayexpr_convert_indexed_to_array_out_of_bounds(): + + expr = Sum(M[i, i], (i, 0, 4)) + raises(ValueError, lambda: convert_indexed_to_array(expr)) + expr = Sum(M[i, i], (i, 0, k)) + raises(ValueError, lambda: convert_indexed_to_array(expr)) + expr = Sum(M[i, i], (i, 1, k-1)) + raises(ValueError, lambda: convert_indexed_to_array(expr)) + + expr = Sum(M[i, j]*N[j,m], (j, 0, 4)) + raises(ValueError, lambda: convert_indexed_to_array(expr)) + expr = Sum(M[i, j]*N[j,m], (j, 0, k)) + raises(ValueError, lambda: convert_indexed_to_array(expr)) + expr = Sum(M[i, j]*N[j,m], (j, 1, k-1)) + raises(ValueError, lambda: convert_indexed_to_array(expr)) + + +def test_arrayexpr_convert_indexed_to_array_broadcast(): + A = ArraySymbol("A", (3, 3)) + B = ArraySymbol("B", (3, 3)) + + expr = A[i, j] + B[k, l] + O2 = OneArray(3, 3) + expected = ArrayAdd(ArrayTensorProduct(A, O2), ArrayTensorProduct(O2, B)) + assert convert_indexed_to_array(expr) == expected + assert convert_indexed_to_array(expr, [i, j, k, l]) == expected + assert convert_indexed_to_array(expr, [l, k, i, j]) == ArrayAdd(PermuteDims(ArrayTensorProduct(O2, A), [1, 0, 2, 3]), PermuteDims(ArrayTensorProduct(B, O2), [1, 0, 2, 3])) + + expr = A[i, j] + B[j, k] + O1 = OneArray(3) + assert convert_indexed_to_array(expr, [i, j, k]) == ArrayAdd(ArrayTensorProduct(A, O1), ArrayTensorProduct(O1, B)) + + C = ArraySymbol("C", (d0, d1)) + D = ArraySymbol("D", (d3, d1)) + + expr = C[i, j] + D[k, j] + assert convert_indexed_to_array(expr, [i, j, k]) == ArrayAdd(ArrayTensorProduct(C, OneArray(d3)), PermuteDims(ArrayTensorProduct(OneArray(d0), D), [0, 2, 1])) + + X = ArraySymbol("X", (5, 3)) + + expr = X[i, n] - X[j, n] + assert convert_indexed_to_array(expr, [i, j, n]) == ArrayAdd(ArrayTensorProduct(-1, OneArray(5), X), PermuteDims(ArrayTensorProduct(X, OneArray(5)), [0, 2, 1])) + + raises(ValueError, lambda: convert_indexed_to_array(C[i, j] + D[i, j])) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_matrix_to_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_matrix_to_array.py new file mode 100644 index 0000000000000000000000000000000000000000..142585882588df6aa0e4648d9d8881ea755f42a0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_convert_matrix_to_array.py @@ -0,0 +1,128 @@ +from sympy import Lambda, KroneckerProduct +from sympy.core.symbol import symbols, Dummy +from sympy.matrices.expressions.hadamard import (HadamardPower, HadamardProduct) +from sympy.matrices.expressions.inverse import Inverse +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.matpow import MatPow +from sympy.matrices.expressions.special import Identity +from sympy.matrices.expressions.trace import Trace +from sympy.matrices.expressions.transpose import Transpose +from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayContraction, \ + PermuteDims, ArrayDiagonal, ArrayElementwiseApplyFunc, _array_contraction, _array_tensor_product, Reshape +from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix +from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array + +i, j, k, l, m, n = symbols("i j k l m n") + +I = Identity(k) + +M = MatrixSymbol("M", k, k) +N = MatrixSymbol("N", k, k) +P = MatrixSymbol("P", k, k) +Q = MatrixSymbol("Q", k, k) + +A = MatrixSymbol("A", k, k) +B = MatrixSymbol("B", k, k) +C = MatrixSymbol("C", k, k) +D = MatrixSymbol("D", k, k) + +X = MatrixSymbol("X", k, k) +Y = MatrixSymbol("Y", k, k) + +a = MatrixSymbol("a", k, 1) +b = MatrixSymbol("b", k, 1) +c = MatrixSymbol("c", k, 1) +d = MatrixSymbol("d", k, 1) + + +def test_arrayexpr_convert_matrix_to_array(): + + expr = M*N + result = ArrayContraction(ArrayTensorProduct(M, N), (1, 2)) + assert convert_matrix_to_array(expr) == result + + expr = M*N*M + result = _array_contraction(ArrayTensorProduct(M, N, M), (1, 2), (3, 4)) + assert convert_matrix_to_array(expr) == result + + expr = Transpose(M) + assert convert_matrix_to_array(expr) == PermuteDims(M, [1, 0]) + + expr = M*Transpose(N) + assert convert_matrix_to_array(expr) == _array_contraction(_array_tensor_product(M, PermuteDims(N, [1, 0])), (1, 2)) + + expr = 3*M*N + res = convert_matrix_to_array(expr) + rexpr = convert_array_to_matrix(res) + assert expr == rexpr + + expr = 3*M + N*M.T*M + 4*k*N + res = convert_matrix_to_array(expr) + rexpr = convert_array_to_matrix(res) + assert expr == rexpr + + expr = Inverse(M)*N + rexpr = convert_array_to_matrix(convert_matrix_to_array(expr)) + assert expr == rexpr + + expr = M**2 + rexpr = convert_array_to_matrix(convert_matrix_to_array(expr)) + assert expr == rexpr + + expr = M*(2*N + 3*M) + res = convert_matrix_to_array(expr) + rexpr = convert_array_to_matrix(res) + assert expr == rexpr + + expr = Trace(M) + result = ArrayContraction(M, (0, 1)) + assert convert_matrix_to_array(expr) == result + + expr = 3*Trace(M) + result = ArrayContraction(ArrayTensorProduct(3, M), (0, 1)) + assert convert_matrix_to_array(expr) == result + + expr = 3*Trace(Trace(M) * M) + result = ArrayContraction(ArrayTensorProduct(3, M, M), (0, 1), (2, 3)) + assert convert_matrix_to_array(expr) == result + + expr = 3*Trace(M)**2 + result = ArrayContraction(ArrayTensorProduct(3, M, M), (0, 1), (2, 3)) + assert convert_matrix_to_array(expr) == result + + expr = HadamardProduct(M, N) + result = ArrayDiagonal(ArrayTensorProduct(M, N), (0, 2), (1, 3)) + assert convert_matrix_to_array(expr) == result + + expr = HadamardProduct(M*N, N*M) + result = ArrayDiagonal(ArrayContraction(ArrayTensorProduct(M, N, N, M), (1, 2), (5, 6)), (0, 2), (1, 3)) + assert convert_matrix_to_array(expr) == result + + expr = HadamardPower(M, 2) + result = ArrayDiagonal(ArrayTensorProduct(M, M), (0, 2), (1, 3)) + assert convert_matrix_to_array(expr) == result + + expr = HadamardPower(M*N, 2) + result = ArrayDiagonal(ArrayContraction(ArrayTensorProduct(M, N, M, N), (1, 2), (5, 6)), (0, 2), (1, 3)) + assert convert_matrix_to_array(expr) == result + + expr = HadamardPower(M, n) + d0 = Dummy("d0") + result = ArrayElementwiseApplyFunc(Lambda(d0, d0**n), M) + assert convert_matrix_to_array(expr).dummy_eq(result) + + expr = M**2 + assert isinstance(expr, MatPow) + assert convert_matrix_to_array(expr) == ArrayContraction(ArrayTensorProduct(M, M), (1, 2)) + + expr = a.T*b + cg = convert_matrix_to_array(expr) + assert cg == ArrayContraction(ArrayTensorProduct(a, b), (0, 2)) + + expr = KroneckerProduct(A, B) + cg = convert_matrix_to_array(expr) + assert cg == Reshape(PermuteDims(ArrayTensorProduct(A, B), [0, 2, 1, 3]), (k**2, k**2)) + + expr = KroneckerProduct(A, B, C, D) + cg = convert_matrix_to_array(expr) + assert cg == Reshape(PermuteDims(ArrayTensorProduct(A, B, C, D), [0, 2, 4, 6, 1, 3, 5, 7]), (k**4, k**4)) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_deprecated_conv_modules.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_deprecated_conv_modules.py new file mode 100644 index 0000000000000000000000000000000000000000..b41b6105410a308e7774fce760b235497d0303bb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/tests/test_deprecated_conv_modules.py @@ -0,0 +1,22 @@ +from sympy import MatrixSymbol, symbols, Sum +from sympy.tensor.array.expressions import conv_array_to_indexed, from_array_to_indexed, ArrayTensorProduct, \ + ArrayContraction, conv_array_to_matrix, from_array_to_matrix, conv_matrix_to_array, from_matrix_to_array, \ + conv_indexed_to_array, from_indexed_to_array +from sympy.testing.pytest import warns +from sympy.utilities.exceptions import SymPyDeprecationWarning + + +def test_deprecated_conv_module_results(): + + M = MatrixSymbol("M", 3, 3) + N = MatrixSymbol("N", 3, 3) + i, j, d = symbols("i j d") + + x = ArrayContraction(ArrayTensorProduct(M, N), (1, 2)) + y = Sum(M[i, d]*N[d, j], (d, 0, 2)) + + with warns(SymPyDeprecationWarning, test_stacklevel=False): + assert conv_array_to_indexed.convert_array_to_indexed(x, [i, j]).dummy_eq(from_array_to_indexed.convert_array_to_indexed(x, [i, j])) + assert conv_array_to_matrix.convert_array_to_matrix(x) == from_array_to_matrix.convert_array_to_matrix(x) + assert conv_matrix_to_array.convert_matrix_to_array(M*N) == from_matrix_to_array.convert_matrix_to_array(M*N) + assert conv_indexed_to_array.convert_indexed_to_array(y) == from_indexed_to_array.convert_indexed_to_array(y) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/utils.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e55c0e6ed47cdc9ff1c24cc92f006998aeb86822 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/expressions/utils.py @@ -0,0 +1,123 @@ +import bisect +from collections import defaultdict + +from sympy.combinatorics import Permutation +from sympy.core.containers import Tuple +from sympy.core.numbers import Integer + + +def _get_mapping_from_subranks(subranks): + mapping = {} + counter = 0 + for i, rank in enumerate(subranks): + for j in range(rank): + mapping[counter] = (i, j) + counter += 1 + return mapping + + +def _get_contraction_links(args, subranks, *contraction_indices): + mapping = _get_mapping_from_subranks(subranks) + contraction_tuples = [[mapping[j] for j in i] for i in contraction_indices] + dlinks = defaultdict(dict) + for links in contraction_tuples: + if len(links) == 2: + (arg1, pos1), (arg2, pos2) = links + dlinks[arg1][pos1] = (arg2, pos2) + dlinks[arg2][pos2] = (arg1, pos1) + continue + + return args, dict(dlinks) + + +def _sort_contraction_indices(pairing_indices): + pairing_indices = [Tuple(*sorted(i)) for i in pairing_indices] + pairing_indices.sort(key=lambda x: min(x)) + return pairing_indices + + +def _get_diagonal_indices(flattened_indices): + axes_contraction = defaultdict(list) + for i, ind in enumerate(flattened_indices): + if isinstance(ind, (int, Integer)): + # If the indices is a number, there can be no diagonal operation: + continue + axes_contraction[ind].append(i) + axes_contraction = {k: v for k, v in axes_contraction.items() if len(v) > 1} + # Put the diagonalized indices at the end: + ret_indices = [i for i in flattened_indices if i not in axes_contraction] + diag_indices = list(axes_contraction) + diag_indices.sort(key=lambda x: flattened_indices.index(x)) + diagonal_indices = [tuple(axes_contraction[i]) for i in diag_indices] + ret_indices += diag_indices + ret_indices = tuple(ret_indices) + return diagonal_indices, ret_indices + + +def _get_argindex(subindices, ind): + for i, sind in enumerate(subindices): + if ind == sind: + return i + if isinstance(sind, (set, frozenset)) and ind in sind: + return i + raise IndexError("%s not found in %s" % (ind, subindices)) + + +def _apply_recursively_over_nested_lists(func, arr): + if isinstance(arr, (tuple, list, Tuple)): + return tuple(_apply_recursively_over_nested_lists(func, i) for i in arr) + elif isinstance(arr, Tuple): + return Tuple.fromiter(_apply_recursively_over_nested_lists(func, i) for i in arr) + else: + return func(arr) + + +def _build_push_indices_up_func_transformation(flattened_contraction_indices): + shifts = {0: 0} + i = 0 + cumulative = 0 + while i < len(flattened_contraction_indices): + j = 1 + while i+j < len(flattened_contraction_indices): + if flattened_contraction_indices[i] + j != flattened_contraction_indices[i+j]: + break + j += 1 + cumulative += j + shifts[flattened_contraction_indices[i]] = cumulative + i += j + shift_keys = sorted(shifts.keys()) + + def func(idx): + return shifts[shift_keys[bisect.bisect_right(shift_keys, idx)-1]] + + def transform(j): + if j in flattened_contraction_indices: + return None + else: + return j - func(j) + + return transform + + +def _build_push_indices_down_func_transformation(flattened_contraction_indices): + N = flattened_contraction_indices[-1]+2 + + shifts = [i for i in range(N) if i not in flattened_contraction_indices] + + def transform(j): + if j < len(shifts): + return shifts[j] + else: + return j + shifts[-1] - len(shifts) + 1 + + return transform + + +def _apply_permutation_to_list(perm: Permutation, target_list: list): + """ + Permute a list according to the given permutation. + """ + new_list = [None for i in range(perm.size)] + for i, e in enumerate(target_list): + new_list[perm(i)] = e + return new_list diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/mutable_ndim_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/mutable_ndim_array.py new file mode 100644 index 0000000000000000000000000000000000000000..e1eaaf7241bc3b4a48234178d18da3aa5736e189 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/mutable_ndim_array.py @@ -0,0 +1,13 @@ +from sympy.tensor.array.ndim_array import NDimArray + + +class MutableNDimArray(NDimArray): + + def as_immutable(self): + raise NotImplementedError("abstract method") + + def as_mutable(self): + return self + + def _sympy_(self): + return self.as_immutable() diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/ndim_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/ndim_array.py new file mode 100644 index 0000000000000000000000000000000000000000..2b9a857b8cfd9ee46646c46f274636d6b9962b6e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/ndim_array.py @@ -0,0 +1,601 @@ +from sympy.core.basic import Basic +from sympy.core.containers import (Dict, Tuple) +from sympy.core.expr import Expr +from sympy.core.kind import Kind, NumberKind, UndefinedKind +from sympy.core.numbers import Integer +from sympy.core.singleton import S +from sympy.core.sympify import sympify +from sympy.external.gmpy import SYMPY_INTS +from sympy.printing.defaults import Printable + +import itertools +from collections.abc import Iterable + + +class ArrayKind(Kind): + """ + Kind for N-dimensional array in SymPy. + + This kind represents the multidimensional array that algebraic + operations are defined. Basic class for this kind is ``NDimArray``, + but any expression representing the array can have this. + + Parameters + ========== + + element_kind : Kind + Kind of the element. Default is :obj:NumberKind ``, + which means that the array contains only numbers. + + Examples + ======== + + Any instance of array class has ``ArrayKind``. + + >>> from sympy import NDimArray + >>> NDimArray([1,2,3]).kind + ArrayKind(NumberKind) + + Although expressions representing an array may be not instance of + array class, it will have ``ArrayKind`` as well. + + >>> from sympy import Integral + >>> from sympy.tensor.array import NDimArray + >>> from sympy.abc import x + >>> intA = Integral(NDimArray([1,2,3]), x) + >>> isinstance(intA, NDimArray) + False + >>> intA.kind + ArrayKind(NumberKind) + + Use ``isinstance()`` to check for ``ArrayKind` without specifying + the element kind. Use ``is`` with specifying the element kind. + + >>> from sympy.tensor.array import ArrayKind + >>> from sympy.core import NumberKind + >>> boolA = NDimArray([True, False]) + >>> isinstance(boolA.kind, ArrayKind) + True + >>> boolA.kind is ArrayKind(NumberKind) + False + + See Also + ======== + + shape : Function to return the shape of objects with ``MatrixKind``. + + """ + def __new__(cls, element_kind=NumberKind): + obj = super().__new__(cls, element_kind) + obj.element_kind = element_kind + return obj + + def __repr__(self): + return "ArrayKind(%s)" % self.element_kind + + @classmethod + def _union(cls, kinds) -> 'ArrayKind': + elem_kinds = {e.kind for e in kinds} + if len(elem_kinds) == 1: + elemkind, = elem_kinds + else: + elemkind = UndefinedKind + return ArrayKind(elemkind) + + +class NDimArray(Printable): + """N-dimensional array. + + Examples + ======== + + Create an N-dim array of zeros: + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray.zeros(2, 3, 4) + >>> a + [[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]] + + Create an N-dim array from a list; + + >>> a = MutableDenseNDimArray([[2, 3], [4, 5]]) + >>> a + [[2, 3], [4, 5]] + + >>> b = MutableDenseNDimArray([[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]]) + >>> b + [[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]] + + Create an N-dim array from a flat list with dimension shape: + + >>> a = MutableDenseNDimArray([1, 2, 3, 4, 5, 6], (2, 3)) + >>> a + [[1, 2, 3], [4, 5, 6]] + + Create an N-dim array from a matrix: + + >>> from sympy import Matrix + >>> a = Matrix([[1,2],[3,4]]) + >>> a + Matrix([ + [1, 2], + [3, 4]]) + >>> b = MutableDenseNDimArray(a) + >>> b + [[1, 2], [3, 4]] + + Arithmetic operations on N-dim arrays + + >>> a = MutableDenseNDimArray([1, 1, 1, 1], (2, 2)) + >>> b = MutableDenseNDimArray([4, 4, 4, 4], (2, 2)) + >>> c = a + b + >>> c + [[5, 5], [5, 5]] + >>> a - b + [[-3, -3], [-3, -3]] + + """ + + _diff_wrt = True + is_scalar = False + + def __new__(cls, iterable, shape=None, **kwargs): + from sympy.tensor.array import ImmutableDenseNDimArray + return ImmutableDenseNDimArray(iterable, shape, **kwargs) + + def __getitem__(self, index): + raise NotImplementedError("A subclass of NDimArray should implement __getitem__") + + def _parse_index(self, index): + if isinstance(index, (SYMPY_INTS, Integer)): + if index >= self._loop_size: + raise ValueError("Only a tuple index is accepted") + return index + + if self._loop_size == 0: + raise ValueError("Index not valid with an empty array") + + if len(index) != self._rank: + raise ValueError('Wrong number of array axes') + + real_index = 0 + # check if input index can exist in current indexing + for i in range(self._rank): + if (index[i] >= self.shape[i]) or (index[i] < -self.shape[i]): + raise ValueError('Index ' + str(index) + ' out of border') + if index[i] < 0: + real_index += 1 + real_index = real_index*self.shape[i] + index[i] + + return real_index + + def _get_tuple_index(self, integer_index): + index = [] + for sh in reversed(self.shape): + index.append(integer_index % sh) + integer_index //= sh + index.reverse() + return tuple(index) + + def _check_symbolic_index(self, index): + # Check if any index is symbolic: + tuple_index = (index if isinstance(index, tuple) else (index,)) + if any((isinstance(i, Expr) and (not i.is_number)) for i in tuple_index): + for i, nth_dim in zip(tuple_index, self.shape): + if ((i < 0) == True) or ((i >= nth_dim) == True): + raise ValueError("index out of range") + from sympy.tensor import Indexed + return Indexed(self, *tuple_index) + return None + + def _setter_iterable_check(self, value): + from sympy.matrices.matrixbase import MatrixBase + if isinstance(value, (Iterable, MatrixBase, NDimArray)): + raise NotImplementedError + + @classmethod + def _scan_iterable_shape(cls, iterable): + def f(pointer): + if not isinstance(pointer, Iterable): + return [pointer], () + + if len(pointer) == 0: + return [], (0,) + + result = [] + elems, shapes = zip(*[f(i) for i in pointer]) + if len(set(shapes)) != 1: + raise ValueError("could not determine shape unambiguously") + for i in elems: + result.extend(i) + return result, (len(shapes),)+shapes[0] + + return f(iterable) + + @classmethod + def _handle_ndarray_creation_inputs(cls, iterable=None, shape=None, **kwargs): + from sympy.matrices.matrixbase import MatrixBase + from sympy.tensor.array import SparseNDimArray + + if shape is None: + if iterable is None: + shape = () + iterable = () + # Construction of a sparse array from a sparse array + elif isinstance(iterable, SparseNDimArray): + return iterable._shape, iterable._sparse_array + + # Construct N-dim array from another N-dim array: + elif isinstance(iterable, NDimArray): + shape = iterable.shape + + # Construct N-dim array from an iterable (numpy arrays included): + elif isinstance(iterable, Iterable): + iterable, shape = cls._scan_iterable_shape(iterable) + + # Construct N-dim array from a Matrix: + elif isinstance(iterable, MatrixBase): + shape = iterable.shape + + else: + shape = () + iterable = (iterable,) + + if isinstance(iterable, (Dict, dict)) and shape is not None: + new_dict = iterable.copy() + for k in new_dict: + if isinstance(k, (tuple, Tuple)): + new_key = 0 + for i, idx in enumerate(k): + new_key = new_key * shape[i] + idx + iterable[new_key] = iterable[k] + del iterable[k] + + if isinstance(shape, (SYMPY_INTS, Integer)): + shape = (shape,) + + if not all(isinstance(dim, (SYMPY_INTS, Integer)) for dim in shape): + raise TypeError("Shape should contain integers only.") + + return tuple(shape), iterable + + def __len__(self): + """Overload common function len(). Returns number of elements in array. + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray.zeros(3, 3) + >>> a + [[0, 0, 0], [0, 0, 0], [0, 0, 0]] + >>> len(a) + 9 + + """ + return self._loop_size + + @property + def shape(self): + """ + Returns array shape (dimension). + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray.zeros(3, 3) + >>> a.shape + (3, 3) + + """ + return self._shape + + def rank(self): + """ + Returns rank of array. + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray.zeros(3,4,5,6,3) + >>> a.rank() + 5 + + """ + return self._rank + + def diff(self, *args, **kwargs): + """ + Calculate the derivative of each element in the array. + + Examples + ======== + + >>> from sympy import ImmutableDenseNDimArray + >>> from sympy.abc import x, y + >>> M = ImmutableDenseNDimArray([[x, y], [1, x*y]]) + >>> M.diff(x) + [[1, 0], [0, y]] + + """ + from sympy.tensor.array.array_derivatives import ArrayDerivative + kwargs.setdefault('evaluate', True) + return ArrayDerivative(self.as_immutable(), *args, **kwargs) + + def _eval_derivative(self, base): + # Types are (base: scalar, self: array) + return self.applyfunc(lambda x: base.diff(x)) + + def _eval_derivative_n_times(self, s, n): + return Basic._eval_derivative_n_times(self, s, n) + + def applyfunc(self, f): + """Apply a function to each element of the N-dim array. + + Examples + ======== + + >>> from sympy import ImmutableDenseNDimArray + >>> m = ImmutableDenseNDimArray([i*2+j for i in range(2) for j in range(2)], (2, 2)) + >>> m + [[0, 1], [2, 3]] + >>> m.applyfunc(lambda i: 2*i) + [[0, 2], [4, 6]] + """ + from sympy.tensor.array import SparseNDimArray + from sympy.tensor.array.arrayop import Flatten + + if isinstance(self, SparseNDimArray) and f(S.Zero) == 0: + return type(self)({k: f(v) for k, v in self._sparse_array.items() if f(v) != 0}, self.shape) + + return type(self)(map(f, Flatten(self)), self.shape) + + def _sympystr(self, printer): + def f(sh, shape_left, i, j): + if len(shape_left) == 1: + return "["+", ".join([printer._print(self[self._get_tuple_index(e)]) for e in range(i, j)])+"]" + + sh //= shape_left[0] + return "[" + ", ".join([f(sh, shape_left[1:], i+e*sh, i+(e+1)*sh) for e in range(shape_left[0])]) + "]" # + "\n"*len(shape_left) + + if self.rank() == 0: + return printer._print(self[()]) + if 0 in self.shape: + return f"{self.__class__.__name__}([], {self.shape})" + return f(self._loop_size, self.shape, 0, self._loop_size) + + def tolist(self): + """ + Converting MutableDenseNDimArray to one-dim list + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray([1, 2, 3, 4], (2, 2)) + >>> a + [[1, 2], [3, 4]] + >>> b = a.tolist() + >>> b + [[1, 2], [3, 4]] + """ + + def f(sh, shape_left, i, j): + if len(shape_left) == 1: + return [self[self._get_tuple_index(e)] for e in range(i, j)] + result = [] + sh //= shape_left[0] + for e in range(shape_left[0]): + result.append(f(sh, shape_left[1:], i+e*sh, i+(e+1)*sh)) + return result + + return f(self._loop_size, self.shape, 0, self._loop_size) + + def __add__(self, other): + from sympy.tensor.array.arrayop import Flatten + + if not isinstance(other, NDimArray): + return NotImplemented + + if self.shape != other.shape: + raise ValueError("array shape mismatch") + result_list = [i+j for i,j in zip(Flatten(self), Flatten(other))] + + return type(self)(result_list, self.shape) + + def __sub__(self, other): + from sympy.tensor.array.arrayop import Flatten + + if not isinstance(other, NDimArray): + return NotImplemented + + if self.shape != other.shape: + raise ValueError("array shape mismatch") + result_list = [i-j for i,j in zip(Flatten(self), Flatten(other))] + + return type(self)(result_list, self.shape) + + def __mul__(self, other): + from sympy.matrices.matrixbase import MatrixBase + from sympy.tensor.array import SparseNDimArray + from sympy.tensor.array.arrayop import Flatten + + if isinstance(other, (Iterable, NDimArray, MatrixBase)): + raise ValueError("scalar expected, use tensorproduct(...) for tensorial product") + + other = sympify(other) + if isinstance(self, SparseNDimArray): + if other.is_zero: + return type(self)({}, self.shape) + return type(self)({k: other*v for (k, v) in self._sparse_array.items()}, self.shape) + + result_list = [i*other for i in Flatten(self)] + return type(self)(result_list, self.shape) + + def __rmul__(self, other): + from sympy.matrices.matrixbase import MatrixBase + from sympy.tensor.array import SparseNDimArray + from sympy.tensor.array.arrayop import Flatten + + if isinstance(other, (Iterable, NDimArray, MatrixBase)): + raise ValueError("scalar expected, use tensorproduct(...) for tensorial product") + + other = sympify(other) + if isinstance(self, SparseNDimArray): + if other.is_zero: + return type(self)({}, self.shape) + return type(self)({k: other*v for (k, v) in self._sparse_array.items()}, self.shape) + + result_list = [other*i for i in Flatten(self)] + return type(self)(result_list, self.shape) + + def __truediv__(self, other): + from sympy.matrices.matrixbase import MatrixBase + from sympy.tensor.array import SparseNDimArray + from sympy.tensor.array.arrayop import Flatten + + if isinstance(other, (Iterable, NDimArray, MatrixBase)): + raise ValueError("scalar expected") + + other = sympify(other) + if isinstance(self, SparseNDimArray) and other != S.Zero: + return type(self)({k: v/other for (k, v) in self._sparse_array.items()}, self.shape) + + result_list = [i/other for i in Flatten(self)] + return type(self)(result_list, self.shape) + + def __rtruediv__(self, other): + raise NotImplementedError('unsupported operation on NDimArray') + + def __neg__(self): + from sympy.tensor.array import SparseNDimArray + from sympy.tensor.array.arrayop import Flatten + + if isinstance(self, SparseNDimArray): + return type(self)({k: -v for (k, v) in self._sparse_array.items()}, self.shape) + + result_list = [-i for i in Flatten(self)] + return type(self)(result_list, self.shape) + + def __iter__(self): + def iterator(): + if self._shape: + for i in range(self._shape[0]): + yield self[i] + else: + yield self[()] + + return iterator() + + def __eq__(self, other): + """ + NDimArray instances can be compared to each other. + Instances equal if they have same shape and data. + + Examples + ======== + + >>> from sympy import MutableDenseNDimArray + >>> a = MutableDenseNDimArray.zeros(2, 3) + >>> b = MutableDenseNDimArray.zeros(2, 3) + >>> a == b + True + >>> c = a.reshape(3, 2) + >>> c == b + False + >>> a[0,0] = 1 + >>> b[0,0] = 2 + >>> a == b + False + """ + from sympy.tensor.array import SparseNDimArray + if not isinstance(other, NDimArray): + return False + + if not self.shape == other.shape: + return False + + if isinstance(self, SparseNDimArray) and isinstance(other, SparseNDimArray): + return dict(self._sparse_array) == dict(other._sparse_array) + + return list(self) == list(other) + + def __ne__(self, other): + return not self == other + + def _eval_transpose(self): + if self.rank() != 2: + raise ValueError("array rank not 2") + from .arrayop import permutedims + return permutedims(self, (1, 0)) + + def transpose(self): + return self._eval_transpose() + + def _eval_conjugate(self): + from sympy.tensor.array.arrayop import Flatten + + return self.func([i.conjugate() for i in Flatten(self)], self.shape) + + def conjugate(self): + return self._eval_conjugate() + + def _eval_adjoint(self): + return self.transpose().conjugate() + + def adjoint(self): + return self._eval_adjoint() + + def _slice_expand(self, s, dim): + if not isinstance(s, slice): + return (s,) + start, stop, step = s.indices(dim) + return [start + i*step for i in range((stop-start)//step)] + + def _get_slice_data_for_array_access(self, index): + sl_factors = [self._slice_expand(i, dim) for (i, dim) in zip(index, self.shape)] + eindices = itertools.product(*sl_factors) + return sl_factors, eindices + + def _get_slice_data_for_array_assignment(self, index, value): + if not isinstance(value, NDimArray): + value = type(self)(value) + sl_factors, eindices = self._get_slice_data_for_array_access(index) + slice_offsets = [min(i) if isinstance(i, list) else None for i in sl_factors] + # TODO: add checks for dimensions for `value`? + return value, eindices, slice_offsets + + @classmethod + def _check_special_bounds(cls, flat_list, shape): + if shape == () and len(flat_list) != 1: + raise ValueError("arrays without shape need one scalar value") + if shape == (0,) and len(flat_list) > 0: + raise ValueError("if array shape is (0,) there cannot be elements") + + def _check_index_for_getitem(self, index): + if isinstance(index, (SYMPY_INTS, Integer, slice)): + index = (index,) + + if len(index) < self.rank(): + index = tuple(index) + \ + tuple(slice(None) for i in range(len(index), self.rank())) + + if len(index) > self.rank(): + raise ValueError('Dimension of index greater than rank of array') + + return index + + +class ImmutableNDimArray(NDimArray, Basic): + _op_priority = 11.0 + + def __hash__(self): + return Basic.__hash__(self) + + def as_immutable(self): + return self + + def as_mutable(self): + raise NotImplementedError("abstract method") diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/sparse_ndim_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/sparse_ndim_array.py new file mode 100644 index 0000000000000000000000000000000000000000..f11aa95be8ec9d10a9104d48fb28f406fe43845e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/sparse_ndim_array.py @@ -0,0 +1,196 @@ +from sympy.core.basic import Basic +from sympy.core.containers import (Dict, Tuple) +from sympy.core.singleton import S +from sympy.core.sympify import _sympify +from sympy.tensor.array.mutable_ndim_array import MutableNDimArray +from sympy.tensor.array.ndim_array import NDimArray, ImmutableNDimArray +from sympy.utilities.iterables import flatten + +import functools + +class SparseNDimArray(NDimArray): + + def __new__(self, *args, **kwargs): + return ImmutableSparseNDimArray(*args, **kwargs) + + def __getitem__(self, index): + """ + Get an element from a sparse N-dim array. + + Examples + ======== + + >>> from sympy import MutableSparseNDimArray + >>> a = MutableSparseNDimArray(range(4), (2, 2)) + >>> a + [[0, 1], [2, 3]] + >>> a[0, 0] + 0 + >>> a[1, 1] + 3 + >>> a[0] + [0, 1] + >>> a[1] + [2, 3] + + Symbolic indexing: + + >>> from sympy.abc import i, j + >>> a[i, j] + [[0, 1], [2, 3]][i, j] + + Replace `i` and `j` to get element `(0, 0)`: + + >>> a[i, j].subs({i: 0, j: 0}) + 0 + + """ + syindex = self._check_symbolic_index(index) + if syindex is not None: + return syindex + + index = self._check_index_for_getitem(index) + + # `index` is a tuple with one or more slices: + if isinstance(index, tuple) and any(isinstance(i, slice) for i in index): + sl_factors, eindices = self._get_slice_data_for_array_access(index) + array = [self._sparse_array.get(self._parse_index(i), S.Zero) for i in eindices] + nshape = [len(el) for i, el in enumerate(sl_factors) if isinstance(index[i], slice)] + return type(self)(array, nshape) + else: + index = self._parse_index(index) + return self._sparse_array.get(index, S.Zero) + + @classmethod + def zeros(cls, *shape): + """ + Return a sparse N-dim array of zeros. + """ + return cls({}, shape) + + def tomatrix(self): + """ + Converts MutableDenseNDimArray to Matrix. Can convert only 2-dim array, else will raise error. + + Examples + ======== + + >>> from sympy import MutableSparseNDimArray + >>> a = MutableSparseNDimArray([1 for i in range(9)], (3, 3)) + >>> b = a.tomatrix() + >>> b + Matrix([ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1]]) + """ + from sympy.matrices import SparseMatrix + if self.rank() != 2: + raise ValueError('Dimensions must be of size of 2') + + mat_sparse = {} + for key, value in self._sparse_array.items(): + mat_sparse[self._get_tuple_index(key)] = value + + return SparseMatrix(self.shape[0], self.shape[1], mat_sparse) + + def reshape(self, *newshape): + new_total_size = functools.reduce(lambda x,y: x*y, newshape) + if new_total_size != self._loop_size: + raise ValueError("Invalid reshape parameters " + newshape) + + return type(self)(self._sparse_array, newshape) + +class ImmutableSparseNDimArray(SparseNDimArray, ImmutableNDimArray): # type: ignore + + def __new__(cls, iterable=None, shape=None, **kwargs): + shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) + shape = Tuple(*map(_sympify, shape)) + cls._check_special_bounds(flat_list, shape) + loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else len(flat_list) + + # Sparse array: + if isinstance(flat_list, (dict, Dict)): + sparse_array = Dict(flat_list) + else: + sparse_array = {} + for i, el in enumerate(flatten(flat_list)): + if el != 0: + sparse_array[i] = _sympify(el) + + sparse_array = Dict(sparse_array) + + self = Basic.__new__(cls, sparse_array, shape, **kwargs) + self._shape = shape + self._rank = len(shape) + self._loop_size = loop_size + self._sparse_array = sparse_array + + return self + + def __setitem__(self, index, value): + raise TypeError("immutable N-dim array") + + def as_mutable(self): + return MutableSparseNDimArray(self) + + +class MutableSparseNDimArray(MutableNDimArray, SparseNDimArray): + + def __new__(cls, iterable=None, shape=None, **kwargs): + shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) + self = object.__new__(cls) + self._shape = shape + self._rank = len(shape) + self._loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else len(flat_list) + + # Sparse array: + if isinstance(flat_list, (dict, Dict)): + self._sparse_array = dict(flat_list) + return self + + self._sparse_array = {} + + for i, el in enumerate(flatten(flat_list)): + if el != 0: + self._sparse_array[i] = _sympify(el) + + return self + + def __setitem__(self, index, value): + """Allows to set items to MutableDenseNDimArray. + + Examples + ======== + + >>> from sympy import MutableSparseNDimArray + >>> a = MutableSparseNDimArray.zeros(2, 2) + >>> a[0, 0] = 1 + >>> a[1, 1] = 1 + >>> a + [[1, 0], [0, 1]] + """ + if isinstance(index, tuple) and any(isinstance(i, slice) for i in index): + value, eindices, slice_offsets = self._get_slice_data_for_array_assignment(index, value) + for i in eindices: + other_i = [ind - j for ind, j in zip(i, slice_offsets) if j is not None] + other_value = value[other_i] + complete_index = self._parse_index(i) + if other_value != 0: + self._sparse_array[complete_index] = other_value + elif complete_index in self._sparse_array: + self._sparse_array.pop(complete_index) + else: + index = self._parse_index(index) + value = _sympify(value) + if value == 0 and index in self._sparse_array: + self._sparse_array.pop(index) + else: + self._sparse_array[index] = value + + def as_immutable(self): + return ImmutableSparseNDimArray(self) + + @property + def free_symbols(self): + return {i for j in self._sparse_array.values() for i in j.free_symbols} diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd6925c88866ff144959a82cf6f4db89869043f5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_array_comprehension.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_array_comprehension.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7a114b85fdc31504ea2fd50aa4d0fa99919f56f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_array_comprehension.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_array_derivatives.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_array_derivatives.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c925e0f2c95896db4db40878ab04a5af9271c0a2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_array_derivatives.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_arrayop.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_arrayop.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c868923cb744f981b27e62598aaaea165c562e6f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_arrayop.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_immutable_ndim_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_immutable_ndim_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8019cd7370f78b5163b9875f470006c7730376c Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_immutable_ndim_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_mutable_ndim_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_mutable_ndim_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6cb9b8f6dce7fbef5f0b5bbe9cb268d47e2ca49 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_mutable_ndim_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_ndim_array.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_ndim_array.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..220cce23b8f9f5505e913a80ff7381991421539a Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_ndim_array.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_ndim_array_conversions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_ndim_array_conversions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e85bd5b14ec42ca99d858564b4a221e79f23572 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/__pycache__/test_ndim_array_conversions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_array_comprehension.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_array_comprehension.py new file mode 100644 index 0000000000000000000000000000000000000000..510e068f287fa04419712e5e9a16a314e522a62d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_array_comprehension.py @@ -0,0 +1,78 @@ +from sympy.tensor.array.array_comprehension import ArrayComprehension, ArrayComprehensionMap +from sympy.tensor.array import ImmutableDenseNDimArray +from sympy.abc import i, j, k, l +from sympy.testing.pytest import raises +from sympy.matrices import Matrix + + +def test_array_comprehension(): + a = ArrayComprehension(i*j, (i, 1, 3), (j, 2, 4)) + b = ArrayComprehension(i, (i, 1, j+1)) + c = ArrayComprehension(i+j+k+l, (i, 1, 2), (j, 1, 3), (k, 1, 4), (l, 1, 5)) + d = ArrayComprehension(k, (i, 1, 5)) + e = ArrayComprehension(i, (j, k+1, k+5)) + assert a.doit().tolist() == [[2, 3, 4], [4, 6, 8], [6, 9, 12]] + assert a.shape == (3, 3) + assert a.is_shape_numeric == True + assert a.tolist() == [[2, 3, 4], [4, 6, 8], [6, 9, 12]] + assert a.tomatrix() == Matrix([ + [2, 3, 4], + [4, 6, 8], + [6, 9, 12]]) + assert len(a) == 9 + assert isinstance(b.doit(), ArrayComprehension) + assert isinstance(a.doit(), ImmutableDenseNDimArray) + assert b.subs(j, 3) == ArrayComprehension(i, (i, 1, 4)) + assert b.free_symbols == {j} + assert b.shape == (j + 1,) + assert b.rank() == 1 + assert b.is_shape_numeric == False + assert c.free_symbols == set() + assert c.function == i + j + k + l + assert c.limits == ((i, 1, 2), (j, 1, 3), (k, 1, 4), (l, 1, 5)) + assert c.doit().tolist() == [[[[4, 5, 6, 7, 8], [5, 6, 7, 8, 9], [6, 7, 8, 9, 10], [7, 8, 9, 10, 11]], + [[5, 6, 7, 8, 9], [6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12]], + [[6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12], [9, 10, 11, 12, 13]]], + [[[5, 6, 7, 8, 9], [6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12]], + [[6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12], [9, 10, 11, 12, 13]], + [[7, 8, 9, 10, 11], [8, 9, 10, 11, 12], [9, 10, 11, 12, 13], [10, 11, 12, 13, 14]]]] + assert c.free_symbols == set() + assert c.variables == [i, j, k, l] + assert c.bound_symbols == [i, j, k, l] + assert d.doit().tolist() == [k, k, k, k, k] + assert len(e) == 5 + raises(TypeError, lambda: ArrayComprehension(i*j, (i, 1, 3), (j, 2, [1, 3, 2]))) + raises(ValueError, lambda: ArrayComprehension(i*j, (i, 1, 3), (j, 2, 1))) + raises(ValueError, lambda: ArrayComprehension(i*j, (i, 1, 3), (j, 2, j+1))) + raises(ValueError, lambda: len(ArrayComprehension(i*j, (i, 1, 3), (j, 2, j+4)))) + raises(TypeError, lambda: ArrayComprehension(i*j, (i, 0, i + 1.5), (j, 0, 2))) + raises(ValueError, lambda: b.tolist()) + raises(ValueError, lambda: b.tomatrix()) + raises(ValueError, lambda: c.tomatrix()) + +def test_arraycomprehensionmap(): + a = ArrayComprehensionMap(lambda i: i+1, (i, 1, 5)) + assert a.doit().tolist() == [2, 3, 4, 5, 6] + assert a.shape == (5,) + assert a.is_shape_numeric + assert a.tolist() == [2, 3, 4, 5, 6] + assert len(a) == 5 + assert isinstance(a.doit(), ImmutableDenseNDimArray) + expr = ArrayComprehensionMap(lambda i: i+1, (i, 1, k)) + assert expr.doit() == expr + assert expr.subs(k, 4) == ArrayComprehensionMap(lambda i: i+1, (i, 1, 4)) + assert expr.subs(k, 4).doit() == ImmutableDenseNDimArray([2, 3, 4, 5]) + b = ArrayComprehensionMap(lambda i: i+1, (i, 1, 2), (i, 1, 3), (i, 1, 4), (i, 1, 5)) + assert b.doit().tolist() == [[[[2, 3, 4, 5, 6], [3, 5, 7, 9, 11], [4, 7, 10, 13, 16], [5, 9, 13, 17, 21]], + [[3, 5, 7, 9, 11], [5, 9, 13, 17, 21], [7, 13, 19, 25, 31], [9, 17, 25, 33, 41]], + [[4, 7, 10, 13, 16], [7, 13, 19, 25, 31], [10, 19, 28, 37, 46], [13, 25, 37, 49, 61]]], + [[[3, 5, 7, 9, 11], [5, 9, 13, 17, 21], [7, 13, 19, 25, 31], [9, 17, 25, 33, 41]], + [[5, 9, 13, 17, 21], [9, 17, 25, 33, 41], [13, 25, 37, 49, 61], [17, 33, 49, 65, 81]], + [[7, 13, 19, 25, 31], [13, 25, 37, 49, 61], [19, 37, 55, 73, 91], [25, 49, 73, 97, 121]]]] + + # tests about lambda expression + assert ArrayComprehensionMap(lambda: 3, (i, 1, 5)).doit().tolist() == [3, 3, 3, 3, 3] + assert ArrayComprehensionMap(lambda i: i+1, (i, 1, 5)).doit().tolist() == [2, 3, 4, 5, 6] + raises(ValueError, lambda: ArrayComprehensionMap(i*j, (i, 1, 3), (j, 2, 4))) + a = ArrayComprehensionMap(lambda i, j: i+j, (i, 1, 5)) + raises(ValueError, lambda: a.doit()) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_array_derivatives.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_array_derivatives.py new file mode 100644 index 0000000000000000000000000000000000000000..7f6c777c55a9170704f309bf74387d140bf2ec32 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_array_derivatives.py @@ -0,0 +1,52 @@ +from sympy.core.symbol import symbols +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.tensor.array.ndim_array import NDimArray +from sympy.matrices.matrixbase import MatrixBase +from sympy.tensor.array.array_derivatives import ArrayDerivative + +x, y, z, t = symbols("x y z t") + +m = Matrix([[x, y], [z, t]]) + +M = MatrixSymbol("M", 3, 2) +N = MatrixSymbol("N", 4, 3) + + +def test_array_derivative_construction(): + + d = ArrayDerivative(x, m, evaluate=False) + assert d.shape == (2, 2) + expr = d.doit() + assert isinstance(expr, MatrixBase) + assert expr.shape == (2, 2) + + d = ArrayDerivative(m, m, evaluate=False) + assert d.shape == (2, 2, 2, 2) + expr = d.doit() + assert isinstance(expr, NDimArray) + assert expr.shape == (2, 2, 2, 2) + + d = ArrayDerivative(m, x, evaluate=False) + assert d.shape == (2, 2) + expr = d.doit() + assert isinstance(expr, MatrixBase) + assert expr.shape == (2, 2) + + d = ArrayDerivative(M, N, evaluate=False) + assert d.shape == (4, 3, 3, 2) + expr = d.doit() + assert isinstance(expr, ArrayDerivative) + assert expr.shape == (4, 3, 3, 2) + + d = ArrayDerivative(M, (N, 2), evaluate=False) + assert d.shape == (4, 3, 4, 3, 3, 2) + expr = d.doit() + assert isinstance(expr, ArrayDerivative) + assert expr.shape == (4, 3, 4, 3, 3, 2) + + d = ArrayDerivative(M.as_explicit(), (N.as_explicit(), 2), evaluate=False) + assert d.doit().shape == (4, 3, 4, 3, 3, 2) + expr = d.doit() + assert isinstance(expr, NDimArray) + assert expr.shape == (4, 3, 4, 3, 3, 2) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_arrayop.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_arrayop.py new file mode 100644 index 0000000000000000000000000000000000000000..de56e81e0064f1e303a7a58e41932d15f2d0b41e --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_arrayop.py @@ -0,0 +1,361 @@ +import itertools +import random + +from sympy.combinatorics import Permutation +from sympy.combinatorics.permutations import _af_invert +from sympy.testing.pytest import raises + +from sympy.core.function import diff +from sympy.core.symbol import symbols +from sympy.functions.elementary.complexes import (adjoint, conjugate, transpose) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.tensor.array import Array, ImmutableDenseNDimArray, ImmutableSparseNDimArray, MutableSparseNDimArray + +from sympy.tensor.array.arrayop import tensorproduct, tensorcontraction, derive_by_array, permutedims, Flatten, \ + tensordiagonal + + +def test_import_NDimArray(): + from sympy.tensor.array import NDimArray + del NDimArray + + +def test_tensorproduct(): + x,y,z,t = symbols('x y z t') + from sympy.abc import a,b,c,d + assert tensorproduct() == 1 + assert tensorproduct([x]) == Array([x]) + assert tensorproduct([x], [y]) == Array([[x*y]]) + assert tensorproduct([x], [y], [z]) == Array([[[x*y*z]]]) + assert tensorproduct([x], [y], [z], [t]) == Array([[[[x*y*z*t]]]]) + + assert tensorproduct(x) == x + assert tensorproduct(x, y) == x*y + assert tensorproduct(x, y, z) == x*y*z + assert tensorproduct(x, y, z, t) == x*y*z*t + + for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray]: + A = ArrayType([x, y]) + B = ArrayType([1, 2, 3]) + C = ArrayType([a, b, c, d]) + + assert tensorproduct(A, B, C) == ArrayType([[[a*x, b*x, c*x, d*x], [2*a*x, 2*b*x, 2*c*x, 2*d*x], [3*a*x, 3*b*x, 3*c*x, 3*d*x]], + [[a*y, b*y, c*y, d*y], [2*a*y, 2*b*y, 2*c*y, 2*d*y], [3*a*y, 3*b*y, 3*c*y, 3*d*y]]]) + + assert tensorproduct([x, y], [1, 2, 3]) == tensorproduct(A, B) + + assert tensorproduct(A, 2) == ArrayType([2*x, 2*y]) + assert tensorproduct(A, [2]) == ArrayType([[2*x], [2*y]]) + assert tensorproduct([2], A) == ArrayType([[2*x, 2*y]]) + assert tensorproduct(a, A) == ArrayType([a*x, a*y]) + assert tensorproduct(a, A, B) == ArrayType([[a*x, 2*a*x, 3*a*x], [a*y, 2*a*y, 3*a*y]]) + assert tensorproduct(A, B, a) == ArrayType([[a*x, 2*a*x, 3*a*x], [a*y, 2*a*y, 3*a*y]]) + assert tensorproduct(B, a, A) == ArrayType([[a*x, a*y], [2*a*x, 2*a*y], [3*a*x, 3*a*y]]) + + # tests for large scale sparse array + for SparseArrayType in [ImmutableSparseNDimArray, MutableSparseNDimArray]: + a = SparseArrayType({1:2, 3:4},(1000, 2000)) + b = SparseArrayType({1:2, 3:4},(1000, 2000)) + assert tensorproduct(a, b) == ImmutableSparseNDimArray({2000001: 4, 2000003: 8, 6000001: 8, 6000003: 16}, (1000, 2000, 1000, 2000)) + + +def test_tensorcontraction(): + from sympy.abc import a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x + B = Array(range(18), (2, 3, 3)) + assert tensorcontraction(B, (1, 2)) == Array([12, 39]) + C1 = Array([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x], (2, 3, 2, 2)) + + assert tensorcontraction(C1, (0, 2)) == Array([[a + o, b + p], [e + s, f + t], [i + w, j + x]]) + assert tensorcontraction(C1, (0, 2, 3)) == Array([a + p, e + t, i + x]) + assert tensorcontraction(C1, (2, 3)) == Array([[a + d, e + h, i + l], [m + p, q + t, u + x]]) + + +def test_derivative_by_array(): + from sympy.abc import i, j, t, x, y, z + + bexpr = x*y**2*exp(z)*log(t) + sexpr = sin(bexpr) + cexpr = cos(bexpr) + + a = Array([sexpr]) + + assert derive_by_array(sexpr, t) == x*y**2*exp(z)*cos(x*y**2*exp(z)*log(t))/t + assert derive_by_array(sexpr, [x, y, z]) == Array([bexpr/x*cexpr, 2*y*bexpr/y**2*cexpr, bexpr*cexpr]) + assert derive_by_array(a, [x, y, z]) == Array([[bexpr/x*cexpr], [2*y*bexpr/y**2*cexpr], [bexpr*cexpr]]) + + assert derive_by_array(sexpr, [[x, y], [z, t]]) == Array([[bexpr/x*cexpr, 2*y*bexpr/y**2*cexpr], [bexpr*cexpr, bexpr/log(t)/t*cexpr]]) + assert derive_by_array(a, [[x, y], [z, t]]) == Array([[[bexpr/x*cexpr], [2*y*bexpr/y**2*cexpr]], [[bexpr*cexpr], [bexpr/log(t)/t*cexpr]]]) + assert derive_by_array([[x, y], [z, t]], [x, y]) == Array([[[1, 0], [0, 0]], [[0, 1], [0, 0]]]) + assert derive_by_array([[x, y], [z, t]], [[x, y], [z, t]]) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], + [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + + assert diff(sexpr, t) == x*y**2*exp(z)*cos(x*y**2*exp(z)*log(t))/t + assert diff(sexpr, Array([x, y, z])) == Array([bexpr/x*cexpr, 2*y*bexpr/y**2*cexpr, bexpr*cexpr]) + assert diff(a, Array([x, y, z])) == Array([[bexpr/x*cexpr], [2*y*bexpr/y**2*cexpr], [bexpr*cexpr]]) + + assert diff(sexpr, Array([[x, y], [z, t]])) == Array([[bexpr/x*cexpr, 2*y*bexpr/y**2*cexpr], [bexpr*cexpr, bexpr/log(t)/t*cexpr]]) + assert diff(a, Array([[x, y], [z, t]])) == Array([[[bexpr/x*cexpr], [2*y*bexpr/y**2*cexpr]], [[bexpr*cexpr], [bexpr/log(t)/t*cexpr]]]) + assert diff(Array([[x, y], [z, t]]), Array([x, y])) == Array([[[1, 0], [0, 0]], [[0, 1], [0, 0]]]) + assert diff(Array([[x, y], [z, t]]), Array([[x, y], [z, t]])) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]], + [[[0, 0], [1, 0]], [[0, 0], [0, 1]]]]) + + # test for large scale sparse array + for SparseArrayType in [ImmutableSparseNDimArray, MutableSparseNDimArray]: + b = MutableSparseNDimArray({0:i, 1:j}, (10000, 20000)) + assert derive_by_array(b, i) == ImmutableSparseNDimArray({0: 1}, (10000, 20000)) + assert derive_by_array(b, (i, j)) == ImmutableSparseNDimArray({0: 1, 200000001: 1}, (2, 10000, 20000)) + + #https://github.com/sympy/sympy/issues/20655 + U = Array([x, y, z]) + E = 2 + assert derive_by_array(E, U) == ImmutableDenseNDimArray([0, 0, 0]) + + +def test_issue_emerged_while_discussing_10972(): + ua = Array([-1,0]) + Fa = Array([[0, 1], [-1, 0]]) + po = tensorproduct(Fa, ua, Fa, ua) + assert tensorcontraction(po, (1, 2), (4, 5)) == Array([[0, 0], [0, 1]]) + + sa = symbols('a0:144') + po = Array(sa, [2, 2, 3, 3, 2, 2]) + assert tensorcontraction(po, (0, 1), (2, 3), (4, 5)) == sa[0] + sa[108] + sa[111] + sa[124] + sa[127] + sa[140] + sa[143] + sa[16] + sa[19] + sa[3] + sa[32] + sa[35] + assert tensorcontraction(po, (0, 1, 4, 5), (2, 3)) == sa[0] + sa[111] + sa[127] + sa[143] + sa[16] + sa[32] + assert tensorcontraction(po, (0, 1), (4, 5)) == Array([[sa[0] + sa[108] + sa[111] + sa[3], sa[112] + sa[115] + sa[4] + sa[7], + sa[11] + sa[116] + sa[119] + sa[8]], [sa[12] + sa[120] + sa[123] + sa[15], + sa[124] + sa[127] + sa[16] + sa[19], sa[128] + sa[131] + sa[20] + sa[23]], + [sa[132] + sa[135] + sa[24] + sa[27], sa[136] + sa[139] + sa[28] + sa[31], + sa[140] + sa[143] + sa[32] + sa[35]]]) + assert tensorcontraction(po, (0, 1), (2, 3)) == Array([[sa[0] + sa[108] + sa[124] + sa[140] + sa[16] + sa[32], sa[1] + sa[109] + sa[125] + sa[141] + sa[17] + sa[33]], + [sa[110] + sa[126] + sa[142] + sa[18] + sa[2] + sa[34], sa[111] + sa[127] + sa[143] + sa[19] + sa[3] + sa[35]]]) + + +def test_array_permutedims(): + sa = symbols('a0:144') + + for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray]: + m1 = ArrayType(sa[:6], (2, 3)) + assert permutedims(m1, (1, 0)) == transpose(m1) + assert m1.tomatrix().T == permutedims(m1, (1, 0)).tomatrix() + + assert m1.tomatrix().T == transpose(m1).tomatrix() + assert m1.tomatrix().C == conjugate(m1).tomatrix() + assert m1.tomatrix().H == adjoint(m1).tomatrix() + + assert m1.tomatrix().T == m1.transpose().tomatrix() + assert m1.tomatrix().C == m1.conjugate().tomatrix() + assert m1.tomatrix().H == m1.adjoint().tomatrix() + + raises(ValueError, lambda: permutedims(m1, (0,))) + raises(ValueError, lambda: permutedims(m1, (0, 0))) + raises(ValueError, lambda: permutedims(m1, (1, 2, 0))) + + # Some tests with random arrays: + dims = 6 + shape = [random.randint(1,5) for i in range(dims)] + elems = [random.random() for i in range(tensorproduct(*shape))] + ra = ArrayType(elems, shape) + perm = list(range(dims)) + # Randomize the permutation: + random.shuffle(perm) + # Test inverse permutation: + assert permutedims(permutedims(ra, perm), _af_invert(perm)) == ra + # Test that permuted shape corresponds to action by `Permutation`: + assert permutedims(ra, perm).shape == tuple(Permutation(perm)(shape)) + + z = ArrayType.zeros(4,5,6,7) + + assert permutedims(z, (2, 3, 1, 0)).shape == (6, 7, 5, 4) + assert permutedims(z, [2, 3, 1, 0]).shape == (6, 7, 5, 4) + assert permutedims(z, Permutation([2, 3, 1, 0])).shape == (6, 7, 5, 4) + + po = ArrayType(sa, [2, 2, 3, 3, 2, 2]) + + raises(ValueError, lambda: permutedims(po, (1, 1))) + raises(ValueError, lambda: po.transpose()) + raises(ValueError, lambda: po.adjoint()) + + assert permutedims(po, reversed(range(po.rank()))) == ArrayType( + [[[[[[sa[0], sa[72]], [sa[36], sa[108]]], [[sa[12], sa[84]], [sa[48], sa[120]]], [[sa[24], + sa[96]], [sa[60], sa[132]]]], + [[[sa[4], sa[76]], [sa[40], sa[112]]], [[sa[16], + sa[88]], [sa[52], sa[124]]], + [[sa[28], sa[100]], [sa[64], sa[136]]]], + [[[sa[8], + sa[80]], [sa[44], sa[116]]], [[sa[20], sa[92]], [sa[56], sa[128]]], [[sa[32], + sa[104]], [sa[68], sa[140]]]]], + [[[[sa[2], sa[74]], [sa[38], sa[110]]], [[sa[14], + sa[86]], [sa[50], sa[122]]], [[sa[26], sa[98]], [sa[62], sa[134]]]], + [[[sa[6], + sa[78]], [sa[42], sa[114]]], [[sa[18], sa[90]], [sa[54], sa[126]]], [[sa[30], + sa[102]], [sa[66], sa[138]]]], + [[[sa[10], sa[82]], [sa[46], sa[118]]], [[sa[22], + sa[94]], [sa[58], sa[130]]], + [[sa[34], sa[106]], [sa[70], sa[142]]]]]], + [[[[[sa[1], + sa[73]], [sa[37], sa[109]]], [[sa[13], sa[85]], [sa[49], sa[121]]], [[sa[25], + sa[97]], [sa[61], sa[133]]]], + [[[sa[5], sa[77]], [sa[41], sa[113]]], [[sa[17], + sa[89]], [sa[53], sa[125]]], + [[sa[29], sa[101]], [sa[65], sa[137]]]], + [[[sa[9], + sa[81]], [sa[45], sa[117]]], [[sa[21], sa[93]], [sa[57], sa[129]]], [[sa[33], + sa[105]], [sa[69], sa[141]]]]], + [[[[sa[3], sa[75]], [sa[39], sa[111]]], [[sa[15], + sa[87]], [sa[51], sa[123]]], [[sa[27], sa[99]], [sa[63], sa[135]]]], + [[[sa[7], + sa[79]], [sa[43], sa[115]]], [[sa[19], sa[91]], [sa[55], sa[127]]], [[sa[31], + sa[103]], [sa[67], sa[139]]]], + [[[sa[11], sa[83]], [sa[47], sa[119]]], [[sa[23], + sa[95]], [sa[59], sa[131]]], + [[sa[35], sa[107]], [sa[71], sa[143]]]]]]]) + + assert permutedims(po, (1, 0, 2, 3, 4, 5)) == ArrayType( + [[[[[[sa[0], sa[1]], [sa[2], sa[3]]], [[sa[4], sa[5]], [sa[6], sa[7]]], [[sa[8], sa[9]], [sa[10], + sa[11]]]], + [[[sa[12], sa[13]], [sa[14], sa[15]]], [[sa[16], sa[17]], [sa[18], + sa[19]]], [[sa[20], sa[21]], [sa[22], sa[23]]]], + [[[sa[24], sa[25]], [sa[26], + sa[27]]], [[sa[28], sa[29]], [sa[30], sa[31]]], [[sa[32], sa[33]], [sa[34], + sa[35]]]]], + [[[[sa[72], sa[73]], [sa[74], sa[75]]], [[sa[76], sa[77]], [sa[78], + sa[79]]], [[sa[80], sa[81]], [sa[82], sa[83]]]], + [[[sa[84], sa[85]], [sa[86], + sa[87]]], [[sa[88], sa[89]], [sa[90], sa[91]]], [[sa[92], sa[93]], [sa[94], + sa[95]]]], + [[[sa[96], sa[97]], [sa[98], sa[99]]], [[sa[100], sa[101]], [sa[102], + sa[103]]], + [[sa[104], sa[105]], [sa[106], sa[107]]]]]], [[[[[sa[36], sa[37]], [sa[38], + sa[39]]], + [[sa[40], sa[41]], [sa[42], sa[43]]], + [[sa[44], sa[45]], [sa[46], + sa[47]]]], + [[[sa[48], sa[49]], [sa[50], sa[51]]], + [[sa[52], sa[53]], [sa[54], + sa[55]]], + [[sa[56], sa[57]], [sa[58], sa[59]]]], + [[[sa[60], sa[61]], [sa[62], + sa[63]]], + [[sa[64], sa[65]], [sa[66], sa[67]]], + [[sa[68], sa[69]], [sa[70], + sa[71]]]]], [ + [[[sa[108], sa[109]], [sa[110], sa[111]]], + [[sa[112], sa[113]], [sa[114], + sa[115]]], + [[sa[116], sa[117]], [sa[118], sa[119]]]], + [[[sa[120], sa[121]], [sa[122], + sa[123]]], + [[sa[124], sa[125]], [sa[126], sa[127]]], + [[sa[128], sa[129]], [sa[130], + sa[131]]]], + [[[sa[132], sa[133]], [sa[134], sa[135]]], + [[sa[136], sa[137]], [sa[138], + sa[139]]], + [[sa[140], sa[141]], [sa[142], sa[143]]]]]]]) + + assert permutedims(po, (0, 2, 1, 4, 3, 5)) == ArrayType( + [[[[[[sa[0], sa[1]], [sa[4], sa[5]], [sa[8], sa[9]]], [[sa[2], sa[3]], [sa[6], sa[7]], [sa[10], + sa[11]]]], + [[[sa[36], sa[37]], [sa[40], sa[41]], [sa[44], sa[45]]], [[sa[38], + sa[39]], [sa[42], sa[43]], [sa[46], sa[47]]]]], + [[[[sa[12], sa[13]], [sa[16], + sa[17]], [sa[20], sa[21]]], [[sa[14], sa[15]], [sa[18], sa[19]], [sa[22], + sa[23]]]], + [[[sa[48], sa[49]], [sa[52], sa[53]], [sa[56], sa[57]]], [[sa[50], + sa[51]], [sa[54], sa[55]], [sa[58], sa[59]]]]], + [[[[sa[24], sa[25]], [sa[28], + sa[29]], [sa[32], sa[33]]], [[sa[26], sa[27]], [sa[30], sa[31]], [sa[34], + sa[35]]]], + [[[sa[60], sa[61]], [sa[64], sa[65]], [sa[68], sa[69]]], [[sa[62], + sa[63]], [sa[66], sa[67]], [sa[70], sa[71]]]]]], + [[[[[sa[72], sa[73]], [sa[76], + sa[77]], [sa[80], sa[81]]], [[sa[74], sa[75]], [sa[78], sa[79]], [sa[82], + sa[83]]]], + [[[sa[108], sa[109]], [sa[112], sa[113]], [sa[116], sa[117]]], [[sa[110], + sa[111]], [sa[114], sa[115]], + [sa[118], sa[119]]]]], + [[[[sa[84], sa[85]], [sa[88], + sa[89]], [sa[92], sa[93]]], [[sa[86], sa[87]], [sa[90], sa[91]], [sa[94], + sa[95]]]], + [[[sa[120], sa[121]], [sa[124], sa[125]], [sa[128], sa[129]]], [[sa[122], + sa[123]], [sa[126], sa[127]], + [sa[130], sa[131]]]]], + [[[[sa[96], sa[97]], [sa[100], + sa[101]], [sa[104], sa[105]]], [[sa[98], sa[99]], [sa[102], sa[103]], [sa[106], + sa[107]]]], + [[[sa[132], sa[133]], [sa[136], sa[137]], [sa[140], sa[141]]], [[sa[134], + sa[135]], [sa[138], sa[139]], + [sa[142], sa[143]]]]]]]) + + po2 = po.reshape(4, 9, 2, 2) + assert po2 == ArrayType([[[[sa[0], sa[1]], [sa[2], sa[3]]], [[sa[4], sa[5]], [sa[6], sa[7]]], [[sa[8], sa[9]], [sa[10], sa[11]]], [[sa[12], sa[13]], [sa[14], sa[15]]], [[sa[16], sa[17]], [sa[18], sa[19]]], [[sa[20], sa[21]], [sa[22], sa[23]]], [[sa[24], sa[25]], [sa[26], sa[27]]], [[sa[28], sa[29]], [sa[30], sa[31]]], [[sa[32], sa[33]], [sa[34], sa[35]]]], [[[sa[36], sa[37]], [sa[38], sa[39]]], [[sa[40], sa[41]], [sa[42], sa[43]]], [[sa[44], sa[45]], [sa[46], sa[47]]], [[sa[48], sa[49]], [sa[50], sa[51]]], [[sa[52], sa[53]], [sa[54], sa[55]]], [[sa[56], sa[57]], [sa[58], sa[59]]], [[sa[60], sa[61]], [sa[62], sa[63]]], [[sa[64], sa[65]], [sa[66], sa[67]]], [[sa[68], sa[69]], [sa[70], sa[71]]]], [[[sa[72], sa[73]], [sa[74], sa[75]]], [[sa[76], sa[77]], [sa[78], sa[79]]], [[sa[80], sa[81]], [sa[82], sa[83]]], [[sa[84], sa[85]], [sa[86], sa[87]]], [[sa[88], sa[89]], [sa[90], sa[91]]], [[sa[92], sa[93]], [sa[94], sa[95]]], [[sa[96], sa[97]], [sa[98], sa[99]]], [[sa[100], sa[101]], [sa[102], sa[103]]], [[sa[104], sa[105]], [sa[106], sa[107]]]], [[[sa[108], sa[109]], [sa[110], sa[111]]], [[sa[112], sa[113]], [sa[114], sa[115]]], [[sa[116], sa[117]], [sa[118], sa[119]]], [[sa[120], sa[121]], [sa[122], sa[123]]], [[sa[124], sa[125]], [sa[126], sa[127]]], [[sa[128], sa[129]], [sa[130], sa[131]]], [[sa[132], sa[133]], [sa[134], sa[135]]], [[sa[136], sa[137]], [sa[138], sa[139]]], [[sa[140], sa[141]], [sa[142], sa[143]]]]]) + + assert permutedims(po2, (3, 2, 0, 1)) == ArrayType([[[[sa[0], sa[4], sa[8], sa[12], sa[16], sa[20], sa[24], sa[28], sa[32]], [sa[36], sa[40], sa[44], sa[48], sa[52], sa[56], sa[60], sa[64], sa[68]], [sa[72], sa[76], sa[80], sa[84], sa[88], sa[92], sa[96], sa[100], sa[104]], [sa[108], sa[112], sa[116], sa[120], sa[124], sa[128], sa[132], sa[136], sa[140]]], [[sa[2], sa[6], sa[10], sa[14], sa[18], sa[22], sa[26], sa[30], sa[34]], [sa[38], sa[42], sa[46], sa[50], sa[54], sa[58], sa[62], sa[66], sa[70]], [sa[74], sa[78], sa[82], sa[86], sa[90], sa[94], sa[98], sa[102], sa[106]], [sa[110], sa[114], sa[118], sa[122], sa[126], sa[130], sa[134], sa[138], sa[142]]]], [[[sa[1], sa[5], sa[9], sa[13], sa[17], sa[21], sa[25], sa[29], sa[33]], [sa[37], sa[41], sa[45], sa[49], sa[53], sa[57], sa[61], sa[65], sa[69]], [sa[73], sa[77], sa[81], sa[85], sa[89], sa[93], sa[97], sa[101], sa[105]], [sa[109], sa[113], sa[117], sa[121], sa[125], sa[129], sa[133], sa[137], sa[141]]], [[sa[3], sa[7], sa[11], sa[15], sa[19], sa[23], sa[27], sa[31], sa[35]], [sa[39], sa[43], sa[47], sa[51], sa[55], sa[59], sa[63], sa[67], sa[71]], [sa[75], sa[79], sa[83], sa[87], sa[91], sa[95], sa[99], sa[103], sa[107]], [sa[111], sa[115], sa[119], sa[123], sa[127], sa[131], sa[135], sa[139], sa[143]]]]]) + + # test for large scale sparse array + for SparseArrayType in [ImmutableSparseNDimArray, MutableSparseNDimArray]: + A = SparseArrayType({1:1, 10000:2}, (10000, 20000, 10000)) + assert permutedims(A, (0, 1, 2)) == A + assert permutedims(A, (1, 0, 2)) == SparseArrayType({1: 1, 100000000: 2}, (20000, 10000, 10000)) + B = SparseArrayType({1:1, 20000:2}, (10000, 20000)) + assert B.transpose() == SparseArrayType({10000: 1, 1: 2}, (20000, 10000)) + + +def test_permutedims_with_indices(): + A = Array(range(32)).reshape(2, 2, 2, 2, 2) + indices_new = list("abcde") + indices_old = list("ebdac") + new_A = permutedims(A, index_order_new=indices_new, index_order_old=indices_old) + for a, b, c, d, e in itertools.product(range(2), range(2), range(2), range(2), range(2)): + assert new_A[a, b, c, d, e] == A[e, b, d, a, c] + indices_old = list("cabed") + new_A = permutedims(A, index_order_new=indices_new, index_order_old=indices_old) + for a, b, c, d, e in itertools.product(range(2), range(2), range(2), range(2), range(2)): + assert new_A[a, b, c, d, e] == A[c, a, b, e, d] + raises(ValueError, lambda: permutedims(A, index_order_old=list("aacde"), index_order_new=list("abcde"))) + raises(ValueError, lambda: permutedims(A, index_order_old=list("abcde"), index_order_new=list("abcce"))) + raises(ValueError, lambda: permutedims(A, index_order_old=list("abcde"), index_order_new=list("abce"))) + raises(ValueError, lambda: permutedims(A, index_order_old=list("abce"), index_order_new=list("abce"))) + raises(ValueError, lambda: permutedims(A, [2, 1, 0, 3, 4], index_order_old=list("abcde"))) + raises(ValueError, lambda: permutedims(A, [2, 1, 0, 3, 4], index_order_new=list("abcde"))) + + +def test_flatten(): + from sympy.matrices.dense import Matrix + for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray, Matrix]: + A = ArrayType(range(24)).reshape(4, 6) + assert list(Flatten(A)) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] + + for i, v in enumerate(Flatten(A)): + assert i == v + + +def test_tensordiagonal(): + from sympy.matrices.dense import eye + expr = Array(range(9)).reshape(3, 3) + raises(ValueError, lambda: tensordiagonal(expr, [0], [1])) + raises(ValueError, lambda: tensordiagonal(expr, [0, 0])) + assert tensordiagonal(eye(3), [0, 1]) == Array([1, 1, 1]) + assert tensordiagonal(expr, [0, 1]) == Array([0, 4, 8]) + x, y, z = symbols("x y z") + expr2 = tensorproduct([x, y, z], expr) + assert tensordiagonal(expr2, [1, 2]) == Array([[0, 4*x, 8*x], [0, 4*y, 8*y], [0, 4*z, 8*z]]) + assert tensordiagonal(expr2, [0, 1]) == Array([[0, 3*y, 6*z], [x, 4*y, 7*z], [2*x, 5*y, 8*z]]) + assert tensordiagonal(expr2, [0, 1, 2]) == Array([0, 4*y, 8*z]) + # assert tensordiagonal(expr2, [0]) == permutedims(expr2, [1, 2, 0]) + # assert tensordiagonal(expr2, [1]) == permutedims(expr2, [0, 2, 1]) + # assert tensordiagonal(expr2, [2]) == expr2 + # assert tensordiagonal(expr2, [1], [2]) == expr2 + # assert tensordiagonal(expr2, [0], [1]) == permutedims(expr2, [2, 0, 1]) + + a, b, c, X, Y, Z = symbols("a b c X Y Z") + expr3 = tensorproduct([x, y, z], [1, 2, 3], [a, b, c], [X, Y, Z]) + assert tensordiagonal(expr3, [0, 1, 2, 3]) == Array([x*a*X, 2*y*b*Y, 3*z*c*Z]) + assert tensordiagonal(expr3, [0, 1], [2, 3]) == tensorproduct([x, 2*y, 3*z], [a*X, b*Y, c*Z]) + + # assert tensordiagonal(expr3, [0], [1, 2], [3]) == tensorproduct([x, y, z], [a, 2*b, 3*c], [X, Y, Z]) + assert tensordiagonal(tensordiagonal(expr3, [2, 3]), [0, 1]) == tensorproduct([a*X, b*Y, c*Z], [x, 2*y, 3*z]) + + raises(ValueError, lambda: tensordiagonal([[1, 2, 3], [4, 5, 6]], [0, 1])) + raises(ValueError, lambda: tensordiagonal(expr3.reshape(3, 3, 9), [1, 2])) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_immutable_ndim_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_immutable_ndim_array.py new file mode 100644 index 0000000000000000000000000000000000000000..c6bed4b605c424284b4752592b03b13a9178aac8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_immutable_ndim_array.py @@ -0,0 +1,452 @@ +from copy import copy + +from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray +from sympy.core.containers import Dict +from sympy.core.function import diff +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.matrices import SparseMatrix +from sympy.tensor.indexed import (Indexed, IndexedBase) +from sympy.matrices import Matrix +from sympy.tensor.array.sparse_ndim_array import ImmutableSparseNDimArray +from sympy.testing.pytest import raises + + +def test_ndim_array_initiation(): + arr_with_no_elements = ImmutableDenseNDimArray([], shape=(0,)) + assert len(arr_with_no_elements) == 0 + assert arr_with_no_elements.rank() == 1 + + raises(ValueError, lambda: ImmutableDenseNDimArray([0], shape=(0,))) + raises(ValueError, lambda: ImmutableDenseNDimArray([1, 2, 3], shape=(0,))) + raises(ValueError, lambda: ImmutableDenseNDimArray([], shape=())) + + raises(ValueError, lambda: ImmutableSparseNDimArray([0], shape=(0,))) + raises(ValueError, lambda: ImmutableSparseNDimArray([1, 2, 3], shape=(0,))) + raises(ValueError, lambda: ImmutableSparseNDimArray([], shape=())) + + arr_with_one_element = ImmutableDenseNDimArray([23]) + assert len(arr_with_one_element) == 1 + assert arr_with_one_element[0] == 23 + assert arr_with_one_element[:] == ImmutableDenseNDimArray([23]) + assert arr_with_one_element.rank() == 1 + + arr_with_symbol_element = ImmutableDenseNDimArray([Symbol('x')]) + assert len(arr_with_symbol_element) == 1 + assert arr_with_symbol_element[0] == Symbol('x') + assert arr_with_symbol_element[:] == ImmutableDenseNDimArray([Symbol('x')]) + assert arr_with_symbol_element.rank() == 1 + + number5 = 5 + vector = ImmutableDenseNDimArray.zeros(number5) + assert len(vector) == number5 + assert vector.shape == (number5,) + assert vector.rank() == 1 + + vector = ImmutableSparseNDimArray.zeros(number5) + assert len(vector) == number5 + assert vector.shape == (number5,) + assert vector._sparse_array == Dict() + assert vector.rank() == 1 + + n_dim_array = ImmutableDenseNDimArray(range(3**4), (3, 3, 3, 3,)) + assert len(n_dim_array) == 3 * 3 * 3 * 3 + assert n_dim_array.shape == (3, 3, 3, 3) + assert n_dim_array.rank() == 4 + + array_shape = (3, 3, 3, 3) + sparse_array = ImmutableSparseNDimArray.zeros(*array_shape) + assert len(sparse_array._sparse_array) == 0 + assert len(sparse_array) == 3 * 3 * 3 * 3 + assert n_dim_array.shape == array_shape + assert n_dim_array.rank() == 4 + + one_dim_array = ImmutableDenseNDimArray([2, 3, 1]) + assert len(one_dim_array) == 3 + assert one_dim_array.shape == (3,) + assert one_dim_array.rank() == 1 + assert one_dim_array.tolist() == [2, 3, 1] + + shape = (3, 3) + array_with_many_args = ImmutableSparseNDimArray.zeros(*shape) + assert len(array_with_many_args) == 3 * 3 + assert array_with_many_args.shape == shape + assert array_with_many_args[0, 0] == 0 + assert array_with_many_args.rank() == 2 + + shape = (int(3), int(3)) + array_with_long_shape = ImmutableSparseNDimArray.zeros(*shape) + assert len(array_with_long_shape) == 3 * 3 + assert array_with_long_shape.shape == shape + assert array_with_long_shape[int(0), int(0)] == 0 + assert array_with_long_shape.rank() == 2 + + vector_with_long_shape = ImmutableDenseNDimArray(range(5), int(5)) + assert len(vector_with_long_shape) == 5 + assert vector_with_long_shape.shape == (int(5),) + assert vector_with_long_shape.rank() == 1 + raises(ValueError, lambda: vector_with_long_shape[int(5)]) + + from sympy.abc import x + for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray]: + rank_zero_array = ArrayType(x) + assert len(rank_zero_array) == 1 + assert rank_zero_array.shape == () + assert rank_zero_array.rank() == 0 + assert rank_zero_array[()] == x + raises(ValueError, lambda: rank_zero_array[0]) + + +def test_reshape(): + array = ImmutableDenseNDimArray(range(50), 50) + assert array.shape == (50,) + assert array.rank() == 1 + + array = array.reshape(5, 5, 2) + assert array.shape == (5, 5, 2) + assert array.rank() == 3 + assert len(array) == 50 + + +def test_getitem(): + for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray]: + array = ArrayType(range(24)).reshape(2, 3, 4) + assert array.tolist() == [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]] + assert array[0] == ArrayType([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]) + assert array[0, 0] == ArrayType([0, 1, 2, 3]) + value = 0 + for i in range(2): + for j in range(3): + for k in range(4): + assert array[i, j, k] == value + value += 1 + + raises(ValueError, lambda: array[3, 4, 5]) + raises(ValueError, lambda: array[3, 4, 5, 6]) + raises(ValueError, lambda: array[3, 4, 5, 3:4]) + + +def test_iterator(): + array = ImmutableDenseNDimArray(range(4), (2, 2)) + assert array[0] == ImmutableDenseNDimArray([0, 1]) + assert array[1] == ImmutableDenseNDimArray([2, 3]) + + array = array.reshape(4) + j = 0 + for i in array: + assert i == j + j += 1 + + +def test_sparse(): + sparse_array = ImmutableSparseNDimArray([0, 0, 0, 1], (2, 2)) + assert len(sparse_array) == 2 * 2 + # dictionary where all data is, only non-zero entries are actually stored: + assert len(sparse_array._sparse_array) == 1 + + assert sparse_array.tolist() == [[0, 0], [0, 1]] + + for i, j in zip(sparse_array, [[0, 0], [0, 1]]): + assert i == ImmutableSparseNDimArray(j) + + def sparse_assignment(): + sparse_array[0, 0] = 123 + + assert len(sparse_array._sparse_array) == 1 + raises(TypeError, sparse_assignment) + assert len(sparse_array._sparse_array) == 1 + assert sparse_array[0, 0] == 0 + assert sparse_array/0 == ImmutableSparseNDimArray([[S.NaN, S.NaN], [S.NaN, S.ComplexInfinity]], (2, 2)) + + # test for large scale sparse array + # equality test + assert ImmutableSparseNDimArray.zeros(100000, 200000) == ImmutableSparseNDimArray.zeros(100000, 200000) + + # __mul__ and __rmul__ + a = ImmutableSparseNDimArray({200001: 1}, (100000, 200000)) + assert a * 3 == ImmutableSparseNDimArray({200001: 3}, (100000, 200000)) + assert 3 * a == ImmutableSparseNDimArray({200001: 3}, (100000, 200000)) + assert a * 0 == ImmutableSparseNDimArray({}, (100000, 200000)) + assert 0 * a == ImmutableSparseNDimArray({}, (100000, 200000)) + + # __truediv__ + assert a/3 == ImmutableSparseNDimArray({200001: Rational(1, 3)}, (100000, 200000)) + + # __neg__ + assert -a == ImmutableSparseNDimArray({200001: -1}, (100000, 200000)) + + +def test_calculation(): + + a = ImmutableDenseNDimArray([1]*9, (3, 3)) + b = ImmutableDenseNDimArray([9]*9, (3, 3)) + + c = a + b + for i in c: + assert i == ImmutableDenseNDimArray([10, 10, 10]) + + assert c == ImmutableDenseNDimArray([10]*9, (3, 3)) + assert c == ImmutableSparseNDimArray([10]*9, (3, 3)) + + c = b - a + for i in c: + assert i == ImmutableDenseNDimArray([8, 8, 8]) + + assert c == ImmutableDenseNDimArray([8]*9, (3, 3)) + assert c == ImmutableSparseNDimArray([8]*9, (3, 3)) + + +def test_ndim_array_converting(): + dense_array = ImmutableDenseNDimArray([1, 2, 3, 4], (2, 2)) + alist = dense_array.tolist() + + assert alist == [[1, 2], [3, 4]] + + matrix = dense_array.tomatrix() + assert (isinstance(matrix, Matrix)) + + for i in range(len(dense_array)): + assert dense_array[dense_array._get_tuple_index(i)] == matrix[i] + assert matrix.shape == dense_array.shape + + assert ImmutableDenseNDimArray(matrix) == dense_array + assert ImmutableDenseNDimArray(matrix.as_immutable()) == dense_array + assert ImmutableDenseNDimArray(matrix.as_mutable()) == dense_array + + sparse_array = ImmutableSparseNDimArray([1, 2, 3, 4], (2, 2)) + alist = sparse_array.tolist() + + assert alist == [[1, 2], [3, 4]] + + matrix = sparse_array.tomatrix() + assert(isinstance(matrix, SparseMatrix)) + + for i in range(len(sparse_array)): + assert sparse_array[sparse_array._get_tuple_index(i)] == matrix[i] + assert matrix.shape == sparse_array.shape + + assert ImmutableSparseNDimArray(matrix) == sparse_array + assert ImmutableSparseNDimArray(matrix.as_immutable()) == sparse_array + assert ImmutableSparseNDimArray(matrix.as_mutable()) == sparse_array + + +def test_converting_functions(): + arr_list = [1, 2, 3, 4] + arr_matrix = Matrix(((1, 2), (3, 4))) + + # list + arr_ndim_array = ImmutableDenseNDimArray(arr_list, (2, 2)) + assert (isinstance(arr_ndim_array, ImmutableDenseNDimArray)) + assert arr_matrix.tolist() == arr_ndim_array.tolist() + + # Matrix + arr_ndim_array = ImmutableDenseNDimArray(arr_matrix) + assert (isinstance(arr_ndim_array, ImmutableDenseNDimArray)) + assert arr_matrix.tolist() == arr_ndim_array.tolist() + assert arr_matrix.shape == arr_ndim_array.shape + + +def test_equality(): + first_list = [1, 2, 3, 4] + second_list = [1, 2, 3, 4] + third_list = [4, 3, 2, 1] + assert first_list == second_list + assert first_list != third_list + + first_ndim_array = ImmutableDenseNDimArray(first_list, (2, 2)) + second_ndim_array = ImmutableDenseNDimArray(second_list, (2, 2)) + fourth_ndim_array = ImmutableDenseNDimArray(first_list, (2, 2)) + + assert first_ndim_array == second_ndim_array + + def assignment_attempt(a): + a[0, 0] = 0 + + raises(TypeError, lambda: assignment_attempt(second_ndim_array)) + assert first_ndim_array == second_ndim_array + assert first_ndim_array == fourth_ndim_array + + +def test_arithmetic(): + a = ImmutableDenseNDimArray([3 for i in range(9)], (3, 3)) + b = ImmutableDenseNDimArray([7 for i in range(9)], (3, 3)) + + c1 = a + b + c2 = b + a + assert c1 == c2 + + d1 = a - b + d2 = b - a + assert d1 == d2 * (-1) + + e1 = a * 5 + e2 = 5 * a + e3 = copy(a) + e3 *= 5 + assert e1 == e2 == e3 + + f1 = a / 5 + f2 = copy(a) + f2 /= 5 + assert f1 == f2 + assert f1[0, 0] == f1[0, 1] == f1[0, 2] == f1[1, 0] == f1[1, 1] == \ + f1[1, 2] == f1[2, 0] == f1[2, 1] == f1[2, 2] == Rational(3, 5) + + assert type(a) == type(b) == type(c1) == type(c2) == type(d1) == type(d2) \ + == type(e1) == type(e2) == type(e3) == type(f1) + + z0 = -a + assert z0 == ImmutableDenseNDimArray([-3 for i in range(9)], (3, 3)) + + +def test_higher_dimenions(): + m3 = ImmutableDenseNDimArray(range(10, 34), (2, 3, 4)) + + assert m3.tolist() == [[[10, 11, 12, 13], + [14, 15, 16, 17], + [18, 19, 20, 21]], + + [[22, 23, 24, 25], + [26, 27, 28, 29], + [30, 31, 32, 33]]] + + assert m3._get_tuple_index(0) == (0, 0, 0) + assert m3._get_tuple_index(1) == (0, 0, 1) + assert m3._get_tuple_index(4) == (0, 1, 0) + assert m3._get_tuple_index(12) == (1, 0, 0) + + assert str(m3) == '[[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]]' + + m3_rebuilt = ImmutableDenseNDimArray([[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]]) + assert m3 == m3_rebuilt + + m3_other = ImmutableDenseNDimArray([[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]], (2, 3, 4)) + + assert m3 == m3_other + + +def test_rebuild_immutable_arrays(): + sparr = ImmutableSparseNDimArray(range(10, 34), (2, 3, 4)) + densarr = ImmutableDenseNDimArray(range(10, 34), (2, 3, 4)) + + assert sparr == sparr.func(*sparr.args) + assert densarr == densarr.func(*densarr.args) + + +def test_slices(): + md = ImmutableDenseNDimArray(range(10, 34), (2, 3, 4)) + + assert md[:] == ImmutableDenseNDimArray(range(10, 34), (2, 3, 4)) + assert md[:, :, 0].tomatrix() == Matrix([[10, 14, 18], [22, 26, 30]]) + assert md[0, 1:2, :].tomatrix() == Matrix([[14, 15, 16, 17]]) + assert md[0, 1:3, :].tomatrix() == Matrix([[14, 15, 16, 17], [18, 19, 20, 21]]) + assert md[:, :, :] == md + + sd = ImmutableSparseNDimArray(range(10, 34), (2, 3, 4)) + assert sd == ImmutableSparseNDimArray(md) + + assert sd[:] == ImmutableSparseNDimArray(range(10, 34), (2, 3, 4)) + assert sd[:, :, 0].tomatrix() == Matrix([[10, 14, 18], [22, 26, 30]]) + assert sd[0, 1:2, :].tomatrix() == Matrix([[14, 15, 16, 17]]) + assert sd[0, 1:3, :].tomatrix() == Matrix([[14, 15, 16, 17], [18, 19, 20, 21]]) + assert sd[:, :, :] == sd + + +def test_diff_and_applyfunc(): + from sympy.abc import x, y, z + md = ImmutableDenseNDimArray([[x, y], [x*z, x*y*z]]) + assert md.diff(x) == ImmutableDenseNDimArray([[1, 0], [z, y*z]]) + assert diff(md, x) == ImmutableDenseNDimArray([[1, 0], [z, y*z]]) + + sd = ImmutableSparseNDimArray(md) + assert sd == ImmutableSparseNDimArray([x, y, x*z, x*y*z], (2, 2)) + assert sd.diff(x) == ImmutableSparseNDimArray([[1, 0], [z, y*z]]) + assert diff(sd, x) == ImmutableSparseNDimArray([[1, 0], [z, y*z]]) + + mdn = md.applyfunc(lambda x: x*3) + assert mdn == ImmutableDenseNDimArray([[3*x, 3*y], [3*x*z, 3*x*y*z]]) + assert md != mdn + + sdn = sd.applyfunc(lambda x: x/2) + assert sdn == ImmutableSparseNDimArray([[x/2, y/2], [x*z/2, x*y*z/2]]) + assert sd != sdn + + sdp = sd.applyfunc(lambda x: x+1) + assert sdp == ImmutableSparseNDimArray([[x + 1, y + 1], [x*z + 1, x*y*z + 1]]) + assert sd != sdp + + +def test_op_priority(): + from sympy.abc import x + md = ImmutableDenseNDimArray([1, 2, 3]) + e1 = (1+x)*md + e2 = md*(1+x) + assert e1 == ImmutableDenseNDimArray([1+x, 2+2*x, 3+3*x]) + assert e1 == e2 + + sd = ImmutableSparseNDimArray([1, 2, 3]) + e3 = (1+x)*sd + e4 = sd*(1+x) + assert e3 == ImmutableDenseNDimArray([1+x, 2+2*x, 3+3*x]) + assert e3 == e4 + + +def test_symbolic_indexing(): + x, y, z, w = symbols("x y z w") + M = ImmutableDenseNDimArray([[x, y], [z, w]]) + i, j = symbols("i, j") + Mij = M[i, j] + assert isinstance(Mij, Indexed) + Ms = ImmutableSparseNDimArray([[2, 3*x], [4, 5]]) + msij = Ms[i, j] + assert isinstance(msij, Indexed) + for oi, oj in [(0, 0), (0, 1), (1, 0), (1, 1)]: + assert Mij.subs({i: oi, j: oj}) == M[oi, oj] + assert msij.subs({i: oi, j: oj}) == Ms[oi, oj] + A = IndexedBase("A", (0, 2)) + assert A[0, 0].subs(A, M) == x + assert A[i, j].subs(A, M) == M[i, j] + assert M[i, j].subs(M, A) == A[i, j] + + assert isinstance(M[3 * i - 2, j], Indexed) + assert M[3 * i - 2, j].subs({i: 1, j: 0}) == M[1, 0] + assert isinstance(M[i, 0], Indexed) + assert M[i, 0].subs(i, 0) == M[0, 0] + assert M[0, i].subs(i, 1) == M[0, 1] + + assert M[i, j].diff(x) == ImmutableDenseNDimArray([[1, 0], [0, 0]])[i, j] + assert Ms[i, j].diff(x) == ImmutableSparseNDimArray([[0, 3], [0, 0]])[i, j] + + Mo = ImmutableDenseNDimArray([1, 2, 3]) + assert Mo[i].subs(i, 1) == 2 + Mos = ImmutableSparseNDimArray([1, 2, 3]) + assert Mos[i].subs(i, 1) == 2 + + raises(ValueError, lambda: M[i, 2]) + raises(ValueError, lambda: M[i, -1]) + raises(ValueError, lambda: M[2, i]) + raises(ValueError, lambda: M[-1, i]) + + raises(ValueError, lambda: Ms[i, 2]) + raises(ValueError, lambda: Ms[i, -1]) + raises(ValueError, lambda: Ms[2, i]) + raises(ValueError, lambda: Ms[-1, i]) + + +def test_issue_12665(): + # Testing Python 3 hash of immutable arrays: + arr = ImmutableDenseNDimArray([1, 2, 3]) + # This should NOT raise an exception: + hash(arr) + + +def test_zeros_without_shape(): + arr = ImmutableDenseNDimArray.zeros() + assert arr == ImmutableDenseNDimArray(0) + +def test_issue_21870(): + a0 = ImmutableDenseNDimArray(0) + assert a0.rank() == 0 + a1 = ImmutableDenseNDimArray(a0) + assert a1.rank() == 0 diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_mutable_ndim_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_mutable_ndim_array.py new file mode 100644 index 0000000000000000000000000000000000000000..9a232f399bbc0639d326217975fb0a12e645a984 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_mutable_ndim_array.py @@ -0,0 +1,374 @@ +from copy import copy + +from sympy.tensor.array.dense_ndim_array import MutableDenseNDimArray +from sympy.core.function import diff +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.matrices import SparseMatrix +from sympy.matrices import Matrix +from sympy.tensor.array.sparse_ndim_array import MutableSparseNDimArray +from sympy.testing.pytest import raises + + +def test_ndim_array_initiation(): + arr_with_one_element = MutableDenseNDimArray([23]) + assert len(arr_with_one_element) == 1 + assert arr_with_one_element[0] == 23 + assert arr_with_one_element.rank() == 1 + raises(ValueError, lambda: arr_with_one_element[1]) + + arr_with_symbol_element = MutableDenseNDimArray([Symbol('x')]) + assert len(arr_with_symbol_element) == 1 + assert arr_with_symbol_element[0] == Symbol('x') + assert arr_with_symbol_element.rank() == 1 + + number5 = 5 + vector = MutableDenseNDimArray.zeros(number5) + assert len(vector) == number5 + assert vector.shape == (number5,) + assert vector.rank() == 1 + raises(ValueError, lambda: arr_with_one_element[5]) + + vector = MutableSparseNDimArray.zeros(number5) + assert len(vector) == number5 + assert vector.shape == (number5,) + assert vector._sparse_array == {} + assert vector.rank() == 1 + + n_dim_array = MutableDenseNDimArray(range(3**4), (3, 3, 3, 3,)) + assert len(n_dim_array) == 3 * 3 * 3 * 3 + assert n_dim_array.shape == (3, 3, 3, 3) + assert n_dim_array.rank() == 4 + raises(ValueError, lambda: n_dim_array[0, 0, 0, 3]) + raises(ValueError, lambda: n_dim_array[3, 0, 0, 0]) + raises(ValueError, lambda: n_dim_array[3**4]) + + array_shape = (3, 3, 3, 3) + sparse_array = MutableSparseNDimArray.zeros(*array_shape) + assert len(sparse_array._sparse_array) == 0 + assert len(sparse_array) == 3 * 3 * 3 * 3 + assert n_dim_array.shape == array_shape + assert n_dim_array.rank() == 4 + + one_dim_array = MutableDenseNDimArray([2, 3, 1]) + assert len(one_dim_array) == 3 + assert one_dim_array.shape == (3,) + assert one_dim_array.rank() == 1 + assert one_dim_array.tolist() == [2, 3, 1] + + shape = (3, 3) + array_with_many_args = MutableSparseNDimArray.zeros(*shape) + assert len(array_with_many_args) == 3 * 3 + assert array_with_many_args.shape == shape + assert array_with_many_args[0, 0] == 0 + assert array_with_many_args.rank() == 2 + + shape = (int(3), int(3)) + array_with_long_shape = MutableSparseNDimArray.zeros(*shape) + assert len(array_with_long_shape) == 3 * 3 + assert array_with_long_shape.shape == shape + assert array_with_long_shape[int(0), int(0)] == 0 + assert array_with_long_shape.rank() == 2 + + vector_with_long_shape = MutableDenseNDimArray(range(5), int(5)) + assert len(vector_with_long_shape) == 5 + assert vector_with_long_shape.shape == (int(5),) + assert vector_with_long_shape.rank() == 1 + raises(ValueError, lambda: vector_with_long_shape[int(5)]) + + from sympy.abc import x + for ArrayType in [MutableDenseNDimArray, MutableSparseNDimArray]: + rank_zero_array = ArrayType(x) + assert len(rank_zero_array) == 1 + assert rank_zero_array.shape == () + assert rank_zero_array.rank() == 0 + assert rank_zero_array[()] == x + raises(ValueError, lambda: rank_zero_array[0]) + +def test_sympify(): + from sympy.abc import x, y, z, t + arr = MutableDenseNDimArray([[x, y], [1, z*t]]) + arr_other = sympify(arr) + assert arr_other.shape == (2, 2) + assert arr_other == arr + + +def test_reshape(): + array = MutableDenseNDimArray(range(50), 50) + assert array.shape == (50,) + assert array.rank() == 1 + + array = array.reshape(5, 5, 2) + assert array.shape == (5, 5, 2) + assert array.rank() == 3 + assert len(array) == 50 + + +def test_iterator(): + array = MutableDenseNDimArray(range(4), (2, 2)) + assert array[0] == MutableDenseNDimArray([0, 1]) + assert array[1] == MutableDenseNDimArray([2, 3]) + + array = array.reshape(4) + j = 0 + for i in array: + assert i == j + j += 1 + + +def test_getitem(): + for ArrayType in [MutableDenseNDimArray, MutableSparseNDimArray]: + array = ArrayType(range(24)).reshape(2, 3, 4) + assert array.tolist() == [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]] + assert array[0] == ArrayType([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]) + assert array[0, 0] == ArrayType([0, 1, 2, 3]) + value = 0 + for i in range(2): + for j in range(3): + for k in range(4): + assert array[i, j, k] == value + value += 1 + + raises(ValueError, lambda: array[3, 4, 5]) + raises(ValueError, lambda: array[3, 4, 5, 6]) + raises(ValueError, lambda: array[3, 4, 5, 3:4]) + + +def test_sparse(): + sparse_array = MutableSparseNDimArray([0, 0, 0, 1], (2, 2)) + assert len(sparse_array) == 2 * 2 + # dictionary where all data is, only non-zero entries are actually stored: + assert len(sparse_array._sparse_array) == 1 + + assert sparse_array.tolist() == [[0, 0], [0, 1]] + + for i, j in zip(sparse_array, [[0, 0], [0, 1]]): + assert i == MutableSparseNDimArray(j) + + sparse_array[0, 0] = 123 + assert len(sparse_array._sparse_array) == 2 + assert sparse_array[0, 0] == 123 + assert sparse_array/0 == MutableSparseNDimArray([[S.ComplexInfinity, S.NaN], [S.NaN, S.ComplexInfinity]], (2, 2)) + + # when element in sparse array become zero it will disappear from + # dictionary + sparse_array[0, 0] = 0 + assert len(sparse_array._sparse_array) == 1 + sparse_array[1, 1] = 0 + assert len(sparse_array._sparse_array) == 0 + assert sparse_array[0, 0] == 0 + + # test for large scale sparse array + # equality test + a = MutableSparseNDimArray.zeros(100000, 200000) + b = MutableSparseNDimArray.zeros(100000, 200000) + assert a == b + a[1, 1] = 1 + b[1, 1] = 2 + assert a != b + + # __mul__ and __rmul__ + assert a * 3 == MutableSparseNDimArray({200001: 3}, (100000, 200000)) + assert 3 * a == MutableSparseNDimArray({200001: 3}, (100000, 200000)) + assert a * 0 == MutableSparseNDimArray({}, (100000, 200000)) + assert 0 * a == MutableSparseNDimArray({}, (100000, 200000)) + + # __truediv__ + assert a/3 == MutableSparseNDimArray({200001: Rational(1, 3)}, (100000, 200000)) + + # __neg__ + assert -a == MutableSparseNDimArray({200001: -1}, (100000, 200000)) + + +def test_calculation(): + + a = MutableDenseNDimArray([1]*9, (3, 3)) + b = MutableDenseNDimArray([9]*9, (3, 3)) + + c = a + b + for i in c: + assert i == MutableDenseNDimArray([10, 10, 10]) + + assert c == MutableDenseNDimArray([10]*9, (3, 3)) + assert c == MutableSparseNDimArray([10]*9, (3, 3)) + + c = b - a + for i in c: + assert i == MutableSparseNDimArray([8, 8, 8]) + + assert c == MutableDenseNDimArray([8]*9, (3, 3)) + assert c == MutableSparseNDimArray([8]*9, (3, 3)) + + +def test_ndim_array_converting(): + dense_array = MutableDenseNDimArray([1, 2, 3, 4], (2, 2)) + alist = dense_array.tolist() + + assert alist == [[1, 2], [3, 4]] + + matrix = dense_array.tomatrix() + assert (isinstance(matrix, Matrix)) + + for i in range(len(dense_array)): + assert dense_array[dense_array._get_tuple_index(i)] == matrix[i] + assert matrix.shape == dense_array.shape + + assert MutableDenseNDimArray(matrix) == dense_array + assert MutableDenseNDimArray(matrix.as_immutable()) == dense_array + assert MutableDenseNDimArray(matrix.as_mutable()) == dense_array + + sparse_array = MutableSparseNDimArray([1, 2, 3, 4], (2, 2)) + alist = sparse_array.tolist() + + assert alist == [[1, 2], [3, 4]] + + matrix = sparse_array.tomatrix() + assert(isinstance(matrix, SparseMatrix)) + + for i in range(len(sparse_array)): + assert sparse_array[sparse_array._get_tuple_index(i)] == matrix[i] + assert matrix.shape == sparse_array.shape + + assert MutableSparseNDimArray(matrix) == sparse_array + assert MutableSparseNDimArray(matrix.as_immutable()) == sparse_array + assert MutableSparseNDimArray(matrix.as_mutable()) == sparse_array + + +def test_converting_functions(): + arr_list = [1, 2, 3, 4] + arr_matrix = Matrix(((1, 2), (3, 4))) + + # list + arr_ndim_array = MutableDenseNDimArray(arr_list, (2, 2)) + assert (isinstance(arr_ndim_array, MutableDenseNDimArray)) + assert arr_matrix.tolist() == arr_ndim_array.tolist() + + # Matrix + arr_ndim_array = MutableDenseNDimArray(arr_matrix) + assert (isinstance(arr_ndim_array, MutableDenseNDimArray)) + assert arr_matrix.tolist() == arr_ndim_array.tolist() + assert arr_matrix.shape == arr_ndim_array.shape + + +def test_equality(): + first_list = [1, 2, 3, 4] + second_list = [1, 2, 3, 4] + third_list = [4, 3, 2, 1] + assert first_list == second_list + assert first_list != third_list + + first_ndim_array = MutableDenseNDimArray(first_list, (2, 2)) + second_ndim_array = MutableDenseNDimArray(second_list, (2, 2)) + third_ndim_array = MutableDenseNDimArray(third_list, (2, 2)) + fourth_ndim_array = MutableDenseNDimArray(first_list, (2, 2)) + + assert first_ndim_array == second_ndim_array + second_ndim_array[0, 0] = 0 + assert first_ndim_array != second_ndim_array + assert first_ndim_array != third_ndim_array + assert first_ndim_array == fourth_ndim_array + + +def test_arithmetic(): + a = MutableDenseNDimArray([3 for i in range(9)], (3, 3)) + b = MutableDenseNDimArray([7 for i in range(9)], (3, 3)) + + c1 = a + b + c2 = b + a + assert c1 == c2 + + d1 = a - b + d2 = b - a + assert d1 == d2 * (-1) + + e1 = a * 5 + e2 = 5 * a + e3 = copy(a) + e3 *= 5 + assert e1 == e2 == e3 + + f1 = a / 5 + f2 = copy(a) + f2 /= 5 + assert f1 == f2 + assert f1[0, 0] == f1[0, 1] == f1[0, 2] == f1[1, 0] == f1[1, 1] == \ + f1[1, 2] == f1[2, 0] == f1[2, 1] == f1[2, 2] == Rational(3, 5) + + assert type(a) == type(b) == type(c1) == type(c2) == type(d1) == type(d2) \ + == type(e1) == type(e2) == type(e3) == type(f1) + + z0 = -a + assert z0 == MutableDenseNDimArray([-3 for i in range(9)], (3, 3)) + + +def test_higher_dimenions(): + m3 = MutableDenseNDimArray(range(10, 34), (2, 3, 4)) + + assert m3.tolist() == [[[10, 11, 12, 13], + [14, 15, 16, 17], + [18, 19, 20, 21]], + + [[22, 23, 24, 25], + [26, 27, 28, 29], + [30, 31, 32, 33]]] + + assert m3._get_tuple_index(0) == (0, 0, 0) + assert m3._get_tuple_index(1) == (0, 0, 1) + assert m3._get_tuple_index(4) == (0, 1, 0) + assert m3._get_tuple_index(12) == (1, 0, 0) + + assert str(m3) == '[[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]]' + + m3_rebuilt = MutableDenseNDimArray([[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]]) + assert m3 == m3_rebuilt + + m3_other = MutableDenseNDimArray([[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]], (2, 3, 4)) + + assert m3 == m3_other + + +def test_slices(): + md = MutableDenseNDimArray(range(10, 34), (2, 3, 4)) + + assert md[:] == MutableDenseNDimArray(range(10, 34), (2, 3, 4)) + assert md[:, :, 0].tomatrix() == Matrix([[10, 14, 18], [22, 26, 30]]) + assert md[0, 1:2, :].tomatrix() == Matrix([[14, 15, 16, 17]]) + assert md[0, 1:3, :].tomatrix() == Matrix([[14, 15, 16, 17], [18, 19, 20, 21]]) + assert md[:, :, :] == md + + sd = MutableSparseNDimArray(range(10, 34), (2, 3, 4)) + assert sd == MutableSparseNDimArray(md) + + assert sd[:] == MutableSparseNDimArray(range(10, 34), (2, 3, 4)) + assert sd[:, :, 0].tomatrix() == Matrix([[10, 14, 18], [22, 26, 30]]) + assert sd[0, 1:2, :].tomatrix() == Matrix([[14, 15, 16, 17]]) + assert sd[0, 1:3, :].tomatrix() == Matrix([[14, 15, 16, 17], [18, 19, 20, 21]]) + assert sd[:, :, :] == sd + + +def test_slices_assign(): + a = MutableDenseNDimArray(range(12), shape=(4, 3)) + b = MutableSparseNDimArray(range(12), shape=(4, 3)) + + for i in [a, b]: + assert i.tolist() == [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]] + i[0, :] = [2, 2, 2] + assert i.tolist() == [[2, 2, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]] + i[0, 1:] = [8, 8] + assert i.tolist() == [[2, 8, 8], [3, 4, 5], [6, 7, 8], [9, 10, 11]] + i[1:3, 1] = [20, 44] + assert i.tolist() == [[2, 8, 8], [3, 20, 5], [6, 44, 8], [9, 10, 11]] + + +def test_diff(): + from sympy.abc import x, y, z + md = MutableDenseNDimArray([[x, y], [x*z, x*y*z]]) + assert md.diff(x) == MutableDenseNDimArray([[1, 0], [z, y*z]]) + assert diff(md, x) == MutableDenseNDimArray([[1, 0], [z, y*z]]) + + sd = MutableSparseNDimArray(md) + assert sd == MutableSparseNDimArray([x, y, x*z, x*y*z], (2, 2)) + assert sd.diff(x) == MutableSparseNDimArray([[1, 0], [z, y*z]]) + assert diff(sd, x) == MutableSparseNDimArray([[1, 0], [z, y*z]]) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_ndim_array.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_ndim_array.py new file mode 100644 index 0000000000000000000000000000000000000000..7ff9b032631c01272c00478e4cdf0dcbc6997990 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_ndim_array.py @@ -0,0 +1,73 @@ +from sympy.testing.pytest import raises +from sympy.functions.elementary.trigonometric import sin, cos +from sympy.matrices.dense import Matrix +from sympy.simplify import simplify +from sympy.tensor.array import Array +from sympy.tensor.array.dense_ndim_array import ( + ImmutableDenseNDimArray, MutableDenseNDimArray) +from sympy.tensor.array.sparse_ndim_array import ( + ImmutableSparseNDimArray, MutableSparseNDimArray) + +from sympy.abc import x, y + +mutable_array_types = [ + MutableDenseNDimArray, + MutableSparseNDimArray +] + +array_types = [ + ImmutableDenseNDimArray, + ImmutableSparseNDimArray, + MutableDenseNDimArray, + MutableSparseNDimArray +] + + +def test_array_negative_indices(): + for ArrayType in array_types: + test_array = ArrayType([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) + assert test_array[:, -1] == Array([5, 10]) + assert test_array[:, -2] == Array([4, 9]) + assert test_array[:, -3] == Array([3, 8]) + assert test_array[:, -4] == Array([2, 7]) + assert test_array[:, -5] == Array([1, 6]) + assert test_array[:, 0] == Array([1, 6]) + assert test_array[:, 1] == Array([2, 7]) + assert test_array[:, 2] == Array([3, 8]) + assert test_array[:, 3] == Array([4, 9]) + assert test_array[:, 4] == Array([5, 10]) + + raises(ValueError, lambda: test_array[:, -6]) + raises(ValueError, lambda: test_array[-3, :]) + + assert test_array[-1, -1] == 10 + + +def test_issue_18361(): + A = Array([sin(2 * x) - 2 * sin(x) * cos(x)]) + B = Array([sin(x)**2 + cos(x)**2, 0]) + C = Array([(x + x**2)/(x*sin(y)**2 + x*cos(y)**2), 2*sin(x)*cos(x)]) + assert simplify(A) == Array([0]) + assert simplify(B) == Array([1, 0]) + assert simplify(C) == Array([x + 1, sin(2*x)]) + + +def test_issue_20222(): + A = Array([[1, 2], [3, 4]]) + B = Matrix([[1,2],[3,4]]) + raises(TypeError, lambda: A - B) + + +def test_issue_17851(): + for array_type in array_types: + A = array_type([]) + assert isinstance(A, array_type) + assert A.shape == (0,) + assert list(A) == [] + + +def test_issue_and_18715(): + for array_type in mutable_array_types: + A = array_type([0, 1, 2]) + A[0] += 5 + assert A[0] == 5 diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_ndim_array_conversions.py b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_ndim_array_conversions.py new file mode 100644 index 0000000000000000000000000000000000000000..f43260ccc636ac461ba0c06dbfcf3fe3a8d5338d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/array/tests/test_ndim_array_conversions.py @@ -0,0 +1,22 @@ +from sympy.tensor.array import (ImmutableDenseNDimArray, + ImmutableSparseNDimArray, MutableDenseNDimArray, MutableSparseNDimArray) +from sympy.abc import x, y, z + + +def test_NDim_array_conv(): + MD = MutableDenseNDimArray([x, y, z]) + MS = MutableSparseNDimArray([x, y, z]) + ID = ImmutableDenseNDimArray([x, y, z]) + IS = ImmutableSparseNDimArray([x, y, z]) + + assert MD.as_immutable() == ID + assert MD.as_mutable() == MD + + assert MS.as_immutable() == IS + assert MS.as_mutable() == MS + + assert ID.as_immutable() == ID + assert ID.as_mutable() == MD + + assert IS.as_immutable() == IS + assert IS.as_mutable() == MS diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/functions.py b/venv/lib/python3.10/site-packages/sympy/tensor/functions.py new file mode 100644 index 0000000000000000000000000000000000000000..f14599d69152db1713f21c9dd785683901c5eeb9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/functions.py @@ -0,0 +1,154 @@ +from collections.abc import Iterable +from functools import singledispatch + +from sympy.core.expr import Expr +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.core.sympify import sympify +from sympy.core.parameters import global_parameters + + +class TensorProduct(Expr): + """ + Generic class for tensor products. + """ + is_number = False + + def __new__(cls, *args, **kwargs): + from sympy.tensor.array import NDimArray, tensorproduct, Array + from sympy.matrices.expressions.matexpr import MatrixExpr + from sympy.matrices.matrixbase import MatrixBase + from sympy.strategies import flatten + + args = [sympify(arg) for arg in args] + evaluate = kwargs.get("evaluate", global_parameters.evaluate) + + if not evaluate: + obj = Expr.__new__(cls, *args) + return obj + + arrays = [] + other = [] + scalar = S.One + for arg in args: + if isinstance(arg, (Iterable, MatrixBase, NDimArray)): + arrays.append(Array(arg)) + elif isinstance(arg, (MatrixExpr,)): + other.append(arg) + else: + scalar *= arg + + coeff = scalar*tensorproduct(*arrays) + if len(other) == 0: + return coeff + if coeff != 1: + newargs = [coeff] + other + else: + newargs = other + obj = Expr.__new__(cls, *newargs, **kwargs) + return flatten(obj) + + def rank(self): + return len(self.shape) + + def _get_args_shapes(self): + from sympy.tensor.array import Array + return [i.shape if hasattr(i, "shape") else Array(i).shape for i in self.args] + + @property + def shape(self): + shape_list = self._get_args_shapes() + return sum(shape_list, ()) + + def __getitem__(self, index): + index = iter(index) + return Mul.fromiter( + arg.__getitem__(tuple(next(index) for i in shp)) + for arg, shp in zip(self.args, self._get_args_shapes()) + ) + + +@singledispatch +def shape(expr): + """ + Return the shape of the *expr* as a tuple. *expr* should represent + suitable object such as matrix or array. + + Parameters + ========== + + expr : SymPy object having ``MatrixKind`` or ``ArrayKind``. + + Raises + ====== + + NoShapeError : Raised when object with wrong kind is passed. + + Examples + ======== + + This function returns the shape of any object representing matrix or array. + + >>> from sympy import shape, Array, ImmutableDenseMatrix, Integral + >>> from sympy.abc import x + >>> A = Array([1, 2]) + >>> shape(A) + (2,) + >>> shape(Integral(A, x)) + (2,) + >>> M = ImmutableDenseMatrix([1, 2]) + >>> shape(M) + (2, 1) + >>> shape(Integral(M, x)) + (2, 1) + + You can support new type by dispatching. + + >>> from sympy import Expr + >>> class NewExpr(Expr): + ... pass + >>> @shape.register(NewExpr) + ... def _(expr): + ... return shape(expr.args[0]) + >>> shape(NewExpr(M)) + (2, 1) + + If unsuitable expression is passed, ``NoShapeError()`` will be raised. + + >>> shape(Integral(x, x)) + Traceback (most recent call last): + ... + sympy.tensor.functions.NoShapeError: shape() called on non-array object: Integral(x, x) + + Notes + ===== + + Array-like classes (such as ``Matrix`` or ``NDimArray``) has ``shape`` + property which returns its shape, but it cannot be used for non-array + classes containing array. This function returns the shape of any + registered object representing array. + + """ + if hasattr(expr, "shape"): + return expr.shape + raise NoShapeError( + "%s does not have shape, or its type is not registered to shape()." % expr) + + +class NoShapeError(Exception): + """ + Raised when ``shape()`` is called on non-array object. + + This error can be imported from ``sympy.tensor.functions``. + + Examples + ======== + + >>> from sympy import shape + >>> from sympy.abc import x + >>> shape(x) + Traceback (most recent call last): + ... + sympy.tensor.functions.NoShapeError: shape() called on non-array object: x + """ + pass diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/index_methods.py b/venv/lib/python3.10/site-packages/sympy/tensor/index_methods.py new file mode 100644 index 0000000000000000000000000000000000000000..12f707b60b4ad0bcadc35a222d9abe0cc5e033fc --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/index_methods.py @@ -0,0 +1,469 @@ +"""Module with functions operating on IndexedBase, Indexed and Idx objects + + - Check shape conformance + - Determine indices in resulting expression + + etc. + + Methods in this module could be implemented by calling methods on Expr + objects instead. When things stabilize this could be a useful + refactoring. +""" + +from functools import reduce + +from sympy.core.function import Function +from sympy.functions import exp, Piecewise +from sympy.tensor.indexed import Idx, Indexed +from sympy.utilities import sift + +from collections import OrderedDict + +class IndexConformanceException(Exception): + pass + +def _unique_and_repeated(inds): + """ + Returns the unique and repeated indices. Also note, from the examples given below + that the order of indices is maintained as given in the input. + + Examples + ======== + + >>> from sympy.tensor.index_methods import _unique_and_repeated + >>> _unique_and_repeated([2, 3, 1, 3, 0, 4, 0]) + ([2, 1, 4], [3, 0]) + """ + uniq = OrderedDict() + for i in inds: + if i in uniq: + uniq[i] = 0 + else: + uniq[i] = 1 + return sift(uniq, lambda x: uniq[x], binary=True) + +def _remove_repeated(inds): + """ + Removes repeated objects from sequences + + Returns a set of the unique objects and a tuple of all that have been + removed. + + Examples + ======== + + >>> from sympy.tensor.index_methods import _remove_repeated + >>> l1 = [1, 2, 3, 2] + >>> _remove_repeated(l1) + ({1, 3}, (2,)) + + """ + u, r = _unique_and_repeated(inds) + return set(u), tuple(r) + + +def _get_indices_Mul(expr, return_dummies=False): + """Determine the outer indices of a Mul object. + + Examples + ======== + + >>> from sympy.tensor.index_methods import _get_indices_Mul + >>> from sympy.tensor.indexed import IndexedBase, Idx + >>> i, j, k = map(Idx, ['i', 'j', 'k']) + >>> x = IndexedBase('x') + >>> y = IndexedBase('y') + >>> _get_indices_Mul(x[i, k]*y[j, k]) + ({i, j}, {}) + >>> _get_indices_Mul(x[i, k]*y[j, k], return_dummies=True) + ({i, j}, {}, (k,)) + + """ + + inds = list(map(get_indices, expr.args)) + inds, syms = list(zip(*inds)) + + inds = list(map(list, inds)) + inds = list(reduce(lambda x, y: x + y, inds)) + inds, dummies = _remove_repeated(inds) + + symmetry = {} + for s in syms: + for pair in s: + if pair in symmetry: + symmetry[pair] *= s[pair] + else: + symmetry[pair] = s[pair] + + if return_dummies: + return inds, symmetry, dummies + else: + return inds, symmetry + + +def _get_indices_Pow(expr): + """Determine outer indices of a power or an exponential. + + A power is considered a universal function, so that the indices of a Pow is + just the collection of indices present in the expression. This may be + viewed as a bit inconsistent in the special case: + + x[i]**2 = x[i]*x[i] (1) + + The above expression could have been interpreted as the contraction of x[i] + with itself, but we choose instead to interpret it as a function + + lambda y: y**2 + + applied to each element of x (a universal function in numpy terms). In + order to allow an interpretation of (1) as a contraction, we need + contravariant and covariant Idx subclasses. (FIXME: this is not yet + implemented) + + Expressions in the base or exponent are subject to contraction as usual, + but an index that is present in the exponent, will not be considered + contractable with its own base. Note however, that indices in the same + exponent can be contracted with each other. + + Examples + ======== + + >>> from sympy.tensor.index_methods import _get_indices_Pow + >>> from sympy import Pow, exp, IndexedBase, Idx + >>> A = IndexedBase('A') + >>> x = IndexedBase('x') + >>> i, j, k = map(Idx, ['i', 'j', 'k']) + >>> _get_indices_Pow(exp(A[i, j]*x[j])) + ({i}, {}) + >>> _get_indices_Pow(Pow(x[i], x[i])) + ({i}, {}) + >>> _get_indices_Pow(Pow(A[i, j]*x[j], x[i])) + ({i}, {}) + + """ + base, exp = expr.as_base_exp() + binds, bsyms = get_indices(base) + einds, esyms = get_indices(exp) + + inds = binds | einds + + # FIXME: symmetries from power needs to check special cases, else nothing + symmetries = {} + + return inds, symmetries + + +def _get_indices_Add(expr): + """Determine outer indices of an Add object. + + In a sum, each term must have the same set of outer indices. A valid + expression could be + + x(i)*y(j) - x(j)*y(i) + + But we do not allow expressions like: + + x(i)*y(j) - z(j)*z(j) + + FIXME: Add support for Numpy broadcasting + + Examples + ======== + + >>> from sympy.tensor.index_methods import _get_indices_Add + >>> from sympy.tensor.indexed import IndexedBase, Idx + >>> i, j, k = map(Idx, ['i', 'j', 'k']) + >>> x = IndexedBase('x') + >>> y = IndexedBase('y') + >>> _get_indices_Add(x[i] + x[k]*y[i, k]) + ({i}, {}) + + """ + + inds = list(map(get_indices, expr.args)) + inds, syms = list(zip(*inds)) + + # allow broadcast of scalars + non_scalars = [x for x in inds if x != set()] + if not non_scalars: + return set(), {} + + if not all(x == non_scalars[0] for x in non_scalars[1:]): + raise IndexConformanceException("Indices are not consistent: %s" % expr) + if not reduce(lambda x, y: x != y or y, syms): + symmetries = syms[0] + else: + # FIXME: search for symmetries + symmetries = {} + + return non_scalars[0], symmetries + + +def get_indices(expr): + """Determine the outer indices of expression ``expr`` + + By *outer* we mean indices that are not summation indices. Returns a set + and a dict. The set contains outer indices and the dict contains + information about index symmetries. + + Examples + ======== + + >>> from sympy.tensor.index_methods import get_indices + >>> from sympy import symbols + >>> from sympy.tensor import IndexedBase + >>> x, y, A = map(IndexedBase, ['x', 'y', 'A']) + >>> i, j, a, z = symbols('i j a z', integer=True) + + The indices of the total expression is determined, Repeated indices imply a + summation, for instance the trace of a matrix A: + + >>> get_indices(A[i, i]) + (set(), {}) + + In the case of many terms, the terms are required to have identical + outer indices. Else an IndexConformanceException is raised. + + >>> get_indices(x[i] + A[i, j]*y[j]) + ({i}, {}) + + :Exceptions: + + An IndexConformanceException means that the terms ar not compatible, e.g. + + >>> get_indices(x[i] + y[j]) #doctest: +SKIP + (...) + IndexConformanceException: Indices are not consistent: x(i) + y(j) + + .. warning:: + The concept of *outer* indices applies recursively, starting on the deepest + level. This implies that dummies inside parenthesis are assumed to be + summed first, so that the following expression is handled gracefully: + + >>> get_indices((x[i] + A[i, j]*y[j])*x[j]) + ({i, j}, {}) + + This is correct and may appear convenient, but you need to be careful + with this as SymPy will happily .expand() the product, if requested. The + resulting expression would mix the outer ``j`` with the dummies inside + the parenthesis, which makes it a different expression. To be on the + safe side, it is best to avoid such ambiguities by using unique indices + for all contractions that should be held separate. + + """ + # We call ourself recursively to determine indices of sub expressions. + + # break recursion + if isinstance(expr, Indexed): + c = expr.indices + inds, dummies = _remove_repeated(c) + return inds, {} + elif expr is None: + return set(), {} + elif isinstance(expr, Idx): + return {expr}, {} + elif expr.is_Atom: + return set(), {} + + + # recurse via specialized functions + else: + if expr.is_Mul: + return _get_indices_Mul(expr) + elif expr.is_Add: + return _get_indices_Add(expr) + elif expr.is_Pow or isinstance(expr, exp): + return _get_indices_Pow(expr) + + elif isinstance(expr, Piecewise): + # FIXME: No support for Piecewise yet + return set(), {} + elif isinstance(expr, Function): + # Support ufunc like behaviour by returning indices from arguments. + # Functions do not interpret repeated indices across arguments + # as summation + ind0 = set() + for arg in expr.args: + ind, sym = get_indices(arg) + ind0 |= ind + return ind0, sym + + # this test is expensive, so it should be at the end + elif not expr.has(Indexed): + return set(), {} + raise NotImplementedError( + "FIXME: No specialized handling of type %s" % type(expr)) + + +def get_contraction_structure(expr): + """Determine dummy indices of ``expr`` and describe its structure + + By *dummy* we mean indices that are summation indices. + + The structure of the expression is determined and described as follows: + + 1) A conforming summation of Indexed objects is described with a dict where + the keys are summation indices and the corresponding values are sets + containing all terms for which the summation applies. All Add objects + in the SymPy expression tree are described like this. + + 2) For all nodes in the SymPy expression tree that are *not* of type Add, the + following applies: + + If a node discovers contractions in one of its arguments, the node + itself will be stored as a key in the dict. For that key, the + corresponding value is a list of dicts, each of which is the result of a + recursive call to get_contraction_structure(). The list contains only + dicts for the non-trivial deeper contractions, omitting dicts with None + as the one and only key. + + .. Note:: The presence of expressions among the dictionary keys indicates + multiple levels of index contractions. A nested dict displays nested + contractions and may itself contain dicts from a deeper level. In + practical calculations the summation in the deepest nested level must be + calculated first so that the outer expression can access the resulting + indexed object. + + Examples + ======== + + >>> from sympy.tensor.index_methods import get_contraction_structure + >>> from sympy import default_sort_key + >>> from sympy.tensor import IndexedBase, Idx + >>> x, y, A = map(IndexedBase, ['x', 'y', 'A']) + >>> i, j, k, l = map(Idx, ['i', 'j', 'k', 'l']) + >>> get_contraction_structure(x[i]*y[i] + A[j, j]) + {(i,): {x[i]*y[i]}, (j,): {A[j, j]}} + >>> get_contraction_structure(x[i]*y[j]) + {None: {x[i]*y[j]}} + + A multiplication of contracted factors results in nested dicts representing + the internal contractions. + + >>> d = get_contraction_structure(x[i, i]*y[j, j]) + >>> sorted(d.keys(), key=default_sort_key) + [None, x[i, i]*y[j, j]] + + In this case, the product has no contractions: + + >>> d[None] + {x[i, i]*y[j, j]} + + Factors are contracted "first": + + >>> sorted(d[x[i, i]*y[j, j]], key=default_sort_key) + [{(i,): {x[i, i]}}, {(j,): {y[j, j]}}] + + A parenthesized Add object is also returned as a nested dictionary. The + term containing the parenthesis is a Mul with a contraction among the + arguments, so it will be found as a key in the result. It stores the + dictionary resulting from a recursive call on the Add expression. + + >>> d = get_contraction_structure(x[i]*(y[i] + A[i, j]*x[j])) + >>> sorted(d.keys(), key=default_sort_key) + [(A[i, j]*x[j] + y[i])*x[i], (i,)] + >>> d[(i,)] + {(A[i, j]*x[j] + y[i])*x[i]} + >>> d[x[i]*(A[i, j]*x[j] + y[i])] + [{None: {y[i]}, (j,): {A[i, j]*x[j]}}] + + Powers with contractions in either base or exponent will also be found as + keys in the dictionary, mapping to a list of results from recursive calls: + + >>> d = get_contraction_structure(A[j, j]**A[i, i]) + >>> d[None] + {A[j, j]**A[i, i]} + >>> nested_contractions = d[A[j, j]**A[i, i]] + >>> nested_contractions[0] + {(j,): {A[j, j]}} + >>> nested_contractions[1] + {(i,): {A[i, i]}} + + The description of the contraction structure may appear complicated when + represented with a string in the above examples, but it is easy to iterate + over: + + >>> from sympy import Expr + >>> for key in d: + ... if isinstance(key, Expr): + ... continue + ... for term in d[key]: + ... if term in d: + ... # treat deepest contraction first + ... pass + ... # treat outermost contactions here + + """ + + # We call ourself recursively to inspect sub expressions. + + if isinstance(expr, Indexed): + junk, key = _remove_repeated(expr.indices) + return {key or None: {expr}} + elif expr.is_Atom: + return {None: {expr}} + elif expr.is_Mul: + junk, junk, key = _get_indices_Mul(expr, return_dummies=True) + result = {key or None: {expr}} + # recurse on every factor + nested = [] + for fac in expr.args: + facd = get_contraction_structure(fac) + if not (None in facd and len(facd) == 1): + nested.append(facd) + if nested: + result[expr] = nested + return result + elif expr.is_Pow or isinstance(expr, exp): + # recurse in base and exp separately. If either has internal + # contractions we must include ourselves as a key in the returned dict + b, e = expr.as_base_exp() + dbase = get_contraction_structure(b) + dexp = get_contraction_structure(e) + + dicts = [] + for d in dbase, dexp: + if not (None in d and len(d) == 1): + dicts.append(d) + result = {None: {expr}} + if dicts: + result[expr] = dicts + return result + elif expr.is_Add: + # Note: we just collect all terms with identical summation indices, We + # do nothing to identify equivalent terms here, as this would require + # substitutions or pattern matching in expressions of unknown + # complexity. + result = {} + for term in expr.args: + # recurse on every term + d = get_contraction_structure(term) + for key in d: + if key in result: + result[key] |= d[key] + else: + result[key] = d[key] + return result + + elif isinstance(expr, Piecewise): + # FIXME: No support for Piecewise yet + return {None: expr} + elif isinstance(expr, Function): + # Collect non-trivial contraction structures in each argument + # We do not report repeated indices in separate arguments as a + # contraction + deeplist = [] + for arg in expr.args: + deep = get_contraction_structure(arg) + if not (None in deep and len(deep) == 1): + deeplist.append(deep) + d = {None: {expr}} + if deeplist: + d[expr] = deeplist + return d + + # this test is expensive, so it should be at the end + elif not expr.has(Indexed): + return {None: {expr}} + raise NotImplementedError( + "FIXME: No specialized handling of type %s" % type(expr)) diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/indexed.py b/venv/lib/python3.10/site-packages/sympy/tensor/indexed.py new file mode 100644 index 0000000000000000000000000000000000000000..feddad21e52bbab2e1243beafdb11f30b2eded4d --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/indexed.py @@ -0,0 +1,793 @@ +r"""Module that defines indexed objects. + +The classes ``IndexedBase``, ``Indexed``, and ``Idx`` represent a +matrix element ``M[i, j]`` as in the following diagram:: + + 1) The Indexed class represents the entire indexed object. + | + ___|___ + ' ' + M[i, j] + / \__\______ + | | + | | + | 2) The Idx class represents indices; each Idx can + | optionally contain information about its range. + | + 3) IndexedBase represents the 'stem' of an indexed object, here `M`. + The stem used by itself is usually taken to represent the entire + array. + +There can be any number of indices on an Indexed object. No +transformation properties are implemented in these Base objects, but +implicit contraction of repeated indices is supported. + +Note that the support for complicated (i.e. non-atomic) integer +expressions as indices is limited. (This should be improved in +future releases.) + +Examples +======== + +To express the above matrix element example you would write: + +>>> from sympy import symbols, IndexedBase, Idx +>>> M = IndexedBase('M') +>>> i, j = symbols('i j', cls=Idx) +>>> M[i, j] +M[i, j] + +Repeated indices in a product implies a summation, so to express a +matrix-vector product in terms of Indexed objects: + +>>> x = IndexedBase('x') +>>> M[i, j]*x[j] +M[i, j]*x[j] + +If the indexed objects will be converted to component based arrays, e.g. +with the code printers or the autowrap framework, you also need to provide +(symbolic or numerical) dimensions. This can be done by passing an +optional shape parameter to IndexedBase upon construction: + +>>> dim1, dim2 = symbols('dim1 dim2', integer=True) +>>> A = IndexedBase('A', shape=(dim1, 2*dim1, dim2)) +>>> A.shape +(dim1, 2*dim1, dim2) +>>> A[i, j, 3].shape +(dim1, 2*dim1, dim2) + +If an IndexedBase object has no shape information, it is assumed that the +array is as large as the ranges of its indices: + +>>> n, m = symbols('n m', integer=True) +>>> i = Idx('i', m) +>>> j = Idx('j', n) +>>> M[i, j].shape +(m, n) +>>> M[i, j].ranges +[(0, m - 1), (0, n - 1)] + +The above can be compared with the following: + +>>> A[i, 2, j].shape +(dim1, 2*dim1, dim2) +>>> A[i, 2, j].ranges +[(0, m - 1), None, (0, n - 1)] + +To analyze the structure of indexed expressions, you can use the methods +get_indices() and get_contraction_structure(): + +>>> from sympy.tensor import get_indices, get_contraction_structure +>>> get_indices(A[i, j, j]) +({i}, {}) +>>> get_contraction_structure(A[i, j, j]) +{(j,): {A[i, j, j]}} + +See the appropriate docstrings for a detailed explanation of the output. +""" + +# TODO: (some ideas for improvement) +# +# o test and guarantee numpy compatibility +# - implement full support for broadcasting +# - strided arrays +# +# o more functions to analyze indexed expressions +# - identify standard constructs, e.g matrix-vector product in a subexpression +# +# o functions to generate component based arrays (numpy and sympy.Matrix) +# - generate a single array directly from Indexed +# - convert simple sub-expressions +# +# o sophisticated indexing (possibly in subclasses to preserve simplicity) +# - Idx with range smaller than dimension of Indexed +# - Idx with stepsize != 1 +# - Idx with step determined by function call +from collections.abc import Iterable + +from sympy.core.numbers import Number +from sympy.core.assumptions import StdFactKB +from sympy.core import Expr, Tuple, sympify, S +from sympy.core.symbol import _filter_assumptions, Symbol +from sympy.core.logic import fuzzy_bool, fuzzy_not +from sympy.core.sympify import _sympify +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.multipledispatch import dispatch +from sympy.utilities.iterables import is_sequence, NotIterable +from sympy.utilities.misc import filldedent + + +class IndexException(Exception): + pass + + +class Indexed(Expr): + """Represents a mathematical object with indices. + + >>> from sympy import Indexed, IndexedBase, Idx, symbols + >>> i, j = symbols('i j', cls=Idx) + >>> Indexed('A', i, j) + A[i, j] + + It is recommended that ``Indexed`` objects be created by indexing ``IndexedBase``: + ``IndexedBase('A')[i, j]`` instead of ``Indexed(IndexedBase('A'), i, j)``. + + >>> A = IndexedBase('A') + >>> a_ij = A[i, j] # Prefer this, + >>> b_ij = Indexed(A, i, j) # over this. + >>> a_ij == b_ij + True + + """ + is_Indexed = True + is_symbol = True + is_Atom = True + + def __new__(cls, base, *args, **kw_args): + from sympy.tensor.array.ndim_array import NDimArray + from sympy.matrices.matrixbase import MatrixBase + + if not args: + raise IndexException("Indexed needs at least one index.") + if isinstance(base, (str, Symbol)): + base = IndexedBase(base) + elif not hasattr(base, '__getitem__') and not isinstance(base, IndexedBase): + raise TypeError(filldedent(""" + The base can only be replaced with a string, Symbol, + IndexedBase or an object with a method for getting + items (i.e. an object with a `__getitem__` method). + """)) + args = list(map(sympify, args)) + if isinstance(base, (NDimArray, Iterable, Tuple, MatrixBase)) and all(i.is_number for i in args): + if len(args) == 1: + return base[args[0]] + else: + return base[args] + + base = _sympify(base) + + obj = Expr.__new__(cls, base, *args, **kw_args) + + IndexedBase._set_assumptions(obj, base.assumptions0) + + return obj + + def _hashable_content(self): + return super()._hashable_content() + tuple(sorted(self.assumptions0.items())) + + @property + def name(self): + return str(self) + + @property + def _diff_wrt(self): + """Allow derivatives with respect to an ``Indexed`` object.""" + return True + + def _eval_derivative(self, wrt): + from sympy.tensor.array.ndim_array import NDimArray + + if isinstance(wrt, Indexed) and wrt.base == self.base: + if len(self.indices) != len(wrt.indices): + msg = "Different # of indices: d({!s})/d({!s})".format(self, + wrt) + raise IndexException(msg) + result = S.One + for index1, index2 in zip(self.indices, wrt.indices): + result *= KroneckerDelta(index1, index2) + return result + elif isinstance(self.base, NDimArray): + from sympy.tensor.array import derive_by_array + return Indexed(derive_by_array(self.base, wrt), *self.args[1:]) + else: + if Tuple(self.indices).has(wrt): + return S.NaN + return S.Zero + + @property + def assumptions0(self): + return {k: v for k, v in self._assumptions.items() if v is not None} + + @property + def base(self): + """Returns the ``IndexedBase`` of the ``Indexed`` object. + + Examples + ======== + + >>> from sympy import Indexed, IndexedBase, Idx, symbols + >>> i, j = symbols('i j', cls=Idx) + >>> Indexed('A', i, j).base + A + >>> B = IndexedBase('B') + >>> B == B[i, j].base + True + + """ + return self.args[0] + + @property + def indices(self): + """ + Returns the indices of the ``Indexed`` object. + + Examples + ======== + + >>> from sympy import Indexed, Idx, symbols + >>> i, j = symbols('i j', cls=Idx) + >>> Indexed('A', i, j).indices + (i, j) + + """ + return self.args[1:] + + @property + def rank(self): + """ + Returns the rank of the ``Indexed`` object. + + Examples + ======== + + >>> from sympy import Indexed, Idx, symbols + >>> i, j, k, l, m = symbols('i:m', cls=Idx) + >>> Indexed('A', i, j).rank + 2 + >>> q = Indexed('A', i, j, k, l, m) + >>> q.rank + 5 + >>> q.rank == len(q.indices) + True + + """ + return len(self.args) - 1 + + @property + def shape(self): + """Returns a list with dimensions of each index. + + Dimensions is a property of the array, not of the indices. Still, if + the ``IndexedBase`` does not define a shape attribute, it is assumed + that the ranges of the indices correspond to the shape of the array. + + >>> from sympy import IndexedBase, Idx, symbols + >>> n, m = symbols('n m', integer=True) + >>> i = Idx('i', m) + >>> j = Idx('j', m) + >>> A = IndexedBase('A', shape=(n, n)) + >>> B = IndexedBase('B') + >>> A[i, j].shape + (n, n) + >>> B[i, j].shape + (m, m) + """ + + if self.base.shape: + return self.base.shape + sizes = [] + for i in self.indices: + upper = getattr(i, 'upper', None) + lower = getattr(i, 'lower', None) + if None in (upper, lower): + raise IndexException(filldedent(""" + Range is not defined for all indices in: %s""" % self)) + try: + size = upper - lower + 1 + except TypeError: + raise IndexException(filldedent(""" + Shape cannot be inferred from Idx with + undefined range: %s""" % self)) + sizes.append(size) + return Tuple(*sizes) + + @property + def ranges(self): + """Returns a list of tuples with lower and upper range of each index. + + If an index does not define the data members upper and lower, the + corresponding slot in the list contains ``None`` instead of a tuple. + + Examples + ======== + + >>> from sympy import Indexed,Idx, symbols + >>> Indexed('A', Idx('i', 2), Idx('j', 4), Idx('k', 8)).ranges + [(0, 1), (0, 3), (0, 7)] + >>> Indexed('A', Idx('i', 3), Idx('j', 3), Idx('k', 3)).ranges + [(0, 2), (0, 2), (0, 2)] + >>> x, y, z = symbols('x y z', integer=True) + >>> Indexed('A', x, y, z).ranges + [None, None, None] + + """ + ranges = [] + sentinel = object() + for i in self.indices: + upper = getattr(i, 'upper', sentinel) + lower = getattr(i, 'lower', sentinel) + if sentinel not in (upper, lower): + ranges.append((lower, upper)) + else: + ranges.append(None) + return ranges + + def _sympystr(self, p): + indices = list(map(p.doprint, self.indices)) + return "%s[%s]" % (p.doprint(self.base), ", ".join(indices)) + + @property + def free_symbols(self): + base_free_symbols = self.base.free_symbols + indices_free_symbols = { + fs for i in self.indices for fs in i.free_symbols} + if base_free_symbols: + return {self} | base_free_symbols | indices_free_symbols + else: + return indices_free_symbols + + @property + def expr_free_symbols(self): + from sympy.utilities.exceptions import sympy_deprecation_warning + sympy_deprecation_warning(""" + The expr_free_symbols property is deprecated. Use free_symbols to get + the free symbols of an expression. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-expr-free-symbols") + + return {self} + + +class IndexedBase(Expr, NotIterable): + """Represent the base or stem of an indexed object + + The IndexedBase class represent an array that contains elements. The main purpose + of this class is to allow the convenient creation of objects of the Indexed + class. The __getitem__ method of IndexedBase returns an instance of + Indexed. Alone, without indices, the IndexedBase class can be used as a + notation for e.g. matrix equations, resembling what you could do with the + Symbol class. But, the IndexedBase class adds functionality that is not + available for Symbol instances: + + - An IndexedBase object can optionally store shape information. This can + be used in to check array conformance and conditions for numpy + broadcasting. (TODO) + - An IndexedBase object implements syntactic sugar that allows easy symbolic + representation of array operations, using implicit summation of + repeated indices. + - The IndexedBase object symbolizes a mathematical structure equivalent + to arrays, and is recognized as such for code generation and automatic + compilation and wrapping. + + >>> from sympy.tensor import IndexedBase, Idx + >>> from sympy import symbols + >>> A = IndexedBase('A'); A + A + >>> type(A) + + + When an IndexedBase object receives indices, it returns an array with named + axes, represented by an Indexed object: + + >>> i, j = symbols('i j', integer=True) + >>> A[i, j, 2] + A[i, j, 2] + >>> type(A[i, j, 2]) + + + The IndexedBase constructor takes an optional shape argument. If given, + it overrides any shape information in the indices. (But not the index + ranges!) + + >>> m, n, o, p = symbols('m n o p', integer=True) + >>> i = Idx('i', m) + >>> j = Idx('j', n) + >>> A[i, j].shape + (m, n) + >>> B = IndexedBase('B', shape=(o, p)) + >>> B[i, j].shape + (o, p) + + Assumptions can be specified with keyword arguments the same way as for Symbol: + + >>> A_real = IndexedBase('A', real=True) + >>> A_real.is_real + True + >>> A != A_real + True + + Assumptions can also be inherited if a Symbol is used to initialize the IndexedBase: + + >>> I = symbols('I', integer=True) + >>> C_inherit = IndexedBase(I) + >>> C_explicit = IndexedBase('I', integer=True) + >>> C_inherit == C_explicit + True + """ + is_symbol = True + is_Atom = True + + @staticmethod + def _set_assumptions(obj, assumptions): + """Set assumptions on obj, making sure to apply consistent values.""" + tmp_asm_copy = assumptions.copy() + is_commutative = fuzzy_bool(assumptions.get('commutative', True)) + assumptions['commutative'] = is_commutative + obj._assumptions = StdFactKB(assumptions) + obj._assumptions._generator = tmp_asm_copy # Issue #8873 + + def __new__(cls, label, shape=None, *, offset=S.Zero, strides=None, **kw_args): + from sympy.matrices.matrixbase import MatrixBase + from sympy.tensor.array.ndim_array import NDimArray + + assumptions, kw_args = _filter_assumptions(kw_args) + if isinstance(label, str): + label = Symbol(label, **assumptions) + elif isinstance(label, Symbol): + assumptions = label._merge(assumptions) + elif isinstance(label, (MatrixBase, NDimArray)): + return label + elif isinstance(label, Iterable): + return _sympify(label) + else: + label = _sympify(label) + + if is_sequence(shape): + shape = Tuple(*shape) + elif shape is not None: + shape = Tuple(shape) + + if shape is not None: + obj = Expr.__new__(cls, label, shape) + else: + obj = Expr.__new__(cls, label) + obj._shape = shape + obj._offset = offset + obj._strides = strides + obj._name = str(label) + + IndexedBase._set_assumptions(obj, assumptions) + return obj + + @property + def name(self): + return self._name + + def _hashable_content(self): + return super()._hashable_content() + tuple(sorted(self.assumptions0.items())) + + @property + def assumptions0(self): + return {k: v for k, v in self._assumptions.items() if v is not None} + + def __getitem__(self, indices, **kw_args): + if is_sequence(indices): + # Special case needed because M[*my_tuple] is a syntax error. + if self.shape and len(self.shape) != len(indices): + raise IndexException("Rank mismatch.") + return Indexed(self, *indices, **kw_args) + else: + if self.shape and len(self.shape) != 1: + raise IndexException("Rank mismatch.") + return Indexed(self, indices, **kw_args) + + @property + def shape(self): + """Returns the shape of the ``IndexedBase`` object. + + Examples + ======== + + >>> from sympy import IndexedBase, Idx + >>> from sympy.abc import x, y + >>> IndexedBase('A', shape=(x, y)).shape + (x, y) + + Note: If the shape of the ``IndexedBase`` is specified, it will override + any shape information given by the indices. + + >>> A = IndexedBase('A', shape=(x, y)) + >>> B = IndexedBase('B') + >>> i = Idx('i', 2) + >>> j = Idx('j', 1) + >>> A[i, j].shape + (x, y) + >>> B[i, j].shape + (2, 1) + + """ + return self._shape + + @property + def strides(self): + """Returns the strided scheme for the ``IndexedBase`` object. + + Normally this is a tuple denoting the number of + steps to take in the respective dimension when traversing + an array. For code generation purposes strides='C' and + strides='F' can also be used. + + strides='C' would mean that code printer would unroll + in row-major order and 'F' means unroll in column major + order. + + """ + + return self._strides + + @property + def offset(self): + """Returns the offset for the ``IndexedBase`` object. + + This is the value added to the resulting index when the + 2D Indexed object is unrolled to a 1D form. Used in code + generation. + + Examples + ========== + >>> from sympy.printing import ccode + >>> from sympy.tensor import IndexedBase, Idx + >>> from sympy import symbols + >>> l, m, n, o = symbols('l m n o', integer=True) + >>> A = IndexedBase('A', strides=(l, m, n), offset=o) + >>> i, j, k = map(Idx, 'ijk') + >>> ccode(A[i, j, k]) + 'A[l*i + m*j + n*k + o]' + + """ + return self._offset + + @property + def label(self): + """Returns the label of the ``IndexedBase`` object. + + Examples + ======== + + >>> from sympy import IndexedBase + >>> from sympy.abc import x, y + >>> IndexedBase('A', shape=(x, y)).label + A + + """ + return self.args[0] + + def _sympystr(self, p): + return p.doprint(self.label) + + +class Idx(Expr): + """Represents an integer index as an ``Integer`` or integer expression. + + There are a number of ways to create an ``Idx`` object. The constructor + takes two arguments: + + ``label`` + An integer or a symbol that labels the index. + ``range`` + Optionally you can specify a range as either + + * ``Symbol`` or integer: This is interpreted as a dimension. Lower and + upper bounds are set to ``0`` and ``range - 1``, respectively. + * ``tuple``: The two elements are interpreted as the lower and upper + bounds of the range, respectively. + + Note: bounds of the range are assumed to be either integer or infinite (oo + and -oo are allowed to specify an unbounded range). If ``n`` is given as a + bound, then ``n.is_integer`` must not return false. + + For convenience, if the label is given as a string it is automatically + converted to an integer symbol. (Note: this conversion is not done for + range or dimension arguments.) + + Examples + ======== + + >>> from sympy import Idx, symbols, oo + >>> n, i, L, U = symbols('n i L U', integer=True) + + If a string is given for the label an integer ``Symbol`` is created and the + bounds are both ``None``: + + >>> idx = Idx('qwerty'); idx + qwerty + >>> idx.lower, idx.upper + (None, None) + + Both upper and lower bounds can be specified: + + >>> idx = Idx(i, (L, U)); idx + i + >>> idx.lower, idx.upper + (L, U) + + When only a single bound is given it is interpreted as the dimension + and the lower bound defaults to 0: + + >>> idx = Idx(i, n); idx.lower, idx.upper + (0, n - 1) + >>> idx = Idx(i, 4); idx.lower, idx.upper + (0, 3) + >>> idx = Idx(i, oo); idx.lower, idx.upper + (0, oo) + + """ + + is_integer = True + is_finite = True + is_real = True + is_symbol = True + is_Atom = True + _diff_wrt = True + + def __new__(cls, label, range=None, **kw_args): + + if isinstance(label, str): + label = Symbol(label, integer=True) + label, range = list(map(sympify, (label, range))) + + if label.is_Number: + if not label.is_integer: + raise TypeError("Index is not an integer number.") + return label + + if not label.is_integer: + raise TypeError("Idx object requires an integer label.") + + elif is_sequence(range): + if len(range) != 2: + raise ValueError(filldedent(""" + Idx range tuple must have length 2, but got %s""" % len(range))) + for bound in range: + if (bound.is_integer is False and bound is not S.Infinity + and bound is not S.NegativeInfinity): + raise TypeError("Idx object requires integer bounds.") + args = label, Tuple(*range) + elif isinstance(range, Expr): + if range is not S.Infinity and fuzzy_not(range.is_integer): + raise TypeError("Idx object requires an integer dimension.") + args = label, Tuple(0, range - 1) + elif range: + raise TypeError(filldedent(""" + The range must be an ordered iterable or + integer SymPy expression.""")) + else: + args = label, + + obj = Expr.__new__(cls, *args, **kw_args) + obj._assumptions["finite"] = True + obj._assumptions["real"] = True + return obj + + @property + def label(self): + """Returns the label (Integer or integer expression) of the Idx object. + + Examples + ======== + + >>> from sympy import Idx, Symbol + >>> x = Symbol('x', integer=True) + >>> Idx(x).label + x + >>> j = Symbol('j', integer=True) + >>> Idx(j).label + j + >>> Idx(j + 1).label + j + 1 + + """ + return self.args[0] + + @property + def lower(self): + """Returns the lower bound of the ``Idx``. + + Examples + ======== + + >>> from sympy import Idx + >>> Idx('j', 2).lower + 0 + >>> Idx('j', 5).lower + 0 + >>> Idx('j').lower is None + True + + """ + try: + return self.args[1][0] + except IndexError: + return + + @property + def upper(self): + """Returns the upper bound of the ``Idx``. + + Examples + ======== + + >>> from sympy import Idx + >>> Idx('j', 2).upper + 1 + >>> Idx('j', 5).upper + 4 + >>> Idx('j').upper is None + True + + """ + try: + return self.args[1][1] + except IndexError: + return + + def _sympystr(self, p): + return p.doprint(self.label) + + @property + def name(self): + return self.label.name if self.label.is_Symbol else str(self.label) + + @property + def free_symbols(self): + return {self} + + +@dispatch(Idx, Idx) +def _eval_is_ge(lhs, rhs): # noqa:F811 + + other_upper = rhs if rhs.upper is None else rhs.upper + other_lower = rhs if rhs.lower is None else rhs.lower + + if lhs.lower is not None and (lhs.lower >= other_upper) == True: + return True + if lhs.upper is not None and (lhs.upper < other_lower) == True: + return False + return None + + +@dispatch(Idx, Number) # type:ignore +def _eval_is_ge(lhs, rhs): # noqa:F811 + + other_upper = rhs + other_lower = rhs + + if lhs.lower is not None and (lhs.lower >= other_upper) == True: + return True + if lhs.upper is not None and (lhs.upper < other_lower) == True: + return False + return None + + +@dispatch(Number, Idx) # type:ignore +def _eval_is_ge(lhs, rhs): # noqa:F811 + + other_upper = lhs + other_lower = lhs + + if rhs.upper is not None and (rhs.upper <= other_lower) == True: + return True + if rhs.lower is not None and (rhs.lower > other_upper) == True: + return False + return None diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tensor.py b/venv/lib/python3.10/site-packages/sympy/tensor/tensor.py new file mode 100644 index 0000000000000000000000000000000000000000..579e7c7a86c2a1f18ab889af32ce0053a729ff5f --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/tensor.py @@ -0,0 +1,5265 @@ +""" +This module defines tensors with abstract index notation. + +The abstract index notation has been first formalized by Penrose. + +Tensor indices are formal objects, with a tensor type; there is no +notion of index range, it is only possible to assign the dimension, +used to trace the Kronecker delta; the dimension can be a Symbol. + +The Einstein summation convention is used. +The covariant indices are indicated with a minus sign in front of the index. + +For instance the tensor ``t = p(a)*A(b,c)*q(-c)`` has the index ``c`` +contracted. + +A tensor expression ``t`` can be called; called with its +indices in sorted order it is equal to itself: +in the above example ``t(a, b) == t``; +one can call ``t`` with different indices; ``t(c, d) == p(c)*A(d,a)*q(-a)``. + +The contracted indices are dummy indices, internally they have no name, +the indices being represented by a graph-like structure. + +Tensors are put in canonical form using ``canon_bp``, which uses +the Butler-Portugal algorithm for canonicalization using the monoterm +symmetries of the tensors. + +If there is a (anti)symmetric metric, the indices can be raised and +lowered when the tensor is put in canonical form. +""" + +from __future__ import annotations +from typing import Any +from functools import reduce +from math import prod + +from abc import abstractmethod, ABC +from collections import defaultdict +import operator +import itertools + +from sympy.core.numbers import (Integer, Rational) +from sympy.combinatorics import Permutation +from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, \ + bsgs_direct_product, canonicalize, riemann_bsgs +from sympy.core import Basic, Expr, sympify, Add, Mul, S +from sympy.core.cache import clear_cache +from sympy.core.containers import Tuple, Dict +from sympy.core.function import WildFunction +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Symbol, symbols, Wild +from sympy.core.sympify import CantSympify, _sympify +from sympy.core.operations import AssocOp +from sympy.external.gmpy import SYMPY_INTS +from sympy.matrices import eye +from sympy.utilities.exceptions import (sympy_deprecation_warning, + SymPyDeprecationWarning, + ignore_warnings) +from sympy.utilities.decorator import memoize_property, deprecated +from sympy.utilities.iterables import sift + + +def deprecate_data(): + sympy_deprecation_warning( + """ + The data attribute of TensorIndexType is deprecated. Use The + replace_with_arrays() method instead. + """, + deprecated_since_version="1.4", + active_deprecations_target="deprecated-tensorindextype-attrs", + stacklevel=4, + ) + +def deprecate_fun_eval(): + sympy_deprecation_warning( + """ + The Tensor.fun_eval() method is deprecated. Use + Tensor.substitute_indices() instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensor-fun-eval", + stacklevel=4, + ) + + +def deprecate_call(): + sympy_deprecation_warning( + """ + Calling a tensor like Tensor(*indices) is deprecated. Use + Tensor.substitute_indices() instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensor-fun-eval", + stacklevel=4, + ) + + +class _IndexStructure(CantSympify): + """ + This class handles the indices (free and dummy ones). It contains the + algorithms to manage the dummy indices replacements and contractions of + free indices under multiplications of tensor expressions, as well as stuff + related to canonicalization sorting, getting the permutation of the + expression and so on. It also includes tools to get the ``TensorIndex`` + objects corresponding to the given index structure. + """ + + def __init__(self, free, dum, index_types, indices, canon_bp=False): + self.free = free + self.dum = dum + self.index_types = index_types + self.indices = indices + self._ext_rank = len(self.free) + 2*len(self.dum) + self.dum.sort(key=lambda x: x[0]) + + @staticmethod + def from_indices(*indices): + """ + Create a new ``_IndexStructure`` object from a list of ``indices``. + + Explanation + =========== + + ``indices`` ``TensorIndex`` objects, the indices. Contractions are + detected upon construction. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, _IndexStructure + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + >>> _IndexStructure.from_indices(m0, m1, -m1, m3) + _IndexStructure([(m0, 0), (m3, 3)], [(1, 2)], [Lorentz, Lorentz, Lorentz, Lorentz]) + """ + + free, dum = _IndexStructure._free_dum_from_indices(*indices) + index_types = [i.tensor_index_type for i in indices] + indices = _IndexStructure._replace_dummy_names(indices, free, dum) + return _IndexStructure(free, dum, index_types, indices) + + @staticmethod + def from_components_free_dum(components, free, dum): + index_types = [] + for component in components: + index_types.extend(component.index_types) + indices = _IndexStructure.generate_indices_from_free_dum_index_types(free, dum, index_types) + return _IndexStructure(free, dum, index_types, indices) + + @staticmethod + def _free_dum_from_indices(*indices): + """ + Convert ``indices`` into ``free``, ``dum`` for single component tensor. + + Explanation + =========== + + ``free`` list of tuples ``(index, pos, 0)``, + where ``pos`` is the position of index in + the list of indices formed by the component tensors + + ``dum`` list of tuples ``(pos_contr, pos_cov, 0, 0)`` + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, \ + _IndexStructure + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) + >>> _IndexStructure._free_dum_from_indices(m0, m1, -m1, m3) + ([(m0, 0), (m3, 3)], [(1, 2)]) + """ + n = len(indices) + if n == 1: + return [(indices[0], 0)], [] + + # find the positions of the free indices and of the dummy indices + free = [True]*len(indices) + index_dict = {} + dum = [] + for i, index in enumerate(indices): + name = index.name + typ = index.tensor_index_type + contr = index.is_up + if (name, typ) in index_dict: + # found a pair of dummy indices + is_contr, pos = index_dict[(name, typ)] + # check consistency and update free + if is_contr: + if contr: + raise ValueError('two equal contravariant indices in slots %d and %d' %(pos, i)) + else: + free[pos] = False + free[i] = False + else: + if contr: + free[pos] = False + free[i] = False + else: + raise ValueError('two equal covariant indices in slots %d and %d' %(pos, i)) + if contr: + dum.append((i, pos)) + else: + dum.append((pos, i)) + else: + index_dict[(name, typ)] = index.is_up, i + + free = [(index, i) for i, index in enumerate(indices) if free[i]] + free.sort() + return free, dum + + def get_indices(self): + """ + Get a list of indices, creating new tensor indices to complete dummy indices. + """ + return self.indices[:] + + @staticmethod + def generate_indices_from_free_dum_index_types(free, dum, index_types): + indices = [None]*(len(free)+2*len(dum)) + for idx, pos in free: + indices[pos] = idx + + generate_dummy_name = _IndexStructure._get_generator_for_dummy_indices(free) + for pos1, pos2 in dum: + typ1 = index_types[pos1] + indname = generate_dummy_name(typ1) + indices[pos1] = TensorIndex(indname, typ1, True) + indices[pos2] = TensorIndex(indname, typ1, False) + + return _IndexStructure._replace_dummy_names(indices, free, dum) + + @staticmethod + def _get_generator_for_dummy_indices(free): + cdt = defaultdict(int) + # if the free indices have names with dummy_name, start with an + # index higher than those for the dummy indices + # to avoid name collisions + for indx, ipos in free: + if indx.name.split('_')[0] == indx.tensor_index_type.dummy_name: + cdt[indx.tensor_index_type] = max(cdt[indx.tensor_index_type], int(indx.name.split('_')[1]) + 1) + + def dummy_name_gen(tensor_index_type): + nd = str(cdt[tensor_index_type]) + cdt[tensor_index_type] += 1 + return tensor_index_type.dummy_name + '_' + nd + + return dummy_name_gen + + @staticmethod + def _replace_dummy_names(indices, free, dum): + dum.sort(key=lambda x: x[0]) + new_indices = list(indices) + assert len(indices) == len(free) + 2*len(dum) + generate_dummy_name = _IndexStructure._get_generator_for_dummy_indices(free) + for ipos1, ipos2 in dum: + typ1 = new_indices[ipos1].tensor_index_type + indname = generate_dummy_name(typ1) + new_indices[ipos1] = TensorIndex(indname, typ1, True) + new_indices[ipos2] = TensorIndex(indname, typ1, False) + return new_indices + + def get_free_indices(self) -> list[TensorIndex]: + """ + Get a list of free indices. + """ + # get sorted indices according to their position: + free = sorted(self.free, key=lambda x: x[1]) + return [i[0] for i in free] + + def __str__(self): + return "_IndexStructure({}, {}, {})".format(self.free, self.dum, self.index_types) + + def __repr__(self): + return self.__str__() + + def _get_sorted_free_indices_for_canon(self): + sorted_free = self.free[:] + sorted_free.sort(key=lambda x: x[0]) + return sorted_free + + def _get_sorted_dum_indices_for_canon(self): + return sorted(self.dum, key=lambda x: x[0]) + + def _get_lexicographically_sorted_index_types(self): + permutation = self.indices_canon_args()[0] + index_types = [None]*self._ext_rank + for i, it in enumerate(self.index_types): + index_types[permutation(i)] = it + return index_types + + def _get_lexicographically_sorted_indices(self): + permutation = self.indices_canon_args()[0] + indices = [None]*self._ext_rank + for i, it in enumerate(self.indices): + indices[permutation(i)] = it + return indices + + def perm2tensor(self, g, is_canon_bp=False): + """ + Returns a ``_IndexStructure`` instance corresponding to the permutation ``g``. + + Explanation + =========== + + ``g`` permutation corresponding to the tensor in the representation + used in canonicalization + + ``is_canon_bp`` if True, then ``g`` is the permutation + corresponding to the canonical form of the tensor + """ + sorted_free = [i[0] for i in self._get_sorted_free_indices_for_canon()] + lex_index_types = self._get_lexicographically_sorted_index_types() + lex_indices = self._get_lexicographically_sorted_indices() + nfree = len(sorted_free) + rank = self._ext_rank + dum = [[None]*2 for i in range((rank - nfree)//2)] + free = [] + + index_types = [None]*rank + indices = [None]*rank + for i in range(rank): + gi = g[i] + index_types[i] = lex_index_types[gi] + indices[i] = lex_indices[gi] + if gi < nfree: + ind = sorted_free[gi] + assert index_types[i] == sorted_free[gi].tensor_index_type + free.append((ind, i)) + else: + j = gi - nfree + idum, cov = divmod(j, 2) + if cov: + dum[idum][1] = i + else: + dum[idum][0] = i + dum = [tuple(x) for x in dum] + + return _IndexStructure(free, dum, index_types, indices) + + def indices_canon_args(self): + """ + Returns ``(g, dummies, msym, v)``, the entries of ``canonicalize`` + + See ``canonicalize`` in ``tensor_can.py`` in combinatorics module. + """ + # to be called after sorted_components + from sympy.combinatorics.permutations import _af_new + n = self._ext_rank + g = [None]*n + [n, n+1] + + # Converts the symmetry of the metric into msym from .canonicalize() + # method in the combinatorics module + def metric_symmetry_to_msym(metric): + if metric is None: + return None + sym = metric.symmetry + if sym == TensorSymmetry.fully_symmetric(2): + return 0 + if sym == TensorSymmetry.fully_symmetric(-2): + return 1 + return None + + # ordered indices: first the free indices, ordered by types + # then the dummy indices, ordered by types and contravariant before + # covariant + # g[position in tensor] = position in ordered indices + for i, (indx, ipos) in enumerate(self._get_sorted_free_indices_for_canon()): + g[ipos] = i + pos = len(self.free) + j = len(self.free) + dummies = [] + prev = None + a = [] + msym = [] + for ipos1, ipos2 in self._get_sorted_dum_indices_for_canon(): + g[ipos1] = j + g[ipos2] = j + 1 + j += 2 + typ = self.index_types[ipos1] + if typ != prev: + if a: + dummies.append(a) + a = [pos, pos + 1] + prev = typ + msym.append(metric_symmetry_to_msym(typ.metric)) + else: + a.extend([pos, pos + 1]) + pos += 2 + if a: + dummies.append(a) + + return _af_new(g), dummies, msym + + +def components_canon_args(components): + numtyp = [] + prev = None + for t in components: + if t == prev: + numtyp[-1][1] += 1 + else: + prev = t + numtyp.append([prev, 1]) + v = [] + for h, n in numtyp: + if h.comm in (0, 1): + comm = h.comm + else: + comm = TensorManager.get_comm(h.comm, h.comm) + v.append((h.symmetry.base, h.symmetry.generators, n, comm)) + return v + + +class _TensorDataLazyEvaluator(CantSympify): + """ + EXPERIMENTAL: do not rely on this class, it may change without deprecation + warnings in future versions of SymPy. + + Explanation + =========== + + This object contains the logic to associate components data to a tensor + expression. Components data are set via the ``.data`` property of tensor + expressions, is stored inside this class as a mapping between the tensor + expression and the ``ndarray``. + + Computations are executed lazily: whereas the tensor expressions can have + contractions, tensor products, and additions, components data are not + computed until they are accessed by reading the ``.data`` property + associated to the tensor expression. + """ + _substitutions_dict: dict[Any, Any] = {} + _substitutions_dict_tensmul: dict[Any, Any] = {} + + def __getitem__(self, key): + dat = self._get(key) + if dat is None: + return None + + from .array import NDimArray + if not isinstance(dat, NDimArray): + return dat + + if dat.rank() == 0: + return dat[()] + elif dat.rank() == 1 and len(dat) == 1: + return dat[0] + return dat + + def _get(self, key): + """ + Retrieve ``data`` associated with ``key``. + + Explanation + =========== + + This algorithm looks into ``self._substitutions_dict`` for all + ``TensorHead`` in the ``TensExpr`` (or just ``TensorHead`` if key is a + TensorHead instance). It reconstructs the components data that the + tensor expression should have by performing on components data the + operations that correspond to the abstract tensor operations applied. + + Metric tensor is handled in a different manner: it is pre-computed in + ``self._substitutions_dict_tensmul``. + """ + if key in self._substitutions_dict: + return self._substitutions_dict[key] + + if isinstance(key, TensorHead): + return None + + if isinstance(key, Tensor): + # special case to handle metrics. Metric tensors cannot be + # constructed through contraction by the metric, their + # components show if they are a matrix or its inverse. + signature = tuple([i.is_up for i in key.get_indices()]) + srch = (key.component,) + signature + if srch in self._substitutions_dict_tensmul: + return self._substitutions_dict_tensmul[srch] + array_list = [self.data_from_tensor(key)] + return self.data_contract_dum(array_list, key.dum, key.ext_rank) + + if isinstance(key, TensMul): + tensmul_args = key.args + if len(tensmul_args) == 1 and len(tensmul_args[0].components) == 1: + # special case to handle metrics. Metric tensors cannot be + # constructed through contraction by the metric, their + # components show if they are a matrix or its inverse. + signature = tuple([i.is_up for i in tensmul_args[0].get_indices()]) + srch = (tensmul_args[0].components[0],) + signature + if srch in self._substitutions_dict_tensmul: + return self._substitutions_dict_tensmul[srch] + #data_list = [self.data_from_tensor(i) for i in tensmul_args if isinstance(i, TensExpr)] + data_list = [self.data_from_tensor(i) if isinstance(i, Tensor) else i.data for i in tensmul_args if isinstance(i, TensExpr)] + coeff = prod([i for i in tensmul_args if not isinstance(i, TensExpr)]) + if all(i is None for i in data_list): + return None + if any(i is None for i in data_list): + raise ValueError("Mixing tensors with associated components "\ + "data with tensors without components data") + data_result = self.data_contract_dum(data_list, key.dum, key.ext_rank) + return coeff*data_result + + if isinstance(key, TensAdd): + data_list = [] + free_args_list = [] + for arg in key.args: + if isinstance(arg, TensExpr): + data_list.append(arg.data) + free_args_list.append([x[0] for x in arg.free]) + else: + data_list.append(arg) + free_args_list.append([]) + if all(i is None for i in data_list): + return None + if any(i is None for i in data_list): + raise ValueError("Mixing tensors with associated components "\ + "data with tensors without components data") + + sum_list = [] + from .array import permutedims + for data, free_args in zip(data_list, free_args_list): + if len(free_args) < 2: + sum_list.append(data) + else: + free_args_pos = {y: x for x, y in enumerate(free_args)} + axes = [free_args_pos[arg] for arg in key.free_args] + sum_list.append(permutedims(data, axes)) + return reduce(lambda x, y: x+y, sum_list) + + return None + + @staticmethod + def data_contract_dum(ndarray_list, dum, ext_rank): + from .array import tensorproduct, tensorcontraction, MutableDenseNDimArray + arrays = list(map(MutableDenseNDimArray, ndarray_list)) + prodarr = tensorproduct(*arrays) + return tensorcontraction(prodarr, *dum) + + def data_tensorhead_from_tensmul(self, data, tensmul, tensorhead): + """ + This method is used when assigning components data to a ``TensMul`` + object, it converts components data to a fully contravariant ndarray, + which is then stored according to the ``TensorHead`` key. + """ + if data is None: + return None + + return self._correct_signature_from_indices( + data, + tensmul.get_indices(), + tensmul.free, + tensmul.dum, + True) + + def data_from_tensor(self, tensor): + """ + This method corrects the components data to the right signature + (covariant/contravariant) using the metric associated with each + ``TensorIndexType``. + """ + tensorhead = tensor.component + + if tensorhead.data is None: + return None + + return self._correct_signature_from_indices( + tensorhead.data, + tensor.get_indices(), + tensor.free, + tensor.dum) + + def _assign_data_to_tensor_expr(self, key, data): + if isinstance(key, TensAdd): + raise ValueError('cannot assign data to TensAdd') + # here it is assumed that `key` is a `TensMul` instance. + if len(key.components) != 1: + raise ValueError('cannot assign data to TensMul with multiple components') + tensorhead = key.components[0] + newdata = self.data_tensorhead_from_tensmul(data, key, tensorhead) + return tensorhead, newdata + + def _check_permutations_on_data(self, tens, data): + from .array import permutedims + from .array.arrayop import Flatten + + if isinstance(tens, TensorHead): + rank = tens.rank + generators = tens.symmetry.generators + elif isinstance(tens, Tensor): + rank = tens.rank + generators = tens.components[0].symmetry.generators + elif isinstance(tens, TensorIndexType): + rank = tens.metric.rank + generators = tens.metric.symmetry.generators + + # Every generator is a permutation, check that by permuting the array + # by that permutation, the array will be the same, except for a + # possible sign change if the permutation admits it. + for gener in generators: + sign_change = +1 if (gener(rank) == rank) else -1 + data_swapped = data + last_data = data + permute_axes = list(map(gener, range(rank))) + # the order of a permutation is the number of times to get the + # identity by applying that permutation. + for i in range(gener.order()-1): + data_swapped = permutedims(data_swapped, permute_axes) + # if any value in the difference array is non-zero, raise an error: + if any(Flatten(last_data - sign_change*data_swapped)): + raise ValueError("Component data symmetry structure error") + last_data = data_swapped + + def __setitem__(self, key, value): + """ + Set the components data of a tensor object/expression. + + Explanation + =========== + + Components data are transformed to the all-contravariant form and stored + with the corresponding ``TensorHead`` object. If a ``TensorHead`` object + cannot be uniquely identified, it will raise an error. + """ + data = _TensorDataLazyEvaluator.parse_data(value) + self._check_permutations_on_data(key, data) + + # TensorHead and TensorIndexType can be assigned data directly, while + # TensMul must first convert data to a fully contravariant form, and + # assign it to its corresponding TensorHead single component. + if not isinstance(key, (TensorHead, TensorIndexType)): + key, data = self._assign_data_to_tensor_expr(key, data) + + if isinstance(key, TensorHead): + for dim, indextype in zip(data.shape, key.index_types): + if indextype.data is None: + raise ValueError("index type {} has no components data"\ + " associated (needed to raise/lower index)".format(indextype)) + if not indextype.dim.is_number: + continue + if dim != indextype.dim: + raise ValueError("wrong dimension of ndarray") + self._substitutions_dict[key] = data + + def __delitem__(self, key): + del self._substitutions_dict[key] + + def __contains__(self, key): + return key in self._substitutions_dict + + def add_metric_data(self, metric, data): + """ + Assign data to the ``metric`` tensor. The metric tensor behaves in an + anomalous way when raising and lowering indices. + + Explanation + =========== + + A fully covariant metric is the inverse transpose of the fully + contravariant metric (it is meant matrix inverse). If the metric is + symmetric, the transpose is not necessary and mixed + covariant/contravariant metrics are Kronecker deltas. + """ + # hard assignment, data should not be added to `TensorHead` for metric: + # the problem with `TensorHead` is that the metric is anomalous, i.e. + # raising and lowering the index means considering the metric or its + # inverse, this is not the case for other tensors. + self._substitutions_dict_tensmul[metric, True, True] = data + inverse_transpose = self.inverse_transpose_matrix(data) + # in symmetric spaces, the transpose is the same as the original matrix, + # the full covariant metric tensor is the inverse transpose, so this + # code will be able to handle non-symmetric metrics. + self._substitutions_dict_tensmul[metric, False, False] = inverse_transpose + # now mixed cases, these are identical to the unit matrix if the metric + # is symmetric. + m = data.tomatrix() + invt = inverse_transpose.tomatrix() + self._substitutions_dict_tensmul[metric, True, False] = m * invt + self._substitutions_dict_tensmul[metric, False, True] = invt * m + + @staticmethod + def _flip_index_by_metric(data, metric, pos): + from .array import tensorproduct, tensorcontraction + + mdim = metric.rank() + ddim = data.rank() + + if pos == 0: + data = tensorcontraction( + tensorproduct( + metric, + data + ), + (1, mdim+pos) + ) + else: + data = tensorcontraction( + tensorproduct( + data, + metric + ), + (pos, ddim) + ) + return data + + @staticmethod + def inverse_matrix(ndarray): + m = ndarray.tomatrix().inv() + return _TensorDataLazyEvaluator.parse_data(m) + + @staticmethod + def inverse_transpose_matrix(ndarray): + m = ndarray.tomatrix().inv().T + return _TensorDataLazyEvaluator.parse_data(m) + + @staticmethod + def _correct_signature_from_indices(data, indices, free, dum, inverse=False): + """ + Utility function to correct the values inside the components data + ndarray according to whether indices are covariant or contravariant. + + It uses the metric matrix to lower values of covariant indices. + """ + # change the ndarray values according covariantness/contravariantness of the indices + # use the metric + for i, indx in enumerate(indices): + if not indx.is_up and not inverse: + data = _TensorDataLazyEvaluator._flip_index_by_metric(data, indx.tensor_index_type.data, i) + elif not indx.is_up and inverse: + data = _TensorDataLazyEvaluator._flip_index_by_metric( + data, + _TensorDataLazyEvaluator.inverse_matrix(indx.tensor_index_type.data), + i + ) + return data + + @staticmethod + def _sort_data_axes(old, new): + from .array import permutedims + + new_data = old.data.copy() + + old_free = [i[0] for i in old.free] + new_free = [i[0] for i in new.free] + + for i in range(len(new_free)): + for j in range(i, len(old_free)): + if old_free[j] == new_free[i]: + old_free[i], old_free[j] = old_free[j], old_free[i] + new_data = permutedims(new_data, (i, j)) + break + return new_data + + @staticmethod + def add_rearrange_tensmul_parts(new_tensmul, old_tensmul): + def sorted_compo(): + return _TensorDataLazyEvaluator._sort_data_axes(old_tensmul, new_tensmul) + + _TensorDataLazyEvaluator._substitutions_dict[new_tensmul] = sorted_compo() + + @staticmethod + def parse_data(data): + """ + Transform ``data`` to array. The parameter ``data`` may + contain data in various formats, e.g. nested lists, SymPy ``Matrix``, + and so on. + + Examples + ======== + + >>> from sympy.tensor.tensor import _TensorDataLazyEvaluator + >>> _TensorDataLazyEvaluator.parse_data([1, 3, -6, 12]) + [1, 3, -6, 12] + + >>> _TensorDataLazyEvaluator.parse_data([[1, 2], [4, 7]]) + [[1, 2], [4, 7]] + """ + from .array import MutableDenseNDimArray + + if not isinstance(data, MutableDenseNDimArray): + if len(data) == 2 and hasattr(data[0], '__call__'): + data = MutableDenseNDimArray(data[0], data[1]) + else: + data = MutableDenseNDimArray(data) + return data + +_tensor_data_substitution_dict = _TensorDataLazyEvaluator() + + +class _TensorManager: + """ + Class to manage tensor properties. + + Notes + ===== + + Tensors belong to tensor commutation groups; each group has a label + ``comm``; there are predefined labels: + + ``0`` tensors commuting with any other tensor + + ``1`` tensors anticommuting among themselves + + ``2`` tensors not commuting, apart with those with ``comm=0`` + + Other groups can be defined using ``set_comm``; tensors in those + groups commute with those with ``comm=0``; by default they + do not commute with any other group. + """ + def __init__(self): + self._comm_init() + + def _comm_init(self): + self._comm = [{} for i in range(3)] + for i in range(3): + self._comm[0][i] = 0 + self._comm[i][0] = 0 + self._comm[1][1] = 1 + self._comm[2][1] = None + self._comm[1][2] = None + self._comm_symbols2i = {0:0, 1:1, 2:2} + self._comm_i2symbol = {0:0, 1:1, 2:2} + + @property + def comm(self): + return self._comm + + def comm_symbols2i(self, i): + """ + Get the commutation group number corresponding to ``i``. + + ``i`` can be a symbol or a number or a string. + + If ``i`` is not already defined its commutation group number + is set. + """ + if i not in self._comm_symbols2i: + n = len(self._comm) + self._comm.append({}) + self._comm[n][0] = 0 + self._comm[0][n] = 0 + self._comm_symbols2i[i] = n + self._comm_i2symbol[n] = i + return n + return self._comm_symbols2i[i] + + def comm_i2symbol(self, i): + """ + Returns the symbol corresponding to the commutation group number. + """ + return self._comm_i2symbol[i] + + def set_comm(self, i, j, c): + """ + Set the commutation parameter ``c`` for commutation groups ``i, j``. + + Parameters + ========== + + i, j : symbols representing commutation groups + + c : group commutation number + + Notes + ===== + + ``i, j`` can be symbols, strings or numbers, + apart from ``0, 1`` and ``2`` which are reserved respectively + for commuting, anticommuting tensors and tensors not commuting + with any other group apart with the commuting tensors. + For the remaining cases, use this method to set the commutation rules; + by default ``c=None``. + + The group commutation number ``c`` is assigned in correspondence + to the group commutation symbols; it can be + + 0 commuting + + 1 anticommuting + + None no commutation property + + Examples + ======== + + ``G`` and ``GH`` do not commute with themselves and commute with + each other; A is commuting. + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead, TensorManager, TensorSymmetry + >>> Lorentz = TensorIndexType('Lorentz') + >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) + >>> A = TensorHead('A', [Lorentz]) + >>> G = TensorHead('G', [Lorentz], TensorSymmetry.no_symmetry(1), 'Gcomm') + >>> GH = TensorHead('GH', [Lorentz], TensorSymmetry.no_symmetry(1), 'GHcomm') + >>> TensorManager.set_comm('Gcomm', 'GHcomm', 0) + >>> (GH(i1)*G(i0)).canon_bp() + G(i0)*GH(i1) + >>> (G(i1)*G(i0)).canon_bp() + G(i1)*G(i0) + >>> (G(i1)*A(i0)).canon_bp() + A(i0)*G(i1) + """ + if c not in (0, 1, None): + raise ValueError('`c` can assume only the values 0, 1 or None') + + i = sympify(i) + j = sympify(j) + + if i not in self._comm_symbols2i: + n = len(self._comm) + self._comm.append({}) + self._comm[n][0] = 0 + self._comm[0][n] = 0 + self._comm_symbols2i[i] = n + self._comm_i2symbol[n] = i + if j not in self._comm_symbols2i: + n = len(self._comm) + self._comm.append({}) + self._comm[0][n] = 0 + self._comm[n][0] = 0 + self._comm_symbols2i[j] = n + self._comm_i2symbol[n] = j + ni = self._comm_symbols2i[i] + nj = self._comm_symbols2i[j] + self._comm[ni][nj] = c + self._comm[nj][ni] = c + + """ + Cached sympy functions (e.g. expand) may have cached the results of + expressions involving tensors, but those results may not be valid after + changing the commutation properties. To stay on the safe side, we clear + the cache of all functions. + """ + clear_cache() + + def set_comms(self, *args): + """ + Set the commutation group numbers ``c`` for symbols ``i, j``. + + Parameters + ========== + + args : sequence of ``(i, j, c)`` + """ + for i, j, c in args: + self.set_comm(i, j, c) + + def get_comm(self, i, j): + """ + Return the commutation parameter for commutation group numbers ``i, j`` + + see ``_TensorManager.set_comm`` + """ + return self._comm[i].get(j, 0 if i == 0 or j == 0 else None) + + def clear(self): + """ + Clear the TensorManager. + """ + self._comm_init() + + +TensorManager = _TensorManager() + + +class TensorIndexType(Basic): + """ + A TensorIndexType is characterized by its name and its metric. + + Parameters + ========== + + name : name of the tensor type + dummy_name : name of the head of dummy indices + dim : dimension, it can be a symbol or an integer or ``None`` + eps_dim : dimension of the epsilon tensor + metric_symmetry : integer that denotes metric symmetry or ``None`` for no metric + metric_name : string with the name of the metric tensor + + Attributes + ========== + + ``metric`` : the metric tensor + ``delta`` : ``Kronecker delta`` + ``epsilon`` : the ``Levi-Civita epsilon`` tensor + ``data`` : (deprecated) a property to add ``ndarray`` values, to work in a specified basis. + + Notes + ===== + + The possible values of the ``metric_symmetry`` parameter are: + + ``1`` : metric tensor is fully symmetric + ``0`` : metric tensor possesses no index symmetry + ``-1`` : metric tensor is fully antisymmetric + ``None``: there is no metric tensor (metric equals to ``None``) + + The metric is assumed to be symmetric by default. It can also be set + to a custom tensor by the ``.set_metric()`` method. + + If there is a metric the metric is used to raise and lower indices. + + In the case of non-symmetric metric, the following raising and + lowering conventions will be adopted: + + ``psi(a) = g(a, b)*psi(-b); chi(-a) = chi(b)*g(-b, -a)`` + + From these it is easy to find: + + ``g(-a, b) = delta(-a, b)`` + + where ``delta(-a, b) = delta(b, -a)`` is the ``Kronecker delta`` + (see ``TensorIndex`` for the conventions on indices). + For antisymmetric metrics there is also the following equality: + + ``g(a, -b) = -delta(a, -b)`` + + If there is no metric it is not possible to raise or lower indices; + e.g. the index of the defining representation of ``SU(N)`` + is 'covariant' and the conjugate representation is + 'contravariant'; for ``N > 2`` they are linearly independent. + + ``eps_dim`` is by default equal to ``dim``, if the latter is an integer; + else it can be assigned (for use in naive dimensional regularization); + if ``eps_dim`` is not an integer ``epsilon`` is ``None``. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> Lorentz.metric + metric(Lorentz,Lorentz) + """ + + def __new__(cls, name, dummy_name=None, dim=None, eps_dim=None, + metric_symmetry=1, metric_name='metric', **kwargs): + if 'dummy_fmt' in kwargs: + dummy_fmt = kwargs['dummy_fmt'] + sympy_deprecation_warning( + f""" + The dummy_fmt keyword to TensorIndexType is deprecated. Use + dummy_name={dummy_fmt} instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensorindextype-dummy-fmt", + ) + dummy_name = dummy_fmt + + if isinstance(name, str): + name = Symbol(name) + + if dummy_name is None: + dummy_name = str(name)[0] + if isinstance(dummy_name, str): + dummy_name = Symbol(dummy_name) + + if dim is None: + dim = Symbol("dim_" + dummy_name.name) + else: + dim = sympify(dim) + + if eps_dim is None: + eps_dim = dim + else: + eps_dim = sympify(eps_dim) + + metric_symmetry = sympify(metric_symmetry) + + if isinstance(metric_name, str): + metric_name = Symbol(metric_name) + + if 'metric' in kwargs: + SymPyDeprecationWarning( + """ + The 'metric' keyword argument to TensorIndexType is + deprecated. Use the 'metric_symmetry' keyword argument or the + TensorIndexType.set_metric() method instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensorindextype-metric", + ) + metric = kwargs.get('metric') + if metric is not None: + if metric in (True, False, 0, 1): + metric_name = 'metric' + #metric_antisym = metric + else: + metric_name = metric.name + #metric_antisym = metric.antisym + + if metric: + metric_symmetry = -1 + else: + metric_symmetry = 1 + + obj = Basic.__new__(cls, name, dummy_name, dim, eps_dim, + metric_symmetry, metric_name) + + obj._autogenerated = [] + return obj + + @property + def name(self): + return self.args[0].name + + @property + def dummy_name(self): + return self.args[1].name + + @property + def dim(self): + return self.args[2] + + @property + def eps_dim(self): + return self.args[3] + + @memoize_property + def metric(self): + metric_symmetry = self.args[4] + metric_name = self.args[5] + if metric_symmetry is None: + return None + + if metric_symmetry == 0: + symmetry = TensorSymmetry.no_symmetry(2) + elif metric_symmetry == 1: + symmetry = TensorSymmetry.fully_symmetric(2) + elif metric_symmetry == -1: + symmetry = TensorSymmetry.fully_symmetric(-2) + + return TensorHead(metric_name, [self]*2, symmetry) + + @memoize_property + def delta(self): + return TensorHead('KD', [self]*2, TensorSymmetry.fully_symmetric(2)) + + @memoize_property + def epsilon(self): + if not isinstance(self.eps_dim, (SYMPY_INTS, Integer)): + return None + symmetry = TensorSymmetry.fully_symmetric(-self.eps_dim) + return TensorHead('Eps', [self]*self.eps_dim, symmetry) + + def set_metric(self, tensor): + self._metric = tensor + + def __lt__(self, other): + return self.name < other.name + + def __str__(self): + return self.name + + __repr__ = __str__ + + # Everything below this line is deprecated + + @property + def data(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return _tensor_data_substitution_dict[self] + + @data.setter + def data(self, data): + deprecate_data() + # This assignment is a bit controversial, should metric components be assigned + # to the metric only or also to the TensorIndexType object? The advantage here + # is the ability to assign a 1D array and transform it to a 2D diagonal array. + from .array import MutableDenseNDimArray + + data = _TensorDataLazyEvaluator.parse_data(data) + if data.rank() > 2: + raise ValueError("data have to be of rank 1 (diagonal metric) or 2.") + if data.rank() == 1: + if self.dim.is_number: + nda_dim = data.shape[0] + if nda_dim != self.dim: + raise ValueError("Dimension mismatch") + + dim = data.shape[0] + newndarray = MutableDenseNDimArray.zeros(dim, dim) + for i, val in enumerate(data): + newndarray[i, i] = val + data = newndarray + dim1, dim2 = data.shape + if dim1 != dim2: + raise ValueError("Non-square matrix tensor.") + if self.dim.is_number: + if self.dim != dim1: + raise ValueError("Dimension mismatch") + _tensor_data_substitution_dict[self] = data + _tensor_data_substitution_dict.add_metric_data(self.metric, data) + with ignore_warnings(SymPyDeprecationWarning): + delta = self.get_kronecker_delta() + i1 = TensorIndex('i1', self) + i2 = TensorIndex('i2', self) + with ignore_warnings(SymPyDeprecationWarning): + delta(i1, -i2).data = _TensorDataLazyEvaluator.parse_data(eye(dim1)) + + @data.deleter + def data(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + if self in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[self] + if self.metric in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[self.metric] + + @deprecated( + """ + The TensorIndexType.get_kronecker_delta() method is deprecated. Use + the TensorIndexType.delta attribute instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensorindextype-methods", + ) + def get_kronecker_delta(self): + sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) + delta = TensorHead('KD', [self]*2, sym2) + return delta + + @deprecated( + """ + The TensorIndexType.get_epsilon() method is deprecated. Use + the TensorIndexType.epsilon attribute instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensorindextype-methods", + ) + def get_epsilon(self): + if not isinstance(self._eps_dim, (SYMPY_INTS, Integer)): + return None + sym = TensorSymmetry(get_symmetric_group_sgs(self._eps_dim, 1)) + epsilon = TensorHead('Eps', [self]*self._eps_dim, sym) + return epsilon + + def _components_data_full_destroy(self): + """ + EXPERIMENTAL: do not rely on this API method. + + This destroys components data associated to the ``TensorIndexType``, if + any, specifically: + + * metric tensor data + * Kronecker tensor data + """ + if self in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[self] + + def delete_tensmul_data(key): + if key in _tensor_data_substitution_dict._substitutions_dict_tensmul: + del _tensor_data_substitution_dict._substitutions_dict_tensmul[key] + + # delete metric data: + delete_tensmul_data((self.metric, True, True)) + delete_tensmul_data((self.metric, True, False)) + delete_tensmul_data((self.metric, False, True)) + delete_tensmul_data((self.metric, False, False)) + + # delete delta tensor data: + delta = self.get_kronecker_delta() + if delta in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[delta] + + +class TensorIndex(Basic): + """ + Represents a tensor index + + Parameters + ========== + + name : name of the index, or ``True`` if you want it to be automatically assigned + tensor_index_type : ``TensorIndexType`` of the index + is_up : flag for contravariant index (is_up=True by default) + + Attributes + ========== + + ``name`` + ``tensor_index_type`` + ``is_up`` + + Notes + ===== + + Tensor indices are contracted with the Einstein summation convention. + + An index can be in contravariant or in covariant form; in the latter + case it is represented prepending a ``-`` to the index name. Adding + ``-`` to a covariant (is_up=False) index makes it contravariant. + + Dummy indices have a name with head given by + ``tensor_inde_type.dummy_name`` with underscore and a number. + + Similar to ``symbols`` multiple contravariant indices can be created + at once using ``tensor_indices(s, typ)``, where ``s`` is a string + of names. + + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, TensorIndex, TensorHead, tensor_indices + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> mu = TensorIndex('mu', Lorentz, is_up=False) + >>> nu, rho = tensor_indices('nu, rho', Lorentz) + >>> A = TensorHead('A', [Lorentz, Lorentz]) + >>> A(mu, nu) + A(-mu, nu) + >>> A(-mu, -rho) + A(mu, -rho) + >>> A(mu, -mu) + A(-L_0, L_0) + """ + def __new__(cls, name, tensor_index_type, is_up=True): + if isinstance(name, str): + name_symbol = Symbol(name) + elif isinstance(name, Symbol): + name_symbol = name + elif name is True: + name = "_i{}".format(len(tensor_index_type._autogenerated)) + name_symbol = Symbol(name) + tensor_index_type._autogenerated.append(name_symbol) + else: + raise ValueError("invalid name") + + is_up = sympify(is_up) + return Basic.__new__(cls, name_symbol, tensor_index_type, is_up) + + @property + def name(self): + return self.args[0].name + + @property + def tensor_index_type(self): + return self.args[1] + + @property + def is_up(self): + return self.args[2] + + def _print(self): + s = self.name + if not self.is_up: + s = '-%s' % s + return s + + def __lt__(self, other): + return ((self.tensor_index_type, self.name) < + (other.tensor_index_type, other.name)) + + def __neg__(self): + t1 = TensorIndex(self.name, self.tensor_index_type, + (not self.is_up)) + return t1 + + +def tensor_indices(s, typ): + """ + Returns list of tensor indices given their names and their types. + + Parameters + ========== + + s : string of comma separated names of indices + + typ : ``TensorIndexType`` of the indices + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + """ + if isinstance(s, str): + a = [x.name for x in symbols(s, seq=True)] + else: + raise ValueError('expecting a string') + + tilist = [TensorIndex(i, typ) for i in a] + if len(tilist) == 1: + return tilist[0] + return tilist + + +class TensorSymmetry(Basic): + """ + Monoterm symmetry of a tensor (i.e. any symmetric or anti-symmetric + index permutation). For the relevant terminology see ``tensor_can.py`` + section of the combinatorics module. + + Parameters + ========== + + bsgs : tuple ``(base, sgs)`` BSGS of the symmetry of the tensor + + Attributes + ========== + + ``base`` : base of the BSGS + ``generators`` : generators of the BSGS + ``rank`` : rank of the tensor + + Notes + ===== + + A tensor can have an arbitrary monoterm symmetry provided by its BSGS. + Multiterm symmetries, like the cyclic symmetry of the Riemann tensor + (i.e., Bianchi identity), are not covered. See combinatorics module for + information on how to generate BSGS for a general index permutation group. + Simple symmetries can be generated using built-in methods. + + See Also + ======== + + sympy.combinatorics.tensor_can.get_symmetric_group_sgs + + Examples + ======== + + Define a symmetric tensor of rank 2 + + >>> from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, get_symmetric_group_sgs, TensorHead + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> sym = TensorSymmetry(get_symmetric_group_sgs(2)) + >>> T = TensorHead('T', [Lorentz]*2, sym) + + Note, that the same can also be done using built-in TensorSymmetry methods + + >>> sym2 = TensorSymmetry.fully_symmetric(2) + >>> sym == sym2 + True + """ + def __new__(cls, *args, **kw_args): + if len(args) == 1: + base, generators = args[0] + elif len(args) == 2: + base, generators = args + else: + raise TypeError("bsgs required, either two separate parameters or one tuple") + + if not isinstance(base, Tuple): + base = Tuple(*base) + if not isinstance(generators, Tuple): + generators = Tuple(*generators) + + return Basic.__new__(cls, base, generators, **kw_args) + + @property + def base(self): + return self.args[0] + + @property + def generators(self): + return self.args[1] + + @property + def rank(self): + return self.generators[0].size - 2 + + @classmethod + def fully_symmetric(cls, rank): + """ + Returns a fully symmetric (antisymmetric if ``rank``<0) + TensorSymmetry object for ``abs(rank)`` indices. + """ + if rank > 0: + bsgs = get_symmetric_group_sgs(rank, False) + elif rank < 0: + bsgs = get_symmetric_group_sgs(-rank, True) + elif rank == 0: + bsgs = ([], [Permutation(1)]) + return TensorSymmetry(bsgs) + + @classmethod + def direct_product(cls, *args): + """ + Returns a TensorSymmetry object that is being a direct product of + fully (anti-)symmetric index permutation groups. + + Notes + ===== + + Some examples for different values of ``(*args)``: + ``(1)`` vector, equivalent to ``TensorSymmetry.fully_symmetric(1)`` + ``(2)`` tensor with 2 symmetric indices, equivalent to ``.fully_symmetric(2)`` + ``(-2)`` tensor with 2 antisymmetric indices, equivalent to ``.fully_symmetric(-2)`` + ``(2, -2)`` tensor with the first 2 indices commuting and the last 2 anticommuting + ``(1, 1, 1)`` tensor with 3 indices without any symmetry + """ + base, sgs = [], [Permutation(1)] + for arg in args: + if arg > 0: + bsgs2 = get_symmetric_group_sgs(arg, False) + elif arg < 0: + bsgs2 = get_symmetric_group_sgs(-arg, True) + else: + continue + base, sgs = bsgs_direct_product(base, sgs, *bsgs2) + + return TensorSymmetry(base, sgs) + + @classmethod + def riemann(cls): + """ + Returns a monotorem symmetry of the Riemann tensor + """ + return TensorSymmetry(riemann_bsgs) + + @classmethod + def no_symmetry(cls, rank): + """ + TensorSymmetry object for ``rank`` indices with no symmetry + """ + return TensorSymmetry([], [Permutation(rank+1)]) + + +@deprecated( + """ + The tensorsymmetry() function is deprecated. Use the TensorSymmetry + constructor instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensorsymmetry", +) +def tensorsymmetry(*args): + """ + Returns a ``TensorSymmetry`` object. This method is deprecated, use + ``TensorSymmetry.direct_product()`` or ``.riemann()`` instead. + + Explanation + =========== + + One can represent a tensor with any monoterm slot symmetry group + using a BSGS. + + ``args`` can be a BSGS + ``args[0]`` base + ``args[1]`` sgs + + Usually tensors are in (direct products of) representations + of the symmetric group; + ``args`` can be a list of lists representing the shapes of Young tableaux + + Notes + ===== + + For instance: + ``[[1]]`` vector + ``[[1]*n]`` symmetric tensor of rank ``n`` + ``[[n]]`` antisymmetric tensor of rank ``n`` + ``[[2, 2]]`` monoterm slot symmetry of the Riemann tensor + ``[[1],[1]]`` vector*vector + ``[[2],[1],[1]`` (antisymmetric tensor)*vector*vector + + Notice that with the shape ``[2, 2]`` we associate only the monoterm + symmetries of the Riemann tensor; this is an abuse of notation, + since the shape ``[2, 2]`` corresponds usually to the irreducible + representation characterized by the monoterm symmetries and by the + cyclic symmetry. + """ + from sympy.combinatorics import Permutation + + def tableau2bsgs(a): + if len(a) == 1: + # antisymmetric vector + n = a[0] + bsgs = get_symmetric_group_sgs(n, 1) + else: + if all(x == 1 for x in a): + # symmetric vector + n = len(a) + bsgs = get_symmetric_group_sgs(n) + elif a == [2, 2]: + bsgs = riemann_bsgs + else: + raise NotImplementedError + return bsgs + + if not args: + return TensorSymmetry(Tuple(), Tuple(Permutation(1))) + + if len(args) == 2 and isinstance(args[1][0], Permutation): + return TensorSymmetry(args) + base, sgs = tableau2bsgs(args[0]) + for a in args[1:]: + basex, sgsx = tableau2bsgs(a) + base, sgs = bsgs_direct_product(base, sgs, basex, sgsx) + return TensorSymmetry(Tuple(base, sgs)) + +@deprecated( + "TensorType is deprecated. Use tensor_heads() instead.", + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensortype", +) +class TensorType(Basic): + """ + Class of tensor types. Deprecated, use tensor_heads() instead. + + Parameters + ========== + + index_types : list of ``TensorIndexType`` of the tensor indices + symmetry : ``TensorSymmetry`` of the tensor + + Attributes + ========== + + ``index_types`` + ``symmetry`` + ``types`` : list of ``TensorIndexType`` without repetitions + """ + is_commutative = False + + def __new__(cls, index_types, symmetry, **kw_args): + assert symmetry.rank == len(index_types) + obj = Basic.__new__(cls, Tuple(*index_types), symmetry, **kw_args) + return obj + + @property + def index_types(self): + return self.args[0] + + @property + def symmetry(self): + return self.args[1] + + @property + def types(self): + return sorted(set(self.index_types), key=lambda x: x.name) + + def __str__(self): + return 'TensorType(%s)' % ([str(x) for x in self.index_types]) + + def __call__(self, s, comm=0): + """ + Return a TensorHead object or a list of TensorHead objects. + + Parameters + ========== + + s : name or string of names. + + comm : Commutation group. + + see ``_TensorManager.set_comm`` + """ + if isinstance(s, str): + names = [x.name for x in symbols(s, seq=True)] + else: + raise ValueError('expecting a string') + if len(names) == 1: + return TensorHead(names[0], self.index_types, self.symmetry, comm) + else: + return [TensorHead(name, self.index_types, self.symmetry, comm) for name in names] + + +@deprecated( + """ + The tensorhead() function is deprecated. Use tensor_heads() instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-tensorhead", +) +def tensorhead(name, typ, sym=None, comm=0): + """ + Function generating tensorhead(s). This method is deprecated, + use TensorHead constructor or tensor_heads() instead. + + Parameters + ========== + + name : name or sequence of names (as in ``symbols``) + + typ : index types + + sym : same as ``*args`` in ``tensorsymmetry`` + + comm : commutation group number + see ``_TensorManager.set_comm`` + """ + if sym is None: + sym = [[1] for i in range(len(typ))] + with ignore_warnings(SymPyDeprecationWarning): + sym = tensorsymmetry(*sym) + return TensorHead(name, typ, sym, comm) + + +class TensorHead(Basic): + """ + Tensor head of the tensor. + + Parameters + ========== + + name : name of the tensor + index_types : list of TensorIndexType + symmetry : TensorSymmetry of the tensor + comm : commutation group number + + Attributes + ========== + + ``name`` + ``index_types`` + ``rank`` : total number of indices + ``symmetry`` + ``comm`` : commutation group + + Notes + ===== + + Similar to ``symbols`` multiple TensorHeads can be created using + ``tensorhead(s, typ, sym=None, comm=0)`` function, where ``s`` + is the string of names and ``sym`` is the monoterm tensor symmetry + (see ``tensorsymmetry``). + + A ``TensorHead`` belongs to a commutation group, defined by a + symbol on number ``comm`` (see ``_TensorManager.set_comm``); + tensors in a commutation group have the same commutation properties; + by default ``comm`` is ``0``, the group of the commuting tensors. + + Examples + ======== + + Define a fully antisymmetric tensor of rank 2: + + >>> from sympy.tensor.tensor import TensorIndexType, TensorHead, TensorSymmetry + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> asym2 = TensorSymmetry.fully_symmetric(-2) + >>> A = TensorHead('A', [Lorentz, Lorentz], asym2) + + Examples with ndarray values, the components data assigned to the + ``TensorHead`` object are assumed to be in a fully-contravariant + representation. In case it is necessary to assign components data which + represents the values of a non-fully covariant tensor, see the other + examples. + + >>> from sympy.tensor.tensor import tensor_indices + >>> from sympy import diag + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> i0, i1 = tensor_indices('i0:2', Lorentz) + + Specify a replacement dictionary to keep track of the arrays to use for + replacements in the tensorial expression. The ``TensorIndexType`` is + associated to the metric used for contractions (in fully covariant form): + + >>> repl = {Lorentz: diag(1, -1, -1, -1)} + + Let's see some examples of working with components with the electromagnetic + tensor: + + >>> from sympy import symbols + >>> Ex, Ey, Ez, Bx, By, Bz = symbols('E_x E_y E_z B_x B_y B_z') + >>> c = symbols('c', positive=True) + + Let's define `F`, an antisymmetric tensor: + + >>> F = TensorHead('F', [Lorentz, Lorentz], asym2) + + Let's update the dictionary to contain the matrix to use in the + replacements: + + >>> repl.update({F(-i0, -i1): [ + ... [0, Ex/c, Ey/c, Ez/c], + ... [-Ex/c, 0, -Bz, By], + ... [-Ey/c, Bz, 0, -Bx], + ... [-Ez/c, -By, Bx, 0]]}) + + Now it is possible to retrieve the contravariant form of the Electromagnetic + tensor: + + >>> F(i0, i1).replace_with_arrays(repl, [i0, i1]) + [[0, -E_x/c, -E_y/c, -E_z/c], [E_x/c, 0, -B_z, B_y], [E_y/c, B_z, 0, -B_x], [E_z/c, -B_y, B_x, 0]] + + and the mixed contravariant-covariant form: + + >>> F(i0, -i1).replace_with_arrays(repl, [i0, -i1]) + [[0, E_x/c, E_y/c, E_z/c], [E_x/c, 0, B_z, -B_y], [E_y/c, -B_z, 0, B_x], [E_z/c, B_y, -B_x, 0]] + + Energy-momentum of a particle may be represented as: + + >>> from sympy import symbols + >>> P = TensorHead('P', [Lorentz], TensorSymmetry.no_symmetry(1)) + >>> E, px, py, pz = symbols('E p_x p_y p_z', positive=True) + >>> repl.update({P(i0): [E, px, py, pz]}) + + The contravariant and covariant components are, respectively: + + >>> P(i0).replace_with_arrays(repl, [i0]) + [E, p_x, p_y, p_z] + >>> P(-i0).replace_with_arrays(repl, [-i0]) + [E, -p_x, -p_y, -p_z] + + The contraction of a 1-index tensor by itself: + + >>> expr = P(i0)*P(-i0) + >>> expr.replace_with_arrays(repl, []) + E**2 - p_x**2 - p_y**2 - p_z**2 + """ + is_commutative = False + + def __new__(cls, name, index_types, symmetry=None, comm=0): + if isinstance(name, str): + name_symbol = Symbol(name) + elif isinstance(name, Symbol): + name_symbol = name + else: + raise ValueError("invalid name") + + if symmetry is None: + symmetry = TensorSymmetry.no_symmetry(len(index_types)) + else: + assert symmetry.rank == len(index_types) + + obj = Basic.__new__(cls, name_symbol, Tuple(*index_types), symmetry, sympify(comm)) + return obj + + @property + def name(self): + return self.args[0].name + + @property + def index_types(self): + return list(self.args[1]) + + @property + def symmetry(self): + return self.args[2] + + @property + def comm(self): + return TensorManager.comm_symbols2i(self.args[3]) + + @property + def rank(self): + return len(self.index_types) + + def __lt__(self, other): + return (self.name, self.index_types) < (other.name, other.index_types) + + def commutes_with(self, other): + """ + Returns ``0`` if ``self`` and ``other`` commute, ``1`` if they anticommute. + + Returns ``None`` if ``self`` and ``other`` neither commute nor anticommute. + """ + r = TensorManager.get_comm(self.comm, other.comm) + return r + + def _print(self): + return '%s(%s)' %(self.name, ','.join([str(x) for x in self.index_types])) + + def __call__(self, *indices, **kw_args): + """ + Returns a tensor with indices. + + Explanation + =========== + + There is a special behavior in case of indices denoted by ``True``, + they are considered auto-matrix indices, their slots are automatically + filled, and confer to the tensor the behavior of a matrix or vector + upon multiplication with another tensor containing auto-matrix indices + of the same ``TensorIndexType``. This means indices get summed over the + same way as in matrix multiplication. For matrix behavior, define two + auto-matrix indices, for vector behavior define just one. + + Indices can also be strings, in which case the attribute + ``index_types`` is used to convert them to proper ``TensorIndex``. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorHead + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> a, b = tensor_indices('a,b', Lorentz) + >>> A = TensorHead('A', [Lorentz]*2, TensorSymmetry.no_symmetry(2)) + >>> t = A(a, -b) + >>> t + A(a, -b) + + """ + + updated_indices = [] + for idx, typ in zip(indices, self.index_types): + if isinstance(idx, str): + idx = idx.strip().replace(" ", "") + if idx.startswith('-'): + updated_indices.append(TensorIndex(idx[1:], typ, + is_up=False)) + else: + updated_indices.append(TensorIndex(idx, typ)) + else: + updated_indices.append(idx) + + updated_indices += indices[len(updated_indices):] + + tensor = Tensor(self, updated_indices, **kw_args) + return tensor.doit() + + # Everything below this line is deprecated + + def __pow__(self, other): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + if self.data is None: + raise ValueError("No power on abstract tensors.") + from .array import tensorproduct, tensorcontraction + metrics = [_.data for _ in self.index_types] + + marray = self.data + marraydim = marray.rank() + for metric in metrics: + marray = tensorproduct(marray, metric, marray) + marray = tensorcontraction(marray, (0, marraydim), (marraydim+1, marraydim+2)) + + return marray ** (other * S.Half) + + @property + def data(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return _tensor_data_substitution_dict[self] + + @data.setter + def data(self, data): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + _tensor_data_substitution_dict[self] = data + + @data.deleter + def data(self): + deprecate_data() + if self in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[self] + + def __iter__(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return self.data.__iter__() + + def _components_data_full_destroy(self): + """ + EXPERIMENTAL: do not rely on this API method. + + Destroy components data associated to the ``TensorHead`` object, this + checks for attached components data, and destroys components data too. + """ + # do not garbage collect Kronecker tensor (it should be done by + # ``TensorIndexType`` garbage collection) + deprecate_data() + if self.name == "KD": + return + + # the data attached to a tensor must be deleted only by the TensorHead + # destructor. If the TensorHead is deleted, it means that there are no + # more instances of that tensor anywhere. + if self in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[self] + + +def tensor_heads(s, index_types, symmetry=None, comm=0): + """ + Returns a sequence of TensorHeads from a string `s` + """ + if isinstance(s, str): + names = [x.name for x in symbols(s, seq=True)] + else: + raise ValueError('expecting a string') + + thlist = [TensorHead(name, index_types, symmetry, comm) for name in names] + if len(thlist) == 1: + return thlist[0] + return thlist + + +class TensExpr(Expr, ABC): + """ + Abstract base class for tensor expressions + + Notes + ===== + + A tensor expression is an expression formed by tensors; + currently the sums of tensors are distributed. + + A ``TensExpr`` can be a ``TensAdd`` or a ``TensMul``. + + ``TensMul`` objects are formed by products of component tensors, + and include a coefficient, which is a SymPy expression. + + + In the internal representation contracted indices are represented + by ``(ipos1, ipos2, icomp1, icomp2)``, where ``icomp1`` is the position + of the component tensor with contravariant index, ``ipos1`` is the + slot which the index occupies in that component tensor. + + Contracted indices are therefore nameless in the internal representation. + """ + + _op_priority = 12.0 + is_commutative = False + + def __neg__(self): + return self*S.NegativeOne + + def __abs__(self): + raise NotImplementedError + + def __add__(self, other): + return TensAdd(self, other).doit(deep=False) + + def __radd__(self, other): + return TensAdd(other, self).doit(deep=False) + + def __sub__(self, other): + return TensAdd(self, -other).doit(deep=False) + + def __rsub__(self, other): + return TensAdd(other, -self).doit(deep=False) + + def __mul__(self, other): + """ + Multiply two tensors using Einstein summation convention. + + Explanation + =========== + + If the two tensors have an index in common, one contravariant + and the other covariant, in their product the indices are summed + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> g = Lorentz.metric + >>> p, q = tensor_heads('p,q', [Lorentz]) + >>> t1 = p(m0) + >>> t2 = q(-m0) + >>> t1*t2 + p(L_0)*q(-L_0) + """ + return TensMul(self, other).doit(deep=False) + + def __rmul__(self, other): + return TensMul(other, self).doit(deep=False) + + def __truediv__(self, other): + other = _sympify(other) + if isinstance(other, TensExpr): + raise ValueError('cannot divide by a tensor') + return TensMul(self, S.One/other).doit(deep=False) + + def __rtruediv__(self, other): + raise ValueError('cannot divide by a tensor') + + def __pow__(self, other): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + if self.data is None: + raise ValueError("No power without ndarray data.") + from .array import tensorproduct, tensorcontraction + free = self.free + marray = self.data + mdim = marray.rank() + for metric in free: + marray = tensorcontraction( + tensorproduct( + marray, + metric[0].tensor_index_type.data, + marray), + (0, mdim), (mdim+1, mdim+2) + ) + return marray ** (other * S.Half) + + def __rpow__(self, other): + raise NotImplementedError + + @property + @abstractmethod + def nocoeff(self): + raise NotImplementedError("abstract method") + + @property + @abstractmethod + def coeff(self): + raise NotImplementedError("abstract method") + + @abstractmethod + def get_indices(self): + raise NotImplementedError("abstract method") + + @abstractmethod + def get_free_indices(self) -> list[TensorIndex]: + raise NotImplementedError("abstract method") + + @abstractmethod + def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: + raise NotImplementedError("abstract method") + + def fun_eval(self, *index_tuples): + deprecate_fun_eval() + return self.substitute_indices(*index_tuples) + + def get_matrix(self): + """ + DEPRECATED: do not use. + + Returns ndarray components data as a matrix, if components data are + available and ndarray dimension does not exceed 2. + """ + from sympy.matrices.dense import Matrix + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + if 0 < self.rank <= 2: + rows = self.data.shape[0] + columns = self.data.shape[1] if self.rank == 2 else 1 + if self.rank == 2: + mat_list = [] * rows + for i in range(rows): + mat_list.append([]) + for j in range(columns): + mat_list[i].append(self[i, j]) + else: + mat_list = [None] * rows + for i in range(rows): + mat_list[i] = self[i] + return Matrix(mat_list) + else: + raise NotImplementedError( + "missing multidimensional reduction to matrix.") + + @staticmethod + def _get_indices_permutation(indices1, indices2): + return [indices1.index(i) for i in indices2] + + def _get_free_indices_set(self): + indset = set() + for arg in self.args: + if isinstance(arg, TensExpr): + indset.update(arg._get_free_indices_set()) + return indset + + def _get_dummy_indices_set(self): + indset = set() + for arg in self.args: + if isinstance(arg, TensExpr): + indset.update(arg._get_dummy_indices_set()) + return indset + + def _get_indices_set(self): + indset = set() + for arg in self.args: + if isinstance(arg, TensExpr): + indset.update(arg._get_indices_set()) + return indset + + @property + def _iterate_dummy_indices(self): + dummy_set = self._get_dummy_indices_set() + + def recursor(expr, pos): + if isinstance(expr, TensorIndex): + if expr in dummy_set: + yield (expr, pos) + elif isinstance(expr, (Tuple, TensExpr)): + for p, arg in enumerate(expr.args): + yield from recursor(arg, pos+(p,)) + + return recursor(self, ()) + + @property + def _iterate_free_indices(self): + free_set = self._get_free_indices_set() + + def recursor(expr, pos): + if isinstance(expr, TensorIndex): + if expr in free_set: + yield (expr, pos) + elif isinstance(expr, (Tuple, TensExpr)): + for p, arg in enumerate(expr.args): + yield from recursor(arg, pos+(p,)) + + return recursor(self, ()) + + @property + def _iterate_indices(self): + def recursor(expr, pos): + if isinstance(expr, TensorIndex): + yield (expr, pos) + elif isinstance(expr, (Tuple, TensExpr)): + for p, arg in enumerate(expr.args): + yield from recursor(arg, pos+(p,)) + + return recursor(self, ()) + + @staticmethod + def _contract_and_permute_with_metric(metric, array, pos, dim): + # TODO: add possibility of metric after (spinors) + from .array import tensorcontraction, tensorproduct, permutedims + + array = tensorcontraction(tensorproduct(metric, array), (1, 2+pos)) + permu = list(range(dim)) + permu[0], permu[pos] = permu[pos], permu[0] + return permutedims(array, permu) + + @staticmethod + def _match_indices_with_other_tensor(array, free_ind1, free_ind2, replacement_dict): + from .array import permutedims + + index_types1 = [i.tensor_index_type for i in free_ind1] + + # Check if variance of indices needs to be fixed: + pos2up = [] + pos2down = [] + free2remaining = free_ind2[:] + for pos1, index1 in enumerate(free_ind1): + if index1 in free2remaining: + pos2 = free2remaining.index(index1) + free2remaining[pos2] = None + continue + if -index1 in free2remaining: + pos2 = free2remaining.index(-index1) + free2remaining[pos2] = None + free_ind2[pos2] = index1 + if index1.is_up: + pos2up.append(pos2) + else: + pos2down.append(pos2) + else: + index2 = free2remaining[pos1] + if index2 is None: + raise ValueError("incompatible indices: %s and %s" % (free_ind1, free_ind2)) + free2remaining[pos1] = None + free_ind2[pos1] = index1 + if index1.is_up ^ index2.is_up: + if index1.is_up: + pos2up.append(pos1) + else: + pos2down.append(pos1) + + if len(set(free_ind1) & set(free_ind2)) < len(free_ind1): + raise ValueError("incompatible indices: %s and %s" % (free_ind1, free_ind2)) + + # Raise indices: + for pos in pos2up: + index_type_pos = index_types1[pos] + if index_type_pos not in replacement_dict: + raise ValueError("No metric provided to lower index") + metric = replacement_dict[index_type_pos] + metric_inverse = _TensorDataLazyEvaluator.inverse_matrix(metric) + array = TensExpr._contract_and_permute_with_metric(metric_inverse, array, pos, len(free_ind1)) + # Lower indices: + for pos in pos2down: + index_type_pos = index_types1[pos] + if index_type_pos not in replacement_dict: + raise ValueError("No metric provided to lower index") + metric = replacement_dict[index_type_pos] + array = TensExpr._contract_and_permute_with_metric(metric, array, pos, len(free_ind1)) + + if free_ind1: + permutation = TensExpr._get_indices_permutation(free_ind2, free_ind1) + array = permutedims(array, permutation) + + if hasattr(array, "rank") and array.rank() == 0: + array = array[()] + + return free_ind2, array + + def replace_with_arrays(self, replacement_dict, indices=None): + """ + Replace the tensorial expressions with arrays. The final array will + correspond to the N-dimensional array with indices arranged according + to ``indices``. + + Parameters + ========== + + replacement_dict + dictionary containing the replacement rules for tensors. + indices + the index order with respect to which the array is read. The + original index order will be used if no value is passed. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices + >>> from sympy.tensor.tensor import TensorHead + >>> from sympy import symbols, diag + + >>> L = TensorIndexType("L") + >>> i, j = tensor_indices("i j", L) + >>> A = TensorHead("A", [L]) + >>> A(i).replace_with_arrays({A(i): [1, 2]}, [i]) + [1, 2] + + Since 'indices' is optional, we can also call replace_with_arrays by + this way if no specific index order is needed: + + >>> A(i).replace_with_arrays({A(i): [1, 2]}) + [1, 2] + + >>> expr = A(i)*A(j) + >>> expr.replace_with_arrays({A(i): [1, 2]}) + [[1, 2], [2, 4]] + + For contractions, specify the metric of the ``TensorIndexType``, which + in this case is ``L``, in its covariant form: + + >>> expr = A(i)*A(-i) + >>> expr.replace_with_arrays({A(i): [1, 2], L: diag(1, -1)}) + -3 + + Symmetrization of an array: + + >>> H = TensorHead("H", [L, L]) + >>> a, b, c, d = symbols("a b c d") + >>> expr = H(i, j)/2 + H(j, i)/2 + >>> expr.replace_with_arrays({H(i, j): [[a, b], [c, d]]}) + [[a, b/2 + c/2], [b/2 + c/2, d]] + + Anti-symmetrization of an array: + + >>> expr = H(i, j)/2 - H(j, i)/2 + >>> repl = {H(i, j): [[a, b], [c, d]]} + >>> expr.replace_with_arrays(repl) + [[0, b/2 - c/2], [-b/2 + c/2, 0]] + + The same expression can be read as the transpose by inverting ``i`` and + ``j``: + + >>> expr.replace_with_arrays(repl, [j, i]) + [[0, -b/2 + c/2], [b/2 - c/2, 0]] + """ + from .array import Array + + indices = indices or [] + remap = {k.args[0] if k.is_up else -k.args[0]: k for k in self.get_free_indices()} + for i, index in enumerate(indices): + if isinstance(index, (Symbol, Mul)): + if index in remap: + indices[i] = remap[index] + else: + indices[i] = -remap[-index] + + replacement_dict = {tensor: Array(array) for tensor, array in replacement_dict.items()} + + # Check dimensions of replaced arrays: + for tensor, array in replacement_dict.items(): + if isinstance(tensor, TensorIndexType): + expected_shape = [tensor.dim for i in range(2)] + else: + expected_shape = [index_type.dim for index_type in tensor.index_types] + if len(expected_shape) != array.rank() or (not all(dim1 == dim2 if + dim1.is_number else True for dim1, dim2 in zip(expected_shape, + array.shape))): + raise ValueError("shapes for tensor %s expected to be %s, "\ + "replacement array shape is %s" % (tensor, expected_shape, + array.shape)) + + ret_indices, array = self._extract_data(replacement_dict) + + last_indices, array = self._match_indices_with_other_tensor(array, indices, ret_indices, replacement_dict) + return array + + def _check_add_Sum(self, expr, index_symbols): + from sympy.concrete.summations import Sum + indices = self.get_indices() + dum = self.dum + sum_indices = [ (index_symbols[i], 0, + indices[i].tensor_index_type.dim-1) for i, j in dum] + if sum_indices: + expr = Sum(expr, *sum_indices) + return expr + + def _expand_partial_derivative(self): + # simply delegate the _expand_partial_derivative() to + # its arguments to expand a possibly found PartialDerivative + return self.func(*[ + a._expand_partial_derivative() + if isinstance(a, TensExpr) else a + for a in self.args]) + + def _matches_simple(self, expr, repl_dict=None, old=False): + """ + Matches assuming there are no wild objects in self. + """ + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + if not isinstance(expr, TensExpr): + if len(self.get_free_indices()) > 0: + #self has indices, but expr does not. + return None + elif set(self.get_free_indices()) != set(expr.get_free_indices()): + #If there are no wilds and the free indices are not the same, they cannot match. + return None + + if canon_bp(self - expr) == S.Zero: + return repl_dict + else: + return None + + +class TensAdd(TensExpr, AssocOp): + """ + Sum of tensors. + + Parameters + ========== + + free_args : list of the free indices + + Attributes + ========== + + ``args`` : tuple of addends + ``rank`` : rank of the tensor + ``free_args`` : list of the free indices in sorted order + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_heads, tensor_indices + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> a, b = tensor_indices('a,b', Lorentz) + >>> p, q = tensor_heads('p,q', [Lorentz]) + >>> t = p(a) + q(a); t + p(a) + q(a) + + Examples with components data added to the tensor expression: + + >>> from sympy import symbols, diag + >>> x, y, z, t = symbols("x y z t") + >>> repl = {} + >>> repl[Lorentz] = diag(1, -1, -1, -1) + >>> repl[p(a)] = [1, 2, 3, 4] + >>> repl[q(a)] = [x, y, z, t] + + The following are: 2**2 - 3**2 - 2**2 - 7**2 ==> -58 + + >>> expr = p(a) + q(a) + >>> expr.replace_with_arrays(repl, [a]) + [x + 1, y + 2, z + 3, t + 4] + """ + + def __new__(cls, *args, **kw_args): + args = [_sympify(x) for x in args if x] + args = TensAdd._tensAdd_flatten(args) + args.sort(key=default_sort_key) + if not args: + return S.Zero + if len(args) == 1: + return args[0] + + return Basic.__new__(cls, *args, **kw_args) + + @property + def coeff(self): + return S.One + + @property + def nocoeff(self): + return self + + def get_free_indices(self) -> list[TensorIndex]: + return self.free_indices + + def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: + newargs = [arg._replace_indices(repl) if isinstance(arg, TensExpr) else arg for arg in self.args] + return self.func(*newargs) + + @memoize_property + def rank(self): + if isinstance(self.args[0], TensExpr): + return self.args[0].rank + else: + return 0 + + @memoize_property + def free_args(self): + if isinstance(self.args[0], TensExpr): + return self.args[0].free_args + else: + return [] + + @memoize_property + def free_indices(self): + if isinstance(self.args[0], TensExpr): + return self.args[0].get_free_indices() + else: + return set() + + def doit(self, **hints) -> Expr: + deep = hints.get('deep', True) + if deep: + args = [arg.doit(**hints) for arg in self.args] + else: + args = self.args # type: ignore + + # if any of the args are zero (after doit), drop them. Otherwise, _tensAdd_check will complain about non-matching indices, even though the TensAdd is correctly formed. + args = [arg for arg in args if arg != S.Zero] + + if len(args) == 0: + return S.Zero + elif len(args) == 1: + return args[0] + + # now check that all addends have the same indices: + TensAdd._tensAdd_check(args) + + # Collect terms appearing more than once, differing by their coefficients: + args = TensAdd._tensAdd_collect_terms(args) + + # collect canonicalized terms + def sort_key(t): + if not isinstance(t, TensExpr): + return [], [], [] + if hasattr(t, "_index_structure") and hasattr(t, "components"): + x = get_index_structure(t) + return t.components, x.free, x.dum + return [], [], [] + args.sort(key=sort_key) + + if not args: + return S.Zero + # it there is only a component tensor return it + if len(args) == 1: + return args[0] + + obj = self.func(*args) + return obj + + @staticmethod + def _tensAdd_flatten(args): + # flatten TensAdd, coerce terms which are not tensors to tensors + a = [] + for x in args: + if isinstance(x, (Add, TensAdd)): + a.extend(list(x.args)) + else: + a.append(x) + args = [x for x in a if x.coeff] + return args + + @staticmethod + def _tensAdd_check(args): + # check that all addends have the same free indices + + def get_indices_set(x: Expr) -> set[TensorIndex]: + if isinstance(x, TensExpr): + return set(x.get_free_indices()) + return set() + + indices0 = get_indices_set(args[0]) + list_indices = [get_indices_set(arg) for arg in args[1:]] + if not all(x == indices0 for x in list_indices): + raise ValueError('all tensors must have the same indices') + + @staticmethod + def _tensAdd_collect_terms(args): + # collect TensMul terms differing at most by their coefficient + terms_dict = defaultdict(list) + scalars = S.Zero + if isinstance(args[0], TensExpr): + free_indices = set(args[0].get_free_indices()) + else: + free_indices = set() + + for arg in args: + if not isinstance(arg, TensExpr): + if free_indices != set(): + raise ValueError("wrong valence") + scalars += arg + continue + if free_indices != set(arg.get_free_indices()): + raise ValueError("wrong valence") + # TODO: what is the part which is not a coeff? + # needs an implementation similar to .as_coeff_Mul() + terms_dict[arg.nocoeff].append(arg.coeff) + + new_args = [TensMul(Add(*coeff), t).doit(deep=False) for t, coeff in terms_dict.items() if Add(*coeff) != 0] + if isinstance(scalars, Add): + new_args = list(scalars.args) + new_args + elif scalars != 0: + new_args = [scalars] + new_args + return new_args + + def get_indices(self): + indices = [] + for arg in self.args: + indices.extend([i for i in get_indices(arg) if i not in indices]) + return indices + + + def __call__(self, *indices): + deprecate_call() + free_args = self.free_args + indices = list(indices) + if [x.tensor_index_type for x in indices] != [x.tensor_index_type for x in free_args]: + raise ValueError('incompatible types') + if indices == free_args: + return self + index_tuples = list(zip(free_args, indices)) + a = [x.func(*x.substitute_indices(*index_tuples).args) for x in self.args] + res = TensAdd(*a).doit(deep=False) + return res + + def canon_bp(self): + """ + Canonicalize using the Butler-Portugal algorithm for canonicalization + under monoterm symmetries. + """ + expr = self.expand() + if isinstance(expr, self.func): + args = [canon_bp(x) for x in expr.args] + res = TensAdd(*args).doit(deep=False) + return res + else: + return canon_bp(expr) + + def equals(self, other): + other = _sympify(other) + if isinstance(other, TensMul) and other.coeff == 0: + return all(x.coeff == 0 for x in self.args) + if isinstance(other, TensExpr): + if self.rank != other.rank: + return False + if isinstance(other, TensAdd): + if set(self.args) != set(other.args): + return False + else: + return True + t = self - other + if not isinstance(t, TensExpr): + return t == 0 + else: + if isinstance(t, TensMul): + return t.coeff == 0 + else: + return all(x.coeff == 0 for x in t.args) + + def __getitem__(self, item): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return self.data[item] + + def contract_delta(self, delta): + args = [x.contract_delta(delta) if isinstance(x, TensExpr) else x for x in self.args] + t = TensAdd(*args).doit(deep=False) + return canon_bp(t) + + def contract_metric(self, g): + """ + Raise or lower indices with the metric ``g``. + + Parameters + ========== + + g : metric + + contract_all : if True, eliminate all ``g`` which are contracted + + Notes + ===== + + see the ``TensorIndexType`` docstring for the contraction conventions + """ + + args = [contract_metric(x, g) for x in self.args] + t = TensAdd(*args).doit(deep=False) + return canon_bp(t) + + def substitute_indices(self, *index_tuples): + new_args = [] + for arg in self.args: + if isinstance(arg, TensExpr): + arg = arg.substitute_indices(*index_tuples) + new_args.append(arg) + return TensAdd(*new_args).doit(deep=False) + + def _print(self): + a = [] + args = self.args + for x in args: + a.append(str(x)) + s = ' + '.join(a) + s = s.replace('+ -', '- ') + return s + + def _extract_data(self, replacement_dict): + from sympy.tensor.array import Array, permutedims + args_indices, arrays = zip(*[ + arg._extract_data(replacement_dict) if + isinstance(arg, TensExpr) else ([], arg) for arg in self.args + ]) + arrays = [Array(i) for i in arrays] + ref_indices = args_indices[0] + for i in range(1, len(args_indices)): + indices = args_indices[i] + array = arrays[i] + permutation = TensMul._get_indices_permutation(indices, ref_indices) + arrays[i] = permutedims(array, permutation) + return ref_indices, sum(arrays, Array.zeros(*array.shape)) + + @property + def data(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return _tensor_data_substitution_dict[self.expand()] + + @data.setter + def data(self, data): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + _tensor_data_substitution_dict[self] = data + + @data.deleter + def data(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + if self in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[self] + + def __iter__(self): + deprecate_data() + if not self.data: + raise ValueError("No iteration on abstract tensors") + return self.data.flatten().__iter__() + + def _eval_rewrite_as_Indexed(self, *args, **kwargs): + return Add.fromiter(args) + + def _eval_partial_derivative(self, s): + # Evaluation like Add + list_addends = [] + for a in self.args: + if isinstance(a, TensExpr): + list_addends.append(a._eval_partial_derivative(s)) + # do not call diff if s is no symbol + elif s._diff_wrt: + list_addends.append(a._eval_derivative(s)) + + return self.func(*list_addends).doit(deep=False) + + def matches(self, expr, repl_dict=None, old=False): + expr = sympify(expr) + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + if not isinstance(expr, TensAdd): + return None + + if len(_get_wilds(self)) == 0: + return self._matches_simple(expr, repl_dict, old) + + def siftkey(arg): + wildatoms = _get_wilds(arg) + wildatom_types = sift(wildatoms, type) + if len(wildatoms) == 0: + return "nonwild" + elif WildTensor in wildatom_types.keys(): + for w in wildatom_types["WildTensor"]: + if len(w.get_indices()) == 0: + return "indexless_wildtensor" + return "wildtensor" + else: + return "otherwild" + + query_sifted = sift(self.args, siftkey) + expr_sifted = sift(expr.args, siftkey) + + #First try to match the terms without WildTensors + matched_e_tensors = [] #Used to make sure that the same tensor in expr is not matched with more than one tensor in self. + for q_tensor in query_sifted["nonwild"]: + matched_this_q = False + for e_tensor in expr_sifted["nonwild"]: + if e_tensor in matched_e_tensors: + continue + + m = q_tensor.matches(e_tensor, repl_dict=repl_dict, old=old) + if m is None: + continue + else: + matched_this_q = True + repl_dict.update(m) + matched_e_tensors.append(e_tensor) + break + + if not matched_this_q: + return None + + remaining_e_tensors = [t for t in expr_sifted["nonwild"] if t not in matched_e_tensors] + for w in query_sifted["otherwild"]: + for e in remaining_e_tensors: + m = w.matches(e) + if m is not None: + matched_e_tensors.append(e) + if w in repl_dict.keys(): + repl_dict[w] += m.pop(w) + repl_dict.update(m) + + remaining_e_tensors = [t for t in expr_sifted["nonwild"] if t not in matched_e_tensors] + for w in query_sifted["wildtensor"]: + for e in remaining_e_tensors: + m = w.matches(e) + if m is not None: + matched_e_tensors.append(e) + if w.component in repl_dict.keys(): + repl_dict[w.component] += m.pop(w.component) + repl_dict.update(m) + + remaining_e_tensors = [t for t in expr_sifted["nonwild"] if t not in matched_e_tensors] + for w in query_sifted["indexless_wildtensor"]: + for e in remaining_e_tensors: + m = w.matches(e) + if m is not None: + matched_e_tensors.append(e) + if w.component in repl_dict.keys(): + repl_dict[w.component] += m.pop(w.component) + repl_dict.update(m) + + remaining_e_tensors = [t for t in expr_sifted["nonwild"] if t not in matched_e_tensors] + if len(remaining_e_tensors) > 0: + return None + else: + return repl_dict + + +class Tensor(TensExpr): + """ + Base tensor class, i.e. this represents a tensor, the single unit to be + put into an expression. + + Explanation + =========== + + This object is usually created from a ``TensorHead``, by attaching indices + to it. Indices preceded by a minus sign are considered contravariant, + otherwise covariant. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead + >>> Lorentz = TensorIndexType("Lorentz", dummy_name="L") + >>> mu, nu = tensor_indices('mu nu', Lorentz) + >>> A = TensorHead("A", [Lorentz, Lorentz]) + >>> A(mu, -nu) + A(mu, -nu) + >>> A(mu, -mu) + A(L_0, -L_0) + + It is also possible to use symbols instead of inidices (appropriate indices + are then generated automatically). + + >>> from sympy import Symbol + >>> x = Symbol('x') + >>> A(x, mu) + A(x, mu) + >>> A(x, -x) + A(L_0, -L_0) + + """ + + is_commutative = False + + _index_structure: _IndexStructure + args: tuple[TensorHead, Tuple] + + def __new__(cls, tensor_head, indices, *, is_canon_bp=False, **kw_args): + indices = cls._parse_indices(tensor_head, indices) + obj = Basic.__new__(cls, tensor_head, Tuple(*indices), **kw_args) + obj._index_structure = _IndexStructure.from_indices(*indices) + obj._free = obj._index_structure.free[:] + obj._dum = obj._index_structure.dum[:] + obj._ext_rank = obj._index_structure._ext_rank + obj._coeff = S.One + obj._nocoeff = obj + obj._component = tensor_head + obj._components = [tensor_head] + if tensor_head.rank != len(indices): + raise ValueError("wrong number of indices") + obj.is_canon_bp = is_canon_bp + obj._index_map = Tensor._build_index_map(indices, obj._index_structure) + return obj + + @property + def free(self): + return self._free + + @property + def dum(self): + return self._dum + + @property + def ext_rank(self): + return self._ext_rank + + @property + def coeff(self): + return self._coeff + + @property + def nocoeff(self): + return self._nocoeff + + @property + def component(self): + return self._component + + @property + def components(self): + return self._components + + @property + def head(self): + return self.args[0] + + @property + def indices(self): + return self.args[1] + + @property + def free_indices(self): + return set(self._index_structure.get_free_indices()) + + @property + def index_types(self): + return self.head.index_types + + @property + def rank(self): + return len(self.free_indices) + + @staticmethod + def _build_index_map(indices, index_structure): + index_map = {} + for idx in indices: + index_map[idx] = (indices.index(idx),) + return index_map + + def doit(self, **hints): + args, indices, free, dum = TensMul._tensMul_contract_indices([self]) + return args[0] + + @staticmethod + def _parse_indices(tensor_head, indices): + if not isinstance(indices, (tuple, list, Tuple)): + raise TypeError("indices should be an array, got %s" % type(indices)) + indices = list(indices) + for i, index in enumerate(indices): + if isinstance(index, Symbol): + indices[i] = TensorIndex(index, tensor_head.index_types[i], True) + elif isinstance(index, Mul): + c, e = index.as_coeff_Mul() + if c == -1 and isinstance(e, Symbol): + indices[i] = TensorIndex(e, tensor_head.index_types[i], False) + else: + raise ValueError("index not understood: %s" % index) + elif not isinstance(index, TensorIndex): + raise TypeError("wrong type for index: %s is %s" % (index, type(index))) + return indices + + def _set_new_index_structure(self, im, is_canon_bp=False): + indices = im.get_indices() + return self._set_indices(*indices, is_canon_bp=is_canon_bp) + + def _set_indices(self, *indices, is_canon_bp=False, **kw_args): + if len(indices) != self.ext_rank: + raise ValueError("indices length mismatch") + return self.func(self.args[0], indices, is_canon_bp=is_canon_bp).doit() + + def _get_free_indices_set(self): + return {i[0] for i in self._index_structure.free} + + def _get_dummy_indices_set(self): + dummy_pos = set(itertools.chain(*self._index_structure.dum)) + return {idx for i, idx in enumerate(self.args[1]) if i in dummy_pos} + + def _get_indices_set(self): + return set(self.args[1].args) + + @property + def free_in_args(self): + return [(ind, pos, 0) for ind, pos in self.free] + + @property + def dum_in_args(self): + return [(p1, p2, 0, 0) for p1, p2 in self.dum] + + @property + def free_args(self): + return sorted([x[0] for x in self.free]) + + def commutes_with(self, other): + """ + :param other: + :return: + 0 commute + 1 anticommute + None neither commute nor anticommute + """ + if not isinstance(other, TensExpr): + return 0 + elif isinstance(other, Tensor): + return self.component.commutes_with(other.component) + return NotImplementedError + + def perm2tensor(self, g, is_canon_bp=False): + """ + Returns the tensor corresponding to the permutation ``g``. + + For further details, see the method in ``TIDS`` with the same name. + """ + return perm2tensor(self, g, is_canon_bp) + + def canon_bp(self): + if self.is_canon_bp: + return self + expr = self.expand() + g, dummies, msym = expr._index_structure.indices_canon_args() + v = components_canon_args([expr.component]) + can = canonicalize(g, dummies, msym, *v) + if can == 0: + return S.Zero + tensor = self.perm2tensor(can, True) + return tensor + + def split(self): + return [self] + + def sorted_components(self): + return self + + def get_indices(self) -> list[TensorIndex]: + """ + Get a list of indices, corresponding to those of the tensor. + """ + return list(self.args[1]) + + def get_free_indices(self) -> list[TensorIndex]: + """ + Get a list of free indices, corresponding to those of the tensor. + """ + return self._index_structure.get_free_indices() + + def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: + # TODO: this could be optimized by only swapping the indices + # instead of visiting the whole expression tree: + return self.xreplace(repl) + + def as_base_exp(self): + return self, S.One + + def substitute_indices(self, *index_tuples): + """ + Return a tensor with free indices substituted according to ``index_tuples``. + + ``index_types`` list of tuples ``(old_index, new_index)``. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads, TensorSymmetry + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) + >>> A, B = tensor_heads('A,B', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) + >>> t = A(i, k)*B(-k, -j); t + A(i, L_0)*B(-L_0, -j) + >>> t.substitute_indices((i, k),(-j, l)) + A(k, L_0)*B(-L_0, l) + """ + indices = [] + for index in self.indices: + for ind_old, ind_new in index_tuples: + if (index.name == ind_old.name and index.tensor_index_type == + ind_old.tensor_index_type): + if index.is_up == ind_old.is_up: + indices.append(ind_new) + else: + indices.append(-ind_new) + break + else: + indices.append(index) + return self.head(*indices) + + def _get_symmetrized_forms(self): + """ + Return a list giving all possible permutations of self that are allowed by its symmetries. + """ + comp = self.component + gens = comp.symmetry.generators + rank = comp.rank + + old_perms = None + new_perms = {self} + while new_perms != old_perms: + old_perms = new_perms.copy() + for tens in old_perms: + for gen in gens: + inds = tens.get_indices() + per = [gen.apply(i) for i in range(0,rank)] + sign = (-1)**(gen.apply(rank) - rank) + ind_map = dict(zip(inds, [inds[i] for i in per])) + new_perms.add( sign * tens._replace_indices(ind_map) ) + + return new_perms + + def matches(self, expr, repl_dict=None, old=False): + expr = sympify(expr) + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + #simple checks + if self == expr: + return repl_dict + if not isinstance(expr, Tensor): + return None + if self.head != expr.head: + return None + + #Now consider all index symmetries of expr, and see if any of them allow a match. + for new_expr in expr._get_symmetrized_forms(): + m = self._matches(new_expr, repl_dict, old=old) + if m is not None: + repl_dict.update(m) + return repl_dict + + return None + + def _matches(self, expr, repl_dict=None, old=False): + """ + This does not account for index symmetries of expr + """ + expr = sympify(expr) + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + #simple checks + if self == expr: + return repl_dict + if not isinstance(expr, Tensor): + return None + if self.head != expr.head: + return None + + s_indices = self.get_indices() + e_indices = expr.get_indices() + + if len(s_indices) != len(e_indices): + return None + + for i in range(len(s_indices)): + s_ind = s_indices[i] + m = s_ind.matches(e_indices[i]) + if m is None: + return None + elif -s_ind in repl_dict.keys() and -repl_dict[-s_ind] != m[s_ind]: + return None + else: + repl_dict.update(m) + + return repl_dict + + def __call__(self, *indices): + deprecate_call() + free_args = self.free_args + indices = list(indices) + if [x.tensor_index_type for x in indices] != [x.tensor_index_type for x in free_args]: + raise ValueError('incompatible types') + if indices == free_args: + return self + t = self.substitute_indices(*list(zip(free_args, indices))) + + # object is rebuilt in order to make sure that all contracted indices + # get recognized as dummies, but only if there are contracted indices. + if len({i if i.is_up else -i for i in indices}) != len(indices): + return t.func(*t.args) + return t + + # TODO: put this into TensExpr? + def __iter__(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return self.data.__iter__() + + # TODO: put this into TensExpr? + def __getitem__(self, item): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return self.data[item] + + def _extract_data(self, replacement_dict): + from .array import Array + for k, v in replacement_dict.items(): + if isinstance(k, Tensor) and k.args[0] == self.args[0]: + other = k + array = v + break + else: + raise ValueError("%s not found in %s" % (self, replacement_dict)) + + # TODO: inefficient, this should be done at root level only: + replacement_dict = {k: Array(v) for k, v in replacement_dict.items()} + array = Array(array) + + dum1 = self.dum + dum2 = other.dum + + if len(dum2) > 0: + for pair in dum2: + # allow `dum2` if the contained values are also in `dum1`. + if pair not in dum1: + raise NotImplementedError("%s with contractions is not implemented" % other) + # Remove elements in `dum2` from `dum1`: + dum1 = [pair for pair in dum1 if pair not in dum2] + if len(dum1) > 0: + indices1 = self.get_indices() + indices2 = other.get_indices() + repl = {} + for p1, p2 in dum1: + repl[indices2[p2]] = -indices2[p1] + for pos in (p1, p2): + if indices1[pos].is_up ^ indices2[pos].is_up: + metric = replacement_dict[indices1[pos].tensor_index_type] + if indices1[pos].is_up: + metric = _TensorDataLazyEvaluator.inverse_matrix(metric) + array = self._contract_and_permute_with_metric(metric, array, pos, len(indices2)) + other = other.xreplace(repl).doit() + array = _TensorDataLazyEvaluator.data_contract_dum([array], dum1, len(indices2)) + + free_ind1 = self.get_free_indices() + free_ind2 = other.get_free_indices() + + return self._match_indices_with_other_tensor(array, free_ind1, free_ind2, replacement_dict) + + @property + def data(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return _tensor_data_substitution_dict[self] + + @data.setter + def data(self, data): + deprecate_data() + # TODO: check data compatibility with properties of tensor. + with ignore_warnings(SymPyDeprecationWarning): + _tensor_data_substitution_dict[self] = data + + @data.deleter + def data(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + if self in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[self] + if self.metric in _tensor_data_substitution_dict: + del _tensor_data_substitution_dict[self.metric] + + def _print(self): + indices = [str(ind) for ind in self.indices] + component = self.component + if component.rank > 0: + return ('%s(%s)' % (component.name, ', '.join(indices))) + else: + return ('%s' % component.name) + + def equals(self, other): + if other == 0: + return self.coeff == 0 + other = _sympify(other) + if not isinstance(other, TensExpr): + assert not self.components + return S.One == other + + def _get_compar_comp(self): + t = self.canon_bp() + r = (t.coeff, tuple(t.components), \ + tuple(sorted(t.free)), tuple(sorted(t.dum))) + return r + + return _get_compar_comp(self) == _get_compar_comp(other) + + def contract_metric(self, g): + # if metric is not the same, ignore this step: + if self.component != g: + return self + # in case there are free components, do not perform anything: + if len(self.free) != 0: + return self + + #antisym = g.index_types[0].metric_antisym + if g.symmetry == TensorSymmetry.fully_symmetric(-2): + antisym = 1 + elif g.symmetry == TensorSymmetry.fully_symmetric(2): + antisym = 0 + elif g.symmetry == TensorSymmetry.no_symmetry(2): + antisym = None + else: + raise NotImplementedError + sign = S.One + typ = g.index_types[0] + + if not antisym: + # g(i, -i) + sign = sign*typ.dim + else: + # g(i, -i) + sign = sign*typ.dim + + dp0, dp1 = self.dum[0] + if dp0 < dp1: + # g(i, -i) = -D with antisymmetric metric + sign = -sign + + return sign + + def contract_delta(self, metric): + return self.contract_metric(metric) + + def _eval_rewrite_as_Indexed(self, tens, indices, **kwargs): + from sympy.tensor.indexed import Indexed + # TODO: replace .args[0] with .name: + index_symbols = [i.args[0] for i in self.get_indices()] + expr = Indexed(tens.args[0], *index_symbols) + return self._check_add_Sum(expr, index_symbols) + + def _eval_partial_derivative(self, s: Tensor) -> Expr: + + if not isinstance(s, Tensor): + return S.Zero + else: + + # @a_i/@a_k = delta_i^k + # @a_i/@a^k = g_ij delta^j_k + # @a^i/@a^k = delta^i_k + # @a^i/@a_k = g^ij delta_j^k + # TODO: if there is no metric present, the derivative should be zero? + + if self.head != s.head: + return S.Zero + + # if heads are the same, provide delta and/or metric products + # for every free index pair in the appropriate tensor + # assumed that the free indices are in proper order + # A contravariante index in the derivative becomes covariant + # after performing the derivative and vice versa + + kronecker_delta_list = [1] + + # not guarantee a correct index order + + for (count, (iself, iother)) in enumerate(zip(self.get_free_indices(), s.get_free_indices())): + if iself.tensor_index_type != iother.tensor_index_type: + raise ValueError("index types not compatible") + else: + tensor_index_type = iself.tensor_index_type + tensor_metric = tensor_index_type.metric + dummy = TensorIndex("d_" + str(count), tensor_index_type, + is_up=iself.is_up) + if iself.is_up == iother.is_up: + kroneckerdelta = tensor_index_type.delta(iself, -iother) + else: + kroneckerdelta = ( + TensMul(tensor_metric(iself, dummy), + tensor_index_type.delta(-dummy, -iother)) + ) + kronecker_delta_list.append(kroneckerdelta) + return TensMul.fromiter(kronecker_delta_list).doit(deep=False) + # doit necessary to rename dummy indices accordingly + + +class TensMul(TensExpr, AssocOp): + """ + Product of tensors. + + Parameters + ========== + + coeff : SymPy coefficient of the tensor + args + + Attributes + ========== + + ``components`` : list of ``TensorHead`` of the component tensors + ``types`` : list of nonrepeated ``TensorIndexType`` + ``free`` : list of ``(ind, ipos, icomp)``, see Notes + ``dum`` : list of ``(ipos1, ipos2, icomp1, icomp2)``, see Notes + ``ext_rank`` : rank of the tensor counting the dummy indices + ``rank`` : rank of the tensor + ``coeff`` : SymPy coefficient of the tensor + ``free_args`` : list of the free indices in sorted order + ``is_canon_bp`` : ``True`` if the tensor in in canonical form + + Notes + ===== + + ``args[0]`` list of ``TensorHead`` of the component tensors. + + ``args[1]`` list of ``(ind, ipos, icomp)`` + where ``ind`` is a free index, ``ipos`` is the slot position + of ``ind`` in the ``icomp``-th component tensor. + + ``args[2]`` list of tuples representing dummy indices. + ``(ipos1, ipos2, icomp1, icomp2)`` indicates that the contravariant + dummy index is the ``ipos1``-th slot position in the ``icomp1``-th + component tensor; the corresponding covariant index is + in the ``ipos2`` slot position in the ``icomp2``-th component tensor. + + """ + identity = S.One + + _index_structure: _IndexStructure + + def __new__(cls, *args, **kw_args): + is_canon_bp = kw_args.get('is_canon_bp', False) + args = list(map(_sympify, args)) + + """ + If the internal dummy indices in one arg conflict with the free indices + of the remaining args, we need to rename those internal dummy indices. + """ + free = [get_free_indices(arg) for arg in args] + free = set(itertools.chain(*free)) #flatten free + newargs = [] + for arg in args: + dum_this = set(get_dummy_indices(arg)) + dum_other = [get_dummy_indices(a) for a in newargs] + dum_other = set(itertools.chain(*dum_other)) #flatten dum_other + free_this = set(get_free_indices(arg)) + if len(dum_this.intersection(free)) > 0: + exclude = free_this.union(free, dum_other) + newarg = TensMul._dedupe_indices(arg, exclude) + else: + newarg = arg + newargs.append(newarg) + + args = newargs + + # Flatten: + args = [i for arg in args for i in (arg.args if isinstance(arg, (TensMul, Mul)) else [arg])] + + args, indices, free, dum = TensMul._tensMul_contract_indices(args, replace_indices=False) + + # Data for indices: + index_types = [i.tensor_index_type for i in indices] + index_structure = _IndexStructure(free, dum, index_types, indices, canon_bp=is_canon_bp) + + obj = TensExpr.__new__(cls, *args) + obj._indices = indices + obj._index_types = index_types.copy() + obj._index_structure = index_structure + obj._free = index_structure.free[:] + obj._dum = index_structure.dum[:] + obj._free_indices = {x[0] for x in obj.free} + obj._rank = len(obj.free) + obj._ext_rank = len(obj._index_structure.free) + 2*len(obj._index_structure.dum) + obj._coeff = S.One + obj._is_canon_bp = is_canon_bp + return obj + + index_types = property(lambda self: self._index_types) + free = property(lambda self: self._free) + dum = property(lambda self: self._dum) + free_indices = property(lambda self: self._free_indices) + rank = property(lambda self: self._rank) + ext_rank = property(lambda self: self._ext_rank) + + @staticmethod + def _indices_to_free_dum(args_indices): + free2pos1 = {} + free2pos2 = {} + dummy_data = [] + indices = [] + + # Notation for positions (to better understand the code): + # `pos1`: position in the `args`. + # `pos2`: position in the indices. + + # Example: + # A(i, j)*B(k, m, n)*C(p) + # `pos1` of `n` is 1 because it's in `B` (second `args` of TensMul). + # `pos2` of `n` is 4 because it's the fifth overall index. + + # Counter for the index position wrt the whole expression: + pos2 = 0 + + for pos1, arg_indices in enumerate(args_indices): + + for index in arg_indices: + if not isinstance(index, TensorIndex): + raise TypeError("expected TensorIndex") + if -index in free2pos1: + # Dummy index detected: + other_pos1 = free2pos1.pop(-index) + other_pos2 = free2pos2.pop(-index) + if index.is_up: + dummy_data.append((index, pos1, other_pos1, pos2, other_pos2)) + else: + dummy_data.append((-index, other_pos1, pos1, other_pos2, pos2)) + indices.append(index) + elif index in free2pos1: + raise ValueError("Repeated index: %s" % index) + else: + free2pos1[index] = pos1 + free2pos2[index] = pos2 + indices.append(index) + pos2 += 1 + + free = list(free2pos2.items()) + free_names = [i.name for i in free2pos2.keys()] + + dummy_data.sort(key=lambda x: x[3]) + return indices, free, free_names, dummy_data + + @staticmethod + def _dummy_data_to_dum(dummy_data): + return [(p2a, p2b) for (i, p1a, p1b, p2a, p2b) in dummy_data] + + @staticmethod + def _tensMul_contract_indices(args, replace_indices=True): + replacements = [{} for _ in args] + + #_index_order = all(_has_index_order(arg) for arg in args) + + args_indices = [get_indices(arg) for arg in args] + indices, free, free_names, dummy_data = TensMul._indices_to_free_dum(args_indices) + + cdt = defaultdict(int) + + def dummy_name_gen(tensor_index_type): + nd = str(cdt[tensor_index_type]) + cdt[tensor_index_type] += 1 + return tensor_index_type.dummy_name + '_' + nd + + if replace_indices: + for old_index, pos1cov, pos1contra, pos2cov, pos2contra in dummy_data: + index_type = old_index.tensor_index_type + while True: + dummy_name = dummy_name_gen(index_type) + if dummy_name not in free_names: + break + dummy = old_index.func(dummy_name, index_type, *old_index.args[2:]) + replacements[pos1cov][old_index] = dummy + replacements[pos1contra][-old_index] = -dummy + indices[pos2cov] = dummy + indices[pos2contra] = -dummy + args = [ + arg._replace_indices(repl) if isinstance(arg, TensExpr) else arg + for arg, repl in zip(args, replacements)] + + """ + The order of indices might've changed due to the replacements (e.g. if one of the args is a TensAdd, replacing an index can change the sort order of the terms, thus changing the order of indices returned by its get_indices() method). + To stay on the safe side, we calculate these quantities again. + """ + args_indices = [get_indices(arg) for arg in args] + indices, free, free_names, dummy_data = TensMul._indices_to_free_dum(args_indices) + + dum = TensMul._dummy_data_to_dum(dummy_data) + return args, indices, free, dum + + @staticmethod + def _get_components_from_args(args): + """ + Get a list of ``Tensor`` objects having the same ``TIDS`` if multiplied + by one another. + """ + components = [] + for arg in args: + if not isinstance(arg, TensExpr): + continue + if isinstance(arg, TensAdd): + continue + components.extend(arg.components) + return components + + @staticmethod + def _rebuild_tensors_list(args, index_structure): + indices = index_structure.get_indices() + #tensors = [None for i in components] # pre-allocate list + ind_pos = 0 + for i, arg in enumerate(args): + if not isinstance(arg, TensExpr): + continue + prev_pos = ind_pos + ind_pos += arg.ext_rank + args[i] = Tensor(arg.component, indices[prev_pos:ind_pos]) + + def doit(self, **hints): + is_canon_bp = self._is_canon_bp + deep = hints.get('deep', True) + if deep: + args = [arg.doit(**hints) for arg in self.args] + + """ + There may now be conflicts between dummy indices of different args + (each arg's doit method does not have any information about which + dummy indices are already used in the other args), so we + deduplicate them. + """ + rule = dict(zip(self.args, args)) + rule = self._dedupe_indices_in_rule(rule) + args = [rule[a] for a in self.args] + + else: + args = self.args + + args = [arg for arg in args if arg != self.identity] + + # Extract non-tensor coefficients: + coeff = reduce(lambda a, b: a*b, [arg for arg in args if not isinstance(arg, TensExpr)], S.One) + args = [arg for arg in args if isinstance(arg, TensExpr)] + + if len(args) == 0: + return coeff + + if coeff != self.identity: + args = [coeff] + args + if coeff == 0: + return S.Zero + + if len(args) == 1: + return args[0] + + args, indices, free, dum = TensMul._tensMul_contract_indices(args) + + # Data for indices: + index_types = [i.tensor_index_type for i in indices] + index_structure = _IndexStructure(free, dum, index_types, indices, canon_bp=is_canon_bp) + + obj = self.func(*args) + obj._index_types = index_types + obj._index_structure = index_structure + obj._ext_rank = len(obj._index_structure.free) + 2*len(obj._index_structure.dum) + obj._coeff = coeff + obj._is_canon_bp = is_canon_bp + return obj + + # TODO: this method should be private + # TODO: should this method be renamed _from_components_free_dum ? + @staticmethod + def from_data(coeff, components, free, dum, **kw_args): + return TensMul(coeff, *TensMul._get_tensors_from_components_free_dum(components, free, dum), **kw_args).doit(deep=False) + + @staticmethod + def _get_tensors_from_components_free_dum(components, free, dum): + """ + Get a list of ``Tensor`` objects by distributing ``free`` and ``dum`` indices on the ``components``. + """ + index_structure = _IndexStructure.from_components_free_dum(components, free, dum) + indices = index_structure.get_indices() + tensors = [None for i in components] # pre-allocate list + + # distribute indices on components to build a list of tensors: + ind_pos = 0 + for i, component in enumerate(components): + prev_pos = ind_pos + ind_pos += component.rank + tensors[i] = Tensor(component, indices[prev_pos:ind_pos]) + return tensors + + def _get_free_indices_set(self): + return {i[0] for i in self.free} + + def _get_dummy_indices_set(self): + dummy_pos = set(itertools.chain(*self.dum)) + return {idx for i, idx in enumerate(self._index_structure.get_indices()) if i in dummy_pos} + + def _get_position_offset_for_indices(self): + arg_offset = [None for i in range(self.ext_rank)] + counter = 0 + for arg in self.args: + if not isinstance(arg, TensExpr): + continue + for j in range(arg.ext_rank): + arg_offset[j + counter] = counter + counter += arg.ext_rank + return arg_offset + + @property + def free_args(self): + return sorted([x[0] for x in self.free]) + + @property + def components(self): + return self._get_components_from_args(self.args) + + @property + def free_in_args(self): + arg_offset = self._get_position_offset_for_indices() + argpos = self._get_indices_to_args_pos() + return [(ind, pos-arg_offset[pos], argpos[pos]) for (ind, pos) in self.free] + + @property + def coeff(self): + # return Mul.fromiter([c for c in self.args if not isinstance(c, TensExpr)]) + return self._coeff + + @property + def nocoeff(self): + return self.func(*self.args, 1/self.coeff).doit(deep=False) + + @property + def dum_in_args(self): + arg_offset = self._get_position_offset_for_indices() + argpos = self._get_indices_to_args_pos() + return [(p1-arg_offset[p1], p2-arg_offset[p2], argpos[p1], argpos[p2]) for p1, p2 in self.dum] + + def equals(self, other): + if other == 0: + return self.coeff == 0 + other = _sympify(other) + if not isinstance(other, TensExpr): + assert not self.components + return self.coeff == other + + return self.canon_bp() == other.canon_bp() + + def get_indices(self): + """ + Returns the list of indices of the tensor. + + Explanation + =========== + + The indices are listed in the order in which they appear in the + component tensors. + The dummy indices are given a name which does not collide with + the names of the free indices. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> g = Lorentz.metric + >>> p, q = tensor_heads('p,q', [Lorentz]) + >>> t = p(m1)*g(m0,m2) + >>> t.get_indices() + [m1, m0, m2] + >>> t2 = p(m1)*g(-m1, m2) + >>> t2.get_indices() + [L_0, -L_0, m2] + """ + return self._indices + + def get_free_indices(self) -> list[TensorIndex]: + """ + Returns the list of free indices of the tensor. + + Explanation + =========== + + The indices are listed in the order in which they appear in the + component tensors. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> g = Lorentz.metric + >>> p, q = tensor_heads('p,q', [Lorentz]) + >>> t = p(m1)*g(m0,m2) + >>> t.get_free_indices() + [m1, m0, m2] + >>> t2 = p(m1)*g(-m1, m2) + >>> t2.get_free_indices() + [m2] + """ + return self._index_structure.get_free_indices() + + def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: + return self.func(*[arg._replace_indices(repl) if isinstance(arg, TensExpr) else arg for arg in self.args]) + + def split(self): + """ + Returns a list of tensors, whose product is ``self``. + + Explanation + =========== + + Dummy indices contracted among different tensor components + become free indices with the same name as the one used to + represent the dummy indices. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads, TensorSymmetry + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) + >>> A, B = tensor_heads('A,B', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) + >>> t = A(a,b)*B(-b,c) + >>> t + A(a, L_0)*B(-L_0, c) + >>> t.split() + [A(a, L_0), B(-L_0, c)] + """ + if self.args == (): + return [self] + splitp = [] + res = 1 + for arg in self.args: + if isinstance(arg, Tensor): + splitp.append(res*arg) + res = 1 + else: + res *= arg + return splitp + + def _eval_expand_mul(self, **hints): + args1 = [arg.args if isinstance(arg, (Add, TensAdd)) else (arg,) for arg in self.args] + return TensAdd(*[ + TensMul(*i).doit(deep=False) for i in itertools.product(*args1)] + ) + + def __neg__(self): + return TensMul(S.NegativeOne, self, is_canon_bp=self._is_canon_bp).doit(deep=False) + + def __getitem__(self, item): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + return self.data[item] + + def _get_args_for_traditional_printer(self): + args = list(self.args) + if self.coeff.could_extract_minus_sign(): + # expressions like "-A(a)" + sign = "-" + if args[0] == S.NegativeOne: + args = args[1:] + else: + args[0] = -args[0] + else: + sign = "" + return sign, args + + def _sort_args_for_sorted_components(self): + """ + Returns the ``args`` sorted according to the components commutation + properties. + + Explanation + =========== + + The sorting is done taking into account the commutation group + of the component tensors. + """ + cv = [arg for arg in self.args if isinstance(arg, TensExpr)] + sign = 1 + n = len(cv) - 1 + for i in range(n): + for j in range(n, i, -1): + c = cv[j-1].commutes_with(cv[j]) + # if `c` is `None`, it does neither commute nor anticommute, skip: + if c not in (0, 1): + continue + typ1 = sorted(set(cv[j-1].component.index_types), key=lambda x: x.name) + typ2 = sorted(set(cv[j].component.index_types), key=lambda x: x.name) + if (typ1, cv[j-1].component.name) > (typ2, cv[j].component.name): + cv[j-1], cv[j] = cv[j], cv[j-1] + # if `c` is 1, the anticommute, so change sign: + if c: + sign = -sign + + coeff = sign * self.coeff + if coeff != 1: + return [coeff] + cv + return cv + + def sorted_components(self): + """ + Returns a tensor product with sorted components. + """ + return TensMul(*self._sort_args_for_sorted_components()).doit(deep=False) + + def perm2tensor(self, g, is_canon_bp=False): + """ + Returns the tensor corresponding to the permutation ``g`` + + For further details, see the method in ``TIDS`` with the same name. + """ + return perm2tensor(self, g, is_canon_bp=is_canon_bp) + + def canon_bp(self): + """ + Canonicalize using the Butler-Portugal algorithm for canonicalization + under monoterm symmetries. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead, TensorSymmetry + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> A = TensorHead('A', [Lorentz]*2, TensorSymmetry.fully_symmetric(-2)) + >>> t = A(m0,-m1)*A(m1,-m0) + >>> t.canon_bp() + -A(L_0, L_1)*A(-L_0, -L_1) + >>> t = A(m0,-m1)*A(m1,-m2)*A(m2,-m0) + >>> t.canon_bp() + 0 + """ + if self._is_canon_bp: + return self + expr = self.expand() + if isinstance(expr, TensAdd): + return expr.canon_bp() + if not expr.components: + return expr + expr = expr.doit(deep=False) #make sure self.coeff is populated correctly + t = expr.sorted_components() + g, dummies, msym = t._index_structure.indices_canon_args() + v = components_canon_args(t.components) + can = canonicalize(g, dummies, msym, *v) + if can == 0: + return S.Zero + tmul = t.perm2tensor(can, True) + return tmul + + def contract_delta(self, delta): + t = self.contract_metric(delta) + return t + + def _get_indices_to_args_pos(self): + """ + Get a dict mapping the index position to TensMul's argument number. + """ + pos_map = {} + pos_counter = 0 + for arg_i, arg in enumerate(self.args): + if not isinstance(arg, TensExpr): + continue + assert isinstance(arg, Tensor) + for i in range(arg.ext_rank): + pos_map[pos_counter] = arg_i + pos_counter += 1 + return pos_map + + def contract_metric(self, g): + """ + Raise or lower indices with the metric ``g``. + + Parameters + ========== + + g : metric + + Notes + ===== + + See the ``TensorIndexType`` docstring for the contraction conventions. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) + >>> g = Lorentz.metric + >>> p, q = tensor_heads('p,q', [Lorentz]) + >>> t = p(m0)*q(m1)*g(-m0, -m1) + >>> t.canon_bp() + metric(L_0, L_1)*p(-L_0)*q(-L_1) + >>> t.contract_metric(g).canon_bp() + p(L_0)*q(-L_0) + """ + expr = self.expand().doit(deep=False) + if self != expr: + expr = canon_bp(expr) + return contract_metric(expr, g) + pos_map = self._get_indices_to_args_pos() + args = list(self.args) + + #antisym = g.index_types[0].metric_antisym + if g.symmetry == TensorSymmetry.fully_symmetric(-2): + antisym = 1 + elif g.symmetry == TensorSymmetry.fully_symmetric(2): + antisym = 0 + elif g.symmetry == TensorSymmetry.no_symmetry(2): + antisym = None + else: + raise NotImplementedError + + # list of positions of the metric ``g`` inside ``args`` + gpos = [i for i, x in enumerate(self.args) if isinstance(x, Tensor) and x.component == g] + if not gpos: + return self + + # Sign is either 1 or -1, to correct the sign after metric contraction + # (for spinor indices). + sign = 1 + dum = self.dum[:] + free = self.free[:] + elim = set() + for gposx in gpos: + if gposx in elim: + continue + free1 = [x for x in free if pos_map[x[1]] == gposx] + dum1 = [x for x in dum if pos_map[x[0]] == gposx or pos_map[x[1]] == gposx] + if not dum1: + continue + elim.add(gposx) + # subs with the multiplication neutral element, that is, remove it: + args[gposx] = 1 + if len(dum1) == 2: + if not antisym: + dum10, dum11 = dum1 + if pos_map[dum10[1]] == gposx: + # the index with pos p0 contravariant + p0 = dum10[0] + else: + # the index with pos p0 is covariant + p0 = dum10[1] + if pos_map[dum11[1]] == gposx: + # the index with pos p1 is contravariant + p1 = dum11[0] + else: + # the index with pos p1 is covariant + p1 = dum11[1] + + dum.append((p0, p1)) + else: + dum10, dum11 = dum1 + # change the sign to bring the indices of the metric to contravariant + # form; change the sign if dum10 has the metric index in position 0 + if pos_map[dum10[1]] == gposx: + # the index with pos p0 is contravariant + p0 = dum10[0] + if dum10[1] == 1: + sign = -sign + else: + # the index with pos p0 is covariant + p0 = dum10[1] + if dum10[0] == 0: + sign = -sign + if pos_map[dum11[1]] == gposx: + # the index with pos p1 is contravariant + p1 = dum11[0] + sign = -sign + else: + # the index with pos p1 is covariant + p1 = dum11[1] + + dum.append((p0, p1)) + + elif len(dum1) == 1: + if not antisym: + dp0, dp1 = dum1[0] + if pos_map[dp0] == pos_map[dp1]: + # g(i, -i) + typ = g.index_types[0] + sign = sign*typ.dim + + else: + # g(i0, i1)*p(-i1) + if pos_map[dp0] == gposx: + p1 = dp1 + else: + p1 = dp0 + + ind, p = free1[0] + free.append((ind, p1)) + else: + dp0, dp1 = dum1[0] + if pos_map[dp0] == pos_map[dp1]: + # g(i, -i) + typ = g.index_types[0] + sign = sign*typ.dim + + if dp0 < dp1: + # g(i, -i) = -D with antisymmetric metric + sign = -sign + else: + # g(i0, i1)*p(-i1) + if pos_map[dp0] == gposx: + p1 = dp1 + if dp0 == 0: + sign = -sign + else: + p1 = dp0 + ind, p = free1[0] + free.append((ind, p1)) + dum = [x for x in dum if x not in dum1] + free = [x for x in free if x not in free1] + + # shift positions: + shift = 0 + shifts = [0]*len(args) + for i in range(len(args)): + if i in elim: + shift += 2 + continue + shifts[i] = shift + free = [(ind, p - shifts[pos_map[p]]) for (ind, p) in free if pos_map[p] not in elim] + dum = [(p0 - shifts[pos_map[p0]], p1 - shifts[pos_map[p1]]) for p0, p1 in dum if pos_map[p0] not in elim and pos_map[p1] not in elim] + + res = ( sign*TensMul(*args) ).doit(deep=False) + if not isinstance(res, TensExpr): + return res + im = _IndexStructure.from_components_free_dum(res.components, free, dum) + return res._set_new_index_structure(im) + + def _set_new_index_structure(self, im, is_canon_bp=False): + indices = im.get_indices() + return self._set_indices(*indices, is_canon_bp=is_canon_bp) + + def _set_indices(self, *indices, is_canon_bp=False, **kw_args): + if len(indices) != self.ext_rank: + raise ValueError("indices length mismatch") + args = list(self.args) + pos = 0 + for i, arg in enumerate(args): + if not isinstance(arg, TensExpr): + continue + assert isinstance(arg, Tensor) + ext_rank = arg.ext_rank + args[i] = arg._set_indices(*indices[pos:pos+ext_rank]) + pos += ext_rank + return TensMul(*args, is_canon_bp=is_canon_bp).doit(deep=False) + + @staticmethod + def _index_replacement_for_contract_metric(args, free, dum): + for arg in args: + if not isinstance(arg, TensExpr): + continue + assert isinstance(arg, Tensor) + + def substitute_indices(self, *index_tuples): + new_args = [] + for arg in self.args: + if isinstance(arg, TensExpr): + arg = arg.substitute_indices(*index_tuples) + new_args.append(arg) + return TensMul(*new_args).doit(deep=False) + + def __call__(self, *indices): + deprecate_call() + free_args = self.free_args + indices = list(indices) + if [x.tensor_index_type for x in indices] != [x.tensor_index_type for x in free_args]: + raise ValueError('incompatible types') + if indices == free_args: + return self + t = self.substitute_indices(*list(zip(free_args, indices))) + + # object is rebuilt in order to make sure that all contracted indices + # get recognized as dummies, but only if there are contracted indices. + if len({i if i.is_up else -i for i in indices}) != len(indices): + return t.func(*t.args) + return t + + def _extract_data(self, replacement_dict): + args_indices, arrays = zip(*[arg._extract_data(replacement_dict) for arg in self.args if isinstance(arg, TensExpr)]) + coeff = reduce(operator.mul, [a for a in self.args if not isinstance(a, TensExpr)], S.One) + indices, free, free_names, dummy_data = TensMul._indices_to_free_dum(args_indices) + dum = TensMul._dummy_data_to_dum(dummy_data) + ext_rank = self.ext_rank + free.sort(key=lambda x: x[1]) + free_indices = [i[0] for i in free] + return free_indices, coeff*_TensorDataLazyEvaluator.data_contract_dum(arrays, dum, ext_rank) + + @property + def data(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + dat = _tensor_data_substitution_dict[self.expand()] + return dat + + @data.setter + def data(self, data): + deprecate_data() + raise ValueError("Not possible to set component data to a tensor expression") + + @data.deleter + def data(self): + deprecate_data() + raise ValueError("Not possible to delete component data to a tensor expression") + + def __iter__(self): + deprecate_data() + with ignore_warnings(SymPyDeprecationWarning): + if self.data is None: + raise ValueError("No iteration on abstract tensors") + return self.data.__iter__() + + @staticmethod + def _dedupe_indices(new, exclude): + """ + exclude: set + new: TensExpr + + If ``new`` has any dummy indices that are in ``exclude``, return a version + of new with those indices replaced. If no replacements are needed, + return None + + """ + exclude = set(exclude) + dums_new = set(get_dummy_indices(new)) + free_new = set(get_free_indices(new)) + + conflicts = dums_new.intersection(exclude) + if len(conflicts) == 0: + return None + + """ + ``exclude_for_gen`` is to be passed to ``_IndexStructure._get_generator_for_dummy_indices()``. + Since the latter does not use the index position for anything, we just + set it as ``None`` here. + """ + exclude.update(dums_new) + exclude.update(free_new) + exclude_for_gen = [(i, None) for i in exclude] + gen = _IndexStructure._get_generator_for_dummy_indices(exclude_for_gen) + repl = {} + for d in conflicts: + if -d in repl.keys(): + continue + newname = gen(d.tensor_index_type) + new_d = d.func(newname, *d.args[1:]) + repl[d] = new_d + repl[-d] = -new_d + + if len(repl) == 0: + return None + + new_renamed = new._replace_indices(repl) + return new_renamed + + def _dedupe_indices_in_rule(self, rule): + """ + rule: dict + + This applies TensMul._dedupe_indices on all values of rule. + + """ + index_rules = {k:v for k,v in rule.items() if isinstance(k, TensorIndex)} + other_rules = {k:v for k,v in rule.items() if k not in index_rules.keys()} + exclude = set(self.get_indices()) + + newrule = {} + newrule.update(index_rules) + exclude.update(index_rules.keys()) + exclude.update(index_rules.values()) + for old, new in other_rules.items(): + new_renamed = TensMul._dedupe_indices(new, exclude) + if old == new or new_renamed is None: + newrule[old] = new + else: + newrule[old] = new_renamed + exclude.update(get_indices(new_renamed)) + return newrule + + def _eval_subs(self, old, new): + """ + If new is an index which is already present in self as a dummy, the dummies in self should be renamed. + """ + + if not isinstance(new, TensorIndex): + return None + + exclude = {new} + self_renamed = self._dedupe_indices(self, exclude) + if self_renamed is None: + return None + else: + return self_renamed._subs(old, new).doit(deep=False) + + def _eval_rewrite_as_Indexed(self, *args, **kwargs): + from sympy.concrete.summations import Sum + index_symbols = [i.args[0] for i in self.get_indices()] + args = [arg.args[0] if isinstance(arg, Sum) else arg for arg in args] + expr = Mul.fromiter(args) + return self._check_add_Sum(expr, index_symbols) + + def _eval_partial_derivative(self, s): + # Evaluation like Mul + terms = [] + for i, arg in enumerate(self.args): + # checking whether some tensor instance is differentiated + # or some other thing is necessary, but ugly + if isinstance(arg, TensExpr): + d = arg._eval_partial_derivative(s) + else: + # do not call diff is s is no symbol + if s._diff_wrt: + d = arg._eval_derivative(s) + else: + d = S.Zero + if d: + terms.append(TensMul.fromiter(self.args[:i] + (d,) + self.args[i + 1:]).doit(deep=False)) + return TensAdd.fromiter(terms).doit(deep=False) + + + def _matches_commutative(self, expr, repl_dict=None, old=False): + """ + Match assuming all tensors commute. But note that we are not assuming anything about their symmetry under index permutations. + """ + #Take care of the various possible types for expr. + if not isinstance(expr, TensMul): + if isinstance(expr, (TensExpr, Expr)): + expr = TensMul(expr) + else: + return None + + #The code that follows assumes expr is a TensMul + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + #Make sure that none of the dummy indices in self, expr conflict with the values already present in repl_dict. This may happen due to automatic index relabelling when rem_query and rem_expr are formed later on in this function (it calls itself recursively). + indices = [k for k in repl_dict.values() if isinstance(k ,TensorIndex)] + + def dedupe(expr): + renamed = TensMul._dedupe_indices(expr, indices) + if renamed is not None: + return renamed + else: + return expr + + self = dedupe(self) + expr = dedupe(expr) + + #Find the non-tensor part of expr. This need not be the same as expr.coeff when expr.doit() has not been called. + expr_coeff = reduce(lambda a, b: a*b, [arg for arg in expr.args if not isinstance(arg, TensExpr)], S.One) + + # handle simple patterns + if self == expr: + return repl_dict + + if len(_get_wilds(self)) == 0: + return self._matches_simple(expr, repl_dict, old) + + def siftkey(arg): + if isinstance(arg, WildTensor): + return "WildTensor" + elif isinstance(arg, (Tensor, TensExpr)): + return "Tensor" + else: + return "coeff" + + query_sifted = sift(self.args, siftkey) + expr_sifted = sift(expr.args, siftkey) + + #Sanity checks + if "coeff" in query_sifted.keys(): + if TensMul(*query_sifted["coeff"]).doit(deep=False) != self.coeff: + raise NotImplementedError(f"Found something that we do not know to handle: {query_sifted['coeff']}") + if "coeff" in expr_sifted.keys(): + if TensMul(*expr_sifted["coeff"]).doit(deep=False) != expr_coeff: + raise NotImplementedError(f"Found something that we do not know to handle: {expr_sifted['coeff']}") + + query_tens_heads = {tuple(getattr(x, "components", [])) for x in query_sifted["Tensor"]} #We use getattr because, e.g. TensAdd does not have the 'components' attribute. + expr_tens_heads = {tuple(getattr(x, "components", [])) for x in expr_sifted["Tensor"]} + if not query_tens_heads.issubset(expr_tens_heads): + #Some tensorheads in self are not present in the expr + return None + + #Try to match all non-wild tensors of self with tensors that compose expr + if len(query_sifted["Tensor"]) > 0: + q_tensor = query_sifted["Tensor"][0] + """ + We need to iterate over all possible symmetrized forms of q_tensor since the matches given by some of them may map dummy indices to free indices; the information about which indices are dummy/free will only be available later, when we are doing rem_q.matches(rem_e) + """ + for q_tens in q_tensor._get_symmetrized_forms(): + for e in expr_sifted["Tensor"]: + if isinstance(q_tens, TensMul): + #q_tensor got a minus sign due to this permutation. + sign = -1 + else: + sign = 1 + + """ + _matches is used here since we are already iterating over index permutations of q_tensor. Also note that the sign is removed from q_tensor, and will later be put into rem_q. + """ + m = (sign*q_tens)._matches(e) + if m is None: + continue + + rem_query = self.func(sign, *[a for a in self.args if a != q_tensor]).doit(deep=False) + rem_expr = expr.func(*[a for a in expr.args if a != e]).doit(deep=False) + tmp_repl = {} + tmp_repl.update(repl_dict) + tmp_repl.update(m) + rem_m = rem_query.matches(rem_expr, repl_dict=tmp_repl) + if rem_m is not None: + #Check that contracted indices are not mapped to different indices. + internally_consistent = True + for k in rem_m.keys(): + if isinstance(k,TensorIndex): + if -k in rem_m.keys() and rem_m[-k] != -rem_m[k]: + internally_consistent = False + break + if internally_consistent: + repl_dict.update(rem_m) + return repl_dict + + return None + + #Try to match WildTensor instances which have indices + matched_e_tensors = [] + remaining_e_tensors = expr_sifted["Tensor"] + indexless_wilds, wilds = sift(query_sifted["WildTensor"], lambda x: len(x.get_free_indices()) == 0, binary=True) + + for w in wilds: + free_this_wild = set(w.get_free_indices()) + tensors_to_try = [] + for t in remaining_e_tensors: + free = t.get_free_indices() + shares_indices_with_wild = True + for i in free: + if all(j.matches(i) is None for j in free_this_wild): + #The index i matches none of the indices in free_this_wild + shares_indices_with_wild = False + if shares_indices_with_wild: + tensors_to_try.append(t) + + m = w.matches(TensMul(*tensors_to_try).doit(deep=False) ) + if m is None: + return None + else: + for tens in tensors_to_try: + matched_e_tensors.append(tens) + repl_dict.update(m) + + #Try to match indexless WildTensor instances + remaining_e_tensors = [t for t in expr_sifted["Tensor"] if t not in matched_e_tensors] + if len(indexless_wilds) > 0: + #If there are any remaining tensors, match them with the indexless WildTensor + m = indexless_wilds[0].matches( TensMul(1,*remaining_e_tensors).doit(deep=False) ) + if m is None: + return None + else: + repl_dict.update(m) + elif len(remaining_e_tensors) > 0: + return None + + #Try to match the non-tensorial coefficient + m = self.coeff.matches(expr_coeff, old=old) + if m is None: + return None + else: + repl_dict.update(m) + + return repl_dict + + def matches(self, expr, repl_dict=None, old=False): + expr = sympify(expr) + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + commute = all(arg.component.comm == 0 for arg in expr.args if isinstance(arg, Tensor)) + if commute: + return self._matches_commutative(expr, repl_dict, old) + else: + raise NotImplementedError("Tensor matching not implemented for non-commuting tensors") + +class TensorElement(TensExpr): + """ + Tensor with evaluated components. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, TensorHead, TensorSymmetry + >>> from sympy import symbols + >>> L = TensorIndexType("L") + >>> i, j, k = symbols("i j k") + >>> A = TensorHead("A", [L, L], TensorSymmetry.fully_symmetric(2)) + >>> A(i, j).get_free_indices() + [i, j] + + If we want to set component ``i`` to a specific value, use the + ``TensorElement`` class: + + >>> from sympy.tensor.tensor import TensorElement + >>> te = TensorElement(A(i, j), {i: 2}) + + As index ``i`` has been accessed (``{i: 2}`` is the evaluation of its 3rd + element), the free indices will only contain ``j``: + + >>> te.get_free_indices() + [j] + """ + + def __new__(cls, expr, index_map): + if not isinstance(expr, Tensor): + # remap + if not isinstance(expr, TensExpr): + raise TypeError("%s is not a tensor expression" % expr) + return expr.func(*[TensorElement(arg, index_map) for arg in expr.args]) + expr_free_indices = expr.get_free_indices() + name_translation = {i.args[0]: i for i in expr_free_indices} + index_map = {name_translation.get(index, index): value for index, value in index_map.items()} + index_map = {index: value for index, value in index_map.items() if index in expr_free_indices} + if len(index_map) == 0: + return expr + free_indices = [i for i in expr_free_indices if i not in index_map.keys()] + index_map = Dict(index_map) + obj = TensExpr.__new__(cls, expr, index_map) + obj._free_indices = free_indices + return obj + + @property + def free(self): + return [(index, i) for i, index in enumerate(self.get_free_indices())] + + @property + def dum(self): + # TODO: inherit dummies from expr + return [] + + @property + def expr(self): + return self._args[0] + + @property + def index_map(self): + return self._args[1] + + @property + def coeff(self): + return S.One + + @property + def nocoeff(self): + return self + + def get_free_indices(self): + return self._free_indices + + def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: + # TODO: can be improved: + return self.xreplace(repl) + + def get_indices(self): + return self.get_free_indices() + + def _extract_data(self, replacement_dict): + ret_indices, array = self.expr._extract_data(replacement_dict) + index_map = self.index_map + slice_tuple = tuple(index_map.get(i, slice(None)) for i in ret_indices) + ret_indices = [i for i in ret_indices if i not in index_map] + array = array.__getitem__(slice_tuple) + return ret_indices, array + + +class WildTensorHead(TensorHead): + """ + A wild object that is used to create ``WildTensor`` instances + + Explanation + =========== + + Examples + ======== + >>> from sympy.tensor.tensor import TensorHead, TensorIndex, WildTensorHead, TensorIndexType + >>> R3 = TensorIndexType('R3', dim=3) + >>> p = TensorIndex('p', R3) + >>> q = TensorIndex('q', R3) + + A WildTensorHead can be created without specifying a ``TensorIndexType`` + + >>> W = WildTensorHead("W") + + Calling it with a ``TensorIndex`` creates a ``WildTensor`` instance. + + >>> type(W(p)) + + + The ``TensorIndexType`` is automatically detected from the index that is passed + + >>> W(p).component + W(R3) + + Calling it with no indices returns an object that can match tensors with any number of indices. + + >>> K = TensorHead('K', [R3]) + >>> Q = TensorHead('Q', [R3, R3]) + >>> W().matches(K(p)) + {W: K(p)} + >>> W().matches(Q(p,q)) + {W: Q(p, q)} + + If you want to ignore the order of indices while matching, pass ``unordered_indices=True``. + + >>> U = WildTensorHead("U", unordered_indices=True) + >>> W(p,q).matches(Q(q,p)) + >>> U(p,q).matches(Q(q,p)) + {U(R3,R3): _WildTensExpr(Q(q, p))} + + Parameters + ========== + name : name of the tensor + unordered_indices : whether the order of the indices matters for matching + (default: False) + + See also + ======== + ``WildTensor`` + ``TensorHead`` + + """ + def __new__(cls, name, index_types=None, symmetry=None, comm=0, unordered_indices=False): + if isinstance(name, str): + name_symbol = Symbol(name) + elif isinstance(name, Symbol): + name_symbol = name + else: + raise ValueError("invalid name") + + if index_types is None: + index_types = [] + + if symmetry is None: + symmetry = TensorSymmetry.no_symmetry(len(index_types)) + else: + assert symmetry.rank == len(index_types) + + if symmetry != TensorSymmetry.no_symmetry(len(index_types)): + raise NotImplementedError("Wild matching based on symmetry is not implemented.") + + obj = Basic.__new__(cls, name_symbol, Tuple(*index_types), sympify(symmetry), sympify(comm), sympify(unordered_indices)) + + return obj + + @property + def unordered_indices(self): + return self.args[4] + + def __call__(self, *indices, **kwargs): + tensor = WildTensor(self, indices, **kwargs) + return tensor.doit() + + +class WildTensor(Tensor): + """ + A wild object which matches ``Tensor`` instances + + Explanation + =========== + This is instantiated by attaching indices to a ``WildTensorHead`` instance. + + Examples + ======== + >>> from sympy.tensor.tensor import TensorHead, TensorIndex, WildTensorHead, TensorIndexType + >>> W = WildTensorHead("W") + >>> R3 = TensorIndexType('R3', dim=3) + >>> p = TensorIndex('p', R3) + >>> q = TensorIndex('q', R3) + >>> K = TensorHead('K', [R3]) + >>> Q = TensorHead('Q', [R3, R3]) + + Matching also takes the indices into account + >>> W(p).matches(K(p)) + {W(R3): _WildTensExpr(K(p))} + >>> W(p).matches(K(q)) + >>> W(p).matches(K(-p)) + + If you want to match objects with any number of indices, just use a ``WildTensor`` with no indices. + >>> W().matches(K(p)) + {W: K(p)} + >>> W().matches(Q(p,q)) + {W: Q(p, q)} + + See Also + ======== + ``WildTensorHead`` + ``Tensor`` + + """ + def __new__(cls, tensor_head, indices, **kw_args): + is_canon_bp = kw_args.pop("is_canon_bp", False) + + if tensor_head.func == TensorHead: + """ + If someone tried to call WildTensor by supplying a TensorHead (not a WildTensorHead), return a normal tensor instead. This is helpful when using subs on an expression to replace occurrences of a WildTensorHead with a TensorHead. + """ + return Tensor(tensor_head, indices, is_canon_bp=is_canon_bp, **kw_args) + elif tensor_head.func == _WildTensExpr: + return tensor_head(*indices) + + indices = cls._parse_indices(tensor_head, indices) + index_types = [ind.tensor_index_type for ind in indices] + tensor_head = tensor_head.func( + tensor_head.name, + index_types, + symmetry=None, + comm=tensor_head.comm, + unordered_indices=tensor_head.unordered_indices, + ) + + obj = Basic.__new__(cls, tensor_head, Tuple(*indices)) + obj.name = tensor_head.name + obj._index_structure = _IndexStructure.from_indices(*indices) + obj._free = obj._index_structure.free[:] + obj._dum = obj._index_structure.dum[:] + obj._ext_rank = obj._index_structure._ext_rank + obj._coeff = S.One + obj._nocoeff = obj + obj._component = tensor_head + obj._components = [tensor_head] + if tensor_head.rank != len(indices): + raise ValueError("wrong number of indices") + obj.is_canon_bp = is_canon_bp + obj._index_map = obj._build_index_map(indices, obj._index_structure) + + return obj + + + def matches(self, expr, repl_dict=None, old=False): + if not isinstance(expr, TensExpr) and expr != S(1): + return None + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + if len(self.indices) > 0: + if not hasattr(expr, "get_free_indices"): + return None + expr_indices = expr.get_free_indices() + if len(expr_indices) != len(self.indices): + return None + if self._component.unordered_indices: + m = self._match_indices_ignoring_order(expr) + if m is None: + return None + else: + repl_dict.update(m) + else: + for i in range(len(expr_indices)): + m = self.indices[i].matches(expr_indices[i]) + if m is None: + return None + else: + repl_dict.update(m) + + repl_dict[self.component] = _WildTensExpr(expr) + else: + #If no indices were passed to the WildTensor, it may match tensors with any number of indices. + repl_dict[self] = expr + + return repl_dict + + def _match_indices_ignoring_order(self, expr, repl_dict=None, old=False): + """ + Helper method for matches. Checks if the indices of self and expr + match disregarding index ordering. + """ + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + def siftkey(ind): + if isinstance(ind, WildTensorIndex): + if ind.ignore_updown: + return "wild, updown" + else: + return "wild" + else: + return "nonwild" + + indices_sifted = sift(self.indices, siftkey) + + matched_indices = [] + expr_indices_remaining = expr.get_indices() + for ind in indices_sifted["nonwild"]: + matched_this_ind = False + for e_ind in expr_indices_remaining: + if e_ind in matched_indices: + continue + m = ind.matches(e_ind) + if m is not None: + matched_this_ind = True + repl_dict.update(m) + matched_indices.append(e_ind) + break + if not matched_this_ind: + return None + + expr_indices_remaining = [i for i in expr_indices_remaining if i not in matched_indices] + for ind in indices_sifted["wild"]: + matched_this_ind = False + for e_ind in expr_indices_remaining: + m = ind.matches(e_ind) + if m is not None: + if -ind in repl_dict.keys() and -repl_dict[-ind] != m[ind]: + return None + matched_this_ind = True + repl_dict.update(m) + matched_indices.append(e_ind) + break + if not matched_this_ind: + return None + + expr_indices_remaining = [i for i in expr_indices_remaining if i not in matched_indices] + for ind in indices_sifted["wild, updown"]: + matched_this_ind = False + for e_ind in expr_indices_remaining: + m = ind.matches(e_ind) + if m is not None: + if -ind in repl_dict.keys() and -repl_dict[-ind] != m[ind]: + return None + matched_this_ind = True + repl_dict.update(m) + matched_indices.append(e_ind) + break + if not matched_this_ind: + return None + + if len(matched_indices) < len(self.indices): + return None + else: + return repl_dict + +class WildTensorIndex(TensorIndex): + """ + A wild object that matches TensorIndex instances. + + Examples + ======== + >>> from sympy.tensor.tensor import TensorIndex, TensorIndexType, WildTensorIndex + >>> R3 = TensorIndexType('R3', dim=3) + >>> p = TensorIndex("p", R3) + + By default, covariant indices only match with covariant indices (and + similarly for contravariant) + + >>> q = WildTensorIndex("q", R3) + >>> (q).matches(p) + {q: p} + >>> (q).matches(-p) + + If you want matching to ignore whether the index is co/contra-variant, set + ignore_updown=True + + >>> r = WildTensorIndex("r", R3, ignore_updown=True) + >>> (r).matches(-p) + {r: -p} + >>> (r).matches(p) + {r: p} + + Parameters + ========== + name : name of the index (string), or ``True`` if you want it to be + automatically assigned + tensor_index_type : ``TensorIndexType`` of the index + is_up : flag for contravariant index (is_up=True by default) + ignore_updown : bool, Whether this should match both co- and contra-variant + indices (default:False) + """ + def __new__(cls, name, tensor_index_type, is_up=True, ignore_updown=False): + if isinstance(name, str): + name_symbol = Symbol(name) + elif isinstance(name, Symbol): + name_symbol = name + elif name is True: + name = "_i{}".format(len(tensor_index_type._autogenerated)) + name_symbol = Symbol(name) + tensor_index_type._autogenerated.append(name_symbol) + else: + raise ValueError("invalid name") + + is_up = sympify(is_up) + ignore_updown = sympify(ignore_updown) + return Basic.__new__(cls, name_symbol, tensor_index_type, is_up, ignore_updown) + + @property + def ignore_updown(self): + return self.args[3] + + def __neg__(self): + t1 = WildTensorIndex(self.name, self.tensor_index_type, + (not self.is_up), self.ignore_updown) + return t1 + + def matches(self, expr, repl_dict=None, old=False): + if not isinstance(expr, TensorIndex): + return None + if self.tensor_index_type != expr.tensor_index_type: + return None + if not self.ignore_updown: + if self.is_up != expr.is_up: + return None + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + repl_dict[self] = expr + return repl_dict + + +class _WildTensExpr(Basic): + """ + INTERNAL USE ONLY + + This is an object that helps with replacement of WildTensors in expressions. + When this object is set as the tensor_head of a WildTensor, it replaces the + WildTensor by a TensExpr (passed when initializing this object). + + Examples + ======== + >>> from sympy.tensor.tensor import WildTensorHead, TensorIndex, TensorHead, TensorIndexType + >>> W = WildTensorHead("W") + >>> R3 = TensorIndexType('R3', dim=3) + >>> p = TensorIndex('p', R3) + >>> q = TensorIndex('q', R3) + >>> K = TensorHead('K', [R3]) + >>> print( ( K(p) ).replace( W(p), W(q)*W(-q)*W(p) ) ) + K(R_0)*K(-R_0)*K(p) + + """ + def __init__(self, expr): + if not isinstance(expr, TensExpr): + raise TypeError("_WildTensExpr expects a TensExpr as argument") + self.expr = expr + + def __call__(self, *indices): + return self.expr._replace_indices(dict(zip(self.expr.get_free_indices(), indices))) + + def __neg__(self): + return self.func(self.expr*S.NegativeOne) + + def __abs__(self): + raise NotImplementedError + + def __add__(self, other): + if other.func != self.func: + raise TypeError(f"Cannot add {self.func} to {other.func}") + return self.func(self.expr+other.expr) + + def __radd__(self, other): + if other.func != self.func: + raise TypeError(f"Cannot add {self.func} to {other.func}") + return self.func(other.expr+self.expr) + + def __sub__(self, other): + return self + (-other) + + def __rsub__(self, other): + return other + (-self) + + def __mul__(self, other): + raise NotImplementedError + + def __rmul__(self, other): + raise NotImplementedError + + def __truediv__(self, other): + raise NotImplementedError + + def __rtruediv__(self, other): + raise NotImplementedError + + def __pow__(self, other): + raise NotImplementedError + + def __rpow__(self, other): + raise NotImplementedError + + +def canon_bp(p): + """ + Butler-Portugal canonicalization. See ``tensor_can.py`` from the + combinatorics module for the details. + """ + if isinstance(p, TensExpr): + return p.canon_bp() + return p + + +def tensor_mul(*a): + """ + product of tensors + """ + if not a: + return TensMul.from_data(S.One, [], [], []) + t = a[0] + for tx in a[1:]: + t = t*tx + return t + + +def riemann_cyclic_replace(t_r): + """ + replace Riemann tensor with an equivalent expression + + ``R(m,n,p,q) -> 2/3*R(m,n,p,q) - 1/3*R(m,q,n,p) + 1/3*R(m,p,n,q)`` + + """ + free = sorted(t_r.free, key=lambda x: x[1]) + m, n, p, q = [x[0] for x in free] + t0 = t_r*Rational(2, 3) + t1 = -t_r.substitute_indices((m,m),(n,q),(p,n),(q,p))*Rational(1, 3) + t2 = t_r.substitute_indices((m,m),(n,p),(p,n),(q,q))*Rational(1, 3) + t3 = t0 + t1 + t2 + return t3 + +def riemann_cyclic(t2): + """ + Replace each Riemann tensor with an equivalent expression + satisfying the cyclic identity. + + This trick is discussed in the reference guide to Cadabra. + + Examples + ======== + + >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead, riemann_cyclic, TensorSymmetry + >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') + >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) + >>> R = TensorHead('R', [Lorentz]*4, TensorSymmetry.riemann()) + >>> t = R(i,j,k,l)*(R(-i,-j,-k,-l) - 2*R(-i,-k,-j,-l)) + >>> riemann_cyclic(t) + 0 + """ + t2 = t2.expand() + if isinstance(t2, (TensMul, Tensor)): + args = [t2] + else: + args = t2.args + a1 = [x.split() for x in args] + a2 = [[riemann_cyclic_replace(tx) for tx in y] for y in a1] + a3 = [tensor_mul(*v) for v in a2] + t3 = TensAdd(*a3).doit(deep=False) + if not t3: + return t3 + else: + return canon_bp(t3) + + +def get_lines(ex, index_type): + """ + Returns ``(lines, traces, rest)`` for an index type, + where ``lines`` is the list of list of positions of a matrix line, + ``traces`` is the list of list of traced matrix lines, + ``rest`` is the rest of the elements of the tensor. + """ + def _join_lines(a): + i = 0 + while i < len(a): + x = a[i] + xend = x[-1] + xstart = x[0] + hit = True + while hit: + hit = False + for j in range(i + 1, len(a)): + if j >= len(a): + break + if a[j][0] == xend: + hit = True + x.extend(a[j][1:]) + xend = x[-1] + a.pop(j) + continue + if a[j][0] == xstart: + hit = True + a[i] = reversed(a[j][1:]) + x + x = a[i] + xstart = a[i][0] + a.pop(j) + continue + if a[j][-1] == xend: + hit = True + x.extend(reversed(a[j][:-1])) + xend = x[-1] + a.pop(j) + continue + if a[j][-1] == xstart: + hit = True + a[i] = a[j][:-1] + x + x = a[i] + xstart = x[0] + a.pop(j) + continue + i += 1 + return a + + arguments = ex.args + dt = {} + for c in ex.args: + if not isinstance(c, TensExpr): + continue + if c in dt: + continue + index_types = c.index_types + a = [] + for i in range(len(index_types)): + if index_types[i] is index_type: + a.append(i) + if len(a) > 2: + raise ValueError('at most two indices of type %s allowed' % index_type) + if len(a) == 2: + dt[c] = a + #dum = ex.dum + lines = [] + traces = [] + traces1 = [] + #indices_to_args_pos = ex._get_indices_to_args_pos() + # TODO: add a dum_to_components_map ? + for p0, p1, c0, c1 in ex.dum_in_args: + if arguments[c0] not in dt: + continue + if c0 == c1: + traces.append([c0]) + continue + ta0 = dt[arguments[c0]] + ta1 = dt[arguments[c1]] + if p0 not in ta0: + continue + if ta0.index(p0) == ta1.index(p1): + # case gamma(i,s0,-s1) in c0, gamma(j,-s0,s2) in c1; + # to deal with this case one could add to the position + # a flag for transposition; + # one could write [(c0, False), (c1, True)] + raise NotImplementedError + # if p0 == ta0[1] then G in pos c0 is mult on the right by G in c1 + # if p0 == ta0[0] then G in pos c1 is mult on the right by G in c0 + ta0 = dt[arguments[c0]] + b0, b1 = (c0, c1) if p0 == ta0[1] else (c1, c0) + lines1 = lines.copy() + for line in lines: + if line[-1] == b0: + if line[0] == b1: + n = line.index(min(line)) + traces1.append(line) + traces.append(line[n:] + line[:n]) + else: + line.append(b1) + break + elif line[0] == b1: + line.insert(0, b0) + break + else: + lines1.append([b0, b1]) + + lines = [x for x in lines1 if x not in traces1] + lines = _join_lines(lines) + rest = [] + for line in lines: + for y in line: + rest.append(y) + for line in traces: + for y in line: + rest.append(y) + rest = [x for x in range(len(arguments)) if x not in rest] + + return lines, traces, rest + + +def get_free_indices(t): + if not isinstance(t, TensExpr): + return () + return t.get_free_indices() + + +def get_indices(t): + if not isinstance(t, TensExpr): + return () + return t.get_indices() + +def get_dummy_indices(t): + if not isinstance(t, TensExpr): + return () + inds = t.get_indices() + free = t.get_free_indices() + return [i for i in inds if i not in free] + +def get_index_structure(t): + if isinstance(t, TensExpr): + return t._index_structure + return _IndexStructure([], [], [], []) + + +def get_coeff(t): + if isinstance(t, Tensor): + return S.One + if isinstance(t, TensMul): + return t.coeff + if isinstance(t, TensExpr): + raise ValueError("no coefficient associated to this tensor expression") + return t + +def contract_metric(t, g): + if isinstance(t, TensExpr): + return t.contract_metric(g) + return t + +def perm2tensor(t, g, is_canon_bp=False): + """ + Returns the tensor corresponding to the permutation ``g`` + + For further details, see the method in ``TIDS`` with the same name. + """ + if not isinstance(t, TensExpr): + return t + elif isinstance(t, (Tensor, TensMul)): + nim = get_index_structure(t).perm2tensor(g, is_canon_bp=is_canon_bp) + res = t._set_new_index_structure(nim, is_canon_bp=is_canon_bp) + if g[-1] != len(g) - 1: + return -res + + return res + raise NotImplementedError() + + +def substitute_indices(t, *index_tuples): + if not isinstance(t, TensExpr): + return t + return t.substitute_indices(*index_tuples) + + +def _get_wilds(expr): + return list(expr.atoms(Wild, WildFunction, WildTensor, WildTensorIndex, WildTensorHead)) + + +def get_postprocessor(cls): + def _postprocessor(expr): + tens_class = {Mul: TensMul, Add: TensAdd}[cls] + if any(isinstance(a, TensExpr) for a in expr.args): + return tens_class(*expr.args) + else: + return expr + + return _postprocessor + +Basic._constructor_postprocessor_mapping[TensExpr] = { + "Mul": [get_postprocessor(Mul)], +} diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__init__.py b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d36c8203ef04eb82de1ed79cc068f0e8ae1dacc6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_functions.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_functions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b62fce8488fa5bd197de443ee56488e0584b5ea0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_functions.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_index_methods.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_index_methods.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc5680eb73bf2bef0f49949949e1dd39387e1efd Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_index_methods.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_indexed.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_indexed.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a738c314e057a870c80d0e8f5a0755db14f88ab Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_indexed.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_printing.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_printing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b3bbc5a028c471bdc2f6c79fe21688be85ff032 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_printing.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8707579856b2e36160738916c83d28d4e33e6fd Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor_element.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor_element.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0a436a557c5a91fb17126fc6ce7ff9ebf347e3f Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor_element.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor_operators.cpython-310.pyc b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor_operators.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d98b7cf00af9acd4f3140698de9fe12852d4aff5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/sympy/tensor/tests/__pycache__/test_tensor_operators.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_functions.py b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..ae40865d1bddffaa976dc3d94ae1ef1b6c97ca35 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_functions.py @@ -0,0 +1,57 @@ +from sympy.tensor.functions import TensorProduct +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.tensor.array import Array +from sympy.abc import x, y, z +from sympy.abc import i, j, k, l + + +A = MatrixSymbol("A", 3, 3) +B = MatrixSymbol("B", 3, 3) +C = MatrixSymbol("C", 3, 3) + + +def test_TensorProduct_construction(): + assert TensorProduct(3, 4) == 12 + assert isinstance(TensorProduct(A, A), TensorProduct) + + expr = TensorProduct(TensorProduct(x, y), z) + assert expr == x*y*z + + expr = TensorProduct(TensorProduct(A, B), C) + assert expr == TensorProduct(A, B, C) + + expr = TensorProduct(Matrix.eye(2), Array([[0, -1], [1, 0]])) + assert expr == Array([ + [ + [[0, -1], [1, 0]], + [[0, 0], [0, 0]] + ], + [ + [[0, 0], [0, 0]], + [[0, -1], [1, 0]] + ] + ]) + + +def test_TensorProduct_shape(): + + expr = TensorProduct(3, 4, evaluate=False) + assert expr.shape == () + assert expr.rank() == 0 + + expr = TensorProduct(Array([1, 2]), Array([x, y]), evaluate=False) + assert expr.shape == (2, 2) + assert expr.rank() == 2 + expr = TensorProduct(expr, expr, evaluate=False) + assert expr.shape == (2, 2, 2, 2) + assert expr.rank() == 4 + + expr = TensorProduct(Matrix.eye(2), Array([[0, -1], [1, 0]]), evaluate=False) + assert expr.shape == (2, 2, 2, 2) + assert expr.rank() == 4 + + +def test_TensorProduct_getitem(): + expr = TensorProduct(A, B) + assert expr[i, j, k, l] == A[i, j]*B[k, l] diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_index_methods.py b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_index_methods.py new file mode 100644 index 0000000000000000000000000000000000000000..df20f7e7c1ab392321e8350b95dd07c5639c1865 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_index_methods.py @@ -0,0 +1,227 @@ +from sympy.core import symbols, S, Pow, Function +from sympy.functions import exp +from sympy.testing.pytest import raises +from sympy.tensor.indexed import Idx, IndexedBase +from sympy.tensor.index_methods import IndexConformanceException + +from sympy.tensor.index_methods import (get_contraction_structure, get_indices) + + +def test_trivial_indices(): + x, y = symbols('x y') + assert get_indices(x) == (set(), {}) + assert get_indices(x*y) == (set(), {}) + assert get_indices(x + y) == (set(), {}) + assert get_indices(x**y) == (set(), {}) + + +def test_get_indices_Indexed(): + x = IndexedBase('x') + i, j = Idx('i'), Idx('j') + assert get_indices(x[i, j]) == ({i, j}, {}) + assert get_indices(x[j, i]) == ({j, i}, {}) + + +def test_get_indices_Idx(): + f = Function('f') + i, j = Idx('i'), Idx('j') + assert get_indices(f(i)*j) == ({i, j}, {}) + assert get_indices(f(j, i)) == ({j, i}, {}) + assert get_indices(f(i)*i) == (set(), {}) + + +def test_get_indices_mul(): + x = IndexedBase('x') + y = IndexedBase('y') + i, j = Idx('i'), Idx('j') + assert get_indices(x[j]*y[i]) == ({i, j}, {}) + assert get_indices(x[i]*y[j]) == ({i, j}, {}) + + +def test_get_indices_exceptions(): + x = IndexedBase('x') + y = IndexedBase('y') + i, j = Idx('i'), Idx('j') + raises(IndexConformanceException, lambda: get_indices(x[i] + y[j])) + + +def test_scalar_broadcast(): + x = IndexedBase('x') + y = IndexedBase('y') + i, j = Idx('i'), Idx('j') + assert get_indices(x[i] + y[i, i]) == ({i}, {}) + assert get_indices(x[i] + y[j, j]) == ({i}, {}) + + +def test_get_indices_add(): + x = IndexedBase('x') + y = IndexedBase('y') + A = IndexedBase('A') + i, j, k = Idx('i'), Idx('j'), Idx('k') + assert get_indices(x[i] + 2*y[i]) == ({i}, {}) + assert get_indices(y[i] + 2*A[i, j]*x[j]) == ({i}, {}) + assert get_indices(y[i] + 2*(x[i] + A[i, j]*x[j])) == ({i}, {}) + assert get_indices(y[i] + x[i]*(A[j, j] + 1)) == ({i}, {}) + assert get_indices( + y[i] + x[i]*x[j]*(y[j] + A[j, k]*x[k])) == ({i}, {}) + + +def test_get_indices_Pow(): + x = IndexedBase('x') + y = IndexedBase('y') + A = IndexedBase('A') + i, j, k = Idx('i'), Idx('j'), Idx('k') + assert get_indices(Pow(x[i], y[j])) == ({i, j}, {}) + assert get_indices(Pow(x[i, k], y[j, k])) == ({i, j, k}, {}) + assert get_indices(Pow(A[i, k], y[k] + A[k, j]*x[j])) == ({i, k}, {}) + assert get_indices(Pow(2, x[i])) == get_indices(exp(x[i])) + + # test of a design decision, this may change: + assert get_indices(Pow(x[i], 2)) == ({i}, {}) + + +def test_get_contraction_structure_basic(): + x = IndexedBase('x') + y = IndexedBase('y') + i, j = Idx('i'), Idx('j') + assert get_contraction_structure(x[i]*y[j]) == {None: {x[i]*y[j]}} + assert get_contraction_structure(x[i] + y[j]) == {None: {x[i], y[j]}} + assert get_contraction_structure(x[i]*y[i]) == {(i,): {x[i]*y[i]}} + assert get_contraction_structure( + 1 + x[i]*y[i]) == {None: {S.One}, (i,): {x[i]*y[i]}} + assert get_contraction_structure(x[i]**y[i]) == {None: {x[i]**y[i]}} + + +def test_get_contraction_structure_complex(): + x = IndexedBase('x') + y = IndexedBase('y') + A = IndexedBase('A') + i, j, k = Idx('i'), Idx('j'), Idx('k') + expr1 = y[i] + A[i, j]*x[j] + d1 = {None: {y[i]}, (j,): {A[i, j]*x[j]}} + assert get_contraction_structure(expr1) == d1 + expr2 = expr1*A[k, i] + x[k] + d2 = {None: {x[k]}, (i,): {expr1*A[k, i]}, expr1*A[k, i]: [d1]} + assert get_contraction_structure(expr2) == d2 + + +def test_contraction_structure_simple_Pow(): + x = IndexedBase('x') + y = IndexedBase('y') + i, j, k = Idx('i'), Idx('j'), Idx('k') + ii_jj = x[i, i]**y[j, j] + assert get_contraction_structure(ii_jj) == { + None: {ii_jj}, + ii_jj: [ + {(i,): {x[i, i]}}, + {(j,): {y[j, j]}} + ] + } + + ii_jk = x[i, i]**y[j, k] + assert get_contraction_structure(ii_jk) == { + None: {x[i, i]**y[j, k]}, + x[i, i]**y[j, k]: [ + {(i,): {x[i, i]}} + ] + } + + +def test_contraction_structure_Mul_and_Pow(): + x = IndexedBase('x') + y = IndexedBase('y') + i, j, k = Idx('i'), Idx('j'), Idx('k') + + i_ji = x[i]**(y[j]*x[i]) + assert get_contraction_structure(i_ji) == {None: {i_ji}} + ij_i = (x[i]*y[j])**(y[i]) + assert get_contraction_structure(ij_i) == {None: {ij_i}} + j_ij_i = x[j]*(x[i]*y[j])**(y[i]) + assert get_contraction_structure(j_ij_i) == {(j,): {j_ij_i}} + j_i_ji = x[j]*x[i]**(y[j]*x[i]) + assert get_contraction_structure(j_i_ji) == {(j,): {j_i_ji}} + ij_exp_kki = x[i]*y[j]*exp(y[i]*y[k, k]) + result = get_contraction_structure(ij_exp_kki) + expected = { + (i,): {ij_exp_kki}, + ij_exp_kki: [{ + None: {exp(y[i]*y[k, k])}, + exp(y[i]*y[k, k]): [{ + None: {y[i]*y[k, k]}, + y[i]*y[k, k]: [{(k,): {y[k, k]}}] + }]} + ] + } + assert result == expected + + +def test_contraction_structure_Add_in_Pow(): + x = IndexedBase('x') + y = IndexedBase('y') + i, j, k = Idx('i'), Idx('j'), Idx('k') + s_ii_jj_s = (1 + x[i, i])**(1 + y[j, j]) + expected = { + None: {s_ii_jj_s}, + s_ii_jj_s: [ + {None: {S.One}, (i,): {x[i, i]}}, + {None: {S.One}, (j,): {y[j, j]}} + ] + } + result = get_contraction_structure(s_ii_jj_s) + assert result == expected + + s_ii_jk_s = (1 + x[i, i]) ** (1 + y[j, k]) + expected_2 = { + None: {(x[i, i] + 1)**(y[j, k] + 1)}, + s_ii_jk_s: [ + {None: {S.One}, (i,): {x[i, i]}} + ] + } + result_2 = get_contraction_structure(s_ii_jk_s) + assert result_2 == expected_2 + + +def test_contraction_structure_Pow_in_Pow(): + x = IndexedBase('x') + y = IndexedBase('y') + z = IndexedBase('z') + i, j, k = Idx('i'), Idx('j'), Idx('k') + ii_jj_kk = x[i, i]**y[j, j]**z[k, k] + expected = { + None: {ii_jj_kk}, + ii_jj_kk: [ + {(i,): {x[i, i]}}, + { + None: {y[j, j]**z[k, k]}, + y[j, j]**z[k, k]: [ + {(j,): {y[j, j]}}, + {(k,): {z[k, k]}} + ] + } + ] + } + assert get_contraction_structure(ii_jj_kk) == expected + + +def test_ufunc_support(): + f = Function('f') + g = Function('g') + x = IndexedBase('x') + y = IndexedBase('y') + i, j = Idx('i'), Idx('j') + a = symbols('a') + + assert get_indices(f(x[i])) == ({i}, {}) + assert get_indices(f(x[i], y[j])) == ({i, j}, {}) + assert get_indices(f(y[i])*g(x[i])) == (set(), {}) + assert get_indices(f(a, x[i])) == ({i}, {}) + assert get_indices(f(a, y[i], x[j])*g(x[i])) == ({j}, {}) + assert get_indices(g(f(x[i]))) == ({i}, {}) + + assert get_contraction_structure(f(x[i])) == {None: {f(x[i])}} + assert get_contraction_structure( + f(y[i])*g(x[i])) == {(i,): {f(y[i])*g(x[i])}} + assert get_contraction_structure( + f(y[i])*g(f(x[i]))) == {(i,): {f(y[i])*g(f(x[i]))}} + assert get_contraction_structure( + f(x[j], y[i])*g(x[i])) == {(i,): {f(x[j], y[i])*g(x[i])}} diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_indexed.py b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_indexed.py new file mode 100644 index 0000000000000000000000000000000000000000..689ec932c8fcefe0a24de289dd2ffd6820c63f19 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_indexed.py @@ -0,0 +1,511 @@ +from sympy.core import symbols, Symbol, Tuple, oo, Dummy +from sympy.tensor.indexed import IndexException +from sympy.testing.pytest import raises +from sympy.utilities.iterables import iterable + +# import test: +from sympy.concrete.summations import Sum +from sympy.core.function import Function, Subs, Derivative +from sympy.core.relational import (StrictLessThan, GreaterThan, + StrictGreaterThan, LessThan) +from sympy.core.singleton import S +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.series.order import Order +from sympy.sets.fancysets import Range +from sympy.tensor.indexed import IndexedBase, Idx, Indexed + + +def test_Idx_construction(): + i, a, b = symbols('i a b', integer=True) + assert Idx(i) != Idx(i, 1) + assert Idx(i, a) == Idx(i, (0, a - 1)) + assert Idx(i, oo) == Idx(i, (0, oo)) + + x = symbols('x', integer=False) + raises(TypeError, lambda: Idx(x)) + raises(TypeError, lambda: Idx(0.5)) + raises(TypeError, lambda: Idx(i, x)) + raises(TypeError, lambda: Idx(i, 0.5)) + raises(TypeError, lambda: Idx(i, (x, 5))) + raises(TypeError, lambda: Idx(i, (2, x))) + raises(TypeError, lambda: Idx(i, (2, 3.5))) + + +def test_Idx_properties(): + i, a, b = symbols('i a b', integer=True) + assert Idx(i).is_integer + assert Idx(i).name == 'i' + assert Idx(i + 2).name == 'i + 2' + assert Idx('foo').name == 'foo' + + +def test_Idx_bounds(): + i, a, b = symbols('i a b', integer=True) + assert Idx(i).lower is None + assert Idx(i).upper is None + assert Idx(i, a).lower == 0 + assert Idx(i, a).upper == a - 1 + assert Idx(i, 5).lower == 0 + assert Idx(i, 5).upper == 4 + assert Idx(i, oo).lower == 0 + assert Idx(i, oo).upper is oo + assert Idx(i, (a, b)).lower == a + assert Idx(i, (a, b)).upper == b + assert Idx(i, (1, 5)).lower == 1 + assert Idx(i, (1, 5)).upper == 5 + assert Idx(i, (-oo, oo)).lower is -oo + assert Idx(i, (-oo, oo)).upper is oo + + +def test_Idx_fixed_bounds(): + i, a, b, x = symbols('i a b x', integer=True) + assert Idx(x).lower is None + assert Idx(x).upper is None + assert Idx(x, a).lower == 0 + assert Idx(x, a).upper == a - 1 + assert Idx(x, 5).lower == 0 + assert Idx(x, 5).upper == 4 + assert Idx(x, oo).lower == 0 + assert Idx(x, oo).upper is oo + assert Idx(x, (a, b)).lower == a + assert Idx(x, (a, b)).upper == b + assert Idx(x, (1, 5)).lower == 1 + assert Idx(x, (1, 5)).upper == 5 + assert Idx(x, (-oo, oo)).lower is -oo + assert Idx(x, (-oo, oo)).upper is oo + + +def test_Idx_inequalities(): + i14 = Idx("i14", (1, 4)) + i79 = Idx("i79", (7, 9)) + i46 = Idx("i46", (4, 6)) + i35 = Idx("i35", (3, 5)) + + assert i14 <= 5 + assert i14 < 5 + assert not (i14 >= 5) + assert not (i14 > 5) + + assert 5 >= i14 + assert 5 > i14 + assert not (5 <= i14) + assert not (5 < i14) + + assert LessThan(i14, 5) + assert StrictLessThan(i14, 5) + assert not GreaterThan(i14, 5) + assert not StrictGreaterThan(i14, 5) + + assert i14 <= 4 + assert isinstance(i14 < 4, StrictLessThan) + assert isinstance(i14 >= 4, GreaterThan) + assert not (i14 > 4) + + assert isinstance(i14 <= 1, LessThan) + assert not (i14 < 1) + assert i14 >= 1 + assert isinstance(i14 > 1, StrictGreaterThan) + + assert not (i14 <= 0) + assert not (i14 < 0) + assert i14 >= 0 + assert i14 > 0 + + from sympy.abc import x + + assert isinstance(i14 < x, StrictLessThan) + assert isinstance(i14 > x, StrictGreaterThan) + assert isinstance(i14 <= x, LessThan) + assert isinstance(i14 >= x, GreaterThan) + + assert i14 < i79 + assert i14 <= i79 + assert not (i14 > i79) + assert not (i14 >= i79) + + assert i14 <= i46 + assert isinstance(i14 < i46, StrictLessThan) + assert isinstance(i14 >= i46, GreaterThan) + assert not (i14 > i46) + + assert isinstance(i14 < i35, StrictLessThan) + assert isinstance(i14 > i35, StrictGreaterThan) + assert isinstance(i14 <= i35, LessThan) + assert isinstance(i14 >= i35, GreaterThan) + + iNone1 = Idx("iNone1") + iNone2 = Idx("iNone2") + + assert isinstance(iNone1 < iNone2, StrictLessThan) + assert isinstance(iNone1 > iNone2, StrictGreaterThan) + assert isinstance(iNone1 <= iNone2, LessThan) + assert isinstance(iNone1 >= iNone2, GreaterThan) + + +def test_Idx_inequalities_current_fails(): + i14 = Idx("i14", (1, 4)) + + assert S(5) >= i14 + assert S(5) > i14 + assert not (S(5) <= i14) + assert not (S(5) < i14) + + +def test_Idx_func_args(): + i, a, b = symbols('i a b', integer=True) + ii = Idx(i) + assert ii.func(*ii.args) == ii + ii = Idx(i, a) + assert ii.func(*ii.args) == ii + ii = Idx(i, (a, b)) + assert ii.func(*ii.args) == ii + + +def test_Idx_subs(): + i, a, b = symbols('i a b', integer=True) + assert Idx(i, a).subs(a, b) == Idx(i, b) + assert Idx(i, a).subs(i, b) == Idx(b, a) + + assert Idx(i).subs(i, 2) == Idx(2) + assert Idx(i, a).subs(a, 2) == Idx(i, 2) + assert Idx(i, (a, b)).subs(i, 2) == Idx(2, (a, b)) + + +def test_IndexedBase_sugar(): + i, j = symbols('i j', integer=True) + a = symbols('a') + A1 = Indexed(a, i, j) + A2 = IndexedBase(a) + assert A1 == A2[i, j] + assert A1 == A2[(i, j)] + assert A1 == A2[[i, j]] + assert A1 == A2[Tuple(i, j)] + assert all(a.is_Integer for a in A2[1, 0].args[1:]) + + +def test_IndexedBase_subs(): + i = symbols('i', integer=True) + a, b = symbols('a b') + A = IndexedBase(a) + B = IndexedBase(b) + assert A[i] == B[i].subs(b, a) + C = {1: 2} + assert C[1] == A[1].subs(A, C) + + +def test_IndexedBase_shape(): + i, j, m, n = symbols('i j m n', integer=True) + a = IndexedBase('a', shape=(m, m)) + b = IndexedBase('a', shape=(m, n)) + assert b.shape == Tuple(m, n) + assert a[i, j] != b[i, j] + assert a[i, j] == b[i, j].subs(n, m) + assert b.func(*b.args) == b + assert b[i, j].func(*b[i, j].args) == b[i, j] + raises(IndexException, lambda: b[i]) + raises(IndexException, lambda: b[i, i, j]) + F = IndexedBase("F", shape=m) + assert F.shape == Tuple(m) + assert F[i].subs(i, j) == F[j] + raises(IndexException, lambda: F[i, j]) + + +def test_IndexedBase_assumptions(): + i = Symbol('i', integer=True) + a = Symbol('a') + A = IndexedBase(a, positive=True) + for c in (A, A[i]): + assert c.is_real + assert c.is_complex + assert not c.is_imaginary + assert c.is_nonnegative + assert c.is_nonzero + assert c.is_commutative + assert log(exp(c)) == c + + assert A != IndexedBase(a) + assert A == IndexedBase(a, positive=True, real=True) + assert A[i] != Indexed(a, i) + + +def test_IndexedBase_assumptions_inheritance(): + I = Symbol('I', integer=True) + I_inherit = IndexedBase(I) + I_explicit = IndexedBase('I', integer=True) + + assert I_inherit.is_integer + assert I_explicit.is_integer + assert I_inherit.label.is_integer + assert I_explicit.label.is_integer + assert I_inherit == I_explicit + + +def test_issue_17652(): + """Regression test issue #17652. + + IndexedBase.label should not upcast subclasses of Symbol + """ + class SubClass(Symbol): + pass + + x = SubClass('X') + assert type(x) == SubClass + base = IndexedBase(x) + assert type(x) == SubClass + assert type(base.label) == SubClass + + +def test_Indexed_constructor(): + i, j = symbols('i j', integer=True) + A = Indexed('A', i, j) + assert A == Indexed(Symbol('A'), i, j) + assert A == Indexed(IndexedBase('A'), i, j) + raises(TypeError, lambda: Indexed(A, i, j)) + raises(IndexException, lambda: Indexed("A")) + assert A.free_symbols == {A, A.base.label, i, j} + + +def test_Indexed_func_args(): + i, j = symbols('i j', integer=True) + a = symbols('a') + A = Indexed(a, i, j) + assert A == A.func(*A.args) + + +def test_Indexed_subs(): + i, j, k = symbols('i j k', integer=True) + a, b = symbols('a b') + A = IndexedBase(a) + B = IndexedBase(b) + assert A[i, j] == B[i, j].subs(b, a) + assert A[i, j] == A[i, k].subs(k, j) + + +def test_Indexed_properties(): + i, j = symbols('i j', integer=True) + A = Indexed('A', i, j) + assert A.name == 'A[i, j]' + assert A.rank == 2 + assert A.indices == (i, j) + assert A.base == IndexedBase('A') + assert A.ranges == [None, None] + raises(IndexException, lambda: A.shape) + + n, m = symbols('n m', integer=True) + assert Indexed('A', Idx( + i, m), Idx(j, n)).ranges == [Tuple(0, m - 1), Tuple(0, n - 1)] + assert Indexed('A', Idx(i, m), Idx(j, n)).shape == Tuple(m, n) + raises(IndexException, lambda: Indexed("A", Idx(i, m), Idx(j)).shape) + + +def test_Indexed_shape_precedence(): + i, j = symbols('i j', integer=True) + o, p = symbols('o p', integer=True) + n, m = symbols('n m', integer=True) + a = IndexedBase('a', shape=(o, p)) + assert a.shape == Tuple(o, p) + assert Indexed( + a, Idx(i, m), Idx(j, n)).ranges == [Tuple(0, m - 1), Tuple(0, n - 1)] + assert Indexed(a, Idx(i, m), Idx(j, n)).shape == Tuple(o, p) + assert Indexed( + a, Idx(i, m), Idx(j)).ranges == [Tuple(0, m - 1), (None, None)] + assert Indexed(a, Idx(i, m), Idx(j)).shape == Tuple(o, p) + + +def test_complex_indices(): + i, j = symbols('i j', integer=True) + A = Indexed('A', i, i + j) + assert A.rank == 2 + assert A.indices == (i, i + j) + + +def test_not_interable(): + i, j = symbols('i j', integer=True) + A = Indexed('A', i, i + j) + assert not iterable(A) + + +def test_Indexed_coeff(): + N = Symbol('N', integer=True) + len_y = N + i = Idx('i', len_y-1) + y = IndexedBase('y', shape=(len_y,)) + a = (1/y[i+1]*y[i]).coeff(y[i]) + b = (y[i]/y[i+1]).coeff(y[i]) + assert a == b + + +def test_differentiation(): + from sympy.functions.special.tensor_functions import KroneckerDelta + i, j, k, l = symbols('i j k l', cls=Idx) + a = symbols('a') + m, n = symbols("m, n", integer=True, finite=True) + assert m.is_real + h, L = symbols('h L', cls=IndexedBase) + hi, hj = h[i], h[j] + + expr = hi + assert expr.diff(hj) == KroneckerDelta(i, j) + assert expr.diff(hi) == KroneckerDelta(i, i) + + expr = S(2) * hi + assert expr.diff(hj) == S(2) * KroneckerDelta(i, j) + assert expr.diff(hi) == S(2) * KroneckerDelta(i, i) + assert expr.diff(a) is S.Zero + + assert Sum(expr, (i, -oo, oo)).diff(hj) == Sum(2*KroneckerDelta(i, j), (i, -oo, oo)) + assert Sum(expr.diff(hj), (i, -oo, oo)) == Sum(2*KroneckerDelta(i, j), (i, -oo, oo)) + assert Sum(expr, (i, -oo, oo)).diff(hj).doit() == 2 + + assert Sum(expr.diff(hi), (i, -oo, oo)).doit() == Sum(2, (i, -oo, oo)).doit() + assert Sum(expr, (i, -oo, oo)).diff(hi).doit() is oo + + expr = a * hj * hj / S(2) + assert expr.diff(hi) == a * h[j] * KroneckerDelta(i, j) + assert expr.diff(a) == hj * hj / S(2) + assert expr.diff(a, 2) is S.Zero + + assert Sum(expr, (i, -oo, oo)).diff(hi) == Sum(a*KroneckerDelta(i, j)*h[j], (i, -oo, oo)) + assert Sum(expr.diff(hi), (i, -oo, oo)) == Sum(a*KroneckerDelta(i, j)*h[j], (i, -oo, oo)) + assert Sum(expr, (i, -oo, oo)).diff(hi).doit() == a*h[j] + + assert Sum(expr, (j, -oo, oo)).diff(hi) == Sum(a*KroneckerDelta(i, j)*h[j], (j, -oo, oo)) + assert Sum(expr.diff(hi), (j, -oo, oo)) == Sum(a*KroneckerDelta(i, j)*h[j], (j, -oo, oo)) + assert Sum(expr, (j, -oo, oo)).diff(hi).doit() == a*h[i] + + expr = a * sin(hj * hj) + assert expr.diff(hi) == 2*a*cos(hj * hj) * hj * KroneckerDelta(i, j) + assert expr.diff(hj) == 2*a*cos(hj * hj) * hj + + expr = a * L[i, j] * h[j] + assert expr.diff(hi) == a*L[i, j]*KroneckerDelta(i, j) + assert expr.diff(hj) == a*L[i, j] + assert expr.diff(L[i, j]) == a*h[j] + assert expr.diff(L[k, l]) == a*KroneckerDelta(i, k)*KroneckerDelta(j, l)*h[j] + assert expr.diff(L[i, l]) == a*KroneckerDelta(j, l)*h[j] + + assert Sum(expr, (j, -oo, oo)).diff(L[k, l]) == Sum(a * KroneckerDelta(i, k) * KroneckerDelta(j, l) * h[j], (j, -oo, oo)) + assert Sum(expr, (j, -oo, oo)).diff(L[k, l]).doit() == a * KroneckerDelta(i, k) * h[l] + + assert h[m].diff(h[m]) == 1 + assert h[m].diff(h[n]) == KroneckerDelta(m, n) + assert Sum(a*h[m], (m, -oo, oo)).diff(h[n]) == Sum(a*KroneckerDelta(m, n), (m, -oo, oo)) + assert Sum(a*h[m], (m, -oo, oo)).diff(h[n]).doit() == a + assert Sum(a*h[m], (n, -oo, oo)).diff(h[n]) == Sum(a*KroneckerDelta(m, n), (n, -oo, oo)) + assert Sum(a*h[m], (m, -oo, oo)).diff(h[m]).doit() == oo*a + + +def test_indexed_series(): + A = IndexedBase("A") + i = symbols("i", integer=True) + assert sin(A[i]).series(A[i]) == A[i] - A[i]**3/6 + A[i]**5/120 + Order(A[i]**6, A[i]) + + +def test_indexed_is_constant(): + A = IndexedBase("A") + i, j, k = symbols("i,j,k") + assert not A[i].is_constant() + assert A[i].is_constant(j) + assert not A[1+2*i, k].is_constant() + assert not A[1+2*i, k].is_constant(i) + assert A[1+2*i, k].is_constant(j) + assert not A[1+2*i, k].is_constant(k) + + +def test_issue_12533(): + d = IndexedBase('d') + assert IndexedBase(range(5)) == Range(0, 5, 1) + assert d[0].subs(Symbol("d"), range(5)) == 0 + assert d[0].subs(d, range(5)) == 0 + assert d[1].subs(d, range(5)) == 1 + assert Indexed(Range(5), 2) == 2 + + +def test_issue_12780(): + n = symbols("n") + i = Idx("i", (0, n)) + raises(TypeError, lambda: i.subs(n, 1.5)) + + +def test_issue_18604(): + m = symbols("m") + assert Idx("i", m).name == 'i' + assert Idx("i", m).lower == 0 + assert Idx("i", m).upper == m - 1 + m = symbols("m", real=False) + raises(TypeError, lambda: Idx("i", m)) + +def test_Subs_with_Indexed(): + A = IndexedBase("A") + i, j, k = symbols("i,j,k") + x, y, z = symbols("x,y,z") + f = Function("f") + + assert Subs(A[i], A[i], A[j]).diff(A[j]) == 1 + assert Subs(A[i], A[i], x).diff(A[i]) == 0 + assert Subs(A[i], A[i], x).diff(A[j]) == 0 + assert Subs(A[i], A[i], x).diff(x) == 1 + assert Subs(A[i], A[i], x).diff(y) == 0 + assert Subs(A[i], A[i], A[j]).diff(A[k]) == KroneckerDelta(j, k) + assert Subs(x, x, A[i]).diff(A[j]) == KroneckerDelta(i, j) + assert Subs(f(A[i]), A[i], x).diff(A[j]) == 0 + assert Subs(f(A[i]), A[i], A[k]).diff(A[j]) == Derivative(f(A[k]), A[k])*KroneckerDelta(j, k) + assert Subs(x, x, A[i]**2).diff(A[j]) == 2*KroneckerDelta(i, j)*A[i] + assert Subs(A[i], A[i], A[j]**2).diff(A[k]) == 2*KroneckerDelta(j, k)*A[j] + + assert Subs(A[i]*x, x, A[i]).diff(A[i]) == 2*A[i] + assert Subs(A[i]*x, x, A[i]).diff(A[j]) == 2*A[i]*KroneckerDelta(i, j) + assert Subs(A[i]*x, x, A[j]).diff(A[i]) == A[j] + A[i]*KroneckerDelta(i, j) + assert Subs(A[i]*x, x, A[j]).diff(A[j]) == A[i] + A[j]*KroneckerDelta(i, j) + assert Subs(A[i]*x, x, A[i]).diff(A[k]) == 2*A[i]*KroneckerDelta(i, k) + assert Subs(A[i]*x, x, A[j]).diff(A[k]) == KroneckerDelta(i, k)*A[j] + KroneckerDelta(j, k)*A[i] + + assert Subs(A[i]*x, A[i], x).diff(A[i]) == 0 + assert Subs(A[i]*x, A[i], x).diff(A[j]) == 0 + assert Subs(A[i]*x, A[j], x).diff(A[i]) == x + assert Subs(A[i]*x, A[j], x).diff(A[j]) == x*KroneckerDelta(i, j) + assert Subs(A[i]*x, A[i], x).diff(A[k]) == 0 + assert Subs(A[i]*x, A[j], x).diff(A[k]) == x*KroneckerDelta(i, k) + + +def test_complicated_derivative_with_Indexed(): + x, y = symbols("x,y", cls=IndexedBase) + sigma = symbols("sigma") + i, j, k = symbols("i,j,k") + m0,m1,m2,m3,m4,m5 = symbols("m0:6") + f = Function("f") + + expr = f((x[i] - y[i])**2/sigma) + _xi_1 = symbols("xi_1", cls=Dummy) + assert expr.diff(x[m0]).dummy_eq( + (x[i] - y[i])*KroneckerDelta(i, m0)*\ + 2*Subs( + Derivative(f(_xi_1), _xi_1), + (_xi_1,), + ((x[i] - y[i])**2/sigma,) + )/sigma + ) + assert expr.diff(x[m0]).diff(x[m1]).dummy_eq( + 2*KroneckerDelta(i, m0)*\ + KroneckerDelta(i, m1)*Subs( + Derivative(f(_xi_1), _xi_1), + (_xi_1,), + ((x[i] - y[i])**2/sigma,) + )/sigma + \ + 4*(x[i] - y[i])**2*KroneckerDelta(i, m0)*KroneckerDelta(i, m1)*\ + Subs( + Derivative(f(_xi_1), _xi_1, _xi_1), + (_xi_1,), + ((x[i] - y[i])**2/sigma,) + )/sigma**2 + ) + + +def test_IndexedBase_commutative(): + t = IndexedBase('t', commutative=False) + u = IndexedBase('u', commutative=False) + v = IndexedBase('v') + assert t[0]*v[0] == v[0]*t[0] + assert t[0]*u[0] != u[0]*t[0] diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_printing.py b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_printing.py new file mode 100644 index 0000000000000000000000000000000000000000..9f3cf7f0591a7012c93354ab7b8d7e010def38bb --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_printing.py @@ -0,0 +1,13 @@ +from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead +from sympy import I + +def test_printing_TensMul(): + R3 = TensorIndexType('R3', dim=3) + p, q = tensor_indices("p q", R3) + K = TensorHead("K", [R3]) + + assert repr(2*K(p)) == "2*K(p)" + assert repr(-K(p)) == "-K(p)" + assert repr(-2*K(p)*K(q)) == "-2*K(p)*K(q)" + assert repr(-I*K(p)) == "-I*K(p)" + assert repr(I*K(p)) == "I*K(p)" diff --git a/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_tensor.py b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_tensor.py new file mode 100644 index 0000000000000000000000000000000000000000..3113f5be9bcd32224f3525b5d831b6d7476c39e3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/sympy/tensor/tests/test_tensor.py @@ -0,0 +1,2218 @@ +from sympy.concrete.summations import Sum +from sympy.core.function import expand +from sympy.core.numbers import Integer +from sympy.matrices.dense import (Matrix, eye) +from sympy.tensor.indexed import Indexed +from sympy.combinatorics import Permutation +from sympy.core import S, Rational, Symbol, Basic, Add, Wild, Function +from sympy.core.containers import Tuple +from sympy.core.symbol import symbols +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.integrals import integrate +from sympy.tensor.array import Array +from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, \ + get_symmetric_group_sgs, TensorIndex, tensor_mul, TensAdd, \ + riemann_cyclic_replace, riemann_cyclic, TensMul, tensor_heads, \ + TensorManager, TensExpr, TensorHead, canon_bp, \ + tensorhead, tensorsymmetry, TensorType, substitute_indices, \ + WildTensorIndex, WildTensorHead, _WildTensExpr +from sympy.testing.pytest import raises, XFAIL, warns_deprecated_sympy +from sympy.matrices import diag + +def _is_equal(arg1, arg2): + if isinstance(arg1, TensExpr): + return arg1.equals(arg2) + elif isinstance(arg2, TensExpr): + return arg2.equals(arg1) + return arg1 == arg2 + + +#################### Tests from tensor_can.py ####################### +def test_canonicalize_no_slot_sym(): + # A_d0 * B^d0; T_c = A^d0*B_d0 + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + a, b, d0, d1 = tensor_indices('a,b,d0,d1', Lorentz) + A, B = tensor_heads('A,B', [Lorentz], TensorSymmetry.no_symmetry(1)) + t = A(-d0)*B(d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0)*B(-L_0)' + + # A^a * B^b; T_c = T + t = A(a)*B(b) + tc = t.canon_bp() + assert tc == t + # B^b * A^a + t1 = B(b)*A(a) + tc = t1.canon_bp() + assert str(tc) == 'A(a)*B(b)' + + # A symmetric + # A^{b}_{d0}*A^{d0, a}; T_c = A^{a d0}*A{b}_{d0} + A = TensorHead('A', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) + t = A(b, -d0)*A(d0, a) + tc = t.canon_bp() + assert str(tc) == 'A(a, L_0)*A(b, -L_0)' + + # A^{d1}_{d0}*B^d0*C_d1 + # T_c = A^{d0 d1}*B_d0*C_d1 + B, C = tensor_heads('B,C', [Lorentz], TensorSymmetry.no_symmetry(1)) + t = A(d1, -d0)*B(d0)*C(-d1) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-L_0)*C(-L_1)' + + # A without symmetry + # A^{d1}_{d0}*B^d0*C_d1 ord=[d0,-d0,d1,-d1]; g = [2,1,0,3,4,5] + # T_c = A^{d0 d1}*B_d1*C_d0; can = [0,2,3,1,4,5] + A = TensorHead('A', [Lorentz]*2, TensorSymmetry.no_symmetry(2)) + t = A(d1, -d0)*B(d0)*C(-d1) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-L_1)*C(-L_0)' + + # A, B without symmetry + # A^{d1}_{d0}*B_{d1}^{d0} + # T_c = A^{d0 d1}*B_{d0 d1} + B = TensorHead('B', [Lorentz]*2, TensorSymmetry.no_symmetry(2)) + t = A(d1, -d0)*B(-d1, d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-L_0, -L_1)' + # A_{d0}^{d1}*B_{d1}^{d0} + # T_c = A^{d0 d1}*B_{d1 d0} + t = A(-d0, d1)*B(-d1, d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-L_1, -L_0)' + + # A, B, C without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} + # T_c=A^{d0 d1}*B_{a d1}*C_{d0 b} + C = TensorHead('C', [Lorentz]*2, TensorSymmetry.no_symmetry(2)) + t = A(d1, d0)*B(-a, -d0)*C(-d1, -b) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-a, -L_1)*C(-L_0, -b)' + + # A symmetric, B and C without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} + # T_c = A^{d0 d1}*B_{a d0}*C_{d1 b} + A = TensorHead('A', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) + t = A(d1, d0)*B(-a, -d0)*C(-d1, -b) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-a, -L_0)*C(-L_1, -b)' + + # A and C symmetric, B without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1] + # T_c = A^{d0 d1}*B_{a d0}*C_{b d1} + C = TensorHead('C', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) + t = A(d1, d0)*B(-a, -d0)*C(-d1, -b) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1)*B(-a, -L_0)*C(-b, -L_1)' + +def test_canonicalize_no_dummies(): + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + a, b, c, d = tensor_indices('a, b, c, d', Lorentz) + + # A commuting + # A^c A^b A^a + # T_c = A^a A^b A^c + A = TensorHead('A', [Lorentz], TensorSymmetry.no_symmetry(1)) + t = A(c)*A(b)*A(a) + tc = t.canon_bp() + assert str(tc) == 'A(a)*A(b)*A(c)' + + # A anticommuting + # A^c A^b A^a + # T_c = -A^a A^b A^c + A = TensorHead('A', [Lorentz], TensorSymmetry.no_symmetry(1), 1) + t = A(c)*A(b)*A(a) + tc = t.canon_bp() + assert str(tc) == '-A(a)*A(b)*A(c)' + + # A commuting and symmetric + # A^{b,d}*A^{c,a} + # T_c = A^{a c}*A^{b d} + A = TensorHead('A', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) + t = A(b, d)*A(c, a) + tc = t.canon_bp() + assert str(tc) == 'A(a, c)*A(b, d)' + + # A anticommuting and symmetric + # A^{b,d}*A^{c,a} + # T_c = -A^{a c}*A^{b d} + A = TensorHead('A', [Lorentz]*2, TensorSymmetry.fully_symmetric(2), 1) + t = A(b, d)*A(c, a) + tc = t.canon_bp() + assert str(tc) == '-A(a, c)*A(b, d)' + + # A^{c,a}*A^{b,d} + # T_c = A^{a c}*A^{b d} + t = A(c, a)*A(b, d) + tc = t.canon_bp() + assert str(tc) == 'A(a, c)*A(b, d)' + +def test_tensorhead_construction_without_symmetry(): + L = TensorIndexType('Lorentz') + A1 = TensorHead('A', [L, L]) + A2 = TensorHead('A', [L, L], TensorSymmetry.no_symmetry(2)) + assert A1 == A2 + A3 = TensorHead('A', [L, L], TensorSymmetry.fully_symmetric(2)) # Symmetric + assert A1 != A3 + +def test_no_metric_symmetry(): + # no metric symmetry; A no symmetry + # A^d1_d0 * A^d0_d1 + # T_c = A^d0_d1 * A^d1_d0 + Lorentz = TensorIndexType('Lorentz', dummy_name='L', metric_symmetry=0) + d0, d1, d2, d3 = tensor_indices('d:4', Lorentz) + A = TensorHead('A', [Lorentz]*2, TensorSymmetry.no_symmetry(2)) + t = A(d1, -d0)*A(d0, -d1) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, -L_1)*A(L_1, -L_0)' + + # A^d1_d2 * A^d0_d3 * A^d2_d1 * A^d3_d0 + # T_c = A^d0_d1 * A^d1_d0 * A^d2_d3 * A^d3_d2 + t = A(d1, -d2)*A(d0, -d3)*A(d2, -d1)*A(d3, -d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, -L_1)*A(L_1, -L_0)*A(L_2, -L_3)*A(L_3, -L_2)' + + # A^d0_d2 * A^d1_d3 * A^d3_d0 * A^d2_d1 + # T_c = A^d0_d1 * A^d1_d2 * A^d2_d3 * A^d3_d0 + t = A(d0, -d1)*A(d1, -d2)*A(d2, -d3)*A(d3, -d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, -L_1)*A(L_1, -L_2)*A(L_2, -L_3)*A(L_3, -L_0)' + +def test_canonicalize1(): + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ + tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Lorentz) + + # A_d0*A^d0; ord = [d0,-d0] + # T_c = A^d0*A_d0 + A = TensorHead('A', [Lorentz], TensorSymmetry.no_symmetry(1)) + t = A(-d0)*A(d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0)*A(-L_0)' + + # A commuting + # A_d0*A_d1*A_d2*A^d2*A^d1*A^d0 + # T_c = A^d0*A_d0*A^d1*A_d1*A^d2*A_d2 + t = A(-d0)*A(-d1)*A(-d2)*A(d2)*A(d1)*A(d0) + tc = t.canon_bp() + assert str(tc) == 'A(L_0)*A(-L_0)*A(L_1)*A(-L_1)*A(L_2)*A(-L_2)' + + # A anticommuting + # A_d0*A_d1*A_d2*A^d2*A^d1*A^d0 + # T_c 0 + A = TensorHead('A', [Lorentz], TensorSymmetry.no_symmetry(1), 1) + t = A(-d0)*A(-d1)*A(-d2)*A(d2)*A(d1)*A(d0) + tc = t.canon_bp() + assert tc == 0 + + # A commuting symmetric + # A^{d0 b}*A^a_d1*A^d1_d0 + # T_c = A^{a d0}*A^{b d1}*A_{d0 d1} + A = TensorHead('A', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) + t = A(d0, b)*A(a, -d1)*A(d1, -d0) + tc = t.canon_bp() + assert str(tc) == 'A(a, L_0)*A(b, L_1)*A(-L_0, -L_1)' + + # A, B commuting symmetric + # A^{d0 b}*A^d1_d0*B^a_d1 + # T_c = A^{b d0}*A_d0^d1*B^a_d1 + B = TensorHead('B', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) + t = A(d0, b)*A(d1, -d0)*B(a, -d1) + tc = t.canon_bp() + assert str(tc) == 'A(b, L_0)*A(-L_0, L_1)*B(a, -L_1)' + + # A commuting symmetric + # A^{d1 d0 b}*A^{a}_{d1 d0}; ord=[a,b, d0,-d0,d1,-d1] + # T_c = A^{a d0 d1}*A^{b}_{d0 d1} + A = TensorHead('A', [Lorentz]*3, TensorSymmetry.fully_symmetric(3)) + t = A(d1, d0, b)*A(a, -d1, -d0) + tc = t.canon_bp() + assert str(tc) == 'A(a, L_0, L_1)*A(b, -L_0, -L_1)' + + # A^{d3 d0 d2}*A^a0_{d1 d2}*A^d1_d3^a1*A^{a2 a3}_d0 + # T_c = A^{a0 d0 d1}*A^a1_d0^d2*A^{a2 a3 d3}*A_{d1 d2 d3} + t = A(d3, d0, d2)*A(a0, -d1, -d2)*A(d1, -d3, a1)*A(a2, a3, -d0) + tc = t.canon_bp() + assert str(tc) == 'A(a0, L_0, L_1)*A(a1, -L_0, L_2)*A(a2, a3, L_3)*A(-L_1, -L_2, -L_3)' + + # A commuting symmetric, B antisymmetric + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # in this esxample and in the next three, + # renaming dummy indices and using symmetry of A, + # T = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 + # can = 0 + A = TensorHead('A', [Lorentz]*3, TensorSymmetry.fully_symmetric(3)) + B = TensorHead('B', [Lorentz]*2, TensorSymmetry.fully_symmetric(-2)) + t = A(d0, d1, d2)*A(-d2, -d3, -d1)*B(-d0, d3) + tc = t.canon_bp() + assert tc == 0 + + # A anticommuting symmetric, B antisymmetric + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} + A = TensorHead('A', [Lorentz]*3, TensorSymmetry.fully_symmetric(3), 1) + B = TensorHead('B', [Lorentz]*2, TensorSymmetry.fully_symmetric(-2)) + t = A(d0, d1, d2)*A(-d2, -d3, -d1)*B(-d0, d3) + tc = t.canon_bp() + assert str(tc) == 'A(L_0, L_1, L_2)*A(-L_0, -L_1, L_3)*B(-L_2, -L_3)' + + # A anticommuting symmetric, B antisymmetric commuting, antisymmetric metric + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = -A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} + Spinor = TensorIndexType('Spinor', dummy_name='S', metric_symmetry=-1) + a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ + tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Spinor) + A = TensorHead('A', [Spinor]*3, TensorSymmetry.fully_symmetric(3), 1) + B = TensorHead('B', [Spinor]*2, TensorSymmetry.fully_symmetric(-2)) + t = A(d0, d1, d2)*A(-d2, -d3, -d1)*B(-d0, d3) + tc = t.canon_bp() + assert str(tc) == '-A(S_0, S_1, S_2)*A(-S_0, -S_1, S_3)*B(-S_2, -S_3)' + + # A anticommuting symmetric, B antisymmetric anticommuting, + # no metric symmetry + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 + Mat = TensorIndexType('Mat', metric_symmetry=0, dummy_name='M') + a, a0, a1, a2, a3, b, d0, d1, d2, d3 = \ + tensor_indices('a,a0,a1,a2,a3,b,d0,d1,d2,d3', Mat) + A = TensorHead('A', [Mat]*3, TensorSymmetry.fully_symmetric(3), 1) + B = TensorHead('B', [Mat]*2, TensorSymmetry.fully_symmetric(-2)) + t = A(d0, d1, d2)*A(-d2, -d3, -d1)*B(-d0, d3) + tc = t.canon_bp() + assert str(tc) == 'A(M_0, M_1, M_2)*A(-M_0, -M_1, -M_3)*B(-M_2, M_3)' + + # Gamma anticommuting + # Gamma_{mu nu} * gamma^rho * Gamma^{nu mu alpha} + # T_c = -Gamma^{mu nu} * gamma^rho * Gamma_{alpha mu nu} + alpha, beta, gamma, mu, nu, rho = \ + tensor_indices('alpha,beta,gamma,mu,nu,rho', Lorentz) + Gamma = TensorHead('Gamma', [Lorentz], + TensorSymmetry.fully_symmetric(1), 2) + Gamma2 = TensorHead('Gamma', [Lorentz]*2, + TensorSymmetry.fully_symmetric(-2), 2) + Gamma3 = TensorHead('Gamma', [Lorentz]*3, + TensorSymmetry.fully_symmetric(-3), 2) + t = Gamma2(-mu, -nu)*Gamma(rho)*Gamma3(nu, mu, alpha) + tc = t.canon_bp() + assert str(tc) == '-Gamma(L_0, L_1)*Gamma(rho)*Gamma(alpha, -L_0, -L_1)' + + # Gamma_{mu nu} * Gamma^{gamma beta} * gamma_rho * Gamma^{nu mu alpha} + # T_c = Gamma^{mu nu} * Gamma^{beta gamma} * gamma_rho * Gamma^alpha_{mu nu} + t = Gamma2(mu, nu)*Gamma2(beta, gamma)*Gamma(-rho)*Gamma3(alpha, -mu, -nu) + tc = t.canon_bp() + assert str(tc) == 'Gamma(L_0, L_1)*Gamma(beta, gamma)*Gamma(-rho)*Gamma(alpha, -L_0, -L_1)' + + # f^a_{b,c} antisymmetric in b,c; A_mu^a no symmetry + # f^c_{d a} * f_{c e b} * A_mu^d * A_nu^a * A^{nu e} * A^{mu b} + # g = [8,11,5, 9,13,7, 1,10, 3,4, 2,12, 0,6, 14,15] + # T_c = -f^{a b c} * f_a^{d e} * A^mu_b * A_{mu d} * A^nu_c * A_{nu e} + Flavor = TensorIndexType('Flavor', dummy_name='F') + a, b, c, d, e, ff = tensor_indices('a,b,c,d,e,f', Flavor) + mu, nu = tensor_indices('mu,nu', Lorentz) + f = TensorHead('f', [Flavor]*3, TensorSymmetry.direct_product(1, -2)) + A = TensorHead('A', [Lorentz, Flavor], TensorSymmetry.no_symmetry(2)) + t = f(c, -d, -a)*f(-c, -e, -b)*A(-mu, d)*A(-nu, a)*A(nu, e)*A(mu, b) + tc = t.canon_bp() + assert str(tc) == '-f(F_0, F_1, F_2)*f(-F_0, F_3, F_4)*A(L_0, -F_1)*A(-L_0, -F_3)*A(L_1, -F_2)*A(-L_1, -F_4)' + + +def test_bug_correction_tensor_indices(): + # to make sure that tensor_indices does not return a list if creating + # only one index: + A = TensorIndexType("A") + i = tensor_indices('i', A) + assert not isinstance(i, (tuple, list)) + assert isinstance(i, TensorIndex) + + +def test_riemann_invariants(): + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11 = \ + tensor_indices('d0:12', Lorentz) + # R^{d0 d1}_{d1 d0}; ord = [d0,-d0,d1,-d1] + # T_c = -R^{d0 d1}_{d0 d1} + R = TensorHead('R', [Lorentz]*4, TensorSymmetry.riemann()) + t = R(d0, d1, -d1, -d0) + tc = t.canon_bp() + assert str(tc) == '-R(L_0, L_1, -L_0, -L_1)' + + # R_d11^d1_d0^d5 * R^{d6 d4 d0}_d5 * R_{d7 d2 d8 d9} * + # R_{d10 d3 d6 d4} * R^{d2 d7 d11}_d1 * R^{d8 d9 d3 d10} + # can = [0,2,4,6, 1,3,8,10, 5,7,12,14, 9,11,16,18, 13,15,20,22, + # 17,19,21>> from sympy.tensor.tensor import TensorIndexType, TensorHead + >>> from sympy.tensor.toperators import PartialDerivative + >>> from sympy import symbols + >>> L = TensorIndexType("L") + >>> A = TensorHead("A", [L]) + >>> B = TensorHead("B", [L]) + >>> i, j, k = symbols("i j k") + + >>> expr = PartialDerivative(A(i), A(j)) + >>> expr + PartialDerivative(A(i), A(j)) + + The ``PartialDerivative`` object behaves like a tensorial expression: + + >>> expr.get_indices() + [i, -j] + + Notice that the deriving variables have opposite valence than the + printed one: ``A(j)`` is printed as covariant, but the index of the + derivative is actually contravariant, i.e. ``-j``. + + Indices can be contracted: + + >>> expr = PartialDerivative(A(i), A(i)) + >>> expr + PartialDerivative(A(L_0), A(L_0)) + >>> expr.get_indices() + [L_0, -L_0] + + The method ``.get_indices()`` always returns all indices (even the + contracted ones). If only uncontracted indices are needed, call + ``.get_free_indices()``: + + >>> expr.get_free_indices() + [] + + Nested partial derivatives are flattened: + + >>> expr = PartialDerivative(PartialDerivative(A(i), A(j)), A(k)) + >>> expr + PartialDerivative(A(i), A(j), A(k)) + >>> expr.get_indices() + [i, -j, -k] + + Replace a derivative with array values: + + >>> from sympy.abc import x, y + >>> from sympy import sin, log + >>> compA = [sin(x), log(x)*y**3] + >>> compB = [x, y] + >>> expr = PartialDerivative(A(i), B(j)) + >>> expr.replace_with_arrays({A(i): compA, B(i): compB}) + [[cos(x), 0], [y**3/x, 3*y**2*log(x)]] + + The returned array is indexed by `(i, -j)`. + + Be careful that other SymPy modules put the indices of the deriving + variables before the indices of the derivand in the derivative result. + For example: + + >>> expr.get_free_indices() + [i, -j] + + >>> from sympy import Matrix, Array + >>> Matrix(compA).diff(Matrix(compB)).reshape(2, 2) + [[cos(x), y**3/x], [0, 3*y**2*log(x)]] + >>> Array(compA).diff(Array(compB)) + [[cos(x), y**3/x], [0, 3*y**2*log(x)]] + + These are the transpose of the result of ``PartialDerivative``, + as the matrix and the array modules put the index `-j` before `i` in the + derivative result. An array read with index order `(-j, i)` is indeed the + transpose of the same array read with index order `(i, -j)`. By specifying + the index order to ``.replace_with_arrays`` one can get a compatible + expression: + + >>> expr.replace_with_arrays({A(i): compA, B(i): compB}, [-j, i]) + [[cos(x), y**3/x], [0, 3*y**2*log(x)]] + """ + + def __new__(cls, expr, *variables): + + # Flatten: + if isinstance(expr, PartialDerivative): + variables = expr.variables + variables + expr = expr.expr + + args, indices, free, dum = cls._contract_indices_for_derivative( + S(expr), variables) + + obj = TensExpr.__new__(cls, *args) + + obj._indices = indices + obj._free = free + obj._dum = dum + return obj + + @property + def coeff(self): + return S.One + + @property + def nocoeff(self): + return self + + @classmethod + def _contract_indices_for_derivative(cls, expr, variables): + variables_opposite_valence = [] + + for i in variables: + if isinstance(i, Tensor): + i_free_indices = i.get_free_indices() + variables_opposite_valence.append( + i.xreplace({k: -k for k in i_free_indices})) + elif isinstance(i, Symbol): + variables_opposite_valence.append(i) + + args, indices, free, dum = TensMul._tensMul_contract_indices( + [expr] + variables_opposite_valence, replace_indices=True) + + for i in range(1, len(args)): + args_i = args[i] + if isinstance(args_i, Tensor): + i_indices = args[i].get_free_indices() + args[i] = args[i].xreplace({k: -k for k in i_indices}) + + return args, indices, free, dum + + def doit(self, **hints): + args, indices, free, dum = self._contract_indices_for_derivative(self.expr, self.variables) + + obj = self.func(*args) + obj._indices = indices + obj._free = free + obj._dum = dum + + return obj + + def _expand_partial_derivative(self): + args, indices, free, dum = self._contract_indices_for_derivative(self.expr, self.variables) + + obj = self.func(*args) + obj._indices = indices + obj._free = free + obj._dum = dum + + result = obj + + if not args[0].free_symbols: + return S.Zero + elif isinstance(obj.expr, TensAdd): + # take care of sums of multi PDs + result = obj.expr.func(*[ + self.func(a, *obj.variables)._expand_partial_derivative() + for a in result.expr.args]) + elif isinstance(obj.expr, TensMul): + # take care of products of multi PDs + if len(obj.variables) == 1: + # derivative with respect to single variable + terms = [] + mulargs = list(obj.expr.args) + for ind in range(len(mulargs)): + if not isinstance(sympify(mulargs[ind]), Number): + # a number coefficient is not considered for + # expansion of PartialDerivative + d = self.func(mulargs[ind], *obj.variables)._expand_partial_derivative() + terms.append(TensMul(*(mulargs[:ind] + + [d] + + mulargs[(ind + 1):]))) + result = TensAdd.fromiter(terms) + else: + # derivative with respect to multiple variables + # decompose: + # partial(expr, (u, v)) + # = partial(partial(expr, u).doit(), v).doit() + result = obj.expr # init with expr + for v in obj.variables: + result = self.func(result, v)._expand_partial_derivative() + # then throw PD on it + + return result + + def _perform_derivative(self): + result = self.expr + for v in self.variables: + if isinstance(result, TensExpr): + result = result._eval_partial_derivative(v) + else: + if v._diff_wrt: + result = result._eval_derivative(v) + else: + result = S.Zero + return result + + def get_indices(self): + return self._indices + + def get_free_indices(self): + free = sorted(self._free, key=lambda x: x[1]) + return [i[0] for i in free] + + def _replace_indices(self, repl): + expr = self.expr.xreplace(repl) + mirrored = {-k: -v for k, v in repl.items()} + variables = [i.xreplace(mirrored) for i in self.variables] + return self.func(expr, *variables) + + @property + def expr(self): + return self.args[0] + + @property + def variables(self): + return self.args[1:] + + def _extract_data(self, replacement_dict): + from .array import derive_by_array, tensorcontraction + indices, array = self.expr._extract_data(replacement_dict) + for variable in self.variables: + var_indices, var_array = variable._extract_data(replacement_dict) + var_indices = [-i for i in var_indices] + coeff_array, var_array = zip(*[i.as_coeff_Mul() for i in var_array]) + dim_before = len(array.shape) + array = derive_by_array(array, var_array) + dim_after = len(array.shape) + dim_increase = dim_after - dim_before + array = permutedims(array, [i + dim_increase for i in range(dim_before)] + list(range(dim_increase))) + array = array.as_mutable() + varindex = var_indices[0] + # Remove coefficients of base vector: + coeff_index = [0] + [slice(None) for i in range(len(indices))] + for i, coeff in enumerate(coeff_array): + coeff_index[0] = i + array[tuple(coeff_index)] /= coeff + if -varindex in indices: + pos = indices.index(-varindex) + array = tensorcontraction(array, (0, pos+1)) + indices.pop(pos) + else: + indices.append(varindex) + return indices, array diff --git a/venv/lib/python3.10/site-packages/tqdm/__init__.py b/venv/lib/python3.10/site-packages/tqdm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8081f77b8812f3b42d7949daa4195d2c35dc70ac --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/__init__.py @@ -0,0 +1,38 @@ +from ._monitor import TMonitor, TqdmSynchronisationWarning +from ._tqdm_pandas import tqdm_pandas +from .cli import main # TODO: remove in v5.0.0 +from .gui import tqdm as tqdm_gui # TODO: remove in v5.0.0 +from .gui import trange as tgrange # TODO: remove in v5.0.0 +from .std import ( + TqdmDeprecationWarning, TqdmExperimentalWarning, TqdmKeyError, TqdmMonitorWarning, + TqdmTypeError, TqdmWarning, tqdm, trange) +from .version import __version__ + +__all__ = ['tqdm', 'tqdm_gui', 'trange', 'tgrange', 'tqdm_pandas', + 'tqdm_notebook', 'tnrange', 'main', 'TMonitor', + 'TqdmTypeError', 'TqdmKeyError', + 'TqdmWarning', 'TqdmDeprecationWarning', + 'TqdmExperimentalWarning', + 'TqdmMonitorWarning', 'TqdmSynchronisationWarning', + '__version__'] + + +def tqdm_notebook(*args, **kwargs): # pragma: no cover + """See tqdm.notebook.tqdm for full documentation""" + from warnings import warn + + from .notebook import tqdm as _tqdm_notebook + warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`", + TqdmDeprecationWarning, stacklevel=2) + return _tqdm_notebook(*args, **kwargs) + + +def tnrange(*args, **kwargs): # pragma: no cover + """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" + from warnings import warn + + from .notebook import trange as _tnrange + warn("Please use `tqdm.notebook.trange` instead of `tqdm.tnrange`", + TqdmDeprecationWarning, stacklevel=2) + return _tnrange(*args, **kwargs) diff --git a/venv/lib/python3.10/site-packages/tqdm/__main__.py b/venv/lib/python3.10/site-packages/tqdm/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..4e28416e104515e90fca4b69cc60d0c61fd15d61 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c55ce1b6813cd5a217a0fbe6f70be1bd6543b3e2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/__main__.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/__main__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70f49f3bbd6f9378e8de43964d8105903d1959ed Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/__main__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/_dist_ver.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_dist_ver.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92bd5fd68c109ec0b2e764e521fbf23bbfac08c6 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_dist_ver.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/_main.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_main.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bc93c904b054a9fa9ddaff208114208d2c8ff39 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_main.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/_monitor.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_monitor.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa91a844b93890d8c35cb26a26146e56470ee3dc Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_monitor.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a44bfccfc4d1a8921251a1191f4eb43bdf39c4c0 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_gui.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_gui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21b4bab5d410ac7d4c8cd28ce32b06f880c82112 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_gui.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_notebook.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_notebook.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae52ea65642f32a84adc24b21b17914af700f14d Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_notebook.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_pandas.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_pandas.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4b876dca213714271bb1e89323bccf492a234b5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_tqdm_pandas.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/_utils.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a662d2b6f41e8afff478fb546134d8281915d3f7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/_utils.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/asyncio.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/asyncio.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..605389e5e89d7289c577fc3922e526d1915becc2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/asyncio.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/auto.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/auto.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0c19e8e4957ab1aa62e6fe82b1ad1847f78a85a Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/auto.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/autonotebook.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/autonotebook.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6af3934e87f72de3c10ce88d609f6d14f19896fa Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/autonotebook.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/cli.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/cli.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a74fa4ef94977082d41392a88d6ec7270eda0f2 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/cli.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/dask.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/dask.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c9cd5d80ab3374af9b9aa02e6cfa56cca7e7bbb Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/dask.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/gui.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/gui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e64c40c4aeeffbe5c4afdf2096755928b00fd186 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/gui.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/keras.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/keras.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28364b15e8afb90ba9895bc44ad1a5c90cdcd739 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/keras.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/notebook.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/notebook.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..776ae8334eecaa2429beef2e31bbe78416f8eb8c Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/notebook.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/rich.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/rich.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2c1631925526f6966b2bedf75512bcf0b924ea3 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/rich.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/std.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/std.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..808b6c0a77ecf13fb9ebd9be6f256d243ff3dea8 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/std.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/tk.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/tk.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b20800305b71bc22828a227064dc101aaaea78d5 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/tk.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/utils.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a263378a9189b13b71d89a631d59f7fd8809912 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/utils.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/__pycache__/version.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/__pycache__/version.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca54ffe8c0f3fe9f5cbf434d0adf86085bfd4926 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/__pycache__/version.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/_dist_ver.py b/venv/lib/python3.10/site-packages/tqdm/_dist_ver.py new file mode 100644 index 0000000000000000000000000000000000000000..61af7d5bb0b25d8dc934b45b18ea35bd32dbb465 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/_dist_ver.py @@ -0,0 +1 @@ +__version__ = '4.67.1' diff --git a/venv/lib/python3.10/site-packages/tqdm/_main.py b/venv/lib/python3.10/site-packages/tqdm/_main.py new file mode 100644 index 0000000000000000000000000000000000000000..04fdeeff17b5cc84b210f445b54b87d5b99e3748 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/_main.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .cli import * # NOQA +from .cli import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.cli.*` instead of `tqdm._main.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/venv/lib/python3.10/site-packages/tqdm/_monitor.py b/venv/lib/python3.10/site-packages/tqdm/_monitor.py new file mode 100644 index 0000000000000000000000000000000000000000..f71aa56817ca77eba5df4a2dd11cb0c4a9a7ea1c --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/_monitor.py @@ -0,0 +1,95 @@ +import atexit +from threading import Event, Thread, current_thread +from time import time +from warnings import warn + +__all__ = ["TMonitor", "TqdmSynchronisationWarning"] + + +class TqdmSynchronisationWarning(RuntimeWarning): + """tqdm multi-thread/-process errors which may cause incorrect nesting + but otherwise no adverse effects""" + pass + + +class TMonitor(Thread): + """ + Monitoring thread for tqdm bars. + Monitors if tqdm bars are taking too much time to display + and readjusts miniters automatically if necessary. + + Parameters + ---------- + tqdm_cls : class + tqdm class to use (can be core tqdm or a submodule). + sleep_interval : float + Time to sleep between monitoring checks. + """ + _test = {} # internal vars for unit testing + + def __init__(self, tqdm_cls, sleep_interval): + Thread.__init__(self) + self.daemon = True # kill thread when main killed (KeyboardInterrupt) + self.woken = 0 # last time woken up, to sync with monitor + self.tqdm_cls = tqdm_cls + self.sleep_interval = sleep_interval + self._time = self._test.get("time", time) + self.was_killed = self._test.get("Event", Event)() + atexit.register(self.exit) + self.start() + + def exit(self): + self.was_killed.set() + if self is not current_thread(): + self.join() + return self.report() + + def get_instances(self): + # returns a copy of started `tqdm_cls` instances + return [i for i in self.tqdm_cls._instances.copy() + # Avoid race by checking that the instance started + if hasattr(i, 'start_t')] + + def run(self): + cur_t = self._time() + while True: + # After processing and before sleeping, notify that we woke + # Need to be done just before sleeping + self.woken = cur_t + # Sleep some time... + self.was_killed.wait(self.sleep_interval) + # Quit if killed + if self.was_killed.is_set(): + return + # Then monitor! + # Acquire lock (to access _instances) + with self.tqdm_cls.get_lock(): + cur_t = self._time() + # Check tqdm instances are waiting too long to print + instances = self.get_instances() + for instance in instances: + # Check event in loop to reduce blocking time on exit + if self.was_killed.is_set(): + return + # Only if mininterval > 1 (else iterations are just slow) + # and last refresh exceeded maxinterval + if ( + instance.miniters > 1 + and (cur_t - instance.last_print_t) >= instance.maxinterval + ): + # force bypassing miniters on next iteration + # (dynamic_miniters adjusts mininterval automatically) + instance.miniters = 1 + # Refresh now! (works only for manual tqdm) + instance.refresh(nolock=True) + # Remove accidental long-lived strong reference + del instance + if instances != self.get_instances(): # pragma: nocover + warn("Set changed size during iteration" + + " (see https://github.com/tqdm/tqdm/issues/481)", + TqdmSynchronisationWarning, stacklevel=2) + # Remove accidental long-lived strong references + del instances + + def report(self): + return not self.was_killed.is_set() diff --git a/venv/lib/python3.10/site-packages/tqdm/_tqdm.py b/venv/lib/python3.10/site-packages/tqdm/_tqdm.py new file mode 100644 index 0000000000000000000000000000000000000000..7fc4962774a4651db7a739a3f143633b6215a9bd --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/_tqdm.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .std import * # NOQA +from .std import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.std.*` instead of `tqdm._tqdm.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/venv/lib/python3.10/site-packages/tqdm/_tqdm_gui.py b/venv/lib/python3.10/site-packages/tqdm/_tqdm_gui.py new file mode 100644 index 0000000000000000000000000000000000000000..f32aa894f54b3a5b47a0fbf4263c2fd20df56c9d --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/_tqdm_gui.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .gui import * # NOQA +from .gui import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.gui.*` instead of `tqdm._tqdm_gui.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/venv/lib/python3.10/site-packages/tqdm/_tqdm_notebook.py b/venv/lib/python3.10/site-packages/tqdm/_tqdm_notebook.py new file mode 100644 index 0000000000000000000000000000000000000000..f225fbf5b52d04987ccf68f4d5ee4b735e3158b0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/_tqdm_notebook.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .notebook import * # NOQA +from .notebook import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.notebook.*` instead of `tqdm._tqdm_notebook.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/venv/lib/python3.10/site-packages/tqdm/_tqdm_pandas.py b/venv/lib/python3.10/site-packages/tqdm/_tqdm_pandas.py new file mode 100644 index 0000000000000000000000000000000000000000..c4fe6efdc603579e7f8acfa27ac10dccdf3e94ce --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/_tqdm_pandas.py @@ -0,0 +1,24 @@ +import sys + +__author__ = "github.com/casperdcl" +__all__ = ['tqdm_pandas'] + + +def tqdm_pandas(tclass, **tqdm_kwargs): + """ + Registers the given `tqdm` instance with + `pandas.core.groupby.DataFrameGroupBy.progress_apply`. + """ + from tqdm import TqdmDeprecationWarning + + if isinstance(tclass, type) or (getattr(tclass, '__name__', '').startswith( + 'tqdm_')): # delayed adapter case + TqdmDeprecationWarning( + "Please use `tqdm.pandas(...)` instead of `tqdm_pandas(tqdm, ...)`.", + fp_write=getattr(tqdm_kwargs.get('file', None), 'write', sys.stderr.write)) + tclass.pandas(**tqdm_kwargs) + else: + TqdmDeprecationWarning( + "Please use `tqdm.pandas(...)` instead of `tqdm_pandas(tqdm(...))`.", + fp_write=getattr(tclass.fp, 'write', sys.stderr.write)) + type(tclass).pandas(deprecated_t=tclass) diff --git a/venv/lib/python3.10/site-packages/tqdm/_utils.py b/venv/lib/python3.10/site-packages/tqdm/_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..385e849e106d1319fe21045f14eb0aa6552fb153 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/_utils.py @@ -0,0 +1,11 @@ +from warnings import warn + +from .std import TqdmDeprecationWarning +from .utils import ( # NOQA, pylint: disable=unused-import + CUR_OS, IS_NIX, IS_WIN, RE_ANSI, Comparable, FormatReplace, SimpleTextIOWrapper, + _environ_cols_wrapper, _is_ascii, _is_utf, _screen_shape_linux, _screen_shape_tput, + _screen_shape_windows, _screen_shape_wrapper, _supports_unicode, _term_move_up, colorama) + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.utils.*` instead of `tqdm._utils.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/venv/lib/python3.10/site-packages/tqdm/asyncio.py b/venv/lib/python3.10/site-packages/tqdm/asyncio.py new file mode 100644 index 0000000000000000000000000000000000000000..2d00a0a2e755f36068d079ccc12ca84d86ff42be --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/asyncio.py @@ -0,0 +1,93 @@ +""" +Asynchronous progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm.asyncio import trange, tqdm +>>> async for i in trange(10): +... ... +""" +import asyncio +from sys import version_info + +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tqdm_asyncio', 'tarange', 'tqdm', 'trange'] + + +class tqdm_asyncio(std_tqdm): + """ + Asynchronous-friendly version of tqdm. + """ + def __init__(self, iterable=None, *args, **kwargs): + super().__init__(iterable, *args, **kwargs) + self.iterable_awaitable = False + if iterable is not None: + if hasattr(iterable, "__anext__"): + self.iterable_next = iterable.__anext__ + self.iterable_awaitable = True + elif hasattr(iterable, "__next__"): + self.iterable_next = iterable.__next__ + else: + self.iterable_iterator = iter(iterable) + self.iterable_next = self.iterable_iterator.__next__ + + def __aiter__(self): + return self + + async def __anext__(self): + try: + if self.iterable_awaitable: + res = await self.iterable_next() + else: + res = self.iterable_next() + self.update() + return res + except StopIteration: + self.close() + raise StopAsyncIteration + except BaseException: + self.close() + raise + + def send(self, *args, **kwargs): + return self.iterable.send(*args, **kwargs) + + @classmethod + def as_completed(cls, fs, *, loop=None, timeout=None, total=None, **tqdm_kwargs): + """ + Wrapper for `asyncio.as_completed`. + """ + if total is None: + total = len(fs) + kwargs = {} + if version_info[:2] < (3, 10): + kwargs['loop'] = loop + yield from cls(asyncio.as_completed(fs, timeout=timeout, **kwargs), + total=total, **tqdm_kwargs) + + @classmethod + async def gather(cls, *fs, loop=None, timeout=None, total=None, **tqdm_kwargs): + """ + Wrapper for `asyncio.gather`. + """ + async def wrap_awaitable(i, f): + return i, await f + + ifs = [wrap_awaitable(i, f) for i, f in enumerate(fs)] + res = [await f for f in cls.as_completed(ifs, loop=loop, timeout=timeout, + total=total, **tqdm_kwargs)] + return [i for _, i in sorted(res)] + + +def tarange(*args, **kwargs): + """ + A shortcut for `tqdm.asyncio.tqdm(range(*args), **kwargs)`. + """ + return tqdm_asyncio(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_asyncio +trange = tarange diff --git a/venv/lib/python3.10/site-packages/tqdm/auto.py b/venv/lib/python3.10/site-packages/tqdm/auto.py new file mode 100644 index 0000000000000000000000000000000000000000..206c4409d5269594bdbab3a092ef6e09e7c01947 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/auto.py @@ -0,0 +1,40 @@ +""" +Enables multiple commonly used features. + +Method resolution order: + +- `tqdm.autonotebook` without import warnings +- `tqdm.asyncio` +- `tqdm.std` base class + +Usage: +>>> from tqdm.auto import trange, tqdm +>>> for i in trange(10): +... ... +""" +import warnings + +from .std import TqdmExperimentalWarning + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=TqdmExperimentalWarning) + from .autonotebook import tqdm as notebook_tqdm + +from .asyncio import tqdm as asyncio_tqdm +from .std import tqdm as std_tqdm + +if notebook_tqdm != std_tqdm: + class tqdm(notebook_tqdm, asyncio_tqdm): # pylint: disable=inconsistent-mro + pass +else: + tqdm = asyncio_tqdm + + +def trange(*args, **kwargs): + """ + A shortcut for `tqdm.auto.tqdm(range(*args), **kwargs)`. + """ + return tqdm(range(*args), **kwargs) + + +__all__ = ["tqdm", "trange"] diff --git a/venv/lib/python3.10/site-packages/tqdm/autonotebook.py b/venv/lib/python3.10/site-packages/tqdm/autonotebook.py new file mode 100644 index 0000000000000000000000000000000000000000..a09f2ec4b8c95f12b8c7b7774f84d5ec55826334 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/autonotebook.py @@ -0,0 +1,29 @@ +""" +Automatically choose between `tqdm.notebook` and `tqdm.std`. + +Usage: +>>> from tqdm.autonotebook import trange, tqdm +>>> for i in trange(10): +... ... +""" +import sys +from warnings import warn + +try: + get_ipython = sys.modules['IPython'].get_ipython + if 'IPKernelApp' not in get_ipython().config: # pragma: no cover + raise ImportError("console") + from .notebook import WARN_NOIPYW, IProgress + if IProgress is None: + from .std import TqdmWarning + warn(WARN_NOIPYW, TqdmWarning, stacklevel=2) + raise ImportError('ipywidgets') +except Exception: + from .std import tqdm, trange +else: # pragma: no cover + from .notebook import tqdm, trange + from .std import TqdmExperimentalWarning + warn("Using `tqdm.autonotebook.tqdm` in notebook mode." + " Use `tqdm.tqdm` instead to force console mode" + " (e.g. in jupyter console)", TqdmExperimentalWarning, stacklevel=2) +__all__ = ["tqdm", "trange"] diff --git a/venv/lib/python3.10/site-packages/tqdm/cli.py b/venv/lib/python3.10/site-packages/tqdm/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..e54a7fc8599fe0dfef12cd53b76b27ae51b68b4b --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/cli.py @@ -0,0 +1,324 @@ +""" +Module version for monitoring CLI pipes (`... | python -m tqdm | ...`). +""" +import logging +import re +import sys +from ast import literal_eval as numeric +from textwrap import indent + +from .std import TqdmKeyError, TqdmTypeError, tqdm +from .version import __version__ + +__all__ = ["main"] +log = logging.getLogger(__name__) + + +def cast(val, typ): + log.debug((val, typ)) + if " or " in typ: + for t in typ.split(" or "): + try: + return cast(val, t) + except TqdmTypeError: + pass + raise TqdmTypeError(f"{val} : {typ}") + + # sys.stderr.write('\ndebug | `val:type`: `' + val + ':' + typ + '`.\n') + if typ == 'bool': + if (val == 'True') or (val == ''): + return True + if val == 'False': + return False + raise TqdmTypeError(val + ' : ' + typ) + if typ == 'chr': + if len(val) == 1: + return val.encode() + if re.match(r"^\\\w+$", val): + return eval(f'"{val}"').encode() + raise TqdmTypeError(f"{val} : {typ}") + if typ == 'str': + return val + if typ == 'int': + try: + return int(val) + except ValueError as exc: + raise TqdmTypeError(f"{val} : {typ}") from exc + if typ == 'float': + try: + return float(val) + except ValueError as exc: + raise TqdmTypeError(f"{val} : {typ}") from exc + raise TqdmTypeError(f"{val} : {typ}") + + +def posix_pipe(fin, fout, delim=b'\\n', buf_size=256, + callback=lambda float: None, callback_len=True): + """ + Params + ------ + fin : binary file with `read(buf_size : int)` method + fout : binary file with `write` (and optionally `flush`) methods. + callback : function(float), e.g.: `tqdm.update` + callback_len : If (default: True) do `callback(len(buffer))`. + Otherwise, do `callback(data) for data in buffer.split(delim)`. + """ + fp_write = fout.write + + if not delim: + while True: + tmp = fin.read(buf_size) + + # flush at EOF + if not tmp: + getattr(fout, 'flush', lambda: None)() + return + + fp_write(tmp) + callback(len(tmp)) + # return + + buf = b'' + len_delim = len(delim) + # n = 0 + while True: + tmp = fin.read(buf_size) + + # flush at EOF + if not tmp: + if buf: + fp_write(buf) + if callback_len: + # n += 1 + buf.count(delim) + callback(1 + buf.count(delim)) + else: + for i in buf.split(delim): + callback(i) + getattr(fout, 'flush', lambda: None)() + return # n + + while True: + i = tmp.find(delim) + if i < 0: + buf += tmp + break + fp_write(buf + tmp[:i + len(delim)]) + # n += 1 + callback(1 if callback_len else (buf + tmp[:i])) + buf = b'' + tmp = tmp[i + len_delim:] + + +# ((opt, type), ... ) +RE_OPTS = re.compile(r'\n {4}(\S+)\s{2,}:\s*([^,]+)') +# better split method assuming no positional args +RE_SHLEX = re.compile(r'\s*(? : \2', d) + split = RE_OPTS.split(d) + opt_types_desc = zip(split[1::3], split[2::3], split[3::3]) + d = ''.join(('\n --{0} : {2}{3}' if otd[1] == 'bool' else + '\n --{0}=<{1}> : {2}{3}').format( + otd[0].replace('_', '-'), otd[0], *otd[1:]) + for otd in opt_types_desc if otd[0] not in UNSUPPORTED_OPTS) + + help_short = "Usage:\n tqdm [--help | options]\n" + d = help_short + """ +Options: + -h, --help Print this help and exit. + -v, --version Print version and exit. +""" + d.strip('\n') + '\n' + + # opts = docopt(d, version=__version__) + if any(v in argv for v in ('-v', '--version')): + sys.stdout.write(__version__ + '\n') + sys.exit(0) + elif any(v in argv for v in ('-h', '--help')): + sys.stdout.write(d + '\n') + sys.exit(0) + elif argv and argv[0][:2] != '--': + sys.stderr.write(f"Error:Unknown argument:{argv[0]}\n{help_short}") + + argv = RE_SHLEX.split(' '.join(["tqdm"] + argv)) + opts = dict(zip(argv[1::3], argv[3::3])) + + log.debug(opts) + opts.pop('log', True) + + tqdm_args = {'file': fp} + try: + for (o, v) in opts.items(): + o = o.replace('-', '_') + try: + tqdm_args[o] = cast(v, opt_types[o]) + except KeyError as e: + raise TqdmKeyError(str(e)) + log.debug('args:' + str(tqdm_args)) + + delim_per_char = tqdm_args.pop('bytes', False) + update = tqdm_args.pop('update', False) + update_to = tqdm_args.pop('update_to', False) + if sum((delim_per_char, update, update_to)) > 1: + raise TqdmKeyError("Can only have one of --bytes --update --update_to") + except Exception: + fp.write("\nError:\n" + help_short) + stdin, stdout_write = sys.stdin, sys.stdout.write + for i in stdin: + stdout_write(i) + raise + else: + buf_size = tqdm_args.pop('buf_size', 256) + delim = tqdm_args.pop('delim', b'\\n') + tee = tqdm_args.pop('tee', False) + manpath = tqdm_args.pop('manpath', None) + comppath = tqdm_args.pop('comppath', None) + if tqdm_args.pop('null', False): + class stdout(object): + @staticmethod + def write(_): + pass + else: + stdout = sys.stdout + stdout = getattr(stdout, 'buffer', stdout) + stdin = getattr(sys.stdin, 'buffer', sys.stdin) + if manpath or comppath: + try: # py<3.9 + import importlib_resources as resources + except ImportError: + from importlib import resources + from pathlib import Path + + def cp(name, dst): + """copy resource `name` to `dst`""" + fi = resources.files('tqdm') / name + dst.write_bytes(fi.read_bytes()) + log.info("written:%s", dst) + if manpath is not None: + cp('tqdm.1', Path(manpath) / 'tqdm.1') + if comppath is not None: + cp('completion.sh', Path(comppath) / 'tqdm_completion.sh') + sys.exit(0) + if tee: + stdout_write = stdout.write + fp_write = getattr(fp, 'buffer', fp).write + + class stdout(object): # pylint: disable=function-redefined + @staticmethod + def write(x): + with tqdm.external_write_mode(file=fp): + fp_write(x) + stdout_write(x) + if delim_per_char: + tqdm_args.setdefault('unit', 'B') + tqdm_args.setdefault('unit_scale', True) + tqdm_args.setdefault('unit_divisor', 1024) + log.debug(tqdm_args) + with tqdm(**tqdm_args) as t: + posix_pipe(stdin, stdout, '', buf_size, t.update) + elif delim == b'\\n': + log.debug(tqdm_args) + write = stdout.write + if update or update_to: + with tqdm(**tqdm_args) as t: + if update: + def callback(i): + t.update(numeric(i.decode())) + else: # update_to + def callback(i): + t.update(numeric(i.decode()) - t.n) + for i in stdin: + write(i) + callback(i) + else: + for i in tqdm(stdin, **tqdm_args): + write(i) + else: + log.debug(tqdm_args) + with tqdm(**tqdm_args) as t: + callback_len = False + if update: + def callback(i): + t.update(numeric(i.decode())) + elif update_to: + def callback(i): + t.update(numeric(i.decode()) - t.n) + else: + callback = t.update + callback_len = True + posix_pipe(stdin, stdout, delim, buf_size, callback, callback_len) diff --git a/venv/lib/python3.10/site-packages/tqdm/completion.sh b/venv/lib/python3.10/site-packages/tqdm/completion.sh new file mode 100644 index 0000000000000000000000000000000000000000..9f61c7f14bb8c1f6099b9eb75dce28ece6a7ae96 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/completion.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +_tqdm(){ + local cur prv + cur="${COMP_WORDS[COMP_CWORD]}" + prv="${COMP_WORDS[COMP_CWORD - 1]}" + + case ${prv} in + --bar_format|--buf_size|--colour|--comppath|--delay|--delim|--desc|--initial|--lock_args|--manpath|--maxinterval|--mininterval|--miniters|--ncols|--nrows|--position|--postfix|--smoothing|--total|--unit|--unit_divisor) + # await user input + ;; + "--log") + COMPREPLY=($(compgen -W 'CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET' -- ${cur})) + ;; + *) + COMPREPLY=($(compgen -W '--ascii --bar_format --buf_size --bytes --colour --comppath --delay --delim --desc --disable --dynamic_ncols --help --initial --leave --lock_args --log --manpath --maxinterval --mininterval --miniters --ncols --nrows --null --position --postfix --smoothing --tee --total --unit --unit_divisor --unit_scale --update --update_to --version --write_bytes -h -v' -- ${cur})) + ;; + esac +} +complete -F _tqdm tqdm diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__init__.py b/venv/lib/python3.10/site-packages/tqdm/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d059461f91fb79115263c16314c3487e16ab98c2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/__init__.py @@ -0,0 +1,92 @@ +""" +Thin wrappers around common functions. + +Subpackages contain potentially unstable extensions. +""" +from warnings import warn + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmDeprecationWarning, tqdm +from ..utils import ObjectWrapper + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tenumerate', 'tzip', 'tmap'] + + +class DummyTqdmFile(ObjectWrapper): + """Dummy file-like that will write to tqdm""" + + def __init__(self, wrapped): + super().__init__(wrapped) + self._buf = [] + + def write(self, x, nolock=False): + nl = b"\n" if isinstance(x, bytes) else "\n" + pre, sep, post = x.rpartition(nl) + if sep: + blank = type(nl)() + tqdm.write(blank.join(self._buf + [pre, sep]), + end=blank, file=self._wrapped, nolock=nolock) + self._buf = [post] + else: + self._buf.append(x) + + def __del__(self): + if self._buf: + blank = type(self._buf[0])() + try: + tqdm.write(blank.join(self._buf), end=blank, file=self._wrapped) + except (OSError, ValueError): + pass + + +def builtin_iterable(func): + """Returns `func`""" + warn("This function has no effect, and will be removed in tqdm==5.0.0", + TqdmDeprecationWarning, stacklevel=2) + return func + + +def tenumerate(iterable, start=0, total=None, tqdm_class=tqdm_auto, **tqdm_kwargs): + """ + Equivalent of `numpy.ndenumerate` or builtin `enumerate`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + try: + import numpy as np + except ImportError: + pass + else: + if isinstance(iterable, np.ndarray): + return tqdm_class(np.ndenumerate(iterable), total=total or iterable.size, + **tqdm_kwargs) + return enumerate(tqdm_class(iterable, total=total, **tqdm_kwargs), start) + + +def tzip(iter1, *iter2plus, **tqdm_kwargs): + """ + Equivalent of builtin `zip`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = tqdm_kwargs.copy() + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + for i in zip(tqdm_class(iter1, **kwargs), *iter2plus): + yield i + + +def tmap(function, *sequences, **tqdm_kwargs): + """ + Equivalent of builtin `map`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + for i in tzip(*sequences, **tqdm_kwargs): + yield function(*i) diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cef0e753d84b6977b75fadebcbccacf349252a8e Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/bells.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/bells.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..938ab123058d72bd955b129f45a9659020407f52 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/bells.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/concurrent.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/concurrent.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16601d5ef2e5b39b677fb2b13874696bd1672dd7 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/concurrent.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/discord.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/discord.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8fa5207d53e4dc3aae104fc5bdf7ec66c3ee563 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/discord.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/itertools.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/itertools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70f5f7a821b97497adc892e9c739b48b69e67339 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/itertools.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/logging.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/logging.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64dbe8e3ca5401b1e413a016eb41148d26f802cc Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/logging.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/slack.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/slack.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..529d7dfce4e4f168de0edc0417149f324361d731 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/slack.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/telegram.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/telegram.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23c1eb65f1fbebcedcd3b4d52bbb2233f6e58903 Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/telegram.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/utils_worker.cpython-310.pyc b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/utils_worker.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d28d2a138e60c71305b66ec4d6c43fb659e910ea Binary files /dev/null and b/venv/lib/python3.10/site-packages/tqdm/contrib/__pycache__/utils_worker.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/bells.py b/venv/lib/python3.10/site-packages/tqdm/contrib/bells.py new file mode 100644 index 0000000000000000000000000000000000000000..5b8f4b9ecd894f1edfaa08d9fe730b8d7c8b93e0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/bells.py @@ -0,0 +1,26 @@ +""" +Even more features than `tqdm.auto` (all the bells & whistles): + +- `tqdm.auto` +- `tqdm.tqdm.pandas` +- `tqdm.contrib.telegram` + + uses `${TQDM_TELEGRAM_TOKEN}` and `${TQDM_TELEGRAM_CHAT_ID}` +- `tqdm.contrib.discord` + + uses `${TQDM_DISCORD_TOKEN}` and `${TQDM_DISCORD_CHANNEL_ID}` +""" +__all__ = ['tqdm', 'trange'] +import warnings +from os import getenv + +if getenv("TQDM_SLACK_TOKEN") and getenv("TQDM_SLACK_CHANNEL"): + from .slack import tqdm, trange +elif getenv("TQDM_TELEGRAM_TOKEN") and getenv("TQDM_TELEGRAM_CHAT_ID"): + from .telegram import tqdm, trange +elif getenv("TQDM_DISCORD_TOKEN") and getenv("TQDM_DISCORD_CHANNEL_ID"): + from .discord import tqdm, trange +else: + from ..auto import tqdm, trange + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=FutureWarning) + tqdm.pandas() diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/concurrent.py b/venv/lib/python3.10/site-packages/tqdm/contrib/concurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..cd81d622a1309df179042159a56cef4f8c309224 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/concurrent.py @@ -0,0 +1,105 @@ +""" +Thin wrappers around `concurrent.futures`. +""" +from contextlib import contextmanager +from operator import length_hint +from os import cpu_count + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmWarning + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['thread_map', 'process_map'] + + +@contextmanager +def ensure_lock(tqdm_class, lock_name=""): + """get (create if necessary) and then restore `tqdm_class`'s lock""" + old_lock = getattr(tqdm_class, '_lock', None) # don't create a new lock + lock = old_lock or tqdm_class.get_lock() # maybe create a new lock + lock = getattr(lock, lock_name, lock) # maybe subtype + tqdm_class.set_lock(lock) + yield lock + if old_lock is None: + del tqdm_class._lock + else: + tqdm_class.set_lock(old_lock) + + +def _executor_map(PoolExecutor, fn, *iterables, **tqdm_kwargs): + """ + Implementation of `thread_map` and `process_map`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + max_workers : [default: min(32, cpu_count() + 4)]. + chunksize : [default: 1]. + lock_name : [default: "":str]. + """ + kwargs = tqdm_kwargs.copy() + if "total" not in kwargs: + kwargs["total"] = length_hint(iterables[0]) + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + max_workers = kwargs.pop("max_workers", min(32, cpu_count() + 4)) + chunksize = kwargs.pop("chunksize", 1) + lock_name = kwargs.pop("lock_name", "") + with ensure_lock(tqdm_class, lock_name=lock_name) as lk: + # share lock in case workers are already using `tqdm` + with PoolExecutor(max_workers=max_workers, initializer=tqdm_class.set_lock, + initargs=(lk,)) as ex: + return list(tqdm_class(ex.map(fn, *iterables, chunksize=chunksize), **kwargs)) + + +def thread_map(fn, *iterables, **tqdm_kwargs): + """ + Equivalent of `list(map(fn, *iterables))` + driven by `concurrent.futures.ThreadPoolExecutor`. + + Parameters + ---------- + tqdm_class : optional + `tqdm` class to use for bars [default: tqdm.auto.tqdm]. + max_workers : int, optional + Maximum number of workers to spawn; passed to + `concurrent.futures.ThreadPoolExecutor.__init__`. + [default: max(32, cpu_count() + 4)]. + """ + from concurrent.futures import ThreadPoolExecutor + return _executor_map(ThreadPoolExecutor, fn, *iterables, **tqdm_kwargs) + + +def process_map(fn, *iterables, **tqdm_kwargs): + """ + Equivalent of `list(map(fn, *iterables))` + driven by `concurrent.futures.ProcessPoolExecutor`. + + Parameters + ---------- + tqdm_class : optional + `tqdm` class to use for bars [default: tqdm.auto.tqdm]. + max_workers : int, optional + Maximum number of workers to spawn; passed to + `concurrent.futures.ProcessPoolExecutor.__init__`. + [default: min(32, cpu_count() + 4)]. + chunksize : int, optional + Size of chunks sent to worker processes; passed to + `concurrent.futures.ProcessPoolExecutor.map`. [default: 1]. + lock_name : str, optional + Member of `tqdm_class.get_lock()` to use [default: mp_lock]. + """ + from concurrent.futures import ProcessPoolExecutor + if iterables and "chunksize" not in tqdm_kwargs: + # default `chunksize=1` has poor performance for large iterables + # (most time spent dispatching items to workers). + longest_iterable_len = max(map(length_hint, iterables)) + if longest_iterable_len > 1000: + from warnings import warn + warn("Iterable length %d > 1000 but `chunksize` is not set." + " This may seriously degrade multiprocess performance." + " Set `chunksize=1` or more." % longest_iterable_len, + TqdmWarning, stacklevel=2) + if "lock_name" not in tqdm_kwargs: + tqdm_kwargs = tqdm_kwargs.copy() + tqdm_kwargs["lock_name"] = "mp_lock" + return _executor_map(ProcessPoolExecutor, fn, *iterables, **tqdm_kwargs) diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/discord.py b/venv/lib/python3.10/site-packages/tqdm/contrib/discord.py new file mode 100644 index 0000000000000000000000000000000000000000..574baa84bbbeb5afce4a49f23edac894d680ca82 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/discord.py @@ -0,0 +1,156 @@ +""" +Sends updates to a Discord bot. + +Usage: +>>> from tqdm.contrib.discord import tqdm, trange +>>> for i in trange(10, token='{token}', channel_id='{channel_id}'): +... ... + +![screenshot](https://tqdm.github.io/img/screenshot-discord.png) +""" +from os import getenv +from warnings import warn + +from requests import Session +from requests.utils import default_user_agent + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmWarning +from ..version import __version__ +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["casperdcl", "guigoruiz1"]} +__all__ = ['DiscordIO', 'tqdm_discord', 'tdrange', 'tqdm', 'trange'] + + +class DiscordIO(MonoWorker): + """Non-blocking file-like IO using a Discord Bot.""" + API = "https://discord.com/api/v10" + UA = f"tqdm (https://tqdm.github.io, {__version__}) {default_user_agent()}" + + def __init__(self, token, channel_id): + """Creates a new message in the given `channel_id`.""" + super().__init__() + self.token = token + self.channel_id = channel_id + self.session = Session() + self.text = self.__class__.__name__ + self.message_id + + @property + def message_id(self): + if hasattr(self, '_message_id'): + return self._message_id + try: + res = self.session.post( + f'{self.API}/channels/{self.channel_id}/messages', + headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}, + json={'content': f"`{self.text}`"}).json() + except Exception as e: + tqdm_auto.write(str(e)) + else: + if res.get('error_code') == 429: + warn("Creation rate limit: try increasing `mininterval`.", + TqdmWarning, stacklevel=2) + else: + self._message_id = res['id'] + return self._message_id + + def write(self, s): + """Replaces internal `message_id`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # avoid duplicate message Bot error + message_id = self.message_id + if message_id is None: + return + self.text = s + try: + future = self.submit( + self.session.patch, + f'{self.API}/channels/{self.channel_id}/messages/{message_id}', + headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}, + json={'content': f"`{self.text}`"}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + def delete(self): + """Deletes internal `message_id`.""" + try: + future = self.submit( + self.session.delete, + f'{self.API}/channels/{self.channel_id}/messages/{self.message_id}', + headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_discord(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Discord Bot. + May take a few seconds to create (`__init__`). + + - create a discord bot (not public, no requirement of OAuth2 code + grant, only send message permissions) & invite it to a channel: + + - copy the bot `{token}` & `{channel_id}` and paste below + + >>> from tqdm.contrib.discord import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', channel_id='{channel_id}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Discord bot token + [default: ${TQDM_DISCORD_TOKEN}]. + channel_id : int, required. Discord channel ID + [default: ${TQDM_DISCORD_CHANNEL_ID}]. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + self.dio = DiscordIO( + kwargs.pop('token', getenv('TQDM_DISCORD_TOKEN')), + kwargs.pop('channel_id', getenv('TQDM_DISCORD_CHANNEL_ID'))) + super().__init__(*args, **kwargs) + + def display(self, **kwargs): + super().display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '{bar:10u}').replace('{bar}', '{bar:10u}') + else: + fmt['bar_format'] = '{l_bar}{bar:10u}{r_bar}' + self.dio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super().clear(*args, **kwargs) + if not self.disable: + self.dio.write("") + + def close(self): + if self.disable: + return + super().close() + if not (self.leave or (self.leave is None and self.pos == 0)): + self.dio.delete() + + +def tdrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.discord.tqdm(range(*args), **kwargs)`.""" + return tqdm_discord(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_discord +trange = tdrange diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/itertools.py b/venv/lib/python3.10/site-packages/tqdm/contrib/itertools.py new file mode 100644 index 0000000000000000000000000000000000000000..e67651a41a6b8760d9b928ea48239e4611d70315 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/itertools.py @@ -0,0 +1,35 @@ +""" +Thin wrappers around `itertools`. +""" +import itertools + +from ..auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['product'] + + +def product(*iterables, **tqdm_kwargs): + """ + Equivalent of `itertools.product`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = tqdm_kwargs.copy() + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + try: + lens = list(map(len, iterables)) + except TypeError: + total = None + else: + total = 1 + for i in lens: + total *= i + kwargs.setdefault("total", total) + with tqdm_class(**kwargs) as t: + it = itertools.product(*iterables) + for i in it: + yield i + t.update() diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/logging.py b/venv/lib/python3.10/site-packages/tqdm/contrib/logging.py new file mode 100644 index 0000000000000000000000000000000000000000..e06febe37b5d70b5296804c55dca48a397c250e3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/logging.py @@ -0,0 +1,126 @@ +""" +Helper functionality for interoperability with stdlib `logging`. +""" +import logging +import sys +from contextlib import contextmanager + +try: + from typing import Iterator, List, Optional, Type # noqa: F401 +except ImportError: + pass + +from ..std import tqdm as std_tqdm + + +class _TqdmLoggingHandler(logging.StreamHandler): + def __init__( + self, + tqdm_class=std_tqdm # type: Type[std_tqdm] + ): + super().__init__() + self.tqdm_class = tqdm_class + + def emit(self, record): + try: + msg = self.format(record) + self.tqdm_class.write(msg, file=self.stream) + self.flush() + except (KeyboardInterrupt, SystemExit): + raise + except: # noqa pylint: disable=bare-except + self.handleError(record) + + +def _is_console_logging_handler(handler): + return (isinstance(handler, logging.StreamHandler) + and handler.stream in {sys.stdout, sys.stderr}) + + +def _get_first_found_console_logging_handler(handlers): + for handler in handlers: + if _is_console_logging_handler(handler): + return handler + + +@contextmanager +def logging_redirect_tqdm( + loggers=None, # type: Optional[List[logging.Logger]], + tqdm_class=std_tqdm # type: Type[std_tqdm] +): + # type: (...) -> Iterator[None] + """ + Context manager redirecting console logging to `tqdm.write()`, leaving + other logging handlers (e.g. log files) unaffected. + + Parameters + ---------- + loggers : list, optional + Which handlers to redirect (default: [logging.root]). + tqdm_class : optional + + Example + ------- + ```python + import logging + from tqdm import trange + from tqdm.contrib.logging import logging_redirect_tqdm + + LOG = logging.getLogger(__name__) + + if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + with logging_redirect_tqdm(): + for i in trange(9): + if i == 4: + LOG.info("console logging redirected to `tqdm.write()`") + # logging restored + ``` + """ + if loggers is None: + loggers = [logging.root] + original_handlers_list = [logger.handlers for logger in loggers] + try: + for logger in loggers: + tqdm_handler = _TqdmLoggingHandler(tqdm_class) + orig_handler = _get_first_found_console_logging_handler(logger.handlers) + if orig_handler is not None: + tqdm_handler.setFormatter(orig_handler.formatter) + tqdm_handler.stream = orig_handler.stream + logger.handlers = [ + handler for handler in logger.handlers + if not _is_console_logging_handler(handler)] + [tqdm_handler] + yield + finally: + for logger, original_handlers in zip(loggers, original_handlers_list): + logger.handlers = original_handlers + + +@contextmanager +def tqdm_logging_redirect( + *args, + # loggers=None, # type: Optional[List[logging.Logger]] + # tqdm=None, # type: Optional[Type[tqdm.tqdm]] + **kwargs +): + # type: (...) -> Iterator[None] + """ + Convenience shortcut for: + ```python + with tqdm_class(*args, **tqdm_kwargs) as pbar: + with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class): + yield pbar + ``` + + Parameters + ---------- + tqdm_class : optional, (default: tqdm.std.tqdm). + loggers : optional, list. + **tqdm_kwargs : passed to `tqdm_class`. + """ + tqdm_kwargs = kwargs.copy() + loggers = tqdm_kwargs.pop('loggers', None) + tqdm_class = tqdm_kwargs.pop('tqdm_class', std_tqdm) + with tqdm_class(*args, **tqdm_kwargs) as pbar: + with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class): + yield pbar diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/slack.py b/venv/lib/python3.10/site-packages/tqdm/contrib/slack.py new file mode 100644 index 0000000000000000000000000000000000000000..9bca8ee98904ce869a4f8d6417bbdc4f00b38751 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/slack.py @@ -0,0 +1,120 @@ +""" +Sends updates to a Slack app. + +Usage: +>>> from tqdm.contrib.slack import tqdm, trange +>>> for i in trange(10, token='{token}', channel='{channel}'): +... ... + +![screenshot](https://tqdm.github.io/img/screenshot-slack.png) +""" +import logging +from os import getenv + +try: + from slack_sdk import WebClient +except ImportError: + raise ImportError("Please `pip install slack-sdk`") + +from ..auto import tqdm as tqdm_auto +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["0x2b3bfa0", "casperdcl"]} +__all__ = ['SlackIO', 'tqdm_slack', 'tsrange', 'tqdm', 'trange'] + + +class SlackIO(MonoWorker): + """Non-blocking file-like IO using a Slack app.""" + def __init__(self, token, channel): + """Creates a new message in the given `channel`.""" + super().__init__() + self.client = WebClient(token=token) + self.text = self.__class__.__name__ + try: + self.message = self.client.chat_postMessage(channel=channel, text=self.text) + except Exception as e: + tqdm_auto.write(str(e)) + self.message = None + + def write(self, s): + """Replaces internal `message`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # skip duplicate message + message = self.message + if message is None: + return + self.text = s + try: + future = self.submit(self.client.chat_update, channel=message['channel'], + ts=message['ts'], text='`' + s + '`') + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_slack(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Slack app. + May take a few seconds to create (`__init__`). + + - create a Slack app with the `chat:write` scope & invite it to a + channel: + - copy the bot `{token}` & `{channel}` and paste below + >>> from tqdm.contrib.slack import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', channel='{channel}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Slack token + [default: ${TQDM_SLACK_TOKEN}]. + channel : int, required. Slack channel + [default: ${TQDM_SLACK_CHANNEL}]. + mininterval : float, optional. + Minimum of [default: 1.5] to avoid rate limit. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + logging.getLogger("HTTPClient").setLevel(logging.WARNING) + self.sio = SlackIO( + kwargs.pop('token', getenv("TQDM_SLACK_TOKEN")), + kwargs.pop('channel', getenv("TQDM_SLACK_CHANNEL"))) + kwargs['mininterval'] = max(1.5, kwargs.get('mininterval', 1.5)) + super().__init__(*args, **kwargs) + + def display(self, **kwargs): + super().display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '`{bar:10}`').replace('{bar}', '`{bar:10u}`') + else: + fmt['bar_format'] = '{l_bar}`{bar:10}`{r_bar}' + if fmt['ascii'] is False: + fmt['ascii'] = [":black_square:", ":small_blue_diamond:", ":large_blue_diamond:", + ":large_blue_square:"] + fmt['ncols'] = 336 + self.sio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super().clear(*args, **kwargs) + if not self.disable: + self.sio.write("") + + +def tsrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.slack.tqdm(range(*args), **kwargs)`.""" + return tqdm_slack(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_slack +trange = tsrange diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/telegram.py b/venv/lib/python3.10/site-packages/tqdm/contrib/telegram.py new file mode 100644 index 0000000000000000000000000000000000000000..019151800bc0c4c4fc543314b6398aa602b0692a --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/telegram.py @@ -0,0 +1,153 @@ +""" +Sends updates to a Telegram bot. + +Usage: +>>> from tqdm.contrib.telegram import tqdm, trange +>>> for i in trange(10, token='{token}', chat_id='{chat_id}'): +... ... + +![screenshot](https://tqdm.github.io/img/screenshot-telegram.gif) +""" +from os import getenv +from warnings import warn + +from requests import Session + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmWarning +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TelegramIO', 'tqdm_telegram', 'ttgrange', 'tqdm', 'trange'] + + +class TelegramIO(MonoWorker): + """Non-blocking file-like IO using a Telegram Bot.""" + API = 'https://api.telegram.org/bot' + + def __init__(self, token, chat_id): + """Creates a new message in the given `chat_id`.""" + super().__init__() + self.token = token + self.chat_id = chat_id + self.session = Session() + self.text = self.__class__.__name__ + self.message_id + + @property + def message_id(self): + if hasattr(self, '_message_id'): + return self._message_id + try: + res = self.session.post( + self.API + '%s/sendMessage' % self.token, + data={'text': '`' + self.text + '`', 'chat_id': self.chat_id, + 'parse_mode': 'MarkdownV2'}).json() + except Exception as e: + tqdm_auto.write(str(e)) + else: + if res.get('error_code') == 429: + warn("Creation rate limit: try increasing `mininterval`.", + TqdmWarning, stacklevel=2) + else: + self._message_id = res['result']['message_id'] + return self._message_id + + def write(self, s): + """Replaces internal `message_id`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # avoid duplicate message Bot error + message_id = self.message_id + if message_id is None: + return + self.text = s + try: + future = self.submit( + self.session.post, self.API + '%s/editMessageText' % self.token, + data={'text': '`' + s + '`', 'chat_id': self.chat_id, + 'message_id': message_id, 'parse_mode': 'MarkdownV2'}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + def delete(self): + """Deletes internal `message_id`.""" + try: + future = self.submit( + self.session.post, self.API + '%s/deleteMessage' % self.token, + data={'chat_id': self.chat_id, 'message_id': self.message_id}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_telegram(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Telegram Bot. + May take a few seconds to create (`__init__`). + + - create a bot + - copy its `{token}` + - add the bot to a chat and send it a message such as `/start` + - go to to find out + the `{chat_id}` + - paste the `{token}` & `{chat_id}` below + + >>> from tqdm.contrib.telegram import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', chat_id='{chat_id}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Telegram token + [default: ${TQDM_TELEGRAM_TOKEN}]. + chat_id : str, required. Telegram chat ID + [default: ${TQDM_TELEGRAM_CHAT_ID}]. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + self.tgio = TelegramIO( + kwargs.pop('token', getenv('TQDM_TELEGRAM_TOKEN')), + kwargs.pop('chat_id', getenv('TQDM_TELEGRAM_CHAT_ID'))) + super().__init__(*args, **kwargs) + + def display(self, **kwargs): + super().display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '{bar:10u}').replace('{bar}', '{bar:10u}') + else: + fmt['bar_format'] = '{l_bar}{bar:10u}{r_bar}' + self.tgio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super().clear(*args, **kwargs) + if not self.disable: + self.tgio.write("") + + def close(self): + if self.disable: + return + super().close() + if not (self.leave or (self.leave is None and self.pos == 0)): + self.tgio.delete() + + +def ttgrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.telegram.tqdm(range(*args), **kwargs)`.""" + return tqdm_telegram(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_telegram +trange = ttgrange diff --git a/venv/lib/python3.10/site-packages/tqdm/contrib/utils_worker.py b/venv/lib/python3.10/site-packages/tqdm/contrib/utils_worker.py new file mode 100644 index 0000000000000000000000000000000000000000..2a03a2a8930001e37938836196e0d15b649b07a8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/contrib/utils_worker.py @@ -0,0 +1,38 @@ +""" +IO/concurrency helpers for `tqdm.contrib`. +""" +from collections import deque +from concurrent.futures import ThreadPoolExecutor + +from ..auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['MonoWorker'] + + +class MonoWorker(object): + """ + Supports one running task and one waiting task. + The waiting task is the most recent submitted (others are discarded). + """ + def __init__(self): + self.pool = ThreadPoolExecutor(max_workers=1) + self.futures = deque([], 2) + + def submit(self, func, *args, **kwargs): + """`func(*args, **kwargs)` may replace currently waiting task.""" + futures = self.futures + if len(futures) == futures.maxlen: + running = futures.popleft() + if not running.done(): + if len(futures): # clear waiting + waiting = futures.pop() + waiting.cancel() + futures.appendleft(running) # re-insert running + try: + waiting = self.pool.submit(func, *args, **kwargs) + except Exception as e: + tqdm_auto.write(str(e)) + else: + futures.append(waiting) + return waiting diff --git a/venv/lib/python3.10/site-packages/tqdm/dask.py b/venv/lib/python3.10/site-packages/tqdm/dask.py new file mode 100644 index 0000000000000000000000000000000000000000..57f1b668f59dc5991019eee34c7df3232a2c2cd7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/dask.py @@ -0,0 +1,44 @@ +from functools import partial + +from dask.callbacks import Callback + +from .auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TqdmCallback'] + + +class TqdmCallback(Callback): + """Dask callback for task progress.""" + def __init__(self, start=None, pretask=None, tqdm_class=tqdm_auto, + **tqdm_kwargs): + """ + Parameters + ---------- + tqdm_class : optional + `tqdm` class to use for bars [default: `tqdm.auto.tqdm`]. + tqdm_kwargs : optional + Any other arguments used for all bars. + """ + super().__init__(start=start, pretask=pretask) + if tqdm_kwargs: + tqdm_class = partial(tqdm_class, **tqdm_kwargs) + self.tqdm_class = tqdm_class + + def _start_state(self, _, state): + self.pbar = self.tqdm_class(total=sum( + len(state[k]) for k in ['ready', 'waiting', 'running', 'finished'])) + + def _posttask(self, *_, **__): + self.pbar.update() + + def _finish(self, *_, **__): + self.pbar.close() + + def display(self): + """Displays in the current cell in Notebooks.""" + container = getattr(self.bar, 'container', None) + if container is None: + return + from .notebook import display + display(container) diff --git a/venv/lib/python3.10/site-packages/tqdm/gui.py b/venv/lib/python3.10/site-packages/tqdm/gui.py new file mode 100644 index 0000000000000000000000000000000000000000..cb52fb91a8661f4c73edd352bbc6f21b877dcfee --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/gui.py @@ -0,0 +1,179 @@ +""" +Matplotlib GUI progressbar decorator for iterators. + +Usage: +>>> from tqdm.gui import trange, tqdm +>>> for i in trange(10): +... ... +""" +# future division is important to divide integers and get as +# a result precise floating numbers (instead of truncated int) +import re +from warnings import warn + +# to inherit from the tqdm class +from .std import TqdmExperimentalWarning +from .std import tqdm as std_tqdm + +# import compatibility functions and utilities + +__author__ = {"github.com/": ["casperdcl", "lrq3000"]} +__all__ = ['tqdm_gui', 'tgrange', 'tqdm', 'trange'] + + +class tqdm_gui(std_tqdm): # pragma: no cover + """Experimental Matplotlib GUI version of tqdm!""" + # TODO: @classmethod: write() on GUI? + def __init__(self, *args, **kwargs): + from collections import deque + + import matplotlib as mpl + import matplotlib.pyplot as plt + kwargs = kwargs.copy() + kwargs['gui'] = True + colour = kwargs.pop('colour', 'g') + super().__init__(*args, **kwargs) + + if self.disable: + return + + warn("GUI is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + self.mpl = mpl + self.plt = plt + + # Remember if external environment uses toolbars + self.toolbar = self.mpl.rcParams['toolbar'] + self.mpl.rcParams['toolbar'] = 'None' + + self.mininterval = max(self.mininterval, 0.5) + self.fig, ax = plt.subplots(figsize=(9, 2.2)) + # self.fig.subplots_adjust(bottom=0.2) + total = self.__len__() # avoids TypeError on None #971 + if total is not None: + self.xdata = [] + self.ydata = [] + self.zdata = [] + else: + self.xdata = deque([]) + self.ydata = deque([]) + self.zdata = deque([]) + self.line1, = ax.plot(self.xdata, self.ydata, color='b') + self.line2, = ax.plot(self.xdata, self.zdata, color='k') + ax.set_ylim(0, 0.001) + if total is not None: + ax.set_xlim(0, 100) + ax.set_xlabel("percent") + self.fig.legend((self.line1, self.line2), ("cur", "est"), + loc='center right') + # progressbar + self.hspan = plt.axhspan(0, 0.001, xmin=0, xmax=0, color=colour) + else: + # ax.set_xlim(-60, 0) + ax.set_xlim(0, 60) + ax.invert_xaxis() + ax.set_xlabel("seconds") + ax.legend(("cur", "est"), loc='lower left') + ax.grid() + # ax.set_xlabel('seconds') + ax.set_ylabel((self.unit if self.unit else "it") + "/s") + if self.unit_scale: + plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) + ax.yaxis.get_offset_text().set_x(-0.15) + + # Remember if external environment is interactive + self.wasion = plt.isinteractive() + plt.ion() + self.ax = ax + + def close(self): + if self.disable: + return + + self.disable = True + + with self.get_lock(): + self._instances.remove(self) + + # Restore toolbars + self.mpl.rcParams['toolbar'] = self.toolbar + # Return to non-interactive mode + if not self.wasion: + self.plt.ioff() + if self.leave: + self.display() + else: + self.plt.close(self.fig) + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + n = self.n + cur_t = self._time() + elapsed = cur_t - self.start_t + delta_it = n - self.last_print_n + delta_t = cur_t - self.last_print_t + + # Inline due to multiple calls + total = self.total + xdata = self.xdata + ydata = self.ydata + zdata = self.zdata + ax = self.ax + line1 = self.line1 + line2 = self.line2 + hspan = getattr(self, 'hspan', None) + # instantaneous rate + y = delta_it / delta_t + # overall rate + z = n / elapsed + # update line data + xdata.append(n * 100.0 / total if total else cur_t) + ydata.append(y) + zdata.append(z) + + # Discard old values + # xmin, xmax = ax.get_xlim() + # if (not total) and elapsed > xmin * 1.1: + if (not total) and elapsed > 66: + xdata.popleft() + ydata.popleft() + zdata.popleft() + + ymin, ymax = ax.get_ylim() + if y > ymax or z > ymax: + ymax = 1.1 * y + ax.set_ylim(ymin, ymax) + ax.figure.canvas.draw() + + if total: + line1.set_data(xdata, ydata) + line2.set_data(xdata, zdata) + if hspan: + hspan.set_xy((0, ymin)) + hspan.set_height(ymax - ymin) + hspan.set_width(n / total) + else: + t_ago = [cur_t - i for i in xdata] + line1.set_data(t_ago, ydata) + line2.set_data(t_ago, zdata) + + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + if '' in msg: + msg = "".join(re.split(r'\|?\|?', msg, maxsplit=1)) + ax.set_title(msg, fontname="DejaVu Sans Mono", fontsize=11) + self.plt.pause(1e-9) + + +def tgrange(*args, **kwargs): + """Shortcut for `tqdm.gui.tqdm(range(*args), **kwargs)`.""" + return tqdm_gui(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_gui +trange = tgrange diff --git a/venv/lib/python3.10/site-packages/tqdm/keras.py b/venv/lib/python3.10/site-packages/tqdm/keras.py new file mode 100644 index 0000000000000000000000000000000000000000..cce9467c51a95388aaa502d1da9a42f3ebf0af24 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/keras.py @@ -0,0 +1,122 @@ +from copy import copy +from functools import partial + +from .auto import tqdm as tqdm_auto + +try: + import keras +except (ImportError, AttributeError) as e: + try: + from tensorflow import keras + except ImportError: + raise e +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TqdmCallback'] + + +class TqdmCallback(keras.callbacks.Callback): + """Keras callback for epoch and batch progress.""" + @staticmethod + def bar2callback(bar, pop=None, delta=(lambda logs: 1)): + def callback(_, logs=None): + n = delta(logs) + if logs: + if pop: + logs = copy(logs) + [logs.pop(i, 0) for i in pop] + bar.set_postfix(logs, refresh=False) + bar.update(n) + + return callback + + def __init__(self, epochs=None, data_size=None, batch_size=None, verbose=1, + tqdm_class=tqdm_auto, **tqdm_kwargs): + """ + Parameters + ---------- + epochs : int, optional + data_size : int, optional + Number of training pairs. + batch_size : int, optional + Number of training pairs per batch. + verbose : int + 0: epoch, 1: batch (transient), 2: batch. [default: 1]. + Will be set to `0` unless both `data_size` and `batch_size` + are given. + tqdm_class : optional + `tqdm` class to use for bars [default: `tqdm.auto.tqdm`]. + tqdm_kwargs : optional + Any other arguments used for all bars. + """ + if tqdm_kwargs: + tqdm_class = partial(tqdm_class, **tqdm_kwargs) + self.tqdm_class = tqdm_class + self.epoch_bar = tqdm_class(total=epochs, unit='epoch') + self.on_epoch_end = self.bar2callback(self.epoch_bar) + if data_size and batch_size: + self.batches = batches = (data_size + batch_size - 1) // batch_size + else: + self.batches = batches = None + self.verbose = verbose + if verbose == 1: + self.batch_bar = tqdm_class(total=batches, unit='batch', leave=False) + self.on_batch_end = self.bar2callback( + self.batch_bar, pop=['batch', 'size'], + delta=lambda logs: logs.get('size', 1)) + + def on_train_begin(self, *_, **__): + params = self.params.get + auto_total = params('epochs', params('nb_epoch', None)) + if auto_total is not None and auto_total != self.epoch_bar.total: + self.epoch_bar.reset(total=auto_total) + + def on_epoch_begin(self, epoch, *_, **__): + if self.epoch_bar.n < epoch: + ebar = self.epoch_bar + ebar.n = ebar.last_print_n = ebar.initial = epoch + if self.verbose: + params = self.params.get + total = params('samples', params( + 'nb_sample', params('steps', None))) or self.batches + if self.verbose == 2: + if hasattr(self, 'batch_bar'): + self.batch_bar.close() + self.batch_bar = self.tqdm_class( + total=total, unit='batch', leave=True, + unit_scale=1 / (params('batch_size', 1) or 1)) + self.on_batch_end = self.bar2callback( + self.batch_bar, pop=['batch', 'size'], + delta=lambda logs: logs.get('size', 1)) + elif self.verbose == 1: + self.batch_bar.unit_scale = 1 / (params('batch_size', 1) or 1) + self.batch_bar.reset(total=total) + else: + raise KeyError('Unknown verbosity') + + def on_train_end(self, *_, **__): + if hasattr(self, 'batch_bar'): + self.batch_bar.close() + self.epoch_bar.close() + + def display(self): + """Displays in the current cell in Notebooks.""" + container = getattr(self.epoch_bar, 'container', None) + if container is None: + return + from .notebook import display + display(container) + batch_bar = getattr(self, 'batch_bar', None) + if batch_bar is not None: + display(batch_bar.container) + + @staticmethod + def _implements_train_batch_hooks(): + return True + + @staticmethod + def _implements_test_batch_hooks(): + return True + + @staticmethod + def _implements_predict_batch_hooks(): + return True diff --git a/venv/lib/python3.10/site-packages/tqdm/notebook.py b/venv/lib/python3.10/site-packages/tqdm/notebook.py new file mode 100644 index 0000000000000000000000000000000000000000..77b91bdd43183998fcb99e92dd4597ff7fc6c3fb --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/notebook.py @@ -0,0 +1,317 @@ +""" +IPython/Jupyter Notebook progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm.notebook import trange, tqdm +>>> for i in trange(10): +... ... +""" +# import compatibility functions and utilities +import re +import sys +from html import escape +from weakref import proxy + +# to inherit from the tqdm class +from .std import tqdm as std_tqdm + +if True: # pragma: no cover + # import IPython/Jupyter base widget and display utilities + IPY = 0 + try: # IPython 4.x + import ipywidgets + IPY = 4 + except ImportError: # IPython 3.x / 2.x + IPY = 32 + import warnings + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', message=".*The `IPython.html` package has been deprecated.*") + try: + import IPython.html.widgets as ipywidgets # NOQA: F401 + except ImportError: + pass + + try: # IPython 4.x / 3.x + if IPY == 32: + from IPython.html.widgets import HTML + from IPython.html.widgets import FloatProgress as IProgress + from IPython.html.widgets import HBox + IPY = 3 + else: + from ipywidgets import HTML + from ipywidgets import FloatProgress as IProgress + from ipywidgets import HBox + except ImportError: + try: # IPython 2.x + from IPython.html.widgets import HTML + from IPython.html.widgets import ContainerWidget as HBox + from IPython.html.widgets import FloatProgressWidget as IProgress + IPY = 2 + except ImportError: + IPY = 0 + IProgress = None + HBox = object + + try: + from IPython.display import display # , clear_output + except ImportError: + pass + +__author__ = {"github.com/": ["lrq3000", "casperdcl", "alexanderkuk"]} +__all__ = ['tqdm_notebook', 'tnrange', 'tqdm', 'trange'] +WARN_NOIPYW = ("IProgress not found. Please update jupyter and ipywidgets." + " See https://ipywidgets.readthedocs.io/en/stable" + "/user_install.html") + + +class TqdmHBox(HBox): + """`ipywidgets.HBox` with a pretty representation""" + def _json_(self, pretty=None): + pbar = getattr(self, 'pbar', None) + if pbar is None: + return {} + d = pbar.format_dict + if pretty is not None: + d["ascii"] = not pretty + return d + + def __repr__(self, pretty=False): + pbar = getattr(self, 'pbar', None) + if pbar is None: + return super().__repr__() + return pbar.format_meter(**self._json_(pretty)) + + def _repr_pretty_(self, pp, *_, **__): + pp.text(self.__repr__(True)) + + +class tqdm_notebook(std_tqdm): + """ + Experimental IPython/Jupyter Notebook widget using tqdm! + """ + @staticmethod + def status_printer(_, total=None, desc=None, ncols=None): + """ + Manage the printing of an IPython/Jupyter Notebook progress bar widget. + """ + # Fallback to text bar if there's no total + # DEPRECATED: replaced with an 'info' style bar + # if not total: + # return super(tqdm_notebook, tqdm_notebook).status_printer(file) + + # fp = file + + # Prepare IPython progress bar + if IProgress is None: # #187 #451 #558 #872 + raise ImportError(WARN_NOIPYW) + if total: + pbar = IProgress(min=0, max=total) + else: # No total? Show info style bar with no progress tqdm status + pbar = IProgress(min=0, max=1) + pbar.value = 1 + pbar.bar_style = 'info' + if ncols is None: + pbar.layout.width = "20px" + + ltext = HTML() + rtext = HTML() + if desc: + ltext.value = desc + container = TqdmHBox(children=[ltext, pbar, rtext]) + # Prepare layout + if ncols is not None: # use default style of ipywidgets + # ncols could be 100, "100px", "100%" + ncols = str(ncols) # ipywidgets only accepts string + try: + if int(ncols) > 0: # isnumeric and positive + ncols += 'px' + except ValueError: + pass + pbar.layout.flex = '2' + container.layout.width = ncols + container.layout.display = 'inline-flex' + container.layout.flex_flow = 'row wrap' + + return container + + def display(self, msg=None, pos=None, + # additional signals + close=False, bar_style=None, check_delay=True): + # Note: contrary to native tqdm, msg='' does NOT clear bar + # goal is to keep all infos if error happens so user knows + # at which iteration the loop failed. + + # Clear previous output (really necessary?) + # clear_output(wait=1) + + if not msg and not close: + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + + ltext, pbar, rtext = self.container.children + pbar.value = self.n + + if msg: + msg = msg.replace(' ', u'\u2007') # fix html space padding + # html escape special characters (like '&') + if '' in msg: + left, right = map(escape, re.split(r'\|?\|?', msg, maxsplit=1)) + else: + left, right = '', escape(msg) + + # Update description + ltext.value = left + # never clear the bar (signal: msg='') + if right: + rtext.value = right + + # Change bar style + if bar_style: + # Hack-ish way to avoid the danger bar_style being overridden by + # success because the bar gets closed after the error... + if pbar.bar_style != 'danger' or bar_style != 'success': + pbar.bar_style = bar_style + + # Special signal to close the bar + if close and pbar.bar_style != 'danger': # hide only if no error + try: + self.container.close() + except AttributeError: + self.container.visible = False + self.container.layout.visibility = 'hidden' # IPYW>=8 + + if check_delay and self.delay > 0 and not self.displayed: + display(self.container) + self.displayed = True + + @property + def colour(self): + if hasattr(self, 'container'): + return self.container.children[-2].style.bar_color + + @colour.setter + def colour(self, bar_color): + if hasattr(self, 'container'): + self.container.children[-2].style.bar_color = bar_color + + def __init__(self, *args, **kwargs): + """ + Supports the usual `tqdm.tqdm` parameters as well as those listed below. + + Parameters + ---------- + display : Whether to call `display(self.container)` immediately + [default: True]. + """ + kwargs = kwargs.copy() + # Setup default output + file_kwarg = kwargs.get('file', sys.stderr) + if file_kwarg is sys.stderr or file_kwarg is None: + kwargs['file'] = sys.stdout # avoid the red block in IPython + + # Initialize parent class + avoid printing by using gui=True + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + colour = kwargs.pop('colour', None) + display_here = kwargs.pop('display', True) + super().__init__(*args, **kwargs) + if self.disable or not kwargs['gui']: + self.disp = lambda *_, **__: None + return + + # Get bar width + self.ncols = '100%' if self.dynamic_ncols else kwargs.get("ncols", None) + + # Replace with IPython progress bar display (with correct total) + unit_scale = 1 if self.unit_scale is True else self.unit_scale or 1 + total = self.total * unit_scale if self.total else self.total + self.container = self.status_printer(self.fp, total, self.desc, self.ncols) + self.container.pbar = proxy(self) + self.displayed = False + if display_here and self.delay <= 0: + display(self.container) + self.displayed = True + self.disp = self.display + self.colour = colour + + # Print initial bar state + if not self.disable: + self.display(check_delay=False) + + def __iter__(self): + try: + it = super().__iter__() + for obj in it: + # return super(tqdm...) will not catch exception + yield obj + # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt + except: # NOQA + self.disp(bar_style='danger') + raise + # NB: don't `finally: close()` + # since this could be a shared bar which the user will `reset()` + + def update(self, n=1): + try: + return super().update(n=n) + # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt + except: # NOQA + # cannot catch KeyboardInterrupt when using manual tqdm + # as the interrupt will most likely happen on another statement + self.disp(bar_style='danger') + raise + # NB: don't `finally: close()` + # since this could be a shared bar which the user will `reset()` + + def close(self): + if self.disable: + return + super().close() + # Try to detect if there was an error or KeyboardInterrupt + # in manual mode: if n < total, things probably got wrong + if self.total and self.n < self.total: + self.disp(bar_style='danger', check_delay=False) + else: + if self.leave: + self.disp(bar_style='success', check_delay=False) + else: + self.disp(close=True, check_delay=False) + + def clear(self, *_, **__): + pass + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with `leave=True`. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if self.disable: + return super().reset(total=total) + _, pbar, _ = self.container.children + pbar.bar_style = '' + if total is not None: + pbar.max = total + if not self.total and self.ncols is None: # no longer unknown total + pbar.layout.width = None # reset width + return super().reset(total=total) + + +def tnrange(*args, **kwargs): + """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" + return tqdm_notebook(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_notebook +trange = tnrange diff --git a/venv/lib/python3.10/site-packages/tqdm/rich.py b/venv/lib/python3.10/site-packages/tqdm/rich.py new file mode 100644 index 0000000000000000000000000000000000000000..3d392edaf115a93f7c145de52cbe8978dcf1ede8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/rich.py @@ -0,0 +1,151 @@ +""" +`rich.progress` decorator for iterators. + +Usage: +>>> from tqdm.rich import trange, tqdm +>>> for i in trange(10): +... ... +""" +from warnings import warn + +from rich.progress import ( + BarColumn, Progress, ProgressColumn, Text, TimeElapsedColumn, TimeRemainingColumn, filesize) + +from .std import TqdmExperimentalWarning +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tqdm_rich', 'trrange', 'tqdm', 'trange'] + + +class FractionColumn(ProgressColumn): + """Renders completed/total, e.g. '0.5/2.3 G'.""" + def __init__(self, unit_scale=False, unit_divisor=1000): + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + super().__init__() + + def render(self, task): + """Calculate common unit for completed and total.""" + completed = int(task.completed) + total = int(task.total) + if self.unit_scale: + unit, suffix = filesize.pick_unit_and_suffix( + total, + ["", "K", "M", "G", "T", "P", "E", "Z", "Y"], + self.unit_divisor, + ) + else: + unit, suffix = filesize.pick_unit_and_suffix(total, [""], 1) + precision = 0 if unit == 1 else 1 + return Text( + f"{completed/unit:,.{precision}f}/{total/unit:,.{precision}f} {suffix}", + style="progress.download") + + +class RateColumn(ProgressColumn): + """Renders human readable transfer speed.""" + def __init__(self, unit="", unit_scale=False, unit_divisor=1000): + self.unit = unit + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + super().__init__() + + def render(self, task): + """Show data transfer speed.""" + speed = task.speed + if speed is None: + return Text(f"? {self.unit}/s", style="progress.data.speed") + if self.unit_scale: + unit, suffix = filesize.pick_unit_and_suffix( + speed, + ["", "K", "M", "G", "T", "P", "E", "Z", "Y"], + self.unit_divisor, + ) + else: + unit, suffix = filesize.pick_unit_and_suffix(speed, [""], 1) + precision = 0 if unit == 1 else 1 + return Text(f"{speed/unit:,.{precision}f} {suffix}{self.unit}/s", + style="progress.data.speed") + + +class tqdm_rich(std_tqdm): # pragma: no cover + """Experimental rich.progress GUI version of tqdm!""" + # TODO: @classmethod: write()? + def __init__(self, *args, **kwargs): + """ + This class accepts the following parameters *in addition* to + the parameters accepted by `tqdm`. + + Parameters + ---------- + progress : tuple, optional + arguments for `rich.progress.Progress()`. + options : dict, optional + keyword arguments for `rich.progress.Progress()`. + """ + kwargs = kwargs.copy() + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + progress = kwargs.pop('progress', None) + options = kwargs.pop('options', {}).copy() + super().__init__(*args, **kwargs) + + if self.disable: + return + + warn("rich is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + d = self.format_dict + if progress is None: + progress = ( + "[progress.description]{task.description}" + "[progress.percentage]{task.percentage:>4.0f}%", + BarColumn(bar_width=None), + FractionColumn( + unit_scale=d['unit_scale'], unit_divisor=d['unit_divisor']), + "[", TimeElapsedColumn(), "<", TimeRemainingColumn(), + ",", RateColumn(unit=d['unit'], unit_scale=d['unit_scale'], + unit_divisor=d['unit_divisor']), "]" + ) + options.setdefault('transient', not self.leave) + self._prog = Progress(*progress, **options) + self._prog.__enter__() + self._task_id = self._prog.add_task(self.desc or "", **d) + + def close(self): + if self.disable: + return + self.display() # print 100%, vis #1306 + super().close() + self._prog.__exit__(None, None, None) + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + if not hasattr(self, '_prog'): + return + self._prog.update(self._task_id, completed=self.n, description=self.desc) + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if hasattr(self, '_prog'): + self._prog.reset(total=total) + super().reset(total=total) + + +def trrange(*args, **kwargs): + """Shortcut for `tqdm.rich.tqdm(range(*args), **kwargs)`.""" + return tqdm_rich(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_rich +trange = trrange diff --git a/venv/lib/python3.10/site-packages/tqdm/std.py b/venv/lib/python3.10/site-packages/tqdm/std.py new file mode 100644 index 0000000000000000000000000000000000000000..e91ad3090392916fc2bc1e34bc471e43212fe699 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/std.py @@ -0,0 +1,1524 @@ +""" +Customisable progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm import trange, tqdm +>>> for i in trange(10): +... ... +""" +import sys +from collections import OrderedDict, defaultdict +from contextlib import contextmanager +from datetime import datetime, timedelta, timezone +from numbers import Number +from time import time +from warnings import warn +from weakref import WeakSet + +from ._monitor import TMonitor +from .utils import ( + CallbackIOWrapper, Comparable, DisableOnWriteError, FormatReplace, SimpleTextIOWrapper, + _is_ascii, _screen_shape_wrapper, _supports_unicode, _term_move_up, disp_len, disp_trim, + envwrap) + +__author__ = "https://github.com/tqdm/tqdm#contributions" +__all__ = ['tqdm', 'trange', + 'TqdmTypeError', 'TqdmKeyError', 'TqdmWarning', + 'TqdmExperimentalWarning', 'TqdmDeprecationWarning', + 'TqdmMonitorWarning'] + + +class TqdmTypeError(TypeError): + pass + + +class TqdmKeyError(KeyError): + pass + + +class TqdmWarning(Warning): + """base class for all tqdm warnings. + + Used for non-external-code-breaking errors, such as garbled printing. + """ + def __init__(self, msg, fp_write=None, *a, **k): + if fp_write is not None: + fp_write("\n" + self.__class__.__name__ + ": " + str(msg).rstrip() + '\n') + else: + super().__init__(msg, *a, **k) + + +class TqdmExperimentalWarning(TqdmWarning, FutureWarning): + """beta feature, unstable API and behaviour""" + pass + + +class TqdmDeprecationWarning(TqdmWarning, DeprecationWarning): + # not suppressed if raised + pass + + +class TqdmMonitorWarning(TqdmWarning, RuntimeWarning): + """tqdm monitor errors which do not affect external functionality""" + pass + + +def TRLock(*args, **kwargs): + """threading RLock""" + try: + from threading import RLock + return RLock(*args, **kwargs) + except (ImportError, OSError): # pragma: no cover + pass + + +class TqdmDefaultWriteLock(object): + """ + Provide a default write lock for thread and multiprocessing safety. + Works only on platforms supporting `fork` (so Windows is excluded). + You must initialise a `tqdm` or `TqdmDefaultWriteLock` instance + before forking in order for the write lock to work. + On Windows, you need to supply the lock from the parent to the children as + an argument to joblib or the parallelism lib you use. + """ + # global thread lock so no setup required for multithreading. + # NB: Do not create multiprocessing lock as it sets the multiprocessing + # context, disallowing `spawn()`/`forkserver()` + th_lock = TRLock() + + def __init__(self): + # Create global parallelism locks to avoid racing issues with parallel + # bars works only if fork available (Linux/MacOSX, but not Windows) + cls = type(self) + root_lock = cls.th_lock + if root_lock is not None: + root_lock.acquire() + cls.create_mp_lock() + self.locks = [lk for lk in [cls.mp_lock, cls.th_lock] if lk is not None] + if root_lock is not None: + root_lock.release() + + def acquire(self, *a, **k): + for lock in self.locks: + lock.acquire(*a, **k) + + def release(self): + for lock in self.locks[::-1]: # Release in inverse order of acquisition + lock.release() + + def __enter__(self): + self.acquire() + + def __exit__(self, *exc): + self.release() + + @classmethod + def create_mp_lock(cls): + if not hasattr(cls, 'mp_lock'): + try: + from multiprocessing import RLock + cls.mp_lock = RLock() + except (ImportError, OSError): # pragma: no cover + cls.mp_lock = None + + @classmethod + def create_th_lock(cls): + assert hasattr(cls, 'th_lock') + warn("create_th_lock not needed anymore", TqdmDeprecationWarning, stacklevel=2) + + +class Bar(object): + """ + `str.format`-able bar with format specifiers: `[width][type]` + + - `width` + + unspecified (default): use `self.default_len` + + `int >= 0`: overrides `self.default_len` + + `int < 0`: subtract from `self.default_len` + - `type` + + `a`: ascii (`charset=self.ASCII` override) + + `u`: unicode (`charset=self.UTF` override) + + `b`: blank (`charset=" "` override) + """ + ASCII = " 123456789#" + UTF = u" " + u''.join(map(chr, range(0x258F, 0x2587, -1))) + BLANK = " " + COLOUR_RESET = '\x1b[0m' + COLOUR_RGB = '\x1b[38;2;%d;%d;%dm' + COLOURS = {'BLACK': '\x1b[30m', 'RED': '\x1b[31m', 'GREEN': '\x1b[32m', + 'YELLOW': '\x1b[33m', 'BLUE': '\x1b[34m', 'MAGENTA': '\x1b[35m', + 'CYAN': '\x1b[36m', 'WHITE': '\x1b[37m'} + + def __init__(self, frac, default_len=10, charset=UTF, colour=None): + if not 0 <= frac <= 1: + warn("clamping frac to range [0, 1]", TqdmWarning, stacklevel=2) + frac = max(0, min(1, frac)) + assert default_len > 0 + self.frac = frac + self.default_len = default_len + self.charset = charset + self.colour = colour + + @property + def colour(self): + return self._colour + + @colour.setter + def colour(self, value): + if not value: + self._colour = None + return + try: + if value.upper() in self.COLOURS: + self._colour = self.COLOURS[value.upper()] + elif value[0] == '#' and len(value) == 7: + self._colour = self.COLOUR_RGB % tuple( + int(i, 16) for i in (value[1:3], value[3:5], value[5:7])) + else: + raise KeyError + except (KeyError, AttributeError): + warn("Unknown colour (%s); valid choices: [hex (#00ff00), %s]" % ( + value, ", ".join(self.COLOURS)), + TqdmWarning, stacklevel=2) + self._colour = None + + def __format__(self, format_spec): + if format_spec: + _type = format_spec[-1].lower() + try: + charset = {'a': self.ASCII, 'u': self.UTF, 'b': self.BLANK}[_type] + except KeyError: + charset = self.charset + else: + format_spec = format_spec[:-1] + if format_spec: + N_BARS = int(format_spec) + if N_BARS < 0: + N_BARS += self.default_len + else: + N_BARS = self.default_len + else: + charset = self.charset + N_BARS = self.default_len + + nsyms = len(charset) - 1 + bar_length, frac_bar_length = divmod(int(self.frac * N_BARS * nsyms), nsyms) + + res = charset[-1] * bar_length + if bar_length < N_BARS: # whitespace padding + res = res + charset[frac_bar_length] + charset[0] * (N_BARS - bar_length - 1) + return self.colour + res + self.COLOUR_RESET if self.colour else res + + +class EMA(object): + """ + Exponential moving average: smoothing to give progressively lower + weights to older values. + + Parameters + ---------- + smoothing : float, optional + Smoothing factor in range [0, 1], [default: 0.3]. + Increase to give more weight to recent values. + Ranges from 0 (yields old value) to 1 (yields new value). + """ + def __init__(self, smoothing=0.3): + self.alpha = smoothing + self.last = 0 + self.calls = 0 + + def __call__(self, x=None): + """ + Parameters + ---------- + x : float + New value to include in EMA. + """ + beta = 1 - self.alpha + if x is not None: + self.last = self.alpha * x + beta * self.last + self.calls += 1 + return self.last / (1 - beta ** self.calls) if self.calls else self.last + + +class tqdm(Comparable): + """ + Decorate an iterable object, returning an iterator which acts exactly + like the original iterable, but prints a dynamically updating + progressbar every time a value is requested. + + Parameters + ---------- + iterable : iterable, optional + Iterable to decorate with a progressbar. + Leave blank to manually manage the updates. + desc : str, optional + Prefix for the progressbar. + total : int or float, optional + The number of expected iterations. If unspecified, + len(iterable) is used if possible. If float("inf") or as a last + resort, only basic progress statistics are displayed + (no ETA, no progressbar). + If `gui` is True and this parameter needs subsequent updating, + specify an initial arbitrary large positive number, + e.g. 9e9. + leave : bool, optional + If [default: True], keeps all traces of the progressbar + upon termination of iteration. + If `None`, will leave only if `position` is `0`. + file : `io.TextIOWrapper` or `io.StringIO`, optional + Specifies where to output the progress messages + (default: sys.stderr). Uses `file.write(str)` and `file.flush()` + methods. For encoding, see `write_bytes`. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes the progressbar to stay within this bound. + If unspecified, attempts to use environment width. The + fallback is a meter width of 10 and no limit for the counter and + statistics. If 0, will not print any meter (only stats). + mininterval : float, optional + Minimum progress display update interval [default: 0.1] seconds. + maxinterval : float, optional + Maximum progress display update interval [default: 10] seconds. + Automatically adjusts `miniters` to correspond to `mininterval` + after long display update lag. Only works if `dynamic_miniters` + or monitor thread is enabled. + miniters : int or float, optional + Minimum progress display update interval, in iterations. + If 0 and `dynamic_miniters`, will automatically adjust to equal + `mininterval` (more CPU efficient, good for tight loops). + If > 0, will skip display of specified number of iterations. + Tweak this and `mininterval` to get very efficient loops. + If your progress is erratic with both fast and slow iterations + (network, skipping items, etc) you should set miniters=1. + ascii : bool or str, optional + If unspecified or False, use unicode (smooth blocks) to fill + the meter. The fallback is to use ASCII characters " 123456789#". + disable : bool, optional + Whether to disable the entire progressbar wrapper + [default: False]. If set to None, disable on non-TTY. + unit : str, optional + String that will be used to define the unit of each iteration + [default: it]. + unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be reduced/scaled + automatically and a metric prefix following the + International System of Units standard will be added + (kilo, mega, etc.) [default: False]. If any other non-zero + number, will scale `total` and `n`. + dynamic_ncols : bool, optional + If set, constantly alters `ncols` and `nrows` to the + environment (allowing for window resizes) [default: False]. + smoothing : float, optional + Exponential moving average smoothing factor for speed estimates + (ignored in GUI mode). Ranges from 0 (average speed) to 1 + (current/instantaneous speed) [default: 0.3]. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s, eta. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + initial : int or float, optional + The initial counter value. Useful when restarting a progress + bar [default: 0]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + position : int, optional + Specify the line offset to print this bar (starting from 0) + Automatic if unspecified. + Useful to manage multiple bars at once (eg, from threads). + postfix : dict or *, optional + Specify additional stats to display at the end of the bar. + Calls `set_postfix(**postfix)` if possible (dict). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + write_bytes : bool, optional + Whether to write bytes. If (default: False) will write unicode. + lock_args : tuple, optional + Passed to `refresh` for intermediate output + (initialisation, iterating, and updating). + nrows : int, optional + The screen height. If specified, hides nested bars outside this + bound. If unspecified, attempts to use environment height. + The fallback is 20. + colour : str, optional + Bar colour (e.g. 'green', '#00ff00'). + delay : float, optional + Don't display until [default: 0] seconds have elapsed. + gui : bool, optional + WARNING: internal parameter - do not use. + Use tqdm.gui.tqdm(...) instead. If set, will attempt to use + matplotlib animations for a graphical output [default: False]. + + Returns + ------- + out : decorated iterator. + """ + + monitor_interval = 10 # set to 0 to disable the thread + monitor = None + _instances = WeakSet() + + @staticmethod + def format_sizeof(num, suffix='', divisor=1000): + """ + Formats a number (greater than unity) with SI Order of Magnitude + prefixes. + + Parameters + ---------- + num : float + Number ( >= 1) to format. + suffix : str, optional + Post-postfix [default: '']. + divisor : float, optional + Divisor between prefixes [default: 1000]. + + Returns + ------- + out : str + Number with Order of Magnitude SI unit postfix. + """ + for unit in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']: + if abs(num) < 999.5: + if abs(num) < 99.95: + if abs(num) < 9.995: + return f'{num:1.2f}{unit}{suffix}' + return f'{num:2.1f}{unit}{suffix}' + return f'{num:3.0f}{unit}{suffix}' + num /= divisor + return f'{num:3.1f}Y{suffix}' + + @staticmethod + def format_interval(t): + """ + Formats a number of seconds as a clock time, [H:]MM:SS + + Parameters + ---------- + t : int + Number of seconds. + + Returns + ------- + out : str + [H:]MM:SS + """ + mins, s = divmod(int(t), 60) + h, m = divmod(mins, 60) + return f'{h:d}:{m:02d}:{s:02d}' if h else f'{m:02d}:{s:02d}' + + @staticmethod + def format_num(n): + """ + Intelligent scientific notation (.3g). + + Parameters + ---------- + n : int or float or Numeric + A Number. + + Returns + ------- + out : str + Formatted number. + """ + f = f'{n:.3g}'.replace('e+0', 'e+').replace('e-0', 'e-') + n = str(n) + return f if len(f) < len(n) else n + + @staticmethod + def status_printer(file): + """ + Manage the printing and in-place updating of a line of characters. + Note that if the string is longer than a line, then in-place + updating may not work (it will print a new line at each refresh). + """ + fp = file + fp_flush = getattr(fp, 'flush', lambda: None) # pragma: no cover + if fp in (sys.stderr, sys.stdout): + getattr(sys.stderr, 'flush', lambda: None)() + getattr(sys.stdout, 'flush', lambda: None)() + + def fp_write(s): + fp.write(str(s)) + fp_flush() + + last_len = [0] + + def print_status(s): + len_s = disp_len(s) + fp_write('\r' + s + (' ' * max(last_len[0] - len_s, 0))) + last_len[0] = len_s + + return print_status + + @staticmethod + def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False, unit='it', + unit_scale=False, rate=None, bar_format=None, postfix=None, + unit_divisor=1000, initial=0, colour=None, **extra_kwargs): + """ + Return a string-based progress bar given some parameters + + Parameters + ---------- + n : int or float + Number of finished iterations. + total : int or float + The expected total number of iterations. If meaningless (None), + only basic progress statistics are displayed (no ETA). + elapsed : float + Number of seconds passed since start. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes `{bar}` to stay within this bound + [default: None]. If `0`, will not print any bar (only stats). + The fallback is `{bar:10}`. + prefix : str, optional + Prefix message (included in total width) [default: '']. + Use as {desc} in bar_format string. + ascii : bool, optional or str, optional + If not set, use unicode (smooth blocks) to fill the meter + [default: False]. The fallback is to use ASCII characters + " 123456789#". + unit : str, optional + The iteration unit [default: 'it']. + unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be printed with an + appropriate SI metric prefix (k = 10^3, M = 10^6, etc.) + [default: False]. If any other non-zero number, will scale + `total` and `n`. + rate : float, optional + Manual override for iteration rate. + If [default: None], uses n/elapsed. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s, eta. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + postfix : *, optional + Similar to `prefix`, but placed at the end + (e.g. for additional stats). + Note: postfix is usually a string (not a dict) for this method, + and will if possible be set to postfix = ', ' + postfix. + However other types are supported (#382). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + initial : int or float, optional + The initial counter value [default: 0]. + colour : str, optional + Bar colour (e.g. 'green', '#00ff00'). + + Returns + ------- + out : Formatted meter and stats, ready to display. + """ + + # sanity check: total + if total and n >= (total + 0.5): # allow float imprecision (#849) + total = None + + # apply custom scale if necessary + if unit_scale and unit_scale not in (True, 1): + if total: + total *= unit_scale + n *= unit_scale + if rate: + rate *= unit_scale # by default rate = self.avg_dn / self.avg_dt + unit_scale = False + + elapsed_str = tqdm.format_interval(elapsed) + + # if unspecified, attempt to use rate = average speed + # (we allow manual override since predicting time is an arcane art) + if rate is None and elapsed: + rate = (n - initial) / elapsed + inv_rate = 1 / rate if rate else None + format_sizeof = tqdm.format_sizeof + rate_noinv_fmt = ((format_sizeof(rate) if unit_scale else f'{rate:5.2f}') + if rate else '?') + unit + '/s' + rate_inv_fmt = ( + (format_sizeof(inv_rate) if unit_scale else f'{inv_rate:5.2f}') + if inv_rate else '?') + 's/' + unit + rate_fmt = rate_inv_fmt if inv_rate and inv_rate > 1 else rate_noinv_fmt + + if unit_scale: + n_fmt = format_sizeof(n, divisor=unit_divisor) + total_fmt = format_sizeof(total, divisor=unit_divisor) if total is not None else '?' + else: + n_fmt = str(n) + total_fmt = str(total) if total is not None else '?' + + try: + postfix = ', ' + postfix if postfix else '' + except TypeError: + pass + + remaining = (total - n) / rate if rate and total else 0 + remaining_str = tqdm.format_interval(remaining) if rate else '?' + try: + eta_dt = (datetime.now() + timedelta(seconds=remaining) + if rate and total else datetime.fromtimestamp(0, timezone.utc)) + except OverflowError: + eta_dt = datetime.max + + # format the stats displayed to the left and right sides of the bar + if prefix: + # old prefix setup work around + bool_prefix_colon_already = (prefix[-2:] == ": ") + l_bar = prefix if bool_prefix_colon_already else prefix + ": " + else: + l_bar = '' + + r_bar = f'| {n_fmt}/{total_fmt} [{elapsed_str}<{remaining_str}, {rate_fmt}{postfix}]' + + # Custom bar formatting + # Populate a dict with all available progress indicators + format_dict = { + # slight extension of self.format_dict + 'n': n, 'n_fmt': n_fmt, 'total': total, 'total_fmt': total_fmt, + 'elapsed': elapsed_str, 'elapsed_s': elapsed, + 'ncols': ncols, 'desc': prefix or '', 'unit': unit, + 'rate': inv_rate if inv_rate and inv_rate > 1 else rate, + 'rate_fmt': rate_fmt, 'rate_noinv': rate, + 'rate_noinv_fmt': rate_noinv_fmt, 'rate_inv': inv_rate, + 'rate_inv_fmt': rate_inv_fmt, + 'postfix': postfix, 'unit_divisor': unit_divisor, + 'colour': colour, + # plus more useful definitions + 'remaining': remaining_str, 'remaining_s': remaining, + 'l_bar': l_bar, 'r_bar': r_bar, 'eta': eta_dt, + **extra_kwargs} + + # total is known: we can predict some stats + if total: + # fractional and percentage progress + frac = n / total + percentage = frac * 100 + + l_bar += f'{percentage:3.0f}%|' + + if ncols == 0: + return l_bar[:-1] + r_bar[1:] + + format_dict.update(l_bar=l_bar) + if bar_format: + format_dict.update(percentage=percentage) + + # auto-remove colon for empty `{desc}` + if not prefix: + bar_format = bar_format.replace("{desc}: ", '') + else: + bar_format = "{l_bar}{bar}{r_bar}" + + full_bar = FormatReplace() + nobar = bar_format.format(bar=full_bar, **format_dict) + if not full_bar.format_called: + return nobar # no `{bar}`; nothing else to do + + # Formatting progress bar space available for bar's display + full_bar = Bar(frac, + max(1, ncols - disp_len(nobar)) if ncols else 10, + charset=Bar.ASCII if ascii is True else ascii or Bar.UTF, + colour=colour) + if not _is_ascii(full_bar.charset) and _is_ascii(bar_format): + bar_format = str(bar_format) + res = bar_format.format(bar=full_bar, **format_dict) + return disp_trim(res, ncols) if ncols else res + + elif bar_format: + # user-specified bar_format but no total + l_bar += '|' + format_dict.update(l_bar=l_bar, percentage=0) + full_bar = FormatReplace() + nobar = bar_format.format(bar=full_bar, **format_dict) + if not full_bar.format_called: + return nobar + full_bar = Bar(0, + max(1, ncols - disp_len(nobar)) if ncols else 10, + charset=Bar.BLANK, colour=colour) + res = bar_format.format(bar=full_bar, **format_dict) + return disp_trim(res, ncols) if ncols else res + else: + # no total: no progressbar, ETA, just progress stats + return (f'{(prefix + ": ") if prefix else ""}' + f'{n_fmt}{unit} [{elapsed_str}, {rate_fmt}{postfix}]') + + def __new__(cls, *_, **__): + instance = object.__new__(cls) + with cls.get_lock(): # also constructs lock if non-existent + cls._instances.add(instance) + # create monitoring thread + if cls.monitor_interval and (cls.monitor is None + or not cls.monitor.report()): + try: + cls.monitor = TMonitor(cls, cls.monitor_interval) + except Exception as e: # pragma: nocover + warn("tqdm:disabling monitor support" + " (monitor_interval = 0) due to:\n" + str(e), + TqdmMonitorWarning, stacklevel=2) + cls.monitor_interval = 0 + return instance + + @classmethod + def _get_free_pos(cls, instance=None): + """Skips specified instance.""" + positions = {abs(inst.pos) for inst in cls._instances + if inst is not instance and hasattr(inst, "pos")} + return min(set(range(len(positions) + 1)).difference(positions)) + + @classmethod + def _decr_instances(cls, instance): + """ + Remove from list and reposition another unfixed bar + to fill the new gap. + + This means that by default (where all nested bars are unfixed), + order is not maintained but screen flicker/blank space is minimised. + (tqdm<=4.44.1 moved ALL subsequent unfixed bars up.) + """ + with cls._lock: + try: + cls._instances.remove(instance) + except KeyError: + # if not instance.gui: # pragma: no cover + # raise + pass # py2: maybe magically removed already + # else: + if not instance.gui: + last = (instance.nrows or 20) - 1 + # find unfixed (`pos >= 0`) overflow (`pos >= nrows - 1`) + instances = list(filter( + lambda i: hasattr(i, "pos") and last <= i.pos, + cls._instances)) + # set first found to current `pos` + if instances: + inst = min(instances, key=lambda i: i.pos) + inst.clear(nolock=True) + inst.pos = abs(instance.pos) + + @classmethod + def write(cls, s, file=None, end="\n", nolock=False): + """Print a message via tqdm (without overlap with bars).""" + fp = file if file is not None else sys.stdout + with cls.external_write_mode(file=file, nolock=nolock): + # Write the message + fp.write(s) + fp.write(end) + + @classmethod + @contextmanager + def external_write_mode(cls, file=None, nolock=False): + """ + Disable tqdm within context and refresh tqdm when exits. + Useful when writing to standard output stream + """ + fp = file if file is not None else sys.stdout + + try: + if not nolock: + cls.get_lock().acquire() + # Clear all bars + inst_cleared = [] + for inst in getattr(cls, '_instances', []): + # Clear instance if in the target output file + # or if write output + tqdm output are both either + # sys.stdout or sys.stderr (because both are mixed in terminal) + if hasattr(inst, "start_t") and (inst.fp == fp or all( + f in (sys.stdout, sys.stderr) for f in (fp, inst.fp))): + inst.clear(nolock=True) + inst_cleared.append(inst) + yield + # Force refresh display of bars we cleared + for inst in inst_cleared: + inst.refresh(nolock=True) + finally: + if not nolock: + cls._lock.release() + + @classmethod + def set_lock(cls, lock): + """Set the global lock.""" + cls._lock = lock + + @classmethod + def get_lock(cls): + """Get the global lock. Construct it if it does not exist.""" + if not hasattr(cls, '_lock'): + cls._lock = TqdmDefaultWriteLock() + return cls._lock + + @classmethod + def pandas(cls, **tqdm_kwargs): + """ + Registers the current `tqdm` class with + pandas.core. + ( frame.DataFrame + | series.Series + | groupby.(generic.)DataFrameGroupBy + | groupby.(generic.)SeriesGroupBy + ).progress_apply + + A new instance will be created every time `progress_apply` is called, + and each instance will automatically `close()` upon completion. + + Parameters + ---------- + tqdm_kwargs : arguments for the tqdm instance + + Examples + -------- + >>> import pandas as pd + >>> import numpy as np + >>> from tqdm import tqdm + >>> from tqdm.gui import tqdm as tqdm_gui + >>> + >>> df = pd.DataFrame(np.random.randint(0, 100, (100000, 6))) + >>> tqdm.pandas(ncols=50) # can use tqdm_gui, optional kwargs, etc + >>> # Now you can use `progress_apply` instead of `apply` + >>> df.groupby(0).progress_apply(lambda x: x**2) + + References + ---------- + + """ + from warnings import catch_warnings, simplefilter + + from pandas.core.frame import DataFrame + from pandas.core.series import Series + try: + with catch_warnings(): + simplefilter("ignore", category=FutureWarning) + from pandas import Panel + except ImportError: # pandas>=1.2.0 + Panel = None + Rolling, Expanding = None, None + try: # pandas>=1.0.0 + from pandas.core.window.rolling import _Rolling_and_Expanding + except ImportError: + try: # pandas>=0.18.0 + from pandas.core.window import _Rolling_and_Expanding + except ImportError: # pandas>=1.2.0 + try: # pandas>=1.2.0 + from pandas.core.window.expanding import Expanding + from pandas.core.window.rolling import Rolling + _Rolling_and_Expanding = Rolling, Expanding + except ImportError: # pragma: no cover + _Rolling_and_Expanding = None + try: # pandas>=0.25.0 + from pandas.core.groupby.generic import SeriesGroupBy # , NDFrameGroupBy + from pandas.core.groupby.generic import DataFrameGroupBy + except ImportError: # pragma: no cover + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import DataFrameGroupBy, SeriesGroupBy + except ImportError: + from pandas.core.groupby import DataFrameGroupBy, SeriesGroupBy + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import GroupBy + except ImportError: # pragma: no cover + from pandas.core.groupby import GroupBy + + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import PanelGroupBy + except ImportError: + try: + from pandas.core.groupby import PanelGroupBy + except ImportError: # pandas>=0.25.0 + PanelGroupBy = None + + tqdm_kwargs = tqdm_kwargs.copy() + deprecated_t = [tqdm_kwargs.pop('deprecated_t', None)] + + def inner_generator(df_function='apply'): + def inner(df, func, *args, **kwargs): + """ + Parameters + ---------- + df : (DataFrame|Series)[GroupBy] + Data (may be grouped). + func : function + To be applied on the (grouped) data. + **kwargs : optional + Transmitted to `df.apply()`. + """ + + # Precompute total iterations + total = tqdm_kwargs.pop("total", getattr(df, 'ngroups', None)) + if total is None: # not grouped + if df_function == 'applymap': + total = df.size + elif isinstance(df, Series): + total = len(df) + elif (_Rolling_and_Expanding is None or + not isinstance(df, _Rolling_and_Expanding)): + # DataFrame or Panel + axis = kwargs.get('axis', 0) + if axis == 'index': + axis = 0 + elif axis == 'columns': + axis = 1 + # when axis=0, total is shape[axis1] + total = df.size // df.shape[axis] + + # Init bar + if deprecated_t[0] is not None: + t = deprecated_t[0] + deprecated_t[0] = None + else: + t = cls(total=total, **tqdm_kwargs) + + if len(args) > 0: + # *args intentionally not supported (see #244, #299) + TqdmDeprecationWarning( + "Except func, normal arguments are intentionally" + + " not supported by" + + " `(DataFrame|Series|GroupBy).progress_apply`." + + " Use keyword arguments instead.", + fp_write=getattr(t.fp, 'write', sys.stderr.write)) + + try: # pandas>=1.3.0 + from pandas.core.common import is_builtin_func + except ImportError: + is_builtin_func = df._is_builtin_func + try: + func = is_builtin_func(func) + except TypeError: + pass + + # Define bar updating wrapper + def wrapper(*args, **kwargs): + # update tbar correctly + # it seems `pandas apply` calls `func` twice + # on the first column/row to decide whether it can + # take a fast or slow code path; so stop when t.total==t.n + t.update(n=1 if not t.total or t.n < t.total else 0) + return func(*args, **kwargs) + + # Apply the provided function (in **kwargs) + # on the df using our wrapper (which provides bar updating) + try: + return getattr(df, df_function)(wrapper, **kwargs) + finally: + t.close() + + return inner + + # Monkeypatch pandas to provide easy methods + # Enable custom tqdm progress in pandas! + Series.progress_apply = inner_generator() + SeriesGroupBy.progress_apply = inner_generator() + Series.progress_map = inner_generator('map') + SeriesGroupBy.progress_map = inner_generator('map') + + DataFrame.progress_apply = inner_generator() + DataFrameGroupBy.progress_apply = inner_generator() + DataFrame.progress_applymap = inner_generator('applymap') + DataFrame.progress_map = inner_generator('map') + DataFrameGroupBy.progress_map = inner_generator('map') + + if Panel is not None: + Panel.progress_apply = inner_generator() + if PanelGroupBy is not None: + PanelGroupBy.progress_apply = inner_generator() + + GroupBy.progress_apply = inner_generator() + GroupBy.progress_aggregate = inner_generator('aggregate') + GroupBy.progress_transform = inner_generator('transform') + + if Rolling is not None and Expanding is not None: + Rolling.progress_apply = inner_generator() + Expanding.progress_apply = inner_generator() + elif _Rolling_and_Expanding is not None: + _Rolling_and_Expanding.progress_apply = inner_generator() + + # override defaults via env vars + @envwrap("TQDM_", is_method=True, types={'total': float, 'ncols': int, 'miniters': float, + 'position': int, 'nrows': int}) + def __init__(self, iterable=None, desc=None, total=None, leave=True, file=None, + ncols=None, mininterval=0.1, maxinterval=10.0, miniters=None, + ascii=None, disable=False, unit='it', unit_scale=False, + dynamic_ncols=False, smoothing=0.3, bar_format=None, initial=0, + position=None, postfix=None, unit_divisor=1000, write_bytes=False, + lock_args=None, nrows=None, colour=None, delay=0.0, gui=False, + **kwargs): + """see tqdm.tqdm for arguments""" + if file is None: + file = sys.stderr + + if write_bytes: + # Despite coercing unicode into bytes, py2 sys.std* streams + # should have bytes written to them. + file = SimpleTextIOWrapper( + file, encoding=getattr(file, 'encoding', None) or 'utf-8') + + file = DisableOnWriteError(file, tqdm_instance=self) + + if disable is None and hasattr(file, "isatty") and not file.isatty(): + disable = True + + if total is None and iterable is not None: + try: + total = len(iterable) + except (TypeError, AttributeError): + total = None + if total == float("inf"): + # Infinite iterations, behave same as unknown + total = None + + if disable: + self.iterable = iterable + self.disable = disable + with self._lock: + self.pos = self._get_free_pos(self) + self._instances.remove(self) + self.n = initial + self.total = total + self.leave = leave + return + + if kwargs: + self.disable = True + with self._lock: + self.pos = self._get_free_pos(self) + self._instances.remove(self) + raise ( + TqdmDeprecationWarning( + "`nested` is deprecated and automated.\n" + "Use `position` instead for manual control.\n", + fp_write=getattr(file, 'write', sys.stderr.write)) + if "nested" in kwargs else + TqdmKeyError("Unknown argument(s): " + str(kwargs))) + + # Preprocess the arguments + if ( + (ncols is None or nrows is None) and (file in (sys.stderr, sys.stdout)) + ) or dynamic_ncols: # pragma: no cover + if dynamic_ncols: + dynamic_ncols = _screen_shape_wrapper() + if dynamic_ncols: + ncols, nrows = dynamic_ncols(file) + else: + _dynamic_ncols = _screen_shape_wrapper() + if _dynamic_ncols: + _ncols, _nrows = _dynamic_ncols(file) + if ncols is None: + ncols = _ncols + if nrows is None: + nrows = _nrows + + if miniters is None: + miniters = 0 + dynamic_miniters = True + else: + dynamic_miniters = False + + if mininterval is None: + mininterval = 0 + + if maxinterval is None: + maxinterval = 0 + + if ascii is None: + ascii = not _supports_unicode(file) + + if bar_format and ascii is not True and not _is_ascii(ascii): + # Convert bar format into unicode since terminal uses unicode + bar_format = str(bar_format) + + if smoothing is None: + smoothing = 0 + + # Store the arguments + self.iterable = iterable + self.desc = desc or '' + self.total = total + self.leave = leave + self.fp = file + self.ncols = ncols + self.nrows = nrows + self.mininterval = mininterval + self.maxinterval = maxinterval + self.miniters = miniters + self.dynamic_miniters = dynamic_miniters + self.ascii = ascii + self.disable = disable + self.unit = unit + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + self.initial = initial + self.lock_args = lock_args + self.delay = delay + self.gui = gui + self.dynamic_ncols = dynamic_ncols + self.smoothing = smoothing + self._ema_dn = EMA(smoothing) + self._ema_dt = EMA(smoothing) + self._ema_miniters = EMA(smoothing) + self.bar_format = bar_format + self.postfix = None + self.colour = colour + self._time = time + if postfix: + try: + self.set_postfix(refresh=False, **postfix) + except TypeError: + self.postfix = postfix + + # Init the iterations counters + self.last_print_n = initial + self.n = initial + + # if nested, at initial sp() call we replace '\r' by '\n' to + # not overwrite the outer progress bar + with self._lock: + # mark fixed positions as negative + self.pos = self._get_free_pos(self) if position is None else -position + + if not gui: + # Initialize the screen printer + self.sp = self.status_printer(self.fp) + if delay <= 0: + self.refresh(lock_args=self.lock_args) + + # Init the time counter + self.last_print_t = self._time() + # NB: Avoid race conditions by setting start_t at the very end of init + self.start_t = self.last_print_t + + def __bool__(self): + if self.total is not None: + return self.total > 0 + if self.iterable is None: + raise TypeError('bool() undefined when iterable == total == None') + return bool(self.iterable) + + def __len__(self): + return ( + self.total if self.iterable is None + else self.iterable.shape[0] if hasattr(self.iterable, "shape") + else len(self.iterable) if hasattr(self.iterable, "__len__") + else self.iterable.__length_hint__() if hasattr(self.iterable, "__length_hint__") + else getattr(self, "total", None)) + + def __reversed__(self): + try: + orig = self.iterable + except AttributeError: + raise TypeError("'tqdm' object is not reversible") + else: + self.iterable = reversed(self.iterable) + return self.__iter__() + finally: + self.iterable = orig + + def __contains__(self, item): + contains = getattr(self.iterable, '__contains__', None) + return contains(item) if contains is not None else item in self.__iter__() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + try: + self.close() + except AttributeError: + # maybe eager thread cleanup upon external error + if (exc_type, exc_value, traceback) == (None, None, None): + raise + warn("AttributeError ignored", TqdmWarning, stacklevel=2) + + def __del__(self): + self.close() + + def __str__(self): + return self.format_meter(**self.format_dict) + + @property + def _comparable(self): + return abs(getattr(self, "pos", 1 << 31)) + + def __hash__(self): + return id(self) + + def __iter__(self): + """Backward-compatibility to use: for x in tqdm(iterable)""" + + # Inlining instance variables as locals (speed optimisation) + iterable = self.iterable + + # If the bar is disabled, then just walk the iterable + # (note: keep this check outside the loop for performance) + if self.disable: + for obj in iterable: + yield obj + return + + mininterval = self.mininterval + last_print_t = self.last_print_t + last_print_n = self.last_print_n + min_start_t = self.start_t + self.delay + n = self.n + time = self._time + + try: + for obj in iterable: + yield obj + # Update and possibly print the progressbar. + # Note: does not call self.update(1) for speed optimisation. + n += 1 + + if n - last_print_n >= self.miniters: + cur_t = time() + dt = cur_t - last_print_t + if dt >= mininterval and cur_t >= min_start_t: + self.update(n - last_print_n) + last_print_n = self.last_print_n + last_print_t = self.last_print_t + finally: + self.n = n + self.close() + + def update(self, n=1): + """ + Manually update the progress bar, useful for streams + such as reading files. + E.g.: + >>> t = tqdm(total=filesize) # Initialise + >>> for current_buffer in stream: + ... ... + ... t.update(len(current_buffer)) + >>> t.close() + The last line is highly recommended, but possibly not necessary if + `t.update()` will be called in such a way that `filesize` will be + exactly reached and printed. + + Parameters + ---------- + n : int or float, optional + Increment to add to the internal counter of iterations + [default: 1]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + + Returns + ------- + out : bool or None + True if a `display()` was triggered. + """ + if self.disable: + return + + if n < 0: + self.last_print_n += n # for auto-refresh logic to work + self.n += n + + # check counter first to reduce calls to time() + if self.n - self.last_print_n >= self.miniters: + cur_t = self._time() + dt = cur_t - self.last_print_t + if dt >= self.mininterval and cur_t >= self.start_t + self.delay: + cur_t = self._time() + dn = self.n - self.last_print_n # >= n + if self.smoothing and dt and dn: + # EMA (not just overall average) + self._ema_dn(dn) + self._ema_dt(dt) + self.refresh(lock_args=self.lock_args) + if self.dynamic_miniters: + # If no `miniters` was specified, adjust automatically to the + # maximum iteration rate seen so far between two prints. + # e.g.: After running `tqdm.update(5)`, subsequent + # calls to `tqdm.update()` will only cause an update after + # at least 5 more iterations. + if self.maxinterval and dt >= self.maxinterval: + self.miniters = dn * (self.mininterval or self.maxinterval) / dt + elif self.smoothing: + # EMA miniters update + self.miniters = self._ema_miniters( + dn * (self.mininterval / dt if self.mininterval and dt + else 1)) + else: + # max iters between two prints + self.miniters = max(self.miniters, dn) + + # Store old values for next call + self.last_print_n = self.n + self.last_print_t = cur_t + return True + + def close(self): + """Cleanup and (if leave=False) close the progressbar.""" + if self.disable: + return + + # Prevent multiple closures + self.disable = True + + # decrement instance pos and remove from internal set + pos = abs(self.pos) + self._decr_instances(self) + + if self.last_print_t < self.start_t + self.delay: + # haven't ever displayed; nothing to clear + return + + # GUI mode + if getattr(self, 'sp', None) is None: + return + + # annoyingly, _supports_unicode isn't good enough + def fp_write(s): + self.fp.write(str(s)) + + try: + fp_write('') + except ValueError as e: + if 'closed' in str(e): + return + raise # pragma: no cover + + leave = pos == 0 if self.leave is None else self.leave + + with self._lock: + if leave: + # stats for overall rate (no weighted average) + self._ema_dt = lambda: None + self.display(pos=0) + fp_write('\n') + else: + # clear previous display + if self.display(msg='', pos=pos) and not pos: + fp_write('\r') + + def clear(self, nolock=False): + """Clear current bar display.""" + if self.disable: + return + + if not nolock: + self._lock.acquire() + pos = abs(self.pos) + if pos < (self.nrows or 20): + self.moveto(pos) + self.sp('') + self.fp.write('\r') # place cursor back at the beginning of line + self.moveto(-pos) + if not nolock: + self._lock.release() + + def refresh(self, nolock=False, lock_args=None): + """ + Force refresh the display of this bar. + + Parameters + ---------- + nolock : bool, optional + If `True`, does not lock. + If [default: `False`]: calls `acquire()` on internal lock. + lock_args : tuple, optional + Passed to internal lock's `acquire()`. + If specified, will only `display()` if `acquire()` returns `True`. + """ + if self.disable: + return + + if not nolock: + if lock_args: + if not self._lock.acquire(*lock_args): + return False + else: + self._lock.acquire() + self.display() + if not nolock: + self._lock.release() + return True + + def unpause(self): + """Restart tqdm timer from last print time.""" + if self.disable: + return + cur_t = self._time() + self.start_t += cur_t - self.last_print_t + self.last_print_t = cur_t + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with `leave=True`. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + self.n = 0 + if total is not None: + self.total = total + if self.disable: + return + self.last_print_n = 0 + self.last_print_t = self.start_t = self._time() + self._ema_dn = EMA(self.smoothing) + self._ema_dt = EMA(self.smoothing) + self._ema_miniters = EMA(self.smoothing) + self.refresh() + + def set_description(self, desc=None, refresh=True): + """ + Set/modify description of the progress bar. + + Parameters + ---------- + desc : str, optional + refresh : bool, optional + Forces refresh [default: True]. + """ + self.desc = desc + ': ' if desc else '' + if refresh: + self.refresh() + + def set_description_str(self, desc=None, refresh=True): + """Set/modify description without ': ' appended.""" + self.desc = desc or '' + if refresh: + self.refresh() + + def set_postfix(self, ordered_dict=None, refresh=True, **kwargs): + """ + Set/modify postfix (additional stats) + with automatic formatting based on datatype. + + Parameters + ---------- + ordered_dict : dict or OrderedDict, optional + refresh : bool, optional + Forces refresh [default: True]. + kwargs : dict, optional + """ + # Sort in alphabetical order to be more deterministic + postfix = OrderedDict([] if ordered_dict is None else ordered_dict) + for key in sorted(kwargs.keys()): + postfix[key] = kwargs[key] + # Preprocess stats according to datatype + for key in postfix.keys(): + # Number: limit the length of the string + if isinstance(postfix[key], Number): + postfix[key] = self.format_num(postfix[key]) + # Else for any other type, try to get the string conversion + elif not isinstance(postfix[key], str): + postfix[key] = str(postfix[key]) + # Else if it's a string, don't need to preprocess anything + # Stitch together to get the final postfix + self.postfix = ', '.join(key + '=' + postfix[key].strip() + for key in postfix.keys()) + if refresh: + self.refresh() + + def set_postfix_str(self, s='', refresh=True): + """ + Postfix without dictionary expansion, similar to prefix handling. + """ + self.postfix = str(s) + if refresh: + self.refresh() + + def moveto(self, n): + # TODO: private method + self.fp.write('\n' * n + _term_move_up() * -n) + getattr(self.fp, 'flush', lambda: None)() + + @property + def format_dict(self): + """Public API for read-only member access.""" + if self.disable and not hasattr(self, 'unit'): + return defaultdict(lambda: None, { + 'n': self.n, 'total': self.total, 'elapsed': 0, 'unit': 'it'}) + if self.dynamic_ncols: + self.ncols, self.nrows = self.dynamic_ncols(self.fp) + return { + 'n': self.n, 'total': self.total, + 'elapsed': self._time() - self.start_t if hasattr(self, 'start_t') else 0, + 'ncols': self.ncols, 'nrows': self.nrows, 'prefix': self.desc, + 'ascii': self.ascii, 'unit': self.unit, 'unit_scale': self.unit_scale, + 'rate': self._ema_dn() / self._ema_dt() if self._ema_dt() else None, + 'bar_format': self.bar_format, 'postfix': self.postfix, + 'unit_divisor': self.unit_divisor, 'initial': self.initial, + 'colour': self.colour} + + def display(self, msg=None, pos=None): + """ + Use `self.sp` to display `msg` in the specified `pos`. + + Consider overloading this function when inheriting to use e.g.: + `self.some_frontend(**self.format_dict)` instead of `self.sp`. + + Parameters + ---------- + msg : str, optional. What to display (default: `repr(self)`). + pos : int, optional. Position to `moveto` + (default: `abs(self.pos)`). + """ + if pos is None: + pos = abs(self.pos) + + nrows = self.nrows or 20 + if pos >= nrows - 1: + if pos >= nrows: + return False + if msg or msg is None: # override at `nrows - 1` + msg = " ... (more hidden) ..." + + if not hasattr(self, "sp"): + raise TqdmDeprecationWarning( + "Please use `tqdm.gui.tqdm(...)`" + " instead of `tqdm(..., gui=True)`\n", + fp_write=getattr(self.fp, 'write', sys.stderr.write)) + + if pos: + self.moveto(pos) + self.sp(self.__str__() if msg is None else msg) + if pos: + self.moveto(-pos) + return True + + @classmethod + @contextmanager + def wrapattr(cls, stream, method, total=None, bytes=True, **tqdm_kwargs): + """ + stream : file-like object. + method : str, "read" or "write". The result of `read()` and + the first argument of `write()` should have a `len()`. + + >>> with tqdm.wrapattr(file_obj, "read", total=file_obj.size) as fobj: + ... while True: + ... chunk = fobj.read(chunk_size) + ... if not chunk: + ... break + """ + with cls(total=total, **tqdm_kwargs) as t: + if bytes: + t.unit = "B" + t.unit_scale = True + t.unit_divisor = 1024 + yield CallbackIOWrapper(t.update, stream, method) + + +def trange(*args, **kwargs): + """Shortcut for tqdm(range(*args), **kwargs).""" + return tqdm(range(*args), **kwargs) diff --git a/venv/lib/python3.10/site-packages/tqdm/tk.py b/venv/lib/python3.10/site-packages/tqdm/tk.py new file mode 100644 index 0000000000000000000000000000000000000000..788303c8687e007338ce816bf9afeec8581f0188 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/tk.py @@ -0,0 +1,196 @@ +""" +Tkinter GUI progressbar decorator for iterators. + +Usage: +>>> from tqdm.tk import trange, tqdm +>>> for i in trange(10): +... ... +""" +import re +import sys +import tkinter +import tkinter.ttk as ttk +from warnings import warn + +from .std import TqdmExperimentalWarning, TqdmWarning +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["richardsheridan", "casperdcl"]} +__all__ = ['tqdm_tk', 'ttkrange', 'tqdm', 'trange'] + + +class tqdm_tk(std_tqdm): # pragma: no cover + """ + Experimental Tkinter GUI version of tqdm! + + Note: Window interactivity suffers if `tqdm_tk` is not running within + a Tkinter mainloop and values are generated infrequently. In this case, + consider calling `tqdm_tk.refresh()` frequently in the Tk thread. + """ + + # TODO: @classmethod: write()? + + def __init__(self, *args, **kwargs): + """ + This class accepts the following parameters *in addition* to + the parameters accepted by `tqdm`. + + Parameters + ---------- + grab : bool, optional + Grab the input across all windows of the process. + tk_parent : `tkinter.Wm`, optional + Parent Tk window. + cancel_callback : Callable, optional + Create a cancel button and set `cancel_callback` to be called + when the cancel or window close button is clicked. + """ + kwargs = kwargs.copy() + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + self._warn_leave = 'leave' in kwargs + grab = kwargs.pop('grab', False) + tk_parent = kwargs.pop('tk_parent', None) + self._cancel_callback = kwargs.pop('cancel_callback', None) + super().__init__(*args, **kwargs) + + if self.disable: + return + + if tk_parent is None: # Discover parent widget + try: + tk_parent = tkinter._default_root + except AttributeError: + raise AttributeError( + "`tk_parent` required when using `tkinter.NoDefaultRoot()`") + if tk_parent is None: # use new default root window as display + self._tk_window = tkinter.Tk() + else: # some other windows already exist + self._tk_window = tkinter.Toplevel() + else: + self._tk_window = tkinter.Toplevel(tk_parent) + + warn("GUI is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + self._tk_dispatching = self._tk_dispatching_helper() + + self._tk_window.protocol("WM_DELETE_WINDOW", self.cancel) + self._tk_window.wm_title(self.desc) + self._tk_window.wm_attributes("-topmost", 1) + self._tk_window.after(0, lambda: self._tk_window.wm_attributes("-topmost", 0)) + self._tk_n_var = tkinter.DoubleVar(self._tk_window, value=0) + self._tk_text_var = tkinter.StringVar(self._tk_window) + pbar_frame = ttk.Frame(self._tk_window, padding=5) + pbar_frame.pack() + _tk_label = ttk.Label(pbar_frame, textvariable=self._tk_text_var, + wraplength=600, anchor="center", justify="center") + _tk_label.pack() + self._tk_pbar = ttk.Progressbar( + pbar_frame, variable=self._tk_n_var, length=450) + if self.total is not None: + self._tk_pbar.configure(maximum=self.total) + else: + self._tk_pbar.configure(mode="indeterminate") + self._tk_pbar.pack() + if self._cancel_callback is not None: + _tk_button = ttk.Button(pbar_frame, text="Cancel", command=self.cancel) + _tk_button.pack() + if grab: + self._tk_window.grab_set() + + def close(self): + if self.disable: + return + + self.disable = True + + with self.get_lock(): + self._instances.remove(self) + + def _close(): + self._tk_window.after('idle', self._tk_window.destroy) + if not self._tk_dispatching: + self._tk_window.update() + + self._tk_window.protocol("WM_DELETE_WINDOW", _close) + + # if leave is set but we are self-dispatching, the left window is + # totally unresponsive unless the user manually dispatches + if not self.leave: + _close() + elif not self._tk_dispatching: + if self._warn_leave: + warn("leave flag ignored if not in tkinter mainloop", + TqdmWarning, stacklevel=2) + _close() + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + self._tk_n_var.set(self.n) + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + if '' in msg: + msg = "".join(re.split(r'\|?\|?', msg, maxsplit=1)) + self._tk_text_var.set(msg) + if not self._tk_dispatching: + self._tk_window.update() + + def set_description(self, desc=None, refresh=True): + self.set_description_str(desc, refresh) + + def set_description_str(self, desc=None, refresh=True): + self.desc = desc + if not self.disable: + self._tk_window.wm_title(desc) + if refresh and not self._tk_dispatching: + self._tk_window.update() + + def cancel(self): + """ + `cancel_callback()` followed by `close()` + when close/cancel buttons clicked. + """ + if self._cancel_callback is not None: + self._cancel_callback() + self.close() + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if hasattr(self, '_tk_pbar'): + if total is None: + self._tk_pbar.configure(maximum=100, mode="indeterminate") + else: + self._tk_pbar.configure(maximum=total, mode="determinate") + super().reset(total=total) + + @staticmethod + def _tk_dispatching_helper(): + """determine if Tkinter mainloop is dispatching events""" + codes = {tkinter.mainloop.__code__, tkinter.Misc.mainloop.__code__} + for frame in sys._current_frames().values(): + while frame: + if frame.f_code in codes: + return True + frame = frame.f_back + return False + + +def ttkrange(*args, **kwargs): + """Shortcut for `tqdm.tk.tqdm(range(*args), **kwargs)`.""" + return tqdm_tk(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_tk +trange = ttkrange diff --git a/venv/lib/python3.10/site-packages/tqdm/tqdm.1 b/venv/lib/python3.10/site-packages/tqdm/tqdm.1 new file mode 100644 index 0000000000000000000000000000000000000000..b90ab4b9ebdd183c98ee8ae0c7f0a65ac676e3b7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/tqdm.1 @@ -0,0 +1,314 @@ +.\" Automatically generated by Pandoc 1.19.2 +.\" +.TH "TQDM" "1" "2015\-2021" "tqdm User Manuals" "" +.hy +.SH NAME +.PP +tqdm \- fast, extensible progress bar for Python and CLI +.SH SYNOPSIS +.PP +tqdm [\f[I]options\f[]] +.SH DESCRIPTION +.PP +See . +Can be used as a pipe: +.IP +.nf +\f[C] +$\ #\ count\ lines\ of\ code +$\ cat\ *.py\ |\ tqdm\ |\ wc\ \-l +327it\ [00:00,\ 981773.38it/s] +327 + +$\ #\ find\ all\ files +$\ find\ .\ \-name\ "*.py"\ |\ tqdm\ |\ wc\ \-l +432it\ [00:00,\ 833842.30it/s] +432 + +#\ ...\ and\ more\ info +$\ find\ .\ \-name\ \[aq]*.py\[aq]\ \-exec\ wc\ \-l\ \\{}\ \\;\ \\ +\ \ |\ tqdm\ \-\-total\ 432\ \-\-unit\ files\ \-\-desc\ counting\ \\ +\ \ |\ awk\ \[aq]{\ sum\ +=\ $1\ };\ END\ {\ print\ sum\ }\[aq] +counting:\ 100%|█████████|\ 432/432\ [00:00<00:00,\ 794361.83files/s] +131998 +\f[] +.fi +.SH OPTIONS +.TP +.B \-h, \-\-help +Print this help and exit. +.RS +.RE +.TP +.B \-v, \-\-version +Print version and exit. +.RS +.RE +.TP +.B \-\-desc=\f[I]desc\f[] +str, optional. +Prefix for the progressbar. +.RS +.RE +.TP +.B \-\-total=\f[I]total\f[] +int or float, optional. +The number of expected iterations. +If unspecified, len(iterable) is used if possible. +If float("inf") or as a last resort, only basic progress statistics are +displayed (no ETA, no progressbar). +If \f[C]gui\f[] is True and this parameter needs subsequent updating, +specify an initial arbitrary large positive number, e.g. +9e9. +.RS +.RE +.TP +.B \-\-leave +bool, optional. +If [default: True], keeps all traces of the progressbar upon termination +of iteration. +If \f[C]None\f[], will leave only if \f[C]position\f[] is \f[C]0\f[]. +.RS +.RE +.TP +.B \-\-ncols=\f[I]ncols\f[] +int, optional. +The width of the entire output message. +If specified, dynamically resizes the progressbar to stay within this +bound. +If unspecified, attempts to use environment width. +The fallback is a meter width of 10 and no limit for the counter and +statistics. +If 0, will not print any meter (only stats). +.RS +.RE +.TP +.B \-\-mininterval=\f[I]mininterval\f[] +float, optional. +Minimum progress display update interval [default: 0.1] seconds. +.RS +.RE +.TP +.B \-\-maxinterval=\f[I]maxinterval\f[] +float, optional. +Maximum progress display update interval [default: 10] seconds. +Automatically adjusts \f[C]miniters\f[] to correspond to +\f[C]mininterval\f[] after long display update lag. +Only works if \f[C]dynamic_miniters\f[] or monitor thread is enabled. +.RS +.RE +.TP +.B \-\-miniters=\f[I]miniters\f[] +int or float, optional. +Minimum progress display update interval, in iterations. +If 0 and \f[C]dynamic_miniters\f[], will automatically adjust to equal +\f[C]mininterval\f[] (more CPU efficient, good for tight loops). +If > 0, will skip display of specified number of iterations. +Tweak this and \f[C]mininterval\f[] to get very efficient loops. +If your progress is erratic with both fast and slow iterations (network, +skipping items, etc) you should set miniters=1. +.RS +.RE +.TP +.B \-\-ascii=\f[I]ascii\f[] +bool or str, optional. +If unspecified or False, use unicode (smooth blocks) to fill the meter. +The fallback is to use ASCII characters " 123456789#". +.RS +.RE +.TP +.B \-\-disable +bool, optional. +Whether to disable the entire progressbar wrapper [default: False]. +If set to None, disable on non\-TTY. +.RS +.RE +.TP +.B \-\-unit=\f[I]unit\f[] +str, optional. +String that will be used to define the unit of each iteration [default: +it]. +.RS +.RE +.TP +.B \-\-unit\-scale=\f[I]unit_scale\f[] +bool or int or float, optional. +If 1 or True, the number of iterations will be reduced/scaled +automatically and a metric prefix following the International System of +Units standard will be added (kilo, mega, etc.) [default: False]. +If any other non\-zero number, will scale \f[C]total\f[] and \f[C]n\f[]. +.RS +.RE +.TP +.B \-\-dynamic\-ncols +bool, optional. +If set, constantly alters \f[C]ncols\f[] and \f[C]nrows\f[] to the +environment (allowing for window resizes) [default: False]. +.RS +.RE +.TP +.B \-\-smoothing=\f[I]smoothing\f[] +float, optional. +Exponential moving average smoothing factor for speed estimates (ignored +in GUI mode). +Ranges from 0 (average speed) to 1 (current/instantaneous speed) +[default: 0.3]. +.RS +.RE +.TP +.B \-\-bar\-format=\f[I]bar_format\f[] +str, optional. +Specify a custom bar string formatting. +May impact performance. +[default: \[aq]{l_bar}{bar}{r_bar}\[aq]], where l_bar=\[aq]{desc}: +{percentage:3.0f}%|\[aq] and r_bar=\[aq]| {n_fmt}/{total_fmt} +[{elapsed}<{remaining}, \[aq] \[aq]{rate_fmt}{postfix}]\[aq] Possible +vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, percentage, +elapsed, elapsed_s, ncols, nrows, desc, unit, rate, rate_fmt, +rate_noinv, rate_noinv_fmt, rate_inv, rate_inv_fmt, postfix, +unit_divisor, remaining, remaining_s, eta. +Note that a trailing ": " is automatically removed after {desc} if the +latter is empty. +.RS +.RE +.TP +.B \-\-initial=\f[I]initial\f[] +int or float, optional. +The initial counter value. +Useful when restarting a progress bar [default: 0]. +If using float, consider specifying \f[C]{n:.3f}\f[] or similar in +\f[C]bar_format\f[], or specifying \f[C]unit_scale\f[]. +.RS +.RE +.TP +.B \-\-position=\f[I]position\f[] +int, optional. +Specify the line offset to print this bar (starting from 0) Automatic if +unspecified. +Useful to manage multiple bars at once (eg, from threads). +.RS +.RE +.TP +.B \-\-postfix=\f[I]postfix\f[] +dict or *, optional. +Specify additional stats to display at the end of the bar. +Calls \f[C]set_postfix(**postfix)\f[] if possible (dict). +.RS +.RE +.TP +.B \-\-unit\-divisor=\f[I]unit_divisor\f[] +float, optional. +[default: 1000], ignored unless \f[C]unit_scale\f[] is True. +.RS +.RE +.TP +.B \-\-write\-bytes +bool, optional. +Whether to write bytes. +If (default: False) will write unicode. +.RS +.RE +.TP +.B \-\-lock\-args=\f[I]lock_args\f[] +tuple, optional. +Passed to \f[C]refresh\f[] for intermediate output (initialisation, +iterating, and updating). +.RS +.RE +.TP +.B \-\-nrows=\f[I]nrows\f[] +int, optional. +The screen height. +If specified, hides nested bars outside this bound. +If unspecified, attempts to use environment height. +The fallback is 20. +.RS +.RE +.TP +.B \-\-colour=\f[I]colour\f[] +str, optional. +Bar colour (e.g. +\[aq]green\[aq], \[aq]#00ff00\[aq]). +.RS +.RE +.TP +.B \-\-delay=\f[I]delay\f[] +float, optional. +Don\[aq]t display until [default: 0] seconds have elapsed. +.RS +.RE +.TP +.B \-\-delim=\f[I]delim\f[] +chr, optional. +Delimiting character [default: \[aq]\\n\[aq]]. +Use \[aq]\\0\[aq] for null. +N.B.: on Windows systems, Python converts \[aq]\\n\[aq] to +\[aq]\\r\\n\[aq]. +.RS +.RE +.TP +.B \-\-buf\-size=\f[I]buf_size\f[] +int, optional. +String buffer size in bytes [default: 256] used when \f[C]delim\f[] is +specified. +.RS +.RE +.TP +.B \-\-bytes +bool, optional. +If true, will count bytes, ignore \f[C]delim\f[], and default +\f[C]unit_scale\f[] to True, \f[C]unit_divisor\f[] to 1024, and +\f[C]unit\f[] to \[aq]B\[aq]. +.RS +.RE +.TP +.B \-\-tee +bool, optional. +If true, passes \f[C]stdin\f[] to both \f[C]stderr\f[] and +\f[C]stdout\f[]. +.RS +.RE +.TP +.B \-\-update +bool, optional. +If true, will treat input as newly elapsed iterations, i.e. +numbers to pass to \f[C]update()\f[]. +Note that this is slow (~2e5 it/s) since every input must be decoded as +a number. +.RS +.RE +.TP +.B \-\-update\-to +bool, optional. +If true, will treat input as total elapsed iterations, i.e. +numbers to assign to \f[C]self.n\f[]. +Note that this is slow (~2e5 it/s) since every input must be decoded as +a number. +.RS +.RE +.TP +.B \-\-null +bool, optional. +If true, will discard input (no stdout). +.RS +.RE +.TP +.B \-\-manpath=\f[I]manpath\f[] +str, optional. +Directory in which to install tqdm man pages. +.RS +.RE +.TP +.B \-\-comppath=\f[I]comppath\f[] +str, optional. +Directory in which to place tqdm completion. +.RS +.RE +.TP +.B \-\-log=\f[I]log\f[] +str, optional. +CRITICAL|FATAL|ERROR|WARN(ING)|[default: \[aq]INFO\[aq]]|DEBUG|NOTSET. +.RS +.RE +.SH AUTHORS +tqdm developers . diff --git a/venv/lib/python3.10/site-packages/tqdm/utils.py b/venv/lib/python3.10/site-packages/tqdm/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..af3ec7ded55daa98e1f268a3ee891e9a6bd72974 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/utils.py @@ -0,0 +1,399 @@ +""" +General helpers required for `tqdm.std`. +""" +import os +import re +import sys +from functools import partial, partialmethod, wraps +from inspect import signature +# TODO consider using wcswidth third-party package for 0-width characters +from unicodedata import east_asian_width +from warnings import warn +from weakref import proxy + +_range, _unich, _unicode, _basestring = range, chr, str, str +CUR_OS = sys.platform +IS_WIN = any(CUR_OS.startswith(i) for i in ['win32', 'cygwin']) +IS_NIX = any(CUR_OS.startswith(i) for i in ['aix', 'linux', 'darwin', 'freebsd']) +RE_ANSI = re.compile(r"\x1b\[[;\d]*[A-Za-z]") + +try: + if IS_WIN: + import colorama + else: + raise ImportError +except ImportError: + colorama = None +else: + try: + colorama.init(strip=False) + except TypeError: + colorama.init() + + +def envwrap(prefix, types=None, is_method=False): + """ + Override parameter defaults via `os.environ[prefix + param_name]`. + Maps UPPER_CASE env vars map to lower_case param names. + camelCase isn't supported (because Windows ignores case). + + Precedence (highest first): + + - call (`foo(a=3)`) + - environ (`FOO_A=2`) + - signature (`def foo(a=1)`) + + Parameters + ---------- + prefix : str + Env var prefix, e.g. "FOO_" + types : dict, optional + Fallback mappings `{'param_name': type, ...}` if types cannot be + inferred from function signature. + Consider using `types=collections.defaultdict(lambda: ast.literal_eval)`. + is_method : bool, optional + Whether to use `functools.partialmethod`. If (default: False) use `functools.partial`. + + Examples + -------- + ``` + $ cat foo.py + from tqdm.utils import envwrap + @envwrap("FOO_") + def test(a=1, b=2, c=3): + print(f"received: a={a}, b={b}, c={c}") + + $ FOO_A=42 FOO_C=1337 python -c 'import foo; foo.test(c=99)' + received: a=42, b=2, c=99 + ``` + """ + if types is None: + types = {} + i = len(prefix) + env_overrides = {k[i:].lower(): v for k, v in os.environ.items() if k.startswith(prefix)} + part = partialmethod if is_method else partial + + def wrap(func): + params = signature(func).parameters + # ignore unknown env vars + overrides = {k: v for k, v in env_overrides.items() if k in params} + # infer overrides' `type`s + for k in overrides: + param = params[k] + if param.annotation is not param.empty: # typehints + for typ in getattr(param.annotation, '__args__', (param.annotation,)): + try: + overrides[k] = typ(overrides[k]) + except Exception: + pass + else: + break + elif param.default is not None: # type of default value + overrides[k] = type(param.default)(overrides[k]) + else: + try: # `types` fallback + overrides[k] = types[k](overrides[k]) + except KeyError: # keep unconverted (`str`) + pass + return part(func, **overrides) + return wrap + + +class FormatReplace(object): + """ + >>> a = FormatReplace('something') + >>> f"{a:5d}" + 'something' + """ # NOQA: P102 + def __init__(self, replace=''): + self.replace = replace + self.format_called = 0 + + def __format__(self, _): + self.format_called += 1 + return self.replace + + +class Comparable(object): + """Assumes child has self._comparable attr/@property""" + def __lt__(self, other): + return self._comparable < other._comparable + + def __le__(self, other): + return (self < other) or (self == other) + + def __eq__(self, other): + return self._comparable == other._comparable + + def __ne__(self, other): + return not self == other + + def __gt__(self, other): + return not self <= other + + def __ge__(self, other): + return not self < other + + +class ObjectWrapper(object): + def __getattr__(self, name): + return getattr(self._wrapped, name) + + def __setattr__(self, name, value): + return setattr(self._wrapped, name, value) + + def wrapper_getattr(self, name): + """Actual `self.getattr` rather than self._wrapped.getattr""" + try: + return object.__getattr__(self, name) + except AttributeError: # py2 + return getattr(self, name) + + def wrapper_setattr(self, name, value): + """Actual `self.setattr` rather than self._wrapped.setattr""" + return object.__setattr__(self, name, value) + + def __init__(self, wrapped): + """ + Thin wrapper around a given object + """ + self.wrapper_setattr('_wrapped', wrapped) + + +class SimpleTextIOWrapper(ObjectWrapper): + """ + Change only `.write()` of the wrapped object by encoding the passed + value and passing the result to the wrapped object's `.write()` method. + """ + # pylint: disable=too-few-public-methods + def __init__(self, wrapped, encoding): + super().__init__(wrapped) + self.wrapper_setattr('encoding', encoding) + + def write(self, s): + """ + Encode `s` and pass to the wrapped object's `.write()` method. + """ + return self._wrapped.write(s.encode(self.wrapper_getattr('encoding'))) + + def __eq__(self, other): + return self._wrapped == getattr(other, '_wrapped', other) + + +class DisableOnWriteError(ObjectWrapper): + """ + Disable the given `tqdm_instance` upon `write()` or `flush()` errors. + """ + @staticmethod + def disable_on_exception(tqdm_instance, func): + """ + Quietly set `tqdm_instance.miniters=inf` if `func` raises `errno=5`. + """ + tqdm_instance = proxy(tqdm_instance) + + def inner(*args, **kwargs): + try: + return func(*args, **kwargs) + except OSError as e: + if e.errno != 5: + raise + try: + tqdm_instance.miniters = float('inf') + except ReferenceError: + pass + except ValueError as e: + if 'closed' not in str(e): + raise + try: + tqdm_instance.miniters = float('inf') + except ReferenceError: + pass + return inner + + def __init__(self, wrapped, tqdm_instance): + super().__init__(wrapped) + if hasattr(wrapped, 'write'): + self.wrapper_setattr( + 'write', self.disable_on_exception(tqdm_instance, wrapped.write)) + if hasattr(wrapped, 'flush'): + self.wrapper_setattr( + 'flush', self.disable_on_exception(tqdm_instance, wrapped.flush)) + + def __eq__(self, other): + return self._wrapped == getattr(other, '_wrapped', other) + + +class CallbackIOWrapper(ObjectWrapper): + def __init__(self, callback, stream, method="read"): + """ + Wrap a given `file`-like object's `read()` or `write()` to report + lengths to the given `callback` + """ + super().__init__(stream) + func = getattr(stream, method) + if method == "write": + @wraps(func) + def write(data, *args, **kwargs): + res = func(data, *args, **kwargs) + callback(len(data)) + return res + self.wrapper_setattr('write', write) + elif method == "read": + @wraps(func) + def read(*args, **kwargs): + data = func(*args, **kwargs) + callback(len(data)) + return data + self.wrapper_setattr('read', read) + else: + raise KeyError("Can only wrap read/write methods") + + +def _is_utf(encoding): + try: + u'\u2588\u2589'.encode(encoding) + except UnicodeEncodeError: + return False + except Exception: + try: + return encoding.lower().startswith('utf-') or ('U8' == encoding) + except Exception: + return False + else: + return True + + +def _supports_unicode(fp): + try: + return _is_utf(fp.encoding) + except AttributeError: + return False + + +def _is_ascii(s): + if isinstance(s, str): + for c in s: + if ord(c) > 255: + return False + return True + return _supports_unicode(s) + + +def _screen_shape_wrapper(): # pragma: no cover + """ + Return a function which returns console dimensions (width, height). + Supported: linux, osx, windows, cygwin. + """ + _screen_shape = None + if IS_WIN: + _screen_shape = _screen_shape_windows + if _screen_shape is None: + _screen_shape = _screen_shape_tput + if IS_NIX: + _screen_shape = _screen_shape_linux + return _screen_shape + + +def _screen_shape_windows(fp): # pragma: no cover + try: + import struct + from ctypes import create_string_buffer, windll + from sys import stdin, stdout + + io_handle = -12 # assume stderr + if fp == stdin: + io_handle = -10 + elif fp == stdout: + io_handle = -11 + + h = windll.kernel32.GetStdHandle(io_handle) + csbi = create_string_buffer(22) + res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) + if res: + (_bufx, _bufy, _curx, _cury, _wattr, left, top, right, bottom, + _maxx, _maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) + return right - left, bottom - top # +1 + except Exception: # nosec + pass + return None, None + + +def _screen_shape_tput(*_): # pragma: no cover + """cygwin xterm (windows)""" + try: + import shlex + from subprocess import check_call # nosec + return [int(check_call(shlex.split('tput ' + i))) - 1 + for i in ('cols', 'lines')] + except Exception: # nosec + pass + return None, None + + +def _screen_shape_linux(fp): # pragma: no cover + + try: + from array import array + from fcntl import ioctl + from termios import TIOCGWINSZ + except ImportError: + return None, None + else: + try: + rows, cols = array('h', ioctl(fp, TIOCGWINSZ, '\0' * 8))[:2] + return cols, rows + except Exception: + try: + return [int(os.environ[i]) - 1 for i in ("COLUMNS", "LINES")] + except (KeyError, ValueError): + return None, None + + +def _environ_cols_wrapper(): # pragma: no cover + """ + Return a function which returns console width. + Supported: linux, osx, windows, cygwin. + """ + warn("Use `_screen_shape_wrapper()(file)[0]` instead of" + " `_environ_cols_wrapper()(file)`", DeprecationWarning, stacklevel=2) + shape = _screen_shape_wrapper() + if not shape: + return None + + @wraps(shape) + def inner(fp): + return shape(fp)[0] + + return inner + + +def _term_move_up(): # pragma: no cover + return '' if (os.name == 'nt') and (colorama is None) else '\x1b[A' + + +def _text_width(s): + return sum(2 if east_asian_width(ch) in 'FW' else 1 for ch in str(s)) + + +def disp_len(data): + """ + Returns the real on-screen length of a string which may contain + ANSI control codes and wide chars. + """ + return _text_width(RE_ANSI.sub('', data)) + + +def disp_trim(data, length): + """ + Trim a string which may contain ANSI control characters. + """ + if len(data) == disp_len(data): + return data[:length] + + ansi_present = bool(RE_ANSI.search(data)) + while disp_len(data) > length: # carefully delete one char at a time + data = data[:-1] + if ansi_present and bool(RE_ANSI.search(data)): + # assume ANSI reset is required + return data if data.endswith("\033[0m") else data + "\033[0m" + return data diff --git a/venv/lib/python3.10/site-packages/tqdm/version.py b/venv/lib/python3.10/site-packages/tqdm/version.py new file mode 100644 index 0000000000000000000000000000000000000000..11cbaea79d1f4f46f9ae4bea542d7c66ded96e34 --- /dev/null +++ b/venv/lib/python3.10/site-packages/tqdm/version.py @@ -0,0 +1,9 @@ +"""`tqdm` version detector. Precedence: installed dist, git, 'UNKNOWN'.""" +try: + from ._dist_ver import __version__ +except ImportError: + try: + from setuptools_scm import get_version + __version__ = get_version(root='..', relative_to=__file__) + except (ImportError, LookupError): + __version__ = "UNKNOWN" diff --git a/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/METADATA b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..9556b218e19af99a382a55cd6e95e8634748d8e3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/METADATA @@ -0,0 +1,2478 @@ +Metadata-Version: 2.4 +Name: yarl +Version: 1.22.0 +Summary: Yet another URL library +Home-page: https://github.com/aio-libs/yarl +Author: Andrew Svetlov +Author-email: andrew.svetlov@gmail.com +Maintainer: aiohttp team +Maintainer-email: team@aiohttp.org +License: Apache-2.0 +Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org +Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org +Project-URL: CI: GitHub Workflows, https://github.com/aio-libs/yarl/actions?query=branch:master +Project-URL: Code of Conduct, https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/yarl +Project-URL: Docs: Changelog, https://yarl.aio-libs.org/en/latest/changes/ +Project-URL: Docs: RTD, https://yarl.aio-libs.org +Project-URL: GitHub: issues, https://github.com/aio-libs/yarl/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/yarl +Keywords: cython,cext,yarl +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.9 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: NOTICE +Requires-Dist: idna>=2.0 +Requires-Dist: multidict>=4.0 +Requires-Dist: propcache>=0.2.1 +Dynamic: license-file + +yarl +==== + +The module provides handy URL class for URL parsing and changing. + +.. image:: https://github.com/aio-libs/yarl/workflows/CI/badge.svg + :target: https://github.com/aio-libs/yarl/actions?query=workflow%3ACI + :align: right + +.. image:: https://codecov.io/gh/aio-libs/yarl/graph/badge.svg?flag=pytest + :target: https://app.codecov.io/gh/aio-libs/yarl?flags[]=pytest + :alt: Codecov coverage for the pytest-driven measurements + +.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json + :target: https://codspeed.io/aio-libs/yarl + +.. image:: https://badge.fury.io/py/yarl.svg + :target: https://badge.fury.io/py/yarl + +.. image:: https://readthedocs.org/projects/yarl/badge/?version=latest + :target: https://yarl.aio-libs.org + +.. image:: https://img.shields.io/pypi/pyversions/yarl.svg + :target: https://pypi.python.org/pypi/yarl + +.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs:matrix.org + :alt: Matrix Room — #aio-libs:matrix.org + +.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs-space:matrix.org + :alt: Matrix Space — #aio-libs-space:matrix.org + + +Introduction +------------ + +Url is constructed from ``str``: + +.. code-block:: pycon + + >>> from yarl import URL + >>> url = URL('https://www.python.org/~guido?arg=1#frag') + >>> url + URL('https://www.python.org/~guido?arg=1#frag') + +All url parts: *scheme*, *user*, *password*, *host*, *port*, *path*, +*query* and *fragment* are accessible by properties: + +.. code-block:: pycon + + >>> url.scheme + 'https' + >>> url.host + 'www.python.org' + >>> url.path + '/~guido' + >>> url.query_string + 'arg=1' + >>> url.query + + >>> url.fragment + 'frag' + +All url manipulations produce a new url object: + +.. code-block:: pycon + + >>> url = URL('https://www.python.org') + >>> url / 'foo' / 'bar' + URL('https://www.python.org/foo/bar') + >>> url / 'foo' % {'bar': 'baz'} + URL('https://www.python.org/foo?bar=baz') + +Strings passed to constructor and modification methods are +automatically encoded giving canonical representation as result: + +.. code-block:: pycon + + >>> url = URL('https://www.python.org/шлях') + >>> url + URL('https://www.python.org/%D1%88%D0%BB%D1%8F%D1%85') + +Regular properties are *percent-decoded*, use ``raw_`` versions for +getting *encoded* strings: + +.. code-block:: pycon + + >>> url.path + '/шлях' + + >>> url.raw_path + '/%D1%88%D0%BB%D1%8F%D1%85' + +Human readable representation of URL is available as ``.human_repr()``: + +.. code-block:: pycon + + >>> url.human_repr() + 'https://www.python.org/шлях' + +For full documentation please read https://yarl.aio-libs.org. + + +Installation +------------ + +:: + + $ pip install yarl + +The library is Python 3 only! + +PyPI contains binary wheels for Linux, Windows and MacOS. If you want to install +``yarl`` on another operating system where wheels are not provided, +the tarball will be used to compile the library from +the source code. It requires a C compiler and and Python headers installed. + +To skip the compilation you must explicitly opt-in by using a PEP 517 +configuration setting ``pure-python``, or setting the ``YARL_NO_EXTENSIONS`` +environment variable to a non-empty value, e.g.: + +.. code-block:: console + + $ pip install yarl --config-settings=pure-python=false + +Please note that the pure-Python (uncompiled) version is much slower. However, +PyPy always uses a pure-Python implementation, and, as such, it is unaffected +by this variable. + +Dependencies +------------ + +YARL requires multidict_ and propcache_ libraries. + + +API documentation +------------------ + +The documentation is located at https://yarl.aio-libs.org. + + +Why isn't boolean supported by the URL query API? +------------------------------------------------- + +There is no standard for boolean representation of boolean values. + +Some systems prefer ``true``/``false``, others like ``yes``/``no``, ``on``/``off``, +``Y``/``N``, ``1``/``0``, etc. + +``yarl`` cannot make an unambiguous decision on how to serialize ``bool`` values because +it is specific to how the end-user's application is built and would be different for +different apps. The library doesn't accept booleans in the API; a user should convert +bools into strings using own preferred translation protocol. + + +Comparison with other URL libraries +------------------------------------ + +* furl (https://pypi.python.org/pypi/furl) + + The library has rich functionality but the ``furl`` object is mutable. + + I'm afraid to pass this object into foreign code: who knows if the + code will modify my url in a terrible way while I just want to send URL + with handy helpers for accessing URL properties. + + ``furl`` has other non-obvious tricky things but the main objection + is mutability. + +* URLObject (https://pypi.python.org/pypi/URLObject) + + URLObject is immutable, that's pretty good. + + Every URL change generates a new URL object. + + But the library doesn't do any decode/encode transformations leaving the + end user to cope with these gory details. + + +Source code +----------- + +The project is hosted on GitHub_ + +Please file an issue on the `bug tracker +`_ if you have found a bug +or have some suggestion in order to improve the library. + +Discussion list +--------------- + +*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs + +Feel free to post your questions and ideas here. + + +Authors and License +------------------- + +The ``yarl`` package is written by Andrew Svetlov. + +It's *Apache 2* licensed and freely available. + + +.. _GitHub: https://github.com/aio-libs/yarl + +.. _multidict: https://github.com/aio-libs/multidict + +.. _propcache: https://github.com/aio-libs/propcache + +========= +Changelog +========= + +.. + You should *NOT* be adding new change log entries to this file, this + file is managed by towncrier. You *may* edit previous change logs to + fix problems like typo corrections or such. + To add a new change log entry, please see + https://pip.pypa.io/en/latest/development/#adding-a-news-entry + we named the news folder "changes". + + WARNING: Don't drop the next directive! + +.. towncrier release notes start + +1.22.0 +====== + +*(2025-10-05)* + + +Features +-------- + +- Added arm64 Windows wheel builds + -- by `@finnagin `__. + + *Related issues and pull requests on GitHub:* + `#1516 `__. + + +---- + + +1.21.0 +====== + +*(2025-10-05)* + + +Contributor-facing changes +-------------------------- + +- The ``reusable-cibuildwheel.yml`` workflow has been refactored to + be more generic and ``ci-cd.yml`` now holds all the configuration + toggles -- by `@webknjaz `__. + + *Related issues and pull requests on GitHub:* + `#1535 `__. + +- When building wheels, the source distribution is now passed directly + to the ``cibuildwheel`` invocation -- by `@webknjaz `__. + + *Related issues and pull requests on GitHub:* + `#1536 `__. + +- Added CI for Python 3.14 -- by `@kumaraditya303 `__. + + *Related issues and pull requests on GitHub:* + `#1560 `__. + + +---- + + +1.20.1 +====== + +*(2025-06-09)* + + +Bug fixes +--------- + +- Started raising a ``ValueError`` exception raised for corrupted + IPv6 URL values. + + These fixes the issue where exception ``IndexError`` was + leaking from the internal code because of not being handled and + transformed into a user-facing error. The problem was happening + under the following conditions: empty IPv6 URL, brackets in + reverse order. + + -- by `@MaelPic `__. + + *Related issues and pull requests on GitHub:* + `#1512 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Updated to use Cython 3.1 universally across the build path -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#1514 `__. + +- Made Cython line tracing opt-in via the ``with-cython-tracing`` build config setting -- by `@bdraco `__. + + Previously, line tracing was enabled by default in ``pyproject.toml``, which caused build issues for some users and made wheels nearly twice as slow. + Now line tracing is only enabled when explicitly requested via ``pip install . --config-setting=with-cython-tracing=true`` or by setting the ``YARL_CYTHON_TRACING`` environment variable. + + *Related issues and pull requests on GitHub:* + `#1521 `__. + + +---- + + +1.20.0 +====== + +*(2025-04-16)* + + +Features +-------- + +- Implemented support for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#1456 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Started building wheels for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#1456 `__. + + +---- + + +1.19.0 +====== + +*(2025-04-05)* + + +Bug fixes +--------- + +- Fixed entire name being re-encoded when using ``yarl.URL.with_suffix()`` -- by `@NTFSvolume `__. + + *Related issues and pull requests on GitHub:* + `#1468 `__. + + +Features +-------- + +- Started building armv7l wheels for manylinux -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1495 `__. + + +Contributor-facing changes +-------------------------- + +- GitHub Actions CI/CD is now configured to manage caching pip-ecosystem + dependencies using `re-actors/cache-python-deps`_ -- an action by + `@webknjaz `__ that takes into account ABI stability and the exact + version of Python runtime. + + .. _`re-actors/cache-python-deps`: + https://github.com/marketplace/actions/cache-python-deps + + *Related issues and pull requests on GitHub:* + `#1471 `__. + +- Increased minimum `propcache`_ version to 0.2.1 to fix failing tests -- by `@bdraco `__. + + .. _`propcache`: + https://github.com/aio-libs/propcache + + *Related issues and pull requests on GitHub:* + `#1479 `__. + +- Added all hidden folders to pytest's ``norecursedirs`` to prevent it + from trying to collect tests there -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#1480 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved accuracy of type annotations -- by `@Dreamsorcerer `__. + + *Related issues and pull requests on GitHub:* + `#1484 `__. + +- Improved performance of parsing query strings -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1493 `__, `#1497 `__. + +- Improved performance of the C unquoter -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1496 `__, `#1498 `__. + + +---- + + +1.18.3 +====== + +*(2024-12-01)* + + +Bug fixes +--------- + +- Fixed uppercase ASCII hosts being rejected by ``URL.build()()`` and ``yarl.URL.with_host()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#954 `__, `#1442 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performances of multiple path properties on cache miss -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1443 `__. + + +---- + + +1.18.2 +====== + +*(2024-11-29)* + + +No significant changes. + + +---- + + +1.18.1 +====== + +*(2024-11-29)* + + +Miscellaneous internal changes +------------------------------ + +- Improved cache performance when ``~yarl.URL`` objects are constructed from ``yarl.URL.build()`` with ``encoded=True`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1432 `__. + +- Improved cache performance for operations that produce a new ``~yarl.URL`` object -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1434 `__, `#1436 `__. + + +---- + + +1.18.0 +====== + +*(2024-11-21)* + + +Features +-------- + +- Added ``keep_query`` and ``keep_fragment`` flags in the ``yarl.URL.with_path()``, ``yarl.URL.with_name()`` and ``yarl.URL.with_suffix()`` methods, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path -- by `@paul-nameless `__. + + *Related issues and pull requests on GitHub:* + `#111 `__, `#1421 `__. + + +Contributor-facing changes +-------------------------- + +- Started running downstream ``aiohttp`` tests in CI -- by `@Cycloctane `__. + + *Related issues and pull requests on GitHub:* + `#1415 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of converting ``~yarl.URL`` to a string -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1422 `__. + + +---- + + +1.17.2 +====== + +*(2024-11-17)* + + +Bug fixes +--------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by `@ajsanchezsanz `__ and + `@markgreene74 `__. + + *Related issues and pull requests on GitHub:* + `#1411 `__, `#1412 `__. + +- Fixed a bug causing ``~yarl.URL.port`` to return the default port when the given port was zero + -- by `@gmacon `__. + + *Related issues and pull requests on GitHub:* + `#1413 `__. + + +Features +-------- + +- Make error messages include details of incorrect type when ``port`` is not int in ``yarl.URL.build()``. + -- by `@Cycloctane `__. + + *Related issues and pull requests on GitHub:* + `#1414 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by `@ajsanchezsanz `__ and + `@markgreene74 `__. + + *Related issues and pull requests on GitHub:* + `#1411 `__, `#1412 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of the ``yarl.URL.joinpath()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1418 `__. + + +---- + + +1.17.1 +====== + +*(2024-10-30)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of many ``~yarl.URL`` methods -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1396 `__, `#1397 `__, `#1398 `__. + +- Improved performance of passing a `dict` or `str` to ``yarl.URL.extend_query()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1401 `__. + + +---- + + +1.17.0 +====== + +*(2024-10-28)* + + +Features +-------- + +- Added ``~yarl.URL.host_port_subcomponent`` which returns the ``3986#section-3.2.2`` host and ``3986#section-3.2.3`` port subcomponent -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1375 `__. + + +---- + + +1.16.0 +====== + +*(2024-10-21)* + + +Bug fixes +--------- + +- Fixed blocking I/O to load Python code when creating a new ``~yarl.URL`` with non-ascii characters in the network location part -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1342 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Migrated to using a single cache for encoding hosts -- by `@bdraco `__. + + Passing ``ip_address_size`` and ``host_validate_size`` to ``yarl.cache_configure()`` is deprecated in favor of the new ``encode_host_size`` parameter and will be removed in a future release. For backwards compatibility, the old parameters affect the ``encode_host`` cache size. + + *Related issues and pull requests on GitHub:* + `#1348 `__, `#1357 `__, `#1363 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of constructing ``~yarl.URL`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1336 `__. + +- Improved performance of calling ``yarl.URL.build()`` and constructing unencoded ``~yarl.URL`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1345 `__. + +- Reworked the internal encoding cache to improve performance on cache hit -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1369 `__. + + +---- + + +1.15.5 +====== + +*(2024-10-18)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of the ``yarl.URL.joinpath()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1304 `__. + +- Improved performance of the ``yarl.URL.extend_query()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1305 `__. + +- Improved performance of the ``yarl.URL.origin()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1306 `__. + +- Improved performance of the ``yarl.URL.with_path()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1307 `__. + +- Improved performance of the ``yarl.URL.with_query()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1308 `__, `#1328 `__. + +- Improved performance of the ``yarl.URL.update_query()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1309 `__, `#1327 `__. + +- Improved performance of the ``yarl.URL.join()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1313 `__. + +- Improved performance of ``~yarl.URL`` equality checks -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1315 `__. + +- Improved performance of ``~yarl.URL`` methods that modify the network location -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1316 `__. + +- Improved performance of the ``yarl.URL.with_fragment()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1317 `__. + +- Improved performance of calculating the hash of ``~yarl.URL`` objects -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1318 `__. + +- Improved performance of the ``yarl.URL.relative()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1319 `__. + +- Improved performance of the ``yarl.URL.with_name()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1320 `__. + +- Improved performance of ``~yarl.URL.parent`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1321 `__. + +- Improved performance of the ``yarl.URL.with_scheme()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1322 `__. + + +---- + + +1.15.4 +====== + +*(2024-10-16)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of the quoter when all characters are safe -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1288 `__. + +- Improved performance of unquoting strings -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1292 `__, `#1293 `__. + +- Improved performance of calling ``yarl.URL.build()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1297 `__. + + +---- + + +1.15.3 +====== + +*(2024-10-15)* + + +Bug fixes +--------- + +- Fixed ``yarl.URL.build()`` failing to validate paths must start with a ``/`` when passing ``authority`` -- by `@bdraco `__. + + The validation only worked correctly when passing ``host``. + + *Related issues and pull requests on GitHub:* + `#1265 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Removed support for Python 3.8 as it has reached end of life -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1203 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of constructing ``~yarl.URL`` when the net location is only the host -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1271 `__. + + +---- + + +1.15.2 +====== + +*(2024-10-13)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of converting ``~yarl.URL`` to a string -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1234 `__. + +- Improved performance of ``yarl.URL.joinpath()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1248 `__, `#1250 `__. + +- Improved performance of constructing query strings from ``~multidict.MultiDict`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1256 `__. + +- Improved performance of constructing query strings with ``int`` values -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1259 `__. + + +---- + + +1.15.1 +====== + +*(2024-10-12)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of calling ``yarl.URL.build()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1222 `__. + +- Improved performance of all ``~yarl.URL`` methods that create new ``~yarl.URL`` objects -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1226 `__. + +- Improved performance of ``~yarl.URL`` methods that modify the network location -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1229 `__. + + +---- + + +1.15.0 +====== + +*(2024-10-11)* + + +Bug fixes +--------- + +- Fixed validation with ``yarl.URL.with_scheme()`` when passed scheme is not lowercase -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1189 `__. + + +Features +-------- + +- Started building ``armv7l`` wheels -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1204 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of constructing unencoded ``~yarl.URL`` objects -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1188 `__. + +- Added a cache for parsing hosts to reduce overhead of encoding ``~yarl.URL`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1190 `__. + +- Improved performance of constructing query strings from ``~collections.abc.Mapping`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1193 `__. + +- Improved performance of converting ``~yarl.URL`` objects to strings -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1198 `__. + + +---- + + +1.14.0 +====== + +*(2024-10-08)* + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Switched to using the ``propcache`` package for property caching + -- by `@bdraco `__. + + The ``propcache`` package is derived from the property caching + code in ``yarl`` and has been broken out to avoid maintaining it for multiple + projects. + + *Related issues and pull requests on GitHub:* + `#1169 `__. + + +Contributor-facing changes +-------------------------- + +- Started testing with Hypothesis -- by `@webknjaz `__ and `@bdraco `__. + + Special thanks to `@Zac-HD `__ for helping us get started with this framework. + + *Related issues and pull requests on GitHub:* + `#860 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of ``yarl.URL.is_default_port()`` when no explicit port is set -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1168 `__. + +- Improved performance of converting ``~yarl.URL`` to a string when no explicit port is set -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1170 `__. + +- Improved performance of the ``yarl.URL.origin()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1175 `__. + +- Improved performance of encoding hosts -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1176 `__. + + +---- + + +1.13.1 +====== + +*(2024-09-27)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of calling ``yarl.URL.build()`` with ``authority`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1163 `__. + + +---- + + +1.13.0 +====== + +*(2024-09-26)* + + +Bug fixes +--------- + +- Started rejecting ASCII hostnames with invalid characters. For host strings that + look like authority strings, the exception message includes advice on what to do + instead -- by `@mjpieters `__. + + *Related issues and pull requests on GitHub:* + `#880 `__, `#954 `__. + +- Fixed IPv6 addresses missing brackets when the ``~yarl.URL`` was converted to a string -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1157 `__, `#1158 `__. + + +Features +-------- + +- Added ``~yarl.URL.host_subcomponent`` which returns the ``3986#section-3.2.2`` host subcomponent -- by `@bdraco `__. + + The only current practical difference between ``~yarl.URL.raw_host`` and ``~yarl.URL.host_subcomponent`` is that IPv6 addresses are returned bracketed. + + *Related issues and pull requests on GitHub:* + `#1159 `__. + + +---- + + +1.12.1 +====== + +*(2024-09-23)* + + +No significant changes. + + +---- + + +1.12.0 +====== + +*(2024-09-23)* + + +Features +-------- + +- Added ``~yarl.URL.path_safe`` to be able to fetch the path without ``%2F`` and ``%25`` decoded -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1150 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Restore decoding ``%2F`` (``/``) in ``URL.path`` -- by `@bdraco `__. + + This change restored the behavior before `#1057 `__. + + *Related issues and pull requests on GitHub:* + `#1151 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of processing paths -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1143 `__. + + +---- + + +1.11.1 +====== + +*(2024-09-09)* + + +Bug fixes +--------- + +- Allowed scheme replacement for relative URLs if the scheme does not require a host -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#280 `__, `#1138 `__. + +- Allowed empty host for URL schemes other than the special schemes listed in the WHATWG URL spec -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1136 `__. + + +Features +-------- + +- Loosened restriction on integers as query string values to allow classes that implement ``__int__`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1139 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of normalizing paths -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1137 `__. + + +---- + + +1.11.0 +====== + +*(2024-09-08)* + + +Features +-------- + +- Added ``URL.extend_query()()`` method, which can be used to extend parameters without replacing same named keys -- by `@bdraco `__. + + This method was primarily added to replace the inefficient hand rolled method currently used in ``aiohttp``. + + *Related issues and pull requests on GitHub:* + `#1128 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of the Cython ``cached_property`` implementation -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1122 `__. + +- Simplified computing ports by removing unnecessary code -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1123 `__. + +- Improved performance of encoding non IPv6 hosts -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1125 `__. + +- Improved performance of ``URL.build()()`` when the path, query string, or fragment is an empty string -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1126 `__. + +- Improved performance of the ``URL.update_query()()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1130 `__. + +- Improved performance of processing query string changes when arguments are ``str`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1131 `__. + + +---- + + +1.10.0 +====== + +*(2024-09-06)* + + +Bug fixes +--------- + +- Fixed joining a path when the existing path was empty -- by `@bdraco `__. + + A regression in ``URL.join()()`` was introduced in `#1082 `__. + + *Related issues and pull requests on GitHub:* + `#1118 `__. + + +Features +-------- + +- Added ``URL.without_query_params()()`` method, to drop some parameters from query string -- by `@hongquan `__. + + *Related issues and pull requests on GitHub:* + `#774 `__, `#898 `__, `#1010 `__. + +- The previously protected types ``_SimpleQuery``, ``_QueryVariable``, and ``_Query`` are now available for use externally as ``SimpleQuery``, ``QueryVariable``, and ``Query`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1050 `__, `#1113 `__. + + +Contributor-facing changes +-------------------------- + +- Replaced all ``~typing.Optional`` with ``~typing.Union`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1095 `__. + + +Miscellaneous internal changes +------------------------------ + +- Significantly improved performance of parsing the network location -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1112 `__. + +- Added internal types to the cache to prevent future refactoring errors -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1117 `__. + + +---- + + +1.9.11 +====== + +*(2024-09-04)* + + +Bug fixes +--------- + +- Fixed a ``TypeError`` with ``MultiDictProxy`` and Python 3.8 -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1084 `__, `#1105 `__, `#1107 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of encoding hosts -- by `@bdraco `__. + + Previously, the library would unconditionally try to parse a host as an IP Address. The library now avoids trying to parse a host as an IP Address if the string is not in one of the formats described in ``3986#section-3.2.2``. + + *Related issues and pull requests on GitHub:* + `#1104 `__. + + +---- + + +1.9.10 +====== + +*(2024-09-04)* + + +Bug fixes +--------- + +- ``URL.join()()`` has been changed to match + ``3986`` and align with + ``/ operation()`` and ``URL.joinpath()()`` + when joining URLs with empty segments. + Previously ``urllib.parse.urljoin`` was used, + which has known issues with empty segments + (`python/cpython#84774 `_). + + Due to the semantics of ``URL.join()()``, joining an + URL with scheme requires making it relative, prefixing with ``./``. + + .. code-block:: pycon + + >>> URL("https://web.archive.org/web/").join(URL("./https://github.com/aio-libs/yarl")) + URL('https://web.archive.org/web/https://github.com/aio-libs/yarl') + + + Empty segments are honored in the base as well as the joined part. + + .. code-block:: pycon + + >>> URL("https://web.archive.org/web/https://").join(URL("github.com/aio-libs/yarl")) + URL('https://web.archive.org/web/https://github.com/aio-libs/yarl') + + + + -- by `@commonism `__ + + This change initially appeared in 1.9.5 but was reverted in 1.9.6 to resolve a problem with query string handling. + + *Related issues and pull requests on GitHub:* + `#1039 `__, `#1082 `__. + + +Features +-------- + +- Added ``~yarl.URL.absolute`` which is now preferred over ``URL.is_absolute()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1100 `__. + + +---- + + +1.9.9 +===== + +*(2024-09-04)* + + +Bug fixes +--------- + +- Added missing type on ``~yarl.URL.port`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1097 `__. + + +---- + + +1.9.8 +===== + +*(2024-09-03)* + + +Features +-------- + +- Covered the ``~yarl.URL`` object with types -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1084 `__. + +- Cache parsing of IP Addresses when encoding hosts -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1086 `__. + + +Contributor-facing changes +-------------------------- + +- Covered the ``~yarl.URL`` object with types -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1084 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of handling ports -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1081 `__. + + +---- + + +1.9.7 +===== + +*(2024-09-01)* + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Removed support ``3986#section-3.2.3`` port normalization when the scheme is not one of ``http``, ``https``, ``wss``, or ``ws`` -- by `@bdraco `__. + + Support for port normalization was recently added in `#1033 `__ and contained code that would do blocking I/O if the scheme was not one of the four listed above. The code has been removed because this library is intended to be safe for usage with ``asyncio``. + + *Related issues and pull requests on GitHub:* + `#1076 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of property caching -- by `@bdraco `__. + + The ``reify`` implementation from ``aiohttp`` was adapted to replace the internal ``cached_property`` implementation. + + *Related issues and pull requests on GitHub:* + `#1070 `__. + + +---- + + +1.9.6 +===== + +*(2024-08-30)* + + +Bug fixes +--------- + +- Reverted ``3986`` compatible ``URL.join()()`` honoring empty segments which was introduced in `#1039 `__. + + This change introduced a regression handling query string parameters with joined URLs. The change was reverted to maintain compatibility with the previous behavior. + + *Related issues and pull requests on GitHub:* + `#1067 `__. + + +---- + + +1.9.5 +===== + +*(2024-08-30)* + + +Bug fixes +--------- + +- Joining URLs with empty segments has been changed + to match ``3986``. + + Previously empty segments would be removed from path, + breaking use-cases such as + + .. code-block:: python + + URL("https://web.archive.org/web/") / "https://github.com/" + + Now ``/ operation()`` and ``URL.joinpath()()`` + keep empty segments, but do not introduce new empty segments. + e.g. + + .. code-block:: python + + URL("https://example.org/") / "" + + does not introduce an empty segment. + + -- by `@commonism `__ and `@youtux `__ + + *Related issues and pull requests on GitHub:* + `#1026 `__. + +- The default protocol ports of well-known URI schemes are now taken into account + during the normalization of the URL string representation in accordance with + ``3986#section-3.2.3``. + + Specified ports are removed from the ``str`` representation of a ``~yarl.URL`` + if the port matches the scheme's default port -- by `@commonism `__. + + *Related issues and pull requests on GitHub:* + `#1033 `__. + +- ``URL.join()()`` has been changed to match + ``3986`` and align with + ``/ operation()`` and ``URL.joinpath()()`` + when joining URLs with empty segments. + Previously ``urllib.parse.urljoin`` was used, + which has known issues with empty segments + (`python/cpython#84774 `_). + + Due to the semantics of ``URL.join()()``, joining an + URL with scheme requires making it relative, prefixing with ``./``. + + .. code-block:: pycon + + >>> URL("https://web.archive.org/web/").join(URL("./https://github.com/aio-libs/yarl")) + URL('https://web.archive.org/web/https://github.com/aio-libs/yarl') + + + Empty segments are honored in the base as well as the joined part. + + .. code-block:: pycon + + >>> URL("https://web.archive.org/web/https://").join(URL("github.com/aio-libs/yarl")) + URL('https://web.archive.org/web/https://github.com/aio-libs/yarl') + + + + -- by `@commonism `__ + + *Related issues and pull requests on GitHub:* + `#1039 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Stopped decoding ``%2F`` (``/``) in ``URL.path``, as this could lead to code incorrectly treating it as a path separator + -- by `@Dreamsorcerer `__. + + *Related issues and pull requests on GitHub:* + `#1057 `__. + +- Dropped support for Python 3.7 -- by `@Dreamsorcerer `__. + + *Related issues and pull requests on GitHub:* + `#1016 `__. + + +Improved documentation +---------------------- + +- On the ``Contributing docs`` page, + a link to the ``Towncrier philosophy`` has been fixed. + + *Related issues and pull requests on GitHub:* + `#981 `__. + +- The pre-existing ``/ magic method()`` + has been documented in the API reference -- by `@commonism `__. + + *Related issues and pull requests on GitHub:* + `#1026 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- A flaw in the logic for copying the project directory into a + temporary folder that led to infinite recursion when ``TMPDIR`` + was set to a project subdirectory path. This was happening in Fedora + and its downstream due to the use of `pyproject-rpm-macros + `__. It was + only reproducible with ``pip wheel`` and was not affecting the + ``pyproject-build`` users. + + -- by `@hroncok `__ and `@webknjaz `__ + + *Related issues and pull requests on GitHub:* + `#992 `__, `#1014 `__. + +- Support Python 3.13 and publish non-free-threaded wheels + + *Related issues and pull requests on GitHub:* + `#1054 `__. + + +Contributor-facing changes +-------------------------- + +- The CI/CD setup has been updated to test ``arm64`` wheels + under macOS 14, except for Python 3.7 that is unsupported + in that environment -- by `@webknjaz `__. + + *Related issues and pull requests on GitHub:* + `#1015 `__. + +- Removed unused type ignores and casts -- by `@hauntsaninja `__. + + *Related issues and pull requests on GitHub:* + `#1031 `__. + + +Miscellaneous internal changes +------------------------------ + +- ``port``, ``scheme``, and ``raw_host`` are now ``cached_property`` -- by `@bdraco `__. + + ``aiohttp`` accesses these properties quite often, which cause ``urllib`` to build the ``_hostinfo`` property every time. ``port``, ``scheme``, and ``raw_host`` are now cached properties, which will improve performance. + + *Related issues and pull requests on GitHub:* + `#1044 `__, `#1058 `__. + + +---- + + +1.9.4 (2023-12-06) +================== + +Bug fixes +--------- + +- Started raising ``TypeError`` when a string value is passed into + ``yarl.URL.build()`` as the ``port`` argument -- by `@commonism `__. + + Previously the empty string as port would create malformed URLs when rendered as string representations. (`#883 `__) + + +Packaging updates and notes for downstreams +------------------------------------------- + +- The leading ``--`` has been dropped from the `PEP 517 `__ in-tree build + backend config setting names. ``--pure-python`` is now just ``pure-python`` + -- by `@webknjaz `__. + + The usage now looks as follows: + + .. code-block:: console + + $ python -m build \ + --config-setting=pure-python=true \ + --config-setting=with-cython-tracing=true + + (`#963 `__) + + +Contributor-facing changes +-------------------------- + +- A step-by-step ``Release Guide`` guide has + been added, describing how to release *yarl* -- by `@webknjaz `__. + + This is primarily targeting maintainers. (`#960 `__) +- Coverage collection has been implemented for the Cython modules + -- by `@webknjaz `__. + + It will also be reported to Codecov from any non-release CI jobs. + + To measure coverage in a development environment, *yarl* can be + installed in editable mode: + + .. code-block:: console + + $ python -Im pip install -e . + + Editable install produces C-files required for the Cython coverage + plugin to map the measurements back to the PYX-files. + + `#961 `__ + +- It is now possible to request line tracing in Cython builds using the + ``with-cython-tracing`` `PEP 517 `__ config setting + -- `@webknjaz `__. + + This can be used in CI and development environment to measure coverage + on Cython modules, but is not normally useful to the end-users or + downstream packagers. + + Here's a usage example: + + .. code-block:: console + + $ python -Im pip install . --config-settings=with-cython-tracing=true + + For editable installs, this setting is on by default. Otherwise, it's + off unless requested explicitly. + + The following produces C-files required for the Cython coverage + plugin to map the measurements back to the PYX-files: + + .. code-block:: console + + $ python -Im pip install -e . + + Alternatively, the ``YARL_CYTHON_TRACING=1`` environment variable + can be set to do the same as the `PEP 517 `__ config setting. + + `#962 `__ + + +1.9.3 (2023-11-20) +================== + +Bug fixes +--------- + +- Stopped dropping trailing slashes in ``yarl.URL.joinpath()`` -- by `@gmacon `__. (`#862 `__, `#866 `__) +- Started accepting string subclasses in ``yarl.URL.__truediv__()`` operations (``URL / segment``) -- by `@mjpieters `__. (`#871 `__, `#884 `__) +- Fixed the human representation of URLs with square brackets in usernames and passwords -- by `@mjpieters `__. (`#876 `__, `#882 `__) +- Updated type hints to include ``URL.missing_port()``, ``URL.__bytes__()`` + and the ``encoding`` argument to ``yarl.URL.joinpath()`` + -- by `@mjpieters `__. (`#891 `__) + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Integrated Cython 3 to enable building *yarl* under Python 3.12 -- by `@mjpieters `__. (`#829 `__, `#881 `__) +- Declared modern ``setuptools.build_meta`` as the `PEP 517 `__ build + backend in ``pyproject.toml`` explicitly -- by `@webknjaz `__. (`#886 `__) +- Converted most of the packaging setup into a declarative ``setup.cfg`` + config -- by `@webknjaz `__. (`#890 `__) +- The packaging is replaced from an old-fashioned ``setup.py`` to an + in-tree `PEP 517 `__ build backend -- by `@webknjaz `__. + + Whenever the end-users or downstream packagers need to build ``yarl`` from + source (a Git checkout or an sdist), they may pass a ``config_settings`` + flag ``--pure-python``. If this flag is not set, a C-extension will be built + and included into the distribution. + + Here is how this can be done with ``pip``: + + .. code-block:: console + + $ python -m pip install . --config-settings=--pure-python=false + + This will also work with ``-e | --editable``. + + The same can be achieved via ``pypa/build``: + + .. code-block:: console + + $ python -m build --config-setting=--pure-python=false + + Adding ``-w | --wheel`` can force ``pypa/build`` produce a wheel from source + directly, as opposed to building an ``sdist`` and then building from it. (`#893 `__) + + .. attention:: + + v1.9.3 was the only version using the ``--pure-python`` setting name. + Later versions dropped the ``--`` prefix, making it just ``pure-python``. + +- Declared Python 3.12 supported officially in the distribution package metadata + -- by `@edgarrmondragon `__. (`#942 `__) + + +Contributor-facing changes +-------------------------- + +- A regression test for no-host URLs was added per `#821 `__ + and ``3986`` -- by `@kenballus `__. (`#821 `__, `#822 `__) +- Started testing *yarl* against Python 3.12 in CI -- by `@mjpieters `__. (`#881 `__) +- All Python 3.12 jobs are now marked as required to pass in CI + -- by `@edgarrmondragon `__. (`#942 `__) +- MyST is now integrated in Sphinx -- by `@webknjaz `__. + + This allows the contributors to author new documents in Markdown + when they have difficulties with going straight RST. (`#953 `__) + + +1.9.2 (2023-04-25) +================== + +Bugfixes +-------- + +- Fix regression with ``yarl.URL.__truediv__()`` and absolute URLs with empty paths causing the raw path to lack the leading ``/``. + (`#854 `_) + + +1.9.1 (2023-04-21) +================== + +Bugfixes +-------- + +- Marked tests that fail on older Python patch releases (< 3.7.10, < 3.8.8 and < 3.9.2) as expected to fail due to missing a security fix for CVE-2021-23336. (`#850 `_) + + +1.9.0 (2023-04-19) +================== + +This release was never published to PyPI, due to issues with the build process. + +Features +-------- + +- Added ``URL.joinpath(*elements)``, to create a new URL appending multiple path elements. (`#704 `_) +- Made ``URL.__truediv__()()`` return ``NotImplemented`` if called with an + unsupported type — by `@michaeljpeters `__. + (`#832 `_) + + +Bugfixes +-------- + +- Path normalization for absolute URLs no longer raises a ValueError exception + when ``..`` segments would otherwise go beyond the URL path root. + (`#536 `_) +- Fixed an issue with update_query() not getting rid of the query when argument is None. (`#792 `_) +- Added some input restrictions on with_port() function to prevent invalid boolean inputs or out of valid port inputs; handled incorrect 0 port representation. (`#793 `_) +- Made ``yarl.URL.build()`` raise a ``TypeError`` if the ``host`` argument is ``None`` — by `@paulpapacz `__. (`#808 `_) +- Fixed an issue with ``update_query()`` getting rid of the query when the argument + is empty but not ``None``. (`#845 `_) + + +Misc +---- + +- `#220 `_ + + +1.8.2 (2022-12-03) +================== + +This is the first release that started shipping wheels for Python 3.11. + + +1.8.1 (2022-08-01) +================== + +Misc +---- + +- `#694 `_, `#699 `_, `#700 `_, `#701 `_, `#702 `_, `#703 `_, `#739 `_ + + +1.8.0 (2022-08-01) +================== + +Features +-------- + +- Added ``URL.raw_suffix``, ``URL.suffix``, ``URL.raw_suffixes``, ``URL.suffixes``, ``URL.with_suffix``. (`#613 `_) + + +Improved Documentation +---------------------- + +- Fixed broken internal references to ``yarl.URL.human_repr()``. + (`#665 `_) +- Fixed broken external references to ``multidict:index`` docs. (`#665 `_) + + +Deprecations and Removals +------------------------- + +- Dropped Python 3.6 support. (`#672 `_) + + +Misc +---- + +- `#646 `_, `#699 `_, `#701 `_ + + +1.7.2 (2021-11-01) +================== + +Bugfixes +-------- + +- Changed call in ``with_port()`` to stop reencoding parts of the URL that were already encoded. (`#623 `_) + + +1.7.1 (2021-10-07) +================== + +Bugfixes +-------- + +- Fix 1.7.0 build error + +1.7.0 (2021-10-06) +================== + +Features +-------- + +- Add ``__bytes__()`` magic method so that ``bytes(url)`` will work and use optimal ASCII encoding. + (`#582 `_) +- Started shipping platform-specific arm64 wheels for Apple Silicon. (`#622 `_) +- Started shipping platform-specific wheels with the ``musl`` tag targeting typical Alpine Linux runtimes. (`#622 `_) +- Added support for Python 3.10. (`#622 `_) + + +1.6.3 (2020-11-14) +================== + +Bugfixes +-------- + +- No longer loose characters when decoding incorrect percent-sequences (like ``%e2%82%f8``). All non-decodable percent-sequences are now preserved. + `#517 `_ +- Provide x86 Windows wheels. + `#535 `_ + + +---- + + +1.6.2 (2020-10-12) +================== + + +Bugfixes +-------- + +- Provide generated ``.c`` files in TarBall distribution. + `#530 `_ + +1.6.1 (2020-10-12) +================== + +Features +-------- + +- Provide wheels for ``aarch64``, ``i686``, ``ppc64le``, ``s390x`` architectures on + Linux as well as ``x86_64``. + `#507 `_ +- Provide wheels for Python 3.9. + `#526 `_ + +Bugfixes +-------- + +- ``human_repr()`` now always produces valid representation equivalent to the original URL (if the original URL is valid). + `#511 `_ +- Fixed requoting a single percent followed by a percent-encoded character in the Cython implementation. + `#514 `_ +- Fix ValueError when decoding ``%`` which is not followed by two hexadecimal digits. + `#516 `_ +- Fix decoding ``%`` followed by a space and hexadecimal digit. + `#520 `_ +- Fix annotation of ``with_query()``/``update_query()`` methods for ``key=[val1, val2]`` case. + `#528 `_ + +Removal +------- + +- Drop Python 3.5 support; Python 3.6 is the minimal supported Python version. + + +---- + + +1.6.0 (2020-09-23) +================== + +Features +-------- + +- Allow for int and float subclasses in query, while still denying bool. + `#492 `_ + + +Bugfixes +-------- + +- Do not requote arguments in ``URL.build()``, ``with_xxx()`` and in ``/`` operator. + `#502 `_ +- Keep IPv6 brackets in ``origin()``. + `#504 `_ + + +---- + + +1.5.1 (2020-08-01) +================== + +Bugfixes +-------- + +- Fix including relocated internal ``yarl._quoting_c`` C-extension into published PyPI dists. + `#485 `_ + + +Misc +---- + +- `#484 `_ + + +---- + + +1.5.0 (2020-07-26) +================== + +Features +-------- + +- Convert host to lowercase on URL building. + `#386 `_ +- Allow using ``mod`` operator (``%``) for updating query string (an alias for ``update_query()`` method). + `#435 `_ +- Allow use of sequences such as ``list`` and ``tuple`` in the values + of a mapping such as ``dict`` to represent that a key has many values:: + + url = URL("http://example.com") + assert url.with_query({"a": [1, 2]}) == URL("http://example.com/?a=1&a=2") + + `#443 `_ +- Support ``URL.build()`` with scheme and path (creates a relative URL). + `#464 `_ +- Cache slow IDNA encode/decode calls. + `#476 `_ +- Add ``@final`` / ``Final`` type hints + `#477 `_ +- Support URL authority/raw_authority properties and authority argument of ``URL.build()`` method. + `#478 `_ +- Hide the library implementation details, make the exposed public list very clean. + `#483 `_ + + +Bugfixes +-------- + +- Fix tests with newer Python (3.7.6, 3.8.1 and 3.9.0+). + `#409 `_ +- Fix a bug where query component, passed in a form of mapping or sequence, is unquoted in unexpected way. + `#426 `_ +- Hide ``Query`` and ``QueryVariable`` type aliases in ``__init__.pyi``, now they are prefixed with underscore. + `#431 `_ +- Keep IPv6 brackets after updating port/user/password. + `#451 `_ + + +---- + + +1.4.2 (2019-12-05) +================== + +Features +-------- + +- Workaround for missing ``str.isascii()`` in Python 3.6 + `#389 `_ + + +---- + + +1.4.1 (2019-11-29) +================== + +* Fix regression, make the library work on Python 3.5 and 3.6 again. + +1.4.0 (2019-11-29) +================== + +* Distinguish an empty password in URL from a password not provided at all (#262) + +* Fixed annotations for optional parameters of ``URL.build`` (#309) + +* Use None as default value of ``user`` parameter of ``URL.build`` (#309) + +* Enforce building C Accelerated modules when installing from source tarball, use + ``YARL_NO_EXTENSIONS`` environment variable for falling back to (slower) Pure Python + implementation (#329) + +* Drop Python 3.5 support + +* Fix quoting of plus in path by pure python version (#339) + +* Don't create a new URL if fragment is unchanged (#292) + +* Included in error message the path that produces starting slash forbidden error (#376) + +* Skip slow IDNA encoding for ASCII-only strings (#387) + + +1.3.0 (2018-12-11) +================== + +* Fix annotations for ``query`` parameter (#207) + +* An incoming query sequence can have int variables (the same as for + Mapping type) (#208) + +* Add ``URL.explicit_port`` property (#218) + +* Give a friendlier error when port can't be converted to int (#168) + +* ``bool(URL())`` now returns ``False`` (#272) + +1.2.6 (2018-06-14) +================== + +* Drop Python 3.4 trove classifier (#205) + +1.2.5 (2018-05-23) +================== + +* Fix annotations for ``build`` (#199) + +1.2.4 (2018-05-08) +================== + +* Fix annotations for ``cached_property`` (#195) + +1.2.3 (2018-05-03) +================== + +* Accept ``str`` subclasses in ``URL`` constructor (#190) + +1.2.2 (2018-05-01) +================== + +* Fix build + +1.2.1 (2018-04-30) +================== + +* Pin minimal required Python to 3.5.3 (#189) + +1.2.0 (2018-04-30) +================== + +* Forbid inheritance, replace ``__init__`` with ``__new__`` (#171) + +* Support PEP-561 (provide type hinting marker) (#182) + +1.1.1 (2018-02-17) +================== + +* Fix performance regression: don't encode empty ``netloc`` (#170) + +1.1.0 (2018-01-21) +================== + +* Make pure Python quoter consistent with Cython version (#162) + +1.0.0 (2018-01-15) +================== + +* Use fast path if quoted string does not need requoting (#154) + +* Speed up quoting/unquoting by ``_Quoter`` and ``_Unquoter`` classes (#155) + +* Drop ``yarl.quote`` and ``yarl.unquote`` public functions (#155) + +* Add custom string writer, reuse static buffer if available (#157) + Code is 50-80 times faster than Pure Python version (was 4-5 times faster) + +* Don't recode IP zone (#144) + +* Support ``encoded=True`` in ``yarl.URL.build()`` (#158) + +* Fix updating query with multiple keys (#160) + +0.18.0 (2018-01-10) +=================== + +* Fallback to IDNA 2003 if domain name is not IDNA 2008 compatible (#152) + +0.17.0 (2017-12-30) +=================== + +* Use IDNA 2008 for domain name processing (#149) + +0.16.0 (2017-12-07) +=================== + +* Fix raising ``TypeError`` by ``url.query_string()`` after + ``url.with_query({})`` (empty mapping) (#141) + +0.15.0 (2017-11-23) +=================== + +* Add ``raw_path_qs`` attribute (#137) + +0.14.2 (2017-11-14) +=================== + +* Restore ``strict`` parameter as no-op in ``quote`` / ``unquote`` + +0.14.1 (2017-11-13) +=================== + +* Restore ``strict`` parameter as no-op for sake of compatibility with + aiohttp 2.2 + +0.14.0 (2017-11-11) +=================== + +* Drop strict mode (#123) + +* Fix ``"ValueError: Unallowed PCT %"`` when there's a ``"%"`` in the URL (#124) + +0.13.0 (2017-10-01) +=================== + +* Document ``encoded`` parameter (#102) + +* Support relative URLs like ``'?key=value'`` (#100) + +* Unsafe encoding for QS fixed. Encode ``;`` character in value parameter (#104) + +* Process passwords without user names (#95) + +0.12.0 (2017-06-26) +=================== + +* Properly support paths without leading slash in ``URL.with_path()`` (#90) + +* Enable type annotation checks + +0.11.0 (2017-06-26) +=================== + +* Normalize path (#86) + +* Clear query and fragment parts in ``.with_path()`` (#85) + +0.10.3 (2017-06-13) +=================== + +* Prevent double URL arguments unquoting (#83) + +0.10.2 (2017-05-05) +=================== + +* Unexpected hash behavior (#75) + + +0.10.1 (2017-05-03) +=================== + +* Unexpected compare behavior (#73) + +* Do not quote or unquote + if not a query string. (#74) + + +0.10.0 (2017-03-14) +=================== + +* Added ``URL.build`` class method (#58) + +* Added ``path_qs`` attribute (#42) + + +0.9.8 (2017-02-16) +================== + +* Do not quote ``:`` in path + + +0.9.7 (2017-02-16) +================== + +* Load from pickle without _cache (#56) + +* Percent-encoded pluses in path variables become spaces (#59) + + +0.9.6 (2017-02-15) +================== + +* Revert backward incompatible change (BaseURL) + + +0.9.5 (2017-02-14) +================== + +* Fix BaseURL rich comparison support + + +0.9.4 (2017-02-14) +================== + +* Use BaseURL + + +0.9.3 (2017-02-14) +================== + +* Added BaseURL + + +0.9.2 (2017-02-08) +================== + +* Remove debug print + + +0.9.1 (2017-02-07) +================== + +* Do not lose tail chars (#45) + + +0.9.0 (2017-02-07) +================== + +* Allow to quote ``%`` in non strict mode (#21) + +* Incorrect parsing of query parameters with %3B (;) inside (#34) + +* Fix core dumps (#41) + +* ``tmpbuf`` - compiling error (#43) + +* Added ``URL.update_path()`` method + +* Added ``URL.update_query()`` method (#47) + + +0.8.1 (2016-12-03) +================== + +* Fix broken aiohttp: revert back ``quote`` / ``unquote``. + + +0.8.0 (2016-12-03) +================== + +* Support more verbose error messages in ``.with_query()`` (#24) + +* Don't percent-encode ``@`` and ``:`` in path (#32) + +* Don't expose ``yarl.quote`` and ``yarl.unquote``, these functions are + part of private API + +0.7.1 (2016-11-18) +================== + +* Accept not only ``str`` but all classes inherited from ``str`` also (#25) + +0.7.0 (2016-11-07) +================== + +* Accept ``int`` as value for ``.with_query()`` + +0.6.0 (2016-11-07) +================== + +* Explicitly use UTF8 encoding in ``setup.py`` (#20) +* Properly unquote non-UTF8 strings (#19) + +0.5.3 (2016-11-02) +================== + +* Don't use ``typing.NamedTuple`` fields but indexes on URL construction + +0.5.2 (2016-11-02) +================== + +* Inline ``_encode`` class method + +0.5.1 (2016-11-02) +================== + +* Make URL construction faster by removing extra classmethod calls + +0.5.0 (2016-11-02) +================== + +* Add Cython optimization for quoting/unquoting +* Provide binary wheels + +0.4.3 (2016-09-29) +================== + +* Fix typing stubs + +0.4.2 (2016-09-29) +================== + +* Expose ``quote()`` and ``unquote()`` as public API + +0.4.1 (2016-09-28) +================== + +* Support empty values in query (``'/path?arg'``) + +0.4.0 (2016-09-27) +================== + +* Introduce ``relative()`` (#16) + +0.3.2 (2016-09-27) +================== + +* Typo fixes #15 + +0.3.1 (2016-09-26) +================== + +* Support sequence of pairs as ``with_query()`` parameter + +0.3.0 (2016-09-26) +================== + +* Introduce ``is_default_port()`` + +0.2.1 (2016-09-26) +================== + +* Raise ValueError for URLs like 'http://:8080/' + +0.2.0 (2016-09-18) +================== + +* Avoid doubling slashes when joining paths (#13) + +* Appending path starting from slash is forbidden (#12) + +0.1.4 (2016-09-09) +================== + +* Add ``kwargs`` support for ``with_query()`` (#10) + +0.1.3 (2016-09-07) +================== + +* Document ``with_query()``, ``with_fragment()`` and ``origin()`` + +* Allow ``None`` for ``with_query()`` and ``with_fragment()`` + +0.1.2 (2016-09-07) +================== + +* Fix links, tune docs theme. + +0.1.1 (2016-09-06) +================== + +* Update README, old version used obsolete API + +0.1.0 (2016-09-06) +================== + +* The library was deeply refactored, bytes are gone away but all + accepted strings are encoded if needed. + +0.0.1 (2016-08-30) +================== + +* The first release. diff --git a/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/RECORD b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..49678a6d425f5d049c9079c6911441d89f46b6b6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/RECORD @@ -0,0 +1,27 @@ +yarl-1.22.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +yarl-1.22.0.dist-info/METADATA,sha256=gATDmMXVC53tnEFDwFcOd1skK5FM8kJNbY8GFkCRDLg,75118 +yarl-1.22.0.dist-info/RECORD,, +yarl-1.22.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +yarl-1.22.0.dist-info/WHEEL,sha256=DZl4yYurviXJJQsilHH1qAzCRDsdz--oNtDZ-hUrZUk,190 +yarl-1.22.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +yarl-1.22.0.dist-info/licenses/NOTICE,sha256=VtasbIEFwKUTBMIdsGDjYa-ajqCvmnXCOcKLXRNpODg,609 +yarl-1.22.0.dist-info/top_level.txt,sha256=vf3SJuQh-k7YtvsUrV_OPOrT9Kqn0COlk7IPYyhtGkQ,5 +yarl/__init__.py,sha256=woYZp7KGli7_1P_hR7ZU9ckEj6ho41smyP-PLfEL-lk,281 +yarl/__pycache__/__init__.cpython-310.pyc,, +yarl/__pycache__/_parse.cpython-310.pyc,, +yarl/__pycache__/_path.cpython-310.pyc,, +yarl/__pycache__/_query.cpython-310.pyc,, +yarl/__pycache__/_quoters.cpython-310.pyc,, +yarl/__pycache__/_quoting.cpython-310.pyc,, +yarl/__pycache__/_quoting_py.cpython-310.pyc,, +yarl/__pycache__/_url.cpython-310.pyc,, +yarl/_parse.py,sha256=gNt8zxVFGr95ufUQpSMiiZ9vDrvg4zq6MEtT3f6_8J0,7185 +yarl/_path.py,sha256=A0FJUylZyzmlT0a3UDOBbK-EzZXCAYuQQBvG9eAC9hs,1291 +yarl/_query.py,sha256=nwGAYewdOU8nt5YZNZxqQ4BGES82Y3Y6LanxqTjnZxw,4068 +yarl/_quoters.py,sha256=z-BzsXfLnJK-bd-HrGaoKGri9L3GpDv6vxFEtmu-uCM,1154 +yarl/_quoting.py,sha256=yKIqFTzFzWLVb08xy1DSxKNjFwo4f-oLlzxTuKwC57M,506 +yarl/_quoting_c.cpython-310-x86_64-linux-gnu.so,sha256=WpEIHZwpeReMVhc9UjAletXPO0Ka_zNck7mgRPVRPJ0,1031960 +yarl/_quoting_c.pyx,sha256=X40gvQSUB4l7nPKGeiS6pq2JreM36avLhVeBMxd5zmo,14297 +yarl/_quoting_py.py,sha256=7WD7IHhgaJiLZWoIewvB0JRUsbz9McmfZw5TnjlVs9o,6783 +yarl/_url.py,sha256=4K5gCdoQtVi9FmnQdssEqafdlJILKxSap8RNCBC4IGE,55608 +yarl/py.typed,sha256=ay5OMO475PlcZ_Fbun9maHW7Y6MBTk0UXL4ztHx3Iug,14 diff --git a/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/WHEEL b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..6d6bf82d678c683effaf22b612f4dea70dd9933d --- /dev/null +++ b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/WHEEL @@ -0,0 +1,7 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: false +Tag: cp310-cp310-manylinux_2_17_x86_64 +Tag: cp310-cp310-manylinux2014_x86_64 +Tag: cp310-cp310-manylinux_2_28_x86_64 + diff --git a/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/licenses/LICENSE b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/licenses/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/licenses/NOTICE b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/licenses/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..fa53b2b138df881c4c95239d0e4bede831b36ab5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/licenses/NOTICE @@ -0,0 +1,13 @@ + Copyright 2016-2021, Andrew Svetlov and aio-libs team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/top_level.txt b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..e93e8bddefb14a8a753f7ecab6b934fd899cd9e5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/top_level.txt @@ -0,0 +1 @@ +yarl